Compare commits
346 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 | ||
|
|
cf1d1e3557 | ||
|
|
8f05ee7d79 | ||
|
|
641bd07c0b | ||
|
|
7d9dc0a853 | ||
|
|
e0a46be1b7 | ||
|
|
fd82c67b56 | ||
|
|
f0a83b6f19 | ||
|
|
736b45bb46 | ||
|
|
8eae9b7cb7 | ||
|
|
0aa2d2c613 | ||
|
|
ad43db10f2 | ||
|
|
606f507422 | ||
|
|
36b7778883 | ||
|
|
7b032a6a73 | ||
|
|
0e00237e44 | ||
|
|
e9ec9a7d7f | ||
|
|
6834507f3a | ||
|
|
90e99255b1 | ||
|
|
b6487000a3 | ||
|
|
18ce2f72ed | ||
|
|
8a2d04bf69 | ||
|
|
a210ffecec | ||
|
|
aff96e8144 | ||
|
|
3d4c639bb4 | ||
|
|
d507e79505 | ||
|
|
d3e242ff42 | ||
|
|
df7616403d | ||
|
|
962b15517a | ||
|
|
d295f2391f | ||
|
|
c042222eea | ||
|
|
63f6596bc2 | ||
|
|
d8a8aba0ea | ||
|
|
d9d9682029 | ||
|
|
4a27a8ac70 | ||
|
|
32857ff304 | ||
|
|
375bfd3862 | ||
|
|
9430c41b8a | ||
|
|
9b342e146a | ||
|
|
e5685f2959 | ||
|
|
4150feece2 | ||
|
|
6879ec5deb | ||
|
|
28ad00ffad | ||
|
|
bf51049fbf | ||
|
|
36189e9122 | ||
|
|
4c747463ac | ||
|
|
b4b1fbb9e6 | ||
|
|
209eaff3c6 | ||
|
|
c3f70703fd | ||
|
|
f2967e7b94 | ||
|
|
77c46b5c43 | ||
|
|
5fda5bc081 | ||
|
|
33a7b38c6a | ||
|
|
2a7c0bd58c | ||
|
|
86e4d0db0f | ||
|
|
1310fea64d | ||
|
|
382f5a5bb9 | ||
|
|
ff3303e312 | ||
|
|
6ce0ef62e9 | ||
|
|
2a03341fb6 | ||
|
|
77a55dde56 | ||
|
|
1a71cdff4a | ||
|
|
63f65c51c3 | ||
|
|
75de2526c1 | ||
|
|
6fc4b7b120 | ||
|
|
7b8068139b | ||
|
|
ced7de884f | ||
|
|
bc51c9f25b | ||
|
|
c412e8b9a7 | ||
|
|
4e0ff224b6 | ||
|
|
61c817c9cb | ||
|
|
2ed384f677 | ||
|
|
7a851cb080 | ||
|
|
13aa889633 | ||
|
|
5c3fba5f41 | ||
|
|
145d9216bf | ||
|
|
84f46de940 | ||
|
|
cb9a5b6fbe | ||
|
|
d9718faba4 | ||
|
|
5472ff41f0 | ||
|
|
4f94c3b310 | ||
|
|
420f1efa50 | ||
|
|
5d2ce17817 | ||
|
|
053cb823a1 | ||
|
|
18a7992372 | ||
|
|
9e935f5bfb | ||
|
|
9f49e24dc5 | ||
|
|
dbf60f16bc | ||
|
|
0f3a228788 | ||
|
|
d905f5b095 | ||
|
|
1c310486c7 | ||
|
|
4b01c6da91 | ||
|
|
5782378616 | ||
|
|
64c97ebfba | ||
|
|
5fd4d56b00 | ||
|
|
e658b5167e | ||
|
|
cea698d720 | ||
|
|
c07f41c312 | ||
|
|
a837aa0334 | ||
|
|
0050724e22 | ||
|
|
adac4ac75c | ||
|
|
718f37024a | ||
|
|
fcb3008539 | ||
|
|
8faf3eec53 | ||
|
|
2bc3df3255 | ||
|
|
5b0e550c85 | ||
|
|
e52211abf2 | ||
|
|
9b6f231b34 | ||
|
|
b71223705f | ||
|
|
863fbe69bb | ||
|
|
2d46279961 | ||
|
|
0d0207d77f | ||
|
|
00bbade34f | ||
|
|
682f741ddc | ||
|
|
3d2744c9e3 | ||
|
|
cc286dcf16 | ||
|
|
27c6e2a7bd | ||
|
|
72c7a67ad5 | ||
|
|
8fe5e4e238 | ||
|
|
02f23d0c62 | ||
|
|
ff6f4d4152 | ||
|
|
2242f46792 | ||
|
|
642b5609b2 | ||
|
|
98878f3e7c | ||
|
|
3eb28deccf | ||
|
|
761a852156 | ||
|
|
f4ddb11c1f | ||
|
|
75158c11ea | ||
|
|
fe96706b0c | ||
|
|
b87482e824 | ||
|
|
a9ba99dc79 | ||
|
|
8884e92a1a | ||
|
|
6385514257 | ||
|
|
d3ad47022b | ||
|
|
138d4b507d | ||
|
|
3c0b195bcf | ||
|
|
d941a71bb5 | ||
|
|
08697d9daf | ||
|
|
8959871988 | ||
|
|
bb43a04992 | ||
|
|
5f93dc7991 | ||
|
|
9be8eb223c | ||
|
|
e8b6c47e0f | ||
|
|
697d442afb | ||
|
|
5dbd261b5a | ||
|
|
9bc94ca658 | ||
|
|
4404b5f849 | ||
|
|
6a4b73b8a9 | ||
|
|
b6146224b3 | ||
|
|
e3593c1b0c | ||
|
|
90a2f10da6 | ||
|
|
60bab1c004 | ||
|
|
0898940d0b | ||
|
|
38b65b0ca4 | ||
|
|
d36ce5eefc | ||
|
|
ff99ab9cfe | ||
|
|
64995367c3 | ||
|
|
c67f0ffc11 | ||
|
|
d5403ae112 | ||
|
|
d21d64cbfe | ||
|
|
347ee3c4f5 | ||
|
|
77ed1cca29 | ||
|
|
cfac7dbb37 | ||
|
|
f27d72f3f9 | ||
|
|
3cd93b287e | ||
|
|
5e5605881b | ||
|
|
a9b48610df | ||
|
|
3cca09a48c | ||
|
|
3134448eac | ||
|
|
663bdf945b | ||
|
|
e94d1175e7 | ||
|
|
e20b761965 | ||
|
|
90c64812d0 | ||
|
|
08d368fc49 | ||
|
|
39385f0bff | ||
|
|
8bc3418ce1 | ||
|
|
a145700398 | ||
|
|
409535e617 | ||
|
|
f625016efe | ||
|
|
f4c94ab1d7 | ||
|
|
8234706dd3 | ||
|
|
1a31dc870f | ||
|
|
a1712858c5 | ||
|
|
0059e768b9 | ||
|
|
4fe1550bd2 | ||
|
|
0c182c8a7f | ||
|
|
bcdf746def | ||
|
|
bc13ac3a98 | ||
|
|
a894e018cd | ||
|
|
cbecfd444d | ||
|
|
357e5ef963 | ||
|
|
60594c9f03 | ||
|
|
44b5bae8da | ||
|
|
2e856196c5 | ||
|
|
8672187c02 | ||
|
|
cf251c45b8 | ||
|
|
385c03096d | ||
|
|
f323f5e3de | ||
|
|
9562ee86cd | ||
|
|
adfb4bc861 | ||
|
|
957232ca40 | ||
|
|
44c9d9aead | ||
|
|
f95245cedd | ||
|
|
3c034adf48 | ||
|
|
abd3c5a06d | ||
|
|
ca4951a475 | ||
|
|
e751a3d307 | ||
|
|
2a8bdfd714 | ||
|
|
be9dca3ee2 | ||
|
|
32707fb501 | ||
|
|
d72547e187 | ||
|
|
9150ddffb1 | ||
|
|
d5c1d66c2f | ||
|
|
536b8969ed | ||
|
|
0db3b6d955 | ||
|
|
78bb9a1bd6 | ||
|
|
567e0b6431 | ||
|
|
f2f27c5675 | ||
|
|
5a7ac2287b | ||
|
|
f82da21b75 | ||
|
|
969a5ef94e | ||
|
|
fd7ad07193 | ||
|
|
3f5400b264 | ||
|
|
466b5ed491 | ||
|
|
25f1b71f10 | ||
|
|
d1295f97b9 | ||
|
|
f5eb274aa0 | ||
|
|
58b9dab74f | ||
|
|
9f6b6d10dc | ||
|
|
57f6e516c2 | ||
|
|
8cd9898cf3 | ||
|
|
d53b04213a | ||
|
|
ac99bd1070 | ||
|
|
30df44df96 | ||
|
|
fc55b86f30 | ||
|
|
59ffb67554 | ||
|
|
d46b156b85 | ||
|
|
6492e70599 | ||
|
|
bc5d0f8685 | ||
|
|
838da16da1 | ||
|
|
6e242bf98d | ||
|
|
be1e66c29d | ||
|
|
57866308e3 | ||
|
|
63bc8fca2d | ||
|
|
408712f00f | ||
|
|
8cb6046f94 | ||
|
|
297fb4cb68 | ||
|
|
1501d413f0 | ||
|
|
e747ace0f3 | ||
|
|
6b96f36b2b | ||
|
|
f16fa691b5 | ||
|
|
4fd5e1139f | ||
|
|
0b33079833 | ||
|
|
6069ed5801 | ||
|
|
c2a9061ea2 | ||
|
|
ee963d62a4 | ||
|
|
c12adbc8e6 | ||
|
|
e6b20bcce6 | ||
|
|
10333bba01 | ||
|
|
437764e6fc | ||
|
|
460dd6aa8b | ||
|
|
0a511468e3 | ||
|
|
96517573e7 | ||
|
|
c996921c22 | ||
|
|
8365e00a50 | ||
|
|
a629e6cff1 | ||
|
|
cbb786c6d1 |
22
.clang-format
Normal file
@@ -0,0 +1,22 @@
|
||||
BasedOnStyle: Google
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
# Disable ColumnLimit because it causes some very weird line breaks.
|
||||
# For ObjC the limit is 100
|
||||
# For Cpp the limit is 80
|
||||
ColumnLimit: 0
|
||||
|
||||
# Allow short case statements to be on a single line
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
|
||||
# Ban short loops and functions on a single line
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: false
|
||||
|
||||
# Allow spaces in NSArray/NSDictionary literals @[ and @{
|
||||
SpacesInContainerLiterals: true
|
||||
|
||||
# For pointers, always put the * next to the variable name.
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
11
.travis.yml
@@ -1,10 +1,13 @@
|
||||
---
|
||||
language: objective-c
|
||||
cache: cocoapods
|
||||
sudo: false
|
||||
osx_image: xcode7
|
||||
|
||||
before_install:
|
||||
- gem install cocoapods
|
||||
- brew update
|
||||
- brew upgrade xctool
|
||||
- gem install activesupport
|
||||
- gem install cocoapods xcpretty
|
||||
- pod setup >/dev/null
|
||||
|
||||
script:
|
||||
- xctool -workspace Santa.xcworkspace -scheme All build test CODE_SIGN_IDENTITY=''
|
||||
- xcodebuild -workspace Santa.xcworkspace -scheme All -derivedDataPath build build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}
|
||||
|
||||
84
Conf/Package/Makefile
Normal file
@@ -0,0 +1,84 @@
|
||||
#
|
||||
# Package Makefile for Santa
|
||||
# Requires TheLuggage (github.com/unixorn/luggage) to be installed
|
||||
#
|
||||
# Will generate a package based on the latest release. You can replace
|
||||
# the PACKAGE_VERSION variable with a specific variable instead if you wish.
|
||||
#
|
||||
|
||||
LUGGAGE:=/usr/local/share/luggage/luggage.make
|
||||
include ${LUGGAGE}
|
||||
|
||||
TITLE:=santa
|
||||
REVERSE_DOMAIN:=com.google
|
||||
|
||||
# Get latest Release version using the GitHub API. Each release is bound to a
|
||||
# git tag, which should always be a semantic version number. The most recent
|
||||
# release is always first in the API result.
|
||||
PACKAGE_VERSION:=$(shell curl -fs https://api.github.com/repos/google/santa/releases |\
|
||||
python -c 'import json, sys; print json.load(sys.stdin)[0]["tag_name"]' 2>/dev/null)
|
||||
|
||||
# Get the download URL for the latest Release. Each release should have a
|
||||
# tarball named santa-$version.tar.bz2 containing all of the files associated
|
||||
# with that release. The tarball layout is:
|
||||
#
|
||||
# santa-$version.tar.bz2
|
||||
# +--santa-$version
|
||||
# |-- binaries
|
||||
# | |-- santa-driver.kext
|
||||
# | |-- Santa.app
|
||||
# |-- conf
|
||||
# | |-- install.sh
|
||||
# | |-- com.google.santad.plist
|
||||
# | |-- com.google.santagui.plist
|
||||
# | +-- com.google.santa.asl.conf
|
||||
# +--dsym
|
||||
# |-- santa-driver.kext.dSYM
|
||||
# |-- Santa.app.dSYM
|
||||
# |-- santad.dSYM
|
||||
# +-- santactl.dSYM
|
||||
PACKAGE_DOWNLOAD_URL:="https://github.com/google/santa/releases/download/${PACKAGE_VERSION}/santa-${PACKAGE_VERSION}.tar.bz2"
|
||||
|
||||
PAYLOAD:=pack-Library-Extensions-santa-driver.kext \
|
||||
pack-applications-Santa.app \
|
||||
pack-Library-LaunchDaemons-com.google.santad.plist \
|
||||
pack-Library-LaunchAgents-com.google.santagui.plist \
|
||||
pack-etc-asl-com.google.santa.asl.conf \
|
||||
pack-script-preinstall \
|
||||
pack-script-postinstall
|
||||
|
||||
santa-driver.kext: download
|
||||
Santa.app: download
|
||||
com.google.santad.plist: download
|
||||
com.google.santagui.plist: download
|
||||
com.google.santa.asl.conf: download
|
||||
|
||||
download:
|
||||
$(if $(PACKAGE_VERSION),, $(error GitHub API returned unexpected result. Wait a while and try again))
|
||||
|
||||
@curl -fL ${PACKAGE_DOWNLOAD_URL} | tar xvj --strip=2
|
||||
@rm -rf *.dSYM
|
||||
|
||||
pack-etc-asl-com.google.santa.asl.conf: com.google.santa.asl.conf l_private_etc
|
||||
@sudo mkdir -p ${WORK_D}/private/etc/asl
|
||||
@sudo chown root:wheel ${WORK_D}/private/etc/asl
|
||||
@sudo chmod 755 ${WORK_D}/private/etc/asl
|
||||
@sudo install -m 644 -o root -g wheel com.google.santa.asl.conf ${WORK_D}/private/etc/asl
|
||||
|
||||
pack-Library-Extensions-santa-driver.kext: santa-driver.kext l_Library
|
||||
@sudo mkdir -p ${WORK_D}/Library/Extensions
|
||||
@sudo ${DITTO} --noqtn santa-driver.kext ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
@sudo chown -R root:wheel ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
@sudo chmod -R 755 ${WORK_D}/Library/Extensions/santa-driver.kext
|
||||
|
||||
clean: myclean
|
||||
|
||||
myclean:
|
||||
@rm -rf *.dSYM
|
||||
@rm -rf Santa.app
|
||||
@rm -rf santa-driver.kext
|
||||
@rm -f config.plist
|
||||
@rm -f com.google.santa.asl.conf
|
||||
@rm -f com.google.santad.plist
|
||||
@rm -f com.google.santagui.plist
|
||||
@rm -f install.sh
|
||||
27
Conf/Package/postinstall
Normal file
@@ -0,0 +1,27 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Load the kernel extension, santad, sync client
|
||||
# If a user is logged in, also load the GUI agent.
|
||||
# If the target volume is not /, do nothing
|
||||
|
||||
[[ $3 != "/" ]] && exit 0
|
||||
|
||||
# Restart syslogd to pick up ASL configuration change
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
/sbin/kextload /Library/Extensions/santa-driver.kext
|
||||
|
||||
sleep 1
|
||||
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
|
||||
|
||||
sleep 1
|
||||
|
||||
# Create hopefully useful symlink for santactl
|
||||
/bin/ln -sf /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "$user" ]] && exit 0
|
||||
/bin/launchctl asuser ${user} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
|
||||
|
||||
exit 0
|
||||
26
Conf/Package/preinstall
Normal file
@@ -0,0 +1,26 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Unload the kernel extension, santad, sync client
|
||||
# If a user is logged in, also unload the GUI agent.
|
||||
# If the target volume is not /, do nothing
|
||||
|
||||
[[ $3 != "/" ]] && exit 0
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
|
||||
sleep 1
|
||||
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Remove cruft from old Santa versions
|
||||
/bin/rm /usr/libexec/santad
|
||||
/bin/rm /usr/sbin/santactl
|
||||
/bin/launchctl remove com.google.santasync
|
||||
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist
|
||||
|
||||
sleep 1
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
|
||||
exit 0
|
||||
@@ -1,4 +1,6 @@
|
||||
# Copy this file to /etc/asl to log all messages from santa-driver to the log file
|
||||
? [S= Message santa-driver:] claim only
|
||||
? [S= Message santa-driver:] file /var/log/santa.log format="[$((Time)(utc.3))] $Message"
|
||||
> /var/log/santa.log mode=0644 rotate=seq compress file_max=5M all_max=100M
|
||||
> /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
|
||||
? [= Facility com.google.santa] claim
|
||||
? [= Facility com.google.santa] file /var/log/santa.log
|
||||
|
||||
@@ -6,24 +6,21 @@
|
||||
<string>com.google.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/libexec/santad</string>
|
||||
<string>/Library/Extensions/santa-driver.kext/Contents/MacOS/santad</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>SantaXPCNotifications</key>
|
||||
<true/>
|
||||
<key>SantaXPCControl</key>
|
||||
<true/>
|
||||
<key>SantaXPCControl</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>StandardOutPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true />
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>1</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -7,8 +7,11 @@
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/Santa</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
|
||||
@@ -1,19 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santasync</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/sbin/santactl</string>
|
||||
<string>sync</string>
|
||||
</array>
|
||||
<key>StandardErrorPath</key>
|
||||
<string>/var/log/santa.log</string>
|
||||
<key>ProcessType</key>
|
||||
<string>Background</string>
|
||||
<key>StartInterval</key>
|
||||
<integer>600</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
58
Conf/install.sh
Executable file
@@ -0,0 +1,58 @@
|
||||
#!/bin/bash
|
||||
|
||||
if [[ $EUID -ne 0 ]]; then
|
||||
echo "This script must be run as root" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
if [[ -d "binaries" ]]; then
|
||||
SOURCE="."
|
||||
elif [[ -d "../binaries" ]]; then
|
||||
SOURCE=".."
|
||||
else
|
||||
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
# Unload santad and scheduled sync job.
|
||||
/bin/launchctl remove com.google.santad >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Unload GUI agent if someone is logged in.
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove /Library/LaunchAgents/com.google.santagui.plist
|
||||
|
||||
# Cleanup cruft from old versions
|
||||
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
|
||||
/bin/rm /Library/LaunchDaemons/com.google.santasync.plist >/dev/null 2>&1
|
||||
/bin/rm /usr/libexec/santad >/dev/null 2>&1
|
||||
/bin/rm /usr/sbin/santactl >/dev/null 2>&1
|
||||
|
||||
# Copy new files.
|
||||
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
|
||||
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
|
||||
/bin/ln -s /Library/Extensions/santa-driver.kext/Contents/MacOS/santactl /usr/local/bin
|
||||
|
||||
/bin/cp ${SOURCE}/conf/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
|
||||
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
|
||||
|
||||
# Reload syslogd to pick up ASL configuration change.
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
# Load kext.
|
||||
/sbin/kextload /Library/Extensions/santa-driver.kext
|
||||
|
||||
# Load santad and scheduled sync jobs.
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
|
||||
# Load GUI agent if someone is logged in.
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl load /Library/LaunchAgents/com.google.santagui.plist
|
||||
|
||||
exit 0
|
||||
36
Podfile
@@ -2,15 +2,22 @@ platform :osx, "10.9"
|
||||
|
||||
inhibit_all_warnings!
|
||||
|
||||
target :santad do
|
||||
def mol_pods
|
||||
pod 'MOLCertificate'
|
||||
pod 'MOLCodesignChecker'
|
||||
end
|
||||
|
||||
def fmdb_pod
|
||||
pod 'FMDB'
|
||||
|
||||
post_install do |rep|
|
||||
rep.project.targets.each do |target|
|
||||
# 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|
|
||||
if config.name != 'Release' then
|
||||
break
|
||||
end
|
||||
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] ||= ''
|
||||
config.build_settings['GCC_PREPROCESSOR_DEFINITIONS'] <<= "NDEBUG=1"
|
||||
end
|
||||
@@ -18,7 +25,24 @@ target :santad do
|
||||
end
|
||||
end
|
||||
|
||||
target :LogicTests do
|
||||
pod 'OCMock'
|
||||
pod 'FMDB'
|
||||
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
|
||||
mol_pods
|
||||
fmdb_pod
|
||||
pod 'MOLAuthenticatingURLSession'
|
||||
pod 'OCMock'
|
||||
end
|
||||
|
||||
29
Podfile.lock
@@ -1,17 +1,28 @@
|
||||
PODS:
|
||||
- FMDB (2.5):
|
||||
- FMDB/standard (= 2.5)
|
||||
- FMDB/common (2.5)
|
||||
- FMDB/standard (2.5):
|
||||
- FMDB/common
|
||||
- OCMock (3.1.2)
|
||||
- FMDB (2.6.2):
|
||||
- FMDB/standard (= 2.6.2)
|
||||
- FMDB/standard (2.6.2)
|
||||
- MOLAuthenticatingURLSession (1.6):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- MOLCertificate (1.4)
|
||||
- MOLCodesignChecker (1.5):
|
||||
- MOLCertificate (~> 1.3)
|
||||
- OCMock (3.3)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
- MOLAuthenticatingURLSession
|
||||
- MOLCertificate
|
||||
- MOLCodesignChecker
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
|
||||
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
|
||||
FMDB: 854a0341b4726e53276f2a8996f06f1b80f9259a
|
||||
MOLAuthenticatingURLSession: f956240458fb24b61e5607d735948dc9babfb4e3
|
||||
MOLCertificate: da0bfeb5fa968bb4ac284569fa3f7d5f8f7abe23
|
||||
MOLCodesignChecker: fc9c64147811d7b0d0739127003e0630dff9213a
|
||||
OCMock: d68685bde31f69cb61d518dcb39269080c78b5ed
|
||||
|
||||
COCOAPODS: 0.36.1
|
||||
PODFILE CHECKSUM: 3a8673334ffd78cdbd6576c85e6635248eb1b504
|
||||
|
||||
COCOAPODS: 1.0.0
|
||||
|
||||
60
README.md
@@ -1,7 +1,13 @@
|
||||
Santa [](https://travis-ci.org/google/santa)
|
||||
=====
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for Mac OS X. It consists of
|
||||
<p align="center">
|
||||
<a href="#santa--">
|
||||
<img src="./Source/SantaGUI/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</a>
|
||||
</p>
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for OS X. It consists of
|
||||
a kernel extension that monitors for executions, a userland daemon that makes
|
||||
execution decisions based on the contents of a SQLite database, a GUI agent that
|
||||
notifies the user in case of a block decision and a command-line utility for
|
||||
@@ -46,11 +52,35 @@ server.
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
|
||||
Intentions and Expectations
|
||||
===========================
|
||||
No single system or process will stop *all* attacks, or provide 100% security.
|
||||
Santa is written with the intention of helping protect users from themselves.
|
||||
People often download malware and trust it, giving the malware credentials, or
|
||||
allowing unknown software to exfiltrate more data about your system. As a
|
||||
centrally managed component, Santa can help stop the spread of malware among a
|
||||
larger fleet of machines. Additionally, Santa can aid in analyzing what is
|
||||
running in your fleet.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to protect
|
||||
hosts in whatever other ways you see fit.
|
||||
|
||||
Get Help
|
||||
========
|
||||
|
||||
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.
|
||||
|
||||
Known Issues
|
||||
============
|
||||
|
||||
Santa is not yet a 1.0 and we have some known issues to be aware of:
|
||||
|
||||
* Santa only blocks execution (execve and variants), it doesn't protect against
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been replaced or
|
||||
libraries loaded using `DYLD_INSERT_LIBRARIES`. We are working on also protecting
|
||||
against these avenues of attack.
|
||||
|
||||
* Kext communication security: the kext will only accept a connection from a
|
||||
single client at a time and said client must be running as root. We haven't yet
|
||||
found a good way to ensure the kext only accepts connections from a valid client.
|
||||
@@ -78,9 +108,17 @@ 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
|
||||
git clone https://github.com/google/santa
|
||||
cd santa
|
||||
@@ -98,12 +136,10 @@ and for security-reasons parts of Santa will not operate properly if not signed.
|
||||
|
||||
Kext Signing
|
||||
============
|
||||
|
||||
10.9 requires a special Developer ID certificate to sign kernel extensions and
|
||||
if the kext is not signed with one of these special certificates a warning will
|
||||
be shown when loading the kext for the first time. In 10.10 this is a hard error
|
||||
and the kext will not load at all unless the machine is booted with a debug
|
||||
boot-arg.
|
||||
Kernel extensions on OS X 10.9 and later must be signed using an Apple-provided
|
||||
Developer ID certificate with a kernel extension flag. Without it, the only way
|
||||
to load an extension is to enable kext-dev-mode or disable SIP, depending on the
|
||||
OS version.
|
||||
|
||||
There are two possible solutions for this, for distribution purposes:
|
||||
|
||||
@@ -118,17 +154,11 @@ and distribute a new version of the pre-signed kext.
|
||||
Apple will only grant this for broad distribution within an organization, they
|
||||
won't issue them just for testing purposes.
|
||||
|
||||
If you just want to locally test changes to the kext code, you should enable
|
||||
kext-dev mode, instructions for which can be found on the Apple developer site.
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
|
||||
Patches to this project are very much welcome. Please see the [CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
|
||||
file.
|
||||
|
||||
Disclaimer
|
||||
==========
|
||||
|
||||
This is **not** an official Google product.
|
||||
|
||||
120
Rakefile
@@ -1,47 +1,39 @@
|
||||
require 'timeout'
|
||||
|
||||
WORKSPACE = 'Santa.xcworkspace'
|
||||
DEFAULT_SCHEME = 'All'
|
||||
OUTPUT_PATH = 'Build'
|
||||
DIST_PATH = 'Dist'
|
||||
BINARIES = ['Santa.app', 'santa-driver.kext', 'santad', 'santactl']
|
||||
XCTOOL_DEFAULTS = "-workspace #{WORKSPACE}"
|
||||
XCODE_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
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'
|
||||
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
$DISABLE_XCPRETTY = false
|
||||
|
||||
task :default do
|
||||
system("rake -sT")
|
||||
end
|
||||
|
||||
def xctool_available
|
||||
return system 'xctool --version >/dev/null 2>&1'
|
||||
end
|
||||
def xcodebuild(opts)
|
||||
command = "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts}"
|
||||
if not $DISABLE_XCPRETTY
|
||||
command << " | xcpretty #{XCPRETTY_DEFAULTS} && exit ${PIPESTATUS[0]}"
|
||||
end
|
||||
|
||||
def run_and_output_on_fail(cmd)
|
||||
output=`#{cmd} 2>&1`
|
||||
if not $?.success?
|
||||
raise output
|
||||
if system command
|
||||
puts "\e[32mPass\e[0m"
|
||||
else
|
||||
raise "\e[31mFail\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
def run_and_output_with_color(cmd)
|
||||
output=`#{cmd} 2>&1`
|
||||
|
||||
has_output = false
|
||||
output.scan(/((Test Suite|Test Case|Executed).*)$/) do |match|
|
||||
has_output = true
|
||||
out = match[0]
|
||||
if out.include?("passed")
|
||||
puts "\e[32m#{out}\e[0m"
|
||||
elsif out.include?("failed")
|
||||
puts "\e[31m#{out}\e[0m"
|
||||
else
|
||||
puts out
|
||||
def xcodebuilddir
|
||||
if not $xcode_build_dir
|
||||
output = `xcodebuild #{XCODEBUILD_DEFAULTS} -scheme All -showBuildSettings`
|
||||
if match = output.match(/BUILD_DIR = (.*)/)
|
||||
$xcode_build_dir = match.captures.first
|
||||
puts "Found Xcode build dir #{$xcode_build_dir}"
|
||||
end
|
||||
end
|
||||
|
||||
if not has_output
|
||||
raise output
|
||||
end
|
||||
$xcode_build_dir
|
||||
end
|
||||
|
||||
task :init do
|
||||
@@ -49,19 +41,20 @@ task :init do
|
||||
puts "Pods missing, running 'pod install'"
|
||||
system "pod install" or raise "CocoaPods is not installed. Install with 'sudo gem install cocoapods'"
|
||||
end
|
||||
unless system 'xcpretty -v >/dev/null 2>&1'
|
||||
puts "xcpretty is not installed. Install with 'sudo gem install xcpretty'"
|
||||
$DISABLE_XCPRETTY = true
|
||||
end
|
||||
end
|
||||
|
||||
task :remove_existing do
|
||||
system 'sudo rm -rf /santa-driver.kext'
|
||||
system 'sudo rm -rf /Library/Extensions/santa-driver.kext'
|
||||
system 'sudo rm -rf /Applications/Santa.app'
|
||||
system 'sudo rm /usr/libexec/santad'
|
||||
system 'sudo rm /usr/sbin/santactl'
|
||||
end
|
||||
|
||||
desc "Clean"
|
||||
task :clean => :init do
|
||||
puts "Cleaning"
|
||||
system "xcodebuild #{XCODE_DEFAULTS} -scheme All clean"
|
||||
FileUtils.rm_rf(OUTPUT_PATH)
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
end
|
||||
@@ -81,11 +74,7 @@ namespace :build do
|
||||
task :build, [:configuration] => :init do |t, args|
|
||||
config = args[:configuration]
|
||||
puts "Building with configuration: #{config}"
|
||||
if xctool_available
|
||||
system "xctool #{XCTOOL_DEFAULTS} -scheme All -configuration #{config} build"
|
||||
else
|
||||
system "xcodebuild #{XCODE_DEFAULTS} -scheme All -configuration #{config} build"
|
||||
end
|
||||
xcodebuild("-scheme All -configuration #{config} build")
|
||||
end
|
||||
end
|
||||
|
||||
@@ -105,16 +94,13 @@ namespace :install do
|
||||
task :install, [:configuration] do |t, args|
|
||||
config = args[:configuration]
|
||||
system 'sudo cp conf/com.google.santad.plist /Library/LaunchDaemons'
|
||||
system 'sudo cp conf/com.google.santasync.plist /Library/LaunchDaemons'
|
||||
system 'sudo cp conf/com.google.santagui.plist /Library/LaunchAgents'
|
||||
system 'sudo cp conf/com.google.santa.asl.conf /etc/asl'
|
||||
Rake::Task['build:build'].invoke(config)
|
||||
puts "Installing with configuration: #{config}"
|
||||
Rake::Task['remove_existing'].invoke()
|
||||
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/santa-driver.kext /"
|
||||
system "sudo cp -r #{OUTPUT_PATH}/Products/#{config}/Santa.app /Applications"
|
||||
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santad /usr/libexec"
|
||||
system "sudo cp #{OUTPUT_PATH}/Products/#{config}/santactl /usr/sbin"
|
||||
system "sudo cp -r #{xcodebuilddir}/#{config}/santa-driver.kext /Library/Extensions"
|
||||
system "sudo cp -r #{xcodebuilddir}/#{config}/Santa.app /Applications"
|
||||
end
|
||||
end
|
||||
|
||||
@@ -122,6 +108,7 @@ end
|
||||
task :dist do
|
||||
desc "Create distribution folder"
|
||||
|
||||
Rake::Task['clean'].invoke()
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
@@ -131,11 +118,15 @@ task :dist do
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/dsym")
|
||||
|
||||
BINARIES.each do |x|
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}", "#{DIST_PATH}/binaries")
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}.dSYM", "#{DIST_PATH}/dsym")
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{DIST_PATH}/binaries")
|
||||
end
|
||||
|
||||
Dir.glob("Conf/*") {|x| FileUtils.cp(x, "#{DIST_PATH}/conf")}
|
||||
DSYMS.each do |x|
|
||||
FileUtils.cp_r("#{xcodebuilddir}/Release/#{x}", "#{DIST_PATH}/dsym")
|
||||
end
|
||||
|
||||
|
||||
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{DIST_PATH}/conf")}
|
||||
|
||||
puts "Distribution folder created"
|
||||
end
|
||||
@@ -145,11 +136,7 @@ namespace :tests do
|
||||
desc "Tests: Logic"
|
||||
task :logic => [:init] do
|
||||
puts "Running logic tests"
|
||||
if xctool_available
|
||||
system "xctool #{XCTOOL_DEFAULTS} -scheme LogicTests test"
|
||||
else
|
||||
system "xcodebuild #{XCODE_DEFAULTS} -scheme LogicTests test"
|
||||
end
|
||||
xcodebuild("-scheme LogicTests test")
|
||||
end
|
||||
|
||||
desc "Tests: Kernel"
|
||||
@@ -157,16 +144,17 @@ namespace :tests do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:debug'].invoke()
|
||||
Rake::Task['load_kext'].invoke
|
||||
timeout = 30
|
||||
puts "Running kernel tests with a #{timeout} second timeout"
|
||||
FileUtils.mkdir_p("/tmp/santa_kerneltests_tmp")
|
||||
begin
|
||||
Timeout::timeout(timeout) {
|
||||
system "sudo #{OUTPUT_PATH}/Products/Debug/KernelTests"
|
||||
}
|
||||
rescue Timeout::Error
|
||||
puts "ERROR: tests ran for longer than #{timeout} seconds and were killed."
|
||||
puts "\033[?25l\033[12h" # hide cursor
|
||||
puts "Running kernel tests"
|
||||
system "cd /tmp/santa_kerneltests_tmp && sudo #{xcodebuilddir}/Debug/KernelTests"
|
||||
rescue Exception
|
||||
ensure
|
||||
puts "\033[?25h\033[12l\n\n" # unhide cursor
|
||||
FileUtils.rm_rf("/tmp/santa_kerneltests_tmp")
|
||||
Rake::Task['unload_kext'].execute
|
||||
end
|
||||
Rake::Task['unload_kext'].execute
|
||||
end
|
||||
end
|
||||
|
||||
@@ -183,7 +171,7 @@ end
|
||||
|
||||
task :unload_gui do
|
||||
puts "Unloading GUI agent"
|
||||
system "sudo killall Santa 2>/dev/null"
|
||||
system "launchctl unload /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Unload"
|
||||
@@ -196,12 +184,12 @@ end
|
||||
|
||||
task :load_kext do
|
||||
puts "Loading kernel extension"
|
||||
system "sudo kextload /santa-driver.kext"
|
||||
system "sudo kextload /Library/Extensions/santa-driver.kext"
|
||||
end
|
||||
|
||||
task :load_gui do
|
||||
puts "Loading GUI agent"
|
||||
system "open /Applications/Santa.app"
|
||||
system "launchctl load /Library/LaunchAgents/com.google.santagui.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Load"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0510"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,10 +23,11 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
codeCoverageEnabled = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -39,24 +40,36 @@
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
|
||||
BuildableName = "All"
|
||||
BlueprintName = "All"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0600"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,10 +23,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
@@ -38,17 +38,21 @@
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
@@ -61,12 +65,13 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,10 +23,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
@@ -48,15 +48,18 @@
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
@@ -71,10 +74,10 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0510"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,10 +23,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
@@ -38,17 +38,21 @@
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
@@ -61,12 +65,13 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0510"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,30 +23,42 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
|
||||
BuildableName = "santa-driver.kext"
|
||||
BlueprintName = "santa-driver"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0510"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,10 +23,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
@@ -38,17 +38,21 @@
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
@@ -61,12 +65,13 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
|
||||
@@ -1,6 +1,6 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0510"
|
||||
LastUpgradeVersion = "0720"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
@@ -23,10 +23,10 @@
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
shouldUseLaunchSchemeArgsEnv = "YES">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
@@ -38,18 +38,22 @@
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
buildConfiguration = "Debug"
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
debugAsWhichUser = "root"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
debugServiceExtension = "internal"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
@@ -62,12 +66,13 @@
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
buildConfiguration = "Release"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable>
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
|
||||
@@ -1,5 +1,5 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14C1514" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14E46" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
|
||||
@@ -14,7 +14,7 @@
|
||||
<customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
|
||||
<customObject id="-3" userLabel="Application" customClass="NSObject"/>
|
||||
<window title="Santa" allowsToolTipsWhenApplicationIsInactive="NO" autorecalculatesKeyViewLoop="NO" oneShot="NO" releasedWhenClosed="NO" visibleAtLaunch="NO" animationBehavior="default" id="F0z-JX-Cv5">
|
||||
<windowStyleMask key="styleMask" titled="YES"/>
|
||||
<windowStyleMask key="styleMask" titled="YES" closable="YES"/>
|
||||
<rect key="contentRect" x="196" y="240" width="480" height="200"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<view key="contentView" id="se5-gp-TjO">
|
||||
@@ -37,7 +37,7 @@
|
||||
<rect key="frame" x="18" y="65" width="444" height="60"/>
|
||||
<textFieldCell key="cell" sendsActionOnEndEditing="YES" alignment="center" id="CcT-ul-1eA">
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="title">Santa is an application whitelisting system for Mac OS X.
|
||||
<string key="title">Santa is an application whitelisting system for OS X.
|
||||
|
||||
There are no user-configurable settings.</string>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
@@ -65,6 +65,9 @@ There are no user-configurable settings.</string>
|
||||
<buttonCell key="cell" type="push" title="Dismiss" bezelStyle="rounded" alignment="center" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="uSw-o1-lWW">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="orderOut:" target="-1" id="6oW-zI-zn5"/>
|
||||
|
||||
@@ -31,25 +31,25 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "128x128",
|
||||
"idiom" : "mac",
|
||||
"filename" : "santa-hat-icon-256.png",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"idiom" : "mac",
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "santa-hat-icon-256.png",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"size" : "256x256",
|
||||
"idiom" : "mac",
|
||||
"filename" : "santa-hat-icon-512.png",
|
||||
"size" : "256x256",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"size" : "512x512",
|
||||
"idiom" : "mac",
|
||||
"filename" : "santa-hat-icon-512.png",
|
||||
"size" : "512x512",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
@@ -62,4 +62,4 @@
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
Before Width: | Height: | Size: 9.1 KiB After Width: | Height: | Size: 17 KiB |
|
Before Width: | Height: | Size: 3.8 KiB After Width: | Height: | Size: 1.2 KiB |
|
After Width: | Height: | Size: 40 KiB |
|
Before Width: | Height: | Size: 4.6 KiB After Width: | Height: | Size: 2.8 KiB |
|
Before Width: | Height: | Size: 36 KiB |
|
Before Width: | Height: | Size: 5.3 KiB After Width: | Height: | Size: 6.6 KiB |
6
Source/SantaGUI/Resources/Images.xcassets/Contents.json
Normal file
@@ -0,0 +1,6 @@
|
||||
{
|
||||
"info" : {
|
||||
"version" : 1,
|
||||
"author" : "xcode"
|
||||
}
|
||||
}
|
||||
@@ -1,85 +1,180 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14C1514" 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="6254"/>
|
||||
<development version="6300" identifier="xcode"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="9532"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
|
||||
<connections>
|
||||
<outlet property="applicationNameLabel" destination="qgf-Jf-cJr" id="1JX-X8-03v"/>
|
||||
<outlet property="openEventButton" destination="7ua-5a-uSd" id="9s4-ZA-Vlo"/>
|
||||
<outlet property="window" destination="9Bq-yh-54f" id="Uhs-WF-TV9"/>
|
||||
</connections>
|
||||
</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="550" height="331"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<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="550" height="331"/>
|
||||
<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="234" y="261" width="83" height="40"/>
|
||||
<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="25" y="214" width="500" 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="496" id="XgJ-EV-tBa"/>
|
||||
<constraint firstAttribute="width" constant="450" id="XgJ-EV-tBa"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" 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="175" y="167" width="324" 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="320" id="xVR-j3-dLw"/>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
<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="PXc-xv-A28">
|
||||
<rect key="frame" x="175" y="117" width="304" 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="300" id="4hh-R2-86s"/>
|
||||
<constraint firstAttribute="width" constant="290" id="Pav-ZA-iAu"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="File SHA-256" id="X4W-9e-eIu">
|
||||
<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"/>
|
||||
<textFieldCell key="cell" selectable="YES" sendsActionOnEndEditing="YES" alignment="left" title="Application Name" id="3UG-ca-d1k">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<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="SzX-Ep-rBa"/>
|
||||
<binding destination="-2" name="value" keyPath="self.event.fileBundleName" id="enC-Cl-UWt">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w">
|
||||
<rect key="frame" x="175" y="142" width="309" 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="305" id="Dem-wH-KHm"/>
|
||||
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<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.filePath.lastPathComponent" id="bOu-gv-1Vh"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<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>
|
||||
<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">
|
||||
@@ -89,70 +184,18 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
|
||||
<rect key="frame" x="18" y="92" 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"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
|
||||
<rect key="frame" x="18" y="142" 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"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H">
|
||||
<rect key="frame" x="18" y="167" width="120" height="17"/>
|
||||
<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="116" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<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="18" y="117" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="SHA-256" 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="175" y="92" width="368" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="364" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" 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="xe2-U2-WrZ"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<box horizontalHuggingPriority="750" title="Box" boxType="separator" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
|
||||
<rect key="frame" x="154" y="92" width="5" height="92"/>
|
||||
<color key="borderColor" white="0.0" alpha="0.41999999999999998" 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="51" y="143" width="14" height="14"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="14" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="14" id="YwG-0s-jop"/>
|
||||
<constraint firstAttribute="width" constant="15" id="QTm-Iv-m5p"/>
|
||||
<constraint firstAttribute="height" constant="15" id="YwG-0s-jop"/>
|
||||
</constraints>
|
||||
<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"/>
|
||||
@@ -163,30 +206,103 @@
|
||||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
|
||||
<rect key="frame" x="282" 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>
|
||||
<buttonCell key="cell" type="roundTextured" title="Dismiss" bezelStyle="texturedRounded" alignment="center" refusesFirstResponder="YES" 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"/>
|
||||
<binding destination="-2" name="value" keyPath="self.event.fileSHA256" id="9KB-0b-qLV"/>
|
||||
</connections>
|
||||
</button>
|
||||
</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>
|
||||
<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" 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">
|
||||
<dictionary key="options">
|
||||
<string key="NSDisplayPattern">%{value1}@ (%{value2}@)</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
<binding destination="-2" name="displayPatternValue2" keyPath="self.event.ppid" previousBinding="Lce-TO-q9V" id="ofI-kH-F2d">
|
||||
<dictionary key="options">
|
||||
<string key="NSDisplayPattern">%{value1}@ (%{value2}@)</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<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="value" keyPath="self.event.executingUser" id="IcM-Lt-xTT">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="158" y="33" width="112" height="25"/>
|
||||
<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" refusesFirstResponder="YES" state="on" borderStyle="border" imageScaling="proportionallyDown" inset="2" id="X1b-TF-1TL">
|
||||
<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">
|
||||
@@ -196,50 +312,119 @@ DQ
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<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="h6f-PY-cc0" firstAttribute="bottom" secondItem="4Li-ul-zIi" secondAttribute="bottom" id="1Nc-gl-xMe"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
|
||||
<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="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="oFj-ol-xpL" firstAttribute="leading" secondItem="Iwq-Lx-rLv" secondAttribute="leading" constant="20" id="IwX-ja-ZIs"/>
|
||||
<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" constant="30" id="Nsl-zf-poH"/>
|
||||
<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="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="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
|
||||
<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 firstAttribute="centerX" secondItem="BbV-3h-mmL" secondAttribute="centerX" priority="900" id="acs-5J-vQY"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
|
||||
<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="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="4Li-ul-zIi" firstAttribute="top" secondItem="pc8-G9-4pJ" secondAttribute="top" id="fzY-94-y2n"/>
|
||||
<constraint firstAttribute="centerX" secondItem="t8c-Fx-e5h" secondAttribute="centerX" constant="-0.5" id="h3d-Kc-q88"/>
|
||||
<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="oFj-ol-xpL" firstAttribute="trailing" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" id="lse-kg-lA2"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="qgf-Jf-cJr" secondAttribute="bottom" constant="8" id="lWU-tC-vWg"/>
|
||||
<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="PXc-xv-A28" firstAttribute="bottom" secondItem="h6f-PY-cc0" secondAttribute="top" constant="-8" id="sG1-gQ-Qoo"/>
|
||||
<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="d9e-Wv-Y5H" firstAttribute="baseline" secondItem="pc8-G9-4pJ" secondAttribute="baseline" id="xGd-Xr-3Z0"/>
|
||||
<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>
|
||||
</view>
|
||||
<point key="canvasLocation" x="113" y="777.5"/>
|
||||
<connections>
|
||||
<outlet property="initialFirstResponder" destination="kiB-jZ-69S" id="I96-dS-lq5"/>
|
||||
</connections>
|
||||
<point key="canvasLocation" x="302.5" y="304.5"/>
|
||||
</window>
|
||||
</objects>
|
||||
<resources>
|
||||
|
||||
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
@@ -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,7 +18,9 @@
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileWatcher.h"
|
||||
#import "SNTNotificationManager.h"
|
||||
#import "SNTStrengthify.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTAppDelegate ()
|
||||
@property SNTAboutWindowController *aboutWindowController;
|
||||
@@ -35,27 +37,34 @@
|
||||
[self setupMenu];
|
||||
|
||||
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
|
||||
handler:^{
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
handler:^(unsigned long data) {
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
}];
|
||||
|
||||
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
|
||||
self.notificationManager = [[SNTNotificationManager alloc] init];
|
||||
|
||||
NSNotificationCenter *workspaceNotifications = [[NSWorkspace sharedWorkspace] notificationCenter];
|
||||
[workspaceNotifications addObserver:self
|
||||
selector:@selector(killConnection)
|
||||
name:NSWorkspaceSessionDidResignActiveNotification
|
||||
object:nil];
|
||||
[workspaceNotifications addObserver:self
|
||||
selector:@selector(createConnection)
|
||||
name:NSWorkspaceSessionDidBecomeActiveNotification
|
||||
object:nil];
|
||||
|
||||
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidResignActiveNotification
|
||||
object:nil
|
||||
queue:[NSOperationQueue currentQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
self.listener.invalidationHandler = nil;
|
||||
[self.listener invalidate];
|
||||
self.listener = nil;
|
||||
}];
|
||||
[workspaceNotifications addObserverForName:NSWorkspaceSessionDidBecomeActiveNotification
|
||||
object:nil
|
||||
queue:[NSOperationQueue currentQueue]
|
||||
usingBlock:^(NSNotification *note) {
|
||||
[self attemptReconnection];
|
||||
}];
|
||||
|
||||
[self createConnection];
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
|
||||
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
|
||||
[self.aboutWindowController showWindow:self];
|
||||
return NO;
|
||||
}
|
||||
@@ -63,32 +72,37 @@
|
||||
#pragma mark Connection handling
|
||||
|
||||
- (void)createConnection {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
self.listener = [[SNTXPCConnection alloc] initClientWithName:[SNTXPCNotifierInterface serviceId]
|
||||
options:NSXPCConnectionPrivileged];
|
||||
WEAKIFY(self);
|
||||
|
||||
// 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.rejectedHandler = ^{
|
||||
[weakSelf performSelectorInBackground:@selector(attemptReconnection)
|
||||
withObject:nil];
|
||||
self.listener.acceptedHandler = ^{
|
||||
dispatch_semaphore_signal(sema);
|
||||
};
|
||||
self.listener.invalidationHandler = ^{
|
||||
STRONGIFY(self);
|
||||
[self attemptReconnection];
|
||||
};
|
||||
self.listener.invalidationHandler = self.listener.rejectedHandler;
|
||||
[self.listener resume];
|
||||
}
|
||||
|
||||
- (void)killConnection {
|
||||
self.listener.invalidationHandler = nil;
|
||||
[self.listener invalidate];
|
||||
self.listener = nil;
|
||||
// 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(10);
|
||||
[self performSelectorOnMainThread:@selector(createConnection)
|
||||
withObject:nil
|
||||
waitUntilDone:NO];
|
||||
[self performSelectorInBackground:@selector(createConnection) withObject:nil];
|
||||
}
|
||||
|
||||
#pragma mark Menu Management
|
||||
|
||||
@@ -26,11 +26,12 @@
|
||||
|
||||
- (IBAction)fadeIn:(id)sender {
|
||||
[self setAlphaValue:0.f];
|
||||
[self center];
|
||||
[self makeKeyAndOrderFront:sender];
|
||||
[NSAnimationContext beginGrouping];
|
||||
[[NSAnimationContext currentContext] setDuration:0.15f];
|
||||
[[NSAnimationContext currentContext] setCompletionHandler:^{
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
[NSApp activateIgnoringOtherApps:YES];
|
||||
}];
|
||||
[[self animator] setAlphaValue:1.f];
|
||||
[NSAnimationContext endGrouping];
|
||||
@@ -42,9 +43,9 @@
|
||||
[NSAnimationContext beginGrouping];
|
||||
[[NSAnimationContext currentContext] setDuration:0.15f];
|
||||
[[NSAnimationContext currentContext] setCompletionHandler:^{
|
||||
[weakSelf.windowController windowWillClose:nil];
|
||||
[weakSelf orderOut:nil];
|
||||
[weakSelf setAlphaValue:1.f];
|
||||
[weakSelf.windowController windowWillClose:sender];
|
||||
[weakSelf orderOut:sender];
|
||||
[weakSelf setAlphaValue:1.f];
|
||||
}];
|
||||
[[self animator] setAlphaValue:0.f];
|
||||
[NSAnimationContext endGrouping];
|
||||
|
||||
@@ -15,7 +15,7 @@
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@protocol SNTMessageWindowControllerDelegate
|
||||
- (void)windowDidClose;
|
||||
- (void)windowDidCloseSilenceHash:(NSString *)hash;
|
||||
@end
|
||||
|
||||
///
|
||||
@@ -29,35 +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 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) NSString *publisherInfo;
|
||||
|
||||
///
|
||||
/// An optional message to display with this block.
|
||||
///
|
||||
@property(readonly) 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;
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,19 +16,45 @@
|
||||
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "SNTCertificate.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;
|
||||
}
|
||||
@@ -37,7 +63,6 @@
|
||||
[super loadWindow];
|
||||
[self.window setLevel:NSPopUpMenuWindowLevel];
|
||||
[self.window setMovableByWindowBackground:YES];
|
||||
[self.window center];
|
||||
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
@@ -47,6 +72,10 @@
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
}
|
||||
}
|
||||
|
||||
if (!self.event.fileBundleName) {
|
||||
[self.applicationNameLabel removeFromSuperview];
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showWindow:(id)sender {
|
||||
@@ -58,13 +87,19 @@
|
||||
}
|
||||
|
||||
- (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 {
|
||||
// SFCertificatePanel expects an NSArray of SecCertificateRef's
|
||||
NSMutableArray *certArray = [NSMutableArray arrayWithCapacity:[self.event.signingChain count]];
|
||||
for (SNTCertificate *cert in self.event.signingChain) {
|
||||
for (MOLCertificate *cert in self.event.signingChain) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
@@ -77,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
|
||||
@@ -102,7 +128,7 @@
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
SNTCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
MOLCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
@@ -116,30 +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 && ![self.customMessage isEqual:@""]) {
|
||||
message = self.customMessage;
|
||||
} else {
|
||||
message = @"The following application has been blocked from executing<br />"
|
||||
@"because its trustworthiness cannot be determined.";
|
||||
}
|
||||
|
||||
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
|
||||
|
||||
@@ -20,5 +20,4 @@
|
||||
///
|
||||
@interface SNTNotificationManager : NSObject<SNTMessageWindowControllerDelegate, SNTNotifierXPC>
|
||||
|
||||
|
||||
@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,47 +53,86 @@
|
||||
}
|
||||
}
|
||||
|
||||
#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.
|
||||
NSPredicate *predicate = [NSPredicate predicateWithFormat:@"event.fileSHA256==%@",
|
||||
event.fileSHA256];
|
||||
NSPredicate *predicate =
|
||||
[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
@@ -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
@@ -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
|
||||
@@ -1,111 +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.
|
||||
|
||||
///
|
||||
/// SNTCertificate wraps a @c SecCertificateRef to provide Objective-C accessors to
|
||||
/// commonly used certificate data. Accessors cache data for repeated access.
|
||||
///
|
||||
@interface SNTCertificate : NSObject<NSSecureCoding>
|
||||
|
||||
///
|
||||
/// Initialize a SNTCertificate object with a valid SecCertificateRef. Designated initializer.
|
||||
///
|
||||
/// @param certRef valid SecCertificateRef, which will be retained.
|
||||
///
|
||||
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef;
|
||||
|
||||
///
|
||||
/// Initialize a SNTCertificate object with certificate data in DER format.
|
||||
///
|
||||
/// @param certData DER-encoded certificate data.
|
||||
/// @return initialized SNTCertificate or nil if certData is not a DER-encoded certificate.
|
||||
///
|
||||
- (instancetype)initWithCertificateDataDER:(NSData *)certData;
|
||||
|
||||
///
|
||||
/// Initialize a SNTCertificate object with certificate data in PEM format.
|
||||
/// If multiple PEM certificates exist within the string, the first is used.
|
||||
///
|
||||
/// @param certData PEM-encoded certificate data.
|
||||
/// @return initialized SNTCertifcate or nil if certData is not a PEM-encoded certificate.
|
||||
///
|
||||
- (instancetype)initWithCertificateDataPEM:(NSString *)certData;
|
||||
|
||||
///
|
||||
/// Returns an array of SNTCertificate's for all of the certificates in @c pemData.
|
||||
///
|
||||
/// @param pemData PEM-encoded certificates.
|
||||
/// @return array of SNTCertificate objects.
|
||||
///
|
||||
+ (NSArray *)certificatesFromPEM:(NSString *)pemData;
|
||||
|
||||
///
|
||||
/// Access the underlying certificate ref.
|
||||
///
|
||||
@property(readonly) SecCertificateRef certRef;
|
||||
|
||||
///
|
||||
/// SHA-1 hash of the certificate data.
|
||||
///
|
||||
@property(readonly) NSString *SHA1;
|
||||
|
||||
///
|
||||
/// SHA-256 hash of the certificate data.
|
||||
///
|
||||
@property(readonly) NSString *SHA256;
|
||||
|
||||
///
|
||||
/// Certificate data.
|
||||
///
|
||||
@property(readonly) NSData *certData;
|
||||
|
||||
///
|
||||
/// Common Name e.g: "Software Signing"
|
||||
///
|
||||
@property(readonly) NSString *commonName;
|
||||
|
||||
///
|
||||
/// Country Name e.g: "US"
|
||||
///
|
||||
@property(readonly) NSString *countryName;
|
||||
|
||||
///
|
||||
/// Organizational Name e.g: "Apple Inc."
|
||||
///
|
||||
@property(readonly) NSString *orgName;
|
||||
|
||||
///
|
||||
/// Organizational Unit Name e.g: "Apple Software"
|
||||
///
|
||||
@property(readonly) NSString *orgUnit;
|
||||
|
||||
///
|
||||
/// Issuer details, same fields as above.
|
||||
///
|
||||
@property(readonly) NSString *issuerCommonName;
|
||||
@property(readonly) NSString *issuerCountryName;
|
||||
@property(readonly) NSString *issuerOrgName;
|
||||
@property(readonly) NSString *issuerOrgUnit;
|
||||
|
||||
///
|
||||
/// Validity Not Before
|
||||
///
|
||||
@property(readonly) NSDate *validFrom;
|
||||
|
||||
///
|
||||
/// Validity Not After
|
||||
///
|
||||
@property(readonly) NSDate *validUntil;
|
||||
|
||||
@end
|
||||
@@ -1,362 +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 "SNTCertificate.h"
|
||||
|
||||
#import <CommonCrypto/CommonDigest.h>
|
||||
#import <Security/Security.h>
|
||||
|
||||
@interface SNTCertificate ()
|
||||
/// A container for cached property values
|
||||
@property NSMutableDictionary *memoizedData;
|
||||
@end
|
||||
|
||||
@implementation SNTCertificate
|
||||
|
||||
static NSString *const kCertDataKey = @"certData";
|
||||
|
||||
#pragma mark Init/Dealloc
|
||||
|
||||
- (instancetype)initWithSecCertificateRef:(SecCertificateRef)certRef {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_certRef = certRef;
|
||||
CFRetain(_certRef);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCertificateDataDER:(NSData *)certData {
|
||||
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
|
||||
|
||||
if (cert) {
|
||||
// Despite the header file claiming that SecCertificateCreateWithData will return NULL if
|
||||
// @c certData doesn't contain a valid DER-encoded X509 cert, this isn't always true.
|
||||
// radar://problem/16124651
|
||||
// To workaround, check that the certificate serial number can be retrieved. According to
|
||||
// RFC5280, the serial number field is required.
|
||||
NSData *ser = CFBridgingRelease(SecCertificateCopySerialNumber(cert, NULL));
|
||||
if (ser) {
|
||||
self = [self initWithSecCertificateRef:cert];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
CFRelease(cert); // was retained in initWithSecCertificateRef
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithCertificateDataPEM:(NSString *)certData {
|
||||
// Find the PEM and extract the base64-encoded DER data from within
|
||||
NSScanner *scanner = [NSScanner scannerWithString:certData];
|
||||
NSString *base64der;
|
||||
|
||||
// Locate and parse DER data into |base64der|
|
||||
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
|
||||
if (!([scanner scanString:@"-----BEGIN CERTIFICATE-----" intoString:NULL] &&
|
||||
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&base64der] &&
|
||||
[scanner scanString:@"-----END CERTIFICATE-----" intoString:NULL])) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// base64-decode the DER
|
||||
SecTransformRef transform = SecDecodeTransformCreate(kSecBase64Encoding, NULL);
|
||||
if (!transform) return nil;
|
||||
NSData *input = [base64der dataUsingEncoding:NSUTF8StringEncoding];
|
||||
NSData *output = nil;
|
||||
|
||||
if (SecTransformSetAttribute(transform,
|
||||
kSecTransformInputAttributeName,
|
||||
(__bridge CFDataRef)input,
|
||||
NULL)) {
|
||||
output = CFBridgingRelease(SecTransformExecute(transform, NULL));
|
||||
}
|
||||
if (transform) CFRelease(transform);
|
||||
|
||||
return [self initWithCertificateDataDER:output];
|
||||
}
|
||||
|
||||
+ (NSArray *)certificatesFromPEM:(NSString *)pemData {
|
||||
NSScanner *scanner = [NSScanner scannerWithString:pemData];
|
||||
NSMutableArray *certs = [[NSMutableArray alloc] init];
|
||||
|
||||
while (YES) {
|
||||
NSString *curCert;
|
||||
|
||||
[scanner scanUpToString:@"-----BEGIN CERTIFICATE-----" intoString:NULL];
|
||||
[scanner scanUpToString:@"-----END CERTIFICATE-----" intoString:&curCert];
|
||||
|
||||
// If there was no data, break.
|
||||
if (!curCert) break;
|
||||
|
||||
curCert = [curCert stringByAppendingString:@"-----END CERTIFICATE-----"];
|
||||
SNTCertificate *cert = [[SNTCertificate alloc] initWithCertificateDataPEM:curCert];
|
||||
|
||||
// If the data couldn't be turned into a valid SNTCertificate, continue.
|
||||
if (!cert) continue;
|
||||
|
||||
[certs addObject:cert];
|
||||
}
|
||||
|
||||
return certs;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_certRef) CFRelease(_certRef);
|
||||
}
|
||||
|
||||
#pragma mark Equality & description
|
||||
|
||||
- (BOOL)isEqual:(SNTCertificate *)other {
|
||||
if (self == other) return YES;
|
||||
if (![other isKindOfClass:[SNTCertificate class]]) return NO;
|
||||
|
||||
return [self.certData isEqual:other.certData];
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
return [self.certData hash];
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"/O=%@/OU=%@/CN=%@",
|
||||
self.orgName,
|
||||
self.orgUnit,
|
||||
self.commonName];
|
||||
}
|
||||
|
||||
#pragma mark NSSecureCoding
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
[coder encodeObject:self.certData forKey:kCertDataKey];
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
NSData *certData = [decoder decodeObjectOfClass:[NSData class] forKey:kCertDataKey];
|
||||
if ([certData length] == 0) return nil;
|
||||
SecCertificateRef cert = SecCertificateCreateWithData(NULL, (__bridge CFDataRef)certData);
|
||||
self = [self initWithSecCertificateRef:cert];
|
||||
if (cert) CFRelease(cert);
|
||||
return self;
|
||||
}
|
||||
|
||||
#pragma mark Private Accessors
|
||||
|
||||
///
|
||||
/// For a given selector, caches the value that selector would return on subsequent invocations,
|
||||
/// using the provided block to get the value on the first invocation.
|
||||
/// Assumes the selector's value will never change.
|
||||
///
|
||||
- (id)memoizedSelector:(SEL)selector forBlock:(id (^)(void))block {
|
||||
NSString *selName = NSStringFromSelector(selector);
|
||||
|
||||
if (!self.memoizedData) {
|
||||
self.memoizedData = [NSMutableDictionary dictionary];
|
||||
}
|
||||
|
||||
if (!self.memoizedData[selName]) {
|
||||
id val = block();
|
||||
if (val) {
|
||||
self.memoizedData[selName] = val;
|
||||
} else {
|
||||
self.memoizedData[selName] = [NSNull null];
|
||||
}
|
||||
}
|
||||
|
||||
// Return the value if there is one, or nil if the value is NSNull
|
||||
return self.memoizedData[selName] != [NSNull null] ? self.memoizedData[selName] : nil;
|
||||
}
|
||||
|
||||
- (NSDictionary *)allCertificateValues {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return CFBridgingRelease(SecCertificateCopyValues(self.certRef, NULL, NULL));
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDictionary *)x509SubjectName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1SubjectName];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDictionary *)x509IssuerName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self allCertificateValues][(__bridge NSString *)kSecOIDX509V1IssuerName];
|
||||
}];
|
||||
}
|
||||
|
||||
///
|
||||
/// Retrieve the value with the specified label from the X509 dictionary provided
|
||||
///
|
||||
/// @param desiredLabel The label you want, e.g: kSecOIDOrganizationName.
|
||||
/// @param dict The dictionary to look in (Subject or Issuer)
|
||||
/// @return An @c NSString, the value for the specified label.
|
||||
///
|
||||
- (NSString *)x509ValueForLabel:(NSString *)desiredLabel fromDictionary:(NSDictionary *)dict {
|
||||
@try {
|
||||
NSArray *valArray = dict[(__bridge NSString *)kSecPropertyKeyValue];
|
||||
|
||||
for (NSDictionary *curCertVal in valArray) {
|
||||
NSString *valueLabel = curCertVal[(__bridge NSString *)kSecPropertyKeyLabel];
|
||||
if ([valueLabel isEqual:desiredLabel]) {
|
||||
return curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
|
||||
}
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Retrieve the specified date from the certificate's values and convert from a reference date
|
||||
/// to an NSDate object.
|
||||
///
|
||||
/// @param key The identifier for the date: @c kSecOIDX509V1ValiditityNot{Before,After}
|
||||
/// @return An @c NSDate representing the date and time the certificate is valid from or expires.
|
||||
///
|
||||
- (NSDate *)dateForX509Key:(NSString *)key {
|
||||
NSDictionary *curCertVal = [self allCertificateValues][key];
|
||||
NSNumber *value = curCertVal[(__bridge NSString *)kSecPropertyKeyValue];
|
||||
|
||||
NSTimeInterval interval = [value doubleValue];
|
||||
if (interval) {
|
||||
return [NSDate dateWithTimeIntervalSinceReferenceDate:interval];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
#pragma mark Public Accessors
|
||||
|
||||
- (NSString *)SHA1 {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
NSMutableData *SHA1Buffer = [[NSMutableData alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH];
|
||||
|
||||
CC_SHA1([self.certData bytes], (CC_LONG)[self.certData length], [SHA1Buffer mutableBytes]);
|
||||
|
||||
const unsigned char *bytes = (const unsigned char *)[SHA1Buffer bytes];
|
||||
NSMutableString *hexDigest = [NSMutableString stringWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
||||
[hexDigest appendFormat:@"%02x", bytes[i]];
|
||||
}
|
||||
|
||||
return hexDigest;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)SHA256 {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
NSMutableData *SHA256Buffer = [[NSMutableData alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH];
|
||||
|
||||
CC_SHA256([self.certData bytes], (CC_LONG)[self.certData length], [SHA256Buffer mutableBytes]);
|
||||
|
||||
const unsigned char *bytes = (const unsigned char *)[SHA256Buffer bytes];
|
||||
NSMutableString *hexDigest = [NSMutableString stringWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
||||
[hexDigest appendFormat:@"%02x", bytes[i]];
|
||||
}
|
||||
|
||||
return hexDigest;
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSData *)certData {
|
||||
return CFBridgingRelease(SecCertificateCopyData(self.certRef));
|
||||
}
|
||||
|
||||
- (NSString *)commonName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
CFStringRef commonName = NULL;
|
||||
SecCertificateCopyCommonName(self.certRef, &commonName);
|
||||
return CFBridgingRelease(commonName);
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)countryName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
|
||||
fromDictionary:[self x509SubjectName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)orgName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
|
||||
fromDictionary:[self x509SubjectName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)orgUnit {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
|
||||
fromDictionary:[self x509SubjectName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDate *)validFrom {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotBefore];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSDate *)validUntil {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self dateForX509Key:(__bridge NSString *)kSecOIDX509V1ValidityNotAfter];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerCommonName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCommonName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerCountryName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDCountryName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerOrgName {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
- (NSString *)issuerOrgUnit {
|
||||
return [self memoizedSelector:_cmd forBlock:^id{
|
||||
return [self x509ValueForLabel:(__bridge NSString *)kSecOIDOrganizationalUnitName
|
||||
fromDictionary:[self x509IssuerName]];
|
||||
}];
|
||||
}
|
||||
|
||||
|
||||
@end
|
||||
@@ -1,90 +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 SNTCertificate;
|
||||
|
||||
///
|
||||
/// SNTCodesignChecker validates a binary (either on-disk or in memory) has been signed
|
||||
/// and if so allows for pulling out the certificates that were used to sign it.
|
||||
///
|
||||
@interface SNTCodesignChecker : NSObject
|
||||
|
||||
///
|
||||
/// The SecStaticCodeRef that this SNTCodesignChecker is working around
|
||||
///
|
||||
@property(readonly) SecStaticCodeRef codeRef;
|
||||
|
||||
///
|
||||
/// Returns a dictionary of raw signing information
|
||||
///
|
||||
@property(readonly) NSDictionary *signingInformation;
|
||||
|
||||
///
|
||||
/// Returns an array of @c SNTCertificate objects representing the chain that signed this binary.
|
||||
///
|
||||
@property(readonly) NSArray *certificates;
|
||||
|
||||
///
|
||||
/// Returns the leaf certificate that this binary was signed with
|
||||
///
|
||||
@property(readonly) SNTCertificate *leafCertificate;
|
||||
|
||||
///
|
||||
/// Returns the on-disk path of this binary.
|
||||
///
|
||||
@property(readonly) NSString *binaryPath;
|
||||
|
||||
///
|
||||
/// Designated initializer
|
||||
/// Takes ownership of the codeRef reference.
|
||||
///
|
||||
/// @param codeRef a SecStaticCodeRef or SecCodeRef representing a binary.
|
||||
/// @return an initialized SNTCodesignChecker if the binary is validly signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef;
|
||||
|
||||
///
|
||||
/// Convenience initializer for a binary on disk.
|
||||
///
|
||||
/// @param binaryPath A binary file on disk
|
||||
/// @return an initialized SNTCodesignChecker if file is a binary and is signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithBinaryPath:(NSString *)binaryPath;
|
||||
|
||||
///
|
||||
/// Convenience initializer for a binary that is running, by its process ID.
|
||||
///
|
||||
/// @param PID Id of a running process.
|
||||
/// @return an initialized SNTCodesignChecker if binary is signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithPID:(pid_t)PID;
|
||||
|
||||
///
|
||||
/// Convenience initializer for the currently running process.
|
||||
///
|
||||
/// @return an initialized SNTCodesignChecker if current binary is signed, nil otherwise.
|
||||
///
|
||||
- (instancetype)initWithSelf;
|
||||
|
||||
///
|
||||
/// Compares the signatures of the binaries represented by this SNTCodesignChecker and
|
||||
/// @c otherChecker.
|
||||
///
|
||||
/// If both binaries are correctly signed and the leaf signatures are identical.
|
||||
///
|
||||
/// @return YES if both binaries are signed with the same leaf certificate.
|
||||
///
|
||||
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker;
|
||||
|
||||
@end
|
||||
@@ -1,196 +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 "SNTCodesignChecker.h"
|
||||
|
||||
#import <Security/Security.h>
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
|
||||
|
||||
/**
|
||||
* kStaticSigningFlags are the flags used when validating signatures on disk.
|
||||
*
|
||||
* Don't validate resources but do validate nested code. Ignoring resources _dramatically_ speeds
|
||||
* up validation (see below) but does mean images, plists, etc will not be checked and modifying
|
||||
* these will not be considered invalid. To ensure any code inside the binary is still checked,
|
||||
* we check nested code.
|
||||
*
|
||||
* Timings with different flags:
|
||||
* Checking Xcode 5.1.1 bundle:
|
||||
* kSecCSDefaultFlags: 3.895s
|
||||
* kSecCSDoNotValidateResources: 0.013s
|
||||
* kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.013s
|
||||
*
|
||||
* Checking Google Chrome 36.0.1985.143 bundle:
|
||||
* kSecCSDefaultFlags: 0.529s
|
||||
* kSecCSDoNotValidateResources: 0.032s
|
||||
* kSecCSDoNotValidateResources | kSecCSCheckNestedCode: 0.033s
|
||||
*/
|
||||
const SecCSFlags kStaticSigningFlags = kSecCSDoNotValidateResources | kSecCSCheckNestedCode;
|
||||
|
||||
/**
|
||||
* kSigningFlags are the flags used when validating signatures for running binaries.
|
||||
*
|
||||
* No special flags needed currently.
|
||||
*/
|
||||
const SecCSFlags kSigningFlags = kSecCSDefaultFlags;
|
||||
|
||||
|
||||
@interface SNTCodesignChecker ()
|
||||
/// Array of @c SNTCertificate's representing the chain of certs this executable was signed with.
|
||||
@property NSMutableArray *certificates;
|
||||
@end
|
||||
|
||||
@implementation SNTCodesignChecker
|
||||
|
||||
#pragma mark Init/dealloc
|
||||
|
||||
- (instancetype)initWithSecStaticCodeRef:(SecStaticCodeRef)codeRef {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
// First check the signing is valid
|
||||
if (CFGetTypeID(codeRef) == SecStaticCodeGetTypeID()) {
|
||||
if (SecStaticCodeCheckValidity(codeRef, kStaticSigningFlags, NULL) != errSecSuccess) {
|
||||
return nil;
|
||||
}
|
||||
} else if (CFGetTypeID(codeRef) == SecCodeGetTypeID()) {
|
||||
if (SecCodeCheckValidity((SecCodeRef)codeRef, kSigningFlags, NULL) != errSecSuccess) {
|
||||
return nil;
|
||||
}
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
|
||||
// Get CFDictionary of signing information for binary
|
||||
OSStatus status = errSecSuccess;
|
||||
CFDictionaryRef signingDict = NULL;
|
||||
status = SecCodeCopySigningInformation(codeRef, kSecCSSigningInformation, &signingDict);
|
||||
_signingInformation = CFBridgingRelease(signingDict);
|
||||
if (status != errSecSuccess) return nil;
|
||||
|
||||
// Get array of certificates.
|
||||
NSArray *certs = _signingInformation[(id)kSecCodeInfoCertificates];
|
||||
if (!certs) return nil;
|
||||
|
||||
// Wrap SecCertificateRef objects in SNTCertificate and put in a new NSArray
|
||||
NSMutableArray *mutableCerts = [[NSMutableArray alloc] initWithCapacity:certs.count];
|
||||
for (int i = 0; i < certs.count; ++i) {
|
||||
SecCertificateRef certRef = (__bridge SecCertificateRef)certs[i];
|
||||
SNTCertificate *newCert = [[SNTCertificate alloc] initWithSecCertificateRef:certRef];
|
||||
[mutableCerts addObject:newCert];
|
||||
}
|
||||
_certificates = [mutableCerts copy];
|
||||
|
||||
_codeRef = codeRef;
|
||||
CFRetain(_codeRef);
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithBinaryPath:(NSString *)binaryPath {
|
||||
SecStaticCodeRef codeRef = NULL;
|
||||
|
||||
// Get SecStaticCodeRef for binary
|
||||
if (SecStaticCodeCreateWithPath((__bridge CFURLRef)[NSURL fileURLWithPath:binaryPath
|
||||
isDirectory:NO],
|
||||
kSecCSDefaultFlags,
|
||||
&codeRef) == errSecSuccess) {
|
||||
self = [self initWithSecStaticCodeRef:codeRef];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
if (codeRef) CFRelease(codeRef);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithPID:(pid_t)PID {
|
||||
SecCodeRef codeRef = NULL;
|
||||
NSDictionary *attributes = @{(__bridge NSString *)kSecGuestAttributePid: @(PID)};
|
||||
|
||||
if (SecCodeCopyGuestWithAttributes(NULL,
|
||||
(__bridge CFDictionaryRef)attributes,
|
||||
kSecCSDefaultFlags,
|
||||
&codeRef) == errSecSuccess) {
|
||||
self = [self initWithSecStaticCodeRef:codeRef];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
if (codeRef) CFRelease(codeRef);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithSelf {
|
||||
SecCodeRef codeSelf = NULL;
|
||||
if (SecCodeCopySelf(kSecCSDefaultFlags, &codeSelf) == errSecSuccess) {
|
||||
self = [self initWithSecStaticCodeRef:codeSelf];
|
||||
} else {
|
||||
self = nil;
|
||||
}
|
||||
|
||||
if (codeSelf) CFRelease(codeSelf);
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
if (_codeRef) {
|
||||
CFRelease(_codeRef);
|
||||
_codeRef = NULL;
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Description
|
||||
|
||||
- (NSString *)description {
|
||||
NSString *binarySource;
|
||||
if (CFGetTypeID(self.codeRef) == SecStaticCodeGetTypeID()) {
|
||||
binarySource = @"On-disk";
|
||||
} else {
|
||||
binarySource = @"In-memory";
|
||||
}
|
||||
|
||||
return [NSString stringWithFormat:@"%@ binary, signed by %@, located at: %@",
|
||||
binarySource,
|
||||
self.leafCertificate.orgName,
|
||||
self.binaryPath];
|
||||
}
|
||||
|
||||
#pragma mark Public accessors
|
||||
|
||||
- (SNTCertificate *)leafCertificate {
|
||||
return [self.certificates firstObject];
|
||||
}
|
||||
|
||||
- (NSString *)binaryPath {
|
||||
CFURLRef path;
|
||||
OSStatus status = SecCodeCopyPath(_codeRef, kSecCSDefaultFlags, &path);
|
||||
NSURL *pathURL = CFBridgingRelease(path);
|
||||
if (status != errSecSuccess) return nil;
|
||||
return [pathURL path];
|
||||
}
|
||||
|
||||
- (BOOL)signingInformationMatches:(SNTCodesignChecker *)otherChecker {
|
||||
return [self.certificates isEqual:otherChecker.certificates];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,57 +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,
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
SNTRuleTableErrorEmptyRuleArray,
|
||||
SNTRuleTableErrorInsertOrReplaceFailed,
|
||||
SNTRuleTableErrorInvalidRule,
|
||||
SNTRuleTableErrorMissingRequiredRule,
|
||||
SNTRuleTableErrorRemoveFailed
|
||||
};
|
||||
|
||||
EVENTSTATE_BLOCK_UNKNOWN = 5,
|
||||
EVENTSTATE_BLOCK_BINARY = 6,
|
||||
EVENTSTATE_BLOCK_CERTIFICATE = 7,
|
||||
EVENTSTATE_BLOCK_SCOPE = 8,
|
||||
|
||||
EVENTSTATE_MAX
|
||||
} santa_eventstate_t;
|
||||
|
||||
#endif // SANTA__COMMON__COMMONENUMS_H
|
||||
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";
|
||||
|
||||
@@ -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
|
||||
@@ -21,102 +21,178 @@
|
||||
@interface SNTConfigurator : NSObject
|
||||
|
||||
/// Default config file path
|
||||
extern NSString * const kDefaultConfigFilePath;
|
||||
extern NSString *const kDefaultConfigFilePath;
|
||||
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// The operating mode.
|
||||
///
|
||||
@property santa_clientmode_t clientMode;
|
||||
@property(nonatomic) SNTClientMode clientMode;
|
||||
|
||||
///
|
||||
/// Whether or not to log all events, even for whitelisted binaries.
|
||||
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
|
||||
///
|
||||
@property BOOL logAllEvents;
|
||||
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *fileChangesRegex;
|
||||
|
||||
# pragma mark - GUI Settings
|
||||
///
|
||||
/// The regex of whitelisted paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *whitelistPathRegex;
|
||||
|
||||
///
|
||||
/// The regex of blacklisted paths. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotall) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(nonatomic) NSRegularExpression *blacklistPathRegex;
|
||||
|
||||
///
|
||||
/// Enable __PAGEZERO protection, defaults to YES
|
||||
/// If this flag is set to NO, 32-bit binaries that are missing
|
||||
/// the __PAGEZERO segment will not be blocked.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enablePageZeroProtection;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
///
|
||||
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
|
||||
/// If unset, the button will not be displayed.
|
||||
///
|
||||
@property(readonly) NSURL *moreInfoURL;
|
||||
@property(readonly, nonatomic) NSURL *moreInfoURL;
|
||||
|
||||
///
|
||||
/// 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) NSString *eventDetailURL;
|
||||
@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.
|
||||
///
|
||||
@property(readonly) NSString *eventDetailText;
|
||||
@property(readonly, nonatomic) NSString *eventDetailText;
|
||||
|
||||
# pragma mark - Sync Settings
|
||||
///
|
||||
/// In lockdown mode this is the message shown to the user when an unknown binary
|
||||
/// is blocked. If this message is not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *unknownBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a binary is blocked because of a rule,
|
||||
/// if that rule doesn't provide a custom message. If this is not configured, a reasonable
|
||||
/// default is provided.
|
||||
///
|
||||
@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
|
||||
|
||||
///
|
||||
/// The base URL of the sync server.
|
||||
///
|
||||
@property(readonly) NSURL *syncBaseURL;
|
||||
@property(readonly, nonatomic) NSURL *syncBaseURL;
|
||||
|
||||
///
|
||||
/// If YES, mid-execution event uploads are skipped.
|
||||
/// This property is never stored on disk.
|
||||
///
|
||||
@property BOOL syncBackOff;
|
||||
|
||||
///
|
||||
/// The machine owner.
|
||||
///
|
||||
@property(readonly) NSString *machineOwner;
|
||||
@property(readonly, nonatomic) NSString *machineOwner;
|
||||
|
||||
///
|
||||
/// The last date of successful sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *syncLastSuccess;
|
||||
|
||||
///
|
||||
/// If YES a clean sync is required.
|
||||
///
|
||||
@property(nonatomic) BOOL syncCleanRequired;
|
||||
|
||||
///
|
||||
/// If set, this over-rides the default machine ID used for syncing.
|
||||
///
|
||||
@property(readonly) NSString *machineID;
|
||||
@property(readonly, nonatomic) NSString *machineID;
|
||||
|
||||
# pragma mark Server Auth Settings
|
||||
#pragma mark Server Auth Settings
|
||||
|
||||
///
|
||||
/// If set, this is valid PEM containing one or more certificates to be used to evaluate the
|
||||
/// server's SSL chain, overriding the list of trusted CAs distributed with the OS.
|
||||
///
|
||||
@property(readonly) NSData *syncServerAuthRootsData;
|
||||
@property(readonly, nonatomic) NSData *syncServerAuthRootsData;
|
||||
|
||||
///
|
||||
/// This property is the same as the above but is a file on disk containing the PEM data.
|
||||
///
|
||||
@property(readonly) NSString *syncServerAuthRootsFile;
|
||||
@property(readonly, nonatomic) NSString *syncServerAuthRootsFile;
|
||||
|
||||
# pragma mark Client Auth Settings
|
||||
#pragma mark Client Auth Settings
|
||||
|
||||
///
|
||||
/// If set, this contains the location of a PKCS#12 certificate to be used for sync authentication.
|
||||
///
|
||||
@property(readonly) NSString *syncClientAuthCertificateFile;
|
||||
@property(readonly, nonatomic) NSString *syncClientAuthCertificateFile;
|
||||
|
||||
///
|
||||
/// Contains the password for the pkcs#12 certificate.
|
||||
///
|
||||
@property(readonly) NSString *syncClientAuthCertificatePassword;
|
||||
@property(readonly, nonatomic) NSString *syncClientAuthCertificatePassword;
|
||||
|
||||
///
|
||||
/// If set, this is the Common Name of a certificate in the System keychain to be used for
|
||||
/// sync authentication. The corresponding private key must also be in the keychain.
|
||||
///
|
||||
@property(readonly) NSString *syncClientAuthCertificateCn;
|
||||
@property(readonly, nonatomic) NSString *syncClientAuthCertificateCn;
|
||||
|
||||
///
|
||||
/// If set, this is the Issuer Name of a certificate in the System keychain to be used for
|
||||
/// sync authentication. The corresponding private key must also be in the keychain.
|
||||
///
|
||||
@property(readonly) NSString *syncClientAuthCertificateIssuer;
|
||||
@property(readonly, nonatomic) NSString *syncClientAuthCertificateIssuer;
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
|
||||
@@ -20,38 +20,55 @@
|
||||
@interface SNTConfigurator ()
|
||||
@property NSString *configFilePath;
|
||||
@property NSMutableDictionary *configData;
|
||||
|
||||
/// Creating NSRegularExpression objects is not fast, so cache them.
|
||||
@property NSRegularExpression *cachedFileChangesRegex;
|
||||
@property NSRegularExpression *cachedWhitelistDirRegex;
|
||||
@property NSRegularExpression *cachedBlacklistDirRegex;
|
||||
|
||||
/// Array of keys that cannot be changed while santad is running if santad didn't make the change.
|
||||
@property(readonly) NSArray *protectedKeys;
|
||||
@end
|
||||
|
||||
@implementation SNTConfigurator
|
||||
|
||||
/// The hard-coded path to the config file
|
||||
NSString * const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
NSString *const kDefaultConfigFilePath = @"/var/db/santa/config.plist";
|
||||
|
||||
/// The keys in the config file
|
||||
static NSString * const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kClientModeKey = @"ClientMode";
|
||||
static NSString *const kFileChangesRegexKey = @"FileChangesRegex";
|
||||
static NSString *const kWhitelistRegexKey = @"WhitelistRegex";
|
||||
static NSString *const kBlacklistRegexKey = @"BlacklistRegex";
|
||||
static NSString *const kEnablePageZeroProtectionKey = @"EnablePageZeroProtection";
|
||||
|
||||
static NSString * const kLogAllEventsKey = @"LogAllEvents";
|
||||
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 kMoreInfoURLKey = @"MoreInfoURL";
|
||||
static NSString * const kEventDetailURLKey = @"EventDetailURL";
|
||||
static NSString * const kEventDetailTextKey = @"EventDetailText";
|
||||
static NSString *const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString *const kSyncLastSuccess = @"SyncLastSuccess";
|
||||
static NSString *const kSyncCleanRequired = @"SyncCleanRequired";
|
||||
static NSString *const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString *const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString *const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
static NSString *const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
|
||||
static NSString *const kServerAuthRootsDataKey = @"ServerAuthRootsData";
|
||||
static NSString *const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
|
||||
|
||||
static NSString * const kSyncBaseURLKey = @"SyncBaseURL";
|
||||
static NSString * const kClientAuthCertificateFileKey = @"ClientAuthCertificateFile";
|
||||
static NSString * const kClientAuthCertificatePasswordKey = @"ClientAuthCertificatePassword";
|
||||
static NSString * const kClientAuthCertificateCNKey = @"ClientAuthCertificateCN";
|
||||
static NSString * const kClientAuthCertificateIssuerKey = @"ClientAuthCertificateIssuerCN";
|
||||
static NSString * const kServerAuthRootsDataKey = @"ServerAuthRootsData";
|
||||
static NSString * const kServerAuthRootsFileKey = @"ServerAuthRootsFile";
|
||||
static NSString *const kMachineOwnerKey = @"MachineOwner";
|
||||
static NSString *const kMachineIDKey = @"MachineID";
|
||||
|
||||
static NSString * const kMachineOwnerKey = @"MachineOwner";
|
||||
static NSString * const kMachineIDKey = @"MachineID";
|
||||
static NSString *const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
|
||||
static NSString *const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
|
||||
static NSString * const kMachineOwnerPlistFileKey = @"MachineOwnerPlist";
|
||||
static NSString * const kMachineOwnerPlistKeyKey = @"MachineOwnerKey";
|
||||
|
||||
static NSString * const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
static NSString *const kMachineIDPlistFileKey = @"MachineIDPlist";
|
||||
static NSString *const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath {
|
||||
self = [super init];
|
||||
@@ -62,45 +79,114 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return self;
|
||||
}
|
||||
|
||||
# pragma mark Singleton retriever
|
||||
#pragma mark Singleton retriever
|
||||
|
||||
+ (instancetype)configurator {
|
||||
static SNTConfigurator *sharedConfigurator = nil;
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
|
||||
sharedConfigurator = [[SNTConfigurator alloc] initWithFilePath:kDefaultConfigFilePath];
|
||||
});
|
||||
return sharedConfigurator;
|
||||
}
|
||||
|
||||
# pragma mark Public Interface
|
||||
#pragma mark Protected Keys
|
||||
|
||||
- (santa_clientmode_t)clientMode {
|
||||
int cm = [self.configData[kClientModeKey] intValue];
|
||||
if (cm > CLIENTMODE_UNKNOWN && cm < CLIENTMODE_MAX) {
|
||||
return cm;
|
||||
- (NSArray *)protectedKeys {
|
||||
return @[ kClientModeKey, kWhitelistRegexKey, kBlacklistRegexKey,
|
||||
kFileChangesRegexKey, kSyncBaseURLKey ];
|
||||
}
|
||||
|
||||
#pragma mark Public Interface
|
||||
|
||||
- (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);
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)logAllEvents {
|
||||
return [self.configData[kLogAllEventsKey] boolValue];
|
||||
- (NSRegularExpression *)whitelistPathRegex {
|
||||
if (!self.cachedWhitelistDirRegex && self.configData[kWhitelistRegexKey]) {
|
||||
NSString *re = self.configData[kWhitelistRegexKey];
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedWhitelistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:NULL];
|
||||
}
|
||||
return self.cachedWhitelistDirRegex;
|
||||
}
|
||||
|
||||
- (void)setLogAllEvents:(BOOL)logAllEvents {
|
||||
self.configData[kLogAllEventsKey] = @(logAllEvents);
|
||||
- (void)setWhitelistPathRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kWhitelistRegexKey];
|
||||
} else {
|
||||
self.configData[kWhitelistRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedWhitelistDirRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)blacklistPathRegex {
|
||||
if (!self.cachedBlacklistDirRegex && self.configData[kBlacklistRegexKey]) {
|
||||
NSString *re = self.configData[kBlacklistRegexKey];
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedBlacklistDirRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:NULL];
|
||||
}
|
||||
return self.cachedBlacklistDirRegex;
|
||||
}
|
||||
|
||||
- (void)setBlacklistPathRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kBlacklistRegexKey];
|
||||
} else {
|
||||
self.configData[kBlacklistRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedBlacklistDirRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (NSRegularExpression *)fileChangesRegex {
|
||||
if (!self.cachedFileChangesRegex && self.configData[kFileChangesRegexKey]) {
|
||||
NSString *re = self.configData[kFileChangesRegexKey];
|
||||
if (![re hasPrefix:@"^"]) re = [@"^" stringByAppendingString:re];
|
||||
self.cachedFileChangesRegex = [NSRegularExpression regularExpressionWithPattern:re
|
||||
options:0
|
||||
error:NULL];
|
||||
}
|
||||
return self.cachedFileChangesRegex;
|
||||
}
|
||||
|
||||
- (void)setFileChangesRegex:(NSRegularExpression *)re {
|
||||
if (!re) {
|
||||
[self.configData removeObjectForKey:kFileChangesRegexKey];
|
||||
} else {
|
||||
self.configData[kFileChangesRegexKey] = [re pattern];
|
||||
}
|
||||
self.cachedFileChangesRegex = nil;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (BOOL)enablePageZeroProtection {
|
||||
NSNumber *keyValue = self.configData[kEnablePageZeroProtectionKey];
|
||||
return keyValue ? [keyValue boolValue] : YES;
|
||||
}
|
||||
|
||||
- (NSURL *)moreInfoURL {
|
||||
return [NSURL URLWithString:self.configData[kMoreInfoURLKey]];
|
||||
}
|
||||
@@ -109,12 +195,38 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return self.configData[kEventDetailURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailBundleURL {
|
||||
return self.configData[kEventDetailBundleURLKey];
|
||||
}
|
||||
|
||||
- (NSString *)eventDetailText {
|
||||
return self.configData[kEventDetailTextKey];
|
||||
}
|
||||
|
||||
- (NSString *)unknownBlockMessage {
|
||||
return self.configData[kUnknownBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)bannedBlockMessage {
|
||||
return self.configData[kBannedBlockMessage];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationMonitor {
|
||||
return self.configData[kModeNotificationMonitor];
|
||||
}
|
||||
|
||||
- (NSString *)modeNotificationLockdown {
|
||||
return self.configData[kModeNotificationLockdown];
|
||||
}
|
||||
|
||||
- (NSURL *)syncBaseURL {
|
||||
return [NSURL URLWithString:self.configData[kSyncBaseURLKey]];
|
||||
NSString *urlStr = self.configData[kSyncBaseURLKey];
|
||||
if (urlStr) {
|
||||
NSURL *url = [NSURL URLWithString:urlStr];
|
||||
if (!url) LOGW(@"SyncBaseURL is not a valid URL!");
|
||||
return url;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (NSString *)syncClientAuthCertificateFile {
|
||||
@@ -141,6 +253,24 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
return self.configData[kServerAuthRootsFileKey];
|
||||
}
|
||||
|
||||
- (NSDate *)syncLastSuccess {
|
||||
return self.configData[kSyncLastSuccess];
|
||||
}
|
||||
|
||||
- (void)setSyncLastSuccess:(NSDate *)syncLastSuccess {
|
||||
self.configData[kSyncLastSuccess] = syncLastSuccess;
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (BOOL)syncCleanRequired {
|
||||
return [self.configData[kSyncCleanRequired] boolValue];
|
||||
}
|
||||
|
||||
- (void)setSyncCleanRequired:(BOOL)syncCleanRequired {
|
||||
self.configData[kSyncCleanRequired] = @(syncCleanRequired);
|
||||
[self saveConfigToDisk];
|
||||
}
|
||||
|
||||
- (NSString *)machineOwner {
|
||||
NSString *machineOwner;
|
||||
|
||||
@@ -172,7 +302,7 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
machineId = self.configData[kMachineIDKey];
|
||||
}
|
||||
|
||||
if (!machineId || [machineId isEqual:@""]) {
|
||||
if ([machineId length] == 0) {
|
||||
machineId = [SNTSystemInfo hardwareUUID];
|
||||
}
|
||||
|
||||
@@ -180,8 +310,6 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
}
|
||||
|
||||
- (void)reloadConfigData {
|
||||
if (!self.configData) self.configData = [NSMutableDictionary dictionary];
|
||||
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
if (![fm fileExistsAtPath:self.configFilePath]) return;
|
||||
|
||||
@@ -190,30 +318,44 @@ 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;
|
||||
}
|
||||
|
||||
NSDictionary *configData =
|
||||
NSMutableDictionary *configData =
|
||||
[NSPropertyListSerialization propertyListWithData:readData
|
||||
options:kCFPropertyListImmutable
|
||||
options:NSPropertyListMutableContainers
|
||||
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;
|
||||
}
|
||||
|
||||
// Ensure no-one is trying to change the client mode behind Santa's back.
|
||||
if (self.configData[kClientModeKey] && configData[kClientModeKey] &&
|
||||
![self.configData[kClientModeKey] isEqual:configData[kClientModeKey]] &&
|
||||
geteuid() == 0) {
|
||||
NSMutableDictionary *configDataMutable = [configData mutableCopy];
|
||||
configDataMutable[kClientModeKey] = self.configData[kClientModeKey];
|
||||
self.configData = configDataMutable;
|
||||
[self saveConfigToDisk];
|
||||
if (self.syncBaseURL) {
|
||||
// Ensure no-one is trying to change protected keys behind our back.
|
||||
BOOL changed = NO;
|
||||
if (geteuid() == 0) {
|
||||
for (NSString *key in self.protectedKeys) {
|
||||
if (((self.configData[key] && !configData[key]) ||
|
||||
(!self.configData[key] && configData[key]) ||
|
||||
(self.configData[key] && ![self.configData[key] isEqual:configData[key]]))) {
|
||||
if (self.configData[key]) {
|
||||
configData[key] = self.configData[key];
|
||||
} else {
|
||||
[configData removeObjectForKey:key];
|
||||
}
|
||||
changed = YES;
|
||||
LOGI(@"Ignoring changed configuration key: %@", key);
|
||||
}
|
||||
}
|
||||
}
|
||||
self.configData = configData;
|
||||
if (changed) [self saveConfigToDisk];
|
||||
} else {
|
||||
self.configData = [configData mutableCopy];
|
||||
self.configData = configData;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -223,6 +365,7 @@ static NSString * const kMachineIDPlistKeyKey = @"MachineIDKey";
|
||||
/// Saves the current @c self.configData to disk.
|
||||
///
|
||||
- (void)saveConfigToDisk {
|
||||
if (geteuid() != 0) return;
|
||||
[self.configData writeToFile:self.configFilePath atomically:YES];
|
||||
}
|
||||
|
||||
|
||||
@@ -17,4 +17,4 @@
|
||||
///
|
||||
/// @return YES if dropping was successful or unnecessary.
|
||||
///
|
||||
BOOL DropRootPrivileges();
|
||||
BOOL DropRootPrivileges(void);
|
||||
|
||||
@@ -16,8 +16,9 @@
|
||||
|
||||
BOOL DropRootPrivileges() {
|
||||
if (getuid() == 0 || geteuid() == 0 || getgid() == 0 || getegid() == 0) {
|
||||
if (setgid(-2) != 0 || setgroups(0, NULL) != 0 || setegid(-2) != 0 ||
|
||||
setuid(-2) != 0 || seteuid(-2) != 0) {
|
||||
uid_t nobody = (uid_t)-2;
|
||||
if (setgid(nobody) != 0 || setgroups(0, NULL) != 0 || setegid(nobody) != 0 ||
|
||||
setuid(nobody) != 0 || seteuid(nobody) != 0) {
|
||||
return false;
|
||||
}
|
||||
|
||||
|
||||
@@ -23,6 +23,16 @@
|
||||
///
|
||||
/// @param path The path of the file this instance is to represent. The path will be
|
||||
/// converted to an absolute, standardized path if it isn't already.
|
||||
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
|
||||
/// describing the problem.
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// Convenience initializer.
|
||||
///
|
||||
/// @param path The path to the file this instance is to represent. The path will be
|
||||
/// converted to an absolute, standardized path if it isn't already.
|
||||
///
|
||||
- (instancetype)initWithPath:(NSString *)path;
|
||||
|
||||
@@ -31,6 +41,14 @@
|
||||
///
|
||||
- (NSString *)path;
|
||||
|
||||
///
|
||||
/// Hash this file with SHA-1 and SHA-256 simultaneously.
|
||||
///
|
||||
/// @param sha1 If not NULL, will be filled with the SHA-1 of the file.
|
||||
/// @param sha256 If not NULL, will be filled with the SHA-256 of the file.
|
||||
///
|
||||
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256;
|
||||
|
||||
///
|
||||
/// @return SHA-1 hash of this binary.
|
||||
///
|
||||
@@ -41,12 +59,6 @@
|
||||
///
|
||||
- (NSString *)SHA256;
|
||||
|
||||
///
|
||||
/// @return The type of Mach-O file, one of:
|
||||
/// Dynamic Library, Kernel Extension, Fat Binary or Thin Binary.
|
||||
///
|
||||
- (NSString *)machoType;
|
||||
|
||||
///
|
||||
/// @return The architectures included in this binary (e.g. x86_64, ppc).
|
||||
///
|
||||
@@ -82,6 +94,21 @@
|
||||
///
|
||||
- (BOOL)isScript;
|
||||
|
||||
///
|
||||
/// @return YES if this file is an XAR archive.
|
||||
///
|
||||
- (BOOL)isXARArchive;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a disk image.
|
||||
///
|
||||
- (BOOL)isDMG;
|
||||
|
||||
///
|
||||
/// @return YES if this file has a bad/missing __PAGEZERO .
|
||||
///
|
||||
- (BOOL)isMissingPageZero;
|
||||
|
||||
///
|
||||
/// @return An NSBundle if this file is part of a bundle.
|
||||
///
|
||||
@@ -94,8 +121,8 @@
|
||||
|
||||
///
|
||||
/// @return Either the Info.plist in the bundle this file is part of, or an embedded plist if there
|
||||
/// is one. In the odd case that a file has both an embedded Info.plist and is part of a bundle,
|
||||
/// the Info.plist from the bundle will be returned.
|
||||
/// is one. In the unlikely event that a file has both an embedded Info.plist and is part of a
|
||||
/// bundle, the embedded plist will be returned.
|
||||
///
|
||||
- (NSDictionary *)infoPlist;
|
||||
|
||||
@@ -120,9 +147,28 @@
|
||||
- (NSString *)bundleShortVersionString;
|
||||
|
||||
///
|
||||
/// @return any URLs this file may have been downloaded from, using the
|
||||
/// @c com.apple.metadata:kMDItemWhereFroms extended attribute.
|
||||
/// @return LaunchServices quarantine data - download URL as an absolute string.
|
||||
///
|
||||
- (NSArray *)downloadURLs;
|
||||
- (NSString *)quarantineDataURL;
|
||||
|
||||
///
|
||||
/// @return LaunchServices quarantine data - referer URL as an absolute string.
|
||||
///
|
||||
- (NSString *)quarantineRefererURL;
|
||||
|
||||
///
|
||||
/// @return LaunchServices quarantine data - agent bundle ID.
|
||||
///
|
||||
- (NSString *)quarantineAgentBundleID;
|
||||
|
||||
///
|
||||
/// @return LaunchServices quarantine data - timestamp.
|
||||
///
|
||||
- (NSDate *)quarantineTimestamp;
|
||||
|
||||
///
|
||||
/// @return The size of the file in bytes.
|
||||
///
|
||||
- (NSUInteger)fileSize;
|
||||
|
||||
@end
|
||||
|
||||
@@ -18,180 +18,241 @@
|
||||
|
||||
#include <mach-o/loader.h>
|
||||
#include <mach-o/swap.h>
|
||||
#include <pwd.h>
|
||||
#include <sys/stat.h>
|
||||
#include <sys/xattr.h>
|
||||
|
||||
#import <FMDB/FMDB.h>
|
||||
|
||||
// Simple class to hold the data of a mach_header and the offset within the file
|
||||
// in which that header was found.
|
||||
@interface MachHeaderWithOffset : NSObject
|
||||
@property NSData *data;
|
||||
@property uint32_t offset;
|
||||
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset;
|
||||
@end
|
||||
@implementation MachHeaderWithOffset
|
||||
- (instancetype)initWithData:(NSData *)data offset:(uint32_t)offset {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_data = data;
|
||||
_offset = offset;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@end
|
||||
|
||||
@interface SNTFileInfo ()
|
||||
@property NSString *path;
|
||||
@property NSData *fileData;
|
||||
@property NSFileHandle *fileHandle;
|
||||
@property NSUInteger fileSize;
|
||||
@property NSString *fileOwnerHomeDir;
|
||||
|
||||
// Cached properties
|
||||
@property NSBundle *bundleRef;
|
||||
@property NSDictionary *infoDict;
|
||||
@property NSDictionary *quarantineDict;
|
||||
@property NSDictionary *cachedHeaders;
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfo
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
extern NSString *const NSURLQuarantinePropertiesKey WEAK_IMPORT_ATTRIBUTE;
|
||||
|
||||
- (instancetype)initWithPath:(NSString *)path error:(NSError **)error {
|
||||
self = [super init];
|
||||
|
||||
if (self) {
|
||||
// Convert to absolute, standardized path
|
||||
path = [path stringByResolvingSymlinksInPath];
|
||||
if (![path isAbsolutePath]) {
|
||||
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
|
||||
path = [cwd stringByAppendingPathComponent:path];
|
||||
}
|
||||
path = [path stringByStandardizingPath];
|
||||
|
||||
// Determine if file exists.
|
||||
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
|
||||
BOOL directory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
|
||||
return nil;
|
||||
} else if (directory) {
|
||||
NSString *infoPath = [path stringByAppendingPathComponent:@"Contents/Info.plist"];
|
||||
NSDictionary *d = [NSDictionary dictionaryWithContentsOfFile:infoPath];
|
||||
if (d && d[@"CFBundleExecutable"]) {
|
||||
path = [path stringByAppendingPathComponent:@"Contents/MacOS"];
|
||||
_path = [path stringByAppendingPathComponent:d[@"CFBundleExecutable"]];
|
||||
} else {
|
||||
return nil;
|
||||
NSBundle *bndl;
|
||||
_path = [self resolvePath:path bundle:&bndl];
|
||||
_bundleRef = bndl;
|
||||
if (_path.length == 0) {
|
||||
if (error) {
|
||||
NSString *errStr = @"Unable to resolve empty path";
|
||||
if (path) errStr = [@"Unable to resolve path: " stringByAppendingString:path];
|
||||
*error = [NSError errorWithDomain:@"com.google.santa.fileinfo"
|
||||
code:260
|
||||
userInfo:@{NSLocalizedDescriptionKey : errStr}];
|
||||
}
|
||||
} else {
|
||||
_path = path;
|
||||
return nil;
|
||||
}
|
||||
|
||||
_fileData = [NSData dataWithContentsOfFile:_path options:NSDataReadingMappedIfSafe error:nil];
|
||||
if (!_fileData) return nil;
|
||||
_fileHandle = [NSFileHandle fileHandleForReadingAtPath:_path];
|
||||
|
||||
struct stat fileStat;
|
||||
fstat(_fileHandle.fileDescriptor, &fileStat);
|
||||
_fileSize = fileStat.st_size;
|
||||
|
||||
if (_fileSize == 0) return nil;
|
||||
|
||||
if (fileStat.st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat.st_uid);
|
||||
if (pwd) {
|
||||
_fileOwnerHomeDir = @(pwd->pw_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)SHA1 {
|
||||
unsigned char sha1[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1([self.fileData bytes], (unsigned int)[self.fileData length], sha1);
|
||||
- (instancetype)initWithPath:(NSString *)path {
|
||||
return [self initWithPath:path error:NULL];
|
||||
}
|
||||
|
||||
// Convert the binary SHA into hex
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; i++) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)sha1[i]];
|
||||
#pragma mark Hashing
|
||||
|
||||
- (void)hashSHA1:(NSString **)sha1 SHA256:(NSString **)sha256 {
|
||||
const int chunkSize = 4096;
|
||||
|
||||
CC_SHA1_CTX c1;
|
||||
CC_SHA256_CTX c256;
|
||||
|
||||
if (sha1) CC_SHA1_Init(&c1);
|
||||
if (sha256) CC_SHA256_Init(&c256);
|
||||
|
||||
for (uint64_t offset = 0; offset < self.fileSize; offset += chunkSize) {
|
||||
@autoreleasepool {
|
||||
int readSize = 0;
|
||||
if (offset + chunkSize > self.fileSize) {
|
||||
readSize = (int)(self.fileSize - offset);
|
||||
} else {
|
||||
readSize = chunkSize;
|
||||
}
|
||||
|
||||
NSData *chunk = [self safeSubdataWithRange:NSMakeRange(offset, readSize)];
|
||||
if (!chunk) {
|
||||
if (sha1) CC_SHA1_Final(NULL, &c1);
|
||||
if (sha256) CC_SHA256_Final(NULL, &c256);
|
||||
return;
|
||||
}
|
||||
|
||||
if (sha1) CC_SHA1_Update(&c1, chunk.bytes, readSize);
|
||||
if (sha256) CC_SHA256_Update(&c256, chunk.bytes, readSize);
|
||||
}
|
||||
}
|
||||
|
||||
return buf;
|
||||
if (sha1) {
|
||||
unsigned char dgst[CC_SHA1_DIGEST_LENGTH];
|
||||
CC_SHA1_Final(dgst, &c1);
|
||||
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA1_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA1_DIGEST_LENGTH; ++i) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)dgst[i]];
|
||||
}
|
||||
*sha1 = [buf copy];
|
||||
}
|
||||
if (sha256) {
|
||||
unsigned char dgst[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256_Final(dgst, &c256);
|
||||
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH * 2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; ++i) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)dgst[i]];
|
||||
}
|
||||
*sha256 = [buf copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)SHA1 {
|
||||
NSString *sha1;
|
||||
[self hashSHA1:&sha1 SHA256:NULL];
|
||||
return sha1;
|
||||
}
|
||||
|
||||
- (NSString *)SHA256 {
|
||||
unsigned char sha2[CC_SHA256_DIGEST_LENGTH];
|
||||
CC_SHA256(self.fileData.bytes, (unsigned int)self.fileData.length, sha2);
|
||||
|
||||
NSMutableString *buf = [[NSMutableString alloc] initWithCapacity:CC_SHA256_DIGEST_LENGTH *2];
|
||||
for (int i = 0; i < CC_SHA256_DIGEST_LENGTH; i++) {
|
||||
[buf appendFormat:@"%02x", (unsigned char)sha2[i]];
|
||||
}
|
||||
|
||||
return buf;
|
||||
NSString *sha256;
|
||||
[self hashSHA1:NULL SHA256:&sha256];
|
||||
return sha256;
|
||||
}
|
||||
|
||||
- (NSString *)machoType {
|
||||
if ([self isDylib]) { return @"Dynamic Library"; }
|
||||
if ([self isKext]) { return @"Kernel Extension"; }
|
||||
if ([self isFat]) { return @"Fat Binary"; }
|
||||
if ([self isMachO]) { return @"Thin Binary"; }
|
||||
if ([self isScript]) { return @"Script"; }
|
||||
return @"Unknown (not executable?)";
|
||||
}
|
||||
#pragma mark File Type Info
|
||||
|
||||
- (NSArray *)architectures {
|
||||
if (![self isMachO]) return nil;
|
||||
return [self.machHeaders allKeys];
|
||||
}
|
||||
|
||||
if ([self isFat]) {
|
||||
NSMutableArray *ret = [[NSMutableArray alloc] init];
|
||||
|
||||
// Retrieve just the fat_header, if possible.
|
||||
NSData *head = [self safeSubdataWithRange:NSMakeRange(0, sizeof(struct fat_header))];
|
||||
if (!head) return nil;
|
||||
struct fat_header *fat_header = (struct fat_header *)[head bytes];
|
||||
|
||||
// Get number of architectures in the binary
|
||||
uint32_t narch = NSSwapBigIntToHost(fat_header->nfat_arch);
|
||||
|
||||
// Retrieve just the fat_arch's, make a mutable copy and if necessary swap the bytes
|
||||
NSData *archs = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
|
||||
sizeof(struct fat_arch) * narch)];
|
||||
if (!archs) return nil;
|
||||
struct fat_arch *fat_archs = (struct fat_arch *)[archs bytes];
|
||||
|
||||
// For each arch, get the name of it's architecture
|
||||
for (int i = 0; i < narch; ++i) {
|
||||
[ret addObject:[self nameForCPUType:NSSwapBigIntToHost(fat_archs[i].cputype)]];
|
||||
}
|
||||
return ret;
|
||||
} else {
|
||||
struct mach_header *hdr = [self firstMachHeader];
|
||||
return @[ [self nameForCPUType:hdr->cputype] ];
|
||||
}
|
||||
return nil;
|
||||
- (BOOL)isExecutable {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (mach_header && mach_header->filetype == MH_EXECUTE) return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isDylib {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_DYLIB ||
|
||||
mach_header->filetype == MH_FVMLIB) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (mach_header && mach_header->filetype == MH_DYLIB) return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isKext {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_KEXT_BUNDLE) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
if (mach_header && mach_header->filetype == MH_KEXT_BUNDLE) return YES;
|
||||
return NO;
|
||||
}
|
||||
|
||||
- (BOOL)isMachO {
|
||||
return ([self.fileData length] >= 160 &&
|
||||
([self isMachHeader:(struct mach_header *)[self.fileData bytes]] || [self isFat]));
|
||||
return (self.machHeaders.count > 0);
|
||||
}
|
||||
|
||||
- (BOOL)isFat {
|
||||
return ([self isFatHeader:(struct fat_header *)[self.fileData bytes]]);
|
||||
return (self.machHeaders.count > 1);
|
||||
}
|
||||
|
||||
- (BOOL)isScript {
|
||||
if ([self.fileData length] < 1) return NO;
|
||||
|
||||
char magic[2];
|
||||
[self.fileData getBytes:&magic length:2];
|
||||
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 2)] bytes];
|
||||
return (strncmp("#!", magic, 2) == 0);
|
||||
}
|
||||
|
||||
- (BOOL)isExecutable {
|
||||
struct mach_header *mach_header = [self firstMachHeader];
|
||||
if (!mach_header) return NO;
|
||||
if (mach_header->filetype == MH_OBJECT ||
|
||||
mach_header->filetype == MH_EXECUTE ||
|
||||
mach_header->filetype == MH_PRELOAD) {
|
||||
return YES;
|
||||
}
|
||||
|
||||
return NO;
|
||||
- (BOOL)isXARArchive {
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(0, 4)] bytes];
|
||||
return (strncmp("xar!", magic, 4) == 0);
|
||||
}
|
||||
|
||||
# pragma mark Bundle Information
|
||||
- (BOOL)isDMG {
|
||||
NSUInteger last512 = self.fileSize - 512;
|
||||
const char *magic = (const char *)[[self safeSubdataWithRange:NSMakeRange(last512, 4)] bytes];
|
||||
return (magic && strncmp("koly", magic, 4) == 0);
|
||||
}
|
||||
|
||||
#pragma mark Page Zero
|
||||
|
||||
- (BOOL)isMissingPageZero {
|
||||
// This method only checks i386 arch because the kernel enforces this for other archs
|
||||
// See bsd/kern/mach_loader.c, search for enforce_hard_pagezero.
|
||||
MachHeaderWithOffset *x86Header = self.machHeaders[[self nameForCPUType:CPU_TYPE_X86]];
|
||||
if (!x86Header) return NO;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)[x86Header.data bytes];
|
||||
if (mh->filetype != MH_EXECUTE) return NO;
|
||||
|
||||
NSRange range = NSMakeRange(x86Header.offset + sizeof(struct mach_header),
|
||||
sizeof(struct segment_command));
|
||||
NSData *lcData = [self safeSubdataWithRange:range];
|
||||
if (!lcData) return NO;
|
||||
|
||||
// This code assumes the __PAGEZERO is always the first load-command in the file.
|
||||
// Given that the OS X ABI says "the static linker creates a __PAGEZERO segment
|
||||
// as the first segment of an executable file." this should be OK.
|
||||
struct load_command *lc = (struct load_command *)[lcData bytes];
|
||||
if (lc->cmd == LC_SEGMENT) {
|
||||
struct segment_command *segment = (struct segment_command *)lc;
|
||||
if (segment->vmaddr == 0 && segment->vmsize != 0 &&
|
||||
segment->initprot == 0 && segment->maxprot == 0 &&
|
||||
strcmp("__PAGEZERO", segment->segname) == 0) {
|
||||
return NO;
|
||||
}
|
||||
}
|
||||
return YES;
|
||||
}
|
||||
|
||||
#pragma mark Bundle Information
|
||||
|
||||
///
|
||||
/// Try and determine the bundle that the represented executable is contained within, if any.
|
||||
///
|
||||
/// Rationale: An NSBundle has a method executablePath for discovering the main binary within a
|
||||
/// bundle but provides no way to get an NSBundle object when only the executablePath is known. Also,
|
||||
/// a bundle can contain multiple binaries within the MacOS folder and we want any of these to count
|
||||
/// as being part of the bundle.
|
||||
/// bundle but provides no way to get an NSBundle object when only the executablePath is known.
|
||||
/// Also a bundle can contain multiple binaries within the MacOS folder and we want any of these
|
||||
/// to count as being part of the bundle.
|
||||
///
|
||||
/// This method relies on executable bundles being laid out as follows:
|
||||
///
|
||||
@@ -206,21 +267,19 @@
|
||||
/// NSBundle reference for Bundle.app.
|
||||
///
|
||||
- (NSBundle *)bundle {
|
||||
if (self.bundleRef) return self.bundleRef;
|
||||
if (!self.bundleRef) {
|
||||
self.bundleRef = (NSBundle *)[NSNull null];
|
||||
|
||||
NSArray *pathComponents = [self.path pathComponents];
|
||||
// Check that the full path is at least 4-levels deep:
|
||||
// e.g: /Calendar.app/Contents/MacOS/Calendar
|
||||
NSArray *pathComponents = [self.path pathComponents];
|
||||
if ([pathComponents count] < 4) return nil;
|
||||
|
||||
// Check that the full path is at least 4-levels deep:
|
||||
// e.g: /Calendar.app/Contents/MacOS/Calendar
|
||||
if ([pathComponents count] < 4) return nil;
|
||||
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
|
||||
self.bundleRef = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
|
||||
// Clear the bundle if it doesn't have a bundle ID
|
||||
if (![self.bundleRef objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = nil;
|
||||
|
||||
return self.bundleRef;
|
||||
pathComponents = [pathComponents subarrayWithRange:NSMakeRange(0, [pathComponents count] - 3)];
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:[NSString pathWithComponents:pathComponents]];
|
||||
if (bndl && [bndl objectForInfoDictionaryKey:@"CFBundleIdentifier"]) self.bundleRef = bndl;
|
||||
}
|
||||
return self.bundleRef == (NSBundle *)[NSNull null] ? nil : self.bundleRef;
|
||||
}
|
||||
|
||||
- (NSString *)bundlePath {
|
||||
@@ -228,116 +287,289 @@
|
||||
}
|
||||
|
||||
- (NSDictionary *)infoPlist {
|
||||
if (self.infoDict) return self.infoDict;
|
||||
if (!self.infoDict) {
|
||||
NSDictionary *d = [self embeddedPlist];
|
||||
if (d) {
|
||||
self.infoDict = d;
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
if ([self bundle]) {
|
||||
self.infoDict = [[self bundle] infoDictionary];
|
||||
return self.infoDict;
|
||||
d = self.bundle.infoDictionary;
|
||||
if (d) {
|
||||
self.infoDict = d;
|
||||
return self.infoDict;
|
||||
}
|
||||
|
||||
self.infoDict = (NSDictionary *)[NSNull null];
|
||||
}
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path isDirectory:NO];
|
||||
self.infoDict =
|
||||
(__bridge_transfer NSDictionary*)CFBundleCopyInfoDictionaryForURL((__bridge CFURLRef) url);
|
||||
return self.infoDict;
|
||||
return self.infoDict == (NSDictionary *)[NSNull null] ? nil : self.infoDict;
|
||||
}
|
||||
|
||||
- (NSString *)bundleIdentifier {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleIdentifier"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleIdentifier"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleName {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleName"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleDisplayName"] description] ?:
|
||||
[[self.infoPlist objectForKey:@"CFBundleName"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleVersion {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleVersion"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleVersion"] description];
|
||||
}
|
||||
|
||||
- (NSString *)bundleShortVersionString {
|
||||
return [[self infoPlist] objectForKey:@"CFBundleShortVersionString"];
|
||||
return [[self.infoPlist objectForKey:@"CFBundleShortVersionString"] description];
|
||||
}
|
||||
|
||||
- (NSArray *)downloadURLs {
|
||||
char *path = (char *)[self.path fileSystemRepresentation];
|
||||
size_t size = getxattr(path, "com.apple.metadata:kMDItemWhereFroms", NULL, 0, 0, 0);
|
||||
char *value = malloc(size);
|
||||
if (!value) return nil;
|
||||
#pragma mark Quarantine Data
|
||||
|
||||
if (getxattr(path, "com.apple.metadata:kMDItemWhereFroms", value, size, 0, 0) == -1) {
|
||||
free(value);
|
||||
return nil;
|
||||
- (NSString *)quarantineDataURL {
|
||||
NSURL *dataURL = [self quarantineData][@"LSQuarantineDataURL"];
|
||||
if (dataURL == (NSURL *)[NSNull null]) dataURL = nil;
|
||||
return [dataURL absoluteString];
|
||||
}
|
||||
|
||||
- (NSString *)quarantineRefererURL {
|
||||
NSURL *originURL = [self quarantineData][@"LSQuarantineOriginURL"];
|
||||
if (originURL == (NSURL *)[NSNull null]) originURL = nil;
|
||||
return [originURL absoluteString];
|
||||
}
|
||||
|
||||
- (NSString *)quarantineAgentBundleID {
|
||||
NSString *agentBundle = [self quarantineData][@"LSQuarantineAgentBundleIdentifier"];
|
||||
if (agentBundle == (NSString *)[NSNull null]) agentBundle = nil;
|
||||
return agentBundle;
|
||||
}
|
||||
|
||||
- (NSDate *)quarantineTimestamp {
|
||||
NSDate *timeStamp = [self quarantineData][@"LSQuarantineTimeStamp"];
|
||||
return timeStamp;
|
||||
}
|
||||
|
||||
#pragma mark Internal Methods
|
||||
|
||||
- (NSDictionary *)machHeaders {
|
||||
if (self.cachedHeaders) return self.cachedHeaders;
|
||||
|
||||
// Sanity check file length
|
||||
if (self.fileSize < sizeof(struct mach_header)) {
|
||||
self.cachedHeaders = [NSDictionary dictionary];
|
||||
return self.cachedHeaders;
|
||||
}
|
||||
|
||||
NSData *data = [NSData dataWithBytes:value length:size];
|
||||
free(value);
|
||||
NSMutableDictionary *machHeaders = [NSMutableDictionary dictionary];
|
||||
|
||||
if (data) {
|
||||
NSArray *urls = [NSPropertyListSerialization propertyListWithData:data
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:NULL];
|
||||
return urls;
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:NSMakeRange(0,
|
||||
4096)]];
|
||||
if (machHeader) {
|
||||
struct mach_header *mh = (struct mach_header *)[machHeader bytes];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader offset:0];
|
||||
machHeaders[[self nameForCPUType:mh->cputype]] = mhwo;
|
||||
} else {
|
||||
NSRange range = NSMakeRange(0, sizeof(struct fat_header));
|
||||
NSData *fatHeader = [self safeSubdataWithRange:range];
|
||||
struct fat_header *fh = (struct fat_header *)[fatHeader bytes];
|
||||
|
||||
if (fatHeader && (fh->magic == FAT_MAGIC || fh->magic == FAT_CIGAM)) {
|
||||
int nfat_arch = OSSwapBigToHostInt32(fh->nfat_arch);
|
||||
range = NSMakeRange(sizeof(struct fat_header), sizeof(struct fat_arch) * nfat_arch);
|
||||
NSMutableData *fatArchs = [[self safeSubdataWithRange:range] mutableCopy];
|
||||
if (fatArchs) {
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[fatArchs mutableBytes];
|
||||
for (int i = 0; i < nfat_arch; ++i) {
|
||||
int offset = OSSwapBigToHostInt32(fat_arch[i].offset);
|
||||
int size = OSSwapBigToHostInt32(fat_arch[i].size);
|
||||
int cputype = OSSwapBigToHostInt(fat_arch[i].cputype);
|
||||
|
||||
range = NSMakeRange(offset, size);
|
||||
NSData *machHeader = [self parseSingleMachHeader:[self safeSubdataWithRange:range]];
|
||||
if (machHeader) {
|
||||
NSString *key = [self nameForCPUType:cputype];
|
||||
MachHeaderWithOffset *mhwo = [[MachHeaderWithOffset alloc] initWithData:machHeader
|
||||
offset:offset];
|
||||
machHeaders[key] = mhwo;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
self.cachedHeaders = [machHeaders copy];
|
||||
return self.cachedHeaders;
|
||||
}
|
||||
|
||||
- (NSData *)parseSingleMachHeader:(NSData *)inputData {
|
||||
if (inputData.length < sizeof(struct mach_header)) return nil;
|
||||
struct mach_header *mh = (struct mach_header *)[inputData bytes];
|
||||
|
||||
if (mh->magic == MH_CIGAM || mh->magic == MH_CIGAM_64) {
|
||||
NSMutableData *mutableInput = [inputData mutableCopy];
|
||||
mh = (struct mach_header *)[mutableInput mutableBytes];
|
||||
swap_mach_header(mh, NXHostByteOrder());
|
||||
}
|
||||
|
||||
if (mh->magic == MH_MAGIC || mh->magic == MH_MAGIC_64) {
|
||||
return [NSData dataWithBytes:mh length:sizeof(struct mach_header)];
|
||||
}
|
||||
|
||||
return nil;
|
||||
}
|
||||
|
||||
# pragma mark Internal Methods
|
||||
///
|
||||
/// Locate an embedded plist in the file
|
||||
///
|
||||
- (NSDictionary *)embeddedPlist {
|
||||
// Look for an embedded Info.plist if there is one.
|
||||
// This could (and used to) use CFBundleCopyInfoDictionaryForURL but that uses mmap to read
|
||||
// the file and so can cause SIGBUS if the file is deleted/truncated while it's working.
|
||||
MachHeaderWithOffset *mhwo = [[self.machHeaders allValues] firstObject];
|
||||
if (!mhwo) return nil;
|
||||
|
||||
struct mach_header *mh = (struct mach_header *)mhwo.data.bytes;
|
||||
if (mh->filetype != MH_EXECUTE) return self.infoDict;
|
||||
BOOL is64 = (mh->magic == MH_MAGIC_64 || mh->magic == MH_CIGAM_64);
|
||||
uint32_t ncmds = mh->ncmds;
|
||||
uint32_t nsects = 0;
|
||||
uint64_t offset = mhwo.offset;
|
||||
|
||||
uint32_t sz_header = is64 ? sizeof(struct mach_header_64) : sizeof(struct mach_header);
|
||||
uint32_t sz_segment = is64 ? sizeof(struct segment_command_64) : sizeof(struct segment_command);
|
||||
uint32_t sz_section = is64 ? sizeof(struct section_64) : sizeof(struct section);
|
||||
|
||||
offset += sz_header;
|
||||
|
||||
// Loop through the load commands looking for the segment named __TEXT
|
||||
for (uint32_t i = 0; i < ncmds; ++i) {
|
||||
NSData *cmdData = [self safeSubdataWithRange:NSMakeRange(offset, sz_segment)];
|
||||
if (!cmdData) return nil;
|
||||
struct segment_command_64 *lc = (struct segment_command_64 *)[cmdData bytes];
|
||||
if (lc->cmd == LC_SEGMENT || lc->cmd == LC_SEGMENT_64) {
|
||||
if (strncmp(lc->segname, "__TEXT", 6) == 0) {
|
||||
nsects = lc->nsects;
|
||||
offset += sz_segment;
|
||||
break;
|
||||
}
|
||||
}
|
||||
offset += lc->cmdsize;
|
||||
}
|
||||
|
||||
// Loop through the sections in the __TEXT segment looking for an __info_plist section.
|
||||
for (uint32_t i = 0; i < nsects; ++i) {
|
||||
NSData *sectData = [self safeSubdataWithRange:NSMakeRange(offset, sz_section)];
|
||||
if (!sectData) return nil;
|
||||
struct section_64 *sect = (struct section_64 *)[sectData bytes];
|
||||
if (sect && strncmp(sect->sectname, "__info_plist", 12) == 0 && sect->size < 2000000) {
|
||||
NSData *plistData = [self safeSubdataWithRange:NSMakeRange(sect->offset, sect->size)];
|
||||
if (!plistData) return nil;
|
||||
NSDictionary *plist;
|
||||
plist = [NSPropertyListSerialization propertyListWithData:plistData
|
||||
options:NSPropertyListImmutable
|
||||
format:NULL
|
||||
error:NULL];
|
||||
if (plist) return plist;
|
||||
}
|
||||
offset += sz_section;
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
///
|
||||
/// Look through the file for the first mach_header. If the file is thin, this will be the
|
||||
/// header at the beginning of the file. If the file is fat, it will be the first
|
||||
/// architecture-specific header.
|
||||
/// Return the first mach_header in this file.
|
||||
///
|
||||
- (struct mach_header *)firstMachHeader {
|
||||
if (![self isMachO]) return NULL;
|
||||
|
||||
struct mach_header *mach_header = (struct mach_header *)[self.fileData bytes];
|
||||
struct fat_header *fat_header = (struct fat_header *)[self.fileData bytes];
|
||||
|
||||
if ([self isFatHeader:fat_header]) {
|
||||
// Get the bytes for the fat_arch
|
||||
NSData *archHdr = [self safeSubdataWithRange:NSMakeRange(sizeof(struct fat_header),
|
||||
sizeof(struct fat_arch))];
|
||||
if (!archHdr) return nil;
|
||||
struct fat_arch *fat_arch = (struct fat_arch *)[archHdr bytes];
|
||||
|
||||
// Get bytes for first mach_header
|
||||
NSData *machHdr = [self safeSubdataWithRange:NSMakeRange(NSSwapBigIntToHost(fat_arch->offset),
|
||||
sizeof(struct mach_header))];
|
||||
if (!machHdr) return nil;
|
||||
mach_header = (struct mach_header *)[machHdr bytes];
|
||||
}
|
||||
|
||||
if ([self isMachHeader:mach_header]) {
|
||||
return mach_header;
|
||||
}
|
||||
|
||||
return NULL;
|
||||
}
|
||||
|
||||
- (BOOL)isMachHeader:(struct mach_header *)header {
|
||||
return (header->magic == MH_MAGIC || header->magic == MH_MAGIC_64 ||
|
||||
header->magic == MH_CIGAM || header->magic == MH_CIGAM_64);
|
||||
}
|
||||
|
||||
- (BOOL)isFatHeader:(struct fat_header *)header {
|
||||
return (header->magic == FAT_MAGIC || header->magic == FAT_CIGAM);
|
||||
return (struct mach_header *)([[[[self.machHeaders allValues] firstObject] data] bytes]);
|
||||
}
|
||||
|
||||
///
|
||||
/// Wrap @c subdataWithRange: in a @@try/@@catch, returning nil on exception.
|
||||
/// Useful for when the range is beyond the end of the file.
|
||||
/// Extract a range of the file as an NSData, handling any exceptions.
|
||||
/// Returns nil if the requested range is outside of the range of the file.
|
||||
///
|
||||
- (NSData *)safeSubdataWithRange:(NSRange)range {
|
||||
@try {
|
||||
return [self.fileData subdataWithRange:range];
|
||||
if ((range.location + range.length) > self.fileSize) return nil;
|
||||
[self.fileHandle seekToFileOffset:range.location];
|
||||
NSData *d = [self.fileHandle readDataOfLength:range.length];
|
||||
if (d.length != range.length) return nil;
|
||||
return d;
|
||||
}
|
||||
@catch (NSException *exception) {
|
||||
@catch (NSException *e) {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
///
|
||||
/// Retrieve quarantine data for a file and caches the dictionary
|
||||
/// This method attempts to handle fetching the quarantine data even if the running user
|
||||
/// is not the one who downloaded the file.
|
||||
///
|
||||
- (NSDictionary *)quarantineData {
|
||||
if (!self.quarantineDict && self.fileOwnerHomeDir) {
|
||||
self.quarantineDict = (NSDictionary *)[NSNull null];
|
||||
|
||||
NSURL *url = [NSURL fileURLWithPath:self.path];
|
||||
NSDictionary *d = [url resourceValuesForKeys:@[ NSURLQuarantinePropertiesKey ] error:NULL];
|
||||
|
||||
if (d[NSURLQuarantinePropertiesKey]) {
|
||||
d = d[NSURLQuarantinePropertiesKey];
|
||||
|
||||
if (d[@"LSQuarantineIsOwnedByCurrentUser"]) {
|
||||
self.quarantineDict = d;
|
||||
} else if (d[@"LSQuarantineEventIdentifier"]) {
|
||||
NSMutableDictionary *quarantineDict = [d mutableCopy];
|
||||
|
||||
// If self.path is on a quarantine disk image, LSQuarantineDiskImageURL will point to the
|
||||
// disk image and self.fileOwnerHomeDir will be incorrect (probably root).
|
||||
NSString *fileOwnerHomeDir = self.fileOwnerHomeDir;
|
||||
if (d[@"LSQuarantineDiskImageURL"]) {
|
||||
struct stat fileStat;
|
||||
stat([d[@"LSQuarantineDiskImageURL"] fileSystemRepresentation], &fileStat);
|
||||
if (fileStat.st_uid != 0) {
|
||||
struct passwd *pwd = getpwuid(fileStat.st_uid);
|
||||
if (pwd) {
|
||||
fileOwnerHomeDir = @(pwd->pw_dir);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
NSURL *dbPath = [NSURL fileURLWithPathComponents:@[
|
||||
fileOwnerHomeDir,
|
||||
@"Library",
|
||||
@"Preferences",
|
||||
@"com.apple.LaunchServices.QuarantineEventsV2"
|
||||
]];
|
||||
FMDatabase *db = [FMDatabase databaseWithPath:[dbPath absoluteString]];
|
||||
db.logsErrors = NO;
|
||||
if ([db open]) {
|
||||
FMResultSet *rs = [db executeQuery:@"SELECT * FROM LSQuarantineEvent "
|
||||
@"WHERE LSQuarantineEventIdentifier=?",
|
||||
d[@"LSQuarantineEventIdentifier"]];
|
||||
if ([rs next]) {
|
||||
NSString *agentBundleID = [rs stringForColumn:@"LSQuarantineAgentBundleIdentifier"];
|
||||
NSString *dataURLString = [rs stringForColumn:@"LSQuarantineDataURLString"];
|
||||
NSString *originURLString = [rs stringForColumn:@"LSQuarantineOriginURLString"];
|
||||
double timeStamp = [rs doubleForColumn:@"LSQuarantineTimeStamp"];
|
||||
|
||||
quarantineDict[@"LSQuarantineAgentBundleIdentifier"] = agentBundleID;
|
||||
quarantineDict[@"LSQuarantineDataURL"] = [NSURL URLWithString:dataURLString];
|
||||
quarantineDict[@"LSQuarantineOriginURL"] = [NSURL URLWithString:originURLString];
|
||||
quarantineDict[@"LSQuarantineTimestamp"] =
|
||||
[NSDate dateWithTimeIntervalSinceReferenceDate:timeStamp];
|
||||
|
||||
self.quarantineDict = quarantineDict;
|
||||
}
|
||||
[rs close];
|
||||
[db close];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
return (self.quarantineDict == (NSDictionary *)[NSNull null]) ? nil : self.quarantineDict;
|
||||
}
|
||||
|
||||
///
|
||||
/// Return a human-readable string for a cpu_type_t.
|
||||
///
|
||||
- (NSString *)nameForCPUType:(cpu_type_t)cpuType {
|
||||
switch (cpuType) {
|
||||
case CPU_TYPE_X86:
|
||||
@@ -354,4 +586,35 @@
|
||||
return nil;
|
||||
}
|
||||
|
||||
///
|
||||
/// Resolves a given path:
|
||||
/// + Follows symlinks
|
||||
/// + Converts relative paths to absolute
|
||||
/// + If path is a directory, checks to see if that directory is a bundle and if so
|
||||
/// returns the path to that bundles CFBundleExecutable and stores a reference to the
|
||||
/// bundle in the bundle out-param.
|
||||
///
|
||||
- (NSString *)resolvePath:(NSString *)path bundle:(NSBundle **)bundle {
|
||||
// Convert to absolute, standardized path
|
||||
path = [path stringByResolvingSymlinksInPath];
|
||||
if (![path isAbsolutePath]) {
|
||||
NSString *cwd = [[NSFileManager defaultManager] currentDirectoryPath];
|
||||
path = [cwd stringByAppendingPathComponent:path];
|
||||
}
|
||||
path = [path stringByStandardizingPath];
|
||||
|
||||
// Determine if file exists.
|
||||
// If path is actually a directory, check to see if it's a bundle and has a CFBundleExecutable.
|
||||
BOOL directory;
|
||||
if (![[NSFileManager defaultManager] fileExistsAtPath:path isDirectory:&directory]) {
|
||||
return nil;
|
||||
} else if (directory) {
|
||||
NSBundle *bndl = [NSBundle bundleWithPath:path];
|
||||
if (bundle) *bundle = bndl;
|
||||
return [bndl executablePath];
|
||||
} else {
|
||||
return path;
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -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,11 @@
|
||||
/// 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.
|
||||
/// The handler is always be called on the main thread.
|
||||
///
|
||||
/// @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,56 +45,60 @@
|
||||
[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);
|
||||
|
||||
WEAKIFY(self);
|
||||
|
||||
dispatch_source_set_event_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
unsigned long data = dispatch_source_get_data(self.source);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.handler(data);
|
||||
});
|
||||
if (data & DISPATCH_VNODE_DELETE || data & DISPATCH_VNODE_RENAME) {
|
||||
[self stopWatchingFile];
|
||||
[self startWatchingFile];
|
||||
}
|
||||
};
|
||||
});
|
||||
|
||||
self.internalCancelHandler = ^{
|
||||
int fd;
|
||||
dispatch_source_set_registration_handler(self.source, ^{
|
||||
STRONGIFY(self);
|
||||
dispatch_async(dispatch_get_main_queue(), ^{
|
||||
self.handler(0);
|
||||
});
|
||||
});
|
||||
|
||||
if (weakSelf.monitoringSource) {
|
||||
fd = (int)dispatch_source_get_handle(weakSelf.monitoringSource);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
while ((fd = open([weakSelf.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
weakSelf.monitoringSource = dispatch_source_create(
|
||||
DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
|
||||
dispatch_source_set_event_handler(weakSelf.monitoringSource, weakSelf.internalEventHandler);
|
||||
dispatch_source_set_cancel_handler(weakSelf.monitoringSource, weakSelf.internalCancelHandler);
|
||||
dispatch_resume(weakSelf.monitoringSource);
|
||||
|
||||
weakSelf.eventHandler();
|
||||
};
|
||||
|
||||
dispatch_async(queue, self.internalCancelHandler);
|
||||
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, ^{
|
||||
close(fd);
|
||||
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
|
||||
|
||||
@@ -16,11 +16,12 @@
|
||||
/// Common defines between kernel <-> userspace
|
||||
///
|
||||
|
||||
#include <sys/param.h>
|
||||
|
||||
#ifndef SANTA__COMMON__KERNELCOMMON_H
|
||||
#define SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
// Defines the lengths of paths and Vnode IDs passed around.
|
||||
#define MAX_PATH_LEN 1024 // ==PATH_LEN from syslimits.h
|
||||
#define MAX_VNODE_ID_STR 21 // digits in UINT64_MAX + 1 for NULL-terminator
|
||||
|
||||
// Defines the name of the userclient class and the driver bundle ID.
|
||||
@@ -40,34 +41,54 @@ enum SantaDriverMethods {
|
||||
kSantaUserClientNMethods,
|
||||
};
|
||||
|
||||
typedef enum {
|
||||
QUEUETYPE_DECISION,
|
||||
QUEUETYPE_LOG
|
||||
} santa_queuetype_t;
|
||||
|
||||
// Enum defining actions that can be passed down the IODataQueue and in
|
||||
// response methods.
|
||||
typedef enum {
|
||||
ACTION_UNSET = 0,
|
||||
|
||||
// CHECKBW
|
||||
ACTION_REQUEST_CHECKBW = 10,
|
||||
ACTION_RESPOND_CHECKBW_ALLOW = 11,
|
||||
ACTION_RESPOND_CHECKBW_DENY = 12,
|
||||
// REQUESTS
|
||||
ACTION_REQUEST_SHUTDOWN = 10,
|
||||
ACTION_REQUEST_BINARY = 11,
|
||||
|
||||
// SHUTDOWN
|
||||
ACTION_REQUEST_SHUTDOWN = 60,
|
||||
// RESPONSES
|
||||
ACTION_RESPOND_ALLOW = 20,
|
||||
ACTION_RESPOND_DENY = 21,
|
||||
|
||||
// NOTIFY
|
||||
ACTION_NOTIFY_EXEC = 30,
|
||||
ACTION_NOTIFY_WRITE = 31,
|
||||
ACTION_NOTIFY_RENAME = 32,
|
||||
ACTION_NOTIFY_LINK = 33,
|
||||
ACTION_NOTIFY_EXCHANGE = 34,
|
||||
ACTION_NOTIFY_DELETE = 35,
|
||||
|
||||
// ERROR
|
||||
ACTION_ERROR = 99,
|
||||
} santa_action_t;
|
||||
|
||||
#define CHECKBW_RESPONSE_VALID(x) (x == ACTION_RESPOND_CHECKBW_ALLOW || \
|
||||
x == ACTION_RESPOND_CHECKBW_DENY)
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY)
|
||||
|
||||
// Message struct that is sent down the IODataQueue.
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
uint64_t vnode_id;
|
||||
uid_t userId;
|
||||
uid_t uid;
|
||||
gid_t gid;
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
char path[MAX_PATH_LEN];
|
||||
char path[MAXPATHLEN];
|
||||
char newpath[MAXPATHLEN];
|
||||
// For file events, this is the process name.
|
||||
// For exec requests, this is the parent process name.
|
||||
// While process names can technically be 4*MAXPATHLEN, that never
|
||||
// actually happens, so only take MAXPATHLEN and throw away any excess.
|
||||
char pname[MAXPATHLEN];
|
||||
} santa_message_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
|
||||
@@ -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
|
||||
@@ -32,26 +32,29 @@
|
||||
|
||||
#else // KERNEL
|
||||
|
||||
#define LOG_LEVEL_ERROR 1
|
||||
#define LOG_LEVEL_WARN 2
|
||||
#define LOG_LEVEL_INFO 3
|
||||
#define LOG_LEVEL_DEBUG 4
|
||||
typedef enum : NSUInteger {
|
||||
LOG_LEVEL_ERROR,
|
||||
LOG_LEVEL_WARN,
|
||||
LOG_LEVEL_INFO,
|
||||
LOG_LEVEL_DEBUG
|
||||
} LogLevel;
|
||||
|
||||
///
|
||||
/// Logging function.
|
||||
///
|
||||
/// @param level one of the levels defined above
|
||||
/// @param destination a FILE, generally should be stdout or stderr
|
||||
/// @param destination a FILE, generally stdout/stderr. If the file is closed, the log
|
||||
/// will instead be sent to syslog.
|
||||
/// @param format the printf style format string
|
||||
/// @param ... the arguments to format.
|
||||
///
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...);
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...)
|
||||
__attribute__((format(__NSString__, 3, 4)));
|
||||
|
||||
/// Simple logging macros
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__);
|
||||
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__);
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__);
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__);
|
||||
#define LOGD(logFormat, ...) logMessage(LOG_LEVEL_DEBUG, stdout, logFormat, ##__VA_ARGS__)
|
||||
#define LOGI(logFormat, ...) logMessage(LOG_LEVEL_INFO, stdout, logFormat, ##__VA_ARGS__)
|
||||
#define LOGW(logFormat, ...) logMessage(LOG_LEVEL_WARN, stderr, logFormat, ##__VA_ARGS__)
|
||||
#define LOGE(logFormat, ...) logMessage(LOG_LEVEL_ERROR, stderr, logFormat, ##__VA_ARGS__)
|
||||
|
||||
#endif // KERNEL
|
||||
|
||||
|
||||
@@ -14,28 +14,38 @@
|
||||
|
||||
#import "SNTLogging.h"
|
||||
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
|
||||
#ifdef DEBUG
|
||||
static int logLevel = LOG_LEVEL_DEBUG; // default to info
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static int logLevel = LOG_LEVEL_INFO;
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
static NSDateFormatter *dateFormatter;
|
||||
static NSString *binaryName;
|
||||
void syslogClientDestructor(void *arg) {
|
||||
asl_close((aslclient)arg);
|
||||
}
|
||||
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static const char *binaryName;
|
||||
static dispatch_once_t pred;
|
||||
static pthread_key_t syslogKey = 0;
|
||||
|
||||
dispatch_once(&pred, ^{
|
||||
dateFormatter = [[NSDateFormatter alloc] init];
|
||||
[dateFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
||||
[dateFormatter setDateFormat:@"YYYY-MM-dd HH:mm:ss.SSS'Z"];
|
||||
binaryName = [[[NSProcessInfo processInfo] processName] UTF8String];
|
||||
|
||||
binaryName = [[NSProcessInfo processInfo] processName];
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
|
||||
// If debug logging is enabled, the process must be restarted.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--debug"]) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
// If requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"]) {
|
||||
useSyslog = YES;
|
||||
pthread_key_create(&syslogKey, syslogClientDestructor);
|
||||
}
|
||||
});
|
||||
|
||||
if (logLevel < level) return;
|
||||
@@ -45,19 +55,37 @@ void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
NSString *s = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
va_end(args);
|
||||
|
||||
// Only prepend timestamp, severity and binary name if stdout is not a TTY
|
||||
if (isatty(fileno(destination))) {
|
||||
fprintf(destination, "%s\n", [s UTF8String]);
|
||||
} else {
|
||||
NSString *levelName;
|
||||
switch (level) {
|
||||
case LOG_LEVEL_ERROR: levelName = @"E"; break;
|
||||
case LOG_LEVEL_WARN: levelName = @"W"; break;
|
||||
case LOG_LEVEL_INFO: levelName = @"I"; break;
|
||||
case LOG_LEVEL_DEBUG: levelName = @"D"; break;
|
||||
if (useSyslog) {
|
||||
aslclient client = (aslclient)pthread_getspecific(syslogKey);
|
||||
if (client == NULL) {
|
||||
client = asl_open(NULL, "com.google.santa", 0);
|
||||
asl_set_filter(client, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
|
||||
pthread_setspecific(syslogKey, client);
|
||||
}
|
||||
|
||||
fprintf(destination, "%s\n", [[NSString stringWithFormat:@"[%@] %@ %@: %@",
|
||||
[dateFormatter stringFromDate:[NSDate date]], levelName, binaryName, s] UTF8String]);
|
||||
char *levelName;
|
||||
int syslogLevel = ASL_LEVEL_DEBUG;
|
||||
switch (level) {
|
||||
case LOG_LEVEL_ERROR:
|
||||
levelName = "E";
|
||||
syslogLevel = ASL_LEVEL_ERR;
|
||||
break;
|
||||
case LOG_LEVEL_WARN:
|
||||
levelName = "W";
|
||||
syslogLevel = ASL_LEVEL_WARNING;
|
||||
break;
|
||||
case LOG_LEVEL_INFO:
|
||||
levelName = "I";
|
||||
syslogLevel = ASL_LEVEL_INFO;
|
||||
break;
|
||||
case LOG_LEVEL_DEBUG:
|
||||
levelName = "D";
|
||||
syslogLevel = ASL_LEVEL_DEBUG;
|
||||
break;
|
||||
}
|
||||
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName, [s UTF8String]);
|
||||
} else {
|
||||
fprintf(destination, "%s\n", [s UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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.
|
||||
@@ -22,29 +22,29 @@
|
||||
///
|
||||
/// The hash of the object this rule is for
|
||||
///
|
||||
@property NSString *shasum;
|
||||
@property(copy) NSString *shasum;
|
||||
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
@property NSString *customMsg;
|
||||
@property(copy) NSString *customMsg;
|
||||
|
||||
///
|
||||
/// 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,12 +17,12 @@
|
||||
@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) {
|
||||
_shasum = shasum;
|
||||
_shasum = shasum;
|
||||
_state = state;
|
||||
_type = type;
|
||||
_customMsg = customMsg;
|
||||
@@ -34,11 +34,10 @@
|
||||
|
||||
#define ENCODE(obj, key) if (obj) [coder encodeObject:obj forKey:key]
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding { return YES; }
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.shasum, @"shasum");
|
||||
@@ -58,4 +57,28 @@
|
||||
return self;
|
||||
}
|
||||
|
||||
#undef DECODE
|
||||
#undef ENCODE
|
||||
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (other == self) return YES;
|
||||
if (![other isKindOfClass:[SNTRule class]]) return NO;
|
||||
SNTRule *o = other;
|
||||
return ([self.shasum isEqual:o.shasum] && self.state == o.state && self.type == o.type);
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
NSUInteger prime = 31;
|
||||
NSUInteger result = 1;
|
||||
result = prime * result + [self.shasum hash];
|
||||
result = prime * result + self.state;
|
||||
result = prime * result + self.type;
|
||||
return result;
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTRule: SHA-256: %@, State: %ld, Type: %ld",
|
||||
self.shasum, self.state, self.type];
|
||||
}
|
||||
|
||||
@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"
|
||||
|
||||
///
|
||||
/// 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.
|
||||
///
|
||||
@@ -55,7 +61,7 @@
|
||||
@property NSString *fileBundleVersionString;
|
||||
|
||||
///
|
||||
/// If the executed file was signed, this is an NSArray of SNTCertificate's
|
||||
/// If the executed file was signed, this is an NSArray of MOLCertificate's
|
||||
/// representing the signing chain.
|
||||
///
|
||||
@property NSArray *signingChain;
|
||||
@@ -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.
|
||||
@@ -95,4 +101,17 @@
|
||||
///
|
||||
@property NSNumber *ppid;
|
||||
|
||||
///
|
||||
/// The name of the parent process.
|
||||
///
|
||||
@property NSString *parentName;
|
||||
|
||||
///
|
||||
/// Quarantine data about the executed file, if any.
|
||||
///
|
||||
@property NSString *quarantineDataURL;
|
||||
@property NSString *quarantineRefererURL;
|
||||
@property NSDate *quarantineTimestamp;
|
||||
@property NSString *quarantineAgentBundleID;
|
||||
|
||||
@end
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
#import "MOLCertificate.h"
|
||||
|
||||
@implementation SNTStoredEvent
|
||||
|
||||
@@ -22,9 +22,11 @@
|
||||
#define DECODE(cls, key) [decoder decodeObjectOfClass:[cls class] forKey:key]
|
||||
#define DECODEARRAY(cls, key) \
|
||||
[decoder decodeObjectOfClasses:[NSSet setWithObjects:[NSArray class], [cls class], nil] \
|
||||
forKey:key]
|
||||
forKey:key]
|
||||
|
||||
+ (BOOL)supportsSecureCoding { return YES; }
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.idx, @"idx");
|
||||
@@ -32,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");
|
||||
@@ -43,9 +46,15 @@
|
||||
ENCODE(@(self.decision), @"decision");
|
||||
ENCODE(self.pid, @"pid");
|
||||
ENCODE(self.ppid, @"ppid");
|
||||
ENCODE(self.parentName, @"parentName");
|
||||
|
||||
ENCODE(self.loggedInUsers, @"loggedInUsers");
|
||||
ENCODE(self.currentSessions, @"currentSessions");
|
||||
|
||||
ENCODE(self.quarantineDataURL, @"quarantineDataURL");
|
||||
ENCODE(self.quarantineRefererURL, @"quarantineRefererURL");
|
||||
ENCODE(self.quarantineTimestamp, @"quarantineTimestamp");
|
||||
ENCODE(self.quarantineAgentBundleID, @"quarantineAgentBundleID");
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
@@ -56,29 +65,36 @@
|
||||
_filePath = DECODE(NSString, @"filePath");
|
||||
|
||||
_fileBundleName = DECODE(NSString, @"fileBundleName");
|
||||
_fileBundlePath = DECODE(NSString, @"fileBundlePath");
|
||||
_fileBundleID = DECODE(NSString, @"fileBundleID");
|
||||
_fileBundleVersion = DECODE(NSString, @"fileBundleVersion");
|
||||
_fileBundleVersionString = DECODE(NSString, @"fileBundleVersionString");
|
||||
|
||||
_signingChain = DECODEARRAY(SNTCertificate, @"signingChain");
|
||||
_signingChain = DECODEARRAY(MOLCertificate, @"signingChain");
|
||||
|
||||
_executingUser = DECODE(NSString, @"executingUser");
|
||||
_occurrenceDate = DECODE(NSDate, @"occurrenceDate");
|
||||
_decision = [DECODE(NSNumber, @"decision") intValue];
|
||||
_decision = (SNTEventState)[DECODE(NSNumber, @"decision") intValue];
|
||||
_pid = DECODE(NSNumber, @"pid");
|
||||
_ppid = DECODE(NSNumber, @"ppid");
|
||||
_parentName = DECODE(NSString, @"parentName");
|
||||
|
||||
_loggedInUsers = DECODEARRAY(NSString, @"loggedInUsers");
|
||||
_currentSessions = DECODEARRAY(NSString, @"currentSessions");
|
||||
|
||||
_quarantineDataURL = DECODE(NSString, @"quarantineDataURL");
|
||||
_quarantineRefererURL = DECODE(NSString, @"quarantineRefererURL");
|
||||
_quarantineTimestamp = DECODE(NSDate, @"quarantineTimestamp");
|
||||
_quarantineAgentBundleID = DECODE(NSString, @"quarantineAgentBundleID");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (BOOL)isEqual:(SNTStoredEvent *)other {
|
||||
- (BOOL)isEqual:(id)other {
|
||||
if (other == self) return YES;
|
||||
if (![other isKindOfClass:[SNTStoredEvent class]]) return NO;
|
||||
return ([self.fileSHA256 isEqual:other.fileSHA256] &&
|
||||
[self.idx isEqual:other.idx]);
|
||||
SNTStoredEvent *o = other;
|
||||
return ([self.fileSHA256 isEqual:o.fileSHA256] && [self.idx isEqual:o.idx]);
|
||||
}
|
||||
|
||||
- (NSUInteger)hash {
|
||||
@@ -91,8 +107,8 @@
|
||||
}
|
||||
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@",
|
||||
self.idx, self.fileSHA256];
|
||||
return
|
||||
[NSString stringWithFormat:@"SNTStoredEvent[%@] with SHA-256: %@", self.idx, self.fileSHA256];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
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);
|
||||
@@ -21,11 +21,8 @@
|
||||
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
if (!platformExpert) return nil;
|
||||
|
||||
NSString *serial = CFBridgingRelease(
|
||||
IORegistryEntryCreateCFProperty(platformExpert,
|
||||
CFSTR(kIOPlatformSerialNumberKey),
|
||||
kCFAllocatorDefault,
|
||||
0));
|
||||
NSString *serial = CFBridgingRelease(IORegistryEntryCreateCFProperty(
|
||||
platformExpert, CFSTR(kIOPlatformSerialNumberKey), kCFAllocatorDefault, 0));
|
||||
|
||||
IOObjectRelease(platformExpert);
|
||||
|
||||
@@ -37,10 +34,8 @@
|
||||
kIOMasterPortDefault, IOServiceMatching("IOPlatformExpertDevice"));
|
||||
if (!platformExpert) return nil;
|
||||
|
||||
NSString *uuid = CFBridgingRelease(
|
||||
IORegistryEntryCreateCFProperty(platformExpert,
|
||||
CFSTR(kIOPlatformUUIDKey),
|
||||
kCFAllocatorDefault, 0));
|
||||
NSString *uuid = CFBridgingRelease(IORegistryEntryCreateCFProperty(
|
||||
platformExpert, CFSTR(kIOPlatformUUIDKey), kCFAllocatorDefault, 0));
|
||||
|
||||
IOObjectRelease(platformExpert);
|
||||
|
||||
@@ -65,11 +60,11 @@
|
||||
return @(hostname);
|
||||
}
|
||||
|
||||
# pragma mark - Internal
|
||||
#pragma mark - Internal
|
||||
|
||||
+ (NSDictionary *)_systemVersionDictionary {
|
||||
return [NSDictionary dictionaryWithContentsOfFile:
|
||||
@"/System/Library/CoreServices/SystemVersion.plist"];
|
||||
return [NSDictionary
|
||||
dictionaryWithContentsOfFile:@"/System/Library/CoreServices/SystemVersion.plist"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,108 +12,115 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// A validating XPC connection/listener which uses codesigning to validate that both ends of the
|
||||
/// connection were signed by the same certificate chain.
|
||||
///
|
||||
/// Example server started by @c launchd where the @c launchd job has a @c MachServices key:
|
||||
///
|
||||
/// @code
|
||||
/// SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
|
||||
/// conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
/// conn.exportedObject = myObject;
|
||||
/// conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
|
||||
/// [conn resume];
|
||||
/// @endcode
|
||||
///
|
||||
/// Example client, connecting to above server:
|
||||
///
|
||||
/// @code
|
||||
/// SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer"
|
||||
/// withOptions:0];
|
||||
/// conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyClientProtocol)];
|
||||
/// conn.exportedObject = myObject;
|
||||
/// conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
/// conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
|
||||
/// [conn resume];
|
||||
/// @endcode
|
||||
///
|
||||
/// Either side can then send a message to the other with:
|
||||
///
|
||||
/// @code
|
||||
/// [conn.remoteObjectProxy selectorInRemoteInterface];
|
||||
/// @endcode
|
||||
///
|
||||
/// @note messages are always delivered on a background thread!
|
||||
///
|
||||
/**
|
||||
A wrapper around NSXPCListener and NSXPCConnection to provide client multiplexing, signature
|
||||
validation of connecting clients and forced connection establishment.
|
||||
|
||||
Example server started by @c launchd where the @c launchd job has a @c MachServices key:
|
||||
|
||||
@code
|
||||
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initServerWithName:@"MyServer"];
|
||||
conn.exportedInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
conn.exportedObject = myObject;
|
||||
[conn resume];
|
||||
@endcode
|
||||
|
||||
Example client, connecting to above server:
|
||||
|
||||
@code
|
||||
SNTXPCConnection *conn = [[SNTXPCConnection alloc] initClientWithName:"MyServer"
|
||||
withOptions:0];
|
||||
conn.remoteInterface = [NSXPCInterface interfaceWithProtocol:@protocol(MyServerProtocol)];
|
||||
conn.invalidationHandler = ^{ NSLog(@"Connection invalidated") };
|
||||
[conn resume];
|
||||
@endcode
|
||||
|
||||
The client can send a message to the server with:
|
||||
|
||||
@code
|
||||
[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>
|
||||
|
||||
typedef void (^SNTXPCInvalidationBlock)(void);
|
||||
typedef void (^SNTXPCAcceptedBlock)(void);
|
||||
typedef void (^SNTXPCRejectedBlock)(void);
|
||||
/**
|
||||
Initialize a new server with a given listener, provided by `[NSXPCListener anonymousListener]`.
|
||||
*/
|
||||
- (nullable instancetype)initServerWithListener:(nonnull NSXPCListener *)listener;
|
||||
|
||||
///
|
||||
/// The interface the remote object should conform to.
|
||||
///
|
||||
@property(retain) NSXPCInterface *remoteInterface;
|
||||
/**
|
||||
Initializer for the 'server' side of the connection, started by launchd.
|
||||
|
||||
///
|
||||
/// A proxy to the object at the other end of the connection.
|
||||
///
|
||||
/// @warning Do not send a message to this object if you didn't set @c remoteInterface above
|
||||
/// before calling the @c resume method. Doing so will throw an exception.
|
||||
///
|
||||
@property(readonly) id remoteObjectProxy;
|
||||
@param name MachService name, must match the MachServices key in the launchd.plist
|
||||
*/
|
||||
- (nullable instancetype)initServerWithName:(nonnull NSString *)name;
|
||||
|
||||
///
|
||||
/// The interface this object exports.
|
||||
///
|
||||
@property(retain) NSXPCInterface *exportedInterface;
|
||||
/**
|
||||
Initializer a new client to a service exported by a LaunchDaemon.
|
||||
|
||||
///
|
||||
/// The object that responds to messages from the other end.
|
||||
///
|
||||
@property(retain) id exportedObject;
|
||||
@param name MachService name
|
||||
@param privileged Use YES if the server is running as root.
|
||||
*/
|
||||
- (nullable instancetype)initClientWithName:(nonnull NSString *)name privileged:(BOOL)privileged;
|
||||
|
||||
///
|
||||
/// A block to run when the connection is invalidated.
|
||||
///
|
||||
@property(copy) SNTXPCInvalidationBlock invalidationHandler;
|
||||
/**
|
||||
Initialize a new client with a listener endpoint sent from another process.
|
||||
|
||||
///
|
||||
/// A block to run when the connection has been accepted.
|
||||
///
|
||||
@property(copy) SNTXPCAcceptedBlock acceptedHandler;
|
||||
@param listener An NSXPCListenerEndpoint to connect to.
|
||||
*/
|
||||
- (nullable instancetype)initClientWithListener:(nonnull NSXPCListenerEndpoint *)listener;
|
||||
|
||||
///
|
||||
/// A block to run when the connection has been rejected.
|
||||
///
|
||||
@property(copy) SNTXPCRejectedBlock rejectedHandler;
|
||||
|
||||
///
|
||||
/// Initializer for the 'server' side of the connection, the binary that was started by launchd.
|
||||
///
|
||||
/// @param name MachService name
|
||||
///
|
||||
- (instancetype)initServerWithName:(NSString *)name;
|
||||
|
||||
///
|
||||
/// Initializer for the 'client' side of the connection.
|
||||
///
|
||||
/// @param name MachService name
|
||||
/// @param options Use NSXPCConnectionPrivileged if the server is running as root, otherwise use 0.
|
||||
///
|
||||
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options;
|
||||
|
||||
///
|
||||
/// Call when the properties of the object have been set-up and you're ready for connections.
|
||||
/// Blocks the executing thread for up to 5s while waiting for the verification to complete.
|
||||
///
|
||||
/**
|
||||
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;
|
||||
|
||||
///
|
||||
/// Invalidate the connection. This must be done before the connection can be released.
|
||||
///
|
||||
/**
|
||||
Invalidate the connection(s). This must be done before the object can be released.
|
||||
*/
|
||||
- (void)invalidate;
|
||||
|
||||
/**
|
||||
The interface the remote object should conform to. (client)
|
||||
*/
|
||||
@property(retain, nullable) NSXPCInterface *remoteInterface;
|
||||
|
||||
/**
|
||||
A proxy to the object at the other end of the connection. (client)
|
||||
|
||||
@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, nullable) NSXPCInterface *exportedInterface;
|
||||
|
||||
/**
|
||||
The object that responds to messages from the other end. (server)
|
||||
*/
|
||||
@property(retain, nullable) id exportedObject;
|
||||
|
||||
/**
|
||||
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
|
||||
|
||||
@@ -14,55 +14,83 @@
|
||||
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
#import "SNTCodesignChecker.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
|
||||
@protocol XPCConnectionValidityRequest
|
||||
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block;
|
||||
#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 (used on server-side only).
|
||||
///
|
||||
/// The XPC listener (server only).
|
||||
@property NSXPCListener *listenerObject;
|
||||
|
||||
///
|
||||
/// The current connection object.
|
||||
///
|
||||
/// The current connection object (client only).
|
||||
@property NSXPCConnection *currentConnection;
|
||||
|
||||
///
|
||||
/// The remote interface to use while the connection hasn't been validated.
|
||||
///
|
||||
@property NSXPCInterface *validatorInterface;
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTXPCConnection
|
||||
|
||||
#pragma mark Initializers
|
||||
- (instancetype)initServerWithName:(NSString *)name {
|
||||
|
||||
- (instancetype)initServerWithListener:(NSXPCListener *)listener {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
|
||||
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
|
||||
_listenerObject = [[NSXPCListener alloc] initWithMachServiceName:name];
|
||||
|
||||
if (!_validatorInterface || !_listenerObject) return nil;
|
||||
_listenerObject = listener;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initClientWithName:(NSString *)name options:(NSXPCConnectionOptions)options {
|
||||
- (instancetype)initServerWithName:(NSString *)name {
|
||||
return [self initServerWithListener:[[NSXPCListener alloc] initWithMachServiceName:name]];
|
||||
}
|
||||
|
||||
- (instancetype)initClientWithListener:(NSXPCListenerEndpoint *)listener {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
Protocol *validatorProtocol = @protocol(XPCConnectionValidityRequest);
|
||||
_validatorInterface = [NSXPCInterface interfaceWithProtocol:validatorProtocol];
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name
|
||||
options:options];
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithListenerEndpoint:listener];
|
||||
if (!_currentConnection) return nil;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
if (!_validatorInterface || !_currentConnection) return nil;
|
||||
- (instancetype)initClientWithName:(NSString *)name privileged:(BOOL)privileged {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
NSXPCConnectionOptions options = (privileged ? NSXPCConnectionPrivileged : 0);
|
||||
_currentConnection = [[NSXPCConnection alloc] initWithMachServiceName:name options:options];
|
||||
if (!_currentConnection) return nil;
|
||||
_validationInterface =
|
||||
[NSXPCInterface interfaceWithProtocol:@protocol(SNTXPCConnectionProtocol)];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
@@ -75,148 +103,88 @@
|
||||
#pragma mark Connection set-up
|
||||
|
||||
- (void)resume {
|
||||
if (_listenerObject) {
|
||||
// A new listener doesn't do anything until a client connects.
|
||||
if (self.listenerObject) {
|
||||
self.listenerObject.delegate = self;
|
||||
[self.listenerObject resume];
|
||||
} else {
|
||||
// A new client begins the validation process.
|
||||
NSXPCConnection *connection = _currentConnection;
|
||||
WEAKIFY(self);
|
||||
|
||||
connection.remoteObjectInterface = _validatorInterface;
|
||||
|
||||
connection.invalidationHandler = ^{
|
||||
[self invokeInvalidationHandler];
|
||||
self.currentConnection = nil;
|
||||
};
|
||||
|
||||
connection.interruptionHandler = ^{
|
||||
[self.currentConnection invalidate];
|
||||
};
|
||||
|
||||
[connection resume];
|
||||
|
||||
__block BOOL verificationComplete = NO;
|
||||
[[connection remoteObjectProxy] isConnectionValidWithBlock:^void(BOOL response) {
|
||||
pid_t pid = self.currentConnection.processIdentifier;
|
||||
|
||||
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
|
||||
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
|
||||
|
||||
if (response && [otherCS signingInformationMatches:selfCS]) {
|
||||
[self.currentConnection suspend];
|
||||
self.currentConnection.remoteObjectInterface = self.remoteInterface;
|
||||
self.currentConnection.exportedInterface = self.exportedInterface;
|
||||
self.currentConnection.exportedObject = self.exportedObject;
|
||||
[self invokeAcceptedHandler];
|
||||
[self.currentConnection resume];
|
||||
verificationComplete = YES;
|
||||
} else {
|
||||
[self invokeRejectedHandler];
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
verificationComplete = YES;
|
||||
}
|
||||
// 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();
|
||||
}];
|
||||
|
||||
// Wait for validation to complete, at most 5s
|
||||
for (int sleepLoops = 0; sleepLoops < 1000 && !verificationComplete; sleepLoops++) {
|
||||
usleep(5000);
|
||||
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];
|
||||
}
|
||||
if (!verificationComplete) [self invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)listener:(NSXPCListener *)listener shouldAcceptNewConnection:(NSXPCConnection *)connection {
|
||||
// Reject connection if a connection already exists. As the invalidation/interruption handlers
|
||||
// both cause the currentConnection to be nil'd out, this should be OK.
|
||||
if (self.currentConnection) return NO;
|
||||
pid_t pid = connection.processIdentifier;
|
||||
MOLCodesignChecker *otherCS = [[MOLCodesignChecker alloc] initWithPID:pid];
|
||||
if (![otherCS signingInformationMatches:[[MOLCodesignChecker alloc] initWithSelf]]) {
|
||||
return NO;
|
||||
}
|
||||
|
||||
connection.exportedObject = self;
|
||||
connection.exportedInterface = _validatorInterface;
|
||||
// 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];
|
||||
|
||||
connection.invalidationHandler = ^{
|
||||
[self invokeInvalidationHandler];
|
||||
self.currentConnection = nil;
|
||||
// The connection is now established.
|
||||
if (self.acceptedHandler) self.acceptedHandler();
|
||||
};
|
||||
|
||||
connection.interruptionHandler = ^{
|
||||
// Invalidate the connection, causing the handler above to run
|
||||
[self.currentConnection invalidate];
|
||||
};
|
||||
|
||||
// At this point the client is connected and can send messages but the only message it can send
|
||||
// is isConnectionValidWithBlock: and we won't send anything to it until it has.
|
||||
self.currentConnection = connection;
|
||||
|
||||
connection.exportedInterface = self.validationInterface;
|
||||
connection.exportedObject = ci;
|
||||
[connection resume];
|
||||
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)isConnectionValidWithBlock:(void (^)(BOOL))block {
|
||||
pid_t pid = self.currentConnection.processIdentifier;
|
||||
|
||||
SNTCodesignChecker *selfCS = [[SNTCodesignChecker alloc] initWithSelf];
|
||||
SNTCodesignChecker *otherCS = [[SNTCodesignChecker alloc] initWithPID:pid];
|
||||
|
||||
if ([otherCS signingInformationMatches:selfCS]) {
|
||||
[self.currentConnection suspend];
|
||||
self.currentConnection.remoteObjectInterface = self.remoteInterface;
|
||||
self.currentConnection.exportedInterface = self.exportedInterface;
|
||||
self.currentConnection.exportedObject = self.exportedObject;
|
||||
[self.currentConnection resume];
|
||||
|
||||
[self invokeAcceptedHandler];
|
||||
|
||||
// Let remote end know that we accepted. In acception this must come last otherwise
|
||||
// the remote end might start sending messages before the interface is fully set-up.
|
||||
block(YES);
|
||||
} else {
|
||||
// Let remote end know that we rejected. In rejection this must come first otherwise
|
||||
// the connection is invalidated before the client ever realizes.
|
||||
block(NO);
|
||||
|
||||
[self invokeRejectedHandler];
|
||||
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (id)remoteObjectProxy {
|
||||
if (self.currentConnection && self.currentConnection.remoteObjectInterface) {
|
||||
if (self.currentConnection.remoteObjectInterface &&
|
||||
self.currentConnection.remoteObjectInterface != self.validationInterface) {
|
||||
return [self.currentConnection remoteObjectProxyWithErrorHandler:^(NSError *error) {
|
||||
[self.currentConnection invalidate];
|
||||
[self.currentConnection invalidate];
|
||||
}];
|
||||
}
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (void)invokeAcceptedHandler {
|
||||
if (self.acceptedHandler) {
|
||||
self.acceptedHandler();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invokeRejectedHandler {
|
||||
if (self.rejectedHandler) {
|
||||
self.rejectedHandler();
|
||||
}
|
||||
}
|
||||
|
||||
- (void)invokeInvalidationHandler {
|
||||
if (self.invalidationHandler) {
|
||||
self.invalidationHandler();
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark Connection tear-down
|
||||
|
||||
- (void)invalidate {
|
||||
if (self.currentConnection) {
|
||||
[self.currentConnection invalidate];
|
||||
self.currentConnection = nil;
|
||||
} else if (self.listenerObject) {
|
||||
[self.listenerObject invalidate];
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -12,10 +12,11 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
@class SNTStoredEvent;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
///
|
||||
/// Protocol implemented by santad and utilized by santactl
|
||||
@@ -25,26 +26,42 @@
|
||||
///
|
||||
/// Kernel ops
|
||||
///
|
||||
- (void)cacheCount:(void (^)(uint64_t))reply;
|
||||
- (void)cacheCount:(void (^)(int64_t))reply;
|
||||
- (void)flushCache:(void (^)(BOOL))reply;
|
||||
|
||||
///
|
||||
/// Database ops
|
||||
///
|
||||
- (void)databaseRuleCounts:(void (^)(uint64_t binary, uint64_t certificate))reply;
|
||||
- (void)databaseRuleAddRule:(SNTRule *)rule withReply:(void (^)())reply;
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules withReply:(void (^)())reply;
|
||||
- (void)databaseRuleCounts:(void (^)(int64_t binary, int64_t certificate))reply;
|
||||
- (void)databaseRuleAddRules:(NSArray *)rules
|
||||
cleanSlate:(BOOL)cleanSlate
|
||||
reply:(void (^)(NSError *error))reply;
|
||||
|
||||
- (void)databaseEventCount:(void (^)(uint64_t count))reply;
|
||||
- (void)databaseEventForSHA256:(NSString *)sha256 withReply:(void (^)(SNTStoredEvent *))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;
|
||||
|
||||
///
|
||||
/// Misc ops
|
||||
/// Config ops
|
||||
///
|
||||
- (void)clientMode:(void (^)(santa_clientmode_t))reply;
|
||||
- (void)setClientMode:(santa_clientmode_t)mode withReply:(void (^)())reply;
|
||||
- (void)watchdogInfo:(void (^)(uint64_t, uint64_t, double, double))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;
|
||||
- (void)setWhitelistPathRegex:(NSString *)pattern reply:(void (^)())reply;
|
||||
- (void)setBlacklistPathRegex:(NSString *)pattern reply:(void (^)())reply;
|
||||
|
||||
///
|
||||
/// GUI Ops
|
||||
///
|
||||
- (void)setNotificationListener:(NSXPCListenerEndpoint *)listener;
|
||||
|
||||
@end
|
||||
|
||||
@@ -61,4 +78,10 @@
|
||||
///
|
||||
+ (NSXPCInterface *)controlInterface;
|
||||
|
||||
///
|
||||
/// Retrieve a pre-configured SNTXPCConnection for communicating with santad.
|
||||
/// Connections just needs any handlers set and then can be resumed and used.
|
||||
///
|
||||
+ (SNTXPCConnection *)configuredConnection;
|
||||
|
||||
@end
|
||||
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#import "SNTRule.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
@implementation SNTXPCControlInterface
|
||||
|
||||
@@ -32,11 +33,18 @@
|
||||
ofReply:YES];
|
||||
|
||||
[r setClasses:[NSSet setWithObjects:[NSArray class], [SNTRule class], nil]
|
||||
forSelector:@selector(databaseRuleAddRules:withReply:)
|
||||
forSelector:@selector(databaseRuleAddRules:cleanSlate:reply:)
|
||||
argumentIndex:0
|
||||
ofReply:NO];
|
||||
|
||||
return r;
|
||||
}
|
||||
|
||||
+ (SNTXPCConnection *)configuredConnection {
|
||||
SNTXPCConnection *c = [[SNTXPCConnection alloc] initClientWithName:[self serviceId]
|
||||
privileged:YES];
|
||||
c.remoteInterface = [self controlInterface];
|
||||
return c;
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,19 +12,18 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
/// Protocol implemented by SantaNotifier and utilized by santad
|
||||
#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
|
||||
|
||||
///
|
||||
/// @return the MachService ID for this service.
|
||||
///
|
||||
+ (NSString *)serviceId;
|
||||
|
||||
///
|
||||
/// @return an initialized NSXPCInterface for the SNTNotifierXPC protocol.
|
||||
/// Ensures any methods that accept custom classes as arguments are set-up before returning
|
||||
|
||||
@@ -16,13 +16,8 @@
|
||||
|
||||
@implementation SNTXPCNotifierInterface
|
||||
|
||||
+ (NSString *)serviceId {
|
||||
return @"SantaXPCNotifications";
|
||||
}
|
||||
|
||||
+ (NSXPCInterface *)notifierInterface {
|
||||
NSXPCInterface *r = [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
|
||||
return r;
|
||||
return [NSXPCInterface interfaceWithProtocol:@protocol(SNTNotifierXPC)];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@@ -12,20 +12,20 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SantaMessage.h"
|
||||
#include "SantaCachedDecision.h"
|
||||
|
||||
OSDefineMetaClassAndStructors(SantaMessage, OSObject);
|
||||
OSDefineMetaClassAndStructors(SantaCachedDecision, OSObject);
|
||||
|
||||
uint64_t SantaMessage::getMicrosecs() const {
|
||||
uint64_t SantaCachedDecision::getMicrosecs() const {
|
||||
return microsecs_;
|
||||
}
|
||||
|
||||
santa_action_t SantaMessage::getAction() const {
|
||||
santa_action_t SantaCachedDecision::getAction() const {
|
||||
return action_;
|
||||
}
|
||||
|
||||
void SantaMessage::setAction(const santa_action_t action,
|
||||
const uint64_t microsecs) {
|
||||
void SantaCachedDecision::setAction(
|
||||
const santa_action_t action, const uint64_t microsecs) {
|
||||
action_ = action;
|
||||
microsecs_ = microsecs;
|
||||
}
|
||||
@@ -12,19 +12,19 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTAMESSAGE_H
|
||||
#define SANTA__SANTA_DRIVER__SANTAMESSAGE_H
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTACACHEDDECISION_H
|
||||
#define SANTA__SANTA_DRIVER__SANTACACHEDDECISION_H
|
||||
|
||||
#include <libkern/c++/OSObject.h>
|
||||
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
///
|
||||
/// An OSObject wrapper around a @c santa_action_t and a time.
|
||||
/// An OSObject subclass to store a @c santa_action_t and a timestamp.
|
||||
/// Only OSObject subclasses can be inserted into an OSDictionary.
|
||||
///
|
||||
class SantaMessage : public OSObject {
|
||||
OSDeclareDefaultStructors(SantaMessage)
|
||||
class SantaCachedDecision : public OSObject {
|
||||
OSDeclareDefaultStructors(SantaCachedDecision)
|
||||
|
||||
public:
|
||||
// Returns the time the action was last set.
|
||||
@@ -41,4 +41,4 @@ class SantaMessage : public OSObject {
|
||||
uint64_t microsecs_;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTAMESSAGE_H
|
||||
#endif // SANTA__SANTA_DRIVER__SANTACACHEDDECISIONWRAPPER_H
|
||||
@@ -20,89 +20,152 @@ OSDefineMetaClassAndStructors(SantaDecisionManager, OSObject);
|
||||
#pragma mark Object Lifecycle
|
||||
|
||||
bool SantaDecisionManager::init() {
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", lck_grp_attr_alloc_init());
|
||||
dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, lck_attr_alloc_init());
|
||||
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_, lck_attr_alloc_init());
|
||||
if (!super::init()) return false;
|
||||
|
||||
sdm_lock_grp_attr_ = lck_grp_attr_alloc_init();
|
||||
sdm_lock_grp_ = lck_grp_alloc_init("santa-locks", sdm_lock_grp_attr_);
|
||||
|
||||
sdm_lock_attr_ = lck_attr_alloc_init();
|
||||
|
||||
decision_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
log_dataqueue_lock_ = lck_mtx_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
cached_decisions_lock_ = lck_rw_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
vnode_pid_map_lock_ = lck_rw_alloc_init(sdm_lock_grp_, sdm_lock_attr_);
|
||||
|
||||
cached_decisions_ = OSDictionary::withCapacity(1000);
|
||||
vnode_pid_map_ = OSDictionary::withCapacity(1000);
|
||||
|
||||
owning_pid_ = 0;
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(kMaxDecisionQueueEvents,
|
||||
sizeof(santa_message_t));
|
||||
if (!decision_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
return kIOReturnSuccess;
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(kMaxLogQueueEvents,
|
||||
sizeof(santa_message_t));
|
||||
if (!log_dataqueue_) return kIOReturnNoMemory;
|
||||
|
||||
client_pid_ = 0;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::free() {
|
||||
if (cached_decisions_) {
|
||||
cached_decisions_->release();
|
||||
cached_decisions_ = NULL;
|
||||
}
|
||||
|
||||
if (cached_decisions_lock_) {
|
||||
lck_rw_free(cached_decisions_lock_, sdm_lock_grp_);
|
||||
cached_decisions_lock_ = NULL;
|
||||
cached_decisions_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (dataqueue_lock_ ) {
|
||||
lck_mtx_free(dataqueue_lock_, sdm_lock_grp_);
|
||||
dataqueue_lock_ = NULL;
|
||||
if (vnode_pid_map_lock_) {
|
||||
lck_rw_free(vnode_pid_map_lock_, sdm_lock_grp_);
|
||||
vnode_pid_map_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (decision_dataqueue_lock_) {
|
||||
lck_mtx_free(decision_dataqueue_lock_, sdm_lock_grp_);
|
||||
decision_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (log_dataqueue_lock_) {
|
||||
lck_mtx_free(log_dataqueue_lock_, sdm_lock_grp_);
|
||||
log_dataqueue_lock_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_attr_) {
|
||||
lck_attr_free(sdm_lock_attr_);
|
||||
sdm_lock_attr_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_) {
|
||||
lck_grp_free(sdm_lock_grp_);
|
||||
sdm_lock_grp_ = NULL;
|
||||
sdm_lock_grp_ = nullptr;
|
||||
}
|
||||
|
||||
if (sdm_lock_grp_attr_) {
|
||||
lck_grp_attr_free(sdm_lock_grp_attr_);
|
||||
sdm_lock_grp_attr_ = nullptr;
|
||||
}
|
||||
|
||||
OSSafeReleaseNULL(decision_dataqueue_);
|
||||
OSSafeReleaseNULL(log_dataqueue_);
|
||||
OSSafeReleaseNULL(cached_decisions_);
|
||||
OSSafeReleaseNULL(vnode_pid_map_);
|
||||
|
||||
super::free();
|
||||
}
|
||||
|
||||
#pragma mark Client Management
|
||||
|
||||
void SantaDecisionManager::ConnectClient(IOSharedDataQueue *queue, pid_t pid) {
|
||||
void SantaDecisionManager::ConnectClient(pid_t pid) {
|
||||
if (!pid) return;
|
||||
if (!queue) return;
|
||||
|
||||
// Any decisions made while the daemon wasn't
|
||||
// connected should be cleared
|
||||
ClearCache();
|
||||
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
dataqueue_ = queue;
|
||||
dataqueue_->retain();
|
||||
lck_mtx_unlock(dataqueue_lock_);
|
||||
client_pid_ = pid;
|
||||
|
||||
owning_pid_ = pid;
|
||||
owning_proc_ = proc_find(pid);
|
||||
failed_queue_requests_ = 0;
|
||||
failed_decision_queue_requests_ = 0;
|
||||
failed_log_queue_requests_ = 0;
|
||||
}
|
||||
|
||||
void SantaDecisionManager::DisconnectClient() {
|
||||
if (owning_pid_ < 1) return;
|
||||
|
||||
owning_pid_ = -1;
|
||||
void SantaDecisionManager::DisconnectClient(bool itDied) {
|
||||
if (client_pid_ < 1) return;
|
||||
client_pid_ = 0;
|
||||
|
||||
// Ask santad to shutdown, in case it's running.
|
||||
santa_message_t message;
|
||||
message.action = ACTION_REQUEST_SHUTDOWN;
|
||||
message.userId = 0;
|
||||
message.pid = 0;
|
||||
message.ppid = 0;
|
||||
message.vnode_id = 0;
|
||||
PostToQueue(message);
|
||||
if (!itDied) {
|
||||
auto message = new santa_message_t;
|
||||
message->action = ACTION_REQUEST_SHUTDOWN;
|
||||
PostToDecisionQueue(message);
|
||||
delete message;
|
||||
decision_dataqueue_->setNotificationPort(nullptr);
|
||||
} else {
|
||||
// If the client died, reset the data queues so when it reconnects
|
||||
// it doesn't get swamped straight away.
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->release();
|
||||
decision_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxDecisionQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
dataqueue_->release();
|
||||
dataqueue_ = NULL;
|
||||
lck_mtx_unlock(dataqueue_lock_);
|
||||
|
||||
proc_rele(owning_proc_);
|
||||
owning_proc_ = NULL;
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->release();
|
||||
log_dataqueue_ = IOSharedDataQueue::withEntries(
|
||||
kMaxLogQueueEvents, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
}
|
||||
|
||||
bool SantaDecisionManager::ClientConnected() {
|
||||
return owning_pid_ > 0;
|
||||
bool SantaDecisionManager::ClientConnected() const {
|
||||
auto p = proc_find(client_pid_);
|
||||
auto is_exiting = false;
|
||||
if (p) {
|
||||
is_exiting = proc_exiting(p);
|
||||
proc_rele(p);
|
||||
}
|
||||
return (client_pid_ > 0 && !is_exiting);
|
||||
}
|
||||
|
||||
# pragma mark Listener Control
|
||||
void SantaDecisionManager::SetDecisionPort(mach_port_t port) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
decision_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::SetLogPort(mach_port_t port) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
log_dataqueue_->setNotificationPort(port);
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetDecisionMemoryDescriptor() const {
|
||||
return decision_dataqueue_->getMemoryDescriptor();
|
||||
}
|
||||
|
||||
IOMemoryDescriptor *SantaDecisionManager::GetLogMemoryDescriptor() const {
|
||||
return log_dataqueue_->getMemoryDescriptor();
|
||||
}
|
||||
|
||||
#pragma mark Listener Control
|
||||
|
||||
kern_return_t SantaDecisionManager::StartListener() {
|
||||
vnode_listener_ = kauth_listen_scope(KAUTH_SCOPE_VNODE,
|
||||
@@ -110,14 +173,22 @@ kern_return_t SantaDecisionManager::StartListener() {
|
||||
reinterpret_cast<void *>(this));
|
||||
if (!vnode_listener_) return kIOReturnInternalError;
|
||||
|
||||
LOGD("Vnode listener started.");
|
||||
fileop_listener_ = kauth_listen_scope(KAUTH_SCOPE_FILEOP,
|
||||
fileop_scope_callback,
|
||||
reinterpret_cast<void *>(this));
|
||||
if (!fileop_listener_) return kIOReturnInternalError;
|
||||
|
||||
LOGD("Listeners started.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
kern_return_t SantaDecisionManager::StopListener() {
|
||||
kauth_unlisten_scope(vnode_listener_);
|
||||
vnode_listener_ = NULL;
|
||||
vnode_listener_ = nullptr;
|
||||
|
||||
kauth_unlisten_scope(fileop_listener_);
|
||||
fileop_listener_ = nullptr;
|
||||
|
||||
// Wait for any active invocations to finish before returning
|
||||
do {
|
||||
@@ -127,7 +198,7 @@ kern_return_t SantaDecisionManager::StopListener() {
|
||||
// Delete any cached decisions
|
||||
ClearCache();
|
||||
|
||||
LOGD("Vnode listener stopped.");
|
||||
LOGD("Listeners stopped.");
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
@@ -136,39 +207,44 @@ kern_return_t SantaDecisionManager::StopListener() {
|
||||
|
||||
void SantaDecisionManager::AddToCache(
|
||||
const char *identifier, santa_action_t decision, uint64_t microsecs) {
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
|
||||
if (cached_decisions_->getCount() > kMaxCacheSize) {
|
||||
// This could be made a _lot_ smarter, say only removing entries older
|
||||
// than a certain time period. However, with a kMaxCacheSize set
|
||||
// sufficiently large and a kMaxAllowCacheTimeMilliseconds set
|
||||
// sufficiently low, this should only ever occur if someone is purposefully
|
||||
// trying to make the cache grow.
|
||||
LOGD("Cache too large, flushing.");
|
||||
cached_decisions_->flushCollection();
|
||||
LOGI("Cache too large, flushing.");
|
||||
ClearCache();
|
||||
}
|
||||
|
||||
if (decision == ACTION_REQUEST_CHECKBW) {
|
||||
SantaMessage *pending = new SantaMessage();
|
||||
pending->setAction(ACTION_REQUEST_CHECKBW, 0);
|
||||
if (decision == ACTION_REQUEST_BINARY) {
|
||||
auto pending = new SantaCachedDecision();
|
||||
pending->setAction(ACTION_REQUEST_BINARY, 0);
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
cached_decisions_->setObject(identifier, pending);
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
pending->release(); // it was retained when added to the dictionary
|
||||
} else {
|
||||
SantaMessage *pending = OSDynamicCast(
|
||||
SantaMessage, cached_decisions_->getObject(identifier));
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
auto pending = OSDynamicCast(
|
||||
SantaCachedDecision, cached_decisions_->getObject(identifier));
|
||||
if (pending) {
|
||||
pending->setAction(decision, microsecs);
|
||||
}
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
}
|
||||
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
}
|
||||
|
||||
void SantaDecisionManager::CacheCheck(const char *identifier) {
|
||||
lck_rw_lock_shared(cached_decisions_lock_);
|
||||
bool shouldInvalidate = (cached_decisions_->getObject(identifier) != NULL);
|
||||
auto shouldInvalidate = (cached_decisions_->getObject(identifier) != nullptr);
|
||||
if (shouldInvalidate) {
|
||||
lck_rw_lock_shared_to_exclusive(cached_decisions_lock_);
|
||||
if (!lck_rw_lock_shared_to_exclusive(cached_decisions_lock_)) {
|
||||
// shared_to_exclusive will return false if a previous reader upgraded
|
||||
// and if that happens the lock will have been unlocked. If that happens,
|
||||
// which is rare, relock exclusively.
|
||||
lck_rw_lock_exclusive(cached_decisions_lock_);
|
||||
}
|
||||
cached_decisions_->removeObject(identifier);
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
} else {
|
||||
@@ -176,7 +252,7 @@ void SantaDecisionManager::CacheCheck(const char *identifier) {
|
||||
}
|
||||
}
|
||||
|
||||
uint64_t SantaDecisionManager::CacheCount() {
|
||||
uint64_t SantaDecisionManager::CacheCount() const {
|
||||
return cached_decisions_->getCount();
|
||||
}
|
||||
|
||||
@@ -186,29 +262,31 @@ void SantaDecisionManager::ClearCache() {
|
||||
lck_rw_unlock_exclusive(cached_decisions_lock_);
|
||||
}
|
||||
|
||||
#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_);
|
||||
SantaMessage *cached_decision = OSDynamicCast(
|
||||
SantaMessage, cached_decisions_->getObject(identifier));
|
||||
SantaCachedDecision *cached_decision = OSDynamicCast(
|
||||
SantaCachedDecision, cached_decisions_->getObject(identifier));
|
||||
if (cached_decision) {
|
||||
result = cached_decision->getAction();
|
||||
decision_time = cached_decision->getMicrosecs();
|
||||
}
|
||||
lck_rw_unlock_shared(cached_decisions_lock_);
|
||||
|
||||
if (CHECKBW_RESPONSE_VALID(result)) {
|
||||
uint64_t diff_time = GetCurrentUptime();
|
||||
if (RESPONSE_VALID(result)) {
|
||||
auto diff_time = GetCurrentUptime();
|
||||
|
||||
if (result == ACTION_RESPOND_CHECKBW_ALLOW) {
|
||||
if (result == ACTION_RESPOND_ALLOW) {
|
||||
if ((kMaxAllowCacheTimeMilliseconds * 1000) > diff_time) {
|
||||
diff_time = 0;
|
||||
} else {
|
||||
diff_time -= (kMaxAllowCacheTimeMilliseconds * 1000);
|
||||
}
|
||||
} else if (result == ACTION_RESPOND_CHECKBW_DENY) {
|
||||
} else if (result == ACTION_RESPOND_DENY) {
|
||||
if ((kMaxDenyCacheTimeMilliseconds * 1000) > diff_time) {
|
||||
diff_time = 0;
|
||||
} else {
|
||||
@@ -227,116 +305,104 @@ santa_action_t SantaDecisionManager::GetFromCache(const char *identifier) {
|
||||
return result;
|
||||
}
|
||||
|
||||
# pragma mark Queue Management
|
||||
santa_action_t SantaDecisionManager::GetFromDaemon(
|
||||
santa_message_t *message, const char *vnode_id_str) {
|
||||
auto return_action = ACTION_UNSET;
|
||||
|
||||
bool SantaDecisionManager::PostToQueue(santa_message_t message) {
|
||||
lck_mtx_lock(dataqueue_lock_);
|
||||
bool kr = false;
|
||||
if (dataqueue_) {
|
||||
kr = dataqueue_->enqueue(&message, sizeof(message));
|
||||
}
|
||||
lck_mtx_unlock(dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Add pending request to cache.
|
||||
AddToCache(vnode_id_str, ACTION_REQUEST_BINARY, 0);
|
||||
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t credential,
|
||||
const vfs_context_t vfs_context,
|
||||
const vnode_t vnode) {
|
||||
santa_action_t return_action = ACTION_UNSET;
|
||||
|
||||
// Fetch Vnode ID & string
|
||||
uint64_t vnode_id = GetVnodeIDForVnode(vfs_context, vnode);
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
// Check to see if item is in cache
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
|
||||
// If item wasn't in cache, fetch decision from daemon.
|
||||
if (!CHECKBW_RESPONSE_VALID(return_action)) {
|
||||
// Add pending request to cache
|
||||
AddToCache(vnode_id_str, ACTION_REQUEST_CHECKBW, 0);
|
||||
|
||||
// Get path
|
||||
char path[MAX_PATH_LEN];
|
||||
int name_len = MAX_PATH_LEN;
|
||||
if (vn_getpath(vnode, path, &name_len) != 0) {
|
||||
path[0] = '\0';
|
||||
}
|
||||
|
||||
if (!ClientConnected()) {
|
||||
LOGI("Execution request without daemon running: %s", path);
|
||||
AddToCache(vnode_id_str,
|
||||
ACTION_RESPOND_CHECKBW_ALLOW,
|
||||
GetCurrentUptime());
|
||||
return ACTION_RESPOND_CHECKBW_ALLOW;
|
||||
}
|
||||
|
||||
// Prepare to send message to daemon
|
||||
santa_message_t message;
|
||||
strncpy(message.path, path, MAX_PATH_LEN);
|
||||
message.userId = kauth_cred_getuid(credential);
|
||||
message.pid = proc_selfpid();
|
||||
message.ppid = proc_selfppid();
|
||||
message.action = ACTION_REQUEST_CHECKBW;
|
||||
message.vnode_id = vnode_id;
|
||||
|
||||
// Wait for the daemon to respond or die.
|
||||
do {
|
||||
// Send request to daemon...
|
||||
if (!PostToQueue(message)) {
|
||||
OSIncrementAtomic(&failed_queue_requests_);
|
||||
if (failed_queue_requests_ > kMaxQueueFailures) {
|
||||
LOGE("Failed to queue more than %d requests, killing daemon", kMaxQueueFailures);
|
||||
proc_signal(owning_pid_, SIGKILL);
|
||||
}
|
||||
LOGE("Failed to queue request for %s.", path);
|
||||
CacheCheck(vnode_id_str);
|
||||
return ACTION_ERROR;
|
||||
// Send request to daemon...
|
||||
if (!PostToDecisionQueue(message)) {
|
||||
OSIncrementAtomic(&failed_decision_queue_requests_);
|
||||
if (failed_decision_queue_requests_ > kMaxDecisionQueueFailures) {
|
||||
LOGE("Failed to queue more than %d requests, killing daemon",
|
||||
kMaxDecisionQueueFailures);
|
||||
proc_signal(client_pid_, SIGKILL);
|
||||
client_pid_ = 0;
|
||||
}
|
||||
|
||||
// ... and wait for it to respond. If after kRequestLoopSleepMilliseconds
|
||||
// * kMaxRequestLoops it still hasn't responded, send request again.
|
||||
for (int i = 0; i < kMaxRequestLoops; ++i) {
|
||||
IOSleep(kRequestLoopSleepMilliseconds);
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
if (CHECKBW_RESPONSE_VALID(return_action)) break;
|
||||
}
|
||||
} while (!CHECKBW_RESPONSE_VALID(return_action) &&
|
||||
proc_exiting(owning_proc_) == 0);
|
||||
|
||||
// If response is still not valid, the daemon exited
|
||||
if (!CHECKBW_RESPONSE_VALID(return_action)) {
|
||||
LOGE("Daemon process did not respond correctly. Allowing executions "
|
||||
"until it comes back.");
|
||||
LOGE("Failed to queue request for %s.", message->path);
|
||||
CacheCheck(vnode_id_str);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
do {
|
||||
IOSleep(kRequestLoopSleepMilliseconds);
|
||||
return_action = GetFromCache(vnode_id_str);
|
||||
} while (return_action == ACTION_REQUEST_BINARY && ClientConnected());
|
||||
} while (!RESPONSE_VALID(return_action) && ClientConnected());
|
||||
|
||||
// If response is still not valid, the daemon exited
|
||||
if (!RESPONSE_VALID(return_action)) {
|
||||
LOGE("Daemon process did not respond correctly. Allowing executions "
|
||||
"until it comes back. Executable path: %s", message->path);
|
||||
CacheCheck(vnode_id_str);
|
||||
return ACTION_ERROR;
|
||||
}
|
||||
|
||||
return return_action;
|
||||
}
|
||||
|
||||
# pragma mark Misc
|
||||
santa_action_t SantaDecisionManager::FetchDecision(
|
||||
const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str) {
|
||||
if (!ClientConnected()) return ACTION_RESPOND_ALLOW;
|
||||
|
||||
uint64_t SantaDecisionManager::GetVnodeIDForVnode(const vfs_context_t context,
|
||||
const vnode_t vp) {
|
||||
struct vnode_attr vap;
|
||||
VATTR_INIT(&vap);
|
||||
VATTR_WANTED(&vap, va_fileid);
|
||||
vnode_getattr(vp, &vap, context);
|
||||
return vap.va_fileid;
|
||||
// Check to see if item is in cache
|
||||
auto return_action = GetFromCache(vnode_id_str);
|
||||
|
||||
// If item was in cache return it.
|
||||
if (RESPONSE_VALID(return_action)) return return_action;
|
||||
|
||||
// Get path
|
||||
char path[MAXPATHLEN];
|
||||
int name_len = MAXPATHLEN;
|
||||
if (vn_getpath(vp, path, &name_len) != 0) {
|
||||
path[0] = '\0';
|
||||
}
|
||||
|
||||
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));
|
||||
return_action = GetFromDaemon(message, vnode_id_str);
|
||||
delete message;
|
||||
return return_action;
|
||||
}
|
||||
|
||||
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 Misc
|
||||
|
||||
bool SantaDecisionManager::PostToDecisionQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(decision_dataqueue_lock_);
|
||||
auto kr = decision_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
lck_mtx_unlock(decision_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
# pragma mark Invocation Tracking & PID comparison
|
||||
bool SantaDecisionManager::PostToLogQueue(santa_message_t *message) {
|
||||
lck_mtx_lock(log_dataqueue_lock_);
|
||||
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 = 0;
|
||||
log_dataqueue_->dequeue(0, &dataSize);
|
||||
kr = log_dataqueue_->enqueue(message, sizeof(santa_message_t));
|
||||
} else {
|
||||
OSCompareAndSwap(1, 0, &failed_log_queue_requests_);
|
||||
}
|
||||
lck_mtx_unlock(log_dataqueue_lock_);
|
||||
return kr;
|
||||
}
|
||||
|
||||
#pragma mark Invocation Tracking & PID comparison
|
||||
|
||||
void SantaDecisionManager::IncrementListenerInvocations() {
|
||||
OSIncrementAtomic(&listener_invocations_);
|
||||
@@ -346,81 +412,184 @@ void SantaDecisionManager::DecrementListenerInvocations() {
|
||||
OSDecrementAtomic(&listener_invocations_);
|
||||
}
|
||||
|
||||
#pragma mark Callbacks
|
||||
|
||||
int SantaDecisionManager::VnodeCallback(const kauth_cred_t cred,
|
||||
const vfs_context_t ctx,
|
||||
const vnode_t vp,
|
||||
int *errno) {
|
||||
// Only operate on regular files (not directories, symlinks, etc.).
|
||||
if (vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
|
||||
// Get ID for the vnode and convert it to a string.
|
||||
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
|
||||
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
|
||||
// closed.
|
||||
if (vnode_hasdirtyblks(vp)) {
|
||||
CacheCheck(vnode_str);
|
||||
returnedAction = ACTION_RESPOND_DENY;
|
||||
}
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_ALLOW: {
|
||||
auto proc = vfs_context_proc(ctx);
|
||||
if (proc) {
|
||||
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();
|
||||
}
|
||||
return KAUTH_RESULT_ALLOW;
|
||||
}
|
||||
case ACTION_RESPOND_DENY:
|
||||
*errno = EPERM;
|
||||
return KAUTH_RESULT_DENY;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
// we don't break user's machines.
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
}
|
||||
|
||||
void SantaDecisionManager::FileOpCallback(
|
||||
const kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path) {
|
||||
if (vp) {
|
||||
auto context = vfs_context_create(nullptr);
|
||||
auto vnode_id = GetVnodeIDForVnode(context, vp);
|
||||
vfs_context_rele(context);
|
||||
|
||||
if (action == KAUTH_FILEOP_CLOSE) {
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
CacheCheck(vnode_id_str);
|
||||
} else if (action == KAUTH_FILEOP_EXEC) {
|
||||
auto message = NewMessage(nullptr);
|
||||
message->vnode_id = vnode_id;
|
||||
message->action = ACTION_NOTIFY_EXEC;
|
||||
strlcpy(message->path, path, sizeof(message->path));
|
||||
|
||||
char vnode_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_str, MAX_VNODE_ID_STR, "%llu", vnode_id);
|
||||
|
||||
lck_rw_lock_shared(vnode_pid_map_lock_);
|
||||
auto pidWrapper = OSDynamicCast(
|
||||
SantaPIDAndPPID, vnode_pid_map_->getObject(vnode_str));
|
||||
if (pidWrapper) {
|
||||
message->pid = pidWrapper->pid;
|
||||
message->ppid = pidWrapper->ppid;
|
||||
}
|
||||
lck_rw_unlock_shared(vnode_pid_map_lock_);
|
||||
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// 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));
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_CLOSE:
|
||||
message->action = ACTION_NOTIFY_WRITE;
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
message->action = ACTION_NOTIFY_RENAME;
|
||||
break;
|
||||
case KAUTH_FILEOP_LINK:
|
||||
message->action = ACTION_NOTIFY_LINK;
|
||||
break;
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
message->action = ACTION_NOTIFY_EXCHANGE;
|
||||
break;
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
message->action = ACTION_NOTIFY_DELETE;
|
||||
break;
|
||||
default: delete message; return;
|
||||
}
|
||||
|
||||
PostToLogQueue(message);
|
||||
delete message;
|
||||
}
|
||||
}
|
||||
|
||||
#undef super
|
||||
|
||||
#pragma mark Kauth Callback
|
||||
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) {
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
|
||||
vnode_t vp = nullptr;
|
||||
char *path = nullptr;
|
||||
char *new_path = nullptr;
|
||||
|
||||
switch (action) {
|
||||
case KAUTH_FILEOP_CLOSE:
|
||||
if (!(arg2 & KAUTH_FILEOP_CLOSE_MODIFIED)) return KAUTH_RESULT_DEFER;
|
||||
// Intentional fall-through
|
||||
case KAUTH_FILEOP_DELETE:
|
||||
case KAUTH_FILEOP_EXEC:
|
||||
vp = reinterpret_cast<vnode_t>(arg0);
|
||||
if (vp && vnode_vtype(vp) != VREG) return KAUTH_RESULT_DEFER;
|
||||
path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
case KAUTH_FILEOP_RENAME:
|
||||
case KAUTH_FILEOP_EXCHANGE:
|
||||
case KAUTH_FILEOP_LINK:
|
||||
path = reinterpret_cast<char *>(arg0);
|
||||
new_path = reinterpret_cast<char *>(arg1);
|
||||
break;
|
||||
default:
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
sdm->IncrementListenerInvocations();
|
||||
sdm->FileOpCallback(action, vp, path, new_path);
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3) {
|
||||
// The default action is to defer
|
||||
int returnResult = KAUTH_RESULT_DEFER;
|
||||
|
||||
// Cast arguments to correct types
|
||||
if (idata == NULL) {
|
||||
LOGE("Vnode callback established without valid decision manager.");
|
||||
return returnResult;
|
||||
if (action & KAUTH_VNODE_ACCESS ||
|
||||
!(action & KAUTH_VNODE_EXECUTE) ||
|
||||
idata == nullptr) {
|
||||
return KAUTH_RESULT_DEFER;
|
||||
}
|
||||
SantaDecisionManager *sdm = OSDynamicCast(
|
||||
|
||||
auto sdm = OSDynamicCast(
|
||||
SantaDecisionManager, reinterpret_cast<OSObject *>(idata));
|
||||
vfs_context_t vfs_context = reinterpret_cast<vfs_context_t>(arg0);
|
||||
vnode_t vnode = reinterpret_cast<vnode_t>(arg1);
|
||||
|
||||
// Only operate on regular files (not directories, symlinks, etc.)
|
||||
vtype vt = vnode_vtype(vnode);
|
||||
if (vt != VREG) return returnResult;
|
||||
|
||||
// Don't operate on ACCESS events, as they're advisory
|
||||
if (action & KAUTH_VNODE_ACCESS) return returnResult;
|
||||
|
||||
// Filter for only writes
|
||||
if (action & KAUTH_VNODE_WRITE_DATA ||
|
||||
action & KAUTH_VNODE_APPEND_DATA ||
|
||||
action & KAUTH_VNODE_DELETE) {
|
||||
char vnode_id_str[MAX_VNODE_ID_STR];
|
||||
snprintf(vnode_id_str, MAX_VNODE_ID_STR, "%llu",
|
||||
sdm->GetVnodeIDForVnode(vfs_context, vnode));
|
||||
|
||||
// If an execution request is pending, deny write
|
||||
if (sdm->GetFromCache(vnode_id_str) == ACTION_REQUEST_CHECKBW) {
|
||||
LOGD("Denying write due to pending execution: %s", vnode_id_str);
|
||||
*(reinterpret_cast<int *>(arg3)) = EACCES;
|
||||
return KAUTH_RESULT_DENY;
|
||||
}
|
||||
|
||||
// Otherwise remove from cache
|
||||
sdm->CacheCheck(vnode_id_str);
|
||||
|
||||
return returnResult;
|
||||
}
|
||||
|
||||
// Filter for only EXECUTE actions
|
||||
if (action & KAUTH_VNODE_EXECUTE) {
|
||||
sdm->IncrementListenerInvocations();
|
||||
|
||||
// Fetch decision
|
||||
santa_action_t returnedAction = sdm->FetchDecision(
|
||||
credential, vfs_context, vnode);
|
||||
|
||||
switch (returnedAction) {
|
||||
case ACTION_RESPOND_CHECKBW_ALLOW:
|
||||
returnResult = KAUTH_RESULT_ALLOW;
|
||||
break;
|
||||
case ACTION_RESPOND_CHECKBW_DENY:
|
||||
*(reinterpret_cast<int *>(arg3)) = EACCES;
|
||||
returnResult = KAUTH_RESULT_DENY;
|
||||
break;
|
||||
default:
|
||||
// NOTE: Any unknown response or error condition causes us to fail open.
|
||||
// Whilst from a security perspective this is bad, it's important that
|
||||
// we don't break user's machines.
|
||||
break;
|
||||
}
|
||||
|
||||
sdm->DecrementListenerInvocations();
|
||||
|
||||
return returnResult;
|
||||
}
|
||||
|
||||
return returnResult;
|
||||
sdm->IncrementListenerInvocations();
|
||||
int result = sdm->VnodeCallback(credential,
|
||||
reinterpret_cast<vfs_context_t>(arg0),
|
||||
reinterpret_cast<vnode_t>(arg1),
|
||||
reinterpret_cast<int *>(arg3));
|
||||
sdm->DecrementListenerInvocations();
|
||||
return result;
|
||||
}
|
||||
|
||||
@@ -15,50 +15,19 @@
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
#define SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
|
||||
#include <IOKit/IODataQueueShared.h>
|
||||
#include <IOKit/IOLib.h>
|
||||
#include <IOKit/IOMemoryDescriptor.h>
|
||||
#include <IOKit/IOSharedDataQueue.h>
|
||||
#include <libkern/c++/OSDictionary.h>
|
||||
#include <sys/kauth.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "SantaMessage.h"
|
||||
#include "SNTKernelCommon.h"
|
||||
#include "SNTLogging.h"
|
||||
|
||||
///
|
||||
/// The maximum number of milliseconds a cached deny message should be
|
||||
/// considered valid.
|
||||
///
|
||||
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;
|
||||
|
||||
///
|
||||
/// 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;
|
||||
|
||||
///
|
||||
/// While waiting for a response from the daemon, this is the maximum number
|
||||
/// of loops to wait before sending the request again.
|
||||
///
|
||||
const int kMaxRequestLoops = 50;
|
||||
|
||||
///
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
///
|
||||
const int kMaxCacheSize = 10000;
|
||||
|
||||
///
|
||||
/// Maximum number of PostToQueue failures to allow.
|
||||
///
|
||||
const int kMaxQueueFailures = 10;
|
||||
#include "SantaCachedDecision.h"
|
||||
#include "SantaPIDAndPPID.h"
|
||||
|
||||
///
|
||||
/// SantaDecisionManager is responsible for intercepting Vnode execute actions
|
||||
@@ -73,88 +42,243 @@ class SantaDecisionManager : public OSObject {
|
||||
public:
|
||||
/// Used for initialization after instantiation. Required because
|
||||
/// constructors cannot throw inside kernel-space.
|
||||
bool init();
|
||||
bool init() override;
|
||||
|
||||
/// Called automatically when retain count drops to 0.
|
||||
void free();
|
||||
void free() override;
|
||||
|
||||
/// Called by SantaDriverClient when a client connects, providing the data
|
||||
/// queue used to pass messages and the pid of the client process.
|
||||
void ConnectClient(IOSharedDataQueue *queue, pid_t pid);
|
||||
/// Called by SantaDriverClient during connection to provide the shared
|
||||
/// dataqueue memory to the client.
|
||||
IOMemoryDescriptor *GetDecisionMemoryDescriptor() const;
|
||||
IOMemoryDescriptor *GetLogMemoryDescriptor() const;
|
||||
|
||||
/// Called by SantaDriverClient when a client connects to the decision queue,
|
||||
/// providing the pid of the client process.
|
||||
void ConnectClient(pid_t pid);
|
||||
|
||||
/// Called by SantaDriverClient when a client disconnects
|
||||
void DisconnectClient();
|
||||
void DisconnectClient(bool itDied = false);
|
||||
|
||||
/// Returns whether a client is currently connected or not.
|
||||
bool ClientConnected();
|
||||
bool ClientConnected() const;
|
||||
|
||||
/// Starts the kauth listener.
|
||||
/// Sets the Mach port for notifying the decision queue.
|
||||
void SetDecisionPort(mach_port_t port);
|
||||
|
||||
/// Sets the Mach port for notifying the log queue.
|
||||
void SetLogPort(mach_port_t port);
|
||||
|
||||
/// Starts the kauth listeners.
|
||||
kern_return_t StartListener();
|
||||
|
||||
/// Stops the kauth listener. After stopping new callback requests,
|
||||
/// Stops the kauth listeners. After stopping new callback requests,
|
||||
/// waits until all current invocations have finished before clearing the
|
||||
/// cache and returning.
|
||||
kern_return_t StopListener();
|
||||
|
||||
|
||||
/// Adds a decision to the cache, with a timestamp.
|
||||
void AddToCache(const char *identifier,
|
||||
const santa_action_t decision,
|
||||
const uint64_t microsecs);
|
||||
const uint64_t microsecs = GetCurrentUptime());
|
||||
|
||||
/// Checks to see if a given identifier is in the cache and removes it.
|
||||
void CacheCheck(const char *identifier);
|
||||
|
||||
/// Returns the number of entries in the cache.
|
||||
uint64_t CacheCount();
|
||||
uint64_t CacheCount() const;
|
||||
|
||||
/// Clears the cache.
|
||||
void ClearCache();
|
||||
|
||||
/// Increments the count of active callbacks pending.
|
||||
void IncrementListenerInvocations();
|
||||
|
||||
/// Decrements the count of active callbacks pending.
|
||||
void DecrementListenerInvocations();
|
||||
|
||||
///
|
||||
/// Vnode Callback
|
||||
/// @param cred The kauth credential for this request.
|
||||
/// @param ctx The VFS context for this request.
|
||||
/// @param vp The Vnode for this request.
|
||||
/// @param errno A pointer to return an errno style error.
|
||||
/// @return int A valid KAUTH_RESULT_*.
|
||||
///
|
||||
int VnodeCallback(const kauth_cred_t cred, const vfs_context_t ctx,
|
||||
const vnode_t vp, int *errno);
|
||||
///
|
||||
/// FileOp Callback
|
||||
/// @param action The performed action
|
||||
/// @param vp The Vnode for this request. May be nullptr.
|
||||
/// @param path The path being operated on.
|
||||
/// @param new_path The target path for moves and links.
|
||||
///
|
||||
void FileOpCallback(kauth_action_t action, const vnode_t vp,
|
||||
const char *path, const char *new_path);
|
||||
|
||||
protected:
|
||||
///
|
||||
/// The maximum number of milliseconds a cached deny message should be
|
||||
/// considered valid.
|
||||
///
|
||||
static const uint64_t kMaxDenyCacheTimeMilliseconds = 500;
|
||||
|
||||
///
|
||||
/// The maximum number of milliseconds a cached allow message should be
|
||||
/// considered valid.
|
||||
///
|
||||
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.
|
||||
///
|
||||
static const uint32_t kRequestLoopSleepMilliseconds = 10;
|
||||
|
||||
///
|
||||
/// Maximum number of entries in the in-kernel cache.
|
||||
///
|
||||
static const uint32_t kMaxCacheSize = 10000;
|
||||
|
||||
///
|
||||
/// Maximum number of PostToDecisionQueue failures to allow.
|
||||
///
|
||||
static const uint32_t kMaxDecisionQueueFailures = 10;
|
||||
|
||||
///
|
||||
/// The maximum number of messages can be kept in
|
||||
/// the decision data queue at any time.
|
||||
///
|
||||
static const uint32_t kMaxDecisionQueueEvents = 512;
|
||||
|
||||
///
|
||||
/// The maximum number of messages can be kept
|
||||
/// in the logging data queue at any time.
|
||||
///
|
||||
static const uint32_t kMaxLogQueueEvents = 1024;
|
||||
|
||||
/// Fetches a response from the cache, first checking to see if the
|
||||
/// entry has expired.
|
||||
santa_action_t GetFromCache(const char *identifier);
|
||||
|
||||
/// Posts the requested message to the client data queue, if there is one.
|
||||
/// Uses dataqueue_lock_ to ensure two threads don't try to write to the
|
||||
/// queue at the same time.
|
||||
bool PostToQueue(santa_message_t);
|
||||
/// Fetches a response from the daemon. Handles both daemon death
|
||||
/// and failure to post messages to the daemon.
|
||||
///
|
||||
/// @param message The message to send to the daemon
|
||||
/// @param identifier The vnode ID string for this request
|
||||
/// @return santa_action_t The response for this request
|
||||
///
|
||||
santa_action_t GetFromDaemon(santa_message_t *message,
|
||||
const char *identifier);
|
||||
|
||||
///
|
||||
/// Fetches an execution decision for a file, first using the cache and then
|
||||
/// by sending a message to the daemon and waiting until a response arrives.
|
||||
/// If a daemon isn't connected, will allow execution and cache, logging
|
||||
/// the path to the executed file.
|
||||
santa_action_t FetchDecision(const kauth_cred_t credential,
|
||||
const vfs_context_t vfs_context,
|
||||
const vnode_t vnode);
|
||||
///
|
||||
/// @param cred The credential for this request.
|
||||
/// @param vp The Vnode for this request.
|
||||
/// @param vnode_id The ID for this vnode.
|
||||
/// @param vnode_id_str A string representation of the above ID.
|
||||
///
|
||||
santa_action_t FetchDecision(const kauth_cred_t cred,
|
||||
const vnode_t vp,
|
||||
const uint64_t vnode_id,
|
||||
const char *vnode_id_str);
|
||||
|
||||
///
|
||||
/// Posts the requested message to the decision data queue.
|
||||
///
|
||||
/// @param message The message to send
|
||||
/// @return bool true if sending was successful.
|
||||
///
|
||||
bool PostToDecisionQueue(santa_message_t *message);
|
||||
|
||||
///
|
||||
/// Posts the requested message to the logging data queue.
|
||||
///
|
||||
/// @param message The message to send
|
||||
/// @return bool true if sending was successful.
|
||||
///
|
||||
bool PostToLogQueue(santa_message_t *message);
|
||||
|
||||
///
|
||||
/// Fetches the vnode_id for a given vnode.
|
||||
uint64_t GetVnodeIDForVnode(const vfs_context_t context, const vnode_t vp);
|
||||
///
|
||||
/// @param ctx The VFS context to use.
|
||||
/// @param vp The Vnode to get the ID for
|
||||
/// @return uint64_t The Vnode ID as a 64-bit unsigned int.
|
||||
///
|
||||
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.
|
||||
///
|
||||
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
|
||||
uint64_t GetCurrentUptime();
|
||||
|
||||
/// Increments the count of active vnode callback's pending.
|
||||
void IncrementListenerInvocations();
|
||||
|
||||
/// Decrements the count of active vnode callback's pending.
|
||||
void DecrementListenerInvocations();
|
||||
///
|
||||
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_;
|
||||
lck_grp_attr_t *sdm_lock_grp_attr_;
|
||||
|
||||
lck_attr_t *sdm_lock_attr_;
|
||||
lck_rw_t *cached_decisions_lock_;
|
||||
lck_mtx_t *dataqueue_lock_;
|
||||
lck_mtx_t *decision_dataqueue_lock_;
|
||||
lck_mtx_t *log_dataqueue_lock_;
|
||||
lck_rw_t *vnode_pid_map_lock_;
|
||||
|
||||
OSDictionary *cached_decisions_;
|
||||
OSDictionary *vnode_pid_map_;
|
||||
|
||||
IOSharedDataQueue *dataqueue_;
|
||||
SInt32 failed_queue_requests_;
|
||||
IOSharedDataQueue *decision_dataqueue_;
|
||||
IOSharedDataQueue *log_dataqueue_;
|
||||
int32_t failed_decision_queue_requests_;
|
||||
int32_t failed_log_queue_requests_;
|
||||
|
||||
SInt32 listener_invocations_;
|
||||
int32_t listener_invocations_;
|
||||
|
||||
pid_t owning_pid_;
|
||||
proc_t owning_proc_;
|
||||
pid_t client_pid_;
|
||||
|
||||
kauth_listener_t vnode_listener_;
|
||||
kauth_listener_t fileop_listener_;
|
||||
};
|
||||
|
||||
///
|
||||
@@ -164,11 +288,25 @@ class SantaDecisionManager : public OSObject {
|
||||
/// @param action that was requested
|
||||
/// @param VFS context
|
||||
/// @param Vnode being operated on
|
||||
/// @param Parent Vnode. May be NULL.
|
||||
/// @param Parent Vnode. May be nullptr.
|
||||
/// @param Pointer to an errno-style error.
|
||||
///
|
||||
extern "C" int vnode_scope_callback(
|
||||
kauth_cred_t credential, void *idata, kauth_action_t action,
|
||||
uintptr_t arg0, uintptr_t arg1, uintptr_t arg2, uintptr_t arg3);
|
||||
|
||||
///
|
||||
/// The kauth callback function for the FileOp scope
|
||||
/// @param actor's credentials
|
||||
/// @param data that was passed when the listener was registered
|
||||
/// @param action that was requested
|
||||
/// @param depends on action, usually the vnode ref.
|
||||
/// @param depends on action.
|
||||
/// @param depends on action, usually 0.
|
||||
/// @param depends on action, usually 0.
|
||||
///
|
||||
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);
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADECISIONMANAGER_H
|
||||
|
||||
@@ -24,8 +24,12 @@ bool SantaDriver::start(IOService *provider) {
|
||||
if (!super::start(provider)) return false;
|
||||
|
||||
santaDecisionManager = new SantaDecisionManager;
|
||||
santaDecisionManager->init();
|
||||
santaDecisionManager->StartListener();
|
||||
if (!santaDecisionManager->init() ||
|
||||
santaDecisionManager->StartListener() != kIOReturnSuccess) {
|
||||
santaDecisionManager->release();
|
||||
santaDecisionManager = nullptr;
|
||||
return false;
|
||||
}
|
||||
|
||||
registerService();
|
||||
|
||||
@@ -37,14 +41,14 @@ bool SantaDriver::start(IOService *provider) {
|
||||
void SantaDriver::stop(IOService *provider) {
|
||||
santaDecisionManager->StopListener();
|
||||
santaDecisionManager->release();
|
||||
santaDecisionManager = NULL;
|
||||
santaDecisionManager = nullptr;
|
||||
|
||||
LOGI("Unloaded.");
|
||||
|
||||
super::stop(provider);
|
||||
}
|
||||
|
||||
SantaDecisionManager* SantaDriver::GetDecisionManager() {
|
||||
SantaDecisionManager *SantaDriver::GetDecisionManager() const {
|
||||
return santaDecisionManager;
|
||||
}
|
||||
|
||||
|
||||
@@ -18,8 +18,8 @@
|
||||
#include <IOKit/IOService.h>
|
||||
#include <libkern/OSKextLib.h>
|
||||
|
||||
#include "SantaDecisionManager.h"
|
||||
#include "SNTLogging.h"
|
||||
#include "SantaDecisionManager.h"
|
||||
|
||||
///
|
||||
/// The driver class, which provides the start/stop functions and holds
|
||||
@@ -31,17 +31,16 @@ class com_google_SantaDriver : public IOService {
|
||||
|
||||
public:
|
||||
/// Called by the kernel when the kext is loaded
|
||||
bool start(IOService *provider);
|
||||
bool start(IOService *provider) override;
|
||||
|
||||
/// Called by the kernel when the kext is unloaded
|
||||
void stop(IOService *provider);
|
||||
void stop(IOService *provider) override;
|
||||
|
||||
/// Returns a pointer to the SantaDecisionManager created in start().
|
||||
SantaDecisionManager* GetDecisionManager();
|
||||
SantaDecisionManager *GetDecisionManager() const;
|
||||
|
||||
private:
|
||||
SantaDecisionManager *santaDecisionManager;
|
||||
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADRIVER_H
|
||||
|
||||
@@ -25,7 +25,7 @@ OSDefineMetaClassAndStructors(com_google_SantaDriverClient, IOUserClient);
|
||||
bool SantaDriverClient::initWithTask(
|
||||
task_t owningTask, void *securityID, UInt32 type) {
|
||||
if (clientHasPrivilege(
|
||||
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
|
||||
owningTask, kIOClientPrivilegeAdministrator) != KERN_SUCCESS) {
|
||||
LOGW("Unprivileged client attempted to connect.");
|
||||
return false;
|
||||
}
|
||||
@@ -36,72 +36,76 @@ bool SantaDriverClient::initWithTask(
|
||||
}
|
||||
|
||||
bool SantaDriverClient::start(IOService *provider) {
|
||||
fProvider = OSDynamicCast(com_google_SantaDriver, provider);
|
||||
myProvider = OSDynamicCast(com_google_SantaDriver, provider);
|
||||
|
||||
if (!fProvider) return false;
|
||||
if (!myProvider) return false;
|
||||
if (!super::start(provider)) return false;
|
||||
|
||||
fSDM = fProvider->GetDecisionManager();
|
||||
decisionManager = myProvider->GetDecisionManager();
|
||||
if (!decisionManager) return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
void SantaDriverClient::stop(IOService *provider) {
|
||||
super::stop(provider);
|
||||
|
||||
fProvider = NULL;
|
||||
myProvider = nullptr;
|
||||
decisionManager = nullptr;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientClose() {
|
||||
terminate(kIOServiceSynchronous);
|
||||
return kIOReturnSuccess;
|
||||
decisionManager->DisconnectClient(true);
|
||||
return terminate(kIOServiceSynchronous) ? kIOReturnSuccess : kIOReturnError;
|
||||
}
|
||||
|
||||
bool SantaDriverClient::terminate(IOOptionBits options) {
|
||||
fSDM->DisconnectClient();
|
||||
decisionManager->DisconnectClient();
|
||||
LOGI("Client disconnected.");
|
||||
|
||||
fSharedMemory->release();
|
||||
fSharedMemory = NULL;
|
||||
|
||||
fDataQueue->release();
|
||||
fDataQueue = NULL;
|
||||
|
||||
if (fProvider && fProvider->isOpen(this)) fProvider->close(this);
|
||||
if (myProvider && myProvider->isOpen(this)) myProvider->close(this);
|
||||
|
||||
return super::terminate(options);
|
||||
}
|
||||
|
||||
#pragma mark Fetching memory and data queue notifications
|
||||
|
||||
IOReturn SantaDriverClient::registerNotificationPort(mach_port_t port,
|
||||
UInt32 type,
|
||||
UInt32 ref) {
|
||||
if ((!fDataQueue) || (port == MACH_PORT_NULL)) return kIOReturnError;
|
||||
IOReturn SantaDriverClient::registerNotificationPort(
|
||||
mach_port_t port, UInt32 type, UInt32 ref) {
|
||||
if (port == MACH_PORT_NULL) return kIOReturnError;
|
||||
|
||||
fDataQueue->setNotificationPort(port);
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION:
|
||||
decisionManager->SetDecisionPort(port);
|
||||
break;
|
||||
case QUEUETYPE_LOG:
|
||||
decisionManager->SetLogPort(port);
|
||||
break;
|
||||
default:
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clientMemoryForType(UInt32 type,
|
||||
IOOptionBits *options,
|
||||
IOMemoryDescriptor **memory) {
|
||||
*memory = NULL;
|
||||
*options = 0;
|
||||
|
||||
if (type == kIODefaultMemoryType) {
|
||||
if (!fSharedMemory) return kIOReturnNoMemory;
|
||||
fSharedMemory->retain(); // client will decrement this ref
|
||||
*memory = fSharedMemory;
|
||||
|
||||
fSDM->ConnectClient(fDataQueue, proc_selfpid());
|
||||
LOGI("Client connected, PID: %d.", proc_selfpid());
|
||||
|
||||
return kIOReturnSuccess;
|
||||
IOReturn SantaDriverClient::clientMemoryForType(
|
||||
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) {
|
||||
switch (type) {
|
||||
case QUEUETYPE_DECISION:
|
||||
*options = 0;
|
||||
*memory = decisionManager->GetDecisionMemoryDescriptor();
|
||||
decisionManager->ConnectClient(proc_selfpid());
|
||||
break;
|
||||
case QUEUETYPE_LOG:
|
||||
*options = 0;
|
||||
*memory = decisionManager->GetLogMemoryDescriptor();
|
||||
break;
|
||||
default:
|
||||
return kIOReturnBadArgument;
|
||||
}
|
||||
|
||||
return kIOReturnNoMemory;
|
||||
(*memory)->retain();
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
#pragma mark Callable Methods
|
||||
@@ -109,23 +113,11 @@ IOReturn SantaDriverClient::clientMemoryForType(UInt32 type,
|
||||
IOReturn SantaDriverClient::open() {
|
||||
if (isInactive()) return kIOReturnNotAttached;
|
||||
|
||||
if (!fProvider->open(this)) {
|
||||
if (!myProvider->open(this)) {
|
||||
LOGW("A second client tried to connect.");
|
||||
return kIOReturnExclusiveAccess;
|
||||
}
|
||||
|
||||
fDataQueue = IOSharedDataQueue::withCapacity((sizeof(santa_message_t) +
|
||||
DATA_QUEUE_ENTRY_HEADER_SIZE)
|
||||
* kMaxQueueEvents);
|
||||
if (!fDataQueue) return kIOReturnNoMemory;
|
||||
|
||||
fSharedMemory = fDataQueue->getMemoryDescriptor();
|
||||
if (!fSharedMemory) {
|
||||
fDataQueue->release();
|
||||
fDataQueue = NULL;
|
||||
return kIOReturnVMError;
|
||||
}
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
@@ -140,9 +132,7 @@ IOReturn SantaDriverClient::static_open(
|
||||
IOReturn SantaDriverClient::allow_binary(const uint64_t vnode_id) {
|
||||
char vnode_id_str[21];
|
||||
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
|
||||
fSDM->AddToCache(vnode_id_str,
|
||||
ACTION_RESPOND_CHECKBW_ALLOW,
|
||||
fSDM->GetCurrentUptime());
|
||||
decisionManager->AddToCache(vnode_id_str, ACTION_RESPOND_ALLOW);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
@@ -152,16 +142,16 @@ IOReturn SantaDriverClient::static_allow_binary(
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
|
||||
|
||||
return target->allow_binary(
|
||||
*(static_cast<const uint64_t *>(arguments->scalarInput)));
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::deny_binary(const uint64_t vnode_id) {
|
||||
char vnode_id_str[21];
|
||||
snprintf(vnode_id_str, sizeof(vnode_id_str), "%llu", vnode_id);
|
||||
fSDM->AddToCache(vnode_id_str,
|
||||
ACTION_RESPOND_CHECKBW_DENY,
|
||||
fSDM->GetCurrentUptime());
|
||||
decisionManager->AddToCache(vnode_id_str, ACTION_RESPOND_DENY);
|
||||
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
@@ -171,12 +161,14 @@ IOReturn SantaDriverClient::static_deny_binary(
|
||||
void *reference,
|
||||
IOExternalMethodArguments *arguments) {
|
||||
if (!target) return kIOReturnBadArgument;
|
||||
if (arguments->scalarInput == nullptr) return kIOReturnBadArgument;
|
||||
|
||||
return target->deny_binary(
|
||||
*(static_cast<const uint64_t *>(arguments->scalarInput)));
|
||||
static_cast<const uint64_t>(*arguments->scalarInput));
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::clear_cache() {
|
||||
fSDM->ClearCache();
|
||||
decisionManager->ClearCache();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
@@ -189,7 +181,7 @@ IOReturn SantaDriverClient::static_clear_cache(
|
||||
}
|
||||
|
||||
IOReturn SantaDriverClient::cache_count(uint64_t *output) {
|
||||
*output = fSDM->CacheCount();
|
||||
*output = decisionManager->CacheCount();
|
||||
return kIOReturnSuccess;
|
||||
}
|
||||
|
||||
|
||||
@@ -16,20 +16,13 @@
|
||||
#define SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
|
||||
#include <IOKit/IOUserClient.h>
|
||||
#include <IOKit/IOSharedDataQueue.h>
|
||||
#include <IOKit/IOLib.h>
|
||||
#include <IOKit/IODataQueueShared.h>
|
||||
#include <sys/kauth.h>
|
||||
#include <sys/vnode.h>
|
||||
#include <sys/proc.h>
|
||||
#include <sys/vnode.h>
|
||||
|
||||
#include "SNTKernelCommon.h"
|
||||
#include "SantaDecisionManager.h"
|
||||
#include "SantaDriver.h"
|
||||
#include "SantaMessage.h"
|
||||
#include "SNTKernelCommon.h"
|
||||
|
||||
// The maximum number of messages can be kept in the IODataQueue at any time.
|
||||
const int kMaxQueueEvents = 256;
|
||||
|
||||
///
|
||||
/// This class is instantiated by IOKit when a new client process attempts to
|
||||
@@ -39,33 +32,34 @@ const int kMaxQueueEvents = 256;
|
||||
/// Documentation on how the IOUserClient parts of this code work can be found
|
||||
/// here:
|
||||
/// https://developer.apple.com/library/mac/samplecode/SimpleUserClient/Listings/User_Client_Info_txt.html
|
||||
/// https://developer.apple.com/library/mac/documentation/DeviceDrivers/Conceptual/WritingDeviceDriver/WritingDeviceDriver.pdf
|
||||
///
|
||||
class com_google_SantaDriverClient : public IOUserClient {
|
||||
OSDeclareDefaultStructors(com_google_SantaDriverClient);
|
||||
|
||||
public:
|
||||
/// Called as part of IOServiceOpen in clients
|
||||
bool initWithTask(task_t owningTask, void *securityID, UInt32 type);
|
||||
bool initWithTask(task_t owningTask, void *securityID, UInt32 type) override;
|
||||
|
||||
/// Called after initWithTask as part of IOServiceOpen
|
||||
bool start(IOService *provider);
|
||||
bool start(IOService *provider) override;
|
||||
|
||||
/// Called when this class is stopping
|
||||
void stop(IOService *provider);
|
||||
void stop(IOService *provider) override;
|
||||
|
||||
/// Called when a client disconnects
|
||||
IOReturn clientClose();
|
||||
IOReturn clientClose() override;
|
||||
|
||||
/// Called when the driver is shutting down
|
||||
bool terminate(IOOptionBits options);
|
||||
bool terminate(IOOptionBits options) override;
|
||||
|
||||
/// Called in clients with IOConnectSetNotificationPort
|
||||
IOReturn registerNotificationPort(
|
||||
mach_port_t port, UInt32 type, UInt32 refCon);
|
||||
mach_port_t port, UInt32 type, UInt32 refCon) override;
|
||||
|
||||
/// Called in clients with IOConnectMapMemory
|
||||
IOReturn clientMemoryForType(
|
||||
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory);
|
||||
UInt32 type, IOOptionBits *options, IOMemoryDescriptor **memory) override;
|
||||
|
||||
/// Called in clients with IOConnectCallScalarMethod etc. Dispatches
|
||||
/// to the requested selector using the SantaDriverMethods enum in
|
||||
@@ -74,7 +68,7 @@ class com_google_SantaDriverClient : public IOUserClient {
|
||||
UInt32 selector,
|
||||
IOExternalMethodArguments *arguments,
|
||||
IOExternalMethodDispatch *dispatch,
|
||||
OSObject *target, void *reference);
|
||||
OSObject *target, void *reference) override;
|
||||
|
||||
///
|
||||
/// The userpsace callable methods are below. Each method corresponds
|
||||
@@ -82,7 +76,7 @@ class com_google_SantaDriverClient : public IOUserClient {
|
||||
/// which just calls the method on the provided target.
|
||||
///
|
||||
|
||||
/// Called during client connection
|
||||
/// Called during client connection.
|
||||
IOReturn open();
|
||||
static IOReturn static_open(
|
||||
com_google_SantaDriverClient *target,
|
||||
@@ -118,10 +112,8 @@ class com_google_SantaDriverClient : public IOUserClient {
|
||||
IOExternalMethodArguments *arguments);
|
||||
|
||||
private:
|
||||
IOSharedDataQueue *fDataQueue;
|
||||
IOMemoryDescriptor *fSharedMemory;
|
||||
com_google_SantaDriver *fProvider;
|
||||
SantaDecisionManager *fSDM;
|
||||
com_google_SantaDriver *myProvider;
|
||||
SantaDecisionManager *decisionManager;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTADRIVERUSERCLIENT_H
|
||||
|
||||
17
Source/santa-driver/SantaPIDAndPPID.cc
Normal file
@@ -0,0 +1,17 @@
|
||||
/// 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.
|
||||
|
||||
#include "SantaPIDAndPPID.h"
|
||||
|
||||
OSDefineMetaClassAndStructors(SantaPIDAndPPID, OSObject);
|
||||
32
Source/santa-driver/SantaPIDAndPPID.h
Normal file
@@ -0,0 +1,32 @@
|
||||
/// 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.
|
||||
|
||||
#ifndef SANTA__SANTA_DRIVER__SANTAPIDANDPPID_H
|
||||
#define SANTA__SANTA_DRIVER__SANTAPIDANDPPID_H
|
||||
|
||||
#include <libkern/c++/OSObject.h>
|
||||
|
||||
///
|
||||
/// An OSObject wrapper around a PID and PPID.
|
||||
/// Only OSObject subclasses can be inserted into an OSDictionary.
|
||||
///
|
||||
class SantaPIDAndPPID : public OSObject {
|
||||
OSDeclareDefaultStructors(SantaPIDAndPPID)
|
||||
|
||||
public:
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
};
|
||||
|
||||
#endif // SANTA__SANTA_DRIVER__SANTAPIDANDPPID_H
|
||||
231
Source/santactl/Commands/SNTCommandFileInfo.m
Normal file
@@ -0,0 +1,231 @@
|
||||
/// 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 "SNTCommandController.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTCommandFileInfo : NSObject<SNTCommand>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandFileInfo
|
||||
|
||||
REGISTER_COMMAND_NAME(@"fileinfo")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Prints information about a file.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"The details provided will be the same ones Santa uses to make a decision\n"
|
||||
@"about executables. This includes SHA-256, SHA-1, code signing information and\n"
|
||||
@"the type of file.");
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
NSString *filePath = [arguments firstObject];
|
||||
|
||||
if (!filePath) {
|
||||
printf("Missing file path\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SNTFileInfo *fileInfo = [[SNTFileInfo alloc] initWithPath:filePath];
|
||||
if (!fileInfo) {
|
||||
printf("Invalid or empty file\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
|
||||
if (isatty(STDOUT_FILENO)) printf("Hashing...");
|
||||
NSString *sha1, *sha256;
|
||||
[fileInfo hashSHA1:&sha1 SHA256:&sha256];
|
||||
if (isatty(STDOUT_FILENO)) printf("\r");
|
||||
|
||||
[self printKey:@"Path" value:fileInfo.path];
|
||||
[self printKey:@"SHA-256" value:sha256];
|
||||
[self printKey:@"SHA-1" value:sha1];
|
||||
|
||||
if (fileInfo.bundlePath) {
|
||||
[self printKey:@"Bundle Name" value:fileInfo.bundleName];
|
||||
[self printKey:@"Bundle Version" value:fileInfo.bundleVersion];
|
||||
[self printKey:@"Bundle Version Str" value:fileInfo.bundleShortVersionString];
|
||||
}
|
||||
|
||||
if (fileInfo.quarantineDataURL) {
|
||||
[self printKey:@"Download Referer URL" value:fileInfo.quarantineRefererURL];
|
||||
[self printKey:@"Download URL" value:fileInfo.quarantineDataURL];
|
||||
[self printKey:@"Download Timestamp"
|
||||
value:[dateFormatter stringFromDate:fileInfo.quarantineTimestamp]];
|
||||
[self printKey:@"Download Agent" value:fileInfo.quarantineAgentBundleID];
|
||||
}
|
||||
|
||||
NSArray *archs = [fileInfo architectures];
|
||||
if (archs.count == 0) {
|
||||
[self printKey:@"Type" value:[self humanReadableFileType:fileInfo]];
|
||||
exit(0);
|
||||
}
|
||||
|
||||
NSString *s = [NSString stringWithFormat:@"%@ (%@)",
|
||||
[self humanReadableFileType:fileInfo],
|
||||
[archs componentsJoinedByString:@", "]];
|
||||
[self printKey:@"Type" value:s];
|
||||
|
||||
if ([fileInfo isMissingPageZero]) {
|
||||
[self printKey:@"Page Zero" value:@"__PAGEZERO segment missing/bad!"];
|
||||
}
|
||||
|
||||
// Code signature state
|
||||
NSError *error;
|
||||
MOLCodesignChecker *csc = [[MOLCodesignChecker alloc] initWithBinaryPath:filePath error:&error];
|
||||
if (error) {
|
||||
switch (error.code) {
|
||||
case errSecCSUnsigned:
|
||||
[self printKey:@"Code-signed" value:@"No"];
|
||||
break;
|
||||
case errSecCSSignatureFailed:
|
||||
case errSecCSStaticCodeChanged:
|
||||
case errSecCSSignatureNotVerifiable:
|
||||
case errSecCSSignatureUnsupported:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but code/signature changed/unverifiable"];
|
||||
break;
|
||||
case errSecCSResourceDirectoryFailed:
|
||||
case errSecCSResourceNotSupported:
|
||||
case errSecCSResourceRulesInvalid:
|
||||
case errSecCSResourcesInvalid:
|
||||
case errSecCSResourcesNotFound:
|
||||
case errSecCSResourcesNotSealed:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but resources invalid"];
|
||||
break;
|
||||
case errSecCSReqFailed:
|
||||
case errSecCSReqInvalid:
|
||||
case errSecCSReqUnsupported:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but failed requirement validation"];
|
||||
break;
|
||||
case errSecCSInfoPlistFailed:
|
||||
[self printKey:@"Code-signed" value:@"Yes, but can't validate as Info.plist is missing"];
|
||||
break;
|
||||
default: {
|
||||
NSString *val = [NSString stringWithFormat:@"Yes, but failed to validate (%ld)",
|
||||
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"];
|
||||
}
|
||||
|
||||
// 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,
|
||||
unsigned long idx,
|
||||
BOOL *stop) {
|
||||
printf(" %2lu. %-20s: %s\n", idx + 1, "SHA-256", [c.SHA256 UTF8String]);
|
||||
printf(" %-20s: %s\n", "SHA-1", [c.SHA1 UTF8String]);
|
||||
printf(" %-20s: %s\n", "Common Name", [c.commonName UTF8String]);
|
||||
printf(" %-20s: %s\n", "Organization", [c.orgName UTF8String]);
|
||||
printf(" %-20s: %s\n", "Organizational Unit", [c.orgUnit UTF8String]);
|
||||
printf(" %-20s: %s\n", "Valid From",
|
||||
[[dateFormatter stringFromDate:c.validFrom] UTF8String]);
|
||||
printf(" %-20s: %s\n", "Valid Until",
|
||||
[[dateFormatter stringFromDate:c.validUntil] UTF8String]);
|
||||
printf("\n");
|
||||
}];
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
+ (void)printKey:(NSString *)key value:(NSString *)value {
|
||||
if (!key || !value) return;
|
||||
printf("%-21s: %s\n", [key UTF8String], [value UTF8String]);
|
||||
}
|
||||
|
||||
+ (NSString *)humanReadableFileType:(SNTFileInfo *)fi {
|
||||
if ([fi isScript]) return @"Script";
|
||||
if ([fi isExecutable]) return @"Executable";
|
||||
if ([fi isDylib]) return @"Dynamic Library";
|
||||
if ([fi isKext]) return @"Kernel Extension";
|
||||
if ([fi isXARArchive]) return @"XAR Archive";
|
||||
if ([fi isDMG]) return @"Disk Image";
|
||||
return @"Unknown";
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -23,7 +23,9 @@
|
||||
|
||||
@implementation SNTCommandFlushCache
|
||||
|
||||
REGISTER_COMMAND_NAME(@"flushcache");
|
||||
#ifdef DEBUG
|
||||
REGISTER_COMMAND_NAME(@"flushcache")
|
||||
#endif
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return YES;
|
||||
@@ -44,13 +46,13 @@ REGISTER_COMMAND_NAME(@"flushcache");
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
[[daemonConn remoteObjectProxy] flushCache:^(BOOL success) {
|
||||
if (success) {
|
||||
LOGI(@"Cache flush requested");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Cache flush failed");
|
||||
exit(1);
|
||||
}
|
||||
if (success) {
|
||||
LOGI(@"Cache flush requested");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Cache flush failed");
|
||||
exit(1);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
156
Source/santactl/Commands/SNTCommandRule.m
Normal file
@@ -0,0 +1,156 @@
|
||||
/// 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 "SNTCommandController.h"
|
||||
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "MOLCertificate.h"
|
||||
#import "MOLCodesignChecker.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTCommandRule : NSObject<SNTCommand>
|
||||
@property SNTXPCConnection *daemonConn;
|
||||
@end
|
||||
|
||||
@implementation SNTCommandRule
|
||||
|
||||
REGISTER_COMMAND_NAME(@"rule")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Manually add/remove rules.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"Usage: santactl rule [options]\n"
|
||||
@" One of:\n"
|
||||
@" --whitelist: add to whitelist\n"
|
||||
@" --blacklist: add to blacklist\n"
|
||||
@" --silent-blacklist: add to silent blacklist\n"
|
||||
@" --remove: remove existing rule\n"
|
||||
@"\n"
|
||||
@" One of:\n"
|
||||
@" --path {path}: path of binary/bundle to add/remove.\n"
|
||||
@" Will add the hash of the file currently at that path.\n"
|
||||
@" --sha256 {sha256}: hash to add/remove\n"
|
||||
@"\n"
|
||||
@" Optionally:\n"
|
||||
@" --certificate: add certificate rule instead of binary\n"
|
||||
@" --message {message}: custom message\n");
|
||||
}
|
||||
|
||||
+ (void)printErrorUsageAndExit:(NSString *)error {
|
||||
printf("%s\n\n", [error UTF8String]);
|
||||
printf("%s\n", [[self longHelpText] UTF8String]);
|
||||
exit(1);
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
SNTConfigurator *config = [SNTConfigurator configurator];
|
||||
if ([config syncBaseURL] != nil) {
|
||||
printf("SyncBaseURL is set, rules are managed centrally.\n");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.state = SNTRuleStateUnknown;
|
||||
newRule.type = SNTRuleTypeBinary;
|
||||
|
||||
NSString *path;
|
||||
|
||||
// Parse arguments
|
||||
for (NSUInteger i = 0; i < arguments.count; ++i) {
|
||||
NSString *arg = arguments[i];
|
||||
|
||||
if ([arg caseInsensitiveCompare:@"--whitelist"] == NSOrderedSame) {
|
||||
newRule.state = SNTRuleStateWhitelist;
|
||||
} else if ([arg caseInsensitiveCompare:@"--blacklist"] == NSOrderedSame) {
|
||||
newRule.state = SNTRuleStateBlacklist;
|
||||
} else if ([arg caseInsensitiveCompare:@"--silent-blacklist"] == NSOrderedSame) {
|
||||
newRule.state = SNTRuleStateSilentBlacklist;
|
||||
} else if ([arg caseInsensitiveCompare:@"--remove"] == NSOrderedSame) {
|
||||
newRule.state = SNTRuleStateRemove;
|
||||
} else if ([arg caseInsensitiveCompare:@"--certificate"] == NSOrderedSame) {
|
||||
newRule.type = SNTRuleTypeCertificate;
|
||||
} else if ([arg caseInsensitiveCompare:@"--path"] == NSOrderedSame) {
|
||||
if (++i > arguments.count - 1) {
|
||||
[self printErrorUsageAndExit:@"--path requires an argument"];
|
||||
}
|
||||
path = arguments[i];
|
||||
} else if ([arg caseInsensitiveCompare:@"--sha256"] == NSOrderedSame) {
|
||||
if (++i > arguments.count - 1) {
|
||||
[self printErrorUsageAndExit:@"--sha256 requires an argument"];
|
||||
}
|
||||
newRule.shasum = arguments[i];
|
||||
if (newRule.shasum.length != 64) {
|
||||
[self printErrorUsageAndExit:@"--sha256 requires a valid SHA-256 as the argument"];
|
||||
}
|
||||
} else if ([arg caseInsensitiveCompare:@"--message"] == NSOrderedSame) {
|
||||
if (++i > arguments.count - 1) {
|
||||
[self printErrorUsageAndExit:@"--message requires an argument"];
|
||||
}
|
||||
newRule.customMsg = arguments[i];
|
||||
} else {
|
||||
[self printErrorUsageAndExit:[@"Unknown argument: " stringByAppendingString:arg]];
|
||||
}
|
||||
}
|
||||
|
||||
if (path) {
|
||||
SNTFileInfo *fi = [[SNTFileInfo alloc] initWithPath:path];
|
||||
if (newRule.type == SNTRuleTypeBinary) {
|
||||
newRule.shasum = fi.SHA256;
|
||||
} else if (newRule.type == SNTRuleTypeCertificate) {
|
||||
MOLCodesignChecker *cs = [[MOLCodesignChecker alloc] initWithBinaryPath:fi.path];
|
||||
newRule.shasum = cs.leafCertificate.SHA256;
|
||||
}
|
||||
}
|
||||
|
||||
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] 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 == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.shasum UTF8String]);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
}];
|
||||
}
|
||||
|
||||
@end
|
||||
167
Source/santactl/Commands/SNTCommandStatus.m
Normal file
@@ -0,0 +1,167 @@
|
||||
/// 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 "SNTCommandController.h"
|
||||
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTCommandStatus : NSObject<SNTCommand>
|
||||
@end
|
||||
|
||||
@implementation SNTCommandStatus
|
||||
|
||||
REGISTER_COMMAND_NAME(@"status")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Show Santa status information.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return (@"Provides details about Santa while it's running.\n"
|
||||
@" Use --json to output in JSON format");
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
dispatch_group_t group = dispatch_group_create();
|
||||
|
||||
// Daemon status
|
||||
__block NSString *clientMode;
|
||||
__block uint64_t cpuEvents, ramEvents;
|
||||
__block double cpuPeak, ramPeak;
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] clientMode:^(SNTClientMode cm) {
|
||||
switch (cm) {
|
||||
case SNTClientModeMonitor:
|
||||
clientMode = @"Monitor";
|
||||
break;
|
||||
case SNTClientModeLockdown:
|
||||
clientMode = @"Lockdown";
|
||||
break;
|
||||
default:
|
||||
clientMode = [NSString stringWithFormat:@"Unknown (%ld)", cm];
|
||||
break;
|
||||
}
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] watchdogInfo:^(uint64_t wd_cpuEvents, uint64_t wd_ramEvents,
|
||||
double wd_cpuPeak, double wd_ramPeak) {
|
||||
cpuEvents = wd_cpuEvents;
|
||||
cpuPeak = wd_cpuPeak;
|
||||
ramEvents = wd_ramEvents;
|
||||
ramPeak = wd_ramPeak;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
BOOL fileLogging = ([[SNTConfigurator configurator] fileChangesRegex] != nil);
|
||||
|
||||
// Kext status
|
||||
__block int64_t cacheCount = -1;
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] cacheCount:^(int64_t count) {
|
||||
cacheCount = count;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
// Database counts
|
||||
__block int64_t eventCount = -1, binaryRuleCount = -1, certRuleCount = -1;
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] databaseRuleCounts:^(int64_t binary, int64_t certificate) {
|
||||
binaryRuleCount = binary;
|
||||
certRuleCount = certificate;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
dispatch_group_enter(group);
|
||||
[[daemonConn remoteObjectProxy] databaseEventCount:^(int64_t count) {
|
||||
eventCount = count;
|
||||
dispatch_group_leave(group);
|
||||
}];
|
||||
|
||||
// Sync status
|
||||
NSString *syncURLStr = [[[SNTConfigurator configurator] syncBaseURL] absoluteString];
|
||||
NSDateFormatter *dateFormatter = [[NSDateFormatter alloc] init];
|
||||
dateFormatter.dateFormat = @"yyyy/MM/dd HH:mm:ss Z";
|
||||
NSDate *lastSyncSuccess = [[SNTConfigurator configurator] syncLastSuccess];
|
||||
NSString *lastSyncSuccessStr = [dateFormatter stringFromDate:lastSyncSuccess] ?: @"Never";
|
||||
BOOL syncCleanReqd = [[SNTConfigurator configurator] syncCleanRequired];
|
||||
|
||||
// Wait a maximum of 5s for stats collected from daemon to arrive.
|
||||
if (dispatch_group_wait(group, dispatch_time(DISPATCH_TIME_NOW, NSEC_PER_SEC * 5))) {
|
||||
fprintf(stderr, "Failed to retrieve some stats from daemon\n\n");
|
||||
}
|
||||
|
||||
if ([arguments containsObject:@"--json"]) {
|
||||
NSDictionary *stats = @{
|
||||
@"daemon" : @{
|
||||
@"mode" : clientMode,
|
||||
@"file_logging" : @(fileLogging),
|
||||
@"watchdog_cpu_events" : @(cpuEvents),
|
||||
@"watchdog_ram_events" : @(ramEvents),
|
||||
@"watchdog_cpu_peak" : @(cpuPeak),
|
||||
@"watchdog_ram_peak" : @(ramPeak),
|
||||
},
|
||||
@"kernel" : @{
|
||||
@"cache_count" : @(cacheCount),
|
||||
},
|
||||
@"database" : @{
|
||||
@"binary_rules" : @(binaryRuleCount),
|
||||
@"certificate_rules" : @(certRuleCount),
|
||||
@"events_pending_upload" : @(eventCount),
|
||||
},
|
||||
@"sync" : @{
|
||||
@"server" : syncURLStr,
|
||||
@"clean_required" : @(syncCleanReqd),
|
||||
@"last_successful" : lastSyncSuccessStr
|
||||
},
|
||||
};
|
||||
NSData *statsData = [NSJSONSerialization dataWithJSONObject:stats
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:nil];
|
||||
NSString *statsStr = [[NSString alloc] initWithData:statsData encoding:NSUTF8StringEncoding];
|
||||
printf("%s\n", [statsStr UTF8String]);
|
||||
} else {
|
||||
printf(">>> Daemon Info\n");
|
||||
printf(" %-22s | %s\n", "Mode", [clientMode UTF8String]);
|
||||
printf(" %-22s | %s\n", "File Logging", (fileLogging ? "Yes" : "No"));
|
||||
printf(" %-22s | %lld (Peak: %.2f%%)\n", "Watchdog CPU Events", cpuEvents, cpuPeak);
|
||||
printf(" %-22s | %lld (Peak: %.2fMB)\n", "Watchdog RAM Events", ramEvents, ramPeak);
|
||||
printf(">>> Kernel Info\n");
|
||||
printf(" %-22s | %lld\n", "Kernel cache count", cacheCount);
|
||||
printf(">>> Database Info\n");
|
||||
printf(" %-22s | %lld\n", "Binary Rules", binaryRuleCount);
|
||||
printf(" %-22s | %lld\n", "Certificate Rules", certRuleCount);
|
||||
printf(" %-22s | %lld\n", "Events Pending Upload", eventCount);
|
||||
|
||||
if (syncURLStr) {
|
||||
printf(">>> Sync Info\n");
|
||||
printf(" %-22s | %s\n", "Sync Server", [syncURLStr UTF8String]);
|
||||
printf(" %-22s | %s\n", "Clean Sync Required", (syncCleanReqd ? "Yes" : "No"));
|
||||
printf(" %-22s | %s\n", "Last Successful Sync", [lastSyncSuccessStr UTF8String]);
|
||||
}
|
||||
}
|
||||
|
||||
exit(0);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -16,6 +16,7 @@
|
||||
|
||||
#include <IOKit/kext/KextManager.h>
|
||||
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTKernelCommon.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
@@ -25,7 +26,7 @@
|
||||
|
||||
@implementation SNTCommandVersion
|
||||
|
||||
REGISTER_COMMAND_NAME(@"version");
|
||||
REGISTER_COMMAND_NAME(@"version")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
@@ -40,29 +41,43 @@ REGISTER_COMMAND_NAME(@"version");
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
return nil;
|
||||
return (@"Show versions of all Santa components.\n"
|
||||
@" Use --json to output in JSON format.");
|
||||
}
|
||||
|
||||
+ (void)runWithArguments:(NSArray *)arguments daemonConnection:(SNTXPCConnection *)daemonConn {
|
||||
printf("%-15s | %s\n", "santa-driver", [[self santaKextVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santad", [[self santadVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santactl", [[self santactlVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "SantaGUI", [[self santaAppVersion] UTF8String]);
|
||||
if ([arguments containsObject:@"--json"]) {
|
||||
NSDictionary *versions = @{
|
||||
@"santa-driver" : [self santaKextVersion],
|
||||
@"santad" : [self santadVersion],
|
||||
@"santactl" : [self santactlVersion],
|
||||
@"SantaGUI" : [self santaAppVersion],
|
||||
};
|
||||
NSData *versionsData = [NSJSONSerialization dataWithJSONObject:versions
|
||||
options:NSJSONWritingPrettyPrinted
|
||||
error:nil];
|
||||
NSString *versionsStr = [[NSString alloc] initWithData:versionsData
|
||||
encoding:NSUTF8StringEncoding];
|
||||
printf("%s\n", [versionsStr UTF8String]);
|
||||
} else {
|
||||
printf("%-15s | %s\n", "santa-driver", [[self santaKextVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santad", [[self santadVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "santactl", [[self santactlVersion] UTF8String]);
|
||||
printf("%-15s | %s\n", "SantaGUI", [[self santaAppVersion] UTF8String]);
|
||||
}
|
||||
exit(0);
|
||||
}
|
||||
|
||||
+ (NSString *)santaKextVersion {
|
||||
NSDictionary *loadedKexts = CFBridgingRelease(
|
||||
KextManagerCopyLoadedKextInfo((__bridge CFArrayRef)@[ @(USERCLIENT_ID) ],
|
||||
(__bridge CFArrayRef)@[ @"CFBundleVersion" ])
|
||||
);
|
||||
KextManagerCopyLoadedKextInfo((__bridge CFArrayRef) @[ @(USERCLIENT_ID) ],
|
||||
(__bridge CFArrayRef) @[ @"CFBundleVersion" ]));
|
||||
|
||||
if (loadedKexts[@(USERCLIENT_ID)] && loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"]) {
|
||||
if (loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"]) {
|
||||
return loadedKexts[@(USERCLIENT_ID)][@"CFBundleVersion"];
|
||||
}
|
||||
|
||||
SNTFileInfo *driverInfo =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/Library/Extensions/santa-driver.kext"];
|
||||
SNTFileInfo *driverInfo = [[SNTFileInfo alloc] initWithPath:@(kKextPath)];
|
||||
if (driverInfo) {
|
||||
return [driverInfo.bundleVersion stringByAppendingString:@" (unloaded)"];
|
||||
}
|
||||
@@ -71,13 +86,13 @@ REGISTER_COMMAND_NAME(@"version");
|
||||
}
|
||||
|
||||
+ (NSString *)santadVersion {
|
||||
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@"/usr/libexec/santad"];
|
||||
SNTFileInfo *daemonInfo = [[SNTFileInfo alloc] initWithPath:@(kSantaDPath)];
|
||||
return daemonInfo.bundleVersion;
|
||||
}
|
||||
|
||||
+ (NSString *)santaAppVersion {
|
||||
SNTFileInfo *guiInfo =
|
||||
[[SNTFileInfo alloc] initWithPath:@"/Applications/Santa.app/Contents/MacOS/Santa"];
|
||||
[[SNTFileInfo alloc] initWithPath:@"/Applications/Santa.app/Contents/MacOS/Santa"];
|
||||
return guiInfo.bundleVersion;
|
||||
}
|
||||
|
||||
@@ -14,7 +14,7 @@
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
|
||||
#import <zlib.h>
|
||||
#include <zlib.h>
|
||||
|
||||
@implementation NSData (Zlib)
|
||||
|
||||
@@ -29,7 +29,7 @@
|
||||
stream.total_out = 0;
|
||||
stream.avail_out = 0;
|
||||
|
||||
int chunkSize = 16384;
|
||||
NSUInteger chunkSize = 16384;
|
||||
|
||||
int windowSize = 15;
|
||||
if (includeHeader) {
|
||||
@@ -63,4 +63,4 @@
|
||||
return [self compressIncludingGzipHeader:YES];
|
||||
}
|
||||
|
||||
@end
|
||||
@end
|
||||
237
Source/santactl/Commands/sync/SNTCommandSync.m
Normal file
@@ -0,0 +1,237 @@
|
||||
/// 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 "SNTCommandController.h"
|
||||
|
||||
#import <MOLAuthenticatingURLSession.h>
|
||||
|
||||
#import "SNTCommandSyncEventUpload.h"
|
||||
#import "SNTCommandSyncLogUpload.h"
|
||||
#import "SNTCommandSyncPostflight.h"
|
||||
#import "SNTCommandSyncPreflight.h"
|
||||
#import "SNTCommandSyncRuleDownload.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "SNTLogging.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
@interface SNTCommandSync : NSObject<SNTCommand>
|
||||
@property SNTCommandSyncState *syncState;
|
||||
@end
|
||||
|
||||
@implementation SNTCommandSync
|
||||
|
||||
REGISTER_COMMAND_NAME(@"sync")
|
||||
|
||||
+ (BOOL)requiresRoot {
|
||||
return NO;
|
||||
}
|
||||
|
||||
+ (BOOL)requiresDaemonConn {
|
||||
return YES;
|
||||
}
|
||||
|
||||
+ (NSString *)shortHelpText {
|
||||
return @"Synchronizes Santa with a configured server.";
|
||||
}
|
||||
|
||||
+ (NSString *)longHelpText {
|
||||
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 {
|
||||
// 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]];
|
||||
|
||||
|
||||
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]) {
|
||||
authURLSession.serverRootsPemFile = [config syncServerAuthRootsFile];
|
||||
} else if ([config syncServerAuthRootsData]) {
|
||||
authURLSession.serverRootsPemData = [config syncServerAuthRootsData];
|
||||
}
|
||||
|
||||
// Configure client auth
|
||||
if ([config syncClientAuthCertificateFile]) {
|
||||
authURLSession.clientCertFile = [config syncClientAuthCertificateFile];
|
||||
authURLSession.clientCertPassword = [config syncClientAuthCertificatePassword];
|
||||
} else if ([config syncClientAuthCertificateCn]) {
|
||||
authURLSession.clientCertCommonName = [config syncClientAuthCertificateCn];
|
||||
} else if ([config syncClientAuthCertificateIssuer]) {
|
||||
authURLSession.clientCertIssuerCn = [config syncClientAuthCertificateIssuer];
|
||||
}
|
||||
|
||||
s.syncState.session = [authURLSession session];
|
||||
s.syncState.daemonConn = daemonConn;
|
||||
|
||||
if ([arguments containsObject:@"singleevent"]) {
|
||||
NSUInteger idx = [arguments indexOfObject:@"singleevent"] + 1;
|
||||
if (idx >= arguments.count) {
|
||||
LOGI(@"singleevent takes an argument");
|
||||
exit(1);
|
||||
}
|
||||
|
||||
NSString *obj = arguments[idx];
|
||||
if (obj.length != 64) {
|
||||
LOGI(@"singleevent passed without SHA-256 as next argument");
|
||||
exit(1);
|
||||
}
|
||||
return [s eventUploadSingleEvent:obj];
|
||||
} else {
|
||||
return [s preflight];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)preflight {
|
||||
SNTCommandSyncPreflight *p = [[SNTCommandSyncPreflight alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Preflight complete");
|
||||
if (self.syncState.uploadLogURL) {
|
||||
return [self logUpload];
|
||||
} else {
|
||||
return [self eventUpload];
|
||||
}
|
||||
} else {
|
||||
LOGE(@"Preflight failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)logUpload {
|
||||
SNTCommandSyncLogUpload *p = [[SNTCommandSyncLogUpload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Log upload complete");
|
||||
} else {
|
||||
LOGE(@"Log upload failed, continuing anyway");
|
||||
}
|
||||
return [self eventUpload];
|
||||
}
|
||||
|
||||
- (void)eventUpload {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Event upload complete");
|
||||
return [self ruleDownload];
|
||||
} else {
|
||||
LOGE(@"Event upload failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)eventUploadSingleEvent:(NSString *)sha256 {
|
||||
SNTCommandSyncEventUpload *p = [[SNTCommandSyncEventUpload alloc] initWithState:self.syncState];
|
||||
if ([p syncSingleEventWithSHA256:sha256]) {
|
||||
LOGD(@"Event upload complete");
|
||||
exit(0);
|
||||
} else {
|
||||
LOGE(@"Event upload failed");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)ruleDownload {
|
||||
SNTCommandSyncRuleDownload *p = [[SNTCommandSyncRuleDownload alloc] initWithState:self.syncState];
|
||||
if ([p sync]) {
|
||||
LOGD(@"Rule download complete");
|
||||
if (self.syncState.bundleBinaryRequests.count) {
|
||||
return [self eventUploadBundleBinaries];
|
||||
}
|
||||
return [self postflight];
|
||||
} else {
|
||||
LOGE(@"Rule download failed, aborting run");
|
||||
exit(1);
|
||||
}
|
||||
}
|
||||
|
||||
- (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 *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
|
||||
90
Source/santactl/Commands/sync/SNTCommandSyncConstants.h
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.
|
||||
|
||||
extern NSString *const kXSRFToken;
|
||||
|
||||
extern NSString *const kSerialNumber;
|
||||
extern NSString *const kHostname;
|
||||
extern NSString *const kSantaVer;
|
||||
extern NSString *const kOSVer;
|
||||
extern NSString *const kOSBuild;
|
||||
extern NSString *const kPrimaryUser;
|
||||
extern NSString *const kRequestCleanSync;
|
||||
extern NSString *const kBatchSize;
|
||||
extern NSString *const kUploadLogsURL;
|
||||
extern NSString *const kClientMode;
|
||||
extern NSString *const kClientModeMonitor;
|
||||
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;
|
||||
extern NSString *const kFilePath;
|
||||
extern NSString *const kFileName;
|
||||
extern NSString *const kExecutingUser;
|
||||
extern NSString *const kExecutionTime;
|
||||
extern NSString *const kDecision;
|
||||
extern NSString *const kDecisionAllowUnknown;
|
||||
extern NSString *const kDecisionAllowBinary;
|
||||
extern NSString *const kDecisionAllowCertificate;
|
||||
extern NSString *const kDecisionAllowScope;
|
||||
extern NSString *const kDecisionBlockUnknown;
|
||||
extern NSString *const kDecisionBlockBinary;
|
||||
extern NSString *const kDecisionBlockCertificate;
|
||||
extern NSString *const kDecisionBlockScope;
|
||||
extern NSString *const kDecisionUnknown;
|
||||
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;
|
||||
extern NSString *const kPID;
|
||||
extern NSString *const kPPID;
|
||||
extern NSString *const kParentName;
|
||||
extern NSString *const kSigningChain;
|
||||
extern NSString *const kCertSHA256;
|
||||
extern NSString *const kCertCN;
|
||||
extern NSString *const kCertOrg;
|
||||
extern NSString *const kCertOU;
|
||||
extern NSString *const kCertValidFrom;
|
||||
extern NSString *const kCertValidUntil;
|
||||
extern NSString *const kQuarantineDataURL;
|
||||
extern NSString *const kQuarantineRefererURL;
|
||||
extern NSString *const kQuarantineTimestamp;
|
||||
extern NSString *const kQuarantineAgentBundleID;
|
||||
extern NSString *const kEventUploadBundleBinaries;
|
||||
|
||||
extern NSString *const kLogUploadField;
|
||||
|
||||
extern NSString *const kRules;
|
||||
extern NSString *const kRuleSHA256;
|
||||
extern NSString *const kRulePolicy;
|
||||
extern NSString *const kRulePolicyWhitelist;
|
||||
extern NSString *const kRulePolicyBlacklist;
|
||||
extern NSString *const kRulePolicySilentBlacklist;
|
||||
extern NSString *const kRulePolicyRemove;
|
||||
extern NSString *const kRuleType;
|
||||
extern NSString *const kRuleTypeBinary;
|
||||
extern NSString *const kRuleTypeCertificate;
|
||||
extern NSString *const kRuleCustomMsg;
|
||||
extern NSString *const kCursor;
|
||||
|
||||
extern NSString *const kBackoffInterval;
|
||||
92
Source/santactl/Commands/sync/SNTCommandSyncConstants.m
Normal file
@@ -0,0 +1,92 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
|
||||
NSString *const kXSRFToken = @"X-XSRF-TOKEN";
|
||||
|
||||
NSString *const kSerialNumber = @"serial_num";
|
||||
NSString *const kHostname = @"hostname";
|
||||
NSString *const kSantaVer = @"santa_version";
|
||||
NSString *const kOSVer = @"os_version";
|
||||
NSString *const kOSBuild = @"os_build";
|
||||
NSString *const kPrimaryUser = @"primary_user";
|
||||
NSString *const kRequestCleanSync = @"request_clean_sync";
|
||||
NSString *const kBatchSize = @"batch_size";
|
||||
NSString *const kUploadLogsURL = @"upload_logs_url";
|
||||
NSString *const kClientMode = @"client_mode";
|
||||
NSString *const kClientModeMonitor = @"MONITOR";
|
||||
NSString *const kClientModeLockdown = @"LOCKDOWN";
|
||||
NSString *const 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";
|
||||
NSString *const kFilePath = @"file_path";
|
||||
NSString *const kFileName = @"file_name";
|
||||
NSString *const kExecutingUser = @"executing_user";
|
||||
NSString *const kExecutionTime = @"execution_time";
|
||||
NSString *const kDecision = @"decision";
|
||||
NSString *const kDecisionAllowUnknown = @"ALLOW_UNKNOWN";
|
||||
NSString *const kDecisionAllowBinary = @"ALLOW_BINARY";
|
||||
NSString *const kDecisionAllowCertificate = @"ALLOW_CERTIFICATE";
|
||||
NSString *const kDecisionAllowScope = @"ALLOW_SCOPE";
|
||||
NSString *const kDecisionBlockUnknown = @"BLOCK_UNKNOWN";
|
||||
NSString *const kDecisionBlockBinary = @"BLOCK_BINARY";
|
||||
NSString *const kDecisionBlockCertificate = @"BLOCK_CERTIFICATE";
|
||||
NSString *const kDecisionBlockScope = @"BLOCK_SCOPE";
|
||||
NSString *const kDecisionUnknown = @"UNKNOWN";
|
||||
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";
|
||||
NSString *const kPID = @"pid";
|
||||
NSString *const kPPID = @"ppid";
|
||||
NSString *const kParentName = @"parent_name";
|
||||
NSString *const kSigningChain = @"signing_chain";
|
||||
NSString *const kCertSHA256 = @"sha256";
|
||||
NSString *const kCertCN = @"cn";
|
||||
NSString *const kCertOrg = @"org";
|
||||
NSString *const kCertOU = @"ou";
|
||||
NSString *const kCertValidFrom = @"valid_from";
|
||||
NSString *const kCertValidUntil = @"valid_until";
|
||||
NSString *const 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";
|
||||
|
||||
NSString *const kRules = @"rules";
|
||||
NSString *const kRuleSHA256 = @"sha256";
|
||||
NSString *const kRulePolicy = @"policy";
|
||||
NSString *const kRulePolicyWhitelist = @"WHITELIST";
|
||||
NSString *const kRulePolicyBlacklist = @"BLACKLIST";
|
||||
NSString *const kRulePolicySilentBlacklist = @"SILENT_BLACKLIST";
|
||||
NSString *const kRulePolicyRemove = @"REMOVE";
|
||||
NSString *const kRuleType = @"rule_type";
|
||||
NSString *const kRuleTypeBinary = @"BINARY";
|
||||
NSString *const kRuleTypeCertificate = @"CERTIFICATE";
|
||||
NSString *const kRuleCustomMsg = @"custom_msg";
|
||||
NSString *const kCursor = @"cursor";
|
||||
|
||||
NSString *const kBackoffInterval = @"backoff";
|
||||
@@ -12,14 +12,12 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncStatus;
|
||||
@class SNTXPCConnection;
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncPreflight : NSObject
|
||||
@interface SNTCommandSyncEventUpload : SNTCommandSyncStage
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
progress:(SNTCommandSyncStatus *)progress
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
- (BOOL)syncSingleEventWithSHA256:(NSString *)sha256;
|
||||
|
||||
- (BOOL)syncBundleEvents;
|
||||
|
||||
@end
|
||||
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
|
||||
@@ -12,14 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncStatus;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
@interface SNTCommandSyncLogUpload : NSObject
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
progress:(SNTCommandSyncStatus *)progress
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncLogUpload : SNTCommandSyncStage
|
||||
@end
|
||||
@@ -15,32 +15,43 @@
|
||||
#import "SNTCommandSyncLogUpload.h"
|
||||
|
||||
#import "NSData+Zlib.h"
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#include "SNTLogging.h"
|
||||
|
||||
#import "SNTCommandSyncConstants.h"
|
||||
#import "SNTCommandSyncStatus.h"
|
||||
#import "SNTCommandSyncState.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
#import "SNTLogging.h"
|
||||
|
||||
@implementation SNTCommandSyncLogUpload
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
progress:(SNTCommandSyncStatus *)progress
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler {
|
||||
NSURL *url = progress.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]];
|
||||
|
||||
NSDictionary *d = [self performRequest:req];
|
||||
if (!d) return NO;
|
||||
|
||||
LOGI(@"Uploaded %lu logs", logsToUpload.count);
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (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];
|
||||
NSArray *logsToUpload = [SNTCommandSyncLogUpload logsToUpload];
|
||||
for (NSString *log in logsToUpload) {
|
||||
[reqBody appendData:
|
||||
[[NSString stringWithFormat:@"--%@\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
@@ -48,33 +59,19 @@
|
||||
[[NSString stringWithFormat:@"Content-Disposition: form-data; "
|
||||
@"name=\"%@\"; "
|
||||
@"filename=\"%@.gz\"\r\n", kLogUploadField, [log lastPathComponent]]
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[reqBody appendData:
|
||||
[@"Content-Type: application/x-gzip\r\n\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[reqBody appendData:[[NSData dataWithContentsOfFile:log] gzipCompressed]];
|
||||
[reqBody appendData:[@"\r\n" dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
}
|
||||
[reqBody appendData:
|
||||
[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
[[NSString stringWithFormat:@"--%@--\r\n", boundary] dataUsingEncoding:NSUTF8StringEncoding]];
|
||||
|
||||
// Upload the logs
|
||||
[[session uploadTaskWithRequest:req
|
||||
fromData:reqBody
|
||||
completionHandler:^(NSData *data, NSURLResponse *response, NSError *error) {
|
||||
long statusCode = [(NSHTTPURLResponse *)response statusCode];
|
||||
if (statusCode != 200) {
|
||||
LOGE(@"HTTP Response: %d %@",
|
||||
statusCode,
|
||||
[[NSHTTPURLResponse localizedStringForStatusCode:statusCode] capitalizedString]);
|
||||
handler(NO);
|
||||
} else {
|
||||
LOGI(@"Uploaded %d logs", [logsToUpload count]);
|
||||
handler(YES);
|
||||
}
|
||||
}] resume];
|
||||
return reqBody;
|
||||
}
|
||||
|
||||
+ (NSArray *)logsToUpload {
|
||||
- (NSArray *)logsToUpload {
|
||||
// General logs
|
||||
NSMutableArray *logsToUpload = [@[ @"/var/log/santa.log",
|
||||
@"/var/log/system.log" ] mutableCopy];
|
||||
@@ -84,7 +81,7 @@
|
||||
NSDirectoryEnumerator *dirEnum = [[NSFileManager defaultManager] enumeratorAtPath:diagsDir];
|
||||
NSString *file;
|
||||
while (file = [dirEnum nextObject]) {
|
||||
if ([[file pathExtension] isEqualToString: @"panic"] ||
|
||||
if ([[file pathExtension] isEqualToString:@"panic"] ||
|
||||
[file hasPrefix:@"santad"] ||
|
||||
[file hasPrefix:@"santactl"]) {
|
||||
[logsToUpload addObject:[diagsDir stringByAppendingString:file]];
|
||||
@@ -12,14 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
@class SNTCommandSyncStatus;
|
||||
@class SNTXPCConnection;
|
||||
|
||||
@interface SNTCommandSyncPostflight : NSObject
|
||||
|
||||
+ (void)performSyncInSession:(NSURLSession *)session
|
||||
progress:(SNTCommandSyncStatus *)progress
|
||||
daemonConn:(SNTXPCConnection *)daemonConn
|
||||
completionHandler:(void (^)(BOOL success))handler;
|
||||
#import "SNTCommandSyncStage.h"
|
||||
|
||||
@interface SNTCommandSyncPostflight : SNTCommandSyncStage
|
||||
@end
|
||||
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
|
||||
18
Source/santactl/Commands/sync/SNTCommandSyncPreflight.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 SNTCommandSyncPreflight : SNTCommandSyncStage
|
||||
@end
|
||||
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
|
||||
18
Source/santactl/Commands/sync/SNTCommandSyncRuleDownload.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 SNTCommandSyncRuleDownload : SNTCommandSyncStage
|
||||
@end
|
||||
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
|
||||