mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
83 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
f7528365b0 | ||
|
|
7baa1a345e | ||
|
|
acf7f4fd52 | ||
|
|
f43e8680b8 | ||
|
|
545a6c1b36 | ||
|
|
f01fd8c850 | ||
|
|
c9ec69b0b5 | ||
|
|
3640e2c5f0 | ||
|
|
b3659cb456 | ||
|
|
76284a2916 | ||
|
|
40b1e011bd | ||
|
|
e0bebecd59 | ||
|
|
8ac0cf6831 | ||
|
|
992163206d | ||
|
|
86dd5d8078 | ||
|
|
932aa9d052 | ||
|
|
5f7f5204ec | ||
|
|
a154d23637 | ||
|
|
ac2bb9d362 | ||
|
|
b918958bfa | ||
|
|
215df4ffa6 | ||
|
|
bb28bc5875 | ||
|
|
a82bc3f712 | ||
|
|
b3a507014b | ||
|
|
49c5e35a14 | ||
|
|
869ed33bd4 | ||
|
|
0c4a9be482 | ||
|
|
4410ec575a | ||
|
|
e3b92fc948 | ||
|
|
4ca4692a67 | ||
|
|
c1284d3c23 | ||
|
|
c8c0eadf72 | ||
|
|
f4bbc8abc7 | ||
|
|
a0f6ea57f8 | ||
|
|
88d21a07ac | ||
|
|
88e3a606a0 | ||
|
|
fff693c3f0 | ||
|
|
1e8d792d39 | ||
|
|
dfb149ac6a | ||
|
|
b5cfc92261 | ||
|
|
079f3e3868 | ||
|
|
15a6d58785 | ||
|
|
a404498f8a | ||
|
|
0d133e2df6 | ||
|
|
488b28bfd5 | ||
|
|
0fceb7b2e1 | ||
|
|
a79d1a98e7 | ||
|
|
43434fd445 | ||
|
|
492e523884 | ||
|
|
3d1fdb7a2b | ||
|
|
95a4bf0ec7 | ||
|
|
0d4f261e14 | ||
|
|
e96288b41b | ||
|
|
deda1abcf7 | ||
|
|
ee79d75483 | ||
|
|
0e9e445ddf | ||
|
|
e64720bcd9 | ||
|
|
6e27590b57 | ||
|
|
916c3c7a2a | ||
|
|
8a5fde8ceb | ||
|
|
f5bd9bde7f | ||
|
|
b987f61924 | ||
|
|
482b51a2f9 | ||
|
|
93f2078eda | ||
|
|
158ae11e61 | ||
|
|
d282388266 | ||
|
|
6ecdfcba38 | ||
|
|
88dc8a547e | ||
|
|
58e24b3c11 | ||
|
|
5f1b3a2284 | ||
|
|
31be2584f2 | ||
|
|
a2311e5128 | ||
|
|
e94d42187b | ||
|
|
2b99cc3f62 | ||
|
|
cb7f782893 | ||
|
|
d5a0f8a74b | ||
|
|
2ebd71df24 | ||
|
|
479203f47c | ||
|
|
022b9209d9 | ||
|
|
771c2c868f | ||
|
|
5285a728b1 | ||
|
|
41e6583920 | ||
|
|
cbb60b3a05 |
2
.gitignore
vendored
2
.gitignore
vendored
@@ -1,6 +1,6 @@
|
||||
.DS_Store
|
||||
Build
|
||||
Dist
|
||||
santa-*
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
Santa.xcodeproj/project.xcworkspace
|
||||
|
||||
@@ -2,10 +2,12 @@
|
||||
language: objective-c
|
||||
cache: cocoapods
|
||||
sudo: false
|
||||
osx_image: xcode7
|
||||
|
||||
before_install:
|
||||
- gem install activesupport
|
||||
- gem install cocoapods xcpretty
|
||||
- pod setup >/dev/null
|
||||
|
||||
script:
|
||||
- xcodebuild -workspace Santa.xcworkspace -scheme All -derivedDataPath build build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}
|
||||
|
||||
@@ -2,7 +2,5 @@
|
||||
> /var/log/santa.log format="[$((Time)(ISO8601Z.3))] $Message" mode=0644 rotate=seq compress file_max=25M all_max=100M uid=0 gid=0
|
||||
? [= Sender kernel] [S= Message santa-driver:] claim
|
||||
? [= Sender kernel] [S= Message santa-driver:] file /var/log/santa.log
|
||||
? [= Sender santad] claim
|
||||
? [= Sender santad] file /var/log/santa.log
|
||||
? [= Sender santactl] claim
|
||||
? [= Sender santactl] file /var/log/santa.log
|
||||
? [= Facility com.google.santa] claim
|
||||
? [= Facility com.google.santa] file /var/log/santa.log
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
@@ -19,5 +20,7 @@
|
||||
<true />
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -7,6 +7,7 @@
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
|
||||
34
Podfile
34
Podfile
@@ -2,22 +2,15 @@ platform :osx, "10.9"
|
||||
|
||||
inhibit_all_warnings!
|
||||
|
||||
target :Santa do
|
||||
def mol_pods
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
end
|
||||
|
||||
target :santactl do
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
pod 'FMDB'
|
||||
end
|
||||
|
||||
target :santad do
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
def fmdb_pod
|
||||
pod 'FMDB'
|
||||
|
||||
# This is necessary to get FMDB to not NSLog stuff.
|
||||
post_install do |installer|
|
||||
installer.pods_project.targets.each do |target|
|
||||
target.build_configurations.each do |config|
|
||||
@@ -32,9 +25,24 @@ target :santad do
|
||||
end
|
||||
end
|
||||
|
||||
target :Santa do
|
||||
mol_pods
|
||||
end
|
||||
|
||||
target :santad do
|
||||
mol_pods
|
||||
fmdb_pod
|
||||
end
|
||||
|
||||
target :santactl do
|
||||
mol_pods
|
||||
fmdb_pod
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
end
|
||||
|
||||
target :LogicTests do
|
||||
pod 'FMDB'
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
mol_pods
|
||||
fmdb_pod
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'OCMock'
|
||||
end
|
||||
|
||||
28
Podfile.lock
28
Podfile.lock
@@ -1,22 +1,28 @@
|
||||
PODS:
|
||||
- FMDB (2.6):
|
||||
- FMDB/standard (= 2.6)
|
||||
- FMDB/standard (2.6)
|
||||
- MOLCertificate (1.3)
|
||||
- MOLCodesignChecker (1.4):
|
||||
- FMDB (2.6.2):
|
||||
- FMDB/standard (= 2.6.2)
|
||||
- FMDB/standard (2.6.2)
|
||||
- MOLAuthenticatingURLSession (1.6):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- OCMock (3.2.2)
|
||||
- MOLCertificate (1.4)
|
||||
- MOLCodesignChecker (1.5):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- OCMock (3.3)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: c1968bab3ab0aed38f66cb778ae1e7fa9a652b6e
|
||||
MOLCertificate: a776221906b5a46dd1bd749d0682bef3ee68c1f5
|
||||
MOLCodesignChecker: 34e60cc6beadabfb4762b6e5087e12837774f85f
|
||||
OCMock: 18c9b7e67d4c2770e95bb77a9cc1ae0c91fe3835
|
||||
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
|
||||
MOLAuthenticatingURLSession: f956240458fb24b61e5607d735948dc9babfb4e3
|
||||
MOLCertificate: da0bfeb5fa968bb4ac284569fa3f7d5f8f7abe23
|
||||
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
|
||||
OCMock: d68685bde31f69cb61d518dcb39269080c78b5ed
|
||||
|
||||
COCOAPODS: 0.39.0
|
||||
PODFILE CHECKSUM: 3a8673334ffd78cdbd6576c85e6635248eb1b504
|
||||
|
||||
COCOAPODS: 1.0.0
|
||||
|
||||
11
README.md
11
README.md
@@ -68,7 +68,7 @@ hosts in whatever other ways you see fit.
|
||||
Get Help
|
||||
========
|
||||
|
||||
If you have questions or need help getting started, the
|
||||
If you have questions or need help getting started, the
|
||||
[santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is the
|
||||
best place to start.
|
||||
|
||||
@@ -108,6 +108,15 @@ option) if it would be useful to others.
|
||||
|
||||
* Tests: There aren't enough of them.
|
||||
|
||||
Screenshots
|
||||
===========
|
||||
|
||||
A tool like Santa doesn't really lend itself to screenshots, so here's a video instead.
|
||||
|
||||
<p align="center">
|
||||
<img src="https://zippy.gfycat.com/MadFatalAmphiuma.gif" alt="Santa Block Video" />
|
||||
</p>
|
||||
|
||||
Building
|
||||
========
|
||||
```sh
|
||||
|
||||
20
Rakefile
20
Rakefile
@@ -1,7 +1,6 @@
|
||||
WORKSPACE = 'Santa.xcworkspace'
|
||||
DEFAULT_SCHEME = 'All'
|
||||
OUTPUT_PATH = 'Build'
|
||||
DIST_PATH = 'Dist'
|
||||
BINARIES = ['Santa.app', 'santa-driver.kext']
|
||||
DSYMS = ['Santa.app.dSYM', 'santa-driver.kext.dSYM', 'santad.dSYM', 'santactl.dSYM']
|
||||
XCPRETTY_DEFAULTS = '-sc'
|
||||
@@ -56,7 +55,6 @@ desc "Clean"
|
||||
task :clean => :init do
|
||||
puts "Cleaning"
|
||||
FileUtils.rm_rf(OUTPUT_PATH)
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
end
|
||||
|
||||
# Build
|
||||
@@ -111,24 +109,26 @@ task :dist do
|
||||
Rake::Task['clean'].invoke()
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
dist_path = "santa-#{`defaults read #{xcodebuilddir}/Release/santa-driver.kext/Contents/Info.plist CFBundleVersion`.strip}"
|
||||
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/binaries")
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/conf")
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/dsym")
|
||||
FileUtils.rm_rf(dist_path)
|
||||
|
||||
FileUtils.mkdir_p("#{dist_path}/binaries")
|
||||
FileUtils.mkdir_p("#{dist_path}/conf")
|
||||
FileUtils.mkdir_p("#{dist_path}/dsym")
|
||||
|
||||
BINARIES.each do |x|
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{DIST_PATH}/binaries")
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/binaries")
|
||||
end
|
||||
|
||||
DSYMS.each do |x|
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{DIST_PATH}/dsym")
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{dist_path}/dsym")
|
||||
end
|
||||
|
||||
|
||||
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{DIST_PATH}/conf")}
|
||||
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{dist_path}/conf")}
|
||||
|
||||
puts "Distribution folder created"
|
||||
puts "Distribution folder #{dist_path} created"
|
||||
end
|
||||
|
||||
# Tests
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -26,7 +26,8 @@
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0700"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
|
||||
@@ -1,8 +1,9 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="8191" systemVersion="15C50" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="9532" systemVersion="15E65" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="8191"/>
|
||||
<development version="6300" identifier="xcode"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
|
||||
@@ -14,64 +15,95 @@
|
||||
</customObject>
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<window title="Santa Blocked Execution" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" showsToolbarButton="NO" visibleAtLaunch="NO" animationBehavior="none" id="9Bq-yh-54f" customClass="SNTMessageWindow">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="497" height="381"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="1440" height="877"/>
|
||||
<rect key="contentRect" x="167" y="107" width="497" height="439"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1417"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="497" height="381"/>
|
||||
<rect key="frame" x="0.0" y="0.0" width="497" height="439"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
|
||||
<rect key="frame" x="-6" y="411" width="37" height="32"/>
|
||||
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="vl5-A8-O0H"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
|
||||
<rect key="frame" x="207" y="311" width="83" height="40"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
|
||||
<font key="font" size="34" name="HelveticaNeue-UltraLight"/>
|
||||
<color key="textColor" red="0.18696189413265307" green="0.18696189413265307" blue="0.18696189413265307" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<rect key="frame" x="206" y="368" width="85" height="41"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" refusesFirstResponder="YES" sendsActionOnEndEditing="YES" title="Santa" id="7YA-iB-Zma">
|
||||
<font key="font" metaFont="systemUltraLight" size="34"/>
|
||||
<color key="textColor" red="0.20000000000000001" green="0.20000000000000001" blue="0.20000000000000001" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="z5y-RR-IEH"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR">
|
||||
<rect key="frame" x="22" y="264" width="454" height="17"/>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="22" y="329" width="454" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
|
||||
<textFieldCell key="cell" selectable="YES" refusesFirstResponder="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" alignment="center" title="A message to the user goes here..." allowsEditingTextAttributes="YES" id="5tH-bG-UJA">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
|
||||
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="VC7-bE-uHc"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ">
|
||||
<rect key="frame" x="165" y="192" width="294" height="17"/>
|
||||
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
|
||||
<rect key="frame" x="146" y="132" width="1" height="167"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="E7T-9h-ofr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
|
||||
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
|
||||
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<font key="titleFont" metaFont="system"/>
|
||||
</box>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC" userLabel="Label: Application">
|
||||
<rect key="frame" x="8" y="282" width="120" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.filePath" id="qfp-sR-Nmu"/>
|
||||
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr">
|
||||
<rect key="frame" x="165" y="217" width="294" height="17"/>
|
||||
<textField toolTip="Application Name" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="qgf-Jf-cJr" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="165" y="282" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
|
||||
@@ -81,31 +113,68 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
|
||||
<rect key="frame" x="165" y="142" width="219" height="17"/>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="257" width="120" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
|
||||
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="SHA-256" id="X4W-9e-eIu">
|
||||
<font key="font" metaFont="fixedUser" size="11"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Filename" id="KgY-X1-ESG">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="YNz-ka-cBi" userLabel="Label: Path">
|
||||
<rect key="frame" x="8" y="232" width="120" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="116" id="3wU-P0-gAC"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="adC-be-Beh">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="165" y="257" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Name" id="E7T-9h-ofr">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
|
||||
<binding destination="-2" name="value" keyPath="self.event.filePath.lastPathComponent" id="bOu-gv-1Vh"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w">
|
||||
<rect key="frame" x="165" y="167" width="294" height="17"/>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5" userLabel="Label: Publisher">
|
||||
<rect key="frame" x="8" y="207" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Publisher" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w" userLabel="Publisher" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="165" y="207" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="Dem-wH-KHm"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" selectable="YES" allowsUndo="NO" sendsActionOnEndEditing="YES" title="Code signing information" placeholderString="" id="ztA-La-XgT">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="deviceWhite"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.publisherInfo" id="CEI-Cu-7pC">
|
||||
@@ -115,93 +184,18 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
|
||||
<rect key="frame" x="8" y="92" width="120" height="17"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J">
|
||||
<rect key="frame" x="8" y="117" width="120" height="17"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
|
||||
<rect key="frame" x="8" y="167" width="120" height="17"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H">
|
||||
<rect key="frame" x="8" y="192" width="120" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="KgY-X1-ESG">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y">
|
||||
<rect key="frame" x="8" y="142" width="120" height="17"/>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0">
|
||||
<rect key="frame" x="165" y="92" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
|
||||
<rect key="frame" x="146" y="92" width="1" height="142"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<color key="borderColor" white="0.0" alpha="0.17999999999999999" colorSpace="calibratedWhite"/>
|
||||
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<font key="titleFont" metaFont="system"/>
|
||||
</box>
|
||||
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs">
|
||||
<rect key="frame" x="40" y="168" width="15" height="15"/>
|
||||
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs Button">
|
||||
<rect key="frame" x="40" y="208" width="15" height="15"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="bevel" bezelStyle="regularSquare" image="NSInfo" imagePosition="overlaps" alignment="center" refusesFirstResponder="YES" imageScaling="proportionallyDown" inset="2" id="R72-Qy-Xbb">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
|
||||
@@ -212,55 +206,54 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
|
||||
<rect key="frame" x="256" y="33" width="110" height="25"/>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y" userLabel="Label: Identifier">
|
||||
<rect key="frame" x="8" y="182" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
|
||||
<rect key="frame" x="165" y="182" width="219" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
<constraint firstAttribute="width" constant="215" id="4hh-R2-86s"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
|
||||
</buttonCell>
|
||||
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="SHA-256" id="X4W-9e-eIu">
|
||||
<font key="font" metaFont="fixedUser" size="11"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
|
||||
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
|
||||
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="132" y="33" width="112" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
|
||||
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o">
|
||||
<rect key="frame" x="165" y="117" width="294" height="17"/>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J" userLabel="Label: Parent">
|
||||
<rect key="frame" x="8" y="157" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Parent Process" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o" userLabel="Parent" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="165" y="157" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="displayPatternValue1" keyPath="self.event.parentName" id="Lce-TO-q9V">
|
||||
@@ -275,96 +268,163 @@ DQ
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="pDa-fA-vnC">
|
||||
<rect key="frame" x="8" y="217" width="120" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="116" id="8mA-zi-Ev7"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Application" id="Hy7-WF-6xW">
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL" userLabel="Label: User">
|
||||
<rect key="frame" x="8" y="132" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
<userDefinedRuntimeAttributes>
|
||||
<userDefinedRuntimeAttribute type="boolean" keyPath="accessibilityElement" value="NO"/>
|
||||
</userDefinedRuntimeAttributes>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField toolTip="Executing User" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="165" y="132" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" selectable="YES" sendsActionOnEndEditing="YES" title="Executing User" id="HRT-Be-ePf">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="hidden" keyPath="self.event.fileBundleName" id="r2Q-hh-Uy5">
|
||||
<binding destination="-2" name="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNil</string>
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<button focusRingType="none" verticalHuggingPriority="750" fixedFrame="YES" translatesAutoresizingMaskIntoConstraints="NO" id="kiB-jZ-69S">
|
||||
<rect key="frame" x="-6" y="353" width="37" height="32"/>
|
||||
<animations/>
|
||||
<buttonCell key="cell" type="push" title="Hidden Button" alternateTitle="This button exists so neither of the other two buttons is pre-selected when the dialog opens." bezelStyle="rounded" alignment="center" borderStyle="border" focusRingType="none" transparent="YES" imageScaling="proportionallyDown" inset="2" id="XGa-Sl-F4t">
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="132" y="33" width="112" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Open Event..." bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="vl5-A8-O0H"/>
|
||||
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
|
||||
<outlet property="nextKeyView" destination="BbV-3h-mmL" id="Xkz-va-iGc"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL" userLabel="Dismiss Button">
|
||||
<rect key="frame" x="256" y="33" width="110" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="110" id="6Uh-Bd-N64"/>
|
||||
<constraint firstAttribute="height" constant="22" id="GH6-nw-6rD"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="XR6-Xa-gP4">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" shift="YES"/>
|
||||
</buttonCell>
|
||||
<accessibility description="Dismiss Dialog"/>
|
||||
<connections>
|
||||
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
|
||||
<outlet property="nextKeyView" destination="7ua-5a-uSd" id="4KL-Z2-1op"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField toolTip="Binary Path" verticalHuggingPriority="900" horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="bDE-Tl-UHg" customClass="SNTAccessibleTextField">
|
||||
<rect key="frame" x="165" y="232" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="p1W-f9-KBX"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Binary Path" id="H1b-Ui-CYo">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" red="0.40000000000000002" green="0.40000000000000002" blue="0.40000000000000002" alpha="1" colorSpace="calibratedRGB"/>
|
||||
<color key="backgroundColor" name="textBackgroundColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.filePath" id="Sry-KY-HDb"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<button translatesAutoresizingMaskIntoConstraints="NO" id="5D8-GP-a4l">
|
||||
<rect key="frame" x="91" y="80" width="315" height="29"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="height" constant="25" id="KvD-X6-CsO"/>
|
||||
</constraints>
|
||||
<buttonCell key="cell" type="check" title="Prevent future notifications for this application for a day" bezelStyle="regularSquare" imagePosition="left" alignment="center" inset="2" id="R5Y-Uc-rEP">
|
||||
<behavior key="behavior" changeContents="YES" doesNotDimImage="YES" lightByContents="YES"/>
|
||||
<font key="font" metaFont="smallSystem"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.silenceFutureNotifications" id="tEb-2A-sht"/>
|
||||
</connections>
|
||||
</button>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="0AD-PS-5V1"/>
|
||||
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" priority="500" constant="193" id="2uo-Cm-Tfp"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
|
||||
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="-116" id="6Q5-Oo-1cI"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
|
||||
<constraint firstItem="pDa-fA-vnC" firstAttribute="centerY" secondItem="qgf-Jf-cJr" secondAttribute="centerY" id="AKX-pe-hEX"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="ALv-0v-szi"/>
|
||||
<constraint firstItem="f1p-GL-O3o" firstAttribute="top" secondItem="PXc-xv-A28" secondAttribute="bottom" constant="8" id="E6D-7P-17g"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="FPe-Rd-G4n"/>
|
||||
<constraint firstItem="cJf-k6-OxS" firstAttribute="centerY" secondItem="C3G-wL-u7w" secondAttribute="centerY" id="FdL-ZZ-Vbe"/>
|
||||
<constraint firstItem="t8c-Fx-e5h" firstAttribute="top" secondItem="Iwq-Lx-rLv" secondAttribute="top" constant="30" id="FuB-GX-0jg"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="G0I-O2-S91"/>
|
||||
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="cJf-k6-OxS" secondAttribute="trailing" constant="-45" id="GD2-Ka-deo"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="GT2-tO-2td"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="centerY" secondItem="oFj-ol-xpL" secondAttribute="centerY" id="GXI-pT-FM1"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="top" secondItem="pDa-fA-vnC" secondAttribute="top" id="Gd4-Nr-n5G"/>
|
||||
<constraint firstItem="qgf-Jf-cJr" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="Ht4-Lg-U5N"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="IwX-ja-ZIs"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" priority="500" id="JY4-N1-j8e"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="leading" priority="999" id="MVr-jY-GDj"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="750" constant="30" id="Nsl-zf-poH"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="KmX-kX-VCN"/>
|
||||
<constraint firstItem="5D8-GP-a4l" firstAttribute="centerX" secondItem="Iwq-Lx-rLv" secondAttribute="centerX" id="LkH-F4-Ncm"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" priority="950" constant="30" id="Nsl-zf-poH"/>
|
||||
<constraint firstItem="YNz-ka-cBi" firstAttribute="centerY" secondItem="bDE-Tl-UHg" secondAttribute="centerY" id="ObQ-RA-S5V"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
|
||||
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="20" id="Seb-c0-MUL"/>
|
||||
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="top" secondItem="eQb-0a-76J" secondAttribute="bottom" constant="8" id="abm-cM-PN0"/>
|
||||
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" id="asc-Ga-WHD"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="top" secondItem="bDE-Tl-UHg" secondAttribute="bottom" constant="8" id="ZoS-xV-2WA"/>
|
||||
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aMJ-Wb-vRS"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="aOk-S0-0n2"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="eQb-0a-76J" secondAttribute="trailing" constant="20" id="b0B-3w-grH"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="centerY" secondItem="PXc-xv-A28" secondAttribute="centerY" id="cHe-pZ-0Oq"/>
|
||||
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="30" id="dYg-zP-wh2"/>
|
||||
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="22" id="dYg-zP-wh2"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="eSz-lz-Fdh"/>
|
||||
<constraint firstItem="qgf-Jf-cJr" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="esg-lX-BAT"/>
|
||||
<constraint firstItem="f1p-GL-O3o" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="fGd-YS-phP"/>
|
||||
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" id="h3d-Kc-q88"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="leading" secondItem="7ua-5a-uSd" secondAttribute="trailing" constant="12" id="ioO-NJ-Jqo"/>
|
||||
<constraint firstItem="bDE-Tl-UHg" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="jdk-ak-soQ"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="centerY" secondItem="lvJ-Rk-UT5" secondAttribute="centerY" id="jfs-YI-7Ae"/>
|
||||
<constraint firstItem="lvJ-Rk-UT5" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="jlD-Lo-abc"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="trailing" constant="20" id="kOG-Cj-hFG"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="trailing" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" id="lse-kg-lA2"/>
|
||||
<constraint firstItem="eQb-0a-76J" firstAttribute="top" secondItem="KEB-eH-x2Y" secondAttribute="bottom" constant="8" id="m2z-1O-ifB"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
|
||||
<constraint firstItem="5D8-GP-a4l" firstAttribute="top" secondItem="h6f-PY-cc0" secondAttribute="bottom" constant="25" id="lYd-VZ-lBs"/>
|
||||
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="5D8-GP-a4l" secondAttribute="bottom" priority="900" constant="25" id="pCX-eX-erN"/>
|
||||
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
|
||||
<constraint firstItem="bDE-Tl-UHg" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="bottom" constant="8" id="pis-of-f93"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
|
||||
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
|
||||
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="u1y-6V-moc"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="bottom" secondItem="C3G-wL-u7w" secondAttribute="top" constant="-8" id="zst-nc-VqA"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="YNz-ka-cBi" secondAttribute="trailing" constant="20" id="vfq-83-tKI"/>
|
||||
<constraint firstItem="pDa-fA-vnC" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="10" id="z6s-ga-iAk"/>
|
||||
</constraints>
|
||||
<animations/>
|
||||
</view>
|
||||
<connections>
|
||||
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="112.5" y="307.5"/>
|
||||
<point key="canvasLocation" x="302.5" y="304.5"/>
|
||||
</window>
|
||||
</objects>
|
||||
<resources>
|
||||
|
||||
21
Source/SantaGUI/SNTAccessibleTextField.h
Normal file
21
Source/SantaGUI/SNTAccessibleTextField.h
Normal file
@@ -0,0 +1,21 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
/**
|
||||
An NSTextField subclass that provides an accessiblity label equal to:
|
||||
(self.toolTip + self.stringValue) where available. It also sets the
|
||||
accessibilityRoleDescription to "label".
|
||||
*/
|
||||
@interface SNTAccessibleTextField : NSTextField
|
||||
@end
|
||||
39
Source/SantaGUI/SNTAccessibleTextField.m
Normal file
39
Source/SantaGUI/SNTAccessibleTextField.m
Normal file
@@ -0,0 +1,39 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTAccessibleTextField.h"
|
||||
|
||||
@implementation SNTAccessibleTextField
|
||||
|
||||
- (BOOL)accessibilityIsIgnored {
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityLabel {
|
||||
if (self.toolTip && self.stringValue) {
|
||||
return [NSString stringWithFormat:@"%@: %@", self.toolTip, self.stringValue];
|
||||
} else if (self.stringValue) {
|
||||
return self.stringValue;
|
||||
} else if (self.toolTip) {
|
||||
return self.toolTip;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)accessibilityRoleDescription {
|
||||
return @"label";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -18,6 +18,7 @@
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileWatcher.h"
|
||||
#import "SNTNotificationManager.h"
|
||||
#import "SNTStrengthify.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@@ -36,8 +37,8 @@
|
||||
[self setupMenu];
|
||||
|
||||
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
|
||||
handler:^{
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
handler:^(unsigned long data) {
|
||||
if (! (data & DISPATCH_VNODE_ATTRIB)) [[SNTConfigurator configurator] reloadConfigData];
|
||||
}];
|
||||
|
||||
self.notificationManager = [[SNTNotificationManager alloc] init];
|
||||
@@ -71,28 +72,37 @@
|
||||
#pragma mark Connection handling
|
||||
|
||||
- (void)createConnection {
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
NSXPCListener *listener = [NSXPCListener anonymousListener];
|
||||
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
|
||||
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
|
||||
self.listener.exportedObject = self.notificationManager;
|
||||
self.listener.invalidationHandler = ^{
|
||||
[weakSelf attemptReconnection];
|
||||
};
|
||||
[self.listener resume];
|
||||
WEAKIFY(self);
|
||||
|
||||
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] setNotificationListener:listener.endpoint];
|
||||
});
|
||||
// Create listener for return connection from daemon.
|
||||
NSXPCListener *listener = [NSXPCListener anonymousListener];
|
||||
self.listener = [[SNTXPCConnection alloc] initServerWithListener:listener];
|
||||
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
|
||||
self.listener.exportedObject = self.notificationManager;
|
||||
self.listener.acceptedHandler = ^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
};
|
||||
self.listener.invalidationHandler = ^{
|
||||
STRONGIFY(self);
|
||||
[self attemptReconnection];
|
||||
};
|
||||
[self.listener resume];
|
||||
|
||||
// Tell daemon to connect back to the above listener.
|
||||
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy] setNotificationListener:listener.endpoint];
|
||||
|
||||
// Now wait for the connection to come in.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC))) {
|
||||
[self attemptReconnection];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)attemptReconnection {
|
||||
// TODO(rah): Make this smarter.
|
||||
sleep(5);
|
||||
[self createConnection];
|
||||
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
|
||||
}
|
||||
|
||||
#pragma mark Menu Management
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@protocol SNTMessageWindowControllerDelegate
|
||||
- (void)windowDidClose;
|
||||
- (void)windowDidCloseSilenceHash:(NSString *)hash;
|
||||
@end
|
||||
|
||||
///
|
||||
@@ -29,41 +29,9 @@
|
||||
- (IBAction)closeWindow:(id)sender;
|
||||
- (IBAction)showCertInfo:(id)sender;
|
||||
|
||||
///
|
||||
/// The execution event that this window is for
|
||||
///
|
||||
@property SNTStoredEvent *event;
|
||||
|
||||
///
|
||||
/// The custom message to display for this event
|
||||
///
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
///
|
||||
/// The delegate to inform when the notification is dismissed
|
||||
///
|
||||
@property(weak) id<SNTMessageWindowControllerDelegate> delegate;
|
||||
|
||||
///
|
||||
/// A 'friendly' string representing the certificate information
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *publisherInfo;
|
||||
|
||||
///
|
||||
/// An optional message to display with this block.
|
||||
///
|
||||
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
|
||||
|
||||
///
|
||||
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
|
||||
/// if it isn't needed or set its title if it is.
|
||||
///
|
||||
@property IBOutlet NSButton *openEventButton;
|
||||
|
||||
///
|
||||
/// Reference to the "Application Name" label in the XIB. Used to remove if application
|
||||
/// doesn't have a CFBundleName.
|
||||
///
|
||||
@property IBOutlet NSTextField *applicationNameLabel;
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,18 +17,44 @@
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "SNTBlockMessage.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTMessageWindow.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@interface SNTMessageWindowController ()
|
||||
/// The execution event that this window is for
|
||||
@property SNTStoredEvent *event;
|
||||
|
||||
/// The custom message to display for this event
|
||||
@property(copy) NSString *customMessage;
|
||||
|
||||
/// A 'friendly' string representing the certificate information
|
||||
@property(readonly, nonatomic) NSString *publisherInfo;
|
||||
|
||||
/// An optional message to display with this block.
|
||||
@property(readonly, nonatomic) NSAttributedString *attributedCustomMessage;
|
||||
|
||||
/// Reference to the "Open Event" button in the XIB. Used to either remove the button
|
||||
/// if it isn't needed or set its title if it is.
|
||||
@property IBOutlet NSButton *openEventButton;
|
||||
|
||||
/// Reference to the "Application Name" label in the XIB. Used to remove if application
|
||||
/// doesn't have a CFBundleName.
|
||||
@property IBOutlet NSTextField *applicationNameLabel;
|
||||
|
||||
/// Linked to checkbox in UI to prevent future notifications for this binary.
|
||||
@property BOOL silenceFutureNotifications;
|
||||
@end
|
||||
|
||||
@implementation SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = (message != (NSString *)[NSNull null] ? message : nil);
|
||||
_customMessage = message;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -61,7 +87,13 @@
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
if (self.delegate) [self.delegate windowDidClose];
|
||||
if (!self.delegate) return;
|
||||
|
||||
if (self.silenceFutureNotifications) {
|
||||
[self.delegate windowDidCloseSilenceHash:self.event.fileSHA256];
|
||||
} else {
|
||||
[self.delegate windowDidCloseSilenceHash:nil];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showCertInfo:(id)sender {
|
||||
@@ -80,18 +112,9 @@
|
||||
}
|
||||
|
||||
- (IBAction)openEventDetails:(id)sender {
|
||||
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:config.machineID];
|
||||
|
||||
NSURL *url = [SNTBlockMessage eventDetailURLForEvent:self.event];
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:formatStr]];
|
||||
[[NSWorkspace sharedWorkspace] openURL:url];
|
||||
}
|
||||
|
||||
#pragma mark Generated properties
|
||||
@@ -119,39 +142,8 @@
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
NSString *htmlHeader = @"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: #AAA;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *message;
|
||||
if (self.customMessage.length) {
|
||||
message = self.customMessage;
|
||||
} else if (self.event.decision == EVENTSTATE_BLOCK_UNKNOWN) {
|
||||
message = [[SNTConfigurator configurator] unknownBlockMessage];
|
||||
if (!message) {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because its trustworthiness cannot be determined.";
|
||||
}
|
||||
} else {
|
||||
message = [[SNTConfigurator configurator] bannedBlockMessage];
|
||||
if (!message) {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because it has been deemed malicious.";
|
||||
}
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSAttributedString *returnStr = [[NSAttributedString alloc] initWithHTML:htmlData
|
||||
documentAttributes:NULL];
|
||||
return returnStr;
|
||||
return [SNTBlockMessage attributedBlockMessageForEvent:self.event
|
||||
customMessage:self.customMessage];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,22 +14,23 @@
|
||||
|
||||
#import "SNTNotificationManager.h"
|
||||
|
||||
#import "SNTBlockMessage.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@interface SNTNotificationManager ()
|
||||
///
|
||||
/// The currently displayed notification
|
||||
///
|
||||
@property SNTMessageWindowController *currentWindowController;
|
||||
|
||||
///
|
||||
/// The queue of pending notifications
|
||||
///
|
||||
@property(readonly) NSMutableArray *pendingNotifications;
|
||||
@end
|
||||
|
||||
@implementation SNTNotificationManager
|
||||
|
||||
static NSString * const silencedNotificationsKey = @"SilencedNotifications";
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -38,7 +39,9 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)windowDidClose {
|
||||
- (void)windowDidCloseSilenceHash:(NSString *)hash {
|
||||
if (hash) [self updateSilenceDate:[NSDate date] forHash:hash];
|
||||
|
||||
[self.pendingNotifications removeObject:self.currentWindowController];
|
||||
self.currentWindowController = nil;
|
||||
|
||||
@@ -50,7 +53,43 @@
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark SNTNotifierXPC protocol methods
|
||||
- (void)updateSilenceDate:(NSDate *)date forHash:(NSString *)hash {
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSMutableDictionary *d = [[ud objectForKey:silencedNotificationsKey] mutableCopy];
|
||||
if (!d) d = [NSMutableDictionary dictionary];
|
||||
if (date) {
|
||||
d[hash] = date;
|
||||
} else {
|
||||
[d removeObjectForKey:hash];
|
||||
}
|
||||
[ud setObject:d forKey:silencedNotificationsKey];
|
||||
}
|
||||
|
||||
#pragma mark SNTNotifierXPC protocol method
|
||||
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode {
|
||||
NSUserNotification *un = [[NSUserNotification alloc] init];
|
||||
un.title = @"Santa";
|
||||
un.hasActionButton = NO;
|
||||
NSString *customMsg;
|
||||
switch (clientmode) {
|
||||
case SNTClientModeMonitor:
|
||||
un.informativeText = @"Switching into Monitor mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationMonitor];
|
||||
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
if (customMsg.length) un.informativeText = customMsg;
|
||||
break;
|
||||
case SNTClientModeLockdown:
|
||||
un.informativeText = @"Switching into Lockdown mode";
|
||||
customMsg = [[SNTConfigurator configurator] modeNotificationLockdown];
|
||||
customMsg = [SNTBlockMessage stringFromHTML:customMsg];
|
||||
if (customMsg.length) un.informativeText = customMsg;
|
||||
break;
|
||||
default:
|
||||
return;
|
||||
}
|
||||
[[NSUserNotificationCenter defaultUserNotificationCenter] deliverNotification:un];
|
||||
}
|
||||
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message {
|
||||
// See if this binary is already in the list of pending notifications.
|
||||
@@ -58,40 +97,42 @@
|
||||
[NSPredicate predicateWithFormat:@"event.fileSHA256==%@", event.fileSHA256];
|
||||
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
|
||||
|
||||
// See if this binary is silenced.
|
||||
NSUserDefaults *ud = [NSUserDefaults standardUserDefaults];
|
||||
NSDate *silenceDate = [ud objectForKey:silencedNotificationsKey][event.fileSHA256];
|
||||
if ([silenceDate isKindOfClass:[NSDate class]]) {
|
||||
NSDate *oneDayAgo = [NSDate dateWithTimeIntervalSinceNow:-86400];
|
||||
if ([silenceDate compare:[NSDate date]] == NSOrderedDescending) {
|
||||
LOGI(@"Notification silence: date is in the future, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
} else if ([silenceDate compare:oneDayAgo] == NSOrderedAscending) {
|
||||
LOGI(@"Notification silence: date is more than one day ago, ignoring");
|
||||
[self updateSilenceDate:nil forHash:event.fileSHA256];
|
||||
} else {
|
||||
LOGI(@"Notification silence: dropping notification for %@", event.fileSHA256);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (!event) {
|
||||
NSLog(@"Error: Missing event object in message received from daemon!");
|
||||
LOGI(@"Error: Missing event object in message received from daemon!");
|
||||
return;
|
||||
}
|
||||
if (!message) message = (NSString *)[NSNull null];
|
||||
|
||||
// Notifications arrive on a background thread but UI updates must happen on the main thread.
|
||||
// This includes making windows.
|
||||
[self performSelectorOnMainThread:@selector(postBlockNotificationMainThread:)
|
||||
withObject:@{ @"event" : event,
|
||||
@"custommsg" : message }
|
||||
waitUntilDone:NO];
|
||||
}
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
SNTMessageWindowController *pendingMsg =
|
||||
[[SNTMessageWindowController alloc] initWithEvent:event andMessage:message];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
- (void)postBlockNotificationMainThread:(NSDictionary *)dict {
|
||||
SNTStoredEvent *event = dict[@"event"];
|
||||
NSString *msg = dict[@"custommsg"];
|
||||
|
||||
// Create message window
|
||||
SNTMessageWindowController *pendingMsg = [[SNTMessageWindowController alloc] initWithEvent:event
|
||||
andMessage:msg];
|
||||
pendingMsg.delegate = self;
|
||||
[self.pendingNotifications addObject:pendingMsg];
|
||||
|
||||
// If a notification isn't currently being displayed, display the incoming one.
|
||||
if (!self.currentWindowController) {
|
||||
self.currentWindowController = pendingMsg;
|
||||
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
|
||||
// It's quite likely that we're currently on a background thread, and GUI code should always be
|
||||
// on main thread. Open the window on the main thread so any code it runs is also.
|
||||
[pendingMsg showWindow:nil];
|
||||
}
|
||||
// If a notification isn't currently being displayed, display the incoming one.
|
||||
if (!self.currentWindowController) {
|
||||
self.currentWindowController = pendingMsg;
|
||||
[pendingMsg showWindow:nil];
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
41
Source/common/SNTBlockMessage.h
Normal file
41
Source/common/SNTBlockMessage.h
Normal file
@@ -0,0 +1,41 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@interface SNTBlockMessage : NSObject
|
||||
|
||||
///
|
||||
/// Return a message suitable for presenting to the user.
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one.
|
||||
///
|
||||
/// In SantaGUI this will return an NSAttributedString with links and formatting included
|
||||
/// while for santad all HTML will be properly stripped.
|
||||
///
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage;
|
||||
|
||||
///
|
||||
/// Return a URL generated from the EventDetailURL configuration key
|
||||
/// after replacing templates in the URL with values from the event.
|
||||
///
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event;
|
||||
|
||||
///
|
||||
/// Strip HTML from a string, replacing <br /> with newline.
|
||||
///
|
||||
+ (NSString *)stringFromHTML:(NSString *)html;
|
||||
|
||||
@end
|
||||
128
Source/common/SNTBlockMessage.m
Normal file
128
Source/common/SNTBlockMessage.m
Normal file
@@ -0,0 +1,128 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTBlockMessage.h"
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
NSString *htmlHeader = @"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: #666;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *message;
|
||||
if (customMessage.length) {
|
||||
message = customMessage;
|
||||
} else if (event.decision == SNTEventStateBlockUnknown) {
|
||||
message = [[SNTConfigurator configurator] unknownBlockMessage];
|
||||
if (!message) {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because its trustworthiness cannot be determined.";
|
||||
}
|
||||
} else {
|
||||
message = [[SNTConfigurator configurator] bannedBlockMessage];
|
||||
if (!message) {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because it has been deemed malicious.";
|
||||
}
|
||||
}
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef NSAppKitVersionNumber10_0
|
||||
NSData *htmlData = [fullHTML dataUsingEncoding:NSUTF8StringEncoding];
|
||||
return [[NSAttributedString alloc] initWithHTML:htmlData documentAttributes:NULL];
|
||||
#else
|
||||
NSString *strippedHTML = [self stringFromHTML:fullHTML];
|
||||
if (!strippedHTML) {
|
||||
return [[NSAttributedString alloc] initWithString:@"This binary has been blocked."];
|
||||
}
|
||||
return [[NSAttributedString alloc] initWithString:strippedHTML];
|
||||
#endif
|
||||
}
|
||||
|
||||
+ (NSString *)stringFromHTML:(NSString *)html {
|
||||
NSError *error;
|
||||
NSXMLDocument *xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
|
||||
|
||||
if (!xml && error.code == NSXMLParserEmptyDocumentError) {
|
||||
html = [NSString stringWithFormat:@"<html><body>%@</body></html>", html];
|
||||
xml = [[NSXMLDocument alloc] initWithXMLString:html options:0 error:&error];
|
||||
if (!xml) return html;
|
||||
}
|
||||
|
||||
// Strip any HTML tags out of the message. Also remove any content inside <style> tags and
|
||||
// replace <br> elements with a newline.
|
||||
NSString *stripXslt = @"<?xml version='1.0' encoding='utf-8'?>"
|
||||
@"<xsl:stylesheet version='1.0' xmlns:xsl='http://www.w3.org/1999/XSL/Transform'"
|
||||
@" xmlns:xhtml='http://www.w3.org/1999/xhtml'>"
|
||||
@"<xsl:output method='text'/>"
|
||||
@"<xsl:template match='br'><xsl:text>\n</xsl:text></xsl:template>"
|
||||
@"<xsl:template match='style'/>"
|
||||
@"</xsl:stylesheet>";
|
||||
NSData *data = [xml objectByApplyingXSLTString:stripXslt arguments:NULL error:&error];
|
||||
if (error || ![data isKindOfClass:[NSData class]]) {
|
||||
return html;
|
||||
}
|
||||
return [[NSString alloc] initWithData:data encoding:NSUTF8StringEncoding];
|
||||
}
|
||||
|
||||
+ (NSURL *)eventDetailURLForEvent:(SNTStoredEvent *)event {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
NSString *formatStr;
|
||||
if (config.eventDetailBundleURL && event.fileBundleID) {
|
||||
formatStr = config.eventDetailBundleURL;
|
||||
} else {
|
||||
formatStr = config.eventDetailURL;
|
||||
}
|
||||
|
||||
if (!formatStr.length) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
withString:event.executingUser];
|
||||
}
|
||||
if (config.machineID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
if (event.fileBundleID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_id%"
|
||||
withString:event.fileBundleID];
|
||||
}
|
||||
if (event.fileBundleVersionString) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%bundle_ver%"
|
||||
withString:event.fileBundleVersionString];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,63 +12,58 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__COMMON__COMMONENUMS_H
|
||||
#define SANTA__COMMON__COMMONENUMS_H
|
||||
|
||||
///
|
||||
/// These enums are used in various places throughout the Santa client code.
|
||||
/// The integer values are also stored in the database and so shouldn't be changed.
|
||||
///
|
||||
|
||||
typedef enum {
|
||||
RULETYPE_UNKNOWN,
|
||||
typedef NS_ENUM(NSInteger, SNTRuleType) {
|
||||
SNTRuleTypeUnknown,
|
||||
|
||||
RULETYPE_BINARY = 1,
|
||||
RULETYPE_CERT = 2,
|
||||
SNTRuleTypeBinary = 1,
|
||||
SNTRuleTypeCertificate = 2,
|
||||
};
|
||||
|
||||
RULETYPE_MAX
|
||||
} santa_ruletype_t;
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
SNTRuleStateUnknown,
|
||||
|
||||
typedef enum {
|
||||
RULESTATE_UNKNOWN,
|
||||
SNTRuleStateWhitelist = 1,
|
||||
SNTRuleStateBlacklist = 2,
|
||||
SNTRuleStateSilentBlacklist = 3,
|
||||
SNTRuleStateRemove = 4,
|
||||
};
|
||||
|
||||
RULESTATE_WHITELIST = 1,
|
||||
RULESTATE_BLACKLIST = 2,
|
||||
RULESTATE_SILENT_BLACKLIST = 3,
|
||||
RULESTATE_REMOVE = 4,
|
||||
typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
SNTClientModeUnknown,
|
||||
|
||||
RULESTATE_MAX
|
||||
} santa_rulestate_t;
|
||||
SNTClientModeMonitor = 1,
|
||||
SNTClientModeLockdown = 2,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
CLIENTMODE_UNKNOWN,
|
||||
typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
SNTEventStateUnknown,
|
||||
|
||||
CLIENTMODE_MONITOR = 1,
|
||||
CLIENTMODE_LOCKDOWN = 2,
|
||||
SNTEventStateAllowUnknown = 1,
|
||||
SNTEventStateAllowBinary = 2,
|
||||
SNTEventStateAllowCertificate = 3,
|
||||
SNTEventStateAllowScope = 4,
|
||||
|
||||
CLIENTMODE_MAX
|
||||
} santa_clientmode_t;
|
||||
SNTEventStateBlockUnknown = 5,
|
||||
SNTEventStateBlockBinary = 6,
|
||||
SNTEventStateBlockCertificate = 7,
|
||||
SNTEventStateBlockScope = 8,
|
||||
|
||||
typedef enum {
|
||||
EVENTSTATE_UNKNOWN,
|
||||
SNTEventStateBundleBinary = 9,
|
||||
};
|
||||
|
||||
EVENTSTATE_ALLOW_UNKNOWN = 1,
|
||||
EVENTSTATE_ALLOW_BINARY = 2,
|
||||
EVENTSTATE_ALLOW_CERTIFICATE = 3,
|
||||
EVENTSTATE_ALLOW_SCOPE = 4,
|
||||
|
||||
EVENTSTATE_BLOCK_UNKNOWN = 5,
|
||||
EVENTSTATE_BLOCK_BINARY = 6,
|
||||
EVENTSTATE_BLOCK_CERTIFICATE = 7,
|
||||
EVENTSTATE_BLOCK_SCOPE = 8,
|
||||
|
||||
EVENTSTATE_RELATED_BINARY = 9,
|
||||
|
||||
EVENTSTATE_MAX
|
||||
} santa_eventstate_t;
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
SNTRuleTableErrorEmptyRuleArray,
|
||||
SNTRuleTableErrorInsertOrReplaceFailed,
|
||||
SNTRuleTableErrorInvalidRule,
|
||||
SNTRuleTableErrorMissingRequiredRule,
|
||||
SNTRuleTableErrorRemoveFailed
|
||||
};
|
||||
|
||||
static const char *kKextPath = "/Library/Extensions/santa-driver.kext";
|
||||
static const char *kSantaDPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santad";
|
||||
static const char *kSantaCtlPath = "/Library/Extensions/santa-driver.kext/Contents/MacOS/santactl";
|
||||
|
||||
#endif // SANTA__COMMON__COMMONENUMS_H
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Singleton that provides an interface for managing configuration values on disk
|
||||
@@ -28,7 +28,7 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
/// The operating mode.
|
||||
///
|
||||
@property(nonatomic) santa_clientmode_t clientMode;
|
||||
@property(nonatomic) SNTClientMode clientMode;
|
||||
|
||||
///
|
||||
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
|
||||
@@ -75,18 +75,24 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
/// When the user gets a block notification, a button can be displayed which will
|
||||
/// take them to a web page with more information about that event.
|
||||
/// There are two properties, one for individual binaries and one for binaries that are part
|
||||
/// of a bundle. If the latter is not set the former will be used.
|
||||
///
|
||||
/// This property contains a kind of format string to be turned into the URL to send them to.
|
||||
/// The following sequences will be replaced in the final URL:
|
||||
///
|
||||
/// %file_sha% -- SHA-256 of the file that was blocked.
|
||||
/// %machine_id% -- ID of the machine.
|
||||
/// %username% -- executing user.
|
||||
/// %bundle_id% -- bundle id of the binary, if applicable.
|
||||
/// %bundle_ver% -- bundle version of the binary, if applicable.
|
||||
///
|
||||
/// @note: This is not an NSURL because the format-string parsing is done elsewhere.
|
||||
///
|
||||
/// If this item isn't set, the Open Event button will not be displayed.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventDetailURL;
|
||||
@property(readonly, nonatomic) NSString *eventDetailBundleURL;
|
||||
|
||||
///
|
||||
/// Related to the above property, this string represents the text to show on the button.
|
||||
@@ -106,6 +112,18 @@ extern NSString *const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedBlockMessage;
|
||||
|
||||
///
|
||||
/// The notification text to display when the client goes into MONITOR mode.
|
||||
/// Defaults to "Switching into Monitor mode"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *modeNotificationMonitor;
|
||||
|
||||
///
|
||||
/// The notification text to display when the client goes into LOCKDOWN mode.
|
||||
/// Defaults to "Switching into Lockdown mode"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *modeNotificationLockdown;
|
||||
|
||||
#pragma mark - Sync Settings
|
||||
|
||||
///
|
||||
|
||||
@@ -44,9 +44,12 @@ static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection
|
||||
|
||||
static NSString *const kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString *const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString *const kEventDetailBundleURLKey = @"EventDetailBundleURL";
|
||||
static NSString *const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kUnknownBlockMessage = @"UnknownBlockMessage";
|
||||
static NSString *const kBannedBlockMessage = @"BannedBlockMessage";
|
||||
static NSString *const kModeNotificationMonitor = @"ModeNotificationMonitor";
|
||||
static NSString *const kModeNotificationLockdown = @"ModeNotificationLockdown";
|
||||
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kSyncLastSuccess = @"SyncLastSuccess";
|
||||
@@ -96,20 +99,23 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (santa_clientmode_t)clientMode {
|
||||
int cm = [self.configData[kClientModeKey] intValue];
|
||||
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
|
||||
return (santa_clientmode_t)cm;
|
||||
- (SNTClientMode)clientMode {
|
||||
NSInteger cm = [self.configData[kClientModeKey] longValue];
|
||||
if (cm == SNTClientModeMonitor || cm == SNTClientModeLockdown) {
|
||||
return (SNTClientMode)cm;
|
||||
} else {
|
||||
self.configData[kClientModeKey] = @(CLIENTMODE_MONITOR);
|
||||
return CLIENTMODE_MONITOR;
|
||||
LOGE(@"Client mode was set to bad value: %ld. Resetting to MONITOR.", cm);
|
||||
self.configData[kClientModeKey] = @(SNTClientModeMonitor);
|
||||
return SNTClientModeMonitor;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setClientMode:(santa_clientmode_t)newMode {
|
||||
if (newMode > CLIENTMODE_UNKNOWN && newMode < CLIENTMODE_MAX) {
|
||||
- (void)setClientMode:(SNTClientMode)newMode {
|
||||
if (newMode == SNTClientModeMonitor || newMode == SNTClientModeLockdown) {
|
||||
self.configData[kClientModeKey] = @(newMode);
|
||||
[self saveConfigToDisk];
|
||||
} else {
|
||||
LOGW(@"Ignoring request to change client mode to %ld", newMode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -189,6 +195,10 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return self.configData[kEventDetailURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailBundleURL {
|
||||
return self.configData[kEventDetailBundleURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailText {
|
||||
return self.configData[kEventDetailTextKey];
|
||||
}
|
||||
@@ -201,6 +211,14 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return self.configData[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configData[kModeNotificationMonitor];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationLockdown {
|
||||
return self.configData[kModeNotificationLockdown];
|
||||
}
|
||||
|
||||
- (NSURL *)syncBaseURL {
|
||||
NSString *urlStr = self.configData[kSyncBaseURLKey];
|
||||
if (urlStr) {
|
||||
@@ -300,7 +318,8 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
options:NSDataReadingMappedIfSafe
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not read configuration file: %@", [error localizedDescription]);
|
||||
LOGE(@"Could not read configuration file: %@, replacing.", [error localizedDescription]);
|
||||
[self saveConfigToDisk];
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -310,7 +329,8 @@ static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
format:NULL
|
||||
error:&error];
|
||||
if (error) {
|
||||
LOGE(@"Could not parse configuration file: %@", [error localizedDescription]);
|
||||
LOGE(@"Could not parse configuration file: %@, replacing.", [error localizedDescription]);
|
||||
[self saveConfigToDisk];
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
@@ -210,7 +210,7 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
- (BOOL)isDMG {
|
||||
NSUInteger last512 = self.fileSize - 512;
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
|
||||
return (strncmp("koly", magic, 4) == 0);
|
||||
return (magic && strncmp("koly", magic, 4) == 0);
|
||||
}
|
||||
|
||||
#pragma mark Page Zero
|
||||
@@ -310,7 +310,8 @@ extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
}
|
||||
|
||||
- (NSString *)bundleName {
|
||||
return [[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
|
||||
[[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleVersion {
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
///
|
||||
/// Simple file watching class using dispatch sources. Will automatically
|
||||
/// reload the watch if the file is deleted. Will continue watching for
|
||||
/// reload the watch if the file is deleted and continue watching for
|
||||
/// events until deallocated.
|
||||
///
|
||||
@interface SNTFileWatcher : NSObject
|
||||
@@ -24,11 +24,10 @@
|
||||
/// Initializes the watcher and begins watching for modifications.
|
||||
///
|
||||
/// @param filePath the file to watch.
|
||||
/// @param handler the handler to call when changes happen.
|
||||
/// @param handler the handler to call when changes happen. The argument to the block is the
|
||||
/// type of change that happened as a bitmask to be compared with DISPATCH_VNODE_* constants.
|
||||
///
|
||||
/// @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;
|
||||
- (nonnull instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
handler:(nonnull void (^)(unsigned long))handler;
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,13 +14,13 @@
|
||||
|
||||
#import "SNTFileWatcher.h"
|
||||
|
||||
#import "SNTStrengthify.h"
|
||||
|
||||
@interface SNTFileWatcher ()
|
||||
@property NSString *filePath;
|
||||
@property dispatch_source_t monitoringSource;
|
||||
@property(strong) void (^handler)(unsigned long);
|
||||
|
||||
@property(strong) void (^eventHandler)(void);
|
||||
@property(strong) void (^internalEventHandler)(void);
|
||||
@property(strong) void (^internalCancelHandler)(void);
|
||||
@property dispatch_source_t source;
|
||||
@end
|
||||
|
||||
@implementation SNTFileWatcher
|
||||
@@ -30,15 +30,13 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler {
|
||||
- (instancetype)initWithFilePath:(nonnull NSString *)filePath
|
||||
handler:(nonnull void (^)(unsigned long))handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_eventHandler = handler;
|
||||
|
||||
if (!_filePath || !_eventHandler) return nil;
|
||||
|
||||
[self beginWatchingFile];
|
||||
_handler = handler;
|
||||
[self startWatchingFile];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -47,57 +45,57 @@
|
||||
[self stopWatchingFile];
|
||||
}
|
||||
|
||||
- (void)beginWatchingFile {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE |
|
||||
DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_RENAME);
|
||||
- (void)startWatchingFile {
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
||||
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_RENAME |
|
||||
DISPATCH_VNODE_WRITE | DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_ATTRIB);
|
||||
|
||||
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();
|
||||
dispatch_async(queue, ^{
|
||||
int fd = -1;
|
||||
while ((fd = open([self.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
|
||||
usleep(200000); // wait 200ms
|
||||
}
|
||||
};
|
||||
self.source = dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
|
||||
|
||||
self.internalCancelHandler = ^{
|
||||
int fd;
|
||||
WEAKIFY(self);
|
||||
|
||||
if (weakSelf.monitoringSource) {
|
||||
fd = (int)dispatch_source_get_handle(weakSelf.monitoringSource);
|
||||
close(fd);
|
||||
}
|
||||
dispatch_source_set_event_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
unsigned long data = dispatch_source_get_data(self.source);
|
||||
self.handler(data);
|
||||
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
|
||||
[self stopWatchingFile];
|
||||
[self startWatchingFile];
|
||||
}
|
||||
sleep(2);
|
||||
});
|
||||
|
||||
const char *filePathCString = [weakSelf.filePath fileSystemRepresentation];
|
||||
while ((fd = open(filePathCString, O_EVTONLY)) < 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
dispatch_source_set_registration_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
self.handler(0);
|
||||
});
|
||||
|
||||
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);
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
if (fd > 0) close(fd);
|
||||
});
|
||||
|
||||
dispatch_resume(self.source);
|
||||
});
|
||||
}
|
||||
|
||||
- (void)stopWatchingFile {
|
||||
if (!self.monitoringSource) return;
|
||||
if (!self.source) 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, ^{
|
||||
int fd = (int)dispatch_source_get_handle(self.source);
|
||||
dispatch_source_set_event_handler_f(self.source, NULL);
|
||||
dispatch_source_set_cancel_handler(self.source, ^{
|
||||
close(fd);
|
||||
});
|
||||
|
||||
dispatch_source_cancel(self.monitoringSource);
|
||||
self.monitoringSource = nil;
|
||||
dispatch_source_cancel(self.source);
|
||||
self.source = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -22,7 +22,7 @@
|
||||
#ifdef KERNEL
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n");
|
||||
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#else // DEBUG
|
||||
#define LOGD(...)
|
||||
#endif // DEBUG
|
||||
|
||||
@@ -42,10 +42,8 @@ void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
}
|
||||
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
strcmp(binaryName, "santad") == 0) {
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"]) {
|
||||
useSyslog = YES;
|
||||
|
||||
pthread_key_create(&syslogKey, syslogClientDestructor);
|
||||
}
|
||||
});
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Represents a Rule.
|
||||
@@ -27,12 +27,12 @@
|
||||
///
|
||||
/// The state of this rule
|
||||
///
|
||||
@property santa_rulestate_t state;
|
||||
@property SNTRuleState state;
|
||||
|
||||
///
|
||||
/// The type of object this rule is for (binary, certificate)
|
||||
///
|
||||
@property santa_ruletype_t type;
|
||||
@property SNTRuleType type;
|
||||
|
||||
///
|
||||
/// A custom message that will be displayed if this rule blocks a binary from executing
|
||||
@@ -43,8 +43,8 @@
|
||||
/// Designated initializer.
|
||||
///
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(santa_rulestate_t)state
|
||||
type:(santa_ruletype_t)type
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg;
|
||||
|
||||
@end
|
||||
|
||||
@@ -17,8 +17,8 @@
|
||||
@implementation SNTRule
|
||||
|
||||
- (instancetype)initWithShasum:(NSString *)shasum
|
||||
state:(santa_rulestate_t)state
|
||||
type:(santa_ruletype_t)type
|
||||
state:(SNTRuleState)state
|
||||
type:(SNTRuleType)type
|
||||
customMsg:(NSString *)customMsg {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
@@ -77,7 +77,7 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %d, Type: %d",
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
|
||||
self.shasum, self.state, self.type];
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Represents an event stored in the database.
|
||||
@@ -35,10 +35,16 @@
|
||||
@property NSString *filePath;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the CFBundleName.
|
||||
/// If the executed file was part of the bundle, this is the CFBundleDisplayName, if it exists
|
||||
/// or the CFBundleName if not.
|
||||
///
|
||||
@property NSString *fileBundleName;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the path to the bundle.
|
||||
///
|
||||
@property NSString *fileBundlePath;
|
||||
|
||||
///
|
||||
/// If the executed file was part of the bundle, this is the CFBundleID.
|
||||
///
|
||||
@@ -73,7 +79,7 @@
|
||||
///
|
||||
/// The decision santad returned.
|
||||
///
|
||||
@property santa_eventstate_t decision;
|
||||
@property SNTEventState decision;
|
||||
|
||||
///
|
||||
/// NSArray of logged in users when the decision was made.
|
||||
|
||||
@@ -34,6 +34,7 @@
|
||||
ENCODE(self.filePath, @"filePath");
|
||||
|
||||
ENCODE(self.fileBundleName, @"fileBundleName");
|
||||
ENCODE(self.fileBundlePath, @"fileBundlePath");
|
||||
ENCODE(self.fileBundleID, @"fileBundleID");
|
||||
ENCODE(self.fileBundleVersion, @"fileBundleVersion");
|
||||
ENCODE(self.fileBundleVersionString, @"fileBundleVersionString");
|
||||
@@ -64,6 +65,7 @@
|
||||
_filePath = DECODE(NSString, @"filePath");
|
||||
|
||||
_fileBundleName = DECODE(NSString, @"fileBundleName");
|
||||
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
|
||||
_fileBundleID = DECODE(NSString, @"fileBundleID");
|
||||
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
|
||||
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
|
||||
@@ -72,7 +74,7 @@
|
||||
|
||||
_executingUser = DECODE(NSString, @"executingUser");
|
||||
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
|
||||
_decision = (santa_eventstate_t)[DECODE(NSNumber, @"decision") intValue];
|
||||
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") intValue];
|
||||
_pid = DECODE(NSNumber, @"pid");
|
||||
_ppid = DECODE(NSNumber, @"ppid");
|
||||
_parentName = DECODE(NSString, @"parentName");
|
||||
|
||||
22
Source/common/SNTStrengthify.h
Normal file
22
Source/common/SNTStrengthify.h
Normal file
@@ -0,0 +1,22 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#define STRONGIFY(var) \
|
||||
_Pragma("clang diagnostic push") \
|
||||
_Pragma("clang diagnostic ignored \"-Wshadow\"") \
|
||||
__strong __typeof(var) var = (Weak_##var); \
|
||||
_Pragma("clang diagnostic pop")
|
||||
|
||||
#define WEAKIFY(var) \
|
||||
__weak __typeof(var) Weak_##var = (var);
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
/**
|
||||
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
|
||||
validation of connecting clients and a simpler interface.
|
||||
validation of connecting clients and forced connection establishment.
|
||||
|
||||
Example server started by @c launchd where the @c launchd job has a @c MachServices key:
|
||||
|
||||
@@ -41,6 +41,11 @@
|
||||
[conn.remoteObjectProxy selectorInRemoteInterface];
|
||||
@endcode
|
||||
|
||||
One advantage of the way that SNTXPCConnection works over using NSXPCConnection directly is that
|
||||
from the client-side once the resume method has finished, the connection is either valid or the
|
||||
invalidation handler will be called. Ordinarily, the connection doesn't actually get made until
|
||||
the first message is sent across it.
|
||||
|
||||
@note messages are always delivered on a background thread!
|
||||
*/
|
||||
@interface SNTXPCConnection : NSObject<NSXPCListenerDelegate>
|
||||
@@ -48,14 +53,14 @@
|
||||
/**
|
||||
Initialize a new server with a given listener, provided by `[NSXPCListener anonymousListener]`.
|
||||
*/
|
||||
- (instancetype)initServerWithListener:(NSXPCListener *)listener;
|
||||
- (nullable instancetype)initServerWithListener:(nonnull NSXPCListener *)listener;
|
||||
|
||||
/**
|
||||
Initializer for the 'server' side of the connection, started by launchd.
|
||||
|
||||
@param name MachService name, must match the MachServices key in the launchd.plist
|
||||
*/
|
||||
- (instancetype)initServerWithName:(NSString *)name;
|
||||
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
|
||||
|
||||
/**
|
||||
Initializer a new client to a service exported by a LaunchDaemon.
|
||||
@@ -63,17 +68,20 @@
|
||||
@param name MachService name
|
||||
@param privileged Use YES if the server is running as root.
|
||||
*/
|
||||
- (instancetype)initClientWithName:(NSString *)name privileged:(BOOL)privileged;
|
||||
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
|
||||
|
||||
/**
|
||||
Initialize a new client with a listener endpoint sent from another process.
|
||||
|
||||
@param listener An NSXPCListenerEndpoint to connect to.
|
||||
*/
|
||||
- (instancetype)initClientWithListener:(NSXPCListenerEndpoint *)listener;
|
||||
- (nullable instancetype)initClientWithListener:(nonnull NSXPCListenerEndpoint *)listener;
|
||||
|
||||
/**
|
||||
Call when the properties of the object have been set-up and you're ready for connections.
|
||||
|
||||
For clients, this call can take up to 2s to complete for connection to finish establishing though
|
||||
in basically all cases it will actually complete in a few milliseconds.
|
||||
*/
|
||||
- (void)resume;
|
||||
|
||||
@@ -84,27 +92,35 @@
|
||||
|
||||
/**
|
||||
The interface the remote object should conform to. (client)
|
||||
*/
|
||||
@property(retain) NSXPCInterface *remoteInterface;
|
||||
*/
|
||||
@property(retain, nullable) NSXPCInterface *remoteInterface;
|
||||
|
||||
/**
|
||||
A proxy to the object at the other end of the connection. (client)
|
||||
*/
|
||||
@property(readonly, nonatomic) id remoteObjectProxy;
|
||||
|
||||
@note If the connection to the server failed, this will be nil, so you can safely send messages
|
||||
and rely on the invalidationHandler for handling the failure.
|
||||
*/
|
||||
@property(readonly, nonatomic, nullable) id remoteObjectProxy;
|
||||
|
||||
/**
|
||||
The interface this object exports. (server)
|
||||
*/
|
||||
@property(retain) NSXPCInterface *exportedInterface;
|
||||
*/
|
||||
@property(retain, nullable) NSXPCInterface *exportedInterface;
|
||||
|
||||
/**
|
||||
The object that responds to messages from the other end. (server)
|
||||
*/
|
||||
@property(retain) id exportedObject;
|
||||
*/
|
||||
@property(retain, nullable) id exportedObject;
|
||||
|
||||
/**
|
||||
A block to run when a/the connection is invalidated/interrupted.
|
||||
*/
|
||||
@property(copy) void (^invalidationHandler)(void);
|
||||
A block to run when a/the connection is accepted and fully established.
|
||||
*/
|
||||
@property(copy, nullable) void (^acceptedHandler)(void);
|
||||
|
||||
/**
|
||||
A block to run when a/the connection is invalidated/interrupted/rejected.
|
||||
*/
|
||||
@property(copy, nullable) void (^invalidationHandler)(void);
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,11 +16,39 @@
|
||||
|
||||
#import "MOLCodesignChecker.h"
|
||||
|
||||
#import "SNTStrengthify.h"
|
||||
|
||||
/**
|
||||
Protocol used during connection establishment, @see SNTXPCConnectionInterface
|
||||
*/
|
||||
@protocol SNTXPCConnectionProtocol
|
||||
- (void)connectWithReply:(void (^)())reply;
|
||||
@end
|
||||
|
||||
/**
|
||||
Recipient object used during connection establishment. Each incoming connection
|
||||
has one of these objects created which accept the message in the protocol
|
||||
and call the block provided during creation before replying.
|
||||
|
||||
This allows the server to reset the connection's exporteed interface and
|
||||
object to the correct values after the client has sent the establishment message.
|
||||
*/
|
||||
@interface SNTXPCConnectionInterface : NSObject<SNTXPCConnectionProtocol>
|
||||
@property(strong) void (^block)(void);
|
||||
@end
|
||||
|
||||
@implementation SNTXPCConnectionInterface
|
||||
- (void)connectWithReply:(void (^)())reply {
|
||||
if (self.block) self.block();
|
||||
reply();
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SNTXPCConnection ()
|
||||
@property NSXPCInterface *validationInterface;
|
||||
|
||||
/// The XPC listener (server only).
|
||||
@property NSXPCListener *listenerObject;
|
||||
/// Array of accepted connections (server only).
|
||||
@property NSMutableArray *acceptedConnections;
|
||||
|
||||
/// The current connection object (client only).
|
||||
@property NSXPCConnection *currentConnection;
|
||||
@@ -34,8 +62,8 @@
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_listenerObject = listener;
|
||||
if (!_listenerObject) return nil;
|
||||
_acceptedConnections = [NSMutableArray array];
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -49,6 +77,8 @@
|
||||
if (self) {
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:listener];
|
||||
if (!_currentConnection) return nil;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -59,6 +89,8 @@
|
||||
NSXPCConnectionOptions options = (privileged ? NSXPCConnectionPrivileged : 0);
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name options:options];
|
||||
if (!_currentConnection) return nil;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -75,10 +107,29 @@
|
||||
self.listenerObject.delegate = self;
|
||||
[self.listenerObject resume];
|
||||
} else {
|
||||
self.currentConnection.remoteObjectInterface = self.remoteInterface;
|
||||
WEAKIFY(self);
|
||||
|
||||
// Set-up the connection with the remote interface set to the validation interface,
|
||||
// send a message to the listener to finish establishing the connection
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
self.currentConnection.remoteObjectInterface = self.validationInterface;
|
||||
self.currentConnection.interruptionHandler = self.invalidationHandler;
|
||||
self.currentConnection.invalidationHandler = self.invalidationHandler;
|
||||
[self.currentConnection resume];
|
||||
[[self.currentConnection remoteObjectProxy] connectWithReply:^{
|
||||
STRONGIFY(self);
|
||||
// The connection is now established
|
||||
[self.currentConnection suspend];
|
||||
self.currentConnection.remoteObjectInterface = self.remoteInterface;
|
||||
[self.currentConnection resume];
|
||||
dispatch_semaphore_signal(sema);
|
||||
if (self.acceptedHandler) self.acceptedHandler();
|
||||
}];
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 2 * NSEC_PER_SEC))) {
|
||||
// Connection was not established in a reasonable time, invalidate.
|
||||
self.currentConnection.remoteObjectInterface = nil; // ensure clients don't try to use it.
|
||||
[self.currentConnection invalidate];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -89,23 +140,36 @@
|
||||
return NO;
|
||||
}
|
||||
|
||||
[self.acceptedConnections addObject:connection];
|
||||
// The client passed the code signature check, now we need to resume the listener and
|
||||
// return YES so that the client can send the connectWithReply message. Once the client does
|
||||
// we reset the connection's exportedInterface and exportedObject.
|
||||
SNTXPCConnectionInterface *ci = [[SNTXPCConnectionInterface alloc] init];
|
||||
WEAKIFY(self);
|
||||
WEAKIFY(connection);
|
||||
ci.block = ^{
|
||||
STRONGIFY(self)
|
||||
STRONGIFY(connection);
|
||||
[connection suspend];
|
||||
connection.invalidationHandler = connection.interruptionHandler = ^{
|
||||
if (self.invalidationHandler) self.invalidationHandler();
|
||||
};
|
||||
connection.exportedInterface = self.exportedInterface;
|
||||
connection.exportedObject = self.exportedObject;
|
||||
[connection resume];
|
||||
|
||||
__weak __typeof(connection) weakConnection = connection;
|
||||
connection.interruptionHandler = connection.invalidationHandler = ^{
|
||||
[self.acceptedConnections removeObject:weakConnection];
|
||||
if (self.invalidationHandler) self.invalidationHandler();
|
||||
// The connection is now established.
|
||||
if (self.acceptedHandler) self.acceptedHandler();
|
||||
};
|
||||
|
||||
connection.exportedInterface = self.exportedInterface;
|
||||
connection.exportedObject = self.exportedObject;
|
||||
|
||||
connection.exportedInterface = self.validationInterface;
|
||||
connection.exportedObject = ci;
|
||||
[connection resume];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (id)remoteObjectProxy {
|
||||
if (self.currentConnection.remoteObjectInterface) {
|
||||
if (self.currentConnection.remoteObjectInterface &&
|
||||
self.currentConnection.remoteObjectInterface != self.validationInterface) {
|
||||
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
|
||||
[self.currentConnection invalidate];
|
||||
}];
|
||||
@@ -119,11 +183,8 @@
|
||||
if (self.currentConnection) {
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
} else if (self.acceptedConnections.count) {
|
||||
for (NSXPCConnection *conn in self.acceptedConnections) {
|
||||
[conn invalidate];
|
||||
}
|
||||
[self.acceptedConnections removeAllObjects];
|
||||
} else if (self.listenerObject) {
|
||||
[self.listenerObject invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@@ -33,24 +33,25 @@
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
|
||||
- (void)databaseRuleAddRule:(SNTRule *)rule
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(BOOL success))reply;
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(BOOL success))reply;
|
||||
reply:(void (^)(NSError *error))reply;
|
||||
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply;
|
||||
- (void)databaseEventForSHA256:(NSString *)sha256 reply:(void (^)(SNTStoredEvent *))reply;
|
||||
- (void)databaseEventsPending:(void (^)(NSArray *events))reply;
|
||||
- (void)databaseRemoveEventsWithIDs:(NSArray *)ids;
|
||||
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply;
|
||||
|
||||
///
|
||||
/// Config ops
|
||||
///
|
||||
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))reply;
|
||||
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
|
||||
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply;
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply;
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply;
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply;
|
||||
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply;
|
||||
- (void)setNextSyncInterval:(uint64_t)seconds reply:(void (^)())reply;
|
||||
- (void)setSyncLastSuccess:(NSDate *)date reply:(void (^)())reply;
|
||||
- (void)setSyncCleanRequired:(BOOL)cleanReqd reply:(void (^)())reply;
|
||||
|
||||
@@ -12,11 +12,14 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
/// Protocol implemented by SantaGUI and utilized by santad
|
||||
@protocol SNTNotifierXPC
|
||||
- (void)postBlockNotification:(SNTStoredEvent *)event withCustomMessage:(NSString *)message;
|
||||
- (void)postClientModeNotification:(SNTClientMode)clientmode;
|
||||
@end
|
||||
|
||||
@interface SNTXPCNotifierInterface : NSObject
|
||||
|
||||
@@ -113,7 +113,7 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
|
||||
|
||||
// Ask santad to shutdown, in case it's running.
|
||||
if (!itDied) {
|
||||
santa_message_t *message = new santa_message_t;
|
||||
auto message = new santa_message_t;
|
||||
message->action = ACTION_REQUEST_SHUTDOWN;
|
||||
PostToDecisionQueue(message);
|
||||
delete message;
|
||||
@@ -136,8 +136,8 @@ void SantaDecisionManager::DisconnectClient(bool itDied) {
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::ClientConnected() const {
|
||||
proc_t p = proc_find(client_pid_);
|
||||
bool is_exiting = false;
|
||||
auto p = proc_find(client_pid_);
|
||||
auto is_exiting = false;
|
||||
if (p) {
|
||||
is_exiting = proc_exiting(p);
|
||||
proc_rele(p);
|
||||
@@ -218,7 +218,7 @@ void SantaDecisionManager::AddToCache(
|
||||
}
|
||||
|
||||
if (decision == ACTION_REQUEST_BINARY) {
|
||||
SantaCachedDecision *pending = new SantaCachedDecision();
|
||||
auto pending = new SantaCachedDecision();
|
||||
pending->setAction(ACTION_REQUEST_BINARY, 0);
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
cached_decisions_->setObject(identifier, pending);
|
||||
@@ -226,7 +226,7 @@ void SantaDecisionManager::AddToCache(
|
||||
pending->release(); // it was retained when added to the dictionary
|
||||
} else {
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
SantaCachedDecision *pending = OSDynamicCast(
|
||||
auto pending = OSDynamicCast(
|
||||
SantaCachedDecision, cached_decisions_->getObject(identifier));
|
||||
if (pending) {
|
||||
pending->setAction(decision, microsecs);
|
||||
@@ -237,7 +237,7 @@ void SantaDecisionManager::AddToCache(
|
||||
|
||||
void SantaDecisionManager::CacheCheck(const char *identifier) {
|
||||
lck_rw_lock_shared(cached_decisions_lock_);
|
||||
bool shouldInvalidate = (cached_decisions_->getObject(identifier) != nullptr);
|
||||
auto shouldInvalidate = (cached_decisions_->getObject(identifier) != nullptr);
|
||||
if (shouldInvalidate) {
|
||||
if (!lck_rw_lock_shared_to_exclusive(cached_decisions_lock_)) {
|
||||
// shared_to_exclusive will return false if a previous reader upgraded
|
||||
@@ -265,7 +265,7 @@ void SantaDecisionManager::ClearCache() {
|
||||
#pragma mark Decision Fetching
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
santa_action_t result = ACTION_UNSET;
|
||||
auto result = ACTION_UNSET;
|
||||
uint64_t decision_time = 0;
|
||||
|
||||
lck_rw_lock_shared(cached_decisions_lock_);
|
||||
@@ -278,7 +278,7 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
lck_rw_unlock_shared(cached_decisions_lock_);
|
||||
|
||||
if (RESPONSE_VALID(result)) {
|
||||
uint64_t diff_time = GetCurrentUptime();
|
||||
auto diff_time = GetCurrentUptime();
|
||||
|
||||
if (result == ACTION_RESPOND_ALLOW) {
|
||||
if ((kMaxAllowCacheTimeMilliseconds * 1000) > diff_time) {
|
||||
@@ -307,7 +307,7 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
santa_message_t *message, const char *vnode_id_str) {
|
||||
santa_action_t return_action = ACTION_UNSET;
|
||||
auto return_action = ACTION_UNSET;
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
@@ -350,11 +350,10 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str) {
|
||||
santa_action_t return_action = ACTION_UNSET;
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
// Check to see if item is in cache
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
auto return_action = GetFromCache(vnode_id_str);
|
||||
|
||||
// If item was in cache return it.
|
||||
if (RESPONSE_VALID(return_action)) return return_action;
|
||||
@@ -366,45 +365,34 @@ santa_action_t SantaDecisionManager::FetchDecision(
|
||||
path[0] = '\0';
|
||||
}
|
||||
|
||||
santa_message_t *message = NewMessage();
|
||||
auto message = NewMessage(cred);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
message->action = ACTION_REQUEST_BINARY;
|
||||
message->vnode_id = vnode_id;
|
||||
proc_name(message->ppid, message->pname, sizeof(message->pname));
|
||||
santa_action_t ret = GetFromDaemon(message, vnode_id_str);
|
||||
return_action = GetFromDaemon(message, vnode_id_str);
|
||||
delete message;
|
||||
return ret;
|
||||
return return_action;
|
||||
}
|
||||
|
||||
#pragma mark Misc
|
||||
|
||||
santa_message_t *SantaDecisionManager::NewMessage() const {
|
||||
santa_message_t *message = new santa_message_t;
|
||||
message->uid = kauth_getuid();
|
||||
message->gid = kauth_getgid();
|
||||
message->pid = proc_selfpid();
|
||||
message->ppid = proc_selfppid();
|
||||
return message;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
|
||||
bool kr = false;
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
bool kr = false;
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
auto kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
if (!kr) {
|
||||
if (OSCompareAndSwap(0, 1, &failed_log_queue_requests_)) {
|
||||
LOGW("Dropping log queue messages");
|
||||
}
|
||||
// If enqueue failed, pop an item off the queue and try again.
|
||||
uint32_t dataSize = sizeof(santa_message_t);
|
||||
uint32_t dataSize = 0;
|
||||
log_dataqueue_->dequeue(0, &dataSize);
|
||||
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
} else {
|
||||
@@ -414,23 +402,6 @@ bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
return kr;
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::GetVnodeIDForVnode(
|
||||
const vfs_context_t ctx, const vnode_t vp) const {
|
||||
struct vnode_attr vap;
|
||||
VATTR_INIT(&vap);
|
||||
VATTR_WANTED(&vap, va_fsid);
|
||||
VATTR_WANTED(&vap, va_fileid);
|
||||
vnode_getattr(vp, &vap, ctx);
|
||||
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::GetCurrentUptime() {
|
||||
clock_sec_t sec;
|
||||
clock_usec_t usec;
|
||||
clock_get_system_microtime(&sec, &usec);
|
||||
return (uint64_t)((sec * 1000000) + usec);
|
||||
}
|
||||
|
||||
#pragma mark Invocation Tracking & PID comparison
|
||||
|
||||
void SantaDecisionManager::IncrementListenerInvocations() {
|
||||
@@ -451,12 +422,12 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Get ID for the vnode and convert it to a string.
|
||||
uint64_t vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
auto vnode_id = GetVnodeIDForVnode(ctx, vp);
|
||||
char vnode_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
// Fetch decision
|
||||
santa_action_t returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
|
||||
auto returnedAction = FetchDecision(cred, vp, vnode_id, vnode_str);
|
||||
|
||||
// If file has dirty blocks, remove from cache and deny. This would usually
|
||||
// be the case if a file has been written to and flushed but not yet
|
||||
@@ -468,12 +439,15 @@ int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_ALLOW: {
|
||||
proc_t proc = vfs_context_proc(ctx);
|
||||
auto proc = vfs_context_proc(ctx);
|
||||
if (proc) {
|
||||
SantaPIDAndPPID *pidWrapper = new SantaPIDAndPPID;
|
||||
auto pidWrapper = new SantaPIDAndPPID;
|
||||
pidWrapper->pid = proc_pid(proc);
|
||||
pidWrapper->ppid = proc_ppid(proc);
|
||||
lck_rw_lock_exclusive(vnode_pid_map_lock_);
|
||||
if (vnode_pid_map_->getCount() > 5000) {
|
||||
vnode_pid_map_->flushCollection();
|
||||
}
|
||||
vnode_pid_map_->setObject(vnode_str, pidWrapper);
|
||||
lck_rw_unlock_exclusive(vnode_pid_map_lock_);
|
||||
pidWrapper->release();
|
||||
@@ -495,8 +469,8 @@ void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (vp) {
|
||||
vfs_context_t context = vfs_context_create(nullptr);
|
||||
uint64_t vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
auto context = vfs_context_create(nullptr);
|
||||
auto vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
vfs_context_rele(context);
|
||||
|
||||
if (action == KAUTH_FILEOP_CLOSE) {
|
||||
@@ -504,7 +478,7 @@ void SantaDecisionManager::FileOpCallback(
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
CacheCheck(vnode_id_str);
|
||||
} else if (action == KAUTH_FILEOP_EXEC) {
|
||||
santa_message_t *message = NewMessage();
|
||||
auto message = NewMessage(nullptr);
|
||||
message->vnode_id = vnode_id;
|
||||
message->action = ACTION_NOTIFY_EXEC;
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
@@ -513,7 +487,7 @@ void SantaDecisionManager::FileOpCallback(
|
||||
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
lck_rw_lock_shared(vnode_pid_map_lock_);
|
||||
SantaPIDAndPPID *pidWrapper = OSDynamicCast(
|
||||
auto pidWrapper = OSDynamicCast(
|
||||
SantaPIDAndPPID, vnode_pid_map_->getObject(vnode_str));
|
||||
if (pidWrapper) {
|
||||
message->pid = pidWrapper->pid;
|
||||
@@ -527,9 +501,11 @@ void SantaDecisionManager::FileOpCallback(
|
||||
}
|
||||
}
|
||||
|
||||
// Filter out modifications to locations that are definitely not useful.
|
||||
if (ClientConnected() && !strprefix(path, "/.") && !strprefix(path, "/dev")) {
|
||||
santa_message_t *message = NewMessage();
|
||||
// Filter out modifications to locations that are definitely
|
||||
// not useful or made by santad.
|
||||
if (proc_selfpid() != client_pid_ &&
|
||||
!strprefix(path, "/.") && !strprefix(path, "/dev")) {
|
||||
auto message = NewMessage(nullptr);
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
if (new_path) strlcpy(message->newpath, new_path, sizeof(message->newpath));
|
||||
proc_name(message->pid, message->pname, sizeof(message->pname));
|
||||
@@ -563,7 +539,7 @@ void SantaDecisionManager::FileOpCallback(
|
||||
extern "C" int fileop_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
SantaDecisionManager *sdm = OSDynamicCast(
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
vnode_t vp = nullptr;
|
||||
@@ -606,8 +582,8 @@ extern "C" int vnode_scope_callback(
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
SantaDecisionManager *sdm =
|
||||
OSDynamicCast(SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
|
||||
@@ -121,41 +121,41 @@ class SantaDecisionManager : public OSObject {
|
||||
/// The maximum number of milliseconds a cached deny message should be
|
||||
/// considered valid.
|
||||
///
|
||||
const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
|
||||
///
|
||||
/// The maximum number of milliseconds a cached allow message should be
|
||||
/// considered valid.
|
||||
///
|
||||
const uint64_t kMaxAllowCacheTimeMilliseconds = 1000 * 60 * 60 * 24;
|
||||
static const uint64_t kMaxAllowCacheTimeMilliseconds = 1000 * 60 * 60 * 24;
|
||||
|
||||
///
|
||||
/// While waiting for a response from the daemon, this is the number of
|
||||
/// milliseconds to sleep for before checking the cache for a response.
|
||||
///
|
||||
const int kRequestLoopSleepMilliseconds = 10;
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 10;
|
||||
|
||||
///
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
///
|
||||
const int kMaxCacheSize = 10000;
|
||||
static const uint32_t kMaxCacheSize = 10000;
|
||||
|
||||
///
|
||||
/// Maximum number of PostToDecisionQueue failures to allow.
|
||||
///
|
||||
const int kMaxDecisionQueueFailures = 10;
|
||||
static const uint32_t kMaxDecisionQueueFailures = 10;
|
||||
|
||||
///
|
||||
/// The maximum number of messages can be kept in
|
||||
/// the decision data queue at any time.
|
||||
///
|
||||
const int kMaxDecisionQueueEvents = 512;
|
||||
static const uint32_t kMaxDecisionQueueEvents = 512;
|
||||
|
||||
///
|
||||
/// The maximum number of messages can be kept
|
||||
/// in the logging data queue at any time.
|
||||
///
|
||||
const int kMaxLogQueueEvents = 1024;
|
||||
static const uint32_t kMaxLogQueueEvents = 1024;
|
||||
|
||||
/// Fetches a response from the cache, first checking to see if the
|
||||
/// entry has expired.
|
||||
@@ -210,17 +210,50 @@ class SantaDecisionManager : public OSObject {
|
||||
/// @param vp The Vnode to get the ID for
|
||||
/// @return uint64_t The Vnode ID as a 64-bit unsigned int.
|
||||
///
|
||||
uint64_t GetVnodeIDForVnode(const vfs_context_t ctx, const vnode_t vp) const;
|
||||
static inline uint64_t GetVnodeIDForVnode(const vfs_context_t ctx,
|
||||
const vnode_t vp) {
|
||||
struct vnode_attr vap;
|
||||
VATTR_INIT(&vap);
|
||||
VATTR_WANTED(&vap, va_fsid);
|
||||
VATTR_WANTED(&vap, va_fileid);
|
||||
vnode_getattr(vp, &vap, ctx);
|
||||
return (((uint64_t)vap.va_fsid << 32) | vap.va_fileid);
|
||||
}
|
||||
|
||||
///
|
||||
/// Creates a new santa_message_t with some fields pre-filled.
|
||||
/// @param credential The kauth_cred_t for this action, if available.
|
||||
/// If nullptr, will get the credential for the current process.
|
||||
///
|
||||
santa_message_t *NewMessage() const;
|
||||
static inline santa_message_t *NewMessage(kauth_cred_t credential) {
|
||||
bool should_release = false;
|
||||
if (credential == nullptr) {
|
||||
credential = kauth_cred_get_with_ref();
|
||||
should_release = true;
|
||||
}
|
||||
|
||||
auto message = new santa_message_t;
|
||||
message->uid = kauth_cred_getuid(credential);
|
||||
message->gid = kauth_cred_getgid(credential);
|
||||
message->pid = proc_selfpid();
|
||||
message->ppid = proc_selfppid();
|
||||
|
||||
if (should_release) {
|
||||
kauth_cred_unref(&credential);
|
||||
}
|
||||
|
||||
return message;
|
||||
}
|
||||
|
||||
///
|
||||
/// Returns the current system uptime in microseconds
|
||||
///
|
||||
static uint64_t GetCurrentUptime();
|
||||
static inline uint64_t GetCurrentUptime() {
|
||||
clock_sec_t sec;
|
||||
clock_usec_t usec;
|
||||
clock_get_system_microtime(&sec, &usec);
|
||||
return (uint64_t)((sec * 1000000) + usec);
|
||||
}
|
||||
|
||||
private:
|
||||
lck_grp_t *sdm_lock_grp_;
|
||||
@@ -237,10 +270,10 @@ class SantaDecisionManager : public OSObject {
|
||||
|
||||
IOSharedDataQueue *decision_dataqueue_;
|
||||
IOSharedDataQueue *log_dataqueue_;
|
||||
SInt32 failed_decision_queue_requests_;
|
||||
SInt32 failed_log_queue_requests_;
|
||||
int32_t failed_decision_queue_requests_;
|
||||
int32_t failed_log_queue_requests_;
|
||||
|
||||
SInt32 listener_invocations_;
|
||||
int32_t listener_invocations_;
|
||||
|
||||
pid_t client_pid_;
|
||||
|
||||
|
||||
@@ -19,6 +19,9 @@
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTCommandFileInfo : NSObject<SNTCommand>
|
||||
@end
|
||||
@@ -100,11 +103,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
[self printKey:@"Page Zero" value:@"__PAGEZERO segment missing/bad!"];
|
||||
}
|
||||
|
||||
// Code signature state
|
||||
NSError *error;
|
||||
MOLCodesignChecker *csc = [[MOLCodesignChecker alloc] initWithBinaryPath:filePath error:&error];
|
||||
if (!error) {
|
||||
[self printKey:@"Code-signed" value:@"Yes"];
|
||||
} else {
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
[self printKey:@"Code-signed" value:@"No"];
|
||||
@@ -113,7 +115,7 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
case errSecCSStaticCodeChanged:
|
||||
case errSecCSSignatureNotVerifiable:
|
||||
case errSecCSSignatureUnsupported:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but code/signatured changed/unverifiable"];
|
||||
[self printKey:@"Code-signed" value:@"Yes, but code/signature changed/unverifiable"];
|
||||
break;
|
||||
case errSecCSResourceDirectoryFailed:
|
||||
case errSecCSResourceNotSupported:
|
||||
@@ -133,13 +135,63 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
break;
|
||||
default: {
|
||||
NSString *val = [NSString stringWithFormat:@"Yes, but failed to validate (%ld)",
|
||||
error.code];
|
||||
error.code];
|
||||
[self printKey:@"Code-signed" value:val];
|
||||
break;
|
||||
}
|
||||
}
|
||||
} else if (csc.signatureFlags & kSecCodeSignatureAdhoc) {
|
||||
[self printKey:@"Code-signed" value:@"Yes, but ad-hoc"];
|
||||
} else {
|
||||
[self printKey:@"Code-signed" value:@"Yes"];
|
||||
}
|
||||
if (csc.certificates) {
|
||||
|
||||
// Binary rule state
|
||||
__block SNTRule *r;
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] databaseBinaryRuleForSHA256:sha256 reply:^(SNTRule *rule) {
|
||||
if (rule) r = rule;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
NSString *leafCertSHA = [[csc.certificates firstObject] SHA256];
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] databaseCertificateRuleForSHA256:leafCertSHA
|
||||
reply:^(SNTRule *rule) {
|
||||
if (!r && rule) r = rule;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 1 * NSEC_PER_SEC))) {
|
||||
[self printKey:@"Rule" value:@"Cannot communicate with daemon"];
|
||||
} else {
|
||||
NSString *output;
|
||||
switch (r.state) {
|
||||
case SNTRuleStateWhitelist:
|
||||
output = @"Whitelisted";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[32mWhitelisted\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
break;
|
||||
case SNTRuleStateBlacklist:
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
output = @"Blacklisted";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[31mBlacklisted\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
break;
|
||||
default:
|
||||
output = @"None";
|
||||
if (isatty(STDOUT_FILENO)) {
|
||||
output = @"\033[33mNone\033[0m";
|
||||
}
|
||||
[self printKey:@"Rule" value:output];
|
||||
}
|
||||
}
|
||||
|
||||
// Signing chain
|
||||
if (csc.certificates.count) {
|
||||
printf("Signing chain:\n");
|
||||
|
||||
[csc.certificates enumerateObjectsUsingBlock:^(MOLCertificate *c,
|
||||
@@ -168,11 +220,10 @@ REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
+ (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
|
||||
if ([fi isScript]) return @"Script";
|
||||
if ([fi isXARArchive]) return @"XAR Archive";
|
||||
if ([fi isExecutable]) return @"Executable";
|
||||
if ([fi isDylib]) return @"Dynamic Library";
|
||||
if ([fi isKext]) return @"Kernel Extension";
|
||||
if ([fi isFat]) return @"Fat Binary";
|
||||
if ([fi isMachO]) return @"Thin Binary";
|
||||
if ([fi isXARArchive]) return @"XAR Archive";
|
||||
if ([fi isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
}
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
@implementation SNTCommandFlushCache
|
||||
|
||||
#ifdef DEBUG
|
||||
REGISTER_COMMAND_NAME(@"flushcache")
|
||||
#endif
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return YES;
|
||||
@@ -77,8 +77,8 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
}
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.state = RULESTATE_UNKNOWN;
|
||||
newRule.type = RULETYPE_BINARY;
|
||||
newRule.state = SNTRuleStateUnknown;
|
||||
newRule.type = SNTRuleTypeBinary;
|
||||
|
||||
NSString *path;
|
||||
|
||||
@@ -87,15 +87,15 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
NSString *arg = arguments[i];
|
||||
|
||||
if ([arg caseInsensitiveCompare:@"--whitelist"] == NSOrderedSame) {
|
||||
newRule.state = RULESTATE_WHITELIST;
|
||||
newRule.state = SNTRuleStateWhitelist;
|
||||
} else if ([arg caseInsensitiveCompare:@"--blacklist"] == NSOrderedSame) {
|
||||
newRule.state = RULESTATE_BLACKLIST;
|
||||
newRule.state = SNTRuleStateBlacklist;
|
||||
} else if ([arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) {
|
||||
newRule.state = RULESTATE_SILENT_BLACKLIST;
|
||||
newRule.state = SNTRuleStateSilentBlacklist;
|
||||
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
|
||||
newRule.state = RULESTATE_REMOVE;
|
||||
newRule.state = SNTRuleStateRemove;
|
||||
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
|
||||
newRule.type = RULETYPE_CERT;
|
||||
newRule.type = SNTRuleTypeCertificate;
|
||||
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
|
||||
if (++i > arguments.count - 1) {
|
||||
[self printErrorUsageAndExit:@"--path requires an argument"];
|
||||
@@ -121,26 +121,29 @@ REGISTER_COMMAND_NAME(@"rule")
|
||||
|
||||
if (path) {
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
|
||||
if (newRule.type == RULETYPE_BINARY) {
|
||||
if (newRule.type == SNTRuleTypeBinary) {
|
||||
newRule.shasum = fi.SHA256;
|
||||
} else if (newRule.type == RULETYPE_CERT) {
|
||||
} else if (newRule.type == SNTRuleTypeCertificate) {
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.path];
|
||||
newRule.shasum = cs.leafCertificate.SHA256;
|
||||
}
|
||||
}
|
||||
|
||||
if (newRule.state == RULESTATE_UNKNOWN) {
|
||||
if (newRule.state == SNTRuleStateUnknown) {
|
||||
[self printErrorUsageAndExit:@"No state specified"];
|
||||
} else if (!newRule.shasum) {
|
||||
[self printErrorUsageAndExit:@"Either SHA-256 or path to file must be specified"];
|
||||
}
|
||||
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRule:newRule cleanSlate:NO reply:^(BOOL success) {
|
||||
if (!success) {
|
||||
printf("Failed to modify rules.");
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRules:@[newRule]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (error) {
|
||||
printf("Failed to modify rules: %s", [error.localizedDescription UTF8String]);
|
||||
LOGD(@"Failure reason: %@", error.localizedFailureReason);
|
||||
exit(1);
|
||||
} else {
|
||||
if (newRule.state == RULESTATE_REMOVE) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
@@ -50,16 +50,16 @@ REGISTER_COMMAND_NAME(@"status")
|
||||
__block uint64_t cpuEvents, ramEvents;
|
||||
__block double cpuPeak, ramPeak;
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] clientMode:^(santa_clientmode_t cm) {
|
||||
[[daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case CLIENTMODE_MONITOR:
|
||||
case SNTClientModeMonitor:
|
||||
clientMode = @"Monitor";
|
||||
break;
|
||||
case CLIENTMODE_LOCKDOWN:
|
||||
case SNTClientModeLockdown:
|
||||
clientMode = @"Lockdown";
|
||||
break;
|
||||
default:
|
||||
clientMode = [NSString stringWithFormat:@"Unknown (%d)", cm];
|
||||
clientMode = [NSString stringWithFormat:@"Unknown (%ld)", cm];
|
||||
break;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
@@ -14,7 +14,8 @@
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
|
||||
#import "SNTAuthenticatingURLSession.h"
|
||||
#import <MOLAuthenticatingURLSession.h>
|
||||
|
||||
#import "SNTCommandSyncEventUpload.h"
|
||||
#import "SNTCommandSyncLogUpload.h"
|
||||
#import "SNTCommandSyncPostflight.h"
|
||||
@@ -28,8 +29,6 @@
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTCommandSync : NSObject<SNTCommand>
|
||||
@property NSURLSession *session;
|
||||
@property SNTXPCConnection *daemonConn;
|
||||
@property SNTCommandSyncState *syncState;
|
||||
@end
|
||||
|
||||
@@ -50,50 +49,73 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return nil;
|
||||
return (@"If Santa is configured to synchronize with a a server, "
|
||||
@"this is the command used for syncing.\n\n"
|
||||
@"Options:\n"
|
||||
@" --clean: Perform a clean sync, erasing all existing rules and requesting a"
|
||||
@" clean sync from the server.");
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
LOGE(@"Failed to drop root privileges. Exiting.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
|
||||
SNTCommandSync *s = [[self alloc] init];
|
||||
|
||||
// Gather some data needed during some sync stages
|
||||
s.syncState = [[SNTCommandSyncState alloc] init];
|
||||
|
||||
s.syncState.syncBaseURL = config.syncBaseURL;
|
||||
if (s.syncState.syncBaseURL.absoluteString.length == 0) {
|
||||
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
|
||||
exit(1);
|
||||
} else if (![s.syncState.syncBaseURL.scheme isEqual:@"https"]) {
|
||||
LOGW(@"SyncBaseURL is not over HTTPS!");
|
||||
}
|
||||
|
||||
s.syncState.machineID = config.machineID;
|
||||
if (s.syncState.machineID.length == 0) {
|
||||
LOGE(@"Missing Machine ID. Can't sync without it.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s.syncState.machineOwner = config.machineOwner;
|
||||
if (s.syncState.machineOwner.length == 0) {
|
||||
s.syncState.machineOwner = @"";
|
||||
LOGW(@"Missing Machine Owner.");
|
||||
}
|
||||
|
||||
[[daemonConn remoteObjectProxy] xsrfToken:^(NSString *token) {
|
||||
s.syncState.xsrfToken = token;
|
||||
}];
|
||||
|
||||
// Dropping root privileges to the 'nobody' user causes the default NSURLCache to throw
|
||||
// sandbox errors, which are benign but annoying. This line disables the cache entirely.
|
||||
[NSURLCache setSharedURLCache:[[NSURLCache alloc] initWithMemoryCapacity:0
|
||||
diskCapacity:0
|
||||
diskPath:nil]];
|
||||
|
||||
SNTCommandSync *s = [[self alloc] init];
|
||||
|
||||
SNTAuthenticatingURLSession *authURLSession = [[SNTAuthenticatingURLSession alloc] init];
|
||||
|
||||
MOLAuthenticatingURLSession *authURLSession = [[MOLAuthenticatingURLSession alloc] init];
|
||||
authURLSession.userAgent = @"santactl-sync/";
|
||||
NSString *santactlVersion = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
if (santactlVersion) {
|
||||
authURLSession.userAgent = [authURLSession.userAgent stringByAppendingString:santactlVersion];
|
||||
}
|
||||
authURLSession.refusesRedirects = YES;
|
||||
authURLSession.serverHostname = s.syncState.syncBaseURL.host;
|
||||
authURLSession.loggingBlock = ^(NSString *line) {
|
||||
LOGD(@"%@", line);
|
||||
};
|
||||
|
||||
// Configure server auth
|
||||
if ([config syncServerAuthRootsFile]) {
|
||||
NSError *error = nil;
|
||||
|
||||
NSData *rootsData = [NSData dataWithContentsOfFile:[config syncServerAuthRootsFile]
|
||||
options:0
|
||||
error:&error];
|
||||
authURLSession.serverRootsPemData = rootsData;
|
||||
|
||||
if (!rootsData) {
|
||||
LOGE(@"Couldn't open server root certificate file %@ with error: %@.",
|
||||
[config syncServerAuthRootsFile],
|
||||
[error localizedDescription]);
|
||||
exit(1);
|
||||
}
|
||||
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
|
||||
} else if ([config syncServerAuthRootsData]) {
|
||||
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
|
||||
}
|
||||
@@ -108,134 +130,108 @@ REGISTER_COMMAND_NAME(@"sync")
|
||||
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
|
||||
}
|
||||
|
||||
s.session = [authURLSession session];
|
||||
s.daemonConn = daemonConn;
|
||||
s.syncState.session = [authURLSession session];
|
||||
s.syncState.daemonConn = daemonConn;
|
||||
|
||||
// Gather some data needed during some sync stages
|
||||
s.syncState = [[SNTCommandSyncState alloc] init];
|
||||
if ([arguments containsObject:@"singleevent"]) {
|
||||
NSUInteger idx = [arguments indexOfObject:@"singleevent"] + 1;
|
||||
if (idx >= arguments.count) {
|
||||
LOGI(@"singleevent takes an argument");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s.syncState.syncBaseURL = config.syncBaseURL;
|
||||
if (!s.syncState.syncBaseURL) {
|
||||
LOGE(@"Missing SyncBaseURL. Can't sync without it.");
|
||||
exit(1);
|
||||
} else if (![s.syncState.syncBaseURL.scheme isEqual:@"https"]) {
|
||||
LOGW(@"SyncBaseURL is not over HTTPS!");
|
||||
}
|
||||
authURLSession.serverHostname = s.syncState.syncBaseURL.host;
|
||||
|
||||
s.syncState.machineID = config.machineID;
|
||||
if ([s.syncState.machineID length] == 0) {
|
||||
LOGE(@"Missing Machine ID. Can't sync without it.");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
s.syncState.machineOwner = config.machineOwner;
|
||||
if ([s.syncState.machineOwner length] == 0) {
|
||||
s.syncState.machineOwner = @"";
|
||||
LOGW(@"Missing Machine Owner.");
|
||||
}
|
||||
|
||||
if (arguments.count == 2 && [[arguments firstObject] isEqual:@"singleevent"]) {
|
||||
[s eventUploadSingleEvent:arguments[1]];
|
||||
NSString *obj = arguments[idx];
|
||||
if (obj.length != 64) {
|
||||
LOGI(@"singleevent passed without SHA-256 as next argument");
|
||||
exit(1);
|
||||
}
|
||||
return [s eventUploadSingleEvent:obj];
|
||||
} else {
|
||||
[s preflight];
|
||||
return [s preflight];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)preflight {
|
||||
[SNTCommandSyncPreflight performSyncInSession:self.session
|
||||
syncState:self.syncState
|
||||
daemonConn:self.daemonConn
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Preflight complete");
|
||||
if (self.syncState.uploadLogURL) {
|
||||
[self logUpload];
|
||||
} else {
|
||||
[self eventUpload];
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Preflight failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Preflight complete");
|
||||
if (self.syncState.uploadLogURL) {
|
||||
return [self logUpload];
|
||||
} else {
|
||||
return [self eventUpload];
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Preflight failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logUpload {
|
||||
[SNTCommandSyncLogUpload performSyncInSession:self.session
|
||||
syncState:self.syncState
|
||||
daemonConn:self.daemonConn
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Log upload complete");
|
||||
} else {
|
||||
LOGE(@"Log upload failed, continuing anyway");
|
||||
}
|
||||
[self eventUpload];
|
||||
|
||||
}];
|
||||
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Log upload complete");
|
||||
} else {
|
||||
LOGE(@"Log upload failed, continuing anyway");
|
||||
}
|
||||
return [self eventUpload];
|
||||
}
|
||||
|
||||
- (void)eventUpload {
|
||||
[SNTCommandSyncEventUpload performSyncInSession:self.session
|
||||
syncState:self.syncState
|
||||
daemonConn:self.daemonConn
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Event upload complete");
|
||||
[self ruleDownload];
|
||||
} else {
|
||||
LOGE(@"Event upload failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Event upload complete");
|
||||
return [self ruleDownload];
|
||||
} else {
|
||||
LOGE(@"Event upload failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)eventUploadSingleEvent:(NSString *)sha256 {
|
||||
[SNTCommandSyncEventUpload uploadSingleEventWithSHA256:sha256
|
||||
session:self.session
|
||||
syncState:self.syncState
|
||||
daemonConn:self.daemonConn
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Event upload complete");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGW(@"Event upload failed");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p syncSingleEventWithSHA256:sha256]) {
|
||||
LOGD(@"Event upload complete");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Event upload failed");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)ruleDownload {
|
||||
[SNTCommandSyncRuleDownload performSyncInSession:self.session
|
||||
syncState:self.syncState
|
||||
daemonConn:self.daemonConn
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Rule download complete");
|
||||
[self postflight];
|
||||
} else {
|
||||
LOGE(@"Rule download failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Rule download complete");
|
||||
if (self.syncState.bundleBinaryRequests.count) {
|
||||
return [self eventUploadBundleBinaries];
|
||||
}
|
||||
return [self postflight];
|
||||
} else {
|
||||
LOGE(@"Rule download failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)eventUploadBundleBinaries {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p syncBundleEvents]) {
|
||||
LOGD(@"Event Upload bundle binaries complete");
|
||||
} else {
|
||||
LOGW(@"Event Upload bundle binary search failed");
|
||||
}
|
||||
return [self postflight];
|
||||
}
|
||||
|
||||
- (void)postflight {
|
||||
[SNTCommandSyncPostflight performSyncInSession:self.session
|
||||
syncState:self.syncState
|
||||
daemonConn:self.daemonConn
|
||||
completionHandler:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Postflight complete");
|
||||
LOGI(@"Sync completed successfully");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Postflight failed");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
SNTCommandSyncPostflight *p = [[SNTCommandSyncPostflight alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Postflight complete");
|
||||
LOGI(@"Sync completed successfully");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Postflight failed");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,10 +12,7 @@
|
||||
/// 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 kXSRFToken;
|
||||
|
||||
extern NSString *const kSerialNumber;
|
||||
extern NSString *const kHostname;
|
||||
@@ -32,6 +29,8 @@ extern NSString *const kClientModeLockdown;
|
||||
extern NSString *const kCleanSync;
|
||||
extern NSString *const kWhitelistRegex;
|
||||
extern NSString *const kBlacklistRegex;
|
||||
extern NSString *const kBinaryRuleCount;
|
||||
extern NSString *const kCertificateRuleCount;
|
||||
|
||||
extern NSString *const kEvents;
|
||||
extern NSString *const kFileSHA256;
|
||||
@@ -49,10 +48,11 @@ extern NSString *const kDecisionBlockBinary;
|
||||
extern NSString *const kDecisionBlockCertificate;
|
||||
extern NSString *const kDecisionBlockScope;
|
||||
extern NSString *const kDecisionUnknown;
|
||||
extern NSString *const kDecisionRelatedBinary;
|
||||
extern NSString *const kDecisionBundleBinary;
|
||||
extern NSString *const kLoggedInUsers;
|
||||
extern NSString *const kCurrentSessions;
|
||||
extern NSString *const kFileBundleID;
|
||||
extern NSString *const kFileBundlePath;
|
||||
extern NSString *const kFileBundleName;
|
||||
extern NSString *const kFileBundleVersion;
|
||||
extern NSString *const kFileBundleShortVersionString;
|
||||
@@ -70,6 +70,7 @@ extern NSString *const kQuarantineDataURL;
|
||||
extern NSString *const kQuarantineRefererURL;
|
||||
extern NSString *const kQuarantineTimestamp;
|
||||
extern NSString *const kQuarantineAgentBundleID;
|
||||
extern NSString *const kEventUploadBundleBinaries;
|
||||
|
||||
extern NSString *const kLogUploadField;
|
||||
|
||||
@@ -14,10 +14,7 @@
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
|
||||
NSString *const kURLPreflight = @"preflight/";
|
||||
NSString *const kURLEventUpload = @"eventupload/";
|
||||
NSString *const kURLRuleDownload = @"ruledownload/";
|
||||
NSString *const kURLPostflight = @"postflight/";
|
||||
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
|
||||
|
||||
NSString *const kSerialNumber = @"serial_num";
|
||||
NSString *const kHostname = @"hostname";
|
||||
@@ -34,6 +31,8 @@ NSString *const kClientModeLockdown = @"LOCKDOWN";
|
||||
NSString *const kCleanSync = @"clean_sync";
|
||||
NSString *const kWhitelistRegex = @"whitelist_regex";
|
||||
NSString *const kBlacklistRegex = @"blacklist_regex";
|
||||
NSString *const kBinaryRuleCount = @"binary_rule_count";
|
||||
NSString *const kCertificateRuleCount = @"certificate_rule_count";
|
||||
|
||||
NSString *const kEvents = @"events";
|
||||
NSString *const kFileSHA256 = @"file_sha256";
|
||||
@@ -51,10 +50,11 @@ NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
|
||||
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
|
||||
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
|
||||
NSString *const kDecisionUnknown = @"UNKNOWN";
|
||||
NSString *const kDecisionRelatedBinary = @"RELATED_BINARY";
|
||||
NSString *const kDecisionBundleBinary = @"BUNDLE_BINARY";
|
||||
NSString *const kLoggedInUsers = @"logged_in_users";
|
||||
NSString *const kCurrentSessions = @"current_sessions";
|
||||
NSString *const kFileBundleID = @"file_bundle_id";
|
||||
NSString *const kFileBundlePath = @"file_bundle_path";
|
||||
NSString *const kFileBundleName = @"file_bundle_name";
|
||||
NSString *const kFileBundleVersion = @"file_bundle_version";
|
||||
NSString *const kFileBundleShortVersionString = @"file_bundle_version_string";
|
||||
@@ -72,6 +72,7 @@ NSString *const kQuarantineDataURL = @"quarantine_data_url";
|
||||
NSString *const kQuarantineRefererURL = @"quarantine_referer_url";
|
||||
NSString *const kQuarantineTimestamp = @"quarantine_timestamp";
|
||||
NSString *const kQuarantineAgentBundleID = @"quarantine_agent_bundle_id";
|
||||
NSString *const kEventUploadBundleBinaries = @"event_upload_bundle_binaries";
|
||||
|
||||
NSString *const kLogUploadField = @"files";
|
||||
|
||||
@@ -12,14 +12,12 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncState;
|
||||
@class SNTXPCConnection;
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncLogUpload : NSObject
|
||||
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256;
|
||||
|
||||
- (BOOL)syncBundleEvents;
|
||||
|
||||
@end
|
||||
222
Source/santactl/Commands/sync/SNTCommandSyncEventUpload.m
Normal file
222
Source/santactl/Commands/sync/SNTCommandSyncEventUpload.m
Normal file
@@ -0,0 +1,222 @@
|
||||
/// 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 "SNTCommandSyncEventUpload.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "NSData+Zlib.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@implementation SNTCommandSyncEventUpload
|
||||
|
||||
- (NSURL *)stageURL {
|
||||
NSString *stageName = [@"eventupload" stringByAppendingFormat:@"/%@", self.syncState.machineID];
|
||||
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
|
||||
}
|
||||
|
||||
- (BOOL)sync {
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
[[self.daemonConn remoteObjectProxy] databaseEventsPending:^(NSArray *events) {
|
||||
if (events.count) {
|
||||
[self uploadEvents:events];
|
||||
}
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256 {
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
[[self.daemonConn remoteObjectProxy] databaseEventForSHA256:sha256 reply:^(SNTStoredEvent *e) {
|
||||
if (e) {
|
||||
[self uploadEvents:@[ e ]];
|
||||
}
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
return (dispatch_semaphore_wait(sema, DISPATCH_TIME_FOREVER) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)syncBundleEvents {
|
||||
NSMutableArray *newEvents = [NSMutableArray array];
|
||||
for (NSString *bundlePath in self.syncState.bundleBinaryRequests) {
|
||||
[newEvents addObjectsFromArray:[self findRelatedBinaries:bundlePath]];
|
||||
}
|
||||
return [self uploadEvents:newEvents];
|
||||
}
|
||||
|
||||
- (BOOL)uploadEvents:(NSArray *)events {
|
||||
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
|
||||
|
||||
NSMutableDictionary *eventIds = [NSMutableDictionary dictionaryWithCapacity:events.count];
|
||||
for (SNTStoredEvent *event in events) {
|
||||
[uploadEvents addObject:[self dictionaryForEvent:event]];
|
||||
eventIds[event.idx] = @YES;
|
||||
if (uploadEvents.count >= self.syncState.eventBatchSize) break;
|
||||
}
|
||||
|
||||
NSDictionary *r = [self performRequest:[self requestWithDictionary:@{ kEvents: uploadEvents }]];
|
||||
if (!r) return NO;
|
||||
|
||||
// Keep track of bundle search requests
|
||||
self.syncState.bundleBinaryRequests = r[kEventUploadBundleBinaries];
|
||||
|
||||
LOGI(@"Uploaded %lu events", uploadEvents.count);
|
||||
|
||||
// Remove event IDs. For Bundle Events the ID is 0 so nothing happens.
|
||||
[[self.daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allKeys]];
|
||||
|
||||
// See if there are any events remaining to upload
|
||||
if (uploadEvents.count < events.count) {
|
||||
NSRange nextEventsRange = NSMakeRange(uploadEvents.count, events.count - uploadEvents.count);
|
||||
NSArray *nextEvents = [events subarrayWithRange:nextEventsRange];
|
||||
return [self uploadEvents:nextEvents];
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (NSDictionary *)dictionaryForEvent:(SNTStoredEvent *)event {
|
||||
#define ADDKEY(dict, key, value) if (value) dict[key] = value
|
||||
NSMutableDictionary *newEvent = [NSMutableDictionary dictionary];
|
||||
|
||||
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, kLoggedInUsers, event.loggedInUsers);
|
||||
ADDKEY(newEvent, kCurrentSessions, event.currentSessions);
|
||||
|
||||
switch (event.decision) {
|
||||
case SNTEventStateAllowUnknown: ADDKEY(newEvent, kDecision, kDecisionAllowUnknown); break;
|
||||
case SNTEventStateAllowBinary: ADDKEY(newEvent, kDecision, kDecisionAllowBinary); break;
|
||||
case SNTEventStateAllowCertificate:
|
||||
ADDKEY(newEvent, kDecision, kDecisionAllowCertificate);
|
||||
break;
|
||||
case SNTEventStateAllowScope: ADDKEY(newEvent, kDecision, kDecisionAllowScope); break;
|
||||
case SNTEventStateBlockUnknown: ADDKEY(newEvent, kDecision, kDecisionBlockUnknown); break;
|
||||
case SNTEventStateBlockBinary: ADDKEY(newEvent, kDecision, kDecisionBlockBinary); break;
|
||||
case SNTEventStateBlockCertificate:
|
||||
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
|
||||
break;
|
||||
case SNTEventStateBlockScope: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
|
||||
case SNTEventStateBundleBinary: ADDKEY(newEvent, kDecision, kDecisionBundleBinary); break;
|
||||
default: ADDKEY(newEvent, kDecision, kDecisionUnknown);
|
||||
}
|
||||
|
||||
ADDKEY(newEvent, kFileBundleID, event.fileBundleID);
|
||||
ADDKEY(newEvent, kFileBundlePath, event.fileBundlePath);
|
||||
ADDKEY(newEvent, kFileBundleName, event.fileBundleName);
|
||||
ADDKEY(newEvent, kFileBundleVersion, event.fileBundleVersion);
|
||||
ADDKEY(newEvent, kFileBundleShortVersionString, event.fileBundleVersionString);
|
||||
|
||||
ADDKEY(newEvent, kPID, event.pid);
|
||||
ADDKEY(newEvent, kPPID, event.ppid);
|
||||
ADDKEY(newEvent, kParentName, event.parentName);
|
||||
|
||||
ADDKEY(newEvent, kQuarantineDataURL, event.quarantineDataURL);
|
||||
ADDKEY(newEvent, kQuarantineRefererURL, event.quarantineRefererURL);
|
||||
ADDKEY(newEvent, kQuarantineTimestamp, @([event.quarantineTimestamp timeIntervalSince1970]));
|
||||
ADDKEY(newEvent, kQuarantineAgentBundleID, event.quarantineAgentBundleID);
|
||||
|
||||
NSMutableArray *signingChain = [NSMutableArray arrayWithCapacity:event.signingChain.count];
|
||||
for (NSUInteger i = 0; i < event.signingChain.count; ++i) {
|
||||
MOLCertificate *cert = [event.signingChain objectAtIndex:i];
|
||||
|
||||
NSMutableDictionary *certDict = [NSMutableDictionary dictionary];
|
||||
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[kSigningChain] = signingChain;
|
||||
|
||||
return newEvent;
|
||||
#undef ADDKEY
|
||||
}
|
||||
|
||||
// Find binaries within a bundle given the bundle's path
|
||||
// Searches for 10 minutes, creating new events.
|
||||
- (NSArray *)findRelatedBinaries:(NSString *)path {
|
||||
SNTFileInfo *requestedPath = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
// Prevent processing the same bundle twice.
|
||||
static NSMutableDictionary *previouslyProcessedBundles;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
previouslyProcessedBundles = [NSMutableDictionary dictionary];
|
||||
});
|
||||
if (previouslyProcessedBundles[requestedPath.bundleIdentifier]) return nil;
|
||||
previouslyProcessedBundles[requestedPath.bundleIdentifier] = @YES;
|
||||
|
||||
NSMutableArray *relatedEvents = [NSMutableArray array];
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block BOOL shouldCancel = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:path];
|
||||
NSString *file;
|
||||
|
||||
while (file = [dirEnum nextObject]) {
|
||||
@autoreleasepool {
|
||||
if (shouldCancel) break;
|
||||
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
|
||||
|
||||
file = [path stringByAppendingPathComponent:file];
|
||||
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:file];
|
||||
if (fi.isExecutable) {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
se.filePath = fi.path;
|
||||
se.fileSHA256 = fi.SHA256;
|
||||
se.decision = SNTEventStateBundleBinary;
|
||||
se.fileBundleID = fi.bundleIdentifier;
|
||||
se.fileBundleName = fi.bundleName;
|
||||
se.fileBundlePath = fi.bundlePath;
|
||||
se.fileBundleVersion = fi.bundleVersion;
|
||||
se.fileBundleVersionString = fi.bundleShortVersionString;
|
||||
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
|
||||
se.signingChain = cs.certificates;
|
||||
|
||||
[relatedEvents addObject:[self dictionaryForEvent:se]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(sema);
|
||||
});
|
||||
|
||||
// Give the search up to 10m per bundle to run.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 600))) {
|
||||
shouldCancel = YES;
|
||||
LOGD(@"Timed out while searching for related events at path %@", path);
|
||||
}
|
||||
|
||||
return relatedEvents;
|
||||
}
|
||||
|
||||
@end
|
||||
18
Source/santactl/Commands/sync/SNTCommandSyncLogUpload.h
Normal file
18
Source/santactl/Commands/sync/SNTCommandSyncLogUpload.h
Normal file
@@ -0,0 +1,18 @@
|
||||
/// 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 "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncLogUpload : SNTCommandSyncStage
|
||||
@end
|
||||
@@ -15,49 +15,40 @@
|
||||
#import "SNTCommandSyncLogUpload.h"
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTLogging.h"
|
||||
|
||||
@implementation SNTCommandSyncLogUpload
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSURL *url = syncState.uploadLogURL;
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
NSString *boundary = @"----santa-sync-upload-boundary";
|
||||
- (NSString *)stageName {
|
||||
return @"logupload";
|
||||
}
|
||||
|
||||
- (NSURL *)stageURL {
|
||||
return self.syncState.uploadLogURL;
|
||||
}
|
||||
|
||||
- (BOOL)sync {
|
||||
NSMutableURLRequest *req = [self requestWithDictionary:nil];
|
||||
|
||||
NSString *boundary = @"----santa-sync-upload-boundary";
|
||||
NSString *contentType =
|
||||
[NSString stringWithFormat:@"multipart/form-data; charset=UTF-8; boundary=%@", boundary];
|
||||
[req setValue:contentType forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
NSArray *logsToUpload = [self logsToUpload];
|
||||
[req setHTTPBody:[self requestBodyWithLogs:logsToUpload andBoundary:boundary]];
|
||||
|
||||
// Upload the logs
|
||||
[[session uploadTaskWithRequest:req
|
||||
fromData:[self requestBodyWithLogs:logsToUpload andBoundary:boundary]
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
LOGD(@"%@", error);
|
||||
handler(NO);
|
||||
} else {
|
||||
LOGI(@"Uploaded %lu logs", [logsToUpload count]);
|
||||
handler(YES);
|
||||
}
|
||||
}] resume];
|
||||
NSDictionary *d = [self performRequest:req];
|
||||
if (!d) return NO;
|
||||
|
||||
LOGI(@"Uploaded %lu logs", logsToUpload.count);
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSData *)requestBodyWithLogs:(NSArray *)logsToUpload andBoundary:(NSString *)boundary {
|
||||
- (NSData *)requestBodyWithLogs:(NSArray *)logsToUpload andBoundary:(NSString *)boundary {
|
||||
// Prepare the body of the request, encoded as a multipart/form-data.
|
||||
// Along the way, gzip the individual log files and append .gz to their filenames.
|
||||
NSMutableData *reqBody = [[NSMutableData alloc] init];
|
||||
@@ -80,7 +71,7 @@
|
||||
return reqBody;
|
||||
}
|
||||
|
||||
+ (NSArray *)logsToUpload {
|
||||
- (NSArray *)logsToUpload {
|
||||
// General logs
|
||||
NSMutableArray *logsToUpload = [@[ @"/var/log/santa.log",
|
||||
@"/var/log/system.log" ] mutableCopy];
|
||||
@@ -12,14 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncState;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
@interface SNTCommandSyncPostflight : NSObject
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncPostflight : SNTCommandSyncStage
|
||||
@end
|
||||
82
Source/santactl/Commands/sync/SNTCommandSyncPostflight.m
Normal file
82
Source/santactl/Commands/sync/SNTCommandSyncPostflight.m
Normal file
@@ -0,0 +1,82 @@
|
||||
/// 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 "SNTCommandSyncPostflight.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@implementation SNTCommandSyncPostflight
|
||||
|
||||
- (NSURL *)stageURL {
|
||||
NSString *stageName = [@"postflight" stringByAppendingFormat:@"/%@", self.syncState.machineID];
|
||||
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
|
||||
}
|
||||
|
||||
- (BOOL)sync {
|
||||
NSDictionary *r = [self performRequest:[self requestWithDictionary:nil]];
|
||||
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
void (^replyBlock)() = ^{
|
||||
dispatch_group_leave(group);
|
||||
};
|
||||
|
||||
// Set client mode if it changed
|
||||
if (self.syncState.clientMode) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] setClientMode:self.syncState.clientMode
|
||||
reply:replyBlock];
|
||||
}
|
||||
|
||||
// Update backoff interval
|
||||
NSString *backoffInterval = r[kBackoffInterval];
|
||||
if (backoffInterval) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue]
|
||||
reply:replyBlock];
|
||||
}
|
||||
|
||||
// Remove clean sync flag if we did a clean sync
|
||||
if (self.syncState.cleanSync) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] setSyncCleanRequired:NO reply:replyBlock];
|
||||
}
|
||||
|
||||
// Update whitelist/blacklist regexes
|
||||
if (self.syncState.whitelistRegex) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] setWhitelistPathRegex:self.syncState.whitelistRegex
|
||||
reply:replyBlock];
|
||||
}
|
||||
if (self.syncState.blacklistRegex) {
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] setBlacklistPathRegex:self.syncState.blacklistRegex
|
||||
reply:replyBlock];
|
||||
}
|
||||
|
||||
// Update last sync success
|
||||
dispatch_group_enter(group);
|
||||
[[self.daemonConn remoteObjectProxy] setSyncLastSuccess:[NSDate date] reply:replyBlock];
|
||||
|
||||
// Wait for dispatch group
|
||||
dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, 5 * NSEC_PER_SEC));
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,14 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncState;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
@interface SNTCommandSyncPreflight : NSObject
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncPreflight : SNTCommandSyncStage
|
||||
@end
|
||||
90
Source/santactl/Commands/sync/SNTCommandSyncPreflight.m
Normal file
90
Source/santactl/Commands/sync/SNTCommandSyncPreflight.m
Normal file
@@ -0,0 +1,90 @@
|
||||
/// 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 "SNTCommandSyncPreflight.h"
|
||||
|
||||
#include "SNTKernelCommon.h"
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTSystemInfo.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@implementation SNTCommandSyncPreflight
|
||||
|
||||
- (NSURL *)stageURL {
|
||||
NSString *stageName = [@"preflight" stringByAppendingFormat:@"/%@", self.syncState.machineID];
|
||||
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
|
||||
}
|
||||
|
||||
- (BOOL)sync {
|
||||
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
|
||||
requestDict[kSerialNumber] = [SNTSystemInfo serialNumber];
|
||||
requestDict[kHostname] = [SNTSystemInfo longHostname];
|
||||
requestDict[kOSVer] = [SNTSystemInfo osVersion];
|
||||
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
|
||||
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
requestDict[kPrimaryUser] = self.syncState.machineOwner;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
|
||||
requestDict[kBinaryRuleCount] = @(binary);
|
||||
requestDict[kCertificateRuleCount] = @(certificate);
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, 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]) {
|
||||
requestDict[kRequestCleanSync] = @YES;
|
||||
}
|
||||
|
||||
NSURLRequest *req = [self requestWithDictionary:requestDict];
|
||||
NSDictionary *resp = [self performRequest:req];
|
||||
|
||||
if (!resp) return NO;
|
||||
|
||||
self.syncState.eventBatchSize = [resp[kBatchSize] intValue];
|
||||
if (self.syncState.eventBatchSize == 0) {
|
||||
self.syncState.eventBatchSize = 50;
|
||||
}
|
||||
|
||||
self.syncState.uploadLogURL = [NSURL URLWithString:resp[kUploadLogsURL]];
|
||||
|
||||
if ([resp[kClientMode] isEqual:kClientModeMonitor]) {
|
||||
self.syncState.clientMode = SNTClientModeMonitor;
|
||||
} else if ([resp[kClientMode] isEqual:kClientModeLockdown]) {
|
||||
self.syncState.clientMode = SNTClientModeLockdown;
|
||||
}
|
||||
|
||||
if ([resp[kWhitelistRegex] isKindOfClass:[NSString class]]) {
|
||||
self.syncState.whitelistRegex = resp[kWhitelistRegex];
|
||||
}
|
||||
|
||||
if ([resp[kBlacklistRegex] isKindOfClass:[NSString class]]) {
|
||||
self.syncState.blacklistRegex = resp[kBlacklistRegex];
|
||||
}
|
||||
|
||||
if ([resp[kCleanSync] boolValue]) {
|
||||
self.syncState.cleanSync = YES;
|
||||
}
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,14 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncState;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
@interface SNTCommandSyncRuleDownload : NSObject
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncRuleDownload : SNTCommandSyncStage
|
||||
@end
|
||||
111
Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.m
Normal file
111
Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.m
Normal file
@@ -0,0 +1,111 @@
|
||||
/// 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 "SNTCommandSyncRuleDownload.h"
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
@implementation SNTCommandSyncRuleDownload
|
||||
|
||||
- (NSURL *)stageURL {
|
||||
NSString *stageName = [@"ruledownload" stringByAppendingFormat:@"/%@", self.syncState.machineID];
|
||||
return [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
|
||||
}
|
||||
|
||||
- (BOOL)sync {
|
||||
self.syncState.downloadedRules = [NSMutableArray array];
|
||||
return [self ruleDownloadWithCursor:nil];
|
||||
}
|
||||
|
||||
- (BOOL)ruleDownloadWithCursor:(NSString *)cursor {
|
||||
NSDictionary *requestDict = (cursor ? @{kCursor : cursor} : @{});
|
||||
|
||||
NSDictionary *resp = [self performRequest:[self requestWithDictionary:requestDict]];
|
||||
if (!resp) return NO;
|
||||
|
||||
for (NSDictionary *rule in resp[kRules]) {
|
||||
SNTRule *r = [self ruleFromDictionary:rule];
|
||||
if (r) [self.syncState.downloadedRules addObject:r];
|
||||
}
|
||||
|
||||
if (resp[kCursor]) {
|
||||
return [self ruleDownloadWithCursor:resp[kCursor]];
|
||||
}
|
||||
|
||||
if (!self.syncState.downloadedRules.count) return YES;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block NSError *error;
|
||||
[[self.daemonConn remoteObjectProxy] databaseRuleAddRules:self.syncState.downloadedRules
|
||||
cleanSlate:self.syncState.cleanSync
|
||||
reply:^(NSError *e) {
|
||||
error = e;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 300 * NSEC_PER_SEC));
|
||||
|
||||
if (error) {
|
||||
LOGE(@"Failed to add rule(s) to database: %@", error.localizedDescription);
|
||||
LOGD(@"Failure reason: %@", error.localizedFailureReason);
|
||||
return NO;
|
||||
}
|
||||
|
||||
LOGI(@"Added %lu rules", self.syncState.downloadedRules.count);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (SNTRule *)ruleFromDictionary:(NSDictionary *)dict {
|
||||
if (![dict isKindOfClass:[NSDictionary class]]) return nil;
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.shasum = dict[kRuleSHA256];
|
||||
if (newRule.shasum.length != 64) return nil;
|
||||
|
||||
NSString *policyString = dict[kRulePolicy];
|
||||
if ([policyString isEqual:kRulePolicyWhitelist]) {
|
||||
newRule.state = SNTRuleStateWhitelist;
|
||||
} else if ([policyString isEqual:kRulePolicyBlacklist]) {
|
||||
newRule.state = SNTRuleStateBlacklist;
|
||||
} else if ([policyString isEqual:kRulePolicySilentBlacklist]) {
|
||||
newRule.state = SNTRuleStateSilentBlacklist;
|
||||
} else if ([policyString isEqual:kRulePolicyRemove]) {
|
||||
newRule.state = SNTRuleStateRemove;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *ruleTypeString = dict[kRuleType];
|
||||
if ([ruleTypeString isEqual:kRuleTypeBinary]) {
|
||||
newRule.type = SNTRuleTypeBinary;
|
||||
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
|
||||
newRule.type = SNTRuleTypeCertificate;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *customMsg = dict[kRuleCustomMsg];
|
||||
if (customMsg.length) {
|
||||
newRule.customMsg = customMsg;
|
||||
}
|
||||
|
||||
return newRule;
|
||||
}
|
||||
|
||||
@end
|
||||
72
Source/santactl/Commands/sync/SNTCommandSyncStage.h
Normal file
72
Source/santactl/Commands/sync/SNTCommandSyncStage.h
Normal file
@@ -0,0 +1,72 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncState;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
@interface SNTCommandSyncStage : NSObject
|
||||
|
||||
@property(readonly, nonnull) NSURLSession *urlSession;
|
||||
@property(readonly, nonnull) SNTCommandSyncState *syncState;
|
||||
@property(readonly, nonnull) SNTXPCConnection *daemonConn;
|
||||
|
||||
/**
|
||||
Initialize this stage. Designated initializer.
|
||||
|
||||
@param syncState A holder for state used across requests
|
||||
*/
|
||||
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)state NS_DESIGNATED_INITIALIZER;
|
||||
|
||||
- (nullable instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
/**
|
||||
Performs this sync stage.
|
||||
|
||||
@return YES if sync was successful.
|
||||
*/
|
||||
- (BOOL)sync;
|
||||
|
||||
/**
|
||||
The URL for this stage.
|
||||
|
||||
@return The NSURL for this stage.
|
||||
*/
|
||||
- (nonnull NSURL *)stageURL;
|
||||
|
||||
#pragma mark Internal Helpers
|
||||
|
||||
/**
|
||||
Creates an NSMutableURLRequest pointing at the URL for this stage and containing the JSON-encoded
|
||||
data passed in as a dictionary.
|
||||
|
||||
@param dictionary The values to POST to the server.
|
||||
*/
|
||||
- (nullable NSMutableURLRequest *)requestWithDictionary:(nullable NSDictionary *)dictionary;
|
||||
|
||||
/**
|
||||
Perform the passed in request and attempt to parse the response as JSON into a dictionary.
|
||||
|
||||
@param request The request to perform
|
||||
@param timeout The number of seconds to allow the request to run before timing out.
|
||||
|
||||
@return A populated dictionary if the response data was JSON, an empty dictionary if not and nil
|
||||
if the request failed for any reason.
|
||||
*/
|
||||
- (nullable NSDictionary *)performRequest:(nonnull NSURLRequest *)request
|
||||
timeout:(NSTimeInterval)timeout;
|
||||
|
||||
/** Convenience version of performRequest:timeout: using a 30s timeout. */
|
||||
- (nullable NSDictionary *)performRequest:(nonnull NSURLRequest *)request;
|
||||
|
||||
@end
|
||||
195
Source/santactl/Commands/sync/SNTCommandSyncStage.m
Normal file
195
Source/santactl/Commands/sync/SNTCommandSyncStage.m
Normal file
@@ -0,0 +1,195 @@
|
||||
/// Copyright 2016 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
@interface SNTCommandSyncStage ()
|
||||
|
||||
@property(readwrite) NSURLSession *urlSession;
|
||||
@property(readwrite) SNTCommandSyncState *syncState;
|
||||
@property(readwrite) SNTXPCConnection *daemonConn;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTCommandSyncStage
|
||||
|
||||
- (nullable instancetype)initWithState:(nonnull SNTCommandSyncState *)syncState {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_syncState = syncState;
|
||||
_urlSession = syncState.session;
|
||||
_daemonConn = syncState.daemonConn;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)sync {
|
||||
[self doesNotRecognizeSelector:_cmd]; __builtin_unreachable();
|
||||
}
|
||||
|
||||
- (NSString *)stageURL {
|
||||
[self doesNotRecognizeSelector:_cmd]; __builtin_unreachable();
|
||||
}
|
||||
|
||||
- (NSMutableURLRequest *)requestWithDictionary:(NSDictionary *)dictionary {
|
||||
NSData *requestBody = [NSData data];
|
||||
if (dictionary) {
|
||||
NSError *error;
|
||||
requestBody = [NSJSONSerialization dataWithJSONObject:dictionary options:0 error:&error];
|
||||
if (error) {
|
||||
LOGD(@"Failed to encode JSON request: %@", error);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:[self stageURL]];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[req setValue:self.syncState.xsrfToken forHTTPHeaderField:kXSRFToken];
|
||||
|
||||
NSData *compressed = [requestBody zlibCompressed];
|
||||
if (compressed) {
|
||||
requestBody = compressed;
|
||||
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
|
||||
}
|
||||
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
return req;
|
||||
}
|
||||
|
||||
- (NSDictionary *)performRequest:(NSURLRequest *)request timeout:(NSTimeInterval)timeout {
|
||||
NSHTTPURLResponse *response;
|
||||
NSError *error;
|
||||
NSData *data = [self performRequest:request timeout:timeout response:&response error:&error];
|
||||
|
||||
// If the original request failed, attempt to get a new XSRF token and try again.
|
||||
// Unfortunately some servers cause NSURLSession to return 'client cert required' or
|
||||
// 'could not parse response' when a 403 occurs and SSL cert auth is enabled.
|
||||
if ((response.statusCode == 403 ||
|
||||
error.code == NSURLErrorClientCertificateRequired ||
|
||||
error.code == NSURLErrorCannotParseResponse) &&
|
||||
[self fetchXSRFToken]) {
|
||||
NSMutableURLRequest *mutableRequest = [request mutableCopy];
|
||||
[mutableRequest setValue:self.syncState.xsrfToken forHTTPHeaderField:kXSRFToken];
|
||||
return [self performRequest:mutableRequest timeout:timeout];
|
||||
}
|
||||
|
||||
if (response.statusCode != 200) {
|
||||
long code;
|
||||
NSString *errStr;
|
||||
if (response.statusCode > 0) {
|
||||
code = response.statusCode;
|
||||
errStr = [NSHTTPURLResponse localizedStringForStatusCode:response.statusCode];
|
||||
} else {
|
||||
code = (long)error.code;
|
||||
errStr = error.localizedDescription;
|
||||
}
|
||||
LOGE(@"HTTP Response: %ld %@", code, errStr);
|
||||
return nil;
|
||||
}
|
||||
|
||||
if (data.length == 0) return @{};
|
||||
|
||||
NSDictionary *dict = [NSJSONSerialization JSONObjectWithData:[self stripXssi:data]
|
||||
options:0
|
||||
error:&error];
|
||||
if (error) LOGD(@"Failed to decode JSON response: %@", error);
|
||||
|
||||
return dict ?: @{};
|
||||
}
|
||||
|
||||
- (NSDictionary *)performRequest:(NSURLRequest *)request {
|
||||
return [self performRequest:request timeout:30];
|
||||
}
|
||||
|
||||
#pragma mark Internal Helpers
|
||||
|
||||
/**
|
||||
Perform a data request and capture the returned data, response and error objects.
|
||||
|
||||
@param request, The request to perform
|
||||
@param timeout, The number of seconds to wait before cancelling the request
|
||||
@param response, Return the response details
|
||||
@param error, Return the error details
|
||||
@returns data, The HTTP body of the response
|
||||
*/
|
||||
- (NSData *)performRequest:(NSURLRequest *)request
|
||||
timeout:(NSTimeInterval)timeout
|
||||
response:(out NSHTTPURLResponse **)response
|
||||
error:(out NSError **)error {
|
||||
__block NSData *_data;
|
||||
__block NSHTTPURLResponse *_response;
|
||||
__block NSError *_error;
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
NSURLSessionDataTask *task = [self.urlSession dataTaskWithRequest:request
|
||||
completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
if ([response isKindOfClass:[NSHTTPURLResponse class]]) {
|
||||
_response = (NSHTTPURLResponse *)response;
|
||||
}
|
||||
_data = data;
|
||||
_error = error;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
[task resume];
|
||||
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * timeout))) {
|
||||
[task cancel];
|
||||
}
|
||||
|
||||
if (response) *response = _response;
|
||||
if (error) *error = _error;
|
||||
return _data;
|
||||
}
|
||||
|
||||
- (NSData *)stripXssi:(NSData *)data {
|
||||
static const char xssi[3] = { ']', ')', '}' };
|
||||
if (data.length < 3 || strncmp(data.bytes, xssi, 3)) return data;
|
||||
return [data subdataWithRange:NSMakeRange(3, data.length - 3)];
|
||||
}
|
||||
|
||||
- (BOOL)fetchXSRFToken {
|
||||
__block BOOL success = NO;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{ // only fetch token once per session
|
||||
NSString *stageName = [@"xsrf" stringByAppendingFormat:@"/%@", self.syncState.machineID];
|
||||
NSURL *u = [NSURL URLWithString:stageName relativeToURL:self.syncState.syncBaseURL];
|
||||
NSMutableURLRequest *request = [NSMutableURLRequest requestWithURL:u];
|
||||
[request setHTTPMethod:@"POST"];
|
||||
NSHTTPURLResponse *response;
|
||||
[self performRequest:request timeout:10 response:&response error:NULL];
|
||||
if (response.statusCode == 200) {
|
||||
NSDictionary *headers = [response allHeaderFields];
|
||||
[[self.daemonConn remoteObjectProxy] setXsrfToken:headers[kXSRFToken] reply:^{}];
|
||||
self.syncState.xsrfToken = headers[kXSRFToken];
|
||||
LOGD(@"Retrieved new XSRF token");
|
||||
success = YES;
|
||||
} else {
|
||||
LOGD(@"Failed to retrieve XSRF token");
|
||||
}
|
||||
});
|
||||
return success;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,33 +12,48 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
@class SNTXPCConnection;
|
||||
|
||||
/// An instance of this class is passed to each stage of the sync process for storing data
|
||||
/// that might be needed in later stages.
|
||||
@interface SNTCommandSyncState : NSObject
|
||||
|
||||
/// The base API URL
|
||||
/// Configured session to use for requests.
|
||||
@property NSURLSession *session;
|
||||
|
||||
/// Connection to the daemon control interface.
|
||||
@property SNTXPCConnection *daemonConn;
|
||||
|
||||
/// The base API URL.
|
||||
@property NSURL *syncBaseURL;
|
||||
|
||||
/// Machine identifier and owner
|
||||
/// An XSRF token to send in the headers with each request.
|
||||
@property NSString *xsrfToken;
|
||||
|
||||
/// Machine identifier and owner.
|
||||
@property(copy) NSString *machineID;
|
||||
@property(copy) NSString *machineOwner;
|
||||
|
||||
/// Clean sync flag, sent from server. If True, all existing rules
|
||||
/// should be deleted before inserting any new rules.
|
||||
/// Settings sent from server during preflight that are set during postflight.
|
||||
@property SNTClientMode clientMode;
|
||||
@property NSString *whitelistRegex;
|
||||
@property NSString *blacklistRegex;
|
||||
|
||||
/// Clean sync flag, if True, all existing rules should be deleted before inserting any new rules.
|
||||
@property BOOL cleanSync;
|
||||
|
||||
/// New client mode sent from server
|
||||
@property santa_clientmode_t newClientMode;
|
||||
/// Batch size for uploading events.
|
||||
@property NSUInteger eventBatchSize;
|
||||
|
||||
/// Batch size for uploading events, sent from server
|
||||
@property int32_t eventBatchSize;
|
||||
|
||||
/// Log upload URL sent from server
|
||||
/// Log upload URL sent from server. If set, LogUpload phase needs to happen.
|
||||
@property NSURL *uploadLogURL;
|
||||
|
||||
/// Rules downloaded from server
|
||||
/// Array of bundle paths to find binaries for.
|
||||
@property NSArray *bundleBinaryRequests;
|
||||
|
||||
/// Rules downloaded from server.
|
||||
@property NSMutableArray *downloadedRules;
|
||||
|
||||
@end
|
||||
@@ -55,9 +55,12 @@ static NSMutableDictionary *registeredCommands;
|
||||
+ (NSString *)helpForCommandWithName:(NSString *)commandName {
|
||||
Class<SNTCommand> command = registeredCommands[commandName];
|
||||
if (command) {
|
||||
NSString *shortHelp = [command shortHelpText];
|
||||
NSString *longHelp = [command longHelpText];
|
||||
if (longHelp) {
|
||||
return [NSString stringWithFormat:@"Help for '%@':\n%@", commandName, longHelp];
|
||||
} else if (shortHelp) {
|
||||
return [NSString stringWithFormat:@"Help for '%@':\n%@", commandName, shortHelp];
|
||||
} else {
|
||||
return @"This command does not have any help information.";
|
||||
}
|
||||
@@ -65,15 +68,22 @@ static NSMutableDictionary *registeredCommands;
|
||||
return nil;
|
||||
}
|
||||
|
||||
+ (SNTXPCConnection *)connectToDaemon {
|
||||
+ (SNTXPCConnection *)connectToDaemonRequired:(BOOL)required {
|
||||
SNTXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
if (required) {
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
[daemonConn resume];
|
||||
} else {
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0), ^{
|
||||
[daemonConn resume];
|
||||
});
|
||||
}
|
||||
|
||||
|
||||
[daemonConn resume];
|
||||
return daemonConn;
|
||||
}
|
||||
|
||||
@@ -89,11 +99,7 @@ static NSMutableDictionary *registeredCommands;
|
||||
exit(2);
|
||||
}
|
||||
|
||||
SNTXPCConnection *daemonConn;
|
||||
if ([command requiresDaemonConn]) {
|
||||
daemonConn = [self connectToDaemon];
|
||||
}
|
||||
|
||||
SNTXPCConnection *daemonConn = [self connectToDaemonRequired:[command requiresDaemonConn]];
|
||||
[command runWithArguments:arguments daemonConnection:daemonConn];
|
||||
|
||||
// The command is responsible for quitting.
|
||||
|
||||
@@ -1,84 +0,0 @@
|
||||
/// 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.
|
||||
|
||||
///
|
||||
/// An authenticating NSURLSession, which can do both pinned verification of the SSL server
|
||||
/// and handle client certificate authentication from the keychain.
|
||||
///
|
||||
@interface SNTAuthenticatingURLSession : NSObject<NSURLSessionDelegate>
|
||||
|
||||
///
|
||||
/// The underlying session. Pass this session to NSURLRequest methods.
|
||||
///
|
||||
@property(readonly) NSURLSession *session;
|
||||
|
||||
///
|
||||
/// If set, this is the user-agent to send with requests, otherwise remains the default
|
||||
/// CFNetwork-based name.
|
||||
///
|
||||
@property(copy, nonatomic) NSString *userAgent;
|
||||
|
||||
///
|
||||
/// If set to YES, this session refuses redirect requests. Defaults to NO.
|
||||
///
|
||||
@property(nonatomic) BOOL refusesRedirects;
|
||||
|
||||
///
|
||||
/// If set, the server that we connect to _must_ match this string. Redirects to other
|
||||
/// hosts will not be allowed.
|
||||
///
|
||||
@property(copy, nonatomic) NSString *serverHostname;
|
||||
|
||||
///
|
||||
/// This should be PEM data containing one or more certificates to use to verify the server's
|
||||
/// certificate chain. This will override the trusted roots in the System Roots.
|
||||
///
|
||||
@property(copy, nonatomic) NSData *serverRootsPemData;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, the pkcs#12 file will be loaded
|
||||
///
|
||||
@property(copy, nonatomic) NSString *clientCertFile;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, the password being used for
|
||||
/// loading the clientCertFile
|
||||
///
|
||||
@property(copy, nonatomic) NSString *clientCertPassword;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, will search the keychain for a
|
||||
/// certificate matching this common name and use that for authentication
|
||||
/// @note Not case sensitive
|
||||
/// @note If multiple matching certificates are found, the first one is used.
|
||||
/// @note If this property is not set and neither is |clientCertIssuerCn|, the allowed issuers
|
||||
/// provided by the server will be used to find a matching certificate.
|
||||
///
|
||||
@property(copy, nonatomic) NSString *clientCertCommonName;
|
||||
|
||||
///
|
||||
/// If set and client certificate authentication is needed, will search the keychain for a
|
||||
/// certificate issued by an issuer with this name and use that for authentication.
|
||||
///
|
||||
/// @note Not case sensitive
|
||||
/// @note If multiple matching certificates are found, the first one is used.
|
||||
/// @note If this property is not set and neither is |clientCertCommonName|, the allowed issuers
|
||||
/// provided by the server will be used to find a matching certificate.
|
||||
///
|
||||
@property(copy, nonatomic) NSString *clientCertIssuerCn;
|
||||
|
||||
/// Designated initializer
|
||||
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration;
|
||||
|
||||
@end
|
||||
@@ -1,358 +0,0 @@
|
||||
/// 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 "SNTAuthenticatingURLSession.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "SNTDERDecoder.h"
|
||||
#import "SNTLogging.h"
|
||||
|
||||
@implementation SNTAuthenticatingURLSession
|
||||
|
||||
- (instancetype)initWithSessionConfiguration:(NSURLSessionConfiguration *)configuration {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_session = [NSURLSession sessionWithConfiguration:configuration
|
||||
delegate:self
|
||||
delegateQueue:nil];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
NSURLSessionConfiguration *config = [NSURLSessionConfiguration ephemeralSessionConfiguration];
|
||||
[config setTLSMinimumSupportedProtocol:kTLSProtocol12];
|
||||
[config setHTTPShouldUsePipelining:YES];
|
||||
return [self initWithSessionConfiguration:config];
|
||||
}
|
||||
|
||||
#pragma mark User Agent property
|
||||
|
||||
- (NSString *)userAgent {
|
||||
return _session.configuration.HTTPAdditionalHeaders[@"User-Agent"];
|
||||
}
|
||||
|
||||
- (void)setUserAgent:(NSString *)userAgent {
|
||||
NSMutableDictionary *addlHeaders = [_session.configuration.HTTPAdditionalHeaders mutableCopy];
|
||||
addlHeaders[@"User-Agent"] = userAgent;
|
||||
_session.configuration.HTTPAdditionalHeaders = addlHeaders;
|
||||
}
|
||||
|
||||
#pragma mark NSURLSessionDelegate methods
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
didReceiveChallenge:(NSURLAuthenticationChallenge *)challenge
|
||||
completionHandler:(void (^)(NSURLSessionAuthChallengeDisposition disposition,
|
||||
NSURLCredential *credential))completionHandler {
|
||||
NSURLProtectionSpace *protectionSpace = challenge.protectionSpace;
|
||||
|
||||
if (challenge.previousFailureCount > 0) {
|
||||
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
if (self.serverHostname && ![self.serverHostname isEqual:protectionSpace.host]) {
|
||||
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
if (![protectionSpace.protocol isEqual:NSURLProtectionSpaceHTTPS]) {
|
||||
LOGE(@"%@ is not a secure protocol", protectionSpace.protocol);
|
||||
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
if (!protectionSpace.receivesCredentialSecurely) {
|
||||
LOGE(@"Secure authentication or protocol cannot be established.");
|
||||
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *authMethod = [protectionSpace authenticationMethod];
|
||||
|
||||
if (authMethod == NSURLAuthenticationMethodClientCertificate) {
|
||||
NSURLCredential *cred = [self clientCredentialForProtectionSpace:protectionSpace];
|
||||
if (cred) {
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
|
||||
return;
|
||||
} else {
|
||||
LOGE(@"Server asked for client authentication but no usable client certificate found.");
|
||||
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
||||
return;
|
||||
}
|
||||
} else if (authMethod == NSURLAuthenticationMethodServerTrust) {
|
||||
NSURLCredential *cred = [self serverCredentialForProtectionSpace:protectionSpace];
|
||||
if (cred) {
|
||||
completionHandler(NSURLSessionAuthChallengeUseCredential, cred);
|
||||
return;
|
||||
} else {
|
||||
LOGE(@"Unable to verify server identity.");
|
||||
completionHandler(NSURLSessionAuthChallengeCancelAuthenticationChallenge, nil);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
completionHandler(NSURLSessionAuthChallengePerformDefaultHandling, nil);
|
||||
}
|
||||
|
||||
- (void)URLSession:(NSURLSession *)session
|
||||
task:(NSURLSessionTask *)task
|
||||
willPerformHTTPRedirection:(NSHTTPURLResponse *)response
|
||||
newRequest:(NSURLRequest *)request
|
||||
completionHandler:(void (^)(NSURLRequest *))completionHandler {
|
||||
if (self.refusesRedirects) {
|
||||
LOGD(@"Rejected redirection to: %@", request.URL);
|
||||
[task cancel]; // without this, the connection hangs until timeout!?!
|
||||
completionHandler(NULL);
|
||||
} else {
|
||||
completionHandler(request);
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Private Helpers for URLSession:didReceiveChallenge:completionHandler:
|
||||
|
||||
///
|
||||
/// Handles the process of locating a valid client certificate for authentication.
|
||||
/// Operates in one of four modes, depending on the configuration in config.plist
|
||||
///
|
||||
/// Mode 1: if syncClientAuthCertificateFile is set, use the identity in the pkcs file
|
||||
/// Mode 2: if syncClientAuthCertificateCn is set, look for an identity in the keychain with a
|
||||
/// matching common name and return it.
|
||||
/// Mode 3: if syncClientAuthCertificateIssuer is set, look for an identity in the keychain with a
|
||||
/// matching issuer common name and return it.
|
||||
/// Mode 4: use the list of issuer details sent down by the server to find an identity in the
|
||||
/// keychain.
|
||||
///
|
||||
/// If a valid identity cannot be found, returns nil.
|
||||
///
|
||||
- (NSURLCredential *)clientCredentialForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
|
||||
__block OSStatus err = errSecSuccess;
|
||||
__block SecIdentityRef foundIdentity = NULL;
|
||||
|
||||
if (self.clientCertFile) {
|
||||
foundIdentity = [self identityFromFile:self.clientCertFile password:self.clientCertPassword];
|
||||
} else {
|
||||
CFArrayRef cfResults = NULL;
|
||||
SecItemCopyMatching((__bridge CFDictionaryRef) @{
|
||||
(id)kSecClass : (id)kSecClassCertificate,
|
||||
(id)kSecReturnRef : @YES,
|
||||
(id)kSecMatchLimit : (id)kSecMatchLimitAll
|
||||
}, (CFTypeRef *)&cfResults);
|
||||
NSArray *results = CFBridgingRelease(cfResults);
|
||||
|
||||
NSMutableArray *allCerts = [[MOLCertificate certificatesFromArray:results] mutableCopy];
|
||||
|
||||
if (self.clientCertCommonName) {
|
||||
foundIdentity = [self identityByFilteringArray:allCerts
|
||||
commonName:self.clientCertCommonName
|
||||
issuerCommonName:nil
|
||||
issuerCountryName:nil
|
||||
issuerOrgName:nil
|
||||
issuerOrgUnit:nil];
|
||||
} else if (self.clientCertIssuerCn) {
|
||||
foundIdentity = [self identityByFilteringArray:allCerts
|
||||
commonName:nil
|
||||
issuerCommonName:self.clientCertIssuerCn
|
||||
issuerCountryName:nil
|
||||
issuerOrgName:nil
|
||||
issuerOrgUnit:nil];
|
||||
} else {
|
||||
for (NSData *allowedIssuer in protectionSpace.distinguishedNames) {
|
||||
SNTDERDecoder *decoder = [[SNTDERDecoder alloc] initWithData:allowedIssuer];
|
||||
|
||||
if (!decoder) {
|
||||
LOGW(@"Unable to decode allowed distinguished name.");
|
||||
continue;
|
||||
}
|
||||
|
||||
foundIdentity = [self identityByFilteringArray:allCerts
|
||||
commonName:nil
|
||||
issuerCommonName:decoder.commonName
|
||||
issuerCountryName:decoder.countryName
|
||||
issuerOrgName:decoder.organizationName
|
||||
issuerOrgUnit:decoder.organizationalUnit];
|
||||
if (foundIdentity) break;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (foundIdentity) {
|
||||
SecCertificateRef certificate = NULL;
|
||||
err = SecIdentityCopyCertificate(foundIdentity, &certificate);
|
||||
MOLCertificate *clientCert = [[MOLCertificate alloc] initWithSecCertificateRef:certificate];
|
||||
LOGD(@"Client Trust: Valid client identity %@.", clientCert);
|
||||
NSURLCredential *cred =
|
||||
[NSURLCredential credentialWithIdentity:foundIdentity
|
||||
certificates:nil
|
||||
persistence:NSURLCredentialPersistenceForSession];
|
||||
return cred;
|
||||
} else {
|
||||
LOGD(@"Client Trust: No valid identity found.");
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Handles the process of evaluating the server's certificate chain.
|
||||
/// Operates in one of three modes, depending on the configuration in config.plist
|
||||
///
|
||||
/// Mode 1: if syncServerAuthRootsData is set, evaluates the server's certificate chain contains
|
||||
/// one of the certificates in the PEM data in the config plist.
|
||||
/// Mode 2: if syncServerAuthRootsFile is set, evaluates the server's certificate chain contains
|
||||
/// one of the certificates in the PEM data in the file specified.
|
||||
/// Mode 3: evaluates the server's certificate chain is trusted by the keychain.
|
||||
///
|
||||
/// If the server's certificate chain does not evaluate for any reason, returns nil.
|
||||
///
|
||||
- (NSURLCredential *)serverCredentialForProtectionSpace:(NSURLProtectionSpace *)protectionSpace {
|
||||
SecTrustRef serverTrust = protectionSpace.serverTrust;
|
||||
if (serverTrust == NULL) {
|
||||
LOGD(@"Server Trust: No server trust information available");
|
||||
return nil;
|
||||
}
|
||||
|
||||
OSStatus err = errSecSuccess;
|
||||
|
||||
if (self.serverRootsPemData) {
|
||||
NSString *pemStrings = [[NSString alloc] initWithData:self.serverRootsPemData
|
||||
encoding:NSASCIIStringEncoding];
|
||||
NSArray *certs = [MOLCertificate certificatesFromPEM:pemStrings];
|
||||
|
||||
// Make a new array of the SecCertificateRef's from the MOLCertificate's.
|
||||
NSMutableArray *certRefs = [[NSMutableArray alloc] initWithCapacity:certs.count];
|
||||
for (MOLCertificate *cert in certs) {
|
||||
[certRefs addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
// Set this array of certs as the anchors to trust.
|
||||
err = SecTrustSetAnchorCertificates(serverTrust, (__bridge CFArrayRef)certRefs);
|
||||
if (err != errSecSuccess) {
|
||||
LOGD(@"Server Trust: Could not set anchor certificates: %d", err);
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
// Evaluate the server's cert chain.
|
||||
SecTrustResultType result = kSecTrustResultInvalid;
|
||||
err = SecTrustEvaluate(serverTrust, &result);
|
||||
if (err != errSecSuccess) {
|
||||
LOGD(@"Server Trust: Unable to evaluate certificate chain for server: %d", err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Print details about the server's leaf certificate.
|
||||
SecCertificateRef firstCert = SecTrustGetCertificateAtIndex(serverTrust, 0);
|
||||
if (firstCert) {
|
||||
MOLCertificate *cert = [[MOLCertificate alloc] initWithSecCertificateRef:firstCert];
|
||||
LOGD(@"Server Trust: Server leaf cert: %@", cert);
|
||||
}
|
||||
|
||||
// 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) {
|
||||
LOGD(@"Server Trust: Server isn't trusted. SecTrustResultType: %d", result);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [NSURLCredential credentialForTrust:serverTrust];
|
||||
}
|
||||
|
||||
/**
|
||||
Given an array of MOLCertificate objects and some properties, filter the array
|
||||
repeatedly until an identity is found that fulfills the signing chain.
|
||||
*/
|
||||
- (SecIdentityRef)identityByFilteringArray:(NSArray *)array
|
||||
commonName:(NSString *)commonName
|
||||
issuerCommonName:(NSString *)issuerCommonName
|
||||
issuerCountryName:(NSString *)issuerCountryName
|
||||
issuerOrgName:(NSString *)issuerOrgName
|
||||
issuerOrgUnit:(NSString *)issuerOrgUnit {
|
||||
NSMutableArray *predicates = [NSMutableArray arrayWithCapacity:4];
|
||||
|
||||
if (commonName) {
|
||||
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.commonName == %@",
|
||||
commonName]];
|
||||
}
|
||||
if (issuerCommonName) {
|
||||
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerCommonName == %@",
|
||||
issuerCommonName]];
|
||||
}
|
||||
if (issuerCountryName) {
|
||||
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerCountryName == %@",
|
||||
issuerCountryName]];
|
||||
}
|
||||
if (issuerOrgName) {
|
||||
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerOrgName == %@",
|
||||
issuerOrgName]];
|
||||
}
|
||||
if (issuerOrgUnit) {
|
||||
[predicates addObject:[NSPredicate predicateWithFormat:@"SELF.issuerOrgUnit == %@",
|
||||
issuerOrgUnit]];
|
||||
}
|
||||
|
||||
NSCompoundPredicate *andPreds = [NSCompoundPredicate andPredicateWithSubpredicates:predicates];
|
||||
|
||||
NSArray *filteredCerts = [array filteredArrayUsingPredicate:andPreds];
|
||||
if (!filteredCerts.count) return NULL;
|
||||
|
||||
for (MOLCertificate *cert in filteredCerts) {
|
||||
SecIdentityRef identityRef = NULL;
|
||||
OSStatus status = SecIdentityCreateWithCertificate(NULL, cert.certRef, &identityRef);
|
||||
if (status == errSecSuccess) {
|
||||
return identityRef;
|
||||
} else {
|
||||
// Avoid infinite recursion from self-signed certs
|
||||
if ((cert.commonName && [cert.commonName isEqual:cert.issuerCommonName]) &&
|
||||
(cert.countryName && [cert.countryName isEqual:cert.issuerCountryName]) &&
|
||||
(cert.orgName && [cert.orgName isEqual:cert.issuerOrgName]) &&
|
||||
(cert.orgUnit && [cert.orgUnit isEqual:cert.issuerOrgUnit])) {
|
||||
continue;
|
||||
}
|
||||
|
||||
return [self identityByFilteringArray:array
|
||||
commonName:nil
|
||||
issuerCommonName:cert.commonName
|
||||
issuerCountryName:cert.countryName
|
||||
issuerOrgName:cert.orgName
|
||||
issuerOrgUnit:cert.orgUnit];
|
||||
}
|
||||
}
|
||||
return NULL;
|
||||
}
|
||||
|
||||
- (SecIdentityRef)identityFromFile:(NSString *)file password:(NSString *)password {
|
||||
NSError *error;
|
||||
NSData *data = [NSData dataWithContentsOfFile:file options:0 error:&error];
|
||||
if (error) {
|
||||
LOGD(@"Client Trust: Couldn't open client certificate %@: %@",
|
||||
self.clientCertFile,
|
||||
[error localizedDescription]);
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSDictionary *options = (password ? @{(__bridge id)kSecImportExportPassphrase : password} : @{});
|
||||
CFArrayRef cfIdentities;
|
||||
OSStatus err = SecPKCS12Import(
|
||||
(__bridge CFDataRef)data, (__bridge CFDictionaryRef)options, &cfIdentities);
|
||||
NSArray *identities = CFBridgingRelease(cfIdentities);
|
||||
|
||||
if (err != errSecSuccess) {
|
||||
LOGD(@"Client Trust: Couldn't load client certificate %@: %d", self.clientCertFile, err);
|
||||
return nil;
|
||||
}
|
||||
|
||||
return (__bridge SecIdentityRef)identities[0][(__bridge id)kSecImportItemIdentity];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,31 +0,0 @@
|
||||
/// 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.
|
||||
|
||||
@class SNTCommandSyncState;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
@interface SNTCommandSyncEventUpload : NSObject
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
|
||||
+ (void)uploadSingleEventWithSHA256:(NSString *)SHA256
|
||||
session:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
|
||||
@end
|
||||
@@ -1,275 +0,0 @@
|
||||
/// 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 "SNTCommandSyncEventUpload.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "NSData+Zlib.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@implementation SNTCommandSyncEventUpload
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSURL *url = [NSURL URLWithString:[kURLEventUpload stringByAppendingString:syncState.machineID]
|
||||
relativeToURL:syncState.syncBaseURL];
|
||||
|
||||
[[daemonConn remoteObjectProxy] databaseEventsPending:^(NSArray *events) {
|
||||
if ([events count] == 0) {
|
||||
handler(YES);
|
||||
} else {
|
||||
[self uploadEventsFromArray:events
|
||||
toURL:url
|
||||
inSession:session
|
||||
batchSize:syncState.eventBatchSize
|
||||
daemonConn:daemonConn
|
||||
completionHandler:handler];
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)uploadSingleEventWithSHA256:(NSString *)SHA256
|
||||
session:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSURL *url = [NSURL URLWithString:[kURLEventUpload stringByAppendingString:syncState.machineID]
|
||||
relativeToURL:syncState.syncBaseURL];
|
||||
[[daemonConn remoteObjectProxy] databaseEventForSHA256:SHA256 reply:^(SNTStoredEvent *event) {
|
||||
if (!event) {
|
||||
handler(YES);
|
||||
return;
|
||||
}
|
||||
|
||||
[self uploadEventsFromArray:@[ event ]
|
||||
toURL:url
|
||||
inSession:session
|
||||
batchSize:1
|
||||
daemonConn:daemonConn
|
||||
completionHandler:handler];
|
||||
}];
|
||||
}
|
||||
|
||||
+ (void)uploadEventsFromArray:(NSArray *)events
|
||||
toURL:(NSURL *)url
|
||||
inSession:(NSURLSession *)session
|
||||
batchSize:(NSUInteger)batchSize
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSMutableArray *uploadEvents = [[NSMutableArray alloc] init];
|
||||
|
||||
NSMutableArray *eventIds = [NSMutableArray arrayWithCapacity:events.count];
|
||||
for (SNTStoredEvent *event in events) {
|
||||
[uploadEvents addObject:[self dictionaryForEvent:event]];
|
||||
[eventIds addObject:event.idx];
|
||||
|
||||
if (event.fileBundleID) {
|
||||
NSArray *relatedBinaries = [self findRelatedBinaries:event];
|
||||
[uploadEvents addObjectsFromArray:relatedBinaries];
|
||||
}
|
||||
|
||||
if (eventIds.count >= batchSize) break;
|
||||
}
|
||||
|
||||
NSDictionary *uploadReq = @{kEvents : uploadEvents};
|
||||
|
||||
NSData *requestBody;
|
||||
@try {
|
||||
requestBody = [NSJSONSerialization dataWithJSONObject:uploadReq options:0 error:nil];
|
||||
} @catch (NSException *exception) {
|
||||
LOGE(@"Failed to parse event(s) into JSON");
|
||||
LOGD(@"Parsing error: %@", [exception reason]);
|
||||
}
|
||||
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
NSData *compressed = [requestBody zlibCompressed];
|
||||
if (compressed) {
|
||||
requestBody = compressed;
|
||||
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
|
||||
}
|
||||
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
LOGD(@"%@", error);
|
||||
handler(NO);
|
||||
} else {
|
||||
LOGI(@"Uploaded %lu events", eventIds.count);
|
||||
|
||||
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:eventIds];
|
||||
|
||||
NSArray *nextEvents = [events subarrayWithRange:NSMakeRange(eventIds.count,
|
||||
events.count - eventIds.count)];
|
||||
if (nextEvents.count == 0) {
|
||||
handler(YES);
|
||||
} else {
|
||||
[self uploadEventsFromArray:nextEvents
|
||||
toURL:url
|
||||
inSession:session
|
||||
batchSize:batchSize
|
||||
daemonConn:daemonConn
|
||||
completionHandler:handler];
|
||||
}
|
||||
}
|
||||
}] resume];
|
||||
}
|
||||
|
||||
+ (NSDictionary *)dictionaryForEvent:(SNTStoredEvent *)event {
|
||||
#define ADDKEY(dict, key, value) if (value) dict[key] = value
|
||||
NSMutableDictionary *newEvent = [NSMutableDictionary dictionary];
|
||||
|
||||
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, kLoggedInUsers, event.loggedInUsers);
|
||||
ADDKEY(newEvent, kCurrentSessions, event.currentSessions);
|
||||
|
||||
switch (event.decision) {
|
||||
case EVENTSTATE_ALLOW_UNKNOWN: ADDKEY(newEvent, kDecision, kDecisionAllowUnknown); break;
|
||||
case EVENTSTATE_ALLOW_BINARY: ADDKEY(newEvent, kDecision, kDecisionAllowBinary); break;
|
||||
case EVENTSTATE_ALLOW_CERTIFICATE:
|
||||
ADDKEY(newEvent, kDecision, kDecisionAllowCertificate);
|
||||
break;
|
||||
case EVENTSTATE_ALLOW_SCOPE: ADDKEY(newEvent, kDecision, kDecisionAllowScope); break;
|
||||
case EVENTSTATE_BLOCK_UNKNOWN: ADDKEY(newEvent, kDecision, kDecisionBlockUnknown); break;
|
||||
case EVENTSTATE_BLOCK_BINARY: ADDKEY(newEvent, kDecision, kDecisionBlockBinary); break;
|
||||
case EVENTSTATE_BLOCK_CERTIFICATE:
|
||||
ADDKEY(newEvent, kDecision, kDecisionBlockCertificate);
|
||||
break;
|
||||
case EVENTSTATE_BLOCK_SCOPE: ADDKEY(newEvent, kDecision, kDecisionBlockScope); break;
|
||||
case EVENTSTATE_RELATED_BINARY: ADDKEY(newEvent, kDecision, kDecisionRelatedBinary); break;
|
||||
default: ADDKEY(newEvent, kDecision, kDecisionUnknown);
|
||||
}
|
||||
|
||||
ADDKEY(newEvent, kFileBundleID, event.fileBundleID);
|
||||
ADDKEY(newEvent, kFileBundleName, event.fileBundleName);
|
||||
ADDKEY(newEvent, kFileBundleVersion, event.fileBundleVersion);
|
||||
ADDKEY(newEvent, kFileBundleShortVersionString, event.fileBundleVersionString);
|
||||
|
||||
ADDKEY(newEvent, kPID, event.pid);
|
||||
ADDKEY(newEvent, kPPID, event.ppid);
|
||||
ADDKEY(newEvent, kParentName, event.parentName);
|
||||
|
||||
ADDKEY(newEvent, kQuarantineDataURL, event.quarantineDataURL);
|
||||
ADDKEY(newEvent, kQuarantineRefererURL, event.quarantineRefererURL);
|
||||
ADDKEY(newEvent, kQuarantineTimestamp, @([event.quarantineTimestamp timeIntervalSince1970]));
|
||||
ADDKEY(newEvent, kQuarantineAgentBundleID, event.quarantineAgentBundleID);
|
||||
|
||||
NSMutableArray *signingChain = [NSMutableArray arrayWithCapacity:event.signingChain.count];
|
||||
for (NSUInteger i = 0; i < event.signingChain.count; ++i) {
|
||||
MOLCertificate *cert = [event.signingChain objectAtIndex:i];
|
||||
|
||||
NSMutableDictionary *certDict = [NSMutableDictionary dictionary];
|
||||
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[kSigningChain] = signingChain;
|
||||
|
||||
return newEvent;
|
||||
#undef ADDKEY
|
||||
}
|
||||
|
||||
+ (NSArray *)findRelatedBinaries:(SNTStoredEvent *)event {
|
||||
// Prevent processing the same bundle twice.
|
||||
static NSMutableDictionary *previouslyProcessedBundles;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
previouslyProcessedBundles = [NSMutableDictionary dictionary];
|
||||
});
|
||||
if (previouslyProcessedBundles[event.fileBundleID]) return nil;
|
||||
previouslyProcessedBundles[event.fileBundleID] = @YES;
|
||||
|
||||
NSMutableArray *relatedEvents = [NSMutableArray array];
|
||||
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
__block BOOL shouldCancel = NO;
|
||||
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0), ^{
|
||||
SNTFileInfo *originalFile = [[SNTFileInfo alloc] initWithPath:event.filePath];
|
||||
NSString *bundlePath = originalFile.bundlePath;
|
||||
originalFile = nil; // release originalFile early.
|
||||
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:bundlePath];
|
||||
NSString *file;
|
||||
|
||||
while (file = [dirEnum nextObject]) {
|
||||
@autoreleasepool {
|
||||
if (shouldCancel) break;
|
||||
if ([dirEnum fileAttributes][NSFileType] != NSFileTypeRegular) continue;
|
||||
|
||||
file = [bundlePath stringByAppendingPathComponent:file];
|
||||
|
||||
// Don't record the binary that triggered this event as a related binary.
|
||||
if ([file isEqual:event.filePath]) continue;
|
||||
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:file];
|
||||
if (fi.isExecutable) {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
se.filePath = fi.path;
|
||||
se.fileSHA256 = fi.SHA256;
|
||||
se.decision = EVENTSTATE_RELATED_BINARY;
|
||||
se.fileBundleID = event.fileBundleID;
|
||||
se.fileBundleName = event.fileBundleName;
|
||||
se.fileBundleVersion = event.fileBundleVersion;
|
||||
se.fileBundleVersionString = event.fileBundleVersionString;
|
||||
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:se.filePath];
|
||||
se.signingChain = cs.certificates;
|
||||
|
||||
[relatedEvents addObject:[self dictionaryForEvent:se]];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dispatch_semaphore_signal(sema);
|
||||
});
|
||||
|
||||
// Give the search up to 5s per event to run.
|
||||
// This might need tweaking if it seems to slow down syncing or misses too much to be useful.
|
||||
if (dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
|
||||
shouldCancel = YES;
|
||||
LOGD(@"Timed out while searching for related events. Bundle ID: %@", event.fileBundleID);
|
||||
}
|
||||
|
||||
return relatedEvents;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,70 +0,0 @@
|
||||
/// 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 "SNTCommandSyncPostflight.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@implementation SNTCommandSyncPostflight
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSURL *url = [NSURL URLWithString:[kURLPostflight stringByAppendingString:syncState.machineID]
|
||||
relativeToURL:syncState.syncBaseURL];
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
LOGD(@"%@", error);
|
||||
handler(NO);
|
||||
} else {
|
||||
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
|
||||
if (syncState.newClientMode) {
|
||||
[[daemonConn remoteObjectProxy] setClientMode:syncState.newClientMode reply:^{}];
|
||||
}
|
||||
|
||||
NSString *backoffInterval = r[kBackoffInterval];
|
||||
if (backoffInterval) {
|
||||
[[daemonConn remoteObjectProxy] setNextSyncInterval:[backoffInterval intValue] reply:^{}];
|
||||
}
|
||||
|
||||
if (syncState.cleanSync) {
|
||||
[[daemonConn remoteObjectProxy] setSyncCleanRequired:NO reply:^{}];
|
||||
}
|
||||
|
||||
// Update last sync success
|
||||
[[daemonConn remoteObjectProxy] setSyncLastSuccess:[NSDate date] reply:^{}];
|
||||
|
||||
handler(YES);
|
||||
}
|
||||
}] resume];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,106 +0,0 @@
|
||||
/// 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 "SNTCommandSyncPreflight.h"
|
||||
|
||||
#include "SNTKernelCommon.h"
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTSystemInfo.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@implementation SNTCommandSyncPreflight
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSURL *url = [NSURL URLWithString:[kURLPreflight stringByAppendingString:syncState.machineID]
|
||||
relativeToURL:syncState.syncBaseURL];
|
||||
|
||||
NSMutableDictionary *requestDict = [NSMutableDictionary dictionary];
|
||||
requestDict[kSerialNumber] = [SNTSystemInfo serialNumber];
|
||||
requestDict[kHostname] = [SNTSystemInfo shortHostname];
|
||||
requestDict[kSantaVer] = [[NSBundle mainBundle] objectForInfoDictionaryKey:@"CFBundleVersion"];
|
||||
requestDict[kOSVer] = [SNTSystemInfo osVersion];
|
||||
requestDict[kOSBuild] = [SNTSystemInfo osBuild];
|
||||
requestDict[kPrimaryUser] = syncState.machineOwner;
|
||||
|
||||
// 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]) {
|
||||
requestDict[kRequestCleanSync] = @YES;
|
||||
}
|
||||
|
||||
NSData *requestBody = [NSJSONSerialization dataWithJSONObject:requestDict
|
||||
options:0
|
||||
error:nil];
|
||||
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
|
||||
NSData *compressed = [requestBody zlibCompressed];
|
||||
if (compressed) {
|
||||
requestBody = compressed;
|
||||
[req setValue:@"zlib" forHTTPHeaderField:@"Content-Encoding"];
|
||||
}
|
||||
|
||||
[req setHTTPBody:requestBody];
|
||||
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
LOGD(@"%@", error);
|
||||
handler(NO);
|
||||
} else {
|
||||
NSDictionary *r = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
|
||||
syncState.eventBatchSize = [r[kBatchSize] intValue];
|
||||
syncState.uploadLogURL = [NSURL URLWithString:r[kUploadLogsURL]];
|
||||
|
||||
if ([r[kClientMode] isEqual:kClientModeMonitor]) {
|
||||
syncState.newClientMode = CLIENTMODE_MONITOR;
|
||||
} else if ([r[kClientMode] isEqual:kClientModeLockdown]) {
|
||||
syncState.newClientMode = CLIENTMODE_LOCKDOWN;
|
||||
}
|
||||
|
||||
if ([r[kWhitelistRegex] isKindOfClass:[NSString class]]) {
|
||||
[[daemonConn remoteObjectProxy] setWhitelistPathRegex:r[kWhitelistRegex] reply:^{}];
|
||||
}
|
||||
|
||||
if ([r[kBlacklistRegex] isKindOfClass:[NSString class]]) {
|
||||
[[daemonConn remoteObjectProxy] setBlacklistPathRegex:r[kBlacklistRegex] reply:^{}];
|
||||
}
|
||||
|
||||
if ([r[kCleanSync] boolValue]) {
|
||||
syncState.cleanSync = YES;
|
||||
}
|
||||
|
||||
handler(YES);
|
||||
}
|
||||
}] resume];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,148 +0,0 @@
|
||||
/// 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 "SNTCommandSyncRuleDownload.h"
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
@implementation SNTCommandSyncRuleDownload
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSURL *url = [NSURL URLWithString:[kURLRuleDownload stringByAppendingString:syncState.machineID]
|
||||
relativeToURL:syncState.syncBaseURL];
|
||||
[self ruleDownloadWithCursor:nil
|
||||
url:url
|
||||
session:session
|
||||
syncState:syncState
|
||||
daemonConn:daemonConn
|
||||
completionHandler:handler];
|
||||
}
|
||||
|
||||
+ (void)ruleDownloadWithCursor:(NSString *)cursor
|
||||
url:(NSURL *)url
|
||||
session:(NSURLSession *)session
|
||||
syncState:(SNTCommandSyncState *)syncState
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSDictionary *requestDict = (cursor ? @{kCursor : cursor} : @{});
|
||||
|
||||
if (!syncState.downloadedRules) {
|
||||
syncState.downloadedRules = [NSMutableArray array];
|
||||
}
|
||||
|
||||
NSMutableURLRequest *req = [[NSMutableURLRequest alloc] initWithURL:url];
|
||||
[req setHTTPBody:[NSJSONSerialization dataWithJSONObject:requestDict
|
||||
options:0
|
||||
error:nil]];
|
||||
[req setHTTPMethod:@"POST"];
|
||||
[req setValue:@"application/json" forHTTPHeaderField:@"Content-Type"];
|
||||
[[session dataTaskWithRequest:req completionHandler:^(NSData *data,
|
||||
NSURLResponse *response,
|
||||
NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %ld %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
LOGD(@"%@", error);
|
||||
handler(NO);
|
||||
} else {
|
||||
NSDictionary *resp = [NSJSONSerialization JSONObjectWithData:data options:0 error:nil];
|
||||
if (!resp) {
|
||||
LOGE(@"Failed to decode server's response");
|
||||
handler(NO);
|
||||
return;
|
||||
}
|
||||
|
||||
NSArray *receivedRules = resp[kRules];
|
||||
for (NSDictionary *rule in receivedRules) {
|
||||
SNTRule *r = [self ruleFromDictionary:rule];
|
||||
if (r) [syncState.downloadedRules addObject:r];
|
||||
}
|
||||
|
||||
if (resp[kCursor]) {
|
||||
[self ruleDownloadWithCursor:resp[kCursor]
|
||||
url:url
|
||||
session:session
|
||||
syncState:syncState
|
||||
daemonConn:daemonConn
|
||||
completionHandler:handler];
|
||||
} else {
|
||||
if (syncState.downloadedRules.count) {
|
||||
[[daemonConn remoteObjectProxy] databaseRuleAddRules:syncState.downloadedRules
|
||||
cleanSlate:syncState.cleanSync
|
||||
reply:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Added %lu rule(s)", syncState.downloadedRules.count);
|
||||
handler(YES);
|
||||
} else {
|
||||
LOGE(@"Failed to add rules to database");
|
||||
handler(NO);
|
||||
}
|
||||
}];
|
||||
} else {
|
||||
handler(YES);
|
||||
}
|
||||
}
|
||||
}
|
||||
}] resume];
|
||||
}
|
||||
|
||||
+ (SNTRule *)ruleFromDictionary:(NSDictionary *)dict {
|
||||
if (![dict isKindOfClass:[NSDictionary class]]) return nil;
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.shasum = dict[kRuleSHA256];
|
||||
if (newRule.shasum.length != 64) return nil;
|
||||
|
||||
NSString *policyString = dict[kRulePolicy];
|
||||
if ([policyString isEqual:kRulePolicyWhitelist]) {
|
||||
newRule.state = RULESTATE_WHITELIST;
|
||||
} else if ([policyString isEqual:kRulePolicyBlacklist]) {
|
||||
newRule.state = RULESTATE_BLACKLIST;
|
||||
} else if ([policyString isEqual:kRulePolicySilentBlacklist]) {
|
||||
newRule.state = RULESTATE_SILENT_BLACKLIST;
|
||||
} else if ([policyString isEqual:kRulePolicyRemove]) {
|
||||
newRule.state = RULESTATE_REMOVE;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *ruleTypeString = dict[kRuleType];
|
||||
if ([ruleTypeString isEqual:kRuleTypeBinary]) {
|
||||
newRule.type = RULETYPE_BINARY;
|
||||
} else if ([ruleTypeString isEqual:kRuleTypeCertificate]) {
|
||||
newRule.type = RULETYPE_CERT;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
NSString *customMsg = dict[kRuleCustomMsg];
|
||||
if (customMsg.length) {
|
||||
newRule.customMsg = customMsg;
|
||||
}
|
||||
|
||||
return newRule;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,35 +0,0 @@
|
||||
/// 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.
|
||||
|
||||
///
|
||||
/// This is a simple ASN.1 decoder that utilizes Apple's SecAsn1Decode
|
||||
/// to parse the @c distinguishedNames property of NSURLProtectionSpace.
|
||||
///
|
||||
@interface SNTDERDecoder : NSObject
|
||||
|
||||
@property(readonly) NSString *commonName;
|
||||
@property(readonly) NSString *organizationName;
|
||||
@property(readonly) NSString *organizationalUnit;
|
||||
@property(readonly) NSString *countryName;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
///
|
||||
/// @param data one of the objects in the
|
||||
/// NSURLProtectionSpace.distinguishedNames array
|
||||
/// @return nil if decoding fails to find any expected objects
|
||||
///
|
||||
- (instancetype)initWithData:(NSData *)data;
|
||||
|
||||
@end
|
||||
@@ -1,217 +0,0 @@
|
||||
/// 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 "SNTDERDecoder.h"
|
||||
|
||||
#import <Security/SecAsn1Coder.h>
|
||||
#import <Security/SecAsn1Templates.h>
|
||||
|
||||
@interface SNTDERDecoder ()
|
||||
@property NSDictionary *decodedObjects;
|
||||
@end
|
||||
|
||||
@implementation SNTDERDecoder
|
||||
|
||||
#pragma mark Init
|
||||
|
||||
- (instancetype)initWithData:(NSData *)data {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
if (!data) return nil;
|
||||
|
||||
_decodedObjects = [self decodeData:data];
|
||||
if (!_decodedObjects || [_decodedObjects count] == 0) return nil;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"/C=%@/O=%@/OU=%@/CN=%@",
|
||||
self.countryName,
|
||||
self.organizationName,
|
||||
self.organizationalUnit,
|
||||
self.commonName];
|
||||
}
|
||||
|
||||
#pragma mark Accessors
|
||||
|
||||
- (NSString *)commonName {
|
||||
return self.decodedObjects[(__bridge id)kSecOIDCommonName];
|
||||
}
|
||||
|
||||
- (NSString *)organizationName {
|
||||
return self.decodedObjects[(__bridge id)kSecOIDOrganizationName];
|
||||
}
|
||||
|
||||
- (NSString *)organizationalUnit {
|
||||
return self.decodedObjects[(__bridge id)kSecOIDOrganizationalUnitName];
|
||||
}
|
||||
|
||||
- (NSString *)countryName {
|
||||
return self.decodedObjects[(__bridge id)kSecOIDCountryName];
|
||||
}
|
||||
|
||||
#pragma mark Private
|
||||
|
||||
/**
|
||||
* The DER data provided by NSURLProtectionSpace.distinguishedNames looks like
|
||||
* this:
|
||||
*
|
||||
* SEQUENCE {
|
||||
* SET {
|
||||
* SEQUENCE {
|
||||
* OBJECT IDENTIFIER (2 5 4 6)
|
||||
* PrintableString 'US'
|
||||
* }
|
||||
* }
|
||||
* SET {
|
||||
* SEQUENCE {
|
||||
* OBJECT IDENTIFIER (2 5 4 10)
|
||||
* PrintableString 'Megaco Inc'
|
||||
* }
|
||||
* }
|
||||
* }
|
||||
*
|
||||
* This method assumes the passed in data will be in that format. If it isn't,
|
||||
* the DER decoding will fail and this method will return nil.
|
||||
**/
|
||||
- (NSDictionary *)decodeData:(NSData *)data {
|
||||
typedef struct {
|
||||
SecAsn1Oid oid;
|
||||
SecAsn1Item value;
|
||||
} OIDKeyValue;
|
||||
|
||||
static const SecAsn1Template kOIDValueTemplate[] = {
|
||||
{SEC_ASN1_SEQUENCE, 0, NULL, sizeof(OIDKeyValue)},
|
||||
{SEC_ASN1_OBJECT_ID, offsetof(OIDKeyValue, oid), NULL, 0},
|
||||
{SEC_ASN1_ANY_CONTENTS, offsetof(OIDKeyValue, value), NULL, 0},
|
||||
{0, 0, NULL, 0}};
|
||||
|
||||
typedef struct {
|
||||
OIDKeyValue **vals;
|
||||
} OIDKeyValueList;
|
||||
|
||||
static const SecAsn1Template kSetOfOIDValueTemplate[] = {
|
||||
{SEC_ASN1_SET_OF, 0, kOIDValueTemplate, sizeof(OIDKeyValueList)},
|
||||
{0, 0, NULL, 0}};
|
||||
|
||||
typedef struct {
|
||||
OIDKeyValueList **lists;
|
||||
} OIDKeyValueListSeq;
|
||||
|
||||
static const SecAsn1Template kSequenceOfSetOfOIDValueTemplate[] = {
|
||||
{SEC_ASN1_SEQUENCE_OF, 0, kSetOfOIDValueTemplate, sizeof(OIDKeyValueListSeq)},
|
||||
{0, 0, NULL, 0}};
|
||||
|
||||
OSStatus err = errSecSuccess;
|
||||
SecAsn1CoderRef coder;
|
||||
|
||||
err = SecAsn1CoderCreate(&coder);
|
||||
if (err != errSecSuccess) return nil;
|
||||
|
||||
OIDKeyValueListSeq a;
|
||||
err = SecAsn1Decode(coder,
|
||||
data.bytes,
|
||||
data.length,
|
||||
kSequenceOfSetOfOIDValueTemplate,
|
||||
&a);
|
||||
if (err != errSecSuccess) {
|
||||
SecAsn1CoderRelease(coder);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// The data is decoded but now it's in a number of embedded structs.
|
||||
// Massage that into a nice dictionary of OID->String pairs.
|
||||
NSMutableDictionary *dict = [NSMutableDictionary dictionary];
|
||||
OIDKeyValueList *anAttr;
|
||||
for (NSUInteger i = 0; (anAttr = a.lists[i]); ++i) {
|
||||
OIDKeyValue *keyValue = anAttr->vals[0];
|
||||
|
||||
// Sanity check
|
||||
if (keyValue->value.Length > data.length) {
|
||||
SecAsn1CoderRelease(coder);
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get the string value. First try creating as a UTF-8 string. If that fails,
|
||||
// fallback to trying as an ASCII string. If it still doesn't work, continue on
|
||||
// to the next value.
|
||||
NSString *valueString;
|
||||
valueString = [[NSString alloc] initWithBytes:keyValue->value.Data
|
||||
length:keyValue->value.Length
|
||||
encoding:NSUTF8StringEncoding];
|
||||
if (!valueString) {
|
||||
valueString = [[NSString alloc] initWithBytes:keyValue->value.Data
|
||||
length:keyValue->value.Length
|
||||
encoding:NSASCIIStringEncoding];
|
||||
}
|
||||
if (!valueString) continue;
|
||||
|
||||
// The OID is still encoded, so we need to decode it.
|
||||
NSString *objectId = [SNTDERDecoder decodeOIDWithBytes:keyValue->oid.Data
|
||||
length:keyValue->oid.Length];
|
||||
|
||||
// Add to the dictionary
|
||||
dict[objectId] = valueString;
|
||||
}
|
||||
|
||||
SecAsn1CoderRelease(coder);
|
||||
return dict;
|
||||
}
|
||||
|
||||
/**
|
||||
* Decodes an ASN.1 Object Identifier into a string separated by periods.
|
||||
* See http://msdn.microsoft.com/en-us/library/bb540809(v=vs.85).aspx for
|
||||
* details of the encoding.
|
||||
**/
|
||||
+ (NSString *)decodeOIDWithBytes:(unsigned char *)bytes length:(NSUInteger)length {
|
||||
NSMutableArray *objectId = [NSMutableArray array];
|
||||
BOOL inVariableLengthByte = NO;
|
||||
NSUInteger variableLength = 0;
|
||||
for (NSUInteger i = 0; i < length; ++i) {
|
||||
if (i == 0) {
|
||||
// The first byte is actually two values, the top 4 bits are the first value * 40
|
||||
// and the bottom 4 bits are the second value.
|
||||
[objectId addObject:@((NSUInteger)bytes[i] / 40)];
|
||||
[objectId addObject:@((NSUInteger)bytes[i] % 40)];
|
||||
} else {
|
||||
// The remaining bytes are encoded with Variable Length Quantity.
|
||||
unsigned char byte = bytes[i];
|
||||
if (byte & 0x80) {
|
||||
inVariableLengthByte = YES;
|
||||
|
||||
NSUInteger a = (NSUInteger)(byte & ~0x80);
|
||||
variableLength = variableLength << 7;
|
||||
variableLength += a;
|
||||
} else if (inVariableLengthByte) {
|
||||
NSUInteger a = (NSUInteger)(byte & ~0x80);
|
||||
variableLength = variableLength << 7;
|
||||
variableLength += a;
|
||||
inVariableLengthByte = NO;
|
||||
[objectId addObject:@(variableLength)];
|
||||
variableLength = 0;
|
||||
} else {
|
||||
[objectId addObject:@((NSUInteger)byte)];
|
||||
}
|
||||
}
|
||||
}
|
||||
return [objectId componentsJoinedByString:@"."];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,10 +12,9 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTDatabaseTable.h"
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTNotificationMessage;
|
||||
|
||||
@@ -55,8 +54,9 @@
|
||||
///
|
||||
/// @param rules Array of SNTRule's to add.
|
||||
/// @param cleanSlate If true, remove all rules before adding the new rules.
|
||||
/// @return YES if all rules were added successfully.
|
||||
/// @param error When returning NO, will be filled with appropriate error.
|
||||
/// @return YES if adding all rules passed, NO if any were rejected.
|
||||
///
|
||||
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate;
|
||||
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate error:(NSError **)error;
|
||||
|
||||
@end
|
||||
@@ -28,9 +28,8 @@
|
||||
@implementation SNTRuleTable
|
||||
|
||||
- (uint32_t)initializeDatabase:(FMDatabase *)db fromVersion:(uint32_t)version {
|
||||
// Save hashes of the signing certs for launchd and santad
|
||||
self.santadCertSHA = [[[[MOLCodesignChecker alloc] initWithSelf] leafCertificate] SHA256];
|
||||
self.launchdCertSHA = [[[[MOLCodesignChecker alloc] initWithPID:1] leafCertificate] SHA256];
|
||||
// Lock this database from other processes
|
||||
[[db executeQuery:@"PRAGMA locking_mode = EXCLUSIVE;"] close];
|
||||
|
||||
uint32_t newVersion = 0;
|
||||
|
||||
@@ -41,23 +40,32 @@
|
||||
@"'type' INTEGER NOT NULL, "
|
||||
@"'custommsg' TEXT"
|
||||
@")"];
|
||||
|
||||
[db executeUpdate:@"CREATE VIEW binrules AS SELECT * FROM rules WHERE type=1"];
|
||||
[db executeUpdate:@"CREATE VIEW certrules AS SELECT * FROM rules WHERE type=2"];
|
||||
|
||||
[db executeUpdate:@"CREATE UNIQUE INDEX rulesunique ON rules (shasum, type)"];
|
||||
|
||||
// Insert the codesigning certs for the running santad and launchd into the initial database.
|
||||
// This helps prevent accidentally denying critical system components while the database
|
||||
// is empty. This 'initial database' will then be cleared on the first successful sync.
|
||||
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
|
||||
self.santadCertSHA, @(RULESTATE_WHITELIST), @(RULETYPE_CERT)];
|
||||
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
|
||||
self.launchdCertSHA, @(RULESTATE_WHITELIST), @(RULETYPE_CERT)];
|
||||
[[SNTConfigurator configurator] setSyncCleanRequired:YES];
|
||||
|
||||
newVersion = 1;
|
||||
}
|
||||
|
||||
[[SNTConfigurator configurator] setSyncCleanRequired:YES];
|
||||
// Save hashes of the signing certs for launchd and santad.
|
||||
// Used to ensure rules for them are not removed.
|
||||
self.santadCertSHA = [[[[MOLCodesignChecker alloc] initWithSelf] leafCertificate] SHA256];
|
||||
self.launchdCertSHA = [[[[MOLCodesignChecker alloc] initWithPID:1] leafCertificate] SHA256];
|
||||
|
||||
// Ensure the certificates used to sign the running launchd/santad are whitelisted.
|
||||
// If they weren't previously and the database is not new, log an error.
|
||||
int ruleCount = [db intForQuery:@"SELECT COUNT(*)"
|
||||
@"FROM certrules "
|
||||
@"WHERE (shasum=? OR shasum=?) AND state=?",
|
||||
self.santadCertSHA, self.launchdCertSHA, @(SNTRuleStateWhitelist)];
|
||||
if (ruleCount != 2) {
|
||||
if (version > 0) LOGE(@"Started without launchd/santad certificate rules in place!");
|
||||
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
|
||||
self.santadCertSHA, @(SNTRuleStateWhitelist), @(SNTRuleTypeCertificate)];
|
||||
[db executeUpdate:@"INSERT INTO rules (shasum, state, type) VALUES (?, ?, ?)",
|
||||
self.launchdCertSHA, @(SNTRuleStateWhitelist), @(SNTRuleTypeCertificate)];
|
||||
}
|
||||
|
||||
return newVersion;
|
||||
@@ -130,9 +138,9 @@
|
||||
|
||||
#pragma mark Adding
|
||||
|
||||
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate {
|
||||
- (BOOL)addRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate error:(NSError **)error {
|
||||
if (!rules || rules.count < 1) {
|
||||
LOGE(@"Received request to add rules with nil/empty array.");
|
||||
[self fillError:error code:SNTRuleTableErrorEmptyRuleArray message:nil];
|
||||
return NO;
|
||||
}
|
||||
|
||||
@@ -142,13 +150,14 @@
|
||||
// Protect rules for santad/launchd certificates.
|
||||
NSPredicate *p = [NSPredicate predicateWithFormat:
|
||||
@"(SELF.shasum = %@ OR SELF.shasum = %@) AND SELF.type = %d",
|
||||
self.santadCertSHA, self.launchdCertSHA, RULETYPE_CERT];
|
||||
self.santadCertSHA, self.launchdCertSHA, SNTRuleTypeCertificate];
|
||||
NSArray *requiredHashes = [rules filteredArrayUsingPredicate:p];
|
||||
p = [NSPredicate predicateWithFormat:@"SELF.state == %d", RULESTATE_WHITELIST];
|
||||
p = [NSPredicate predicateWithFormat:@"SELF.state == %d", SNTRuleStateWhitelist];
|
||||
NSArray *requiredHashesWhitelist = [requiredHashes filteredArrayUsingPredicate:p];
|
||||
if ((cleanSlate && requiredHashesWhitelist.count != 2) ||
|
||||
(requiredHashes.count != requiredHashesWhitelist.count)) {
|
||||
LOGE(@"Received request to remove whitelist for launchd/santad ceritifcates.");
|
||||
LOGE(@"Received request to remove whitelist for launchd/santad certificates.");
|
||||
[self fillError:error code:SNTRuleTableErrorMissingRequiredRule message:nil];
|
||||
*rollback = failed = YES;
|
||||
return;
|
||||
}
|
||||
@@ -159,14 +168,18 @@
|
||||
|
||||
for (SNTRule *rule in rules) {
|
||||
if (![rule isKindOfClass:[SNTRule class]] || rule.shasum.length == 0 ||
|
||||
rule.state == RULESTATE_UNKNOWN || rule.type == RULETYPE_UNKNOWN) {
|
||||
rule.state == SNTRuleStateUnknown || rule.type == SNTRuleTypeUnknown) {
|
||||
[self fillError:error code:SNTRuleTableErrorInvalidRule message:nil];
|
||||
*rollback = failed = YES;
|
||||
return;
|
||||
}
|
||||
|
||||
if (rule.state == RULESTATE_REMOVE) {
|
||||
if (rule.state == SNTRuleStateRemove) {
|
||||
if (![db executeUpdate:@"DELETE FROM rules WHERE shasum=? AND type=?",
|
||||
rule.shasum, @(rule.type)]) {
|
||||
[self fillError:error
|
||||
code:SNTRuleTableErrorRemoveFailed
|
||||
message:[db lastErrorMessage]];
|
||||
*rollback = failed = YES;
|
||||
return;
|
||||
}
|
||||
@@ -175,6 +188,9 @@
|
||||
@"(shasum, state, type, custommsg) "
|
||||
@"VALUES (?, ?, ?, ?);",
|
||||
rule.shasum, @(rule.state), @(rule.type), rule.customMsg]) {
|
||||
[self fillError:error
|
||||
code:SNTRuleTableErrorInsertOrReplaceFailed
|
||||
message:[db lastErrorMessage]];
|
||||
*rollback = failed = YES;
|
||||
return;
|
||||
}
|
||||
@@ -185,4 +201,34 @@
|
||||
return !failed;
|
||||
}
|
||||
|
||||
// Helper to create an NSError where necessary.
|
||||
// The return value is irrelevant but the static analyzer complains if it's not a BOOL.
|
||||
- (BOOL)fillError:(NSError **)error code:(SNTRuleTableError)code message:(NSString *)message {
|
||||
if (!error) return NO;
|
||||
|
||||
NSMutableDictionary *d = [NSMutableDictionary dictionary];
|
||||
switch (code) {
|
||||
case SNTRuleTableErrorEmptyRuleArray:
|
||||
d[NSLocalizedDescriptionKey] = @"Empty rule array";
|
||||
break;
|
||||
case SNTRuleTableErrorInvalidRule:
|
||||
d[NSLocalizedDescriptionKey] = @"Rule array contained invalid entry";
|
||||
break;
|
||||
case SNTRuleTableErrorInsertOrReplaceFailed:
|
||||
d[NSLocalizedDescriptionKey] = @"A database error occurred while inserting/replacing a rule";
|
||||
break;
|
||||
case SNTRuleTableErrorRemoveFailed:
|
||||
d[NSLocalizedDescriptionKey] = @"A database error occurred while deleting a rule";
|
||||
break;
|
||||
case SNTRuleTableErrorMissingRequiredRule:
|
||||
d[NSLocalizedDescriptionKey] = @"A required rule was requested to be deleted";
|
||||
break;
|
||||
}
|
||||
|
||||
if (message) d[NSLocalizedFailureReasonErrorKey] = message;
|
||||
|
||||
*error = [NSError errorWithDomain:@"com.google.santad.ruletable" code:code userInfo:d];
|
||||
return YES;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -14,11 +14,11 @@
|
||||
|
||||
#import "SNTApplication.h"
|
||||
|
||||
#import <DiskArbitration/DiskArbitration.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/types.h>
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDaemonControlController.h"
|
||||
#import "SNTDatabaseController.h"
|
||||
@@ -34,6 +34,7 @@
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTApplication ()
|
||||
@property DASessionRef diskArbSession;
|
||||
@property SNTDriverManager *driverManager;
|
||||
@property SNTEventLog *eventLog;
|
||||
@property SNTExecutionController *execController;
|
||||
@@ -51,8 +52,6 @@
|
||||
|
||||
if (!_driverManager) {
|
||||
LOGE(@"Failed to connect to driver, exiting.");
|
||||
|
||||
// TODO(rah): Consider trying to load the extension from within santad.
|
||||
return nil;
|
||||
}
|
||||
|
||||
@@ -81,12 +80,34 @@
|
||||
_controlConnection.exportedObject = dc;
|
||||
[_controlConnection resume];
|
||||
|
||||
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath handler:^{
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
__block SNTClientMode origMode = [[SNTConfigurator configurator] clientMode];
|
||||
_configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
|
||||
handler:^(unsigned long data) {
|
||||
if (data & DISPATCH_VNODE_ATTRIB) {
|
||||
const char *cPath = [kDefaultConfigFilePath fileSystemRepresentation];
|
||||
struct stat fileStat;
|
||||
stat(cPath, &fileStat);
|
||||
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(@"Config file permissions changed, fixing.");
|
||||
chown(cPath, 0, 0);
|
||||
chmod(cPath, desired);
|
||||
}
|
||||
} else {
|
||||
LOGD(@"Config file changed, reloading.");
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
|
||||
// Ensure config file remains root:wheel 0644
|
||||
chown([kDefaultConfigFilePath fileSystemRepresentation], 0, 0);
|
||||
chmod([kDefaultConfigFilePath fileSystemRepresentation], 0644);
|
||||
// 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 flushCache];
|
||||
}
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
_eventLog = [[SNTEventLog alloc] init];
|
||||
@@ -108,6 +129,7 @@
|
||||
|
||||
[self performSelectorInBackground:@selector(beginListeningForDecisionRequests) withObject:nil];
|
||||
[self performSelectorInBackground:@selector(beginListeningForLogRequests) withObject:nil];
|
||||
[self performSelectorInBackground:@selector(beginListeningForDiskMounts) withObject:nil];
|
||||
}
|
||||
|
||||
- (void)beginListeningForDecisionRequests {
|
||||
@@ -174,4 +196,38 @@
|
||||
}];
|
||||
}
|
||||
|
||||
- (void)beginListeningForDiskMounts {
|
||||
dispatch_queue_t disk_queue = dispatch_queue_create(
|
||||
"com.google.santad.disk_queue", DISPATCH_QUEUE_SERIAL);
|
||||
|
||||
_diskArbSession = DASessionCreate(NULL);
|
||||
DASessionSetDispatchQueue(_diskArbSession, disk_queue);
|
||||
|
||||
DARegisterDiskAppearedCallback(
|
||||
_diskArbSession, NULL, diskAppearedCallback, (__bridge void *)self);
|
||||
DARegisterDiskDescriptionChangedCallback(
|
||||
_diskArbSession, NULL, NULL, diskDescriptionChangedCallback, (__bridge void *)self);
|
||||
DARegisterDiskDisappearedCallback(
|
||||
_diskArbSession, NULL, diskDisappearedCallback, (__bridge void *)self);
|
||||
}
|
||||
|
||||
void diskAppearedCallback(DADiskRef disk, void *context) {
|
||||
SNTApplication *app = (__bridge SNTApplication *)context;
|
||||
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
|
||||
[app.eventLog logDiskAppeared:props];
|
||||
}
|
||||
|
||||
void diskDescriptionChangedCallback(DADiskRef disk, CFArrayRef keys, void *context) {
|
||||
SNTApplication *app = (__bridge SNTApplication *)context;
|
||||
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
|
||||
if (props[@"DAVolumePath"]) [app.eventLog logDiskAppeared:props];
|
||||
}
|
||||
|
||||
void diskDisappearedCallback(DADiskRef disk, void *context) {
|
||||
SNTApplication *app = (__bridge SNTApplication *)context;
|
||||
NSDictionary *props = CFBridgingRelease(DADiskCopyDescription(disk));
|
||||
[app.eventLog logDiskDisappeared:props];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
///
|
||||
/// Store information about executions from decision making for later logging.
|
||||
@@ -20,7 +20,7 @@
|
||||
@interface SNTCachedDecision : NSObject
|
||||
|
||||
@property uint64_t vnodeId;
|
||||
@property santa_eventstate_t decision;
|
||||
@property SNTEventState decision;
|
||||
@property NSString *decisionExtra;
|
||||
@property NSString *sha256;
|
||||
@property NSString *certSHA256;
|
||||
|
||||
@@ -33,6 +33,7 @@ double watchdogCPUPeak = 0;
|
||||
double watchdogRAMPeak = 0;
|
||||
|
||||
@interface SNTDaemonControlController ()
|
||||
@property NSString *_syncXsrfToken;
|
||||
@property dispatch_source_t syncTimer;
|
||||
@end
|
||||
|
||||
@@ -97,23 +98,20 @@ double watchdogRAMPeak = 0;
|
||||
reply([rdb binaryRuleCount], [rdb certificateRuleCount]);
|
||||
}
|
||||
|
||||
- (void)databaseRuleAddRule:(SNTRule *)rule cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(BOOL success))reply {
|
||||
[self databaseRuleAddRules:@[ rule ] cleanSlate:cleanSlate reply:reply];
|
||||
}
|
||||
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(BOOL success))reply {
|
||||
BOOL success = [[SNTDatabaseController ruleTable] addRules:rules cleanSlate:cleanSlate];
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(NSError *error))reply {
|
||||
NSError *error;
|
||||
[[SNTDatabaseController ruleTable] addRules:rules cleanSlate:cleanSlate error:&error];
|
||||
|
||||
// If any rules were added that were not whitelist, flush cache.
|
||||
NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF.state != %d", RULESTATE_WHITELIST];
|
||||
NSPredicate *p = [NSPredicate predicateWithFormat:@"SELF.state != %d", SNTRuleStateWhitelist];
|
||||
if ([rules filteredArrayUsingPredicate:p].count || cleanSlate) {
|
||||
LOGI(@"Received non-whitelist rule, flushing cache");
|
||||
[self.driverManager flushCache];
|
||||
}
|
||||
|
||||
reply(success);
|
||||
reply(error);
|
||||
}
|
||||
|
||||
- (void)databaseEventCount:(void (^)(int64_t count))reply {
|
||||
@@ -132,14 +130,34 @@ double watchdogRAMPeak = 0;
|
||||
[[SNTDatabaseController eventTable] deleteEventsWithIds:ids];
|
||||
}
|
||||
|
||||
- (void)databaseBinaryRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] binaryRuleForSHA256:sha256]);
|
||||
}
|
||||
|
||||
- (void)databaseCertificateRuleForSHA256:(NSString *)sha256 reply:(void (^)(SNTRule *))reply {
|
||||
reply([[SNTDatabaseController ruleTable] certificateRuleForSHA256:sha256]);
|
||||
}
|
||||
|
||||
#pragma mark Config Ops
|
||||
|
||||
- (void)clientMode:(void (^)(santa_clientmode_t))reply {
|
||||
- (void)clientMode:(void (^)(SNTClientMode))reply {
|
||||
reply([[SNTConfigurator configurator] clientMode]);
|
||||
}
|
||||
|
||||
- (void)setClientMode:(santa_clientmode_t)mode reply:(void (^)())reply {
|
||||
[[SNTConfigurator configurator] setClientMode:mode];
|
||||
- (void)setClientMode:(SNTClientMode)mode reply:(void (^)())reply {
|
||||
if ([[SNTConfigurator configurator] clientMode] != mode) {
|
||||
[[SNTConfigurator configurator] setClientMode:mode];
|
||||
[[self.notQueue.notifierConnection remoteObjectProxy] postClientModeNotification:mode];
|
||||
}
|
||||
reply();
|
||||
}
|
||||
|
||||
- (void)xsrfToken:(void (^)(NSString *))reply {
|
||||
reply(self._syncXsrfToken);
|
||||
}
|
||||
|
||||
- (void)setXsrfToken:(NSString *)token reply:(void (^)())reply {
|
||||
self._syncXsrfToken = token;
|
||||
reply();
|
||||
}
|
||||
|
||||
|
||||
@@ -14,10 +14,10 @@
|
||||
|
||||
#import "SNTDriverManager.h"
|
||||
|
||||
#include <IOKit/IODataQueueClient.h>
|
||||
#include <mach/mach.h>
|
||||
#import <IOKit/IODataQueueClient.h>
|
||||
#import <IOKit/Kext/KextManager.h>
|
||||
|
||||
#include "SNTLogging.h"
|
||||
#import "SNTLogging.h"
|
||||
|
||||
@interface SNTDriverManager ()
|
||||
@property io_connect_t connection;
|
||||
@@ -41,6 +41,9 @@ static const int MAX_DELAY = 15;
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Attempt to load driver. It may already be running, so ignore any return value.
|
||||
KextManagerLoadKextWithIdentifier(CFSTR(USERCLIENT_ID), NULL);
|
||||
|
||||
// Locate driver. Wait for it if necessary.
|
||||
int delay = 1;
|
||||
do {
|
||||
|
||||
@@ -21,6 +21,9 @@
|
||||
///
|
||||
@interface SNTEventLog : NSObject
|
||||
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties;
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties;
|
||||
|
||||
- (void)logFileModification:(santa_message_t)message;
|
||||
|
||||
- (void)saveDecisionDetails:(SNTCachedDecision *)cd;
|
||||
|
||||
@@ -86,10 +86,8 @@
|
||||
if (newpath) {
|
||||
outStr = [outStr stringByAppendingFormat:@"|newpath=%@", [self sanitizeString:newpath]];
|
||||
}
|
||||
char ppath[PATH_MAX];
|
||||
if (proc_pidpath(message.pid, ppath, PATH_MAX) < 1) {
|
||||
strncpy(ppath, "(null)", 6);
|
||||
}
|
||||
char ppath[PATH_MAX] = "(null)";
|
||||
proc_pidpath(message.pid, ppath, PATH_MAX);
|
||||
|
||||
NSString *user, *group;
|
||||
struct passwd *pw = getpwuid(message.uid);
|
||||
@@ -121,39 +119,39 @@
|
||||
NSString *d, *r, *args, *outLog;
|
||||
|
||||
switch (cd.decision) {
|
||||
case EVENTSTATE_ALLOW_BINARY:
|
||||
case SNTEventStateAllowBinary:
|
||||
d = @"ALLOW";
|
||||
r = @"BINARY";
|
||||
args = [self argsForPid:message.pid];
|
||||
break;
|
||||
case EVENTSTATE_ALLOW_CERTIFICATE:
|
||||
case SNTEventStateAllowCertificate:
|
||||
d = @"ALLOW";
|
||||
r = @"CERTIFICATE";
|
||||
args = [self argsForPid:message.pid];
|
||||
break;
|
||||
case EVENTSTATE_ALLOW_SCOPE:
|
||||
case SNTEventStateAllowScope:
|
||||
d = @"ALLOW";
|
||||
r = @"SCOPE";
|
||||
args = [self argsForPid:message.pid];
|
||||
break;
|
||||
case EVENTSTATE_ALLOW_UNKNOWN:
|
||||
case SNTEventStateAllowUnknown:
|
||||
d = @"ALLOW";
|
||||
r = @"UNKNOWN";
|
||||
args = [self argsForPid:message.pid];
|
||||
break;
|
||||
case EVENTSTATE_BLOCK_BINARY:
|
||||
case SNTEventStateBlockBinary:
|
||||
d = @"DENY";
|
||||
r = @"BINARY";
|
||||
break;
|
||||
case EVENTSTATE_BLOCK_CERTIFICATE:
|
||||
case SNTEventStateBlockCertificate:
|
||||
d = @"DENY";
|
||||
r = @"CERT";
|
||||
break;
|
||||
case EVENTSTATE_BLOCK_SCOPE:
|
||||
case SNTEventStateBlockScope:
|
||||
d = @"DENY";
|
||||
r = @"SCOPE";
|
||||
break;
|
||||
case EVENTSTATE_BLOCK_UNKNOWN:
|
||||
case SNTEventStateBlockUnknown:
|
||||
d = @"DENY";
|
||||
r = @"UNKNOWN";
|
||||
break;
|
||||
@@ -197,6 +195,43 @@
|
||||
LOGI(@"%@", outLog);
|
||||
}
|
||||
|
||||
- (void)logDiskAppeared:(NSDictionary *)diskProperties {
|
||||
if (![diskProperties[@"DAVolumeMountable"] boolValue]) return;
|
||||
|
||||
NSString *dmgPath = @"";
|
||||
NSString *serial = @"";
|
||||
if ([diskProperties[@"DADeviceModel"] isEqual:@"Disk Image"]) {
|
||||
dmgPath = [self diskImageForDevice:diskProperties[@"DADevicePath"]];
|
||||
} else {
|
||||
serial = [self serialForDevice:diskProperties[@"DADevicePath"]];
|
||||
serial = [serial stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
}
|
||||
|
||||
NSString *model = [NSString stringWithFormat:@"%@ %@",
|
||||
diskProperties[@"DADeviceVendor"] ?: @"",
|
||||
diskProperties[@"DADeviceModel"] ?: @""];
|
||||
model = [model stringByTrimmingCharactersInSet:[NSCharacterSet whitespaceCharacterSet]];
|
||||
|
||||
LOGI(@"action=DISKAPPEAR|mount=%@|volume=%@|bsdname=%@|fs=%@|model=%@|serial=%@|bus=%@|dmgpath=%@",
|
||||
[diskProperties[@"DAVolumePath"] path] ?: @"",
|
||||
diskProperties[@"DAVolumeName"] ?: @"",
|
||||
diskProperties[@"DAMediaBSDName"] ?: @"",
|
||||
diskProperties[@"DAVolumeKind"] ?: @"",
|
||||
model ?: @"",
|
||||
serial,
|
||||
diskProperties[@"DADeviceProtocol"] ?: @"",
|
||||
dmgPath);
|
||||
}
|
||||
|
||||
- (void)logDiskDisappeared:(NSDictionary *)diskProperties {
|
||||
if (![diskProperties[@"DAVolumeMountable"] boolValue]) return;
|
||||
|
||||
LOGI(@"action=DISKDISAPPEAR|mount=%@|volume=%@|bsdname=%@",
|
||||
[diskProperties[@"DAVolumePath"] path] ?: @"",
|
||||
diskProperties[@"DAVolumeName"] ?: @"",
|
||||
diskProperties[@"DAMediaBSDName"]);
|
||||
}
|
||||
|
||||
#pragma mark Helpers
|
||||
|
||||
- (NSString *)sanitizeString:(NSString *)inStr {
|
||||
@@ -206,6 +241,9 @@
|
||||
return inStr;
|
||||
}
|
||||
|
||||
/**
|
||||
Use sysctl to get the arguments for a PID, returned as a single string.
|
||||
*/
|
||||
- (NSString *)argsForPid:(pid_t)pid {
|
||||
int mib[3];
|
||||
|
||||
@@ -262,4 +300,59 @@
|
||||
return [args componentsJoinedByString:@" "];
|
||||
}
|
||||
|
||||
/**
|
||||
Given an IOKit device path (like those provided by DiskArbitration), find the disk
|
||||
image path by looking up the device in the IOKit registry and getting its properties.
|
||||
|
||||
This is largely the same as the way hdiutil gathers info for the "info" command.
|
||||
*/
|
||||
- (NSString *)diskImageForDevice:(NSString *)devPath {
|
||||
devPath = [devPath stringByDeletingLastPathComponent];
|
||||
if (!devPath.length) return nil;
|
||||
io_registry_entry_t device = IORegistryEntryCopyFromPath(
|
||||
kIOMasterPortDefault, (__bridge CFStringRef)devPath);
|
||||
CFMutableDictionaryRef deviceProperties = NULL;
|
||||
IORegistryEntryCreateCFProperties(device, &deviceProperties, kCFAllocatorDefault, kNilOptions);
|
||||
NSDictionary *properties = CFBridgingRelease(deviceProperties);
|
||||
IOObjectRelease(device);
|
||||
|
||||
NSData *pathData = properties[@"image-path"];
|
||||
NSString *result = [[NSString alloc] initWithData:pathData encoding:NSUTF8StringEncoding];
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
Given an IOKit device path (like those provided by DiskArbitration), find the device serial number,
|
||||
if there is one. This has only really been tested with USB and internal devices.
|
||||
*/
|
||||
- (NSString *)serialForDevice:(NSString *)devPath {
|
||||
if (!devPath.length) return nil;
|
||||
NSString *serial;
|
||||
io_registry_entry_t device = IORegistryEntryCopyFromPath(
|
||||
kIOMasterPortDefault, (__bridge CFStringRef)devPath);
|
||||
while (!serial && device) {
|
||||
CFMutableDictionaryRef deviceProperties = NULL;
|
||||
IORegistryEntryCreateCFProperties(device, &deviceProperties, kCFAllocatorDefault, kNilOptions);
|
||||
NSDictionary *properties = CFBridgingRelease(deviceProperties);
|
||||
if (properties[@"Serial Number"]) {
|
||||
serial = properties[@"Serial Number"];
|
||||
} else if (properties[@"kUSBSerialNumberString"]) {
|
||||
serial = properties[@"kUSBSerialNumberString"];
|
||||
}
|
||||
|
||||
if (serial) {
|
||||
IOObjectRelease(device);
|
||||
break;
|
||||
}
|
||||
|
||||
io_registry_entry_t parent;
|
||||
IORegistryEntryGetParentEntry(device, kIOServicePlane, &parent);
|
||||
IOObjectRelease(device);
|
||||
device = parent;
|
||||
}
|
||||
|
||||
return serial;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
|
||||
@@ -22,6 +22,7 @@
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTBlockMessage.h"
|
||||
#import "SNTCachedDecision.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTConfigurator.h"
|
||||
@@ -61,17 +62,17 @@
|
||||
|
||||
#pragma mark Binary Validation
|
||||
|
||||
- (santa_eventstate_t)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
|
||||
- (SNTEventState)makeDecision:(SNTCachedDecision *)cd binaryInfo:(SNTFileInfo *)fi {
|
||||
SNTRule *rule = [self.ruleTable binaryRuleForSHA256:cd.sha256];
|
||||
if (rule) {
|
||||
switch (rule.state) {
|
||||
case RULESTATE_WHITELIST:
|
||||
return EVENTSTATE_ALLOW_BINARY;
|
||||
case RULESTATE_SILENT_BLACKLIST:
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowBinary;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case RULESTATE_BLACKLIST:
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return EVENTSTATE_BLOCK_BINARY;
|
||||
return SNTEventStateBlockBinary;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -79,13 +80,13 @@
|
||||
rule = [self.ruleTable certificateRuleForSHA256:cd.certSHA256];
|
||||
if (rule) {
|
||||
switch (rule.state) {
|
||||
case RULESTATE_WHITELIST:
|
||||
return EVENTSTATE_ALLOW_CERTIFICATE;
|
||||
case RULESTATE_SILENT_BLACKLIST:
|
||||
case SNTRuleStateWhitelist:
|
||||
return SNTEventStateAllowCertificate;
|
||||
case SNTRuleStateSilentBlacklist:
|
||||
cd.silentBlock = YES;
|
||||
case RULESTATE_BLACKLIST:
|
||||
case SNTRuleStateBlacklist:
|
||||
cd.customMsg = rule.customMsg;
|
||||
return EVENTSTATE_BLOCK_CERTIFICATE;
|
||||
return SNTEventStateBlockCertificate;
|
||||
default: break;
|
||||
}
|
||||
}
|
||||
@@ -93,19 +94,19 @@
|
||||
NSString *msg = [self fileIsScopeBlacklisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return EVENTSTATE_BLOCK_SCOPE;
|
||||
return SNTEventStateBlockScope;
|
||||
}
|
||||
|
||||
msg = [self fileIsScopeWhitelisted:fi];
|
||||
if (msg) {
|
||||
cd.decisionExtra = msg;
|
||||
return EVENTSTATE_ALLOW_SCOPE;
|
||||
return SNTEventStateAllowScope;
|
||||
}
|
||||
|
||||
switch ([[SNTConfigurator configurator] clientMode]) {
|
||||
case CLIENTMODE_MONITOR: return EVENTSTATE_ALLOW_UNKNOWN;
|
||||
case CLIENTMODE_LOCKDOWN: return EVENTSTATE_BLOCK_UNKNOWN;
|
||||
default: return EVENTSTATE_BLOCK_UNKNOWN;
|
||||
case SNTClientModeMonitor: return SNTEventStateAllowUnknown;
|
||||
case SNTClientModeLockdown: return SNTEventStateBlockUnknown;
|
||||
default: return SNTEventStateBlockUnknown;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -120,6 +121,9 @@
|
||||
return;
|
||||
}
|
||||
|
||||
// PrinterProxy workaround, see description above the method for more details.
|
||||
if ([self printerProxyWorkaround:binInfo]) return;
|
||||
|
||||
// Get codesigning info about the file.
|
||||
MOLCodesignChecker *csInfo = [[MOLCodesignChecker alloc] initWithBinaryPath:binInfo.path];
|
||||
|
||||
@@ -140,9 +144,9 @@
|
||||
[self.driverManager postToKernelAction:action forVnodeID:cd.vnodeId];
|
||||
|
||||
// Log to database if necessary.
|
||||
if (cd.decision != EVENTSTATE_ALLOW_BINARY &&
|
||||
cd.decision != EVENTSTATE_ALLOW_CERTIFICATE &&
|
||||
cd.decision != EVENTSTATE_ALLOW_SCOPE) {
|
||||
if (cd.decision != SNTEventStateAllowBinary &&
|
||||
cd.decision != SNTEventStateAllowCertificate &&
|
||||
cd.decision != SNTEventStateAllowScope) {
|
||||
SNTStoredEvent *se = [[SNTStoredEvent alloc] init];
|
||||
se.occurrenceDate = [[NSDate alloc] init];
|
||||
se.fileSHA256 = cd.sha256;
|
||||
@@ -154,27 +158,26 @@
|
||||
se.ppid = @(message.ppid);
|
||||
se.parentName = @(message.pname);
|
||||
|
||||
// Bundle data
|
||||
se.fileBundleID = [binInfo bundleIdentifier];
|
||||
se.fileBundleName = [binInfo bundleName];
|
||||
|
||||
se.fileBundlePath = [binInfo bundlePath];
|
||||
if ([binInfo bundleShortVersionString]) {
|
||||
se.fileBundleVersionString = [binInfo bundleShortVersionString];
|
||||
}
|
||||
|
||||
if ([binInfo bundleVersion]) {
|
||||
se.fileBundleVersion = [binInfo bundleVersion];
|
||||
}
|
||||
|
||||
// User data
|
||||
struct passwd *user = getpwuid(message.uid);
|
||||
if (user) {
|
||||
se.executingUser = @(user->pw_name);
|
||||
}
|
||||
|
||||
if (user) se.executingUser = @(user->pw_name);
|
||||
NSArray *loggedInUsers, *currentSessions;
|
||||
[self loggedInUsers:&loggedInUsers sessions:¤tSessions];
|
||||
se.currentSessions = currentSessions;
|
||||
se.loggedInUsers = loggedInUsers;
|
||||
|
||||
// Quarantine data
|
||||
se.quarantineDataURL = binInfo.quarantineDataURL;
|
||||
se.quarantineRefererURL = binInfo.quarantineRefererURL;
|
||||
se.quarantineTimestamp = binInfo.quarantineTimestamp;
|
||||
@@ -191,6 +194,16 @@
|
||||
[self initiateEventUploadForEvent:se];
|
||||
|
||||
if (!cd.silentBlock) {
|
||||
// Let the user know what happened, both on the terminal and in the GUI.
|
||||
NSAttributedString *s = [SNTBlockMessage attributedBlockMessageForEvent:se
|
||||
customMessage:cd.customMsg];
|
||||
NSString *msg = [NSString stringWithFormat:@"\033[1mSanta\033[0m\n\n%@\n\n", s.string];
|
||||
NSURL *detailURL = [SNTBlockMessage eventDetailURLForEvent:se];
|
||||
if (detailURL) {
|
||||
msg = [msg stringByAppendingFormat:@"%@\n\n", detailURL.absoluteString];
|
||||
}
|
||||
[self printMessage:msg toTTYForPID:message.ppid];
|
||||
|
||||
[self.notifierQueue addEvent:se customMessage:cd.customMsg];
|
||||
}
|
||||
}
|
||||
@@ -236,6 +249,47 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
/**
|
||||
Workaround for issue with PrinterProxy.app.
|
||||
|
||||
Every time a new printer is added to the machine, a copy of the PrinterProxy.app is copied from
|
||||
the Print.framework to ~/Library/Printers with the name of the printer as the name of the app.
|
||||
The binary inside is changed slightly (in a way that is unique to the printer name) and then
|
||||
re-signed with an adhoc signature. I don't know why this is done but it seems that the binary
|
||||
itself doesn't need to be changed as copying the old one back in-place seems to work,
|
||||
so that's what we do.
|
||||
|
||||
If this workaround is applied the decision request is not responded to as the existing request
|
||||
is invalidated when the file is closed which will trigger a brand new request coming from the
|
||||
kernel.
|
||||
|
||||
@param fi, SNTFileInfo object for the binary being executed.
|
||||
@return YES if the workaround was applied, NO otherwise.
|
||||
*/
|
||||
- (BOOL)printerProxyWorkaround:(SNTFileInfo *)fi {
|
||||
if ([fi.path hasSuffix:@"/Contents/MacOS/PrinterProxy"] &&
|
||||
[fi.path containsString:@"Library/Printers"]) {
|
||||
NSString *proxyPath = (@"/System/Library/Frameworks/Carbon.framework/Versions/Current/"
|
||||
@"Frameworks/Print.framework/Versions/Current/Plugins/PrinterProxy.app/"
|
||||
@"Contents/MacOS/PrinterProxy");
|
||||
SNTFileInfo *proxyFi = [[SNTFileInfo alloc] initWithPath:proxyPath];
|
||||
if ([proxyFi.SHA256 isEqual:fi.SHA256]) return NO;
|
||||
|
||||
NSFileHandle *inFh = [NSFileHandle fileHandleForReadingAtPath:proxyPath];
|
||||
NSFileHandle *outFh = [NSFileHandle fileHandleForWritingAtPath:fi.path];
|
||||
[outFh writeData:[inFh readDataToEndOfFile]];
|
||||
[inFh closeFile];
|
||||
[outFh truncateFileAtOffset:[outFh offsetInFile]];
|
||||
[outFh synchronizeFile];
|
||||
[outFh closeFile];
|
||||
|
||||
LOGW(@"PrinterProxy workaround applied to %@", fi.path);
|
||||
|
||||
return YES;
|
||||
}
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (void)initiateEventUploadForEvent:(SNTStoredEvent *)event {
|
||||
// The event upload is skipped if the full path is equal to that of santactl so that
|
||||
// on the off chance that santactl is not whitelisted, we don't get into an infinite loop.
|
||||
@@ -244,35 +298,63 @@
|
||||
![[SNTConfigurator configurator] syncBaseURL] ||
|
||||
[[SNTConfigurator configurator] syncBackOff]) return;
|
||||
|
||||
// The event upload is skipped if an event upload has been initiated for it in the
|
||||
// last 10 minutes.
|
||||
static dispatch_once_t onceToken;
|
||||
static NSMutableDictionary *uploadBackoff;
|
||||
dispatch_once(&onceToken, ^{
|
||||
uploadBackoff = [NSMutableDictionary dictionary];
|
||||
});
|
||||
NSDate *backoff = uploadBackoff[event.fileSHA256];
|
||||
NSDate *now = [NSDate date];
|
||||
if (([now timeIntervalSince1970] - [backoff timeIntervalSince1970]) < 600) return;
|
||||
uploadBackoff[event.fileSHA256] = now;
|
||||
|
||||
if (fork() == 0) {
|
||||
// Ensure we have no privileges
|
||||
if (!DropRootPrivileges()) {
|
||||
_exit(EPERM);
|
||||
}
|
||||
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "singleevent",
|
||||
[event.fileSHA256 UTF8String], NULL));
|
||||
_exit(execl(kSantaCtlPath, kSantaCtlPath, "sync", "--syslog",
|
||||
"singleevent", [event.fileSHA256 UTF8String], NULL));
|
||||
}
|
||||
}
|
||||
|
||||
- (santa_action_t)actionForEventState:(santa_eventstate_t)state {
|
||||
- (santa_action_t)actionForEventState:(SNTEventState)state {
|
||||
switch (state) {
|
||||
case EVENTSTATE_ALLOW_BINARY:
|
||||
case EVENTSTATE_ALLOW_CERTIFICATE:
|
||||
case EVENTSTATE_ALLOW_SCOPE:
|
||||
case EVENTSTATE_ALLOW_UNKNOWN:
|
||||
case SNTEventStateAllowBinary:
|
||||
case SNTEventStateAllowCertificate:
|
||||
case SNTEventStateAllowScope:
|
||||
case SNTEventStateAllowUnknown:
|
||||
return ACTION_RESPOND_ALLOW;
|
||||
case EVENTSTATE_BLOCK_BINARY:
|
||||
case EVENTSTATE_BLOCK_CERTIFICATE:
|
||||
case EVENTSTATE_BLOCK_SCOPE:
|
||||
case EVENTSTATE_BLOCK_UNKNOWN:
|
||||
case SNTEventStateBlockBinary:
|
||||
case SNTEventStateBlockCertificate:
|
||||
case SNTEventStateBlockScope:
|
||||
case SNTEventStateBlockUnknown:
|
||||
return ACTION_RESPOND_DENY;
|
||||
default:
|
||||
LOGW(@"Invalid event state %d", state);
|
||||
LOGW(@"Invalid event state %ld", state);
|
||||
return ACTION_RESPOND_DENY;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)printMessage:(NSString *)msg toTTYForPID:(pid_t)pid {
|
||||
if (pid < 2) return; // don't bother even looking for launchd.
|
||||
|
||||
struct proc_bsdinfo taskInfo = {};
|
||||
if (proc_pidinfo(pid, PROC_PIDTBSDINFO, 0, &taskInfo, sizeof(taskInfo)) < 1) {
|
||||
return;
|
||||
}
|
||||
|
||||
NSString *devPath = [NSString stringWithFormat:@"/dev/%s", devname(taskInfo.e_tdev, S_IFCHR)];
|
||||
int fd = open(devPath.UTF8String, O_WRONLY | O_NOCTTY);
|
||||
@try {
|
||||
NSFileHandle *fh = [[NSFileHandle alloc] initWithFileDescriptor:fd closeOnDealloc:YES];
|
||||
[fh writeData:[msg dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
} @catch (NSException *) { /* do nothing */ }
|
||||
}
|
||||
|
||||
- (void)loggedInUsers:(NSArray **)users sessions:(NSArray **)sessions {
|
||||
NSMutableDictionary *loggedInUsers = [[NSMutableDictionary alloc] init];
|
||||
NSMutableDictionary *loggedInHosts = [[NSMutableDictionary alloc] init];
|
||||
|
||||
Binary file not shown.
@@ -1,24 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIEBDCCAuygAwIBAgIDAjppMA0GCSqGSIb3DQEBBQUAMEIxCzAJBgNVBAYTAlVT
|
||||
MRYwFAYDVQQKEw1HZW9UcnVzdCBJbmMuMRswGQYDVQQDExJHZW9UcnVzdCBHbG9i
|
||||
YWwgQ0EwHhcNMTMwNDA1MTUxNTU1WhcNMTUwNDA0MTUxNTU1WjBJMQswCQYDVQQG
|
||||
EwJVUzETMBEGA1UEChMKR29vZ2xlIEluYzElMCMGA1UEAxMcR29vZ2xlIEludGVy
|
||||
bmV0IEF1dGhvcml0eSBHMjCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEB
|
||||
AJwqBHdc2FCROgajguDYUEi8iT/xGXAaiEZ+4I/F8YnOIe5a/mENtzJEiaB0C1NP
|
||||
VaTOgmKV7utZX8bhBYASxF6UP7xbSDj0U/ck5vuR6RXEz/RTDfRK/J9U3n2+oGtv
|
||||
h8DQUB8oMANA2ghzUWx//zo8pzcGjr1LEQTrfSTe5vn8MXH7lNVg8y5Kr0LSy+rE
|
||||
ahqyzFPdFUuLH8gZYR/Nnag+YyuENWllhMgZxUYi+FOVvuOAShDGKuy6lyARxzmZ
|
||||
EASg8GF6lSWMTlJ14rbtCMoU/M4iarNOz0YDl5cDfsCx3nuvRTPPuj5xt970JSXC
|
||||
DTWJnZ37DhF5iR43xa+OcmkCAwEAAaOB+zCB+DAfBgNVHSMEGDAWgBTAephojYn7
|
||||
qwVkDBF9qn1luMrMTjAdBgNVHQ4EFgQUSt0GFhu89mi1dvWBtrtiGrpagS8wEgYD
|
||||
VR0TAQH/BAgwBgEB/wIBADAOBgNVHQ8BAf8EBAMCAQYwOgYDVR0fBDMwMTAvoC2g
|
||||
K4YpaHR0cDovL2NybC5nZW90cnVzdC5jb20vY3Jscy9ndGdsb2JhbC5jcmwwPQYI
|
||||
KwYBBQUHAQEEMTAvMC0GCCsGAQUFBzABhiFodHRwOi8vZ3RnbG9iYWwtb2NzcC5n
|
||||
ZW90cnVzdC5jb20wFwYDVR0gBBAwDjAMBgorBgEEAdZ5AgUBMA0GCSqGSIb3DQEB
|
||||
BQUAA4IBAQA21waAESetKhSbOHezI6B1WLuxfoNCunLaHtiONgaX4PCVOzf9G0JY
|
||||
/iLIa704XtE7JW4S615ndkZAkNoUyHgN7ZVm2o6Gb4ChulYylYbc3GrKBIxbf/a/
|
||||
zG+FA1jDaFETzf3I93k9mTXwVqO94FntT0QJo544evZG0R0SnU++0ED8Vf4GXjza
|
||||
HFa9llF7b1cq26KqltyMdMKVvvBulRP/F/A8rLIQjcxz++iPAsbw+zOzlTvjwsto
|
||||
WHPbqCRiOwY1nQ2pM714A5AuTHhdUDqB1O6gyHA43LL5Z/qHQF1hwFGPa4NrzQU6
|
||||
yuGnBXj8ytqU0CwIPX4WecigUCAkVDNx
|
||||
-----END CERTIFICATE-----
|
||||
@@ -1,34 +0,0 @@
|
||||
-----BEGIN CERTIFICATE-----
|
||||
MIIF+DCCBOCgAwIBAgIQXvpnDpnkq4jg8gszhnt4TTANBgkqhkiG9w0BAQUFADCB
|
||||
vjELMAkGA1UEBhMCVVMxFzAVBgNVBAoTDlZlcmlTaWduLCBJbmMuMR8wHQYDVQQL
|
||||
ExZWZXJpU2lnbiBUcnVzdCBOZXR3b3JrMTswOQYDVQQLEzJUZXJtcyBvZiB1c2Ug
|
||||
YXQgaHR0cHM6Ly93d3cudmVyaXNpZ24uY29tL3JwYSAoYykwNjE4MDYGA1UEAxMv
|
||||
VmVyaVNpZ24gQ2xhc3MgMyBFeHRlbmRlZCBWYWxpZGF0aW9uIFNTTCBTR0MgQ0Ew
|
||||
HhcNMTMxMTE0MDAwMDAwWhcNMTUxMTE0MjM1OTU5WjCCAQoxEzARBgsrBgEEAYI3
|
||||
PAIBAxMCVVMxGzAZBgsrBgEEAYI3PAIBAhMKQ2FsaWZvcm5pYTEdMBsGA1UEDxMU
|
||||
UHJpdmF0ZSBPcmdhbml6YXRpb24xETAPBgNVBAUTCEMwODA2NTkyMQswCQYDVQQG
|
||||
EwJVUzEOMAwGA1UEERQFOTUwMTQxEzARBgNVBAgTCkNhbGlmb3JuaWExEjAQBgNV
|
||||
BAcUCUN1cGVydGlubzEYMBYGA1UECRQPMSBJbmZpbml0ZSBMb29wMRMwEQYDVQQK
|
||||
FApBcHBsZSBJbmMuMRcwFQYDVQQLFA5JU0cgZm9yIEFrYW1haTEWMBQGA1UEAxQN
|
||||
d3d3LmFwcGxlLmNvbTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAMRK
|
||||
AzgN5IfMI0ioi1hYtFu5x6CGTH3/wpdlACJhfUtcAk7k1GaaMQKy/6f4scGI3tmj
|
||||
K6qJjvEHf3cp6sliOPBrCLoby8/4r1T1+yBxanpe8arpChtj5KwD+owXQYwN7jqO
|
||||
IB4nQGQin+D3aJiiqJfBc4XxhAEh+u29/RM+ux3l+QDMTzOm2hecl/z+wqtmRNeq
|
||||
wCp1MHL+ejGaf60/SJUQM0QlQjqUcQBNbE73Lx+8UpZzsxjbEwOnstmSLkAjWQTE
|
||||
FBVwPGqLF6ttR6ytACnUi2w6bFJsDv71nuIO56/SpzB/ZivddtZv4iYDg8ALBnFF
|
||||
3AZI5jhbSid+vDzz5XECAwEAAaOCAaEwggGdMBgGA1UdEQQRMA+CDXd3dy5hcHBs
|
||||
ZS5jb20wCQYDVR0TBAIwADAOBgNVHQ8BAf8EBAMCBaAwKAYDVR0lBCEwHwYIKwYB
|
||||
BQUHAwEGCCsGAQUFBwMCBglghkgBhvhCBAEwRAYDVR0gBD0wOzA5BgtghkgBhvhF
|
||||
AQcXBjAqMCgGCCsGAQUFBwIBFhxodHRwczovL3d3dy52ZXJpc2lnbi5jb20vY3Bz
|
||||
MB0GA1UdDgQWBBT/EdiWLzrKfY9ayp23GIwElp5xiTAfBgNVHSMEGDAWgBROQ8gd
|
||||
du83U3pP8lhvlPM44tW93zA+BgNVHR8ENzA1MDOgMaAvhi1odHRwOi8vRVZJbnRs
|
||||
LWNybC52ZXJpc2lnbi5jb20vRVZJbnRsMjAwNi5jcmwwdgYIKwYBBQUHAQEEajBo
|
||||
MCsGCCsGAQUFBzABhh9odHRwOi8vRVZJbnRsLW9jc3AudmVyaXNpZ24uY29tMDkG
|
||||
CCsGAQUFBzAChi1odHRwOi8vRVZJbnRsLWFpYS52ZXJpc2lnbi5jb20vRVZJbnRs
|
||||
MjAwNi5jZXIwDQYJKoZIhvcNAQEFBQADggEBAI4W8R0KFLvKzi20sd3cSaWHLqNb
|
||||
K6AgFSShRtrrPIu2yNXzI+VjHZTuPhViqSGxfZJYI6c4e60zz5BkfuB0RbrJjQF9
|
||||
iFjA/+bfS6+dIU95D9+zwp6u/NhSZAoqUH8LU5+GLGdCy2f9wU1Gyp47jgY9sDdF
|
||||
tgkrVk1AnKU7Okd+n6ZOeUCTXgJgVm6UWFt5FXr80VMdEWv2U1JYxwNzITR+tJ//
|
||||
u5BBaPG2p/6E3d9lmer8Ffq97CZ8poAeXsQXtuwNdwMCrbUGZKalaS71G/sSMrAR
|
||||
xVVZakUhwnXXswczh701AK+TzB3dQNhFWooX/sn4+MiIkg+WPaxkd8AsCIM=
|
||||
-----END CERTIFICATE-----
|
||||
505
Tests/LogicTests/Resources/sync_eventupload_input_basic.plist
Normal file
505
Tests/LogicTests/Resources/sync_eventupload_input_basic.plist
Normal file
@@ -0,0 +1,505 @@
|
||||
<?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>$archiver</key>
|
||||
<string>NSKeyedArchiver</string>
|
||||
<key>$objects</key>
|
||||
<array>
|
||||
<string>$null</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>30</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>29</integer>
|
||||
</dict>
|
||||
<key>currentSessions</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>26</integer>
|
||||
</dict>
|
||||
<key>decision</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>19</integer>
|
||||
</dict>
|
||||
<key>executingUser</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>16</integer>
|
||||
</dict>
|
||||
<key>filePath</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>5</integer>
|
||||
</dict>
|
||||
<key>fileSHA256</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>idx</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>loggedInUsers</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>23</integer>
|
||||
</dict>
|
||||
<key>occurrenceDate</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>17</integer>
|
||||
</dict>
|
||||
<key>parentName</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>22</integer>
|
||||
</dict>
|
||||
<key>pid</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>20</integer>
|
||||
</dict>
|
||||
<key>ppid</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>21</integer>
|
||||
</dict>
|
||||
<key>signingChain</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>6</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<integer>14887</integer>
|
||||
<string>ff98fa0c0a1095fedcbe4d388a9760e71399a5c3c017a847ffa545663b57929a</string>
|
||||
<string>/usr/bin/yes</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>15</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>11</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>13</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
<key>certData</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>8</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIFOzCCBCOgAwIBAgIIKtpxuqe9F58wDQYJKoZIhvcNAQEFBQAw
|
||||
fzELMAkGA1UEBhMCVVMxEzARBgNVBAoMCkFwcGxlIEluYy4xJjAk
|
||||
BgNVBAsMHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MTMw
|
||||
MQYDVQQDDCpBcHBsZSBDb2RlIFNpZ25pbmcgQ2VydGlmaWNhdGlv
|
||||
biBBdXRob3JpdHkwHhcNMTMwNDEyMjIzNDM1WhcNMjEwNDEyMjIz
|
||||
NDM1WjBWMQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUgSW5j
|
||||
LjEXMBUGA1UECwwOQXBwbGUgU29mdHdhcmUxGTAXBgNVBAMMEFNv
|
||||
ZnR3YXJlIFNpZ25pbmcwggEiMA0GCSqGSIb3DQEBAQUAA4IBDwAw
|
||||
ggEKAoIBAQC/MLh0mE+uBguklG4xVG0J0TyjsDkQqdDmqmAiXdPk
|
||||
hKJAQZBkxmA9kWHaUqhFJ54sZMvkHqgkClI6s9PsFkF4wZ7RBuZ4
|
||||
JWMI89/KQeYd/jXpUVwTFYvp0Z1xe9HJqkuemdqPwCm4L5BvpLtl
|
||||
j4Bq1z1obeR4wqUSL/gy6X7JXVyMPhYgG9denRuGLQj3vBmkTQ5B
|
||||
pErbaxqARVAEqUyNFQfqaie9u4iePD+yUjmX47fI61RSmIovI1Zl
|
||||
5ekq2VG0I/oE3ffroN/VmvJeCPFfh/CxR2x1sbGM0RPjesHsYkF0
|
||||
poM08fladGQ5P1luzyzAYIMpPOfeT18N85M5XzCNAgMBAAGjggHi
|
||||
MIIB3jAdBgNVHQ4EFgQUxu0+Svsu6D8T1aAVs13Z57P3aDUwDAYD
|
||||
VR0TAQH/BAIwADAfBgNVHSMEGDAWgBSOaabEd0JOBKVWQpxRH4ba
|
||||
0iCPCTCCARwGA1UdIASCARMwggEPMIIBCwYJKoZIhvdjZAUBMIH9
|
||||
MDUGCCsGAQUFBwIBFilodHRwOi8vd3d3LmFwcGxlLmNvbS9jZXJ0
|
||||
aWZpY2F0ZWF1dGhvcml0eTCBwwYIKwYBBQUHAgIwgbYMgbNSZWxp
|
||||
YW5jZSBvbiB0aGlzIGNlcnRpZmljYXRlIGJ5IGFueSBwYXJ0eSBh
|
||||
c3N1bWVzIGFjY2VwdGFuY2Ugb2YgdGhlIHRoZW4gYXBwbGljYWJs
|
||||
ZSBzdGFuZGFyZCB0ZXJtcyBhbmQgY29uZGl0aW9ucyBvZiB1c2Us
|
||||
IGNlcnRpZmljYXRlIHBvbGljeSBhbmQgY2VydGlmaWNhdGlvbiBw
|
||||
cmFjdGljZSBzdGF0ZW1lbnRzLjA1BgNVHR8ELjAsMCqgKKAmhiRo
|
||||
dHRwOi8vY3JsLmFwcGxlLmNvbS9jb2Rlc2lnbmluZy5jcmwwDgYD
|
||||
VR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMDMA8G
|
||||
CSqGSIb3Y2QGFgQCBQAwDQYJKoZIhvcNAQEFBQADggEBAFfUxSFX
|
||||
GxTaEjIsEQUMBA+VqtTi+vLEbWjeUlINInPIhXMd71FO8IpJsGiU
|
||||
ZVEi3/1AjzW0aEBSuyWOzPrOfBJW2MDQVQW1SrG1YfyVfJFectEo
|
||||
tB0rbdpLZ58F/ObnWUpDXh97hDe+/rqKKzMFlFCDuP6a2wO7jWLy
|
||||
GW13k+N1zzZZMV4IbV0BHGVTUpN2eJwXCxAelLw2kFZLRC6Y2aYx
|
||||
ofAcZpSZVHMTtVE4vCSioDA7emWHrMC8FfReMMedoyoE38TPR4Rt
|
||||
n/3/RcOgGaw8u62PlPm5x8hxNhHt6AG6tHVIgqQqUxoFBZudxkcb
|
||||
9eggcqAbS+W+ZPw4DZr/Q0E=
|
||||
</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSMutableData</string>
|
||||
<string>NSData</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSMutableData</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>MOLCertificate</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>MOLCertificate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
<key>certData</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>12</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEDjCCAvagAwIBAgIBITANBgkqhkiG9w0BAQUFADBiMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
|
||||
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
|
||||
DUFwcGxlIFJvb3QgQ0EwHhcNMTExMDI0MTczOTQxWhcNMjYxMDI0
|
||||
MTczOTQxWjB/MQswCQYDVQQGEwJVUzETMBEGA1UECgwKQXBwbGUg
|
||||
SW5jLjEmMCQGA1UECwwdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHkxMzAxBgNVBAMMKkFwcGxlIENvZGUgU2lnbmluZyBDZXJ0
|
||||
aWZpY2F0aW9uIEF1dGhvcml0eTCCASIwDQYJKoZIhvcNAQEBBQAD
|
||||
ggEPADCCAQoCggEBAKKoEXH/DvkLa/glDZiBXWtZvVobibPn5e7O
|
||||
OZgjNTlInyGrJ9nunCDwZDgIawynz9xQth0GxFvxXRqbVGWGcy9i
|
||||
5Ti9ARBkcm18aUdhnBAFJuPrhcIsJNxqwj+I/MysKUyhSXkRmnV2
|
||||
5R640NIJtExTePvfGHahj6SpMsqRp7b6l705qs0bUBGIq2rt62bK
|
||||
IEusOy3vqufWyYgtacKkKmEv24cC86EhuUyfDvj52S3KcgR/Ha5u
|
||||
+j+Is8yjQO4XhxhRlrzP5C2twulZTl0cZTMnA6pno5Mkh8eHeQK5
|
||||
XZizDu7NaQg+jEiSJLJt1zC+z9jkyKeXgdAeI9w4mV9h/oUCAwEA
|
||||
AaOBsTCBrjAOBgNVHQ8BAf8EBAMCAYYwEwYDVR0lBAwwCgYIKwYB
|
||||
BQUHAwMwDwYDVR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUjmmmxHdC
|
||||
TgSlVkKcUR+G2tIgjwkwHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40u
|
||||
QKb3R01/CF4wNgYDVR0fBC8wLTAroCmgJ4YlaHR0cDovL3d3dy5h
|
||||
cHBsZS5jb20vYXBwbGVjYS9yb290LmNybDANBgkqhkiG9w0BAQUF
|
||||
AAOCAQEAcHOt9lIVarcVGN6pKtGddpsesmmWx8LD4SvQ7wddcPja
|
||||
PFpIR9s5bIDKc95iG7c6yqNaHuOH2iVKk5vvcxCTc13j9J1+3g+B
|
||||
9qmZwVhunPSJAL7PT/8C0w789fP0choysconDt6o05mPauaZ+2HJ
|
||||
T/IXsRhn8DDAxgruyESBpIm78XlBw+6uyGtnfMxsSYZMAtPTam4Y
|
||||
nPhcOMgwh5ow2mcouOKaedqfpTsfUWI7IvF+U3waC8PwTdxJRPKI
|
||||
iM46W7md6bK3W1KnxtVYiXK32MyzqBgdUJc/Hdpqrji/e3kxvmO5
|
||||
94WFF+ltisTiGJQv129SpZmx3USbB3CSiCZ32w==
|
||||
</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
<key>certData</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>14</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
|
||||
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
|
||||
DUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5
|
||||
MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUg
|
||||
SW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmE
|
||||
Les2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRr
|
||||
EdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b
|
||||
8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6ws
|
||||
IG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
|
||||
C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CW
|
||||
QYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMU
|
||||
ZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
|
||||
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4w
|
||||
ggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggr
|
||||
BgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2Ev
|
||||
MIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2Vy
|
||||
dGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
|
||||
ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z
|
||||
IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9s
|
||||
aWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVu
|
||||
dHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J2
|
||||
0ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNii
|
||||
Pvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3
|
||||
iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
|
||||
1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP
|
||||
3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5
|
||||
MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJ
|
||||
BzewdXUh
|
||||
</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSMutableArray</string>
|
||||
<string>NSArray</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSMutableArray</string>
|
||||
</dict>
|
||||
<string>root</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485894498.53763503</real>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSDate</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSDate</string>
|
||||
</dict>
|
||||
<integer>6</integer>
|
||||
<integer>11196</integer>
|
||||
<integer>10760</integer>
|
||||
<string>bash</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<string>foouser</string>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSArray</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSArray</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>27</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>28</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<string>foouser@console</string>
|
||||
<string>foouser@ttys000</string>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>SNTStoredEvent</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>SNTStoredEvent</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>29</integer>
|
||||
</dict>
|
||||
<key>currentSessions</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>38</integer>
|
||||
</dict>
|
||||
<key>decision</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>35</integer>
|
||||
</dict>
|
||||
<key>executingUser</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
<key>filePath</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>33</integer>
|
||||
</dict>
|
||||
<key>fileSHA256</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>32</integer>
|
||||
</dict>
|
||||
<key>idx</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>31</integer>
|
||||
</dict>
|
||||
<key>loggedInUsers</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>37</integer>
|
||||
</dict>
|
||||
<key>occurrenceDate</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>34</integer>
|
||||
</dict>
|
||||
<key>parentName</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>22</integer>
|
||||
</dict>
|
||||
<key>pid</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>36</integer>
|
||||
</dict>
|
||||
<key>ppid</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>21</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<integer>14888</integer>
|
||||
<string>2b3a38f4e4e981afc517b0c7903ffe2f79193c67e2329dd77cf2e74b046ca3a7</string>
|
||||
<string>/usr/local/Cellar/hub/2.2.3/bin/hub</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485894568.92822498</real>
|
||||
</dict>
|
||||
<integer>1</integer>
|
||||
<integer>11427</integer>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>27</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>28</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
</array>
|
||||
<key>$top</key>
|
||||
<dict>
|
||||
<key>root</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>$version</key>
|
||||
<integer>100000</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -0,0 +1,454 @@
|
||||
<?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>$archiver</key>
|
||||
<string>NSKeyedArchiver</string>
|
||||
<key>$objects</key>
|
||||
<array>
|
||||
<string>$null</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>28</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>2</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>36</integer>
|
||||
</dict>
|
||||
<key>currentSessions</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>29</integer>
|
||||
</dict>
|
||||
<key>decision</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
<key>executingUser</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>21</integer>
|
||||
</dict>
|
||||
<key>fileBundleID</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>8</integer>
|
||||
</dict>
|
||||
<key>fileBundleName</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>6</integer>
|
||||
</dict>
|
||||
<key>fileBundlePath</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>7</integer>
|
||||
</dict>
|
||||
<key>fileBundleVersion</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>9</integer>
|
||||
</dict>
|
||||
<key>fileBundleVersionString</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>10</integer>
|
||||
</dict>
|
||||
<key>filePath</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>5</integer>
|
||||
</dict>
|
||||
<key>fileSHA256</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>4</integer>
|
||||
</dict>
|
||||
<key>idx</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>3</integer>
|
||||
</dict>
|
||||
<key>loggedInUsers</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>27</integer>
|
||||
</dict>
|
||||
<key>occurrenceDate</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>22</integer>
|
||||
</dict>
|
||||
<key>parentName</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>26</integer>
|
||||
</dict>
|
||||
<key>pid</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>25</integer>
|
||||
</dict>
|
||||
<key>ppid</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>24</integer>
|
||||
</dict>
|
||||
<key>quarantineAgentBundleID</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>35</integer>
|
||||
</dict>
|
||||
<key>quarantineDataURL</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>32</integer>
|
||||
</dict>
|
||||
<key>quarantineRefererURL</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>33</integer>
|
||||
</dict>
|
||||
<key>quarantineTimestamp</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>34</integer>
|
||||
</dict>
|
||||
<key>signingChain</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>11</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<integer>14896</integer>
|
||||
<string>9dce073ce17dc86cf08182bf1b2c9adff5a5bd7a9b133a963b611daa2dc0f68c</string>
|
||||
<string>/Applications/Paw.app/Contents/MacOS/Paw</string>
|
||||
<string>Paw</string>
|
||||
<string>/Applications/Paw.app</string>
|
||||
<string>com.luckymarmot.Paw</string>
|
||||
<string>2003004001</string>
|
||||
<string>2.3.4</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>20</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>12</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>16</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>18</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>15</integer>
|
||||
</dict>
|
||||
<key>certData</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>13</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>14</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIFaTCCBFGgAwIBAgIIbgRYjgjmV00wDQYJKoZIhvcNAQELBQAw
|
||||
eTEtMCsGA1UEAwwkRGV2ZWxvcGVyIElEIENlcnRpZmljYXRpb24g
|
||||
QXV0aG9yaXR5MSYwJAYDVQQLDB1BcHBsZSBDZXJ0aWZpY2F0aW9u
|
||||
IEF1dGhvcml0eTETMBEGA1UECgwKQXBwbGUgSW5jLjELMAkGA1UE
|
||||
BhMCVVMwHhcNMTIxMjA1MDUzMDIwWhcNMTcxMjA2MDUzMDIwWjCB
|
||||
ijEaMBgGCgmSJomT8ixkAQEMCjg0NTk5Ukw1OEExMTAvBgNVBAMM
|
||||
KERldmVsb3BlciBJRCBBcHBsaWNhdGlvbjogTWljaGEgTWF6YWhl
|
||||
cmkxEzARBgNVBAsMCjg0NTk5Ukw1OEExFzAVBgNVBAoMDk1pY2hh
|
||||
IE1hemFoZXJpMQswCQYDVQQGEwJGUjCCASIwDQYJKoZIhvcNAQEB
|
||||
BQADggEPADCCAQoCggEBAODR8cTAZZCZb/8+/fgVfM3Mu3dr0ga0
|
||||
ry+CoHEZLuWiiHEPbcalOp13DyC1x9bYY341dr2TJcrVFhIRg/bi
|
||||
3arx9U3AQl60W4Ybo223jwdnt5FPK+WuXg2kL1OmE8Aqd9sKWcX0
|
||||
QlLVuVF4HjiKukD44qRMyP8vAxLFPIqPFizw1m1qNSaMa66JaKos
|
||||
hDJOT2WgpLEdPTsxw8U1NMXLVnh+V8Ax+BAjMsIPOE6V7P36TUyt
|
||||
L2J/YgcIdiYo/kr05ATmfQEEk/E680gFAXX2z5L8sxZbKMwPyhJZ
|
||||
antw7u9ygHH/XnL850gcxXr7ggOKjgcJGhlBRpb+j9AIXj4nHl0C
|
||||
AwEAAaOCAeEwggHdMD4GCCsGAQUFBwEBBDIwMDAuBggrBgEFBQcw
|
||||
AYYiaHR0cDovL29jc3AuYXBwbGUuY29tL29jc3AtZGV2aWQwMTAd
|
||||
BgNVHQ4EFgQU4EU6eGBHWGVUVOnfZpSf28AGQfkwDAYDVR0TAQH/
|
||||
BAIwADAfBgNVHSMEGDAWgBRXF+2iz9x8mKEQ4Py+hy0s8uMXVDCC
|
||||
AQ4GA1UdIASCAQUwggEBMIH+BgkqhkiG92NkBQEwgfAwKAYIKwYB
|
||||
BQUHAgEWHGh0dHA6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2EwgcMG
|
||||
CCsGAQUFBwICMIG2DIGzUmVsaWFuY2Ugb24gdGhpcyBjZXJ0aWZp
|
||||
Y2F0ZSBieSBhbnkgcGFydHkgYXNzdW1lcyBhY2NlcHRhbmNlIG9m
|
||||
IHRoZSB0aGVuIGFwcGxpY2FibGUgc3RhbmRhcmQgdGVybXMgYW5k
|
||||
IGNvbmRpdGlvbnMgb2YgdXNlLCBjZXJ0aWZpY2F0ZSBwb2xpY3kg
|
||||
YW5kIGNlcnRpZmljYXRpb24gcHJhY3RpY2Ugc3RhdGVtZW50cy4w
|
||||
DgYDVR0PAQH/BAQDAgeAMBYGA1UdJQEB/wQMMAoGCCsGAQUFBwMD
|
||||
MBMGCiqGSIb3Y2QGAQ0BAf8EAgUAMA0GCSqGSIb3DQEBCwUAA4IB
|
||||
AQAzGxszYVEE5wCR4OnFvk8Ea1A4/uyy8jjQ/loMJXIbeJ436IqX
|
||||
uc+6eCVIbkM+X4c5SEogq0td7NCkupgJUAuxEY2LCD/N3E5sDHaW
|
||||
09qBEV+hVBSF1+mwigLSLexaGgH9lQBNabbwjgkloFpFCdDDRtyT
|
||||
lUhC0Mo1jcjrFH9jfH76yn66SgY4oSnU1r/TiJFdt0a1e6Jbhc4U
|
||||
L9r/mhJFYYjl811DZQUjBrdqnGq9KP6mU6B8MHzay9jUb0vgbFqQ
|
||||
cxoWNKr7fW5U/oHZKAh1LtechJFairDpIsXr9euIOq4UHHjqQ1R7
|
||||
zFBVaYHXWcyNjk9UCQq2Ejjw6NkAr4AA
|
||||
</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSMutableData</string>
|
||||
<string>NSData</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSMutableData</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>MOLCertificate</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>MOLCertificate</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>15</integer>
|
||||
</dict>
|
||||
<key>certData</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>17</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>14</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEBDCCAuygAwIBAgIIGHqpqMKWIQwwDQYJKoZIhvcNAQELBQAw
|
||||
YjELMAkGA1UEBhMCVVMxEzARBgNVBAoTCkFwcGxlIEluYy4xJjAk
|
||||
BgNVBAsTHUFwcGxlIENlcnRpZmljYXRpb24gQXV0aG9yaXR5MRYw
|
||||
FAYDVQQDEw1BcHBsZSBSb290IENBMB4XDTEyMDIwMTIyMTIxNVoX
|
||||
DTI3MDIwMTIyMTIxNVoweTEtMCsGA1UEAwwkRGV2ZWxvcGVyIElE
|
||||
IENlcnRpZmljYXRpb24gQXV0aG9yaXR5MSYwJAYDVQQLDB1BcHBs
|
||||
ZSBDZXJ0aWZpY2F0aW9uIEF1dGhvcml0eTETMBEGA1UECgwKQXBw
|
||||
bGUgSW5jLjELMAkGA1UEBhMCVVMwggEiMA0GCSqGSIb3DQEBAQUA
|
||||
A4IBDwAwggEKAoIBAQCJdk8GW5pB7qUjKwKjX9dzP8A1sIuECj8G
|
||||
JH+nlT/rTw6Tr7QO0Mg+5W0Ysx/oiUe/1wkI5P9WmCkV55SduTWj
|
||||
Cs20wOHiYPTK7Cl4RWlpYGtfipL8niPmOsIiszFPHLrytjRZQu6w
|
||||
qQIDGJEEtrN4LjMfgEUNRW+7Dlpbfzrn2AjXCw4ybfuGNuRsq8QR
|
||||
inCEJqqfRNHxuMZ7lBebSPcLWBa6I8WfFTl+yl3DMl8P4FJ/QOq+
|
||||
rAhklVvJGpzlgMofakQcbD7EsCYfHex7r16gaj1HqVgSMT8gdiht
|
||||
HRywwk4RaSaLy9bQEYLJTg/xVnTQ2QhLZniiq6yn4tJMh1nJAgMB
|
||||
AAGjgaYwgaMwHQYDVR0OBBYEFFcX7aLP3HyYoRDg/L6HLSzy4xdU
|
||||
MA8GA1UdEwEB/wQFMAMBAf8wHwYDVR0jBBgwFoAUK9BpR5R2Cf70
|
||||
a40uQKb3R01/CF4wLgYDVR0fBCcwJTAjoCGgH4YdaHR0cDovL2Ny
|
||||
bC5hcHBsZS5jb20vcm9vdC5jcmwwDgYDVR0PAQH/BAQDAgGGMBAG
|
||||
CiqGSIb3Y2QGAgYEAgUAMA0GCSqGSIb3DQEBCwUAA4IBAQBCOXRr
|
||||
odzGpI83KoyzHQpEvJUsf7xZuKxh+weQkjK51L87wVA5akR0ouxb
|
||||
H3Dlqt1LbBwjcS1f0cWTvu6binBlgp0W4xoQF4ktqM39DHhYSQwo
|
||||
fzPuAHobtHastrW7T9+oG53IGZdKC1ZnL8I+trPEgzrwd210xC4j
|
||||
Ue6apQNvYPSlSKcGwrta4h8fRkV+5Jf1JxC3ICJyb3LaxlB1xT0l
|
||||
j12jAOmfNoxIOY+zO+qQgC6VmmD0eM70DgpTPqL6T9geroSVjTK8
|
||||
Vk2J6XgY4KyaQrp6RhuEoonOFOiI0ViL9q5WxCwFKkWvC9lLqQIP
|
||||
NKyIx2FViUTJJ3MH7oLlTvVw
|
||||
</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>15</integer>
|
||||
</dict>
|
||||
<key>certData</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>19</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>14</integer>
|
||||
</dict>
|
||||
<key>NS.data</key>
|
||||
<data>
|
||||
MIIEuzCCA6OgAwIBAgIBAjANBgkqhkiG9w0BAQUFADBiMQswCQYD
|
||||
VQQGEwJVUzETMBEGA1UEChMKQXBwbGUgSW5jLjEmMCQGA1UECxMd
|
||||
QXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRob3JpdHkxFjAUBgNVBAMT
|
||||
DUFwcGxlIFJvb3QgQ0EwHhcNMDYwNDI1MjE0MDM2WhcNMzUwMjA5
|
||||
MjE0MDM2WjBiMQswCQYDVQQGEwJVUzETMBEGA1UEChMKQXBwbGUg
|
||||
SW5jLjEmMCQGA1UECxMdQXBwbGUgQ2VydGlmaWNhdGlvbiBBdXRo
|
||||
b3JpdHkxFjAUBgNVBAMTDUFwcGxlIFJvb3QgQ0EwggEiMA0GCSqG
|
||||
SIb3DQEBAQUAA4IBDwAwggEKAoIBAQDkkakJH5HbHkdQ6wXtXnmE
|
||||
Les2oldMVeyLGYne+Uts9QerIjAC6Bg++FAJ039BqJj50cpmnCRr
|
||||
EdCju+QbKsMflZ56DKRHi1vUFjczy8QPTc4UadHJGXL1XQ7Vf1+b
|
||||
8iUDulWPTV0N8WQ1IxVLFVkds5T39pyez1C6wVhQZ48ItCD3y6ws
|
||||
IG9wtj8BMIy3Q88PnT3zK0koGsj+zrW5DtleHNbLPbU6rfQPDgCS
|
||||
C7EhFi501TwN22IWq6NxkkdTVcGvL0Gz+PvjcM3mo0xFfh9Ma1CW
|
||||
QYnEdGILEINBhzOKgbEwWOxaBDKMaLOPHd5lc/9nXmW8Sdh2nzMU
|
||||
ZaF3lMktAgMBAAGjggF6MIIBdjAOBgNVHQ8BAf8EBAMCAQYwDwYD
|
||||
VR0TAQH/BAUwAwEB/zAdBgNVHQ4EFgQUK9BpR5R2Cf70a40uQKb3
|
||||
R01/CF4wHwYDVR0jBBgwFoAUK9BpR5R2Cf70a40uQKb3R01/CF4w
|
||||
ggERBgNVHSAEggEIMIIBBDCCAQAGCSqGSIb3Y2QFATCB8jAqBggr
|
||||
BgEFBQcCARYeaHR0cHM6Ly93d3cuYXBwbGUuY29tL2FwcGxlY2Ev
|
||||
MIHDBggrBgEFBQcCAjCBthqBs1JlbGlhbmNlIG9uIHRoaXMgY2Vy
|
||||
dGlmaWNhdGUgYnkgYW55IHBhcnR5IGFzc3VtZXMgYWNjZXB0YW5j
|
||||
ZSBvZiB0aGUgdGhlbiBhcHBsaWNhYmxlIHN0YW5kYXJkIHRlcm1z
|
||||
IGFuZCBjb25kaXRpb25zIG9mIHVzZSwgY2VydGlmaWNhdGUgcG9s
|
||||
aWN5IGFuZCBjZXJ0aWZpY2F0aW9uIHByYWN0aWNlIHN0YXRlbWVu
|
||||
dHMuMA0GCSqGSIb3DQEBBQUAA4IBAQBcNplMLXi37Yyb3PN3m/J2
|
||||
0ncwT8EfhYOFG5k9RzfyqZtAjizUsZAS2L70c5vu0mQPy3lPNNii
|
||||
Pvl4/2vIB+x9OYOLUyDTOMSxv5pPCmv/K/xZpwUJfBdAVhEedNO3
|
||||
iyM7R6PVbyTi69G3cN8PReEnyvFteO3ntRcXqNx+IjXKJdXZD9Zr
|
||||
1KIkIxH3oayPc4FgxhtbCS+SsvhESPBgOJ4V9T0mZyCKM2r3DYLP
|
||||
3uujL/lTaltkwGMzd/c6ByxW69oPIQ7aunMZT7XZNn/Bh1XZp5m5
|
||||
MkL72NVxnn6hUrcbvZNCJBIqxw8dtk2cXmPIS4AXUKqK1drk/NAJ
|
||||
BzewdXUh
|
||||
</data>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSMutableArray</string>
|
||||
<string>NSArray</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSMutableArray</string>
|
||||
</dict>
|
||||
<string>rah</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>23</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485897711.079467</real>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSDate</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSDate</string>
|
||||
</dict>
|
||||
<integer>1</integer>
|
||||
<integer>16331</integer>
|
||||
<string>launchd</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>28</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>21</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>NSArray</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>NSArray</string>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>28</integer>
|
||||
</dict>
|
||||
<key>NS.objects</key>
|
||||
<array>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>30</integer>
|
||||
</dict>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>31</integer>
|
||||
</dict>
|
||||
</array>
|
||||
</dict>
|
||||
<string>rah@ttys000</string>
|
||||
<string>rah@console</string>
|
||||
<string>https://d3hevc2w7wq7nj.cloudfront.net/paw/Paw-2.3.4-2003004001.zip</string>
|
||||
<string>https://luckymarmot.com/paw</string>
|
||||
<dict>
|
||||
<key>$class</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>23</integer>
|
||||
</dict>
|
||||
<key>NS.time</key>
|
||||
<real>485897668</real>
|
||||
</dict>
|
||||
<string>com.google.Chrome</string>
|
||||
<dict>
|
||||
<key>$classes</key>
|
||||
<array>
|
||||
<string>SNTStoredEvent</string>
|
||||
<string>NSObject</string>
|
||||
</array>
|
||||
<key>$classname</key>
|
||||
<string>SNTStoredEvent</string>
|
||||
</dict>
|
||||
</array>
|
||||
<key>$top</key>
|
||||
<dict>
|
||||
<key>root</key>
|
||||
<dict>
|
||||
<key>CF$UID</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</dict>
|
||||
<key>$version</key>
|
||||
<integer>100000</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
1
Tests/LogicTests/Resources/sync_preflight_basic.json
Normal file
1
Tests/LogicTests/Resources/sync_preflight_basic.json
Normal file
@@ -0,0 +1 @@
|
||||
{"whitelist_regex": null, "client_mode": "MONITOR", "blacklist_regex": null, "batch_size": 100}
|
||||
1
Tests/LogicTests/Resources/sync_preflight_lockdown.json
Normal file
1
Tests/LogicTests/Resources/sync_preflight_lockdown.json
Normal file
@@ -0,0 +1 @@
|
||||
{"whitelist_regex": null, "client_mode": "LOCKDOWN", "blacklist_regex": null, "batch_size": 100}
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user