mirror of
https://github.com/google/santa.git
synced 2026-01-15 01:08:12 -05:00
Compare commits
998 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
80b26955b4 | ||
|
|
6a84023548 | ||
|
|
e70acefb5c | ||
|
|
41c918ee87 | ||
|
|
1adb6d2726 | ||
|
|
8c531a256b | ||
|
|
5829363733 | ||
|
|
379f283c62 | ||
|
|
2082345c02 | ||
|
|
dd8f81a60e | ||
|
|
8ccb0813f1 | ||
|
|
b24e7e42bf | ||
|
|
4821ebebd5 | ||
|
|
efeaa82618 | ||
|
|
3f3de02644 | ||
|
|
f6c9456ea7 | ||
|
|
2aaff051c8 | ||
|
|
2df7e91c87 | ||
|
|
37644acd01 | ||
|
|
899ca89e23 | ||
|
|
e7281f1c55 | ||
|
|
bf0ca24ae7 | ||
|
|
4fe8b7908f | ||
|
|
a8dd332402 | ||
|
|
6631b0a8e3 | ||
|
|
07e09db608 | ||
|
|
d041a48c97 | ||
|
|
1683e09cc8 | ||
|
|
d6c73e0c6c | ||
|
|
72969a3c92 | ||
|
|
d2dbed78dd | ||
|
|
8fa91e4ff0 | ||
|
|
551763146d | ||
|
|
7a7f0cd5a8 | ||
|
|
fcb49701b3 | ||
|
|
c9ef723fc5 | ||
|
|
dc6732ef04 | ||
|
|
a48900a4ae | ||
|
|
bb49118d94 | ||
|
|
456333d6d2 | ||
|
|
fd23a5c3b7 | ||
|
|
ec203e8796 | ||
|
|
57ff69208d | ||
|
|
f00b7d2ded | ||
|
|
9791fdd53c | ||
|
|
26e2203f1e | ||
|
|
4a47195d12 | ||
|
|
4436e221df | ||
|
|
deccc8a148 | ||
|
|
06da796a4d | ||
|
|
7b99a76d0d | ||
|
|
c2d3e99446 | ||
|
|
6db7fea8ae | ||
|
|
6fcb4cfe63 | ||
|
|
8b55ee4da5 | ||
|
|
cc3177502c | ||
|
|
a49a59b109 | ||
|
|
2c06c39c82 | ||
|
|
234f81ea7c | ||
|
|
743c567bf8 | ||
|
|
21220f1499 | ||
|
|
39f3ffe8fc | ||
|
|
fdb01928a0 | ||
|
|
fbefbc5910 | ||
|
|
9db00d143d | ||
|
|
1cc40d59d8 | ||
|
|
ba1ace56f0 | ||
|
|
6d911e9d6e | ||
|
|
7e2b291122 | ||
|
|
64096f5d08 | ||
|
|
aec1c74fab | ||
|
|
d4a0d77cb9 | ||
|
|
7df209ed3f | ||
|
|
b7421e4499 | ||
|
|
e044fe3601 | ||
|
|
a67801d5ed | ||
|
|
3d37a3a5ae | ||
|
|
bfae5dc828 | ||
|
|
fde5f52a11 | ||
|
|
01bd1bfdca | ||
|
|
ae13900676 | ||
|
|
a65c91874b | ||
|
|
6a3fda069c | ||
|
|
4d34099142 | ||
|
|
e639574973 | ||
|
|
636f9ea873 | ||
|
|
9099409915 | ||
|
|
976f483a99 | ||
|
|
8a32b7a56b | ||
|
|
7eeb06b406 | ||
|
|
4540a1c656 | ||
|
|
acc7b32b24 | ||
|
|
b92d513f5d | ||
|
|
3458fccd4e | ||
|
|
fdfb00368c | ||
|
|
6bd369cfb2 | ||
|
|
0df26c6214 | ||
|
|
6e22da1d97 | ||
|
|
1725809335 | ||
|
|
3eff49feda | ||
|
|
5caedebb06 | ||
|
|
d823028b72 | ||
|
|
49b2d6e22a | ||
|
|
4236d57e96 | ||
|
|
36d463a1dc | ||
|
|
adbafd6bab | ||
|
|
b5ebe1259c | ||
|
|
e0ae0f481b | ||
|
|
8037c79fc0 | ||
|
|
892d303de1 | ||
|
|
ff3979263e | ||
|
|
01afefd3d4 | ||
|
|
830627e7bc | ||
|
|
601d726fcc | ||
|
|
0be1ca0199 | ||
|
|
8602593149 | ||
|
|
9bca601ce6 | ||
|
|
c73acd59d4 | ||
|
|
3c334e8882 | ||
|
|
5f811cadf8 | ||
|
|
4252475de0 | ||
|
|
45f1822681 | ||
|
|
498a23d907 | ||
|
|
5dff8a18f4 | ||
|
|
676c02626d | ||
|
|
64950d0a99 | ||
|
|
16f74cb85c | ||
|
|
aadc961429 | ||
|
|
be66fd92f4 | ||
|
|
feea349f25 | ||
|
|
1c04c3a257 | ||
|
|
818d3f645f | ||
|
|
15d6bb1f14 | ||
|
|
211dbd123f | ||
|
|
c67364fe76 | ||
|
|
2043983f69 | ||
|
|
2f408936a0 | ||
|
|
02c1d0f267 | ||
|
|
4728c346cc | ||
|
|
9588dd8a0e | ||
|
|
e3e48aed1b | ||
|
|
e60f9cf6c5 | ||
|
|
c7e309ccb1 | ||
|
|
ad8aafbd07 | ||
|
|
9e671c3dee | ||
|
|
d97abe36f2 | ||
|
|
faa8946056 | ||
|
|
8b2b1f0bfc | ||
|
|
16678cd5a0 | ||
|
|
0bd6a199a3 | ||
|
|
58e2b7e1b8 | ||
|
|
b824a8e3e0 | ||
|
|
25bf2a93e4 | ||
|
|
f1ea1b369f | ||
|
|
5503a88308 | ||
|
|
8cf0f8217d | ||
|
|
22799ffc2a | ||
|
|
cb61d0cc99 | ||
|
|
fb7447ceba | ||
|
|
45e51e9c09 | ||
|
|
b0f0cdd4e6 | ||
|
|
65090d3ef2 | ||
|
|
9c80f79d82 | ||
|
|
93adaea81e | ||
|
|
a125b340a5 | ||
|
|
fbd0de3d48 | ||
|
|
6f2ae62bce | ||
|
|
da29b20473 | ||
|
|
197109a8ee | ||
|
|
91f3168c7a | ||
|
|
a00ec41518 | ||
|
|
c32248aaf7 | ||
|
|
afd97bdf3e | ||
|
|
73c4875b1f | ||
|
|
916fc8c0e6 | ||
|
|
e59e6105f3 | ||
|
|
216ac811eb | ||
|
|
48f92f5913 | ||
|
|
6bb08d0490 | ||
|
|
82b71c0f20 | ||
|
|
10ccee9e4c | ||
|
|
acbbb9e7b0 | ||
|
|
3939ad9813 | ||
|
|
d20455252d | ||
|
|
5cd901034f | ||
|
|
4e82392370 | ||
|
|
19710f7233 | ||
|
|
27e32bd9ff | ||
|
|
c268ad4f9a | ||
|
|
f7a1a4cb39 | ||
|
|
ad6e03e6cc | ||
|
|
8ecc3f879a | ||
|
|
d51093501c | ||
|
|
05dd1b6215 | ||
|
|
8c3320e3e9 | ||
|
|
369dc9a63c | ||
|
|
7adc55007c | ||
|
|
fe6be921d3 | ||
|
|
23b31ec413 | ||
|
|
727b009a1c | ||
|
|
1c42f06135 | ||
|
|
e1cf8e70a3 | ||
|
|
7a500b8135 | ||
|
|
3702af0309 | ||
|
|
697cd29a0a | ||
|
|
5735a12424 | ||
|
|
07b8f2121d | ||
|
|
78a1a929fd | ||
|
|
9163417b54 | ||
|
|
fa6630a31a | ||
|
|
1f2b82fc58 | ||
|
|
b77b0142af | ||
|
|
2f80a42845 | ||
|
|
67db370492 | ||
|
|
a0319ecf52 | ||
|
|
16d0bd6db6 | ||
|
|
9e3943ec68 | ||
|
|
e461b4bfbc | ||
|
|
8f836afe86 | ||
|
|
04ad1c34ba | ||
|
|
c3042e21dc | ||
|
|
3ede20a121 | ||
|
|
976118cce4 | ||
|
|
ea85f0f539 | ||
|
|
d193b05057 | ||
|
|
9fb4f2e171 | ||
|
|
58cec5819a | ||
|
|
6ba5831f2d | ||
|
|
a22e3ead83 | ||
|
|
2611b551ce | ||
|
|
023f96f5c8 | ||
|
|
1523d58429 | ||
|
|
81049db170 | ||
|
|
c110245701 | ||
|
|
d7a56b9bd4 | ||
|
|
4bb5804a6f | ||
|
|
e68fb7235a | ||
|
|
f93e7ef879 | ||
|
|
f472f4821c | ||
|
|
1c97761038 | ||
|
|
e569a684b7 | ||
|
|
66c32dc526 | ||
|
|
075d3cbc11 | ||
|
|
340326df8a | ||
|
|
f52edd2a76 | ||
|
|
11c247e33a | ||
|
|
a859b9b341 | ||
|
|
c190f1f52d | ||
|
|
87dc191494 | ||
|
|
3a19591822 | ||
|
|
b225c0740e | ||
|
|
d1fffb4636 | ||
|
|
9d7ca62e46 | ||
|
|
2a6073a9a1 | ||
|
|
296f06582b | ||
|
|
0e27dab4c6 | ||
|
|
256836d7f8 | ||
|
|
b117d8106e | ||
|
|
c980223215 | ||
|
|
635b33ebf9 | ||
|
|
b6f35c9b9f | ||
|
|
796109cc60 | ||
|
|
38f580de72 | ||
|
|
c7a58c77e7 | ||
|
|
9a4fe782d7 | ||
|
|
fbb5f3728f | ||
|
|
24b96c4798 | ||
|
|
1edf6d9200 | ||
|
|
ac1f8ea1b8 | ||
|
|
9923f601b6 | ||
|
|
471ae89406 | ||
|
|
54d6653973 | ||
|
|
27ee66597b | ||
|
|
10f2d852f5 | ||
|
|
1fcb63dc92 | ||
|
|
7944f681f8 | ||
|
|
e3aedc92ba | ||
|
|
d2b6c2b6c2 | ||
|
|
d026989dfb | ||
|
|
e7a8e9b6ac | ||
|
|
1d9af01353 | ||
|
|
9c6af7fc03 | ||
|
|
543b1a29fe | ||
|
|
625ec67789 | ||
|
|
c5696d71e7 | ||
|
|
5f3cef52de | ||
|
|
eeed0b5aa6 | ||
|
|
9ef171e663 | ||
|
|
ad1868a50f | ||
|
|
78643d3c49 | ||
|
|
8b22c85a64 | ||
|
|
58fe5d3d76 | ||
|
|
8b2227967e | ||
|
|
65693acea1 | ||
|
|
7cea383930 | ||
|
|
5ae2376158 | ||
|
|
e851337eac | ||
|
|
2e53834980 | ||
|
|
aef139e93c | ||
|
|
a9e5bf09a7 | ||
|
|
4ee3f281c3 | ||
|
|
462ce89d42 | ||
|
|
44117833c0 | ||
|
|
8b6e029da2 | ||
|
|
f183e246df | ||
|
|
c60a35f280 | ||
|
|
4f65965277 | ||
|
|
01e4e15b81 | ||
|
|
532cb37e0b | ||
|
|
9d379d3884 | ||
|
|
3e7a191bf7 | ||
|
|
c5a048f4d9 | ||
|
|
f4769bad90 | ||
|
|
254497ad15 | ||
|
|
0a83445838 | ||
|
|
eff287259e | ||
|
|
6f2c0e3457 | ||
|
|
38769f7cd1 | ||
|
|
fa785ad3c2 | ||
|
|
5dae0cabdd | ||
|
|
a8b4f4ea7e | ||
|
|
2221c93bbc | ||
|
|
d1c33baf35 | ||
|
|
d2bbdff373 | ||
|
|
db1d65f944 | ||
|
|
d17aeac2f4 | ||
|
|
7840270dd0 | ||
|
|
dcf44c9872 | ||
|
|
fc365c888f | ||
|
|
85f0782399 | ||
|
|
64bc34c302 | ||
|
|
e2fc4c735d | ||
|
|
ff9cb34490 | ||
|
|
60405f1e10 | ||
|
|
ac9d3b2adf | ||
|
|
7e8bd46da3 | ||
|
|
2f6ed455e5 | ||
|
|
8cb86b6d1d | ||
|
|
fc074f6014 | ||
|
|
a7856e60e8 | ||
|
|
41a40c9fbd | ||
|
|
8c18f6ebf5 | ||
|
|
949053fedd | ||
|
|
8d2c39b71d | ||
|
|
8f872fb4fc | ||
|
|
5512f8cf19 | ||
|
|
6742b38e31 | ||
|
|
d1635f7e11 | ||
|
|
e2b865c081 | ||
|
|
012b02de5d | ||
|
|
11ebead617 | ||
|
|
e3fbabfe37 | ||
|
|
8757da7822 | ||
|
|
428582f471 | ||
|
|
6e0effc0f4 | ||
|
|
683114fbec | ||
|
|
d9ebb4e3db | ||
|
|
e6aaf2f198 | ||
|
|
1c3757d4ab | ||
|
|
4346bb29c2 | ||
|
|
09655df8fc | ||
|
|
7504cd36e1 | ||
|
|
cafef66933 | ||
|
|
0c4e9d4b06 | ||
|
|
ac07f5d54b | ||
|
|
d116f7b01e | ||
|
|
63ca34bc54 | ||
|
|
c894029c33 | ||
|
|
de2bdd6653 | ||
|
|
2d066ad671 | ||
|
|
24854d4ad7 | ||
|
|
99ee0af178 | ||
|
|
bf6f78df09 | ||
|
|
c05806916b | ||
|
|
e48ce0cfe3 | ||
|
|
eabca469b9 | ||
|
|
f6dc36e812 | ||
|
|
ac7cbdfd16 | ||
|
|
d1d008af0a | ||
|
|
5db56e01f5 | ||
|
|
726c49bec5 | ||
|
|
ae5db5dde7 | ||
|
|
2671807f0e | ||
|
|
70c8626016 | ||
|
|
436c472a49 | ||
|
|
ed5be6b062 | ||
|
|
a38f24728a | ||
|
|
4af026356f | ||
|
|
c6e1bb5618 | ||
|
|
e64d2e7ad4 | ||
|
|
3d393e9aa4 | ||
|
|
b8f3122ee9 | ||
|
|
8acfa6591e | ||
|
|
25b75b0e1b | ||
|
|
cb01b77f84 | ||
|
|
61582a0324 | ||
|
|
a17b5d51a4 | ||
|
|
447ea8674b | ||
|
|
c5eec850e1 | ||
|
|
1870631150 | ||
|
|
20ed1659c1 | ||
|
|
258de3efba | ||
|
|
394fd5fab9 | ||
|
|
53b7ef86ed | ||
|
|
423479771e | ||
|
|
933271826b | ||
|
|
880170ea7d | ||
|
|
e58ec37881 | ||
|
|
dece50dd10 | ||
|
|
9db9fc6009 | ||
|
|
f38c030805 | ||
|
|
d8060d3af9 | ||
|
|
34b4090b42 | ||
|
|
c6ca3d64b3 | ||
|
|
4913426631 | ||
|
|
455a1c76c3 | ||
|
|
e5a5f6f9fb | ||
|
|
7ef88d06a5 | ||
|
|
bc82d7988b | ||
|
|
545fa858e4 | ||
|
|
71c917649e | ||
|
|
3781556cf5 | ||
|
|
765d10a7c3 | ||
|
|
3583113381 | ||
|
|
46cd60e579 | ||
|
|
8198e59736 | ||
|
|
c5f0f5d177 | ||
|
|
ebc93954be | ||
|
|
cb4d2984b3 | ||
|
|
4c2018ef67 | ||
|
|
06d8295d0a | ||
|
|
ef8e9975e9 | ||
|
|
31509f4b9c | ||
|
|
497c1f393f | ||
|
|
8334a245c7 | ||
|
|
e8826a2941 | ||
|
|
ef040c1e7d | ||
|
|
dc692c8256 | ||
|
|
e9c7bfc087 | ||
|
|
22c72625c8 | ||
|
|
65a2212890 | ||
|
|
0a7c08cafc | ||
|
|
831a32160b | ||
|
|
b186419e54 | ||
|
|
1dc579c00f | ||
|
|
abdd6c319a | ||
|
|
5dd93fadfa | ||
|
|
e6fcbf59df | ||
|
|
9fd04ed301 | ||
|
|
e4b5f595ce | ||
|
|
212b02589b | ||
|
|
42c3631995 | ||
|
|
2695355dd2 | ||
|
|
db0cd861d6 | ||
|
|
57d6a962de | ||
|
|
91608d7366 | ||
|
|
7d4f1ffc45 | ||
|
|
ba539bb555 | ||
|
|
d9ecbf06c0 | ||
|
|
01df4623c7 | ||
|
|
c9cb91a22e | ||
|
|
1f9d60aecc | ||
|
|
52c5b5aade | ||
|
|
2d98173c51 | ||
|
|
5e3f13be70 | ||
|
|
90b894b88a | ||
|
|
6dc7387881 | ||
|
|
b14b017d72 | ||
|
|
d0ede18bf4 | ||
|
|
6d223aea03 | ||
|
|
f7986b0a05 | ||
|
|
629e70287c | ||
|
|
3c2a88144c | ||
|
|
3651f18566 | ||
|
|
472fea75b1 | ||
|
|
e1b5438865 | ||
|
|
fbbf523333 | ||
|
|
15fa53d744 | ||
|
|
9595f80fde | ||
|
|
61a67e45c1 | ||
|
|
143e690dab | ||
|
|
ebd507f143 | ||
|
|
f71bc0a8f7 | ||
|
|
edc0c72464 | ||
|
|
c3ce4f718b | ||
|
|
40ee482973 | ||
|
|
a5d2e6fdd2 | ||
|
|
e9a835a642 | ||
|
|
ac7b95ceb6 | ||
|
|
055b2d8ede | ||
|
|
a75cd0a0f5 | ||
|
|
2b1ddf9a4e | ||
|
|
b70442e483 | ||
|
|
798b0fab15 | ||
|
|
e8630132d7 | ||
|
|
273ae5f21a | ||
|
|
06b688fef4 | ||
|
|
59cc038ab2 | ||
|
|
ea5a6c3438 | ||
|
|
e2adfdf3cf | ||
|
|
5ee6531627 | ||
|
|
1cf8ee09e1 | ||
|
|
4a2cf9d722 | ||
|
|
6a6a32c1cf | ||
|
|
ce03611b52 | ||
|
|
bbe9f83878 | ||
|
|
40e6c6aa92 | ||
|
|
9f6ccf092a | ||
|
|
d4ba4b082f | ||
|
|
cce43829eb | ||
|
|
c1bfbac2fe | ||
|
|
fc87cde668 | ||
|
|
400c413029 | ||
|
|
0e6eb45732 | ||
|
|
7ca2028c19 | ||
|
|
08144b54a7 | ||
|
|
103137498b | ||
|
|
8e57e3709d | ||
|
|
bd6bd66946 | ||
|
|
6973dd0ec2 | ||
|
|
2e8b08cd9e | ||
|
|
edc8f43f42 | ||
|
|
133814cd73 | ||
|
|
57213ee31b | ||
|
|
b4fa2a394b | ||
|
|
0c39342d53 | ||
|
|
1c95e8e25c | ||
|
|
ff5a92772b | ||
|
|
bc2a17f70f | ||
|
|
f2e909e578 | ||
|
|
c3385a808c | ||
|
|
8d480331ff | ||
|
|
5216f0989c | ||
|
|
4238553a2e | ||
|
|
79662d0dcf | ||
|
|
ff095bc53d | ||
|
|
eefd70b2de | ||
|
|
9b3eab67a2 | ||
|
|
54def2deb7 | ||
|
|
cd12744726 | ||
|
|
616fd9570f | ||
|
|
0544011ee0 | ||
|
|
51920c7045 | ||
|
|
6f417a1775 | ||
|
|
51034a24c6 | ||
|
|
f631f219b0 | ||
|
|
aacae020b8 | ||
|
|
7c426e0eec | ||
|
|
363826502f | ||
|
|
1cfadae068 | ||
|
|
d3b3d722b4 | ||
|
|
a82428958b | ||
|
|
b185632bda | ||
|
|
e7a0c3d25b | ||
|
|
ab33de2c15 | ||
|
|
a1031cdc27 | ||
|
|
e3ab3ca506 | ||
|
|
b4cd1ccbee | ||
|
|
14573a5714 | ||
|
|
96150a9668 | ||
|
|
c10c1303ed | ||
|
|
7852e69685 | ||
|
|
094880af50 | ||
|
|
c3db518aca | ||
|
|
41ee0c5fdb | ||
|
|
ae178bc146 | ||
|
|
a2a660d483 | ||
|
|
8684cc34f7 | ||
|
|
0aba8b78ba | ||
|
|
5e735aa8d5 | ||
|
|
a2d6338400 | ||
|
|
5e4b8350ab | ||
|
|
4a65b646df | ||
|
|
24c715aae9 | ||
|
|
9ab85768bd | ||
|
|
16458d96e7 | ||
|
|
b307dd17af | ||
|
|
313552352c | ||
|
|
543ac7c649 | ||
|
|
dacff76694 | ||
|
|
c134169ea1 | ||
|
|
e252945047 | ||
|
|
f8cfcaab20 | ||
|
|
528237a239 | ||
|
|
91aefe25c4 | ||
|
|
a8c11097d9 | ||
|
|
92ba4a3ae9 | ||
|
|
7c5d382010 | ||
|
|
f8fbaefd86 | ||
|
|
181b37296a | ||
|
|
2ab61cfa12 | ||
|
|
1b0e9b14ef | ||
|
|
2aacc9266f | ||
|
|
d648d477bb | ||
|
|
6f91c1a1d3 | ||
|
|
aa1aca24b7 | ||
|
|
6a0867172f | ||
|
|
f025a4b2fb | ||
|
|
8871f36a92 | ||
|
|
f17490edad | ||
|
|
b360e782c6 | ||
|
|
8d94324dd6 | ||
|
|
2818609412 | ||
|
|
270a2e69d4 | ||
|
|
d1d9762e29 | ||
|
|
1666e8b127 | ||
|
|
08dfad208b | ||
|
|
b5921f95f3 | ||
|
|
2063bc3db3 | ||
|
|
4380016d52 | ||
|
|
5e3ceabe46 | ||
|
|
8e7936275b | ||
|
|
4b967239fa | ||
|
|
92945c384c | ||
|
|
79d93c4ecf | ||
|
|
76b6f25b0c | ||
|
|
aadce4890a | ||
|
|
0e95a98fc2 | ||
|
|
9483437e8f | ||
|
|
59542f8aef | ||
|
|
e29f7332f5 | ||
|
|
f8640feafe | ||
|
|
e94e9e2be4 | ||
|
|
4053aac365 | ||
|
|
a5fa6c7aef | ||
|
|
97263894d1 | ||
|
|
1885580958 | ||
|
|
1167b470bb | ||
|
|
7600506d6d | ||
|
|
86bad866a0 | ||
|
|
2f1a15cf7e | ||
|
|
52b0e1870f | ||
|
|
9b181c1e0d | ||
|
|
100f2dc45e | ||
|
|
b247c3d477 | ||
|
|
76ee82b258 | ||
|
|
e8fcd29669 | ||
|
|
8dd16ecea4 | ||
|
|
e9c0bcd877 | ||
|
|
75ed4b52a6 | ||
|
|
71635c00df | ||
|
|
1810af5483 | ||
|
|
b07835dfd5 | ||
|
|
4c33aa2aae | ||
|
|
3c255640cb | ||
|
|
3d08ba9ebc | ||
|
|
f64482500e | ||
|
|
215902f192 | ||
|
|
3e9c3a069d | ||
|
|
841fb48479 | ||
|
|
df8e41925f | ||
|
|
6b0994a990 | ||
|
|
7dd616e891 | ||
|
|
c672edbe4d | ||
|
|
687ecc7097 | ||
|
|
b8882b4826 | ||
|
|
51de0b38a4 | ||
|
|
e0309c0482 | ||
|
|
5dbe86869d | ||
|
|
14a11279c7 | ||
|
|
df0ce42377 | ||
|
|
4c03411405 | ||
|
|
f020e18238 | ||
|
|
629bd4aff9 | ||
|
|
f20825a66c | ||
|
|
f098ca0d02 | ||
|
|
1f96f74f4d | ||
|
|
7a3a98c27a | ||
|
|
1130448cb9 | ||
|
|
d388e99c0e | ||
|
|
2baea9a6b4 | ||
|
|
0629625a9a | ||
|
|
a2d0acc761 | ||
|
|
28a6bce90f | ||
|
|
9058192ffe | ||
|
|
465b358271 | ||
|
|
7de585fe1d | ||
|
|
8479730c95 | ||
|
|
7102e2df4c | ||
|
|
c3bd99ff93 | ||
|
|
c560405a46 | ||
|
|
0c0fb28ccc | ||
|
|
a33fce942c | ||
|
|
369cd40ee5 | ||
|
|
577b431a41 | ||
|
|
75cf8acd33 | ||
|
|
d70983962b | ||
|
|
ff440984b0 | ||
|
|
c631155be7 | ||
|
|
6038930755 | ||
|
|
9edc119c62 | ||
|
|
269a94bf03 | ||
|
|
7f3e4d7468 | ||
|
|
eb89891cdd | ||
|
|
038b068370 | ||
|
|
d2017a59de | ||
|
|
3435b56a84 | ||
|
|
a812558d2d | ||
|
|
aefd85455e | ||
|
|
e42f1347b7 | ||
|
|
c7442a03d1 | ||
|
|
1eda8bdd9d | ||
|
|
c4d0628bdb | ||
|
|
d51ae66242 | ||
|
|
121dde6b8b | ||
|
|
98081b067d | ||
|
|
8cc9345b42 | ||
|
|
f7528365b0 | ||
|
|
7baa1a345e | ||
|
|
acf7f4fd52 | ||
|
|
f43e8680b8 | ||
|
|
545a6c1b36 | ||
|
|
f01fd8c850 | ||
|
|
c9ec69b0b5 | ||
|
|
3640e2c5f0 | ||
|
|
b3659cb456 | ||
|
|
76284a2916 | ||
|
|
40b1e011bd | ||
|
|
e0bebecd59 | ||
|
|
8ac0cf6831 | ||
|
|
992163206d | ||
|
|
86dd5d8078 | ||
|
|
932aa9d052 | ||
|
|
5f7f5204ec | ||
|
|
a154d23637 | ||
|
|
ac2bb9d362 | ||
|
|
b918958bfa | ||
|
|
215df4ffa6 | ||
|
|
bb28bc5875 | ||
|
|
a82bc3f712 | ||
|
|
b3a507014b | ||
|
|
49c5e35a14 | ||
|
|
869ed33bd4 | ||
|
|
0c4a9be482 | ||
|
|
4410ec575a | ||
|
|
e3b92fc948 | ||
|
|
4ca4692a67 | ||
|
|
c1284d3c23 | ||
|
|
c8c0eadf72 | ||
|
|
f4bbc8abc7 | ||
|
|
a0f6ea57f8 | ||
|
|
88d21a07ac | ||
|
|
88e3a606a0 | ||
|
|
fff693c3f0 | ||
|
|
1e8d792d39 | ||
|
|
dfb149ac6a | ||
|
|
b5cfc92261 | ||
|
|
079f3e3868 | ||
|
|
15a6d58785 | ||
|
|
a404498f8a | ||
|
|
0d133e2df6 | ||
|
|
488b28bfd5 | ||
|
|
0fceb7b2e1 | ||
|
|
a79d1a98e7 | ||
|
|
43434fd445 | ||
|
|
492e523884 | ||
|
|
3d1fdb7a2b | ||
|
|
95a4bf0ec7 | ||
|
|
0d4f261e14 | ||
|
|
e96288b41b | ||
|
|
deda1abcf7 | ||
|
|
ee79d75483 | ||
|
|
0e9e445ddf | ||
|
|
e64720bcd9 | ||
|
|
6e27590b57 | ||
|
|
916c3c7a2a | ||
|
|
8a5fde8ceb | ||
|
|
f5bd9bde7f | ||
|
|
b987f61924 | ||
|
|
482b51a2f9 | ||
|
|
93f2078eda | ||
|
|
158ae11e61 | ||
|
|
d282388266 | ||
|
|
6ecdfcba38 | ||
|
|
88dc8a547e | ||
|
|
58e24b3c11 | ||
|
|
5f1b3a2284 | ||
|
|
31be2584f2 | ||
|
|
a2311e5128 | ||
|
|
e94d42187b | ||
|
|
2b99cc3f62 | ||
|
|
cb7f782893 | ||
|
|
d5a0f8a74b | ||
|
|
2ebd71df24 | ||
|
|
479203f47c | ||
|
|
022b9209d9 | ||
|
|
771c2c868f | ||
|
|
5285a728b1 | ||
|
|
41e6583920 | ||
|
|
cbb60b3a05 | ||
|
|
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 |
18
.allstar/binary_artifacts.yaml
Normal file
18
.allstar/binary_artifacts.yaml
Normal file
@@ -0,0 +1,18 @@
|
||||
# Ignore reason: These crafted binaries are used in tests
|
||||
ignorePaths:
|
||||
- Source/common/testdata/bad_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
- Source/common/testdata/missing_pagezero
|
||||
- Source/common/testdata/32bitplist
|
||||
- Source/common/testdata/BundleExample.app/Contents/MacOS/BundleExample
|
||||
- Source/common/testdata/DirectoryBundle/Contents/MacOS/DirectoryBundle
|
||||
- Source/common/testdata/DirectoryBundle/Contents/Resources/BundleExample.app/Contents/MacOS/BundleExample
|
||||
- Source/santad/testdata/binaryrules/badbinary
|
||||
- Source/santad/testdata/binaryrules/goodbinary
|
||||
- Source/santad/testdata/binaryrules/badcert
|
||||
- Source/santad/testdata/binaryrules/banned_teamid_allowed_binary
|
||||
- Source/santad/testdata/binaryrules/banned_teamid
|
||||
- Source/santad/testdata/binaryrules/goodcert
|
||||
- Source/santad/testdata/binaryrules/noop
|
||||
- Source/santad/testdata/binaryrules/rules.db
|
||||
19
.bazelrc
Normal file
19
.bazelrc
Normal file
@@ -0,0 +1,19 @@
|
||||
build --apple_generate_dsym --define=apple.propagate_embedded_extra_outputs=yes
|
||||
|
||||
build --copt=-Werror
|
||||
build --copt=-Wall
|
||||
build --copt=-Wno-error=deprecated-declarations
|
||||
build --per_file_copt=.*\.mm\$@-std=c++17
|
||||
build --cxxopt=-std=c++17
|
||||
|
||||
build --copt=-DSANTA_OPEN_SOURCE=1
|
||||
build --cxxopt=-DSANTA_OPEN_SOURCE=1
|
||||
|
||||
build:asan --strip=never
|
||||
build:asan --copt="-Wno-macro-redefined"
|
||||
build:asan --copt="-D_FORTIFY_SOURCE=0"
|
||||
build:asan --copt="-O1"
|
||||
build:asan --copt="-fno-omit-frame-pointer"
|
||||
build:asan --copt="-fsanitize=address"
|
||||
build:asan --copt="-DADDRESS_SANITIZER"
|
||||
build:asan --linkopt="-fsanitize=address"
|
||||
1
.bazelversion
Normal file
1
.bazelversion
Normal file
@@ -0,0 +1 @@
|
||||
5.3.0
|
||||
32
.clang-format
Normal file
32
.clang-format
Normal file
@@ -0,0 +1,32 @@
|
||||
Language: ObjC
|
||||
BasedOnStyle: Google
|
||||
|
||||
IndentWidth: 2
|
||||
ObjCBlockIndentWidth: 2
|
||||
ContinuationIndentWidth: 2
|
||||
|
||||
# For ObjC, the line limit is 100
|
||||
ColumnLimit: 100
|
||||
|
||||
# Allow short case statements to be on a single line
|
||||
AllowShortCaseLabelsOnASingleLine: true
|
||||
|
||||
AllowShortLoopsOnASingleLine: false
|
||||
AllowShortFunctionsOnASingleLine: Inline
|
||||
|
||||
# Allow spaces in NSArray/NSDictionary literals @[ and @{
|
||||
SpacesInContainerLiterals: true
|
||||
|
||||
# For pointers, always put the * next to the variable name.
|
||||
DerivePointerAlignment: false
|
||||
PointerAlignment: Right
|
||||
|
||||
|
||||
---
|
||||
Language: Cpp
|
||||
Standard: Cpp11
|
||||
|
||||
BasedOnStyle: Google
|
||||
|
||||
# For C++, the line limit is 80
|
||||
ColumnLimit: 80
|
||||
14
.github/workflows/check-markdown.yml
vendored
Normal file
14
.github/workflows/check-markdown.yml
vendored
Normal file
@@ -0,0 +1,14 @@
|
||||
name: Check Markdown
|
||||
|
||||
on:
|
||||
pull_request:
|
||||
paths:
|
||||
- "**.md"
|
||||
|
||||
jobs:
|
||||
markdown-check:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@master
|
||||
- uses: gaurav-nelson/github-action-markdown-link-check@v1
|
||||
- run: "! git grep -EIn $'[ \t]+$' -- ':(exclude)*.patch'"
|
||||
56
.github/workflows/ci.yml
vendored
Normal file
56
.github/workflows/ci.yml
vendored
Normal file
@@ -0,0 +1,56 @@
|
||||
name: CI
|
||||
|
||||
on:
|
||||
push:
|
||||
branches:
|
||||
- '*'
|
||||
paths:
|
||||
- 'Source/**'
|
||||
pull_request:
|
||||
branches:
|
||||
- main
|
||||
paths:
|
||||
- 'Source/**'
|
||||
|
||||
jobs:
|
||||
lint:
|
||||
runs-on: ubuntu-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run linters
|
||||
run: ./Testing/lint.sh
|
||||
|
||||
build_userspace:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Build Userspace
|
||||
run: bazel build --apple_generate_dsym -c opt :release --define=SANTA_BUILD_TYPE=adhoc
|
||||
|
||||
unit_tests:
|
||||
strategy:
|
||||
fail-fast: false
|
||||
matrix:
|
||||
os: [macos-10.15, macos-11, macos-12]
|
||||
runs-on: ${{ matrix.os }}
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Run All Tests
|
||||
run: bazel test :unit_tests --define=SANTA_BUILD_TYPE=adhoc --test_output=errors
|
||||
|
||||
test_coverage:
|
||||
runs-on: macos-11
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Generate test coverage
|
||||
run: sh ./generate_cov.sh
|
||||
- name: Coveralls
|
||||
uses: coverallsapp/github-action@master
|
||||
with:
|
||||
github-token: ${{ secrets.GITHUB_TOKEN }}
|
||||
path-to-lcov: ./bazel-out/_coverage/_coverage_report.dat
|
||||
flag-name: Unit
|
||||
13
.github/workflows/continuous.yml
vendored
Normal file
13
.github/workflows/continuous.yml
vendored
Normal file
@@ -0,0 +1,13 @@
|
||||
name: continuous
|
||||
on:
|
||||
schedule:
|
||||
- cron: '* 10 * * *' # Every day at 10:00 UTC
|
||||
workflow_dispatch: # Allows you to run this workflow manually from the Actions tab
|
||||
|
||||
jobs:
|
||||
preqs:
|
||||
runs-on: macos-latest
|
||||
steps:
|
||||
- uses: actions/checkout@v2
|
||||
- name: Checks for flaky tests
|
||||
run: bazel test --test_strategy=exclusive --test_output=errors --runs_per_test 50 -t- :unit_tests --define=SANTA_BUILD_TYPE=adhoc
|
||||
24
.gitignore
vendored
24
.gitignore
vendored
@@ -1,8 +1,20 @@
|
||||
.DS_Store
|
||||
Build
|
||||
Dist
|
||||
*.profraw
|
||||
*.provisionprofile
|
||||
bazel-*
|
||||
Pods
|
||||
Santa.xcodeproj/xcuserdata
|
||||
Santa.xcodeproj/project.xcworkspace
|
||||
Santa.xcworkspace/xcuserdata
|
||||
Santa.xcworkspace/xcshareddata
|
||||
Santa.xcodeproj/*
|
||||
Santa.xcworkspace/*
|
||||
CoverageData/*
|
||||
*.tulsiconf-user
|
||||
xcuserdata
|
||||
tulsigen-*
|
||||
*.crt
|
||||
*.key
|
||||
*.pem
|
||||
*.p12
|
||||
*.keychain
|
||||
*.swp
|
||||
compile_commands.json
|
||||
.cache/
|
||||
.vscode/*
|
||||
|
||||
@@ -1,8 +0,0 @@
|
||||
---
|
||||
language: objective-c
|
||||
|
||||
before_install:
|
||||
- gem install cocoapods xcpretty
|
||||
|
||||
script:
|
||||
- xcodebuild -workspace Santa.xcworkspace -scheme All build test CODE_SIGN_IDENTITY='' | xcpretty -sc && exit ${PIPESTATUS[0]}
|
||||
200
BUILD
Normal file
200
BUILD
Normal file
@@ -0,0 +1,200 @@
|
||||
load("@build_bazel_rules_apple//apple:versioning.bzl", "apple_bundle_version")
|
||||
load("//:helper.bzl", "run_command")
|
||||
|
||||
package(default_visibility = ["//:santa_package_group"])
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
exports_files(["LICENSE"])
|
||||
|
||||
# The version label for mac_* rules.
|
||||
apple_bundle_version(
|
||||
name = "version",
|
||||
build_label_pattern = ".*santa_{release}\\.{build}",
|
||||
build_version = "{release}.{build}",
|
||||
capture_groups = {
|
||||
"release": "\\d{4}\\.\\d+",
|
||||
"build": "\\d+",
|
||||
},
|
||||
fallback_build_label = "santa_9999.1.1",
|
||||
short_version_string = "{release}",
|
||||
)
|
||||
|
||||
# Used to detect release builds
|
||||
config_setting(
|
||||
name = "release_build",
|
||||
values = {"define": "SANTA_BUILD_TYPE=release"},
|
||||
visibility = [":santa_package_group"],
|
||||
)
|
||||
|
||||
# Adhoc signed - provisioning profiles are not used.
|
||||
# Used for CI runs and dev builds when SIP is disabled.
|
||||
config_setting(
|
||||
name = "adhoc_build",
|
||||
values = {"define": "SANTA_BUILD_TYPE=adhoc"},
|
||||
visibility = [":santa_package_group"],
|
||||
)
|
||||
|
||||
# Used to detect optimized builds
|
||||
config_setting(
|
||||
name = "opt_build",
|
||||
values = {"compilation_mode": "opt"},
|
||||
)
|
||||
|
||||
package_group(
|
||||
name = "santa_package_group",
|
||||
packages = ["//..."],
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Loading/Unloading/Reloading
|
||||
################################################################################
|
||||
run_command(
|
||||
name = "unload",
|
||||
cmd = """
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.bundleservice.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.metricservice.plist 2>/dev/null
|
||||
sudo launchctl unload /Library/LaunchDaemons/com.google.santa.syncservice.plist 2>/dev/null
|
||||
launchctl unload /Library/LaunchAgents/com.google.santa.plist 2>/dev/null
|
||||
""",
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "load",
|
||||
cmd = """
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
sudo launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
""",
|
||||
)
|
||||
|
||||
run_command(
|
||||
name = "reload",
|
||||
srcs = [
|
||||
"//Source/gui:Santa",
|
||||
],
|
||||
cmd = """
|
||||
set -e
|
||||
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
unzip -d /tmp/bazel_santa_reload \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/bazel-out/*$(COMPILATION_MODE)*/bin/Source/gui/Santa.zip >/dev/null
|
||||
echo "You may be asked for your password for sudo"
|
||||
sudo BINARIES=/tmp/bazel_santa_reload CONF=$${BUILD_WORKSPACE_DIRECTORY}/Conf \
|
||||
$${BUILD_WORKSPACE_DIRECTORY}/Conf/install.sh
|
||||
rm -rf /tmp/bazel_santa_reload
|
||||
echo "Time to stop being naughty"
|
||||
""",
|
||||
)
|
||||
|
||||
################################################################################
|
||||
# Release rules - used to create a release tarball
|
||||
################################################################################
|
||||
genrule(
|
||||
name = "release",
|
||||
srcs = [
|
||||
"//Source/gui:Santa",
|
||||
"Conf/install.sh",
|
||||
"Conf/uninstall.sh",
|
||||
"Conf/com.google.santa.bundleservice.plist",
|
||||
"Conf/com.google.santa.metricservice.plist",
|
||||
"Conf/com.google.santa.syncservice.plist",
|
||||
"Conf/com.google.santad.plist",
|
||||
"Conf/com.google.santa.plist",
|
||||
"Conf/com.google.santa.newsyslog.conf",
|
||||
"Conf/Package/Distribution.xml",
|
||||
"Conf/Package/notarization_tool.sh",
|
||||
"Conf/Package/package_and_sign.sh",
|
||||
"Conf/Package/postinstall",
|
||||
"Conf/Package/preinstall",
|
||||
],
|
||||
outs = ["santa-release.tar.gz"],
|
||||
cmd = select({
|
||||
"//conditions:default": """
|
||||
echo "ERROR: Trying to create a release tarball without optimization."
|
||||
echo "Please add '-c opt' flag to bazel invocation"
|
||||
""",
|
||||
":opt_build": """
|
||||
# Extract Santa.zip
|
||||
for SRC in $(SRCS); do
|
||||
if [ "$$(basename $${SRC})" == "Santa.zip" ]; then
|
||||
mkdir -p $(@D)/binaries
|
||||
unzip -q $${SRC} -d $(@D)/binaries >/dev/null
|
||||
fi
|
||||
done
|
||||
|
||||
# Copy config files
|
||||
for SRC in $(SRCS); do
|
||||
if [[ "$$(dirname $${SRC})" == *"Conf"* ]]; then
|
||||
mkdir -p $(@D)/conf
|
||||
cp -H $${SRC} $(@D)/conf/
|
||||
fi
|
||||
done
|
||||
|
||||
# Gather together the dSYMs. Throw an error if no dSYMs were found
|
||||
for SRC in $(SRCS); do
|
||||
case $${SRC} in
|
||||
*santad.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santad.dSYM
|
||||
;;
|
||||
*santactl.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santactl.dSYM
|
||||
;;
|
||||
*santabundleservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santabundleservice.dSYM
|
||||
;;
|
||||
*santametricservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santametricservice.dSYM
|
||||
;;
|
||||
*santasyncservice.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/santasyncservice.dSYM
|
||||
;;
|
||||
*Santa.app.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/Santa.app.dSYM
|
||||
;;
|
||||
*com.google.santa.daemon.systemextension.dSYM*Info.plist)
|
||||
mkdir -p $(@D)/dsym
|
||||
cp -LR $$(dirname $$(dirname $${SRC})) $(@D)/dsym/com.google.santa.daemon.systemextension.dSYM
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
# Cause a build failure if the dSYMs are missing.
|
||||
if [[ ! -d "$(@D)/dsym" ]]; then
|
||||
echo "dsym dir missing: Did you forget to use --apple_generate_dsym?"
|
||||
echo "This flag is required for the 'release' target."
|
||||
exit 1
|
||||
fi
|
||||
|
||||
# Update all the timestamps to now. Bazel avoids timestamps to allow
|
||||
# builds to be hermetic and cacheable but for releases we want the
|
||||
# timestamps to be more-or-less correct.
|
||||
find $(@D)/{binaries,conf,dsym} -exec touch {} \\;
|
||||
|
||||
# Create final output tar
|
||||
tar -C $(@D) -czpf $(@) binaries dsym conf
|
||||
""",
|
||||
}),
|
||||
heuristic_label_expansion = 0,
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
"//Source/common:unit_tests",
|
||||
"//Source/gui:unit_tests",
|
||||
"//Source/santactl:unit_tests",
|
||||
"//Source/santad:unit_tests",
|
||||
"//Source/santametricservice:unit_tests",
|
||||
"//Source/santasyncservice:unit_tests",
|
||||
],
|
||||
)
|
||||
@@ -1,37 +0,0 @@
|
||||
Want to contribute? Great! First, read this page (including the small print at the end).
|
||||
|
||||
### Before you contribute
|
||||
Before we can use your code, you must sign the
|
||||
[Google Individual Contributor License Agreement](https://developers.google.com/open-source/cla/individual)
|
||||
(CLA), which you can do online. The CLA is necessary mainly because you own the
|
||||
copyright to your changes, even after your contribution becomes part of our
|
||||
codebase, so we need your permission to use and distribute your code. We also
|
||||
need to be sure of various other things—for instance that you'll tell us if you
|
||||
know that your code infringes on other people's patents. You don't have to sign
|
||||
the CLA until after you've submitted your code for review and a member has
|
||||
approved it, but you must do it before we can put your code into our codebase.
|
||||
|
||||
Before you start working on a larger contribution, you should get in touch with
|
||||
us first through the [issue tracker](https://github.com/google/santa/issues)
|
||||
with your idea so that we can help out and possibly guide you. Coordinating up
|
||||
front makes it much easier to avoid frustration later on.
|
||||
|
||||
### Code reviews
|
||||
All submissions, including submissions by project members, require review. We
|
||||
use GitHub pull requests for this purpose. It's also a good idea to run the
|
||||
tests beforehand, which you can do with the following commands:
|
||||
|
||||
```sh
|
||||
rake tests:logic
|
||||
rake tests:kernel # only necessary if you're changing the kext code
|
||||
```
|
||||
### Code Style
|
||||
|
||||
All code submissions should try to match the surrounding code. Wherever possible,
|
||||
code should adhere to either the
|
||||
[Google Objective-C Style Guide](http://google-styleguide.googlecode.com/svn/trunk/objcguide.xml)
|
||||
or the [Google C++ Style Guide](http://google-styleguide.googlecode.com/svn/trunk/cppguide.html).
|
||||
|
||||
### The small print
|
||||
Contributions made by corporations are covered by a different agreement than
|
||||
the one above, the [Software Grant and Corporate Contributor License Agreement](https://developers.google.com/open-source/cla/corporate).
|
||||
1
CONTRIBUTING.md
Symbolic link
1
CONTRIBUTING.md
Symbolic link
@@ -0,0 +1 @@
|
||||
docs/development/contributing.md
|
||||
16
Conf/Package/Distribution.xml
Normal file
16
Conf/Package/Distribution.xml
Normal file
@@ -0,0 +1,16 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<installer-gui-script minSpecVersion="1">
|
||||
<title>Santa</title>
|
||||
<options customize="never" allow-external-scripts="no" hostArchitectures="x86_64,arm64" />
|
||||
|
||||
<choices-outline>
|
||||
<line choice="default" />
|
||||
</choices-outline>
|
||||
|
||||
<choice id="default">
|
||||
<pkg-ref id="com.google.santa"/>
|
||||
</choice>
|
||||
|
||||
<pkg-ref id="com.google.santa">app.pkg</pkg-ref>
|
||||
|
||||
</installer-gui-script>
|
||||
@@ -1,102 +0,0 @@
|
||||
#
|
||||
# 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
|
||||
# | |-- santad
|
||||
# | +-- santactl
|
||||
# |-- conf
|
||||
# | |-- install.sh
|
||||
# | |-- com.google.santad.plist
|
||||
# | |-- com.google.santasync.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-usr-libexec-santad \
|
||||
pack-usr-sbin-santactl \
|
||||
pack-Library-Extensions-santa-driver.kext \
|
||||
pack-applications-Santa.app \
|
||||
pack-Library-LaunchDaemons-com.google.santad.plist \
|
||||
pack-Library-LaunchDaemons-com.google.santasync.plist \
|
||||
pack-Library-LaunchAgents-com.google.santagui.plist \
|
||||
pack-etc-asl-com.google.santa.asl.conf \
|
||||
pack-script-preinstall \
|
||||
pack-script-postinstall
|
||||
|
||||
santad: download
|
||||
santactl: download
|
||||
santa-driver.kext: download
|
||||
Santa.app: download
|
||||
com.google.santad.plist: download
|
||||
com.google.santagui.plist: download
|
||||
com.google.santasync.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-usr-libexec-santad: santad l_usr
|
||||
@sudo mkdir -p ${WORK_D}/usr/libexec
|
||||
@sudo chown root:wheel ${WORK_D}/usr/libexec
|
||||
@sudo chmod 755 ${WORK_D}/usr/libexec
|
||||
@sudo install -m 755 -o root -g wheel santad ${WORK_D}/usr/libexec
|
||||
|
||||
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 santad
|
||||
@rm -f santactl
|
||||
@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 com.google.santasync.plist
|
||||
@rm -f install.sh
|
||||
6
Conf/Package/notarization_tool.sh
Normal file
6
Conf/Package/notarization_tool.sh
Normal file
@@ -0,0 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Example NOTARIZATION_TOOL wrapper.
|
||||
|
||||
/usr/bin/xcrun altool --notarize-app "${2}" --primary-bundle-id "${4}" \
|
||||
-u "${NOTARIZATION_USERNAME}" -p "${NOTARIZATION_PASSWORD}"
|
||||
185
Conf/Package/package_and_sign.sh
Executable file
185
Conf/Package/package_and_sign.sh
Executable file
@@ -0,0 +1,185 @@
|
||||
#!/bin/bash
|
||||
|
||||
# This script signs all of Santa's components, verifies the signatures,
|
||||
# notarizes all of the components, staples them, packages them up, signs the
|
||||
# package, notarizes the package, puts the package in a DMG and notarizes the
|
||||
# DMG. It also outputs a single release tarball.
|
||||
# All of the following environment variables are required.
|
||||
|
||||
# RELEASE_ROOT is a required environment variable that points to the root
|
||||
# of an extracted release tarball produced with the :release and :release_driver
|
||||
# rules in Santa's main BUILD file.
|
||||
[[ -n "${RELEASE_ROOT}" ]] || die "RELEASE_ROOT unset"
|
||||
|
||||
# SIGNING_IDENTITY, SIGNING_TEAMID and SIGNING_KEYCHAIN are required environment
|
||||
# variables specifying the identity and keychain to pass to the codesign tool
|
||||
# and the team ID to use for verification.
|
||||
[[ -n "${SIGNING_IDENTITY}" ]] || die "SIGNING_IDENTITY unset"
|
||||
[[ -n "${SIGNING_TEAMID}" ]] || die "SIGNING_TEAMID unset"
|
||||
[[ -n "${SIGNING_KEYCHAIN}" ]] || die "SIGNING_KEYCHAIN unset"
|
||||
|
||||
# INSTALLER_SIGNING_IDENTITY and INSTALLER_SIGNING_KEYCHAIN are required
|
||||
# environment variables specifying the identity and keychain to use when signing
|
||||
# the distribution package.
|
||||
[[ -n "${INSTALLER_SIGNING_IDENTITY}" ]] || die "INSTALLER_SIGNING_IDENTITY unset"
|
||||
[[ -n "${INSTALLER_SIGNING_KEYCHAIN}" ]] || die "INSTALLER_SIGNING_KEYCHAIN unset"
|
||||
|
||||
# NOTARIZATION_TOOL is a required environment variable pointing to a wrapper
|
||||
# tool around the tool to use for notarization. The tool must take 2 flags:
|
||||
# --file
|
||||
# - pointing at a zip file containing the artifact to notarize
|
||||
# --primary-bundle-id
|
||||
# - specifying the CFBundleID of the artifact being notarized
|
||||
[[ -n "${NOTARIZATION_TOOL}" ]] || die "NOTARIZATION_TOOL unset"
|
||||
|
||||
# ARTIFACTS_DIR is a required environment variable pointing at a directory to
|
||||
# place the output artifacts in.
|
||||
[[ -n "${ARTIFACTS_DIR}" ]] || die "ARTIFACTS_DIR unset"
|
||||
|
||||
################################################################################
|
||||
|
||||
function die {
|
||||
echo "${@}"
|
||||
exit 2
|
||||
}
|
||||
|
||||
readonly INPUT_APP="${RELEASE_ROOT}/binaries/Santa.app"
|
||||
readonly INPUT_SYSX="${INPUT_APP}/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension"
|
||||
readonly INPUT_SANTACTL="${INPUT_APP}/Contents/MacOS/santactl"
|
||||
readonly INPUT_SANTABS="${INPUT_APP}/Contents/MacOS/santabundleservice"
|
||||
readonly INPUT_SANTAMS="${INPUT_APP}/Contents/MacOS/santametricservice"
|
||||
readonly INPUT_SANTASS="${INPUT_APP}/Contents/MacOS/santasyncservice"
|
||||
|
||||
readonly RELEASE_NAME="santa-$(/usr/bin/defaults read "${INPUT_APP}/Contents/Info.plist" CFBundleShortVersionString)"
|
||||
|
||||
readonly SCRATCH=$(/usr/bin/mktemp -d "${TMPDIR}/santa-"XXXXXX)
|
||||
readonly APP_PKG_ROOT="${SCRATCH}/app_pkg_root"
|
||||
readonly APP_PKG_SCRIPTS="${SCRATCH}/pkg_scripts"
|
||||
readonly ENTITLEMENTS="${SCRATCH}/entitlements"
|
||||
|
||||
readonly SCRIPT_PATH="$(/usr/bin/dirname -- ${BASH_SOURCE[0]})"
|
||||
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}" "${APP_PKG_SCRIPTS}" "${ENTITLEMENTS}"
|
||||
|
||||
readonly DMG_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.dmg"
|
||||
readonly TAR_PATH="${ARTIFACTS_DIR}/${RELEASE_NAME}.tar.gz"
|
||||
|
||||
# Sign all of binaries/bundles. Maintain inside-out ordering where necessary
|
||||
for ARTIFACT in "${INPUT_SANTACTL}" "${INPUT_SANTABS}" "${INPUT_SANTAMS}" "${INPUT_SANTASS}" "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
EN="${ENTITLEMENTS}/${BN}.entitlements"
|
||||
|
||||
echo "extracting ${BN} entitlements"
|
||||
/usr/bin/codesign -d --entitlements "${EN}" "${ARTIFACT}"
|
||||
if [[ -s "${EN}" ]]; then
|
||||
EN="--entitlements ${EN}"
|
||||
else
|
||||
EN=""
|
||||
fi
|
||||
|
||||
echo "codesigning ${BN}"
|
||||
/usr/bin/codesign --sign "${SIGNING_IDENTITY}" --keychain "${SIGNING_KEYCHAIN}" \
|
||||
${EN} --timestamp --force --generate-entitlement-der \
|
||||
--options library,kill,runtime "${ARTIFACT}"
|
||||
done
|
||||
|
||||
# Notarize all the bundles
|
||||
for ARTIFACT in "${INPUT_SYSX}" "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "zipping ${BN}"
|
||||
/usr/bin/zip -9r "${SCRATCH}/${BN}.zip" "${ARTIFACT}"
|
||||
|
||||
echo "notarizing ${BN}"
|
||||
PBID=$(/usr/bin/defaults read "${ARTIFACT}/Contents/Info.plist" CFBundleIdentifier)
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${BN}.zip" --primary-bundle-id "${PBID}"
|
||||
done
|
||||
|
||||
# Staple the App.
|
||||
for ARTIFACT in "${INPUT_APP}"; do
|
||||
BN=$(/usr/bin/basename "${ARTIFACT}")
|
||||
|
||||
echo "stapling ${BN}"
|
||||
/usr/bin/xcrun stapler staple "${ARTIFACT}"
|
||||
done
|
||||
|
||||
# Ensure _CodeSignature/CodeResources files have 0644 permissions so they can
|
||||
# be verified without using sudo.
|
||||
/usr/bin/find "${RELEASE_ROOT}/binaries" -type f -name CodeResources -exec chmod 0644 {} \;
|
||||
/usr/bin/find "${RELEASE_ROOT}/binaries" -type d -exec chmod 0755 {} \;
|
||||
/usr/bin/find "${RELEASE_ROOT}/conf" -type f -name "com.google.santa*" -exec chmod 0644 {} \;
|
||||
|
||||
echo "verifying signatures"
|
||||
/usr/bin/codesign -vv -R="certificate leaf[subject.OU] = ${SIGNING_TEAMID}" \
|
||||
"${RELEASE_ROOT}/binaries/"* || die "bad signature"
|
||||
|
||||
echo "creating fresh release tarball"
|
||||
/bin/mkdir -p "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/binaries" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/conf" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/bin/cp -r "${RELEASE_ROOT}/dsym" "${SCRATCH}/tar_root/${RELEASE_NAME}"
|
||||
/usr/bin/tar -C "${SCRATCH}/tar_root" -czvf "${TAR_PATH}" "${RELEASE_NAME}" || die "failed to create release tarball"
|
||||
|
||||
echo "creating app pkg"
|
||||
/bin/mkdir -p "${APP_PKG_ROOT}/Applications" \
|
||||
"${APP_PKG_ROOT}/Library/LaunchAgents" \
|
||||
"${APP_PKG_ROOT}/Library/LaunchDaemons" \
|
||||
"${APP_PKG_ROOT}/private/etc/asl" \
|
||||
"${APP_PKG_ROOT}/private/etc/newsyslog.d"
|
||||
/bin/cp -vXR "${RELEASE_ROOT}/binaries/Santa.app" "${APP_PKG_ROOT}/Applications/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santad.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.plist" "${APP_PKG_ROOT}/Library/LaunchAgents/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.bundleservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.metricservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.syncservice.plist" "${APP_PKG_ROOT}/Library/LaunchDaemons/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.asl.conf" "${APP_PKG_ROOT}/private/etc/asl/"
|
||||
/bin/cp -vX "${RELEASE_ROOT}/conf/com.google.santa.newsyslog.conf" "${APP_PKG_ROOT}/private/etc/newsyslog.d/"
|
||||
/bin/cp -vXL "${SCRIPT_PATH}/preinstall" "${APP_PKG_SCRIPTS}/"
|
||||
/bin/cp -vXL "${SCRIPT_PATH}/postinstall" "${APP_PKG_SCRIPTS}/"
|
||||
/bin/chmod +x "${APP_PKG_SCRIPTS}/"*
|
||||
|
||||
# Disable bundle relocation.
|
||||
/usr/bin/pkgbuild --analyze --root "${APP_PKG_ROOT}" "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsRelocatable -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleIsVersionChecked -bool NO "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace BundleOverwriteAction -string upgrade "${SCRATCH}/component.plist"
|
||||
/usr/bin/plutil -replace ChildBundles -json "[]" "${SCRATCH}/component.plist"
|
||||
|
||||
# Build app package
|
||||
/usr/bin/pkgbuild --identifier "com.google.santa" \
|
||||
--version "$(echo "${RELEASE_NAME}" | cut -d - -f2)" \
|
||||
--root "${APP_PKG_ROOT}" \
|
||||
--component-plist "${SCRATCH}/component.plist" \
|
||||
--scripts "${APP_PKG_SCRIPTS}" \
|
||||
"${SCRATCH}/app.pkg"
|
||||
|
||||
# Build signed distribution package
|
||||
echo "productbuild pkg"
|
||||
/bin/mkdir -p "${SCRATCH}/${RELEASE_NAME}"
|
||||
/usr/bin/productbuild \
|
||||
--distribution "${SCRIPT_PATH}/Distribution.xml" \
|
||||
--package-path "${SCRATCH}" \
|
||||
--sign "${INSTALLER_SIGNING_IDENTITY}" --keychain "${INSTALLER_SIGNING_KEYCHAIN}" \
|
||||
"${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg"
|
||||
|
||||
echo "verifying pkg signature"
|
||||
/usr/sbin/pkgutil --check-signature "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "bad pkg signature"
|
||||
|
||||
echo "notarizing pkg"
|
||||
"${NOTARIZATION_TOOL}" --file "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" \
|
||||
--primary-bundle-id "com.google.santa"
|
||||
|
||||
echo "stapling pkg"
|
||||
/usr/bin/xcrun stapler staple "${SCRATCH}/${RELEASE_NAME}/${RELEASE_NAME}.pkg" || die "failed to staple pkg"
|
||||
|
||||
echo "wrapping pkg in dmg"
|
||||
/usr/bin/hdiutil create -fs HFS+ -format UDZO \
|
||||
-volname "${RELEASE_NAME}" \
|
||||
-ov -imagekey zlib-level=9 \
|
||||
-srcfolder "${SCRATCH}/${RELEASE_NAME}/" "${DMG_PATH}" || die "failed to wrap pkg in dmg"
|
||||
|
||||
echo "notarizing dmg"
|
||||
"${NOTARIZATION_TOOL}" --file "${DMG_PATH}" --primary-bundle-id "com.google.santa"
|
||||
|
||||
echo "stapling dmg"
|
||||
/usr/bin/xcrun stapler staple "${DMG_PATH}" || die "failed to staple dmg"
|
||||
@@ -1,6 +1,6 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Load the kernel extension, santad, sync client
|
||||
# Load com.google.santa.daemon and com.google.santa.bundleservice
|
||||
# If a user is logged in, also load the GUI agent.
|
||||
# If the target volume is not /, do nothing
|
||||
|
||||
@@ -9,17 +9,28 @@
|
||||
# Restart syslogd to pick up ASL configuration change
|
||||
/usr/bin/killall -HUP syslogd
|
||||
|
||||
/sbin/kextload /Library/Extensions/santa-driver.kext
|
||||
# Create hopefully useful symlink for santactl
|
||||
mkdir -p /usr/local/bin
|
||||
/bin/ln -sf /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin/santactl
|
||||
|
||||
sleep 1
|
||||
# Remove the kext before com.google.santa.daemon loads if the SystemExtension is already present.
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && rm -rf /Library/Extensions/santa-driver.kext
|
||||
|
||||
# Load com.google.santa.daemon, its main has logic to handle loading the kext
|
||||
# or relaunching itself as a SystemExtension.
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santasync.plist
|
||||
|
||||
sleep 1
|
||||
# Load com.google.santa.bundleservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
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
|
||||
# Load com.google.santa.metricservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
|
||||
# Load com.google.santa.syncservice
|
||||
/bin/launchctl load -w /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl load /Library/LaunchAgents/com.google.santa.plist
|
||||
exit 0
|
||||
|
||||
@@ -6,16 +6,28 @@
|
||||
|
||||
[[ $3 != "/" ]] && exit 0
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
/bin/launchctl remove com.google.santad || true
|
||||
/bin/launchctl remove com.google.santa.bundleservice || true
|
||||
/bin/launchctl remove com.google.santa.metricservice || true
|
||||
/bin/launchctl remove com.google.santa.syncservice || true
|
||||
|
||||
/bin/sleep 1
|
||||
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1 || true
|
||||
|
||||
# Remove cruft from old Santa versions
|
||||
/bin/rm -f /usr/libexec/santad
|
||||
/bin/rm -f /usr/sbin/santactl
|
||||
/bin/launchctl remove com.google.santasync
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santasync.plist
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
|
||||
sleep 1
|
||||
/bin/sleep 1
|
||||
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
sleep 1
|
||||
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santagui
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santa
|
||||
exit 0
|
||||
|
||||
@@ -1,4 +0,0 @@
|
||||
# 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
|
||||
26
Conf/com.google.santa.bundleservice.plist
Normal file
26
Conf/com.google.santa.bundleservice.plist
Normal file
@@ -0,0 +1,26 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.bundleservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santabundleservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.bundleservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<false/>
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
<key>ThrottleInterval</key>
|
||||
<integer>0</integer>
|
||||
</dict>
|
||||
</plist>
|
||||
22
Conf/com.google.santa.metricservice.plist
Normal file
22
Conf/com.google.santa.metricservice.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.metricservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santametricservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.metricservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
</dict>
|
||||
</plist>
|
||||
2
Conf/com.google.santa.newsyslog.conf
Normal file
2
Conf/com.google.santa.newsyslog.conf
Normal file
@@ -0,0 +1,2 @@
|
||||
# logfilename [owner:group] mode count size(KiB) when flags [/pid_file] # [sig_num]
|
||||
/var/db/santa/santa.log root:wheel 644 10 25000 * Z
|
||||
@@ -3,12 +3,15 @@
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santagui</string>
|
||||
<string>com.google.santa</string>
|
||||
<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>
|
||||
22
Conf/com.google.santa.syncservice.plist
Normal file
22
Conf/com.google.santa.syncservice.plist
Normal file
@@ -0,0 +1,22 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<key>Label</key>
|
||||
<string>com.google.santa.syncservice</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/MacOS/santasyncservice</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.syncservice</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<false/>
|
||||
<key>KeepAlive</key>
|
||||
<false/>
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -1,29 +1,24 @@
|
||||
<?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">
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//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.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/usr/libexec/santad</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>SantaXPCNotifications</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>Label</key>
|
||||
<string>com.google.santad</string>
|
||||
<key>ProgramArguments</key>
|
||||
<array>
|
||||
<string>/Applications/Santa.app/Contents/Library/SystemExtensions/com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon</string>
|
||||
<string>--syslog</string>
|
||||
</array>
|
||||
<key>MachServices</key>
|
||||
<dict>
|
||||
<key>com.google.santa.daemon</key>
|
||||
<true/>
|
||||
</dict>
|
||||
<key>RunAtLoad</key>
|
||||
<true/>
|
||||
<key>KeepAlive</key>
|
||||
<true/>
|
||||
<key>ProcessType</key>
|
||||
<string>Interactive</string>
|
||||
</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>
|
||||
@@ -1,12 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||
<plist version="1.0">
|
||||
<dict>
|
||||
<!-- Minimal Configuration -->
|
||||
<key>ClientMode</key>
|
||||
<integer>1</integer>
|
||||
|
||||
<!-- For documentation of other keys, see the following URL:
|
||||
https://github.com/google/santa/wiki/Configuration-Keys -->
|
||||
</dict>
|
||||
</plist>
|
||||
@@ -5,51 +5,84 @@ if [[ $EUID -ne 0 ]]; then
|
||||
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
|
||||
if [[ -z "${BINARIES}" || -z "${CONF}" ]]; then
|
||||
if [[ -d "binaries" ]]; then
|
||||
BINARIES="${PWD}/binaries"
|
||||
CONF="${PWD}/conf"
|
||||
elif [[ -d "../binaries" ]]; then
|
||||
BINARIES="${PWD}/../binaries"
|
||||
CONF="${PWD}/../conf"
|
||||
else
|
||||
echo "Can't find binaries, run install.sh from inside the conf directory" 1>&2
|
||||
exit 1
|
||||
fi
|
||||
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
|
||||
/bin/launchctl remove com.google.santasync >/dev/null 2>&1
|
||||
|
||||
# Unload bundle service
|
||||
/bin/launchctl remove com.google.santa.bundleservice >/dev/null 2>&1
|
||||
|
||||
# Unload metric service
|
||||
/bin/launchctl remove com.google.santa.metricservice >/dev/null 2>&1
|
||||
|
||||
# Unload sync service
|
||||
/bin/launchctl remove com.google.santa.syncservice >/dev/null 2>&1
|
||||
|
||||
# Unload kext.
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
|
||||
# Determine if anyone is logged into the GUI
|
||||
GUI_USER=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
|
||||
# Unload GUI agent if someone is logged in.
|
||||
[[ -n "${GUI_USER}" ]] && \
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santagui
|
||||
[[ -n "$GUI_USER" ]] && \
|
||||
/bin/launchctl asuser ${GUI_USER} /bin/launchctl remove /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl remove com.google.santa
|
||||
|
||||
# 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
|
||||
/bin/rm -rf /Applications/Santa.app 2>&1
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext 2>&1
|
||||
/bin/rm /etc/asl/com.google.santa.asl.conf
|
||||
|
||||
# Copy new files.
|
||||
/bin/cp ${SOURCE}/binaries/santad /usr/libexec
|
||||
/bin/cp ${SOURCE}/binaries/santactl /usr/sbin
|
||||
/bin/cp -r ${SOURCE}/binaries/santa-driver.kext /Library/Extensions
|
||||
/bin/cp -r ${SOURCE}/binaries/Santa.app /Applications
|
||||
/bin/mkdir -p /var/db/santa
|
||||
|
||||
/bin/cp ${SOURCE}/conf/com.google.{santad,santasync}.plist /Library/LaunchDaemons
|
||||
/bin/cp ${SOURCE}/conf/com.google.santagui.plist /Library/LaunchAgents
|
||||
/bin/cp ${SOURCE}/conf/com.google.santa.asl.conf /etc/asl/
|
||||
/bin/cp -r ${BINARIES}/Santa.app /Applications
|
||||
|
||||
/bin/mkdir -p /usr/local/bin
|
||||
/bin/ln -s /Applications/Santa.app/Contents/MacOS/santactl /usr/local/bin 2>/dev/null
|
||||
|
||||
/bin/cp ${CONF}/com.google.santa.plist /Library/LaunchAgents
|
||||
/bin/cp ${CONF}/com.google.santa.bundleservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.metricservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.syncservice.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santad.plist /Library/LaunchDaemons
|
||||
/bin/cp ${CONF}/com.google.santa.newsyslog.conf /etc/newsyslog.d/
|
||||
|
||||
# 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.
|
||||
# Load com.google.santa.daemon
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santasync.plist
|
||||
|
||||
# Load com.google.santa.bundleservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
|
||||
# Load com.google.santa.metricservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
|
||||
# Load com.google.santa.syncservice
|
||||
/bin/launchctl load /Library/LaunchDaemons/com.google.santa.syncservice.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
|
||||
[[ -z "${GUI_USER}" ]] && exit 0
|
||||
|
||||
/bin/launchctl asuser "${GUI_USER}" /bin/launchctl load -w /Library/LaunchAgents/com.google.santa.plist
|
||||
exit 0
|
||||
|
||||
40
Conf/uninstall.sh
Executable file
40
Conf/uninstall.sh
Executable file
@@ -0,0 +1,40 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Uninstalls Santa from the boot volume, clearing up everything but logs/configs.
|
||||
# Unloads the kernel extension, services, and deletes component files.
|
||||
# If a user is logged in, also unloads the GUI agent.
|
||||
|
||||
[ "$EUID" != 0 ] && printf "%s\n" "This requires running as root/sudo." && exit 1
|
||||
|
||||
# For macOS 10.15+ this will block up to 60 seconds
|
||||
/bin/launchctl list EQHXZ8M8AV.com.google.santa.daemon > /dev/null 2>&1 && /Applications/Santa.app/Contents/MacOS/Santa --unload-system-extension
|
||||
|
||||
/bin/launchctl remove com.google.santad
|
||||
# remove helper XPC services
|
||||
/bin/launchctl remove com.google.santa.bundleservice
|
||||
/bin/launchctl remove com.google.santa.metricservice
|
||||
/bin/launchctl remove com.google.santa.syncservice
|
||||
sleep 1
|
||||
/sbin/kextunload -b com.google.santa-driver >/dev/null 2>&1
|
||||
user=$(/usr/bin/stat -f '%u' /dev/console)
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santagui
|
||||
[[ -n "$user" ]] && /bin/launchctl asuser ${user} /bin/launchctl remove com.google.santa
|
||||
# and to clean out the log config, although it won't write after wiping the binary
|
||||
/usr/bin/killall -HUP syslogd
|
||||
# delete artifacts on-disk
|
||||
/bin/rm -rf /Applications/Santa.app
|
||||
/bin/rm -rf /Library/Extensions/santa-driver.kext
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santagui.plist
|
||||
/bin/rm -f /Library/LaunchAgents/com.google.santa.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santad.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.bundleservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.metricservice.plist
|
||||
/bin/rm -f /Library/LaunchDaemons/com.google.santa.syncservice.plist
|
||||
/bin/rm -f /private/etc/asl/com.google.santa.asl.conf
|
||||
/bin/rm -f /private/etc/newsyslog.d/com.google.santa.newsyslog.conf
|
||||
/bin/rm -f /usr/local/bin/santactl # just a symlink
|
||||
|
||||
#uncomment to remove the config file and all databases, log files
|
||||
#/bin/rm -rf /var/db/santa
|
||||
#/bin/rm -f /var/log/santa*
|
||||
exit 0
|
||||
4
Fuzzing/libFuzzer/.gitignore
vendored
Normal file
4
Fuzzing/libFuzzer/.gitignore
vendored
Normal file
@@ -0,0 +1,4 @@
|
||||
bin
|
||||
llvm-*.src
|
||||
llvm-*.src.tar.xz
|
||||
|
||||
109
Fuzzing/libFuzzer/build.sh
Executable file
109
Fuzzing/libFuzzer/build.sh
Executable file
@@ -0,0 +1,109 @@
|
||||
#!/usr/bin/env bash
|
||||
|
||||
LLVM_VERSION='5.0.1'
|
||||
LLVM_COMPILERRT_TARBALL_NAME="llvm-${LLVM_VERSION}.src.tar.xz"
|
||||
LLVM_COMPILERRT_SRC_FOLDER_NAME=`echo "${LLVM_COMPILERRT_TARBALL_NAME}" | cut -d '.' -f 1-4`
|
||||
LLVM_COMPILERRT_TARBALL_URL="http://releases.llvm.org/${LLVM_VERSION}/${LLVM_COMPILERRT_TARBALL_NAME}"
|
||||
|
||||
LIBFUZZER_FOLDER="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
|
||||
LOG_FILE=`mktemp`
|
||||
|
||||
main() {
|
||||
echo "libFuzzer build script"
|
||||
|
||||
echo " > Checking dependencies..."
|
||||
checkDependencies || return 1
|
||||
|
||||
echo " > Entering libFuzzer folder..."
|
||||
cd "${LIBFUZZER_FOLDER}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "Failed to enter the libFuzzer folder: ${LIBFUZZER_FOLDER}"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ ! -f "${LLVM_COMPILERRT_TARBALL_NAME}" ] ; then
|
||||
echo " > Downloading the LLVM tarball..."
|
||||
curl "${LLVM_COMPILERRT_TARBALL_URL}" -o "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to download the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
else
|
||||
echo " > An existing LLVM tarball was found"
|
||||
fi
|
||||
|
||||
if [ -d "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" ] ; then
|
||||
echo " > Deleting existing LLVM folder..."
|
||||
rm -rf "${LLVM_COMPILERRT_SRC_FOLDER_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing source folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
echo " > Extracting the LLVM tarball..."
|
||||
tar xf "${LLVM_COMPILERRT_TARBALL_NAME}" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
rm "${LLVM_COMPILERRT_TARBALL_NAME}" "${LLVM_COMPILERRT_SRC_FOLDER_NAME}"
|
||||
dumpLogFile "Failed to extract the LLVM tarball"
|
||||
return 1
|
||||
fi
|
||||
|
||||
if [ -d "bin" ] ; then
|
||||
echo " > Deleting existing bin folder..."
|
||||
rm -rf "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to delete the existing bin folder"
|
||||
return 1
|
||||
fi
|
||||
fi
|
||||
|
||||
mkdir "bin" > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to create the bin folder"
|
||||
return 1
|
||||
fi
|
||||
|
||||
echo " > Building libFuzzer..."
|
||||
( cd "bin" && "../${LLVM_COMPILERRT_SRC_FOLDER_NAME}/lib/Fuzzer/build.sh" ) > "${LOG_FILE}" 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
dumpLogFile "Failed to build the library"
|
||||
return 1
|
||||
fi
|
||||
|
||||
printf "\nFinished building libFuzzer\n"
|
||||
rm "${LOG_FILE}"
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
checkDependencies() {
|
||||
executable_list=( "clang++" "curl" "tar" )
|
||||
|
||||
for executable in "${executable_list[@]}" ; do
|
||||
which "${executable}" > /dev/null 2>&1
|
||||
if [ $? -ne 0 ] ; then
|
||||
echo "The following program was not found: ${executable}"
|
||||
return 1
|
||||
fi
|
||||
done
|
||||
|
||||
return 0
|
||||
}
|
||||
|
||||
dumpLogFile() {
|
||||
if [ $# -eq 1 ] ; then
|
||||
local message="$1"
|
||||
else
|
||||
local message="An error has occurred"
|
||||
fi
|
||||
|
||||
printf "${message}\n"
|
||||
printf "Log file follows\n===\n"
|
||||
cat "${LOG_FILE}"
|
||||
printf "\n===\n"
|
||||
rm "${LOG_FILE}"
|
||||
}
|
||||
|
||||
main $@
|
||||
exit $?
|
||||
3
Fuzzing/santacache/.gitignore
vendored
Normal file
3
Fuzzing/santacache/.gitignore
vendored
Normal file
@@ -0,0 +1,3 @@
|
||||
santacache.dSYM
|
||||
santacache
|
||||
|
||||
BIN
Fuzzing/santacache/santacache_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santacache/santacache_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
43
Fuzzing/santacache/src/main.cpp
Normal file
43
Fuzzing/santacache/src/main.cpp
Normal file
@@ -0,0 +1,43 @@
|
||||
/// Copyright 2018 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 <SantaCache.h>
|
||||
|
||||
#include <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data,
|
||||
std::size_t size) {
|
||||
static SantaCache<uint64_t, uint64_t> decision_cache(5000, 2);
|
||||
|
||||
std::uint64_t fields[2] = {};
|
||||
|
||||
if (size > 16) {
|
||||
std::cout << "Invalid size! Start with -max_len=16\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
std::memcpy(fields, data, size);
|
||||
|
||||
decision_cache.set(fields[0], fields[1]);
|
||||
auto returned_value = decision_cache.get(fields[0]);
|
||||
|
||||
if (returned_value != fields[1]) {
|
||||
std::cout << fields[0] << ", " << fields[1] << " -> " << returned_value
|
||||
<< "\n";
|
||||
return 1;
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
16
Fuzzing/santactl/santactl_fuzzer_seed_corpus/example01
Normal file
16
Fuzzing/santactl/santactl_fuzzer_seed_corpus/example01
Normal file
@@ -0,0 +1,16 @@
|
||||
{
|
||||
"rules": [
|
||||
{
|
||||
"rule_type": "BINARY",
|
||||
"policy": "BLACKLIST",
|
||||
"sha256": "2dc104631939b4bdf5d6bccab76e166e37fe5e1605340cf68dab919df58b8eda",
|
||||
"custom_msg": "blacklist firefox"
|
||||
},
|
||||
{
|
||||
"rule_type": "CERTIFICATE",
|
||||
"policy": "BLACKLIST",
|
||||
"sha256": "e7726cf87cba9e25139465df5bd1557c8a8feed5c7dd338342d8da0959b63c8d",
|
||||
"custom_msg": "blacklist dash app certificate"
|
||||
}
|
||||
]
|
||||
}
|
||||
62
Fuzzing/santactl/src/main.mm
Normal file
62
Fuzzing/santactl/src/main.mm
Normal file
@@ -0,0 +1,62 @@
|
||||
/// Copyright 2018 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 <cstdint>
|
||||
#include <iostream>
|
||||
#include <vector>
|
||||
|
||||
#include <SNTRule.h>
|
||||
#include <SNTSyncConstants.h>
|
||||
#include <SNTSyncRuleDownload.h>
|
||||
#include <SNTSyncState.h>
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
NSData *buffer = [NSData dataWithBytes:static_cast<const void *>(data) length:size];
|
||||
if (!buffer) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
NSError *error;
|
||||
NSDictionary *response = [NSJSONSerialization JSONObjectWithData:buffer options:0 error:&error];
|
||||
if (!response) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (![response isKindOfClass:[NSDictionary class]]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
if (![response objectForKey:kRules]) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTSyncState *state = [[SNTSyncState alloc] init];
|
||||
if (!state) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
SNTSyncRuleDownload *obj = [[SNTSyncRuleDownload alloc] initWithState:state];
|
||||
if (!obj) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
for (NSDictionary *ruleDict in response[kRules]) {
|
||||
SNTRule *rule = [obj ruleFromDictionary:ruleDict];
|
||||
if (rule) {
|
||||
std::cerr << "Rule: " << [[rule description] UTF8String] << "\n";
|
||||
}
|
||||
}
|
||||
|
||||
return 0;
|
||||
}
|
||||
BIN
Fuzzing/santad/santad_checkCacheForVnodeID_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santad/santad_checkCacheForVnodeID_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
@@ -0,0 +1 @@
|
||||
К'.p▒└G╗М┐║ЙSЮ╝и▌РУерЭxt1iАЫШ9ы*H╩4R"═©$-├Уww╙+Р╝╘[┼иу╧oС┬ОwRpЗя≤х°е
|
||||
BIN
Fuzzing/santad/santad_databaseRuleAddRules_fuzzer_seed_corpus/example01
Executable file
BIN
Fuzzing/santad/santad_databaseRuleAddRules_fuzzer_seed_corpus/example01
Executable file
Binary file not shown.
58
Fuzzing/santad/src/checkCacheForVnodeID.mm
Normal file
58
Fuzzing/santad/src/checkCacheForVnodeID.mm
Normal file
@@ -0,0 +1,58 @@
|
||||
/// Copyright 2018 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 <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > 16) {
|
||||
std::cerr << "Invalid buffer size of " << size << " (should be <= 16)" << std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
santa_vnode_id_t vnodeID = {};
|
||||
std::memcpy(&vnodeID, data, size);
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
[[daemonConn remoteObjectProxy]
|
||||
checkCacheForVnodeID:vnodeID
|
||||
withReply:^(santa_action_t action) {
|
||||
if (action == ACTION_RESPOND_ALLOW) {
|
||||
std::cerr << "File exists in [whitelist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_RESPOND_DENY) {
|
||||
std::cerr << "File exists in [blacklist] kernel cache" << std::endl;
|
||||
;
|
||||
} else if (action == ACTION_UNSET) {
|
||||
std::cerr << "File does not exist in cache" << std::endl;
|
||||
;
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
51
Fuzzing/santad/src/databaseRemoveEventsWithIDs.mm
Normal file
51
Fuzzing/santad/src/databaseRemoveEventsWithIDs.mm
Normal file
@@ -0,0 +1,51 @@
|
||||
/// Copyright 2018 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 <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
auto *eventId = reinterpret_cast<const std::uint64_t *>(data);
|
||||
std::size_t eventIdCount = size / sizeof(std::uint64_t);
|
||||
if (eventIdCount == 0) {
|
||||
return 0;
|
||||
}
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
|
||||
NSMutableSet *eventIds = [NSMutableSet setWithCapacity:eventIdCount];
|
||||
for (std::size_t i = 0; i < eventIdCount; i++) {
|
||||
auto id = [NSNumber numberWithInteger:eventId[i]];
|
||||
[eventIds addObject:id];
|
||||
}
|
||||
|
||||
[[daemonConn remoteObjectProxy] databaseRemoveEventsWithIDs:[eventIds allObjects]];
|
||||
return 0;
|
||||
}
|
||||
73
Fuzzing/santad/src/databaseRuleAddRules.mm
Normal file
73
Fuzzing/santad/src/databaseRuleAddRules.mm
Normal file
@@ -0,0 +1,73 @@
|
||||
/// Copyright 2018 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 <cstdint>
|
||||
#include <iostream>
|
||||
|
||||
#import <MOLXPCConnection/MOLXPCConnection.h>
|
||||
|
||||
#import "SNTCommandController.h"
|
||||
#import "SNTRule.h"
|
||||
#import "SNTXPCControlInterface.h"
|
||||
|
||||
#pragma pack(push, 1)
|
||||
|
||||
struct InputData {
|
||||
std::uint32_t cleanSlate;
|
||||
std::uint32_t state;
|
||||
std::uint32_t type;
|
||||
char hash[33];
|
||||
};
|
||||
|
||||
#pragma pack(pop)
|
||||
|
||||
extern "C" int LLVMFuzzerTestOneInput(const std::uint8_t *data, std::size_t size) {
|
||||
if (size > sizeof(InputData)) {
|
||||
std::cerr << "Invalid buffer size of " << size << " (should be <= " << sizeof(InputData) << ")"
|
||||
<< std::endl;
|
||||
|
||||
return 1;
|
||||
}
|
||||
|
||||
InputData input_data = {};
|
||||
std::memcpy(&input_data, data, size);
|
||||
|
||||
SNTRule *newRule = [[SNTRule alloc] init];
|
||||
newRule.state = (SNTRuleState)input_data.state;
|
||||
newRule.type = (SNTRuleType)input_data.type;
|
||||
newRule.identifier = @(input_data.hash);
|
||||
newRule.customMsg = @"";
|
||||
|
||||
MOLXPCConnection *daemonConn = [SNTXPCControlInterface configuredConnection];
|
||||
daemonConn.invalidationHandler = ^{
|
||||
printf("An error occurred communicating with the daemon, is it running?\n");
|
||||
exit(1);
|
||||
};
|
||||
|
||||
[daemonConn resume];
|
||||
[[daemonConn remoteObjectProxy]
|
||||
databaseRuleAddRules:@[ newRule ]
|
||||
cleanSlate:NO
|
||||
reply:^(NSError *error) {
|
||||
if (!error) {
|
||||
if (newRule.state == SNTRuleStateRemove) {
|
||||
printf("Removed rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
|
||||
} else {
|
||||
printf("Added rule for SHA-256: %s.\n", [newRule.identifier UTF8String]);
|
||||
}
|
||||
}
|
||||
}];
|
||||
|
||||
return 0;
|
||||
}
|
||||
24
Podfile
24
Podfile
@@ -1,24 +0,0 @@
|
||||
platform :osx, "10.9"
|
||||
|
||||
inhibit_all_warnings!
|
||||
|
||||
target :santad do
|
||||
pod 'FMDB'
|
||||
|
||||
post_install do |rep|
|
||||
rep.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
|
||||
end
|
||||
end
|
||||
end
|
||||
|
||||
target :LogicTests do
|
||||
pod 'OCMock'
|
||||
pod 'FMDB'
|
||||
end
|
||||
17
Podfile.lock
17
Podfile.lock
@@ -1,17 +0,0 @@
|
||||
PODS:
|
||||
- FMDB (2.5):
|
||||
- FMDB/standard (= 2.5)
|
||||
- FMDB/common (2.5)
|
||||
- FMDB/standard (2.5):
|
||||
- FMDB/common
|
||||
- OCMock (3.1.2)
|
||||
|
||||
DEPENDENCIES:
|
||||
- FMDB
|
||||
- OCMock
|
||||
|
||||
SPEC CHECKSUMS:
|
||||
FMDB: 96e8f1bcc1329e269330f99770ad4285d9003e52
|
||||
OCMock: a10ea9f0a6e921651f96f78b6faee95ebc813b92
|
||||
|
||||
COCOAPODS: 0.36.3
|
||||
226
README.md
226
README.md
@@ -1,140 +1,144 @@
|
||||
Santa [](https://travis-ci.org/google/santa)
|
||||
=====
|
||||
# Santa [](https://github.com/google/santa/actions/workflows/ci.yml)
|
||||
|
||||
Santa is a binary whitelisting/blacklisting system for Mac 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
|
||||
managing the system and synchronizing the database with a server.
|
||||
<p align="center">
|
||||
<img src="https://raw.githubusercontent.com/google/santa/main/Source/gui/Resources/Images.xcassets/AppIcon.appiconset/santa-hat-icon-128.png" alt="Santa Icon" />
|
||||
</p>
|
||||
|
||||
Santa is not yet a 1.0. We're writing more tests, fixing bugs, working on TODOs
|
||||
and finishing up a security audit.
|
||||
Santa is a binary authorization system for macOS. It consists of a system
|
||||
extension that monitors for executions, a daemon that makes execution decisions
|
||||
based on the contents of a local database, a GUI agent that notifies the user in
|
||||
case of a block decision and a command-line utility for managing the system and
|
||||
synchronizing the database with a server.
|
||||
|
||||
Santa is named because it keeps track of binaries that are naughty and nice.
|
||||
It is named Santa because it keeps track of binaries that are naughty or nice.
|
||||
|
||||
Santa is a project of Google's Macintosh Operations Team.
|
||||
# Docs
|
||||
|
||||
Features
|
||||
========
|
||||
The Santa docs are stored in the
|
||||
[Docs](https://github.com/google/santa/blob/main/docs) directory and published
|
||||
at https://santa.dev.
|
||||
|
||||
* Multiple modes: MONITOR and LOCKDOWN. In MONITOR mode all binaries except
|
||||
those marked as blacklisted will be allowed to run, whilst being logged and
|
||||
recorded in the database. In LOCKDOWN mode, only whitelisted binaries are
|
||||
allowed to run.
|
||||
The docs include deployment options, details on how parts of Santa work and
|
||||
instructions for developing Santa itself.
|
||||
|
||||
* Codesign listing: Binaries can be whitelisted/blacklisted by their signing
|
||||
certificate, so you can trust/block all binaries by a given publisher. The
|
||||
binary will only be whitelisted by certificate if its signature validates
|
||||
correctly. However, a decision for a binary will override a decision for a
|
||||
certificate; i.e. you can whitelist a certificate while blacklisting a binary
|
||||
signed by that certificate or vice-versa.
|
||||
# Get Help
|
||||
|
||||
* In-kernel caching: whitelisted binaries are cached in the kernel so the
|
||||
processing required to make a request is only done if the binary
|
||||
isn't already cached.
|
||||
If you have questions or otherwise need help getting started,
|
||||
the [santa-dev](https://groups.google.com/forum/#!forum/santa-dev) group is a
|
||||
great place.
|
||||
|
||||
If you believe you have a bug, feel free to report [an
|
||||
issue](https://github.com/google/santa/issues) and we'll respond as soon as we
|
||||
can.
|
||||
|
||||
If you believe you've found a vulnerability, please read the
|
||||
[security policy](https://github.com/google/santa/security/policy) for
|
||||
disclosure reporting.
|
||||
|
||||
# Features
|
||||
|
||||
* Multiple modes: In the default MONITOR mode, all binaries except those marked
|
||||
as blocked will be allowed to run, whilst being logged and recorded in
|
||||
the events database. In LOCKDOWN mode, only listed binaries are allowed to
|
||||
run.
|
||||
|
||||
* Event logging: When the kext is loaded, all binary launches are logged. When
|
||||
in either mode, all unknown or denied binaries are stored in the database to
|
||||
enable later aggregation.
|
||||
|
||||
* Certificate-based rules, with override levels: Instead of relying on a
|
||||
binary's hash (or 'fingerprint'), executables can be allowed/blocked by their
|
||||
signing certificate. You can therefore allow/block all binaries by a
|
||||
given publisher that were signed with that cert across version updates. A
|
||||
binary can only be allowed by its certificate if its signature validates
|
||||
correctly but a rule for a binary's fingerprint will override a decision for
|
||||
a certificate; i.e. you can allowlist a certificate while blocking a binary
|
||||
signed with that certificate, or vice-versa.
|
||||
|
||||
* Path-based rules (via NSRegularExpression/ICU): This allows a similar feature
|
||||
to that found in Managed Client (the precursor to configuration profiles,
|
||||
which used the same implementation mechanism), Application Launch
|
||||
Restrictions via the mcxalr binary. This implementation carries the added
|
||||
benefit of being configurable via regex, and not relying on LaunchServices.
|
||||
As detailed in the wiki, when evaluating rules this holds the lowest
|
||||
precedence.
|
||||
|
||||
* Failsafe cert rules: You cannot put in a deny rule that would block the
|
||||
certificate used to sign launchd, a.k.a. pid 1, and therefore all components
|
||||
used in macOS. The binaries in every OS update (and in some cases entire new
|
||||
versions) are therefore automatically allowed. This does not affect binaries
|
||||
from Apple's App Store, which use various certs that change regularly for
|
||||
common apps. Likewise, you cannot block Santa itself, and Santa uses a
|
||||
distinct separate cert than other Google apps.
|
||||
|
||||
* Userland components validate each other: each of the userland components (the
|
||||
daemon, the GUI agent and the command-line utility) communicate with each other
|
||||
using XPC and check that their signing certificates are identical before any
|
||||
communication is accepted.
|
||||
daemon, the GUI agent and the command-line utility) communicate with each
|
||||
other using XPC and check that their signing certificates are identical
|
||||
before any communication is accepted.
|
||||
|
||||
* Event logging: all executions processed by the userland agent are logged and
|
||||
all unknown or denied binaries are also stored in the database for upload to a
|
||||
server.
|
||||
* Caching: allowed binaries are cached so the processing required to make a
|
||||
request is only done if the binary isn't already cached.
|
||||
|
||||
* Kext uses only KPIs: the kernel extension only uses provided kernel
|
||||
programming interfaces to do its job. This means that the kext code should
|
||||
continue to work across OS versions.
|
||||
# Intentions and Expectations
|
||||
|
||||
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.
|
||||
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
|
||||
large fleet of machines. Independently, Santa can aid in analyzing what is
|
||||
running on your computer.
|
||||
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to protect hosts in whatever other ways you see fit.
|
||||
Santa is part of a defense-in-depth strategy, and you should continue to
|
||||
protect hosts in whatever other ways you see fit.
|
||||
|
||||
Known Issues
|
||||
============
|
||||
Santa is not yet a 1.0 and we have some known issues to be aware of:
|
||||
# Security and Performance-Related Features
|
||||
|
||||
# Known Issues
|
||||
|
||||
* 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.
|
||||
|
||||
* Database protection: the SQLite database is installed with permissions so that
|
||||
only the root user can read/write it. We're considering approaches to secure
|
||||
this further.
|
||||
|
||||
* Sync client: the command-line client includes a command to synchronize with a
|
||||
management server, including the uploading of events that have occurred on the
|
||||
machine and to download new rules. We're still very heavily working on this
|
||||
server (which is AppEngine-based and will be open-sourced in the future), so the
|
||||
sync client code is unfinished. It does show the 'API' that we're expecting to
|
||||
use so if you'd like to write your own management server, feel free to look at
|
||||
how the client currently works (and suggest changes!)
|
||||
dynamic libraries loaded with dlopen, libraries on disk that have been
|
||||
replaced, or libraries loaded using `DYLD_INSERT_LIBRARIES`.
|
||||
|
||||
* Scripts: Santa is currently written to ignore any execution that isn't a
|
||||
binary. This is because after weighing the administration cost vs the benefit,
|
||||
we found it wasn't worthwhile. Additionally, a number of applications make use
|
||||
of temporary generated scripts, which we can't possibly whitelist and not doing
|
||||
so would cause problems. We're happy to revisit this (or at least make it an
|
||||
option) if it would be useful to others.
|
||||
binary. This is because after weighing the administration cost vs the
|
||||
benefit, we found it wasn't worthwhile. Additionally, a number of
|
||||
applications make use of temporary generated scripts, which we can't possibly
|
||||
allowlist and not doing so would cause problems. We're happy to revisit this
|
||||
(or at least make it an option) if it would be useful to others.
|
||||
|
||||
* Documentation: There currently isn't any.
|
||||
# Sync Servers
|
||||
|
||||
* Tests: There aren't enough of them.
|
||||
* The `santactl` command-line client includes a flag to synchronize with a
|
||||
management server, which uploads events that have occurred on the machine and
|
||||
downloads new rules. There are several open-source servers you can sync with:
|
||||
|
||||
Building
|
||||
========
|
||||
```sh
|
||||
git clone https://github.com/google/santa
|
||||
cd santa
|
||||
* [Moroz](https://github.com/groob/moroz) - A simple golang server that
|
||||
serves hardcoded rules from simple configuration files.
|
||||
* [Rudolph](https://github.com/airbnb/rudolph) - An AWS-based serverless sync service
|
||||
primarily built on API GW, DynamoDB, and Lambda components to reduce operational burden.
|
||||
Rudolph is designed to be fast, easy-to-use, and cost-efficient.
|
||||
* [Zentral](https://github.com/zentralopensource/zentral/wiki) - A
|
||||
centralized service that pulls data from multiple sources and deploy
|
||||
configurations to multiple services.
|
||||
* [Zercurity](https://github.com/zercurity/zercurity) - A dockerized service
|
||||
for managing and monitoring applications across a large fleet utilizing
|
||||
Santa + Osquery.
|
||||
|
||||
# Build a debug build. This will install any necessary CocoaPods, create the
|
||||
# workspace and build, outputting the full log only if an error occurred.
|
||||
# If CocoaPods is not installed, you'll be prompted to install it.
|
||||
#
|
||||
# For other build/install/run options, run rake without any arguments
|
||||
rake build:debug
|
||||
```
|
||||
* Alternatively, `santactl` can configure rules locally (without a sync
|
||||
server).
|
||||
|
||||
Note: the Xcode project is setup to use any installed "Mac Developer" certificate
|
||||
and for security-reasons parts of Santa will not operate properly if not signed.
|
||||
# Screenshots
|
||||
|
||||
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.
|
||||
|
||||
There are two possible solutions for this, for distribution purposes:
|
||||
|
||||
1) Use a [pre-built, pre-signed version](https://github.com/google/santa/releases)
|
||||
of the kext that we supply. Each time changes are made to the kext code we will
|
||||
update the pre-built version that you can make use of. This doesn't prevent you
|
||||
from making changes to the non-kext parts of Santa and distributing those.
|
||||
If you make changes to the kext and make a pull request, we can merge them in
|
||||
and distribute a new version of the pre-signed kext.
|
||||
|
||||
2) Apply for your own [kext signing certificate](https://developer.apple.com/contact/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.
|
||||
A tool like Santa doesn't really lend itself to screenshots, so here's a video
|
||||
instead.
|
||||
|
||||
|
||||
Contributing
|
||||
============
|
||||
Patches to this project are very much welcome. Please see the [CONTRIBUTING](https://github.com/google/santa/blob/master/CONTRIBUTING.md)
|
||||
file.
|
||||
<p align="center"> <img src="https://thumbs.gfycat.com/MadFatalAmphiuma-small.gif" alt="Santa Block Video" /> </p>
|
||||
|
||||
Disclaimer
|
||||
==========
|
||||
# Contributing
|
||||
Patches to this project are very much welcome. Please see the
|
||||
[CONTRIBUTING](https://santa.dev/development/contributing) doc.
|
||||
|
||||
# Disclaimer
|
||||
This is **not** an official Google product.
|
||||
|
||||
197
Rakefile
197
Rakefile
@@ -1,197 +0,0 @@
|
||||
require 'timeout'
|
||||
|
||||
WORKSPACE = 'Santa.xcworkspace'
|
||||
DEFAULT_SCHEME = 'All'
|
||||
OUTPUT_PATH = 'Build'
|
||||
DIST_PATH = 'Dist'
|
||||
BINARIES = ['Santa.app', 'santa-driver.kext', 'santad', 'santactl']
|
||||
XCPRETTY_DEFAULTS = '-sc'
|
||||
XCODEBUILD_DEFAULTS = "-workspace #{WORKSPACE} -derivedDataPath #{OUTPUT_PATH} -parallelizeTargets"
|
||||
|
||||
task :default do
|
||||
system("rake -sT")
|
||||
end
|
||||
|
||||
def xcodebuild(opts)
|
||||
if system "xcodebuild #{XCODEBUILD_DEFAULTS} #{opts} | " \
|
||||
"xcpretty #{XCPRETTY_DEFAULTS} && " \
|
||||
"exit ${PIPESTATUS[0]}"
|
||||
puts "\e[32mPass\e[0m"
|
||||
else
|
||||
raise "\e[31mFail\e[0m"
|
||||
end
|
||||
end
|
||||
|
||||
task :init do
|
||||
unless File.exists?(WORKSPACE) and File.exists?('Pods')
|
||||
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'"
|
||||
end
|
||||
end
|
||||
|
||||
task :remove_existing do
|
||||
system 'sudo rm -rf /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"
|
||||
xcodebuild("-scheme All clean")
|
||||
FileUtils.rm_rf(OUTPUT_PATH)
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
end
|
||||
|
||||
# Build
|
||||
namespace :build do
|
||||
desc "Build: Debug"
|
||||
task :debug do
|
||||
Rake::Task['build:build'].invoke("Debug")
|
||||
end
|
||||
|
||||
desc "Build: Release"
|
||||
task :release do
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
end
|
||||
|
||||
task :build, [:configuration] => :init do |t, args|
|
||||
config = args[:configuration]
|
||||
puts "Building with configuration: #{config}"
|
||||
xcodebuild("-scheme All -configuration #{config} build")
|
||||
end
|
||||
end
|
||||
|
||||
|
||||
# Install
|
||||
namespace :install do
|
||||
desc "Install: Debug"
|
||||
task :debug do
|
||||
Rake::Task['install:install'].invoke("Debug")
|
||||
end
|
||||
|
||||
desc "Install: Release"
|
||||
task :release do
|
||||
Rake::Task['install:install'].invoke("Release")
|
||||
end
|
||||
|
||||
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"
|
||||
end
|
||||
end
|
||||
|
||||
# Dist
|
||||
task :dist do
|
||||
desc "Create distribution folder"
|
||||
|
||||
Rake::Task['build:build'].invoke("Release")
|
||||
|
||||
FileUtils.rm_rf(DIST_PATH)
|
||||
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/binaries")
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/conf")
|
||||
FileUtils.mkdir_p("#{DIST_PATH}/dsym")
|
||||
|
||||
BINARIES.each do |x|
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}", "#{DIST_PATH}/binaries")
|
||||
FileUtils.cp_r("#{OUTPUT_PATH}/Products/Release/#{x}.dSYM", "#{DIST_PATH}/dsym")
|
||||
end
|
||||
|
||||
Dir.glob("Conf/*") {|x| File.directory?(x) or FileUtils.cp(x, "#{DIST_PATH}/conf")}
|
||||
|
||||
puts "Distribution folder created"
|
||||
end
|
||||
|
||||
# Tests
|
||||
namespace :tests do
|
||||
desc "Tests: Logic"
|
||||
task :logic => [:init] do
|
||||
puts "Running logic tests"
|
||||
xcodebuild("-scheme LogicTests test")
|
||||
end
|
||||
|
||||
desc "Tests: Kernel"
|
||||
task :kernel 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"
|
||||
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."
|
||||
end
|
||||
Rake::Task['unload_kext'].execute
|
||||
end
|
||||
end
|
||||
|
||||
# Load/Unload
|
||||
task :unload_daemon do
|
||||
puts "Unloading daemon"
|
||||
system "sudo launchctl unload /Library/LaunchDaemons/com.google.santad.plist 2>/dev/null"
|
||||
end
|
||||
|
||||
task :unload_kext do
|
||||
puts "Unloading kernel extension"
|
||||
system "sudo kextunload -b com.google.santa-driver 2>/dev/null"
|
||||
end
|
||||
|
||||
task :unload_gui do
|
||||
puts "Unloading GUI agent"
|
||||
system "sudo killall Santa 2>/dev/null"
|
||||
end
|
||||
|
||||
desc "Unload"
|
||||
task :unload => [:unload_daemon, :unload_kext, :unload_gui]
|
||||
|
||||
task :load_daemon do
|
||||
puts "Loading daemon"
|
||||
system "sudo launchctl load /Library/LaunchDaemons/com.google.santad.plist"
|
||||
end
|
||||
|
||||
task :load_kext do
|
||||
puts "Loading kernel extension"
|
||||
system "sudo kextload /santa-driver.kext"
|
||||
end
|
||||
|
||||
task :load_gui do
|
||||
puts "Loading GUI agent"
|
||||
system "open /Applications/Santa.app"
|
||||
end
|
||||
|
||||
desc "Load"
|
||||
task :load => [:load_kext, :load_daemon, :load_gui]
|
||||
|
||||
namespace :reload do
|
||||
desc "Reload: Debug"
|
||||
task :debug do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:debug'].invoke()
|
||||
Rake::Task['load'].invoke()
|
||||
end
|
||||
|
||||
desc "Reload: Release"
|
||||
task :release do
|
||||
Rake::Task['unload'].invoke()
|
||||
Rake::Task['install:release'].invoke()
|
||||
Rake::Task['load'].invoke()
|
||||
end
|
||||
end
|
||||
12
SECURITY.md
Normal file
12
SECURITY.md
Normal file
@@ -0,0 +1,12 @@
|
||||
# Reporting a Vulnerability
|
||||
|
||||
If you believe you have found a security vulnerability, we would appreciate private disclosure
|
||||
so that we can work on a fix before disclosure. Any vulnerabilities reported to us will be
|
||||
disclosed publicly either when a new version with fixes is released or 90 days has passed,
|
||||
whichever comes first.
|
||||
|
||||
To report vulnerabilities to us privately, please e-mail `santa-team@google.com`.
|
||||
If you want to encrypt your e-mail, you can use our GPG key `0x92AFE41DAB49BBB6`
|
||||
available on pool.sks-keyservers.net:
|
||||
|
||||
`gpg --keyserver pool.sks-keyservers.net --recv-key 0x92AFE41DAB49BBB6`
|
||||
File diff suppressed because it is too large
Load Diff
@@ -1,78 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
|
||||
BuildableName = "All"
|
||||
BlueprintName = "All"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCDC174E8AE600131A7D"
|
||||
BuildableName = "All"
|
||||
BlueprintName = "All"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D0016A1192BCD3C005E7FCD"
|
||||
BuildableName = "KernelTests"
|
||||
BlueprintName = "KernelTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,96 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "NO"
|
||||
buildForArchiving = "NO"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
<TestableReference
|
||||
skipped = "NO">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</TestableReference>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D260DAB18B68E12002A0B55"
|
||||
BuildableName = "LogicTests.xctest"
|
||||
BlueprintName = "LogicTests"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D385DB5180DE4A900418BC6"
|
||||
BuildableName = "Santa.app"
|
||||
BlueprintName = "Santa"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,68 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D91BCB3174E8A7E00131A7D"
|
||||
BuildableName = "santa-driver.kext"
|
||||
BlueprintName = "santa-driver"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
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
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,88 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D35BD9D18FD71CE00921A21"
|
||||
BuildableName = "santactl"
|
||||
BlueprintName = "santactl"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
@@ -1,89 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8"?>
|
||||
<Scheme
|
||||
LastUpgradeVersion = "0620"
|
||||
version = "1.3">
|
||||
<BuildAction
|
||||
parallelizeBuildables = "YES"
|
||||
buildImplicitDependencies = "YES">
|
||||
<BuildActionEntries>
|
||||
<BuildActionEntry
|
||||
buildForTesting = "YES"
|
||||
buildForRunning = "YES"
|
||||
buildForProfiling = "YES"
|
||||
buildForArchiving = "YES"
|
||||
buildForAnalyzing = "YES">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildActionEntry>
|
||||
</BuildActionEntries>
|
||||
</BuildAction>
|
||||
<TestAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
buildConfiguration = "Debug">
|
||||
<Testables>
|
||||
</Testables>
|
||||
<MacroExpansion>
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</MacroExpansion>
|
||||
</TestAction>
|
||||
<LaunchAction
|
||||
selectedDebuggerIdentifier = "Xcode.DebuggerFoundation.Debugger.LLDB"
|
||||
selectedLauncherIdentifier = "Xcode.DebuggerFoundation.Launcher.LLDB"
|
||||
debugAsWhichUser = "root"
|
||||
launchStyle = "0"
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Debug"
|
||||
ignoresPersistentStateOnLaunch = "NO"
|
||||
debugDocumentVersioning = "YES"
|
||||
allowLocationSimulation = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
<AdditionalOptions>
|
||||
</AdditionalOptions>
|
||||
</LaunchAction>
|
||||
<ProfileAction
|
||||
shouldUseLaunchSchemeArgsEnv = "YES"
|
||||
savedToolIdentifier = ""
|
||||
useCustomWorkingDirectory = "NO"
|
||||
buildConfiguration = "Release"
|
||||
debugDocumentVersioning = "YES">
|
||||
<BuildableProductRunnable
|
||||
runnableDebuggingMode = "0">
|
||||
<BuildableReference
|
||||
BuildableIdentifier = "primary"
|
||||
BlueprintIdentifier = "0D9A7F3C1759330400035EB5"
|
||||
BuildableName = "santad"
|
||||
BlueprintName = "santad"
|
||||
ReferencedContainer = "container:Santa.xcodeproj">
|
||||
</BuildableReference>
|
||||
</BuildableProductRunnable>
|
||||
</ProfileAction>
|
||||
<AnalyzeAction
|
||||
buildConfiguration = "Debug">
|
||||
</AnalyzeAction>
|
||||
<ArchiveAction
|
||||
buildConfiguration = "Release"
|
||||
revealArchiveInOrganizer = "YES">
|
||||
</ArchiveAction>
|
||||
</Scheme>
|
||||
1
Santa.xcworkspace/contents.xcworkspacedata
generated
1
Santa.xcworkspace/contents.xcworkspacedata
generated
@@ -1 +0,0 @@
|
||||
<?xml version='1.0' encoding='UTF-8'?><Workspace version='1.0'><FileRef location='group:Santa.xcodeproj'/><FileRef location='group:Pods/Pods.xcodeproj'/></Workspace>
|
||||
Binary file not shown.
|
Before Width: | Height: | Size: 9.1 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 3.8 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 4.6 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 36 KiB |
Binary file not shown.
|
Before Width: | Height: | Size: 5.3 KiB |
@@ -1,289 +0,0 @@
|
||||
<?xml version="1.0" encoding="UTF-8" standalone="no"?>
|
||||
<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="6254" systemVersion="14D136" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES">
|
||||
<dependencies>
|
||||
<deployment identifier="macosx"/>
|
||||
<plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="6254"/>
|
||||
</dependencies>
|
||||
<objects>
|
||||
<customObject id="-2" userLabel="File's Owner" customClass="SNTMessageWindowController">
|
||||
<connections>
|
||||
<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">
|
||||
<windowStyleMask key="styleMask" utility="YES"/>
|
||||
<rect key="contentRect" x="167" y="107" width="497" height="356"/>
|
||||
<rect key="screenRect" x="0.0" y="0.0" width="2560" height="1577"/>
|
||||
<view key="contentView" id="Iwq-Lx-rLv">
|
||||
<rect key="frame" x="0.0" y="0.0" width="497" height="356"/>
|
||||
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
|
||||
<subviews>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="t8c-Fx-e5h">
|
||||
<rect key="frame" x="207" y="286" 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"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField verticalHuggingPriority="750" horizontalCompressionResistancePriority="250" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="cD5-Su-lXR">
|
||||
<rect key="frame" x="22" y="239" width="454" height="17"/>
|
||||
<constraints>
|
||||
<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">
|
||||
<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"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.attributedCustomMessage" id="376-sj-4Q1"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="pc8-G9-4pJ">
|
||||
<rect key="frame" x="165" y="192" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="xVR-j3-dLw"/>
|
||||
</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="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.filePath" id="qfp-sR-Nmu"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="PXc-xv-A28">
|
||||
<rect key="frame" x="165" y="142" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="4hh-R2-86s"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" lineBreakMode="charWrapping" selectable="YES" sendsActionOnEndEditing="YES" title="Part of 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>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.shortenedHash" id="xgu-71-9ZT"/>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" setsMaxLayoutWidthAtFirstLayout="YES" translatesAutoresizingMaskIntoConstraints="NO" id="C3G-wL-u7w">
|
||||
<rect key="frame" x="165" y="167" width="294" height="17"/>
|
||||
<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"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.publisherInfo" id="CEI-Cu-7pC">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Not code-signed</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="oFj-ol-xpL">
|
||||
<rect key="frame" x="8" y="92" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="User" id="1ut-uT-hQD">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="eQb-0a-76J">
|
||||
<rect key="frame" x="8" y="117" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Parent" id="gze-4A-1w5">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="lvJ-Rk-UT5">
|
||||
<rect key="frame" x="8" y="167" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Publisher" id="yL9-yD-JXX">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="d9e-Wv-Y5H">
|
||||
<rect key="frame" x="8" y="192" width="120" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="116" id="Kqd-nX-7df"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Path" id="KgY-X1-ESG">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalHuggingPriority="251" verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="KEB-eH-x2Y">
|
||||
<rect key="frame" x="8" y="142" width="120" height="17"/>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" alignment="right" title="Identifier" id="eKN-Ic-5zy">
|
||||
<font key="font" metaFont="systemBold"/>
|
||||
<color key="textColor" name="controlTextColor" catalog="System" colorSpace="catalog"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
</textField>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="h6f-PY-cc0">
|
||||
<rect key="frame" x="165" y="92" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="on6-pj-m2k"/>
|
||||
</constraints>
|
||||
<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="IcM-Lt-xTT">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
<box horizontalHuggingPriority="750" title="Line" boxType="custom" borderType="line" titlePosition="noTitle" translatesAutoresizingMaskIntoConstraints="NO" id="4Li-ul-zIi">
|
||||
<rect key="frame" x="146" y="92" width="1" height="117"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="1" id="0o1-Jh-epf"/>
|
||||
</constraints>
|
||||
<color key="borderColor" white="0.0" alpha="0.18" colorSpace="calibratedWhite"/>
|
||||
<color key="fillColor" white="0.0" alpha="0.0" colorSpace="calibratedWhite"/>
|
||||
<font key="titleFont" metaFont="system"/>
|
||||
</box>
|
||||
<button toolTip="Show code signing certificate chain" translatesAutoresizingMaskIntoConstraints="NO" id="cJf-k6-OxS" userLabel="Publisher Certs">
|
||||
<rect key="frame" x="40" y="168" width="15" height="15"/>
|
||||
<constraints>
|
||||
<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"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="showCertInfo:" target="-2" id="dB0-a3-X31"/>
|
||||
<binding destination="-2" name="hidden" keyPath="self.publisherInfo" id="fFR-f3-Oiw">
|
||||
<dictionary key="options">
|
||||
<string key="NSValueTransformerName">NSIsNil</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" translatesAutoresizingMaskIntoConstraints="NO" id="BbV-3h-mmL">
|
||||
<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" 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>
|
||||
<connections>
|
||||
<action selector="closeWindow:" target="-2" id="qQq-gh-8lw"/>
|
||||
</connections>
|
||||
</button>
|
||||
<button verticalHuggingPriority="750" horizontalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="7ua-5a-uSd">
|
||||
<rect key="frame" x="132" y="33" width="112" height="25"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" priority="900" constant="112" id="Pec-Pa-4aZ"/>
|
||||
</constraints>
|
||||
<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">
|
||||
<behavior key="behavior" pushIn="YES" lightByBackground="YES" lightByGray="YES"/>
|
||||
<font key="font" metaFont="system"/>
|
||||
<string key="keyEquivalent" base64-UTF8="YES">
|
||||
DQ
|
||||
</string>
|
||||
<modifierMask key="keyEquivalentModifierMask" command="YES"/>
|
||||
</buttonCell>
|
||||
<connections>
|
||||
<action selector="openEventDetails:" target="-2" id="VhL-ql-rCV"/>
|
||||
</connections>
|
||||
</button>
|
||||
<textField horizontalCompressionResistancePriority="250" verticalCompressionResistancePriority="1000" translatesAutoresizingMaskIntoConstraints="NO" id="f1p-GL-O3o">
|
||||
<rect key="frame" x="165" y="117" width="294" height="17"/>
|
||||
<constraints>
|
||||
<constraint firstAttribute="width" constant="290" id="h3Y-mO-38F"/>
|
||||
</constraints>
|
||||
<textFieldCell key="cell" scrollable="YES" lineBreakMode="clipping" sendsActionOnEndEditing="YES" title="Parent Name" id="ieo-WK-aDD">
|
||||
<font key="font" metaFont="system"/>
|
||||
<color key="textColor" white="0.0" alpha="0.5" colorSpace="custom" customColorSpace="genericGamma22GrayColorSpace"/>
|
||||
<color key="backgroundColor" name="controlColor" catalog="System" colorSpace="catalog"/>
|
||||
</textFieldCell>
|
||||
<connections>
|
||||
<binding destination="-2" name="value" keyPath="self.event.parentName" id="arL-Mc-4xj">
|
||||
<dictionary key="options">
|
||||
<string key="NSNullPlaceholder">Unknown</string>
|
||||
</dictionary>
|
||||
</binding>
|
||||
</connections>
|
||||
</textField>
|
||||
</subviews>
|
||||
<constraints>
|
||||
<constraint firstItem="f1p-GL-O3o" firstAttribute="centerY" secondItem="eQb-0a-76J" secondAttribute="centerY" id="2Aq-1E-Ybz"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="top" secondItem="f1p-GL-O3o" secondAttribute="bottom" constant="8" id="496-VQ-Fx5"/>
|
||||
<constraint firstItem="eQb-0a-76J" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="-116" id="6Q5-Oo-1cI"/>
|
||||
<constraint firstItem="BbV-3h-mmL" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="7K6-bY-Rn6"/>
|
||||
<constraint firstItem="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="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="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="10" id="IwX-ja-ZIs"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="top" secondItem="4Li-ul-zIi" secondAttribute="top" id="JY4-N1-j8e"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="leading" priority="999" id="MVr-jY-GDj"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="top" secondItem="cD5-Su-lXR" secondAttribute="bottom" constant="30" id="Nsl-zf-poH"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="SCl-Ky-VmT"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="centerY" secondItem="pc8-G9-4pJ" secondAttribute="centerY" id="SLv-F7-w5k"/>
|
||||
<constraint firstItem="7ua-5a-uSd" firstAttribute="top" secondItem="oFj-ol-xpL" secondAttribute="bottom" constant="35" id="Scq-zQ-Sao"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="KEB-eH-x2Y" secondAttribute="trailing" constant="20" id="Seb-c0-MUL"/>
|
||||
<constraint firstAttribute="centerX" secondItem="cD5-Su-lXR" secondAttribute="centerX" id="V0a-Py-iEc"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="leading" priority="999" id="Z6G-l9-G4a"/>
|
||||
<constraint firstItem="oFj-ol-xpL" firstAttribute="top" secondItem="eQb-0a-76J" secondAttribute="bottom" constant="8" id="abm-cM-PN0"/>
|
||||
<constraint firstAttribute="centerX" secondItem="BbV-3h-mmL" secondAttribute="centerX" priority="900" constant="-62.5" id="acs-5J-vQY"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="eQb-0a-76J" secondAttribute="trailing" constant="20" id="b0B-3w-grH"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="leading" secondItem="oFj-ol-xpL" secondAttribute="leading" priority="999" id="b5A-M7-ZsD"/>
|
||||
<constraint firstItem="KEB-eH-x2Y" firstAttribute="centerY" secondItem="PXc-xv-A28" secondAttribute="centerY" id="cHe-pZ-0Oq"/>
|
||||
<constraint firstItem="cD5-Su-lXR" firstAttribute="top" secondItem="t8c-Fx-e5h" secondAttribute="bottom" constant="30" id="dYg-zP-wh2"/>
|
||||
<constraint firstItem="h6f-PY-cc0" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="eSz-lz-Fdh"/>
|
||||
<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="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="eQb-0a-76J" firstAttribute="top" secondItem="KEB-eH-x2Y" secondAttribute="bottom" constant="8" id="m2z-1O-ifB"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="trailing" secondItem="KEB-eH-x2Y" secondAttribute="trailing" id="pdq-a6-Y73"/>
|
||||
<constraint firstAttribute="centerX" secondItem="7ua-5a-uSd" secondAttribute="centerX" constant="61" id="phL-j9-rPq"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="lvJ-Rk-UT5" secondAttribute="trailing" constant="20" id="qKi-KT-jzJ"/>
|
||||
<constraint firstItem="C3G-wL-u7w" firstAttribute="bottom" secondItem="PXc-xv-A28" secondAttribute="top" constant="-8" id="snd-8T-LjC"/>
|
||||
<constraint firstItem="4Li-ul-zIi" firstAttribute="leading" secondItem="d9e-Wv-Y5H" secondAttribute="trailing" constant="20" id="stz-Vm-Kxo"/>
|
||||
<constraint firstItem="PXc-xv-A28" firstAttribute="leading" secondItem="4Li-ul-zIi" secondAttribute="trailing" constant="20" id="tAa-1s-xVZ"/>
|
||||
<constraint firstItem="d9e-Wv-Y5H" firstAttribute="width" secondItem="eQb-0a-76J" secondAttribute="width" id="u4p-1B-x5B"/>
|
||||
<constraint firstAttribute="bottom" secondItem="BbV-3h-mmL" secondAttribute="bottom" constant="35" id="ukF-FH-DE8"/>
|
||||
<constraint firstItem="pc8-G9-4pJ" firstAttribute="bottom" secondItem="C3G-wL-u7w" secondAttribute="top" constant="-8" id="zst-nc-VqA"/>
|
||||
</constraints>
|
||||
</view>
|
||||
<point key="canvasLocation" x="112.5" y="308"/>
|
||||
</window>
|
||||
</objects>
|
||||
<resources>
|
||||
<image name="NSInfo" width="32" height="32"/>
|
||||
</resources>
|
||||
</document>
|
||||
@@ -1,3 +0,0 @@
|
||||
#ifdef __OBJC__
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#endif
|
||||
@@ -1,108 +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 "SNTAppDelegate.h"
|
||||
|
||||
#import "SNTAboutWindowController.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileWatcher.h"
|
||||
#import "SNTNotificationManager.h"
|
||||
#import "SNTXPCConnection.h"
|
||||
|
||||
@interface SNTAppDelegate ()
|
||||
@property SNTAboutWindowController *aboutWindowController;
|
||||
@property SNTFileWatcher *configFileWatcher;
|
||||
@property SNTNotificationManager *notificationManager;
|
||||
@property SNTXPCConnection *listener;
|
||||
@end
|
||||
|
||||
@implementation SNTAppDelegate
|
||||
|
||||
#pragma mark App Delegate methods
|
||||
|
||||
- (void)applicationDidFinishLaunching:(NSNotification *)aNotification {
|
||||
[self setupMenu];
|
||||
|
||||
self.configFileWatcher = [[SNTFileWatcher alloc] initWithFilePath:kDefaultConfigFilePath
|
||||
handler:^{
|
||||
[[SNTConfigurator configurator] reloadConfigData];
|
||||
}];
|
||||
|
||||
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];
|
||||
|
||||
[self createConnection];
|
||||
}
|
||||
|
||||
- (BOOL)applicationShouldHandleReopen:(NSApplication *)sender hasVisibleWindows:(BOOL)flag {
|
||||
self.aboutWindowController = [[SNTAboutWindowController alloc] init];
|
||||
[self.aboutWindowController showWindow:self];
|
||||
return NO;
|
||||
}
|
||||
|
||||
#pragma mark Connection handling
|
||||
|
||||
- (void)createConnection {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
|
||||
self.listener = [[SNTXPCConnection alloc] initClientWithName:[SNTXPCNotifierInterface serviceId]
|
||||
options:NSXPCConnectionPrivileged];
|
||||
self.listener.exportedInterface = [SNTXPCNotifierInterface notifierInterface];
|
||||
self.listener.exportedObject = self.notificationManager;
|
||||
self.listener.rejectedHandler = ^{
|
||||
[weakSelf performSelectorInBackground:@selector(attemptReconnection)
|
||||
withObject:nil];
|
||||
};
|
||||
self.listener.invalidationHandler = self.listener.rejectedHandler;
|
||||
[self.listener resume];
|
||||
}
|
||||
|
||||
- (void)killConnection {
|
||||
self.listener.invalidationHandler = nil;
|
||||
[self.listener invalidate];
|
||||
self.listener = nil;
|
||||
}
|
||||
|
||||
- (void)attemptReconnection {
|
||||
// TODO(rah): Make this smarter.
|
||||
sleep(10);
|
||||
[self performSelectorOnMainThread:@selector(createConnection) withObject:nil waitUntilDone:NO];
|
||||
}
|
||||
|
||||
#pragma mark Menu Management
|
||||
|
||||
- (void)setupMenu {
|
||||
// Whilst the user will never see the menu, having one with the Copy and Select All options
|
||||
// allows the shortcuts for these items to work, which is useful for being able to copy
|
||||
// information from notifications. The mainMenu must have a nested menu for this to work properly.
|
||||
NSMenu *mainMenu = [[NSMenu alloc] init];
|
||||
NSMenu *editMenu = [[NSMenu alloc] init];
|
||||
[editMenu addItemWithTitle:@"Copy" action:@selector(copy:) keyEquivalent:@"c"];
|
||||
[editMenu addItemWithTitle:@"Select All" action:@selector(selectAll:) keyEquivalent:@"a"];
|
||||
NSMenuItem *editMenuItem = [[NSMenuItem alloc] init];
|
||||
[editMenuItem setSubmenu:editMenu];
|
||||
[mainMenu addItem:editMenuItem];
|
||||
[NSApp setMainMenu:mainMenu];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,148 +0,0 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTMessageWindowController.h"
|
||||
|
||||
#import <SecurityInterface/SFCertificatePanel.h>
|
||||
|
||||
#import "SNTCertificate.h"
|
||||
#import "SNTConfigurator.h"
|
||||
#import "SNTFileInfo.h"
|
||||
#import "SNTMessageWindow.h"
|
||||
#import "SNTStoredEvent.h"
|
||||
|
||||
@implementation SNTMessageWindowController
|
||||
|
||||
- (instancetype)initWithEvent:(SNTStoredEvent *)event andMessage:(NSString *)message {
|
||||
self = [super initWithWindowNibName:@"MessageWindow"];
|
||||
if (self) {
|
||||
_event = event;
|
||||
_customMessage = (message != (NSString *)[NSNull null] ? message : nil);
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)loadWindow {
|
||||
[super loadWindow];
|
||||
[self.window setLevel:NSPopUpMenuWindowLevel];
|
||||
[self.window setMovableByWindowBackground:YES];
|
||||
|
||||
if (![[SNTConfigurator configurator] eventDetailURL]) {
|
||||
[self.openEventButton removeFromSuperview];
|
||||
} else {
|
||||
NSString *eventDetailText = [[SNTConfigurator configurator] eventDetailText];
|
||||
if (eventDetailText) {
|
||||
[self.openEventButton setTitle:eventDetailText];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
- (IBAction)showWindow:(id)sender {
|
||||
[(SNTMessageWindow *)self.window fadeIn:sender];
|
||||
}
|
||||
|
||||
- (IBAction)closeWindow:(id)sender {
|
||||
[(SNTMessageWindow *)self.window fadeOut:sender];
|
||||
}
|
||||
|
||||
- (void)windowWillClose:(NSNotification *)notification {
|
||||
if (self.delegate) [self.delegate windowDidClose];
|
||||
}
|
||||
|
||||
- (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) {
|
||||
[certArray addObject:(id)cert.certRef];
|
||||
}
|
||||
|
||||
[[[SFCertificatePanel alloc] init] beginSheetForWindow:self.window
|
||||
modalDelegate:nil
|
||||
didEndSelector:nil
|
||||
contextInfo:nil
|
||||
certificates:certArray
|
||||
showGroup:YES];
|
||||
}
|
||||
|
||||
- (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];
|
||||
|
||||
[self closeWindow:sender];
|
||||
[[NSWorkspace sharedWorkspace] openURL:[NSURL URLWithString:formatStr]];
|
||||
}
|
||||
|
||||
#pragma mark Generated properties
|
||||
|
||||
+ (NSSet *)keyPathsForValuesAffectingValueForKey:(NSString *)key {
|
||||
if (![key isEqualToString:@"event"]) {
|
||||
return [NSSet setWithObject:@"event"];
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)shortenedHash {
|
||||
return [self.event.fileSHA256 substringWithRange:NSMakeRange(0, 10)];
|
||||
}
|
||||
|
||||
- (NSString *)publisherInfo {
|
||||
SNTCertificate *leafCert = [self.event.signingChain firstObject];
|
||||
|
||||
if (leafCert.commonName && leafCert.orgName) {
|
||||
return [NSString stringWithFormat:@"%@ - %@", leafCert.orgName, leafCert.commonName];
|
||||
} else if (leafCert.commonName) {
|
||||
return leafCert.commonName;
|
||||
} else if (leafCert.orgName) {
|
||||
return leafCert.orgName;
|
||||
} else {
|
||||
return nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSAttributedString *)attributedCustomMessage {
|
||||
NSString *htmlHeader = @"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: #AAA;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *message;
|
||||
if ([self.customMessage length] > 0) {
|
||||
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;
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,96 +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 "SNTNotificationManager.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
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_pendingNotifications = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)windowDidClose {
|
||||
[self.pendingNotifications removeObject:self.currentWindowController];
|
||||
self.currentWindowController = nil;
|
||||
|
||||
if ([self.pendingNotifications count]) {
|
||||
self.currentWindowController = [self.pendingNotifications firstObject];
|
||||
[self.currentWindowController showWindow:self];
|
||||
} else {
|
||||
[NSApp hide:self];
|
||||
}
|
||||
}
|
||||
|
||||
#pragma mark SNTNotifierXPC protocol methods
|
||||
|
||||
- (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];
|
||||
if ([[self.pendingNotifications filteredArrayUsingPredicate:predicate] count]) return;
|
||||
|
||||
if (!event) {
|
||||
NSLog(@"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];
|
||||
}
|
||||
|
||||
- (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];
|
||||
}
|
||||
}
|
||||
|
||||
@end
|
||||
363
Source/common/BUILD
Normal file
363
Source/common/BUILD
Normal file
@@ -0,0 +1,363 @@
|
||||
load("//:helper.bzl", "santa_unit_test")
|
||||
load("@rules_cc//cc:defs.bzl", "cc_proto_library")
|
||||
|
||||
package(
|
||||
default_visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
licenses(["notice"])
|
||||
|
||||
proto_library(
|
||||
name = "santa_proto",
|
||||
srcs = ["santa.proto"],
|
||||
deps = [
|
||||
"@com_google_protobuf//:any_proto",
|
||||
"@com_google_protobuf//:timestamp_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_proto_library(
|
||||
name = "santa_cc_proto",
|
||||
deps = [":santa_proto"],
|
||||
)
|
||||
|
||||
# Note: Simple wrapper for a `cc_proto_library` target which cannot be directly
|
||||
# depended upon by an `objc_library` target.
|
||||
cc_library(
|
||||
name = "santa_cc_proto_library_wrapper",
|
||||
hdrs = ["santa_proto_include_wrapper.h"],
|
||||
deps = [
|
||||
":santa_cc_proto",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SantaCache",
|
||||
hdrs = ["SantaCache.h"],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SantaCacheTest",
|
||||
srcs = [
|
||||
"SantaCache.h",
|
||||
"SantaCacheTest.mm",
|
||||
],
|
||||
deps = ["//Source/common:SNTCommon"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTBlockMessage_SantaGUI",
|
||||
srcs = ["SNTBlockMessage.m"],
|
||||
hdrs = ["SNTBlockMessage.h"],
|
||||
defines = ["SANTAGUI"],
|
||||
deps = [
|
||||
":SNTConfigurator",
|
||||
":SNTDeviceEvent",
|
||||
":SNTLogging",
|
||||
":SNTStoredEvent",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCachedDecision",
|
||||
srcs = ["SNTCachedDecision.m"],
|
||||
hdrs = ["SNTCachedDecision.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDeviceEvent",
|
||||
srcs = ["SNTDeviceEvent.m"],
|
||||
hdrs = ["SNTDeviceEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTCommonEnums",
|
||||
textual_hdrs = ["SNTCommonEnums.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTConfigurator",
|
||||
srcs = ["SNTConfigurator.m"],
|
||||
hdrs = ["SNTConfigurator.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTStrengthify",
|
||||
":SNTSystemInfo",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTKVOManager",
|
||||
srcs = ["SNTKVOManager.mm"],
|
||||
hdrs = ["SNTKVOManager.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTKVOManagerTest",
|
||||
srcs = ["SNTKVOManagerTest.mm"],
|
||||
deps = [
|
||||
":SNTKVOManager",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTDropRootPrivs",
|
||||
srcs = ["SNTDropRootPrivs.m"],
|
||||
hdrs = ["SNTDropRootPrivs.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTFileInfo",
|
||||
srcs = ["SNTFileInfo.m"],
|
||||
hdrs = ["SNTFileInfo.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
"@FMDB",
|
||||
"@MOLCodesignChecker",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTCommon",
|
||||
hdrs = ["SNTCommon.h"],
|
||||
defines = [
|
||||
"TARGET_OS_OSX",
|
||||
"TARGET_OS_MAC",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTLogging",
|
||||
srcs = ["SNTLogging.m"],
|
||||
hdrs = ["SNTLogging.h"],
|
||||
deps = [":SNTConfigurator"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "PrefixTree",
|
||||
hdrs = ["PrefixTree.h"],
|
||||
deps = [
|
||||
":SNTLogging",
|
||||
"@com_google_absl//absl/synchronization",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "Unit",
|
||||
hdrs = ["Unit.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTRule",
|
||||
srcs = ["SNTRule.m"],
|
||||
hdrs = ["SNTRule.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTSyncConstants",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTRuleTest",
|
||||
srcs = ["SNTRuleTest.m"],
|
||||
deps = [":SNTRule"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTStoredEvent",
|
||||
srcs = ["SNTStoredEvent.m"],
|
||||
hdrs = ["SNTStoredEvent.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
"@MOLCertificate",
|
||||
],
|
||||
)
|
||||
|
||||
cc_library(
|
||||
name = "SNTStrengthify",
|
||||
hdrs = ["SNTStrengthify.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSyncConstants",
|
||||
srcs = ["SNTSyncConstants.m"],
|
||||
hdrs = ["SNTSyncConstants.h"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTSystemInfo",
|
||||
srcs = ["SNTSystemInfo.m"],
|
||||
hdrs = ["SNTSystemInfo.h"],
|
||||
sdk_frameworks = ["IOKit"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCBundleServiceInterface",
|
||||
srcs = ["SNTXPCBundleServiceInterface.m"],
|
||||
hdrs = ["SNTXPCBundleServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCMetricServiceInterface",
|
||||
srcs = ["SNTXPCMetricServiceInterface.m"],
|
||||
hdrs = ["SNTXPCMetricServiceInterface.h"],
|
||||
deps = [
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCControlInterface",
|
||||
srcs = ["SNTXPCControlInterface.m"],
|
||||
hdrs = ["SNTXPCControlInterface.h"],
|
||||
defines = select({
|
||||
"//:adhoc_build": ["SANTAADHOC"],
|
||||
"//conditions:default": None,
|
||||
}),
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTConfigurator",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCUnprivilegedControlInterface",
|
||||
"@MOLCodesignChecker",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCNotifierInterface",
|
||||
srcs = ["SNTXPCNotifierInterface.m"],
|
||||
hdrs = ["SNTXPCNotifierInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTMetricSet",
|
||||
srcs = ["SNTMetricSet.m"],
|
||||
hdrs = ["SNTMetricSet.h"],
|
||||
deps = [":SNTCommonEnums"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCSyncServiceInterface",
|
||||
srcs = ["SNTXPCSyncServiceInterface.m"],
|
||||
hdrs = ["SNTXPCSyncServiceInterface.h"],
|
||||
deps = [
|
||||
":SNTCommonEnums",
|
||||
":SNTStoredEvent",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "SNTXPCUnprivilegedControlInterface",
|
||||
srcs = ["SNTXPCUnprivilegedControlInterface.m"],
|
||||
hdrs = ["SNTXPCUnprivilegedControlInterface.h"],
|
||||
deps = [
|
||||
":SNTCommon",
|
||||
":SNTCommonEnums",
|
||||
":SNTRule",
|
||||
":SNTStoredEvent",
|
||||
":SNTXPCBundleServiceInterface",
|
||||
"@MOLCertificate",
|
||||
"@MOLXPCConnection",
|
||||
],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTFileInfoTest",
|
||||
srcs = ["SNTFileInfoTest.m"],
|
||||
resources = [
|
||||
"testdata/32bitplist",
|
||||
"testdata/bad_pagezero",
|
||||
"testdata/missing_pagezero",
|
||||
],
|
||||
structured_resources = glob([
|
||||
"testdata/BundleExample.app/**",
|
||||
"testdata/DirectoryBundle/**",
|
||||
]),
|
||||
deps = [":SNTFileInfo"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "PrefixTreeTest",
|
||||
srcs = ["PrefixTreeTest.mm"],
|
||||
deps = [":PrefixTree"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTMetricSetTest",
|
||||
srcs = ["SNTMetricSetTest.m"],
|
||||
deps = [":SNTMetricSet"],
|
||||
)
|
||||
|
||||
santa_unit_test(
|
||||
name = "SNTCachedDecisionTest",
|
||||
srcs = ["SNTCachedDecisionTest.mm"],
|
||||
deps = [
|
||||
"//Source/common:SNTCachedDecision",
|
||||
"//Source/common:TestUtils",
|
||||
"@OCMock",
|
||||
],
|
||||
)
|
||||
|
||||
test_suite(
|
||||
name = "unit_tests",
|
||||
tests = [
|
||||
":PrefixTreeTest",
|
||||
":SNTCachedDecisionTest",
|
||||
":SNTFileInfoTest",
|
||||
":SNTKVOManagerTest",
|
||||
":SNTMetricSetTest",
|
||||
":SNTRuleTest",
|
||||
":SantaCacheTest",
|
||||
],
|
||||
visibility = ["//:santa_package_group"],
|
||||
)
|
||||
|
||||
objc_library(
|
||||
name = "TestUtils",
|
||||
testonly = 1,
|
||||
srcs = ["TestUtils.mm"],
|
||||
hdrs = ["TestUtils.h"],
|
||||
sdk_dylibs = [
|
||||
"bsm",
|
||||
],
|
||||
deps = [
|
||||
"@OCMock",
|
||||
"@com_google_googletest//:gtest",
|
||||
],
|
||||
)
|
||||
298
Source/common/PrefixTree.h
Normal file
298
Source/common/PrefixTree.h
Normal file
@@ -0,0 +1,298 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// https://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__COMMON__PREFIXTREE_H
|
||||
#define SANTA__COMMON__PREFIXTREE_H
|
||||
|
||||
#include <sys/syslimits.h>
|
||||
|
||||
#include <optional>
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#include "absl/synchronization/mutex.h"
|
||||
|
||||
#if SANTA_PREFIX_TREE_DEBUG
|
||||
#define DEBUG_LOG LOGD
|
||||
#else
|
||||
#define DEBUG_LOG(format, ...) // NOP
|
||||
#endif
|
||||
|
||||
namespace santa::common {
|
||||
|
||||
template <typename ValueT>
|
||||
class PrefixTree {
|
||||
private:
|
||||
// Forward declaration
|
||||
enum class NodeType;
|
||||
class TreeNode;
|
||||
|
||||
public:
|
||||
PrefixTree(uint32_t max_depth = PATH_MAX)
|
||||
: root_(new TreeNode()), max_depth_(max_depth), node_count_(0) {}
|
||||
|
||||
~PrefixTree() { PruneLocked(root_); }
|
||||
|
||||
bool InsertPrefix(const char *s, ValueT value) {
|
||||
absl::MutexLock lock(&lock_);
|
||||
return InsertLocked(s, value, NodeType::kPrefix);
|
||||
}
|
||||
|
||||
bool InsertLiteral(const char *s, ValueT value) {
|
||||
absl::MutexLock lock(&lock_);
|
||||
return InsertLocked(s, value, NodeType::kLiteral);
|
||||
}
|
||||
|
||||
bool HasPrefix(const char *input) {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return HasPrefixLocked(input);
|
||||
}
|
||||
|
||||
std::optional<ValueT> LookupLongestMatchingPrefix(const char *input) {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return LookupLongestMatchingPrefixLocked(input);
|
||||
}
|
||||
|
||||
void Reset() {
|
||||
absl::MutexLock lock(&lock_);
|
||||
PruneLocked(root_);
|
||||
root_ = new TreeNode();
|
||||
node_count_ = 0;
|
||||
}
|
||||
|
||||
#if SANTA_PREFIX_TREE_DEBUG
|
||||
void Print() {
|
||||
char buf[max_depth_ + 1];
|
||||
memset(buf, 0, sizeof(buf));
|
||||
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
PrintLocked(root_, buf, 0);
|
||||
}
|
||||
|
||||
uint32_t NodeCount() {
|
||||
absl::ReaderMutexLock lock(&lock_);
|
||||
return node_count_;
|
||||
}
|
||||
#endif
|
||||
|
||||
private:
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_)
|
||||
bool InsertLocked(const char *input, ValueT value, NodeType node_type) {
|
||||
const char *p = input;
|
||||
TreeNode *node = root_;
|
||||
|
||||
while (*p) {
|
||||
uint8_t cur_byte = (uint8_t)*p;
|
||||
|
||||
TreeNode *child_node = node->children_[cur_byte];
|
||||
if (!child_node) {
|
||||
// Current node doesn't exist...
|
||||
// Create the rest of the nodes in the tree for the given string
|
||||
|
||||
// Keep a pointer to where this new branch starts from. If the
|
||||
// input length exceeds max_depth, the new branch will need to
|
||||
// be pruned.
|
||||
TreeNode *branch_start_node = node;
|
||||
uint8_t branch_start_byte = (uint8_t)*p;
|
||||
|
||||
do {
|
||||
TreeNode *new_node = new TreeNode();
|
||||
node->children_[cur_byte] = new_node;
|
||||
node = new_node;
|
||||
node_count_++;
|
||||
|
||||
// Check current depth...
|
||||
if (p - input >= max_depth_) {
|
||||
// Attempted to add a string that exceeded max depth
|
||||
// Prune tree from start of this new branch
|
||||
PruneLocked(branch_start_node->children_[branch_start_byte]);
|
||||
branch_start_node->children_[branch_start_byte] = nullptr;
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
cur_byte = (uint8_t) * ++p;
|
||||
} while (*p);
|
||||
|
||||
node->node_type_ = node_type;
|
||||
node->value_ = value;
|
||||
|
||||
return true;
|
||||
} else if (*(p + 1) == '\0') {
|
||||
// Current node exists and we're at the end of our input...
|
||||
// Note: The current node's data will be overwritten
|
||||
|
||||
// Only increment node count if the previous node type wasn't already a
|
||||
// prefix or literal type (in which case it was already counted)
|
||||
if (child_node->node_type_ == NodeType::kInner) {
|
||||
node_count_++;
|
||||
}
|
||||
|
||||
child_node->node_type_ = node_type;
|
||||
child_node->value_ = value;
|
||||
return true;
|
||||
}
|
||||
|
||||
node = child_node;
|
||||
p++;
|
||||
}
|
||||
|
||||
// Should only get here when input is an empty string
|
||||
return false;
|
||||
}
|
||||
|
||||
ABSL_SHARED_LOCKS_REQUIRED(lock_)
|
||||
bool HasPrefixLocked(const char *input) {
|
||||
TreeNode *node = root_;
|
||||
const char *p = input;
|
||||
|
||||
while (*p) {
|
||||
node = node->children_[(uint8_t)*p++];
|
||||
|
||||
if (!node) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (node->node_type_ == NodeType::kPrefix ||
|
||||
(*p == '\0' && node->node_type_ == NodeType::kLiteral)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
ABSL_SHARED_LOCKS_REQUIRED(lock_)
|
||||
std::optional<ValueT> LookupLongestMatchingPrefixLocked(const char *input) {
|
||||
TreeNode *node = root_;
|
||||
TreeNode *match = nullptr;
|
||||
const char *p = input;
|
||||
|
||||
while (*p) {
|
||||
node = node->children_[(uint8_t)*p++];
|
||||
|
||||
if (!node) {
|
||||
break;
|
||||
}
|
||||
|
||||
if (node->node_type_ == NodeType::kPrefix ||
|
||||
(*p == '\0' && node->node_type_ == NodeType::kLiteral)) {
|
||||
match = node;
|
||||
}
|
||||
}
|
||||
|
||||
return match ? std::make_optional<ValueT>(match->value_) : std::nullopt;
|
||||
}
|
||||
|
||||
ABSL_EXCLUSIVE_LOCKS_REQUIRED(lock_)
|
||||
void PruneLocked(TreeNode *target) {
|
||||
if (!target) {
|
||||
return;
|
||||
}
|
||||
|
||||
// For deep trees, a recursive approach will generate too many stack frames.
|
||||
// Since the depth of the tree is configurable, err on the side of caution
|
||||
// and use a "stack" to walk the tree in a non-recursive manner.
|
||||
TreeNode **stack = new TreeNode *[node_count_ + 1];
|
||||
if (!stack) {
|
||||
LOGE(@"Unable to prune tree!");
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t count = 0;
|
||||
|
||||
// Seed the "stack" with a starting node.
|
||||
stack[count++] = target;
|
||||
|
||||
// Start at the target node and walk the tree to find and delete all the
|
||||
// sub-nodes.
|
||||
while (count) {
|
||||
TreeNode *node = stack[--count];
|
||||
|
||||
for (int i = 0; i < 256; ++i) {
|
||||
if (!node->children_[i]) {
|
||||
continue;
|
||||
}
|
||||
stack[count++] = node->children_[i];
|
||||
}
|
||||
|
||||
delete node;
|
||||
--node_count_;
|
||||
}
|
||||
|
||||
delete[] stack;
|
||||
}
|
||||
|
||||
#if SANTA_PREFIX_TREE_DEBUG
|
||||
ABSL_SHARED_LOCKS_REQUIRED(lock_)
|
||||
void PrintLocked(TreeNode *node, char *buf, uint32_t depth) {
|
||||
for (size_t i = 0; i < 256; i++) {
|
||||
TreeNode *cur_node = node->children_[i];
|
||||
if (cur_node) {
|
||||
buf[depth] = i;
|
||||
if (cur_node->node_type_ != NodeType::kInner) {
|
||||
printf("\t%s (type: %s)\n", buf,
|
||||
cur_node->node_type_ == NodeType::kPrefix ? "prefix" : "literal");
|
||||
}
|
||||
PrintLocked(cur_node, buf, depth + 1);
|
||||
buf[depth] = '\0';
|
||||
}
|
||||
}
|
||||
}
|
||||
#endif
|
||||
|
||||
enum class NodeType {
|
||||
kInner = 0,
|
||||
kPrefix,
|
||||
kLiteral,
|
||||
};
|
||||
|
||||
///
|
||||
/// TreeNode is a wrapper class that represents one byte.
|
||||
/// 1 node can represent a whole ASCII character.
|
||||
/// For example a pointer to the 'A' node will be stored at children[0x41].
|
||||
/// It takes 1-4 nodes to represent a UTF-8 encoded Unicode character.
|
||||
///
|
||||
/// The path for "/🤘" would look like this:
|
||||
/// children[0x2f] -> children[0xf0] -> children[0x9f] -> children[0xa4]
|
||||
/// -> children[0x98]
|
||||
///
|
||||
/// The path for "/dev" is:
|
||||
/// children[0x2f] -> children[0x64] -> children[0x65] -> children[0x76]
|
||||
///
|
||||
/// Lookups of children are O(1).
|
||||
///
|
||||
/// Having the nodes represented by a smaller width, such as a nibble (1/2
|
||||
/// byte), would drastically decrease the memory footprint but would double
|
||||
/// required dereferences.
|
||||
///
|
||||
/// TODO(bur): Potentially convert this into a full on radix tree.
|
||||
///
|
||||
class TreeNode {
|
||||
public:
|
||||
TreeNode() : children_(), node_type_(NodeType::kInner) {}
|
||||
~TreeNode() = default;
|
||||
TreeNode *children_[256];
|
||||
PrefixTree::NodeType node_type_;
|
||||
ValueT value_;
|
||||
};
|
||||
|
||||
TreeNode *root_;
|
||||
const uint32_t max_depth_;
|
||||
uint32_t node_count_ ABSL_GUARDED_BY(lock_);
|
||||
absl::Mutex lock_;
|
||||
};
|
||||
|
||||
} // namespace santa::common
|
||||
|
||||
#endif
|
||||
224
Source/common/PrefixTreeTest.mm
Normal file
224
Source/common/PrefixTreeTest.mm
Normal file
@@ -0,0 +1,224 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#define SANTA_PREFIX_TREE_DEBUG 1
|
||||
#include "Source/common/PrefixTree.h"
|
||||
|
||||
using santa::common::PrefixTree;
|
||||
|
||||
@interface PrefixTreeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation PrefixTreeTest
|
||||
|
||||
- (void)testBasic {
|
||||
PrefixTree<int> tree;
|
||||
|
||||
XCTAssertFalse(tree.HasPrefix("/foo/bar/baz"));
|
||||
XCTAssertFalse(tree.HasPrefix("/foo/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz"));
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 12));
|
||||
XCTAssertTrue(tree.InsertPrefix("/bar", 34));
|
||||
XCTAssertTrue(tree.InsertLiteral("/foo/bar", 56));
|
||||
|
||||
// Re-inserting something that exists is allowed
|
||||
XCTAssertTrue(tree.InsertLiteral("/foo", 78));
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 56));
|
||||
|
||||
XCTAssertTrue(tree.HasPrefix("/foo/bar/baz"));
|
||||
XCTAssertTrue(tree.HasPrefix("/foo/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz"));
|
||||
|
||||
// Empty strings are not supported
|
||||
XCTAssertFalse(tree.InsertLiteral("", 0));
|
||||
XCTAssertFalse(tree.InsertPrefix("", 0));
|
||||
}
|
||||
|
||||
- (void)testHasPrefix {
|
||||
PrefixTree<int> tree;
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 0));
|
||||
XCTAssertTrue(tree.InsertLiteral("/bar", 0));
|
||||
XCTAssertTrue(tree.InsertLiteral("/baz", 0));
|
||||
XCTAssertTrue(tree.InsertLiteral("/qaz", 0));
|
||||
|
||||
// Check that a tree with a matching prefix is successful
|
||||
XCTAssertTrue(tree.HasPrefix("/foo.txt"));
|
||||
|
||||
// This shouldn't succeed because `/bar` `/baz` and `qaz` are literals
|
||||
XCTAssertFalse(tree.HasPrefix("/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/qaz.txt"));
|
||||
|
||||
// Now change `/bar` to a prefix type and retest HasPrefix
|
||||
// `/bar.txt` should now succeed, but `/baz.txt` should still not pass
|
||||
XCTAssertTrue(tree.InsertPrefix("/bar", 0));
|
||||
XCTAssertTrue(tree.HasPrefix("/bar.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/baz.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/qaz.txt"));
|
||||
|
||||
// Insert a new prefix string to allow `/baz.txt` to have a valid prefix
|
||||
XCTAssertTrue(tree.InsertPrefix("/b", 0));
|
||||
XCTAssertTrue(tree.HasPrefix("/baz.txt"));
|
||||
XCTAssertFalse(tree.HasPrefix("/qaz.txt"));
|
||||
|
||||
// An exact match on a literal allows HasPrefix to succeed
|
||||
XCTAssertTrue(tree.InsertLiteral("/qaz.txt", 0));
|
||||
XCTAssertTrue(tree.HasPrefix("/qaz.txt"));
|
||||
}
|
||||
|
||||
- (void)testLookupLongestMatchingPrefix {
|
||||
PrefixTree<int> tree;
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 12));
|
||||
XCTAssertTrue(tree.InsertPrefix("/bar", 34));
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo/bar.txt", 56));
|
||||
|
||||
std::optional<int> value;
|
||||
|
||||
// Matching exact prefix
|
||||
value = tree.LookupLongestMatchingPrefix("/foo");
|
||||
XCTAssertEqual(value.value_or(0), 12);
|
||||
|
||||
// Ensure changing node type works as expected
|
||||
// Literals must match exactly.
|
||||
value = tree.LookupLongestMatchingPrefix("/foo/bar.txt.tmp");
|
||||
XCTAssertEqual(value.value_or(0), 56);
|
||||
XCTAssertTrue(tree.InsertLiteral("/foo/bar.txt", 90));
|
||||
value = tree.LookupLongestMatchingPrefix("/foo/bar.txt.tmp");
|
||||
XCTAssertEqual(value.value_or(0), 12);
|
||||
|
||||
// Inserting over an exiting node returns the new value
|
||||
XCTAssertTrue(tree.InsertPrefix("/foo", 78));
|
||||
value = tree.LookupLongestMatchingPrefix("/foo");
|
||||
XCTAssertEqual(value.value_or(0), 78);
|
||||
|
||||
// No matching prefix
|
||||
value = tree.LookupLongestMatchingPrefix("/asdf");
|
||||
XCTAssertEqual(value.value_or(0), 0);
|
||||
}
|
||||
|
||||
- (void)testNodeCounts {
|
||||
const uint32_t maxDepth = 100;
|
||||
PrefixTree<int> tree(100);
|
||||
|
||||
XCTAssertEqual(tree.NodeCount(), 0);
|
||||
|
||||
// Start with a small string
|
||||
XCTAssertTrue(tree.InsertPrefix("asdf", 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 4);
|
||||
|
||||
// Add a couple more characters to the existing string
|
||||
XCTAssertTrue(tree.InsertPrefix("asdfgh", 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 6);
|
||||
|
||||
// Inserting a string that exceeds max depth doesn't increase node count
|
||||
XCTAssertFalse(tree.InsertPrefix(std::string(maxDepth + 10, 'A').c_str(), 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 6);
|
||||
|
||||
// Add a new string that is a prefix of an existing string
|
||||
// This should increment the count by one since a new terminal node exists
|
||||
XCTAssertTrue(tree.InsertPrefix("as", 0));
|
||||
XCTAssertEqual(tree.NodeCount(), 7);
|
||||
|
||||
// Re-inserting onto an existing node shouldn't modify the count
|
||||
tree.InsertLiteral("as", 0);
|
||||
tree.InsertPrefix("as", 0);
|
||||
XCTAssertEqual(tree.NodeCount(), 7);
|
||||
}
|
||||
|
||||
- (void)testReset {
|
||||
// Ensure resetting a tree removes all content
|
||||
PrefixTree<int> tree;
|
||||
|
||||
tree.Reset();
|
||||
XCTAssertEqual(tree.NodeCount(), 0);
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("asdf", 0));
|
||||
XCTAssertTrue(tree.InsertPrefix("qwerty", 0));
|
||||
|
||||
XCTAssertTrue(tree.HasPrefix("asdf"));
|
||||
XCTAssertTrue(tree.HasPrefix("qwerty"));
|
||||
XCTAssertEqual(tree.NodeCount(), 10);
|
||||
|
||||
tree.Reset();
|
||||
XCTAssertFalse(tree.HasPrefix("asdf"));
|
||||
XCTAssertFalse(tree.HasPrefix("qwerty"));
|
||||
XCTAssertEqual(tree.NodeCount(), 0);
|
||||
}
|
||||
|
||||
- (void)testComplexValues {
|
||||
class Foo {
|
||||
public:
|
||||
Foo(int x) : x_(x) {}
|
||||
int X() { return x_; }
|
||||
|
||||
private:
|
||||
int x_;
|
||||
};
|
||||
|
||||
PrefixTree<std::shared_ptr<Foo>> tree;
|
||||
|
||||
XCTAssertTrue(tree.InsertPrefix("foo", std::make_shared<Foo>(123)));
|
||||
XCTAssertTrue(tree.InsertPrefix("bar", std::make_shared<Foo>(456)));
|
||||
|
||||
std::optional<std::shared_ptr<Foo>> value;
|
||||
value = tree.LookupLongestMatchingPrefix("foo");
|
||||
XCTAssertTrue(value.has_value() && value->get()->X() == 123);
|
||||
|
||||
value = tree.LookupLongestMatchingPrefix("bar");
|
||||
XCTAssertTrue(value.has_value() && value->get()->X() == 456);
|
||||
|
||||
value = tree.LookupLongestMatchingPrefix("asdf");
|
||||
XCTAssertFalse(value.has_value());
|
||||
}
|
||||
|
||||
- (void)testThreading {
|
||||
uint32_t count = 4096;
|
||||
auto t = new PrefixTree<int>(count * (uint32_t)[NSUUID UUID].UUIDString.length);
|
||||
|
||||
NSMutableArray *UUIDs = [NSMutableArray arrayWithCapacity:count];
|
||||
for (int i = 0; i < count; ++i) {
|
||||
[UUIDs addObject:[NSUUID UUID].UUIDString];
|
||||
}
|
||||
|
||||
__block BOOL stop = NO;
|
||||
|
||||
// Create a bunch of background noise.
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
for (uint64_t i = 0; i < UINT64_MAX; ++i) {
|
||||
dispatch_async(dispatch_get_global_queue(0, 0), ^{
|
||||
t->HasPrefix([UUIDs[i % count] UTF8String]);
|
||||
});
|
||||
if (stop) return;
|
||||
}
|
||||
});
|
||||
|
||||
// Fill up the tree.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
XCTAssertEqual(t->InsertPrefix([UUIDs[i] UTF8String], 0), true);
|
||||
});
|
||||
|
||||
// Make sure every leaf byte is found.
|
||||
dispatch_apply(count, dispatch_get_global_queue(0, 0), ^(size_t i) {
|
||||
XCTAssertTrue(t->HasPrefix([UUIDs[i] UTF8String]));
|
||||
});
|
||||
|
||||
stop = YES;
|
||||
}
|
||||
|
||||
@end
|
||||
52
Source/common/SNTBlockMessage.h
Normal file
52
Source/common/SNTBlockMessage.h
Normal file
@@ -0,0 +1,52 @@
|
||||
/// 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.
|
||||
|
||||
#ifdef SANTAGUI
|
||||
#import <Cocoa/Cocoa.h>
|
||||
#else
|
||||
#import <Foundation/Foundation.h>
|
||||
#endif
|
||||
|
||||
@class SNTStoredEvent;
|
||||
|
||||
@interface SNTBlockMessage : NSObject
|
||||
|
||||
///
|
||||
/// Return a message suitable for presenting to the user.
|
||||
///
|
||||
/// In SantaGUI this will return an NSAttributedString with links and formatting included
|
||||
/// while for santad all HTML will be properly stripped.
|
||||
///
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message;
|
||||
|
||||
///
|
||||
/// Uses either the configured message depending on the event type or a custom message
|
||||
/// if the rule that blocked this file included one, formatted using
|
||||
/// +[SNTBlockMessage formatMessage].
|
||||
///
|
||||
+ (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
|
||||
154
Source/common/SNTBlockMessage.m
Normal file
154
Source/common/SNTBlockMessage.m
Normal file
@@ -0,0 +1,154 @@
|
||||
/// 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 "Source/common/SNTBlockMessage.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
#import "Source/common/SNTStoredEvent.h"
|
||||
#import "Source/common/SNTSystemInfo.h"
|
||||
|
||||
@implementation SNTBlockMessage
|
||||
|
||||
+ (NSAttributedString *)formatMessage:(NSString *)message {
|
||||
NSString *htmlHeader =
|
||||
@"<html><head><style>"
|
||||
@"body {"
|
||||
@" font-family: 'Lucida Grande', 'Helvetica', sans-serif;"
|
||||
@" font-size: 13px;"
|
||||
@" color: %@;"
|
||||
@" text-align: center;"
|
||||
@"}"
|
||||
|
||||
// Supported in beta WebKit. Not sure if it is dynamic when used with NSAttributedString.
|
||||
@"@media (prefers-color-scheme: dark) {"
|
||||
@" body {"
|
||||
@" color: #ddd;"
|
||||
@" }"
|
||||
@"}"
|
||||
@"</style></head><body>";
|
||||
|
||||
// Support Dark Mode. Note, the returned NSAttributedString is static and does not update when
|
||||
// the OS switches modes.
|
||||
NSString *mode = [NSUserDefaults.standardUserDefaults stringForKey:@"AppleInterfaceStyle"];
|
||||
BOOL dark = [mode isEqualToString:@"Dark"];
|
||||
htmlHeader = [NSString stringWithFormat:htmlHeader, dark ? @"#ddd" : @"#333"];
|
||||
|
||||
NSString *htmlFooter = @"</body></html>";
|
||||
|
||||
NSString *fullHTML = [NSString stringWithFormat:@"%@%@%@", htmlHeader, message, htmlFooter];
|
||||
|
||||
#ifdef SANTAGUI
|
||||
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
|
||||
}
|
||||
|
||||
+ (NSAttributedString *)attributedBlockMessageForEvent:(SNTStoredEvent *)event
|
||||
customMessage:(NSString *)customMessage {
|
||||
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.";
|
||||
}
|
||||
}
|
||||
return [SNTBlockMessage formatMessage:message];
|
||||
}
|
||||
|
||||
+ (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 *hostname = [SNTSystemInfo longHostname];
|
||||
NSString *uuid = [SNTSystemInfo hardwareUUID];
|
||||
NSString *serial = [SNTSystemInfo serialNumber];
|
||||
NSString *formatStr = config.eventDetailURL;
|
||||
if (!formatStr.length) return nil;
|
||||
|
||||
if (event.fileSHA256) {
|
||||
// This key is deprecated, use %file_identifier% or %bundle_or_file_identifier%
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%file_sha%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%file_identifier%"
|
||||
withString:event.fileSHA256];
|
||||
formatStr =
|
||||
[formatStr stringByReplacingOccurrencesOfString:@"%bundle_or_file_identifier%"
|
||||
withString:event.fileBundleHash ?: event.fileSHA256];
|
||||
}
|
||||
if (event.executingUser) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%username%"
|
||||
withString:event.executingUser];
|
||||
}
|
||||
if (config.machineID) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%machine_id%"
|
||||
withString:config.machineID];
|
||||
}
|
||||
if (hostname.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%hostname%" withString:hostname];
|
||||
}
|
||||
if (uuid.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%uuid%" withString:uuid];
|
||||
}
|
||||
if (serial.length) {
|
||||
formatStr = [formatStr stringByReplacingOccurrencesOfString:@"%serial%" withString:serial];
|
||||
}
|
||||
|
||||
return [NSURL URLWithString:formatStr];
|
||||
}
|
||||
|
||||
@end
|
||||
45
Source/common/SNTCachedDecision.h
Normal file
45
Source/common/SNTCachedDecision.h
Normal file
@@ -0,0 +1,45 @@
|
||||
/// Copyright 2015-2022 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 <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommon.h"
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class MOLCertificate;
|
||||
|
||||
///
|
||||
/// Store information about executions from decision making for later logging.
|
||||
///
|
||||
@interface SNTCachedDecision : NSObject
|
||||
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile;
|
||||
|
||||
@property santa_vnode_id_t vnodeId;
|
||||
@property SNTEventState decision;
|
||||
@property NSString *decisionExtra;
|
||||
@property NSString *sha256;
|
||||
|
||||
@property NSString *certSHA256;
|
||||
@property NSString *certCommonName;
|
||||
@property NSArray<MOLCertificate *> *certChain;
|
||||
@property NSString *teamID;
|
||||
|
||||
@property NSString *quarantineURL;
|
||||
|
||||
@property NSString *customMsg;
|
||||
@property BOOL silentBlock;
|
||||
|
||||
@end
|
||||
28
Source/common/SNTCachedDecision.m
Normal file
28
Source/common/SNTCachedDecision.m
Normal file
@@ -0,0 +1,28 @@
|
||||
/// Copyright 2015-2022 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 "Source/common/SNTCachedDecision.h"
|
||||
|
||||
@implementation SNTCachedDecision
|
||||
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_vnodeId.fsid = (uint64_t)esFile->stat.st_dev;
|
||||
_vnodeId.fileid = esFile->stat.st_ino;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
@end
|
||||
36
Source/common/SNTCachedDecisionTest.mm
Normal file
36
Source/common/SNTCachedDecisionTest.mm
Normal file
@@ -0,0 +1,36 @@
|
||||
/// Copyright 2022 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTCachedDecision.h"
|
||||
#include "Source/common/TestUtils.h"
|
||||
|
||||
@interface SNTCachedDecisionTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTCachedDecisionTest
|
||||
|
||||
- (void)testSNTCachedDecisionInit {
|
||||
// Ensure the vnodeId field is properly set from the es_file_t
|
||||
struct stat sb = MakeStat();
|
||||
es_file_t file = MakeESFile("foo", sb);
|
||||
|
||||
SNTCachedDecision *cd = [[SNTCachedDecision alloc] initWithEndpointSecurityFile:&file];
|
||||
|
||||
XCTAssertEqual(sb.st_ino, cd.vnodeId.fileid);
|
||||
XCTAssertEqual(sb.st_dev, cd.vnodeId.fsid);
|
||||
}
|
||||
|
||||
@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, nonatomic) SecCertificateRef certRef;
|
||||
|
||||
///
|
||||
/// SHA-1 hash of the certificate data.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *SHA1;
|
||||
|
||||
///
|
||||
/// SHA-256 hash of the certificate data.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *SHA256;
|
||||
|
||||
///
|
||||
/// Certificate data.
|
||||
///
|
||||
@property(readonly, nonatomic) NSData *certData;
|
||||
|
||||
///
|
||||
/// Common Name e.g: "Software Signing"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *commonName;
|
||||
|
||||
///
|
||||
/// Country Name e.g: "US"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *countryName;
|
||||
|
||||
///
|
||||
/// Organizational Name e.g: "Apple Inc."
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *orgName;
|
||||
|
||||
///
|
||||
/// Organizational Unit Name e.g: "Apple Software"
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *orgUnit;
|
||||
|
||||
///
|
||||
/// Issuer details, same fields as above.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *issuerCommonName;
|
||||
@property(readonly, nonatomic) NSString *issuerCountryName;
|
||||
@property(readonly, nonatomic) NSString *issuerOrgName;
|
||||
@property(readonly, nonatomic) NSString *issuerOrgUnit;
|
||||
|
||||
///
|
||||
/// Validity Not Before
|
||||
///
|
||||
@property(readonly, nonatomic) NSDate *validFrom;
|
||||
|
||||
///
|
||||
/// Validity Not After
|
||||
///
|
||||
@property(readonly, nonatomic) NSDate *validUntil;
|
||||
|
||||
@end
|
||||
@@ -1,361 +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:(id)other {
|
||||
if (self == other) return YES;
|
||||
if (![other isKindOfClass:[SNTCertificate class]]) return NO;
|
||||
|
||||
SNTCertificate *o = other;
|
||||
return [self.certData isEqual:o.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 *e) {
|
||||
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, nonatomic) SNTCertificate *leafCertificate;
|
||||
|
||||
///
|
||||
/// Returns the on-disk path of this binary.
|
||||
///
|
||||
@property(readonly, nonatomic) 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,193 +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
|
||||
*/
|
||||
static const SecCSFlags kStaticSigningFlags = kSecCSDoNotValidateResources | kSecCSCheckNestedCode;
|
||||
|
||||
/**
|
||||
* kSigningFlags are the flags used when validating signatures for running binaries.
|
||||
*
|
||||
* No special flags needed currently.
|
||||
*/
|
||||
static 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 (NSUInteger 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(self.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
|
||||
59
Source/common/SNTCommon.h
Normal file
59
Source/common/SNTCommon.h
Normal file
@@ -0,0 +1,59 @@
|
||||
/// Copyright 2015-2022 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.
|
||||
|
||||
///
|
||||
/// Common defines between daemon <-> client
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__COMMON_H
|
||||
#define SANTA__COMMON__COMMON_H
|
||||
|
||||
#include <stdint.h>
|
||||
#include <sys/param.h>
|
||||
|
||||
// Branch prediction
|
||||
#define likely(x) __builtin_expect(!!(x), 1)
|
||||
#define unlikely(x) __builtin_expect(!!(x), 0)
|
||||
|
||||
typedef enum {
|
||||
ACTION_UNSET,
|
||||
|
||||
// REQUESTS
|
||||
// If an operation is awaiting a cache decision from a similar operation
|
||||
// currently being processed, it will poll about every 5 ms for an answer.
|
||||
ACTION_REQUEST_BINARY,
|
||||
|
||||
// RESPONSES
|
||||
ACTION_RESPOND_ALLOW,
|
||||
ACTION_RESPOND_DENY,
|
||||
ACTION_RESPOND_ALLOW_COMPILER,
|
||||
} santa_action_t;
|
||||
|
||||
#define RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_ALLOW || x == ACTION_RESPOND_DENY || \
|
||||
x == ACTION_RESPOND_ALLOW_COMPILER)
|
||||
|
||||
// Struct to manage vnode IDs
|
||||
typedef struct santa_vnode_id_t {
|
||||
uint64_t fsid;
|
||||
uint64_t fileid;
|
||||
|
||||
#ifdef __cplusplus
|
||||
bool operator==(const santa_vnode_id_t &rhs) const {
|
||||
return fsid == rhs.fsid && fileid == rhs.fileid;
|
||||
}
|
||||
#endif
|
||||
} santa_vnode_id_t;
|
||||
|
||||
#endif // SANTA__COMMON__COMMON_H
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2015-2022 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.
|
||||
@@ -12,57 +12,113 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#ifndef SANTA__COMMON__COMMONENUMS_H
|
||||
#define SANTA__COMMON__COMMONENUMS_H
|
||||
#import <Foundation/Foundation.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,
|
||||
SNTRuleTypeTeamID = 3,
|
||||
};
|
||||
|
||||
RULETYPE_MAX
|
||||
} santa_ruletype_t;
|
||||
typedef NS_ENUM(NSInteger, SNTRuleState) {
|
||||
SNTRuleStateUnknown,
|
||||
|
||||
typedef enum {
|
||||
RULESTATE_UNKNOWN,
|
||||
SNTRuleStateAllow = 1,
|
||||
SNTRuleStateBlock = 2,
|
||||
SNTRuleStateSilentBlock = 3,
|
||||
SNTRuleStateRemove = 4,
|
||||
|
||||
RULESTATE_WHITELIST = 1,
|
||||
RULESTATE_BLACKLIST = 2,
|
||||
RULESTATE_SILENT_BLACKLIST = 3,
|
||||
RULESTATE_REMOVE = 4,
|
||||
SNTRuleStateAllowCompiler = 5,
|
||||
SNTRuleStateAllowTransitive = 6,
|
||||
};
|
||||
|
||||
RULESTATE_MAX
|
||||
} santa_rulestate_t;
|
||||
typedef NS_ENUM(NSInteger, SNTClientMode) {
|
||||
SNTClientModeUnknown,
|
||||
|
||||
typedef enum {
|
||||
CLIENTMODE_UNKNOWN,
|
||||
SNTClientModeMonitor = 1,
|
||||
SNTClientModeLockdown = 2,
|
||||
};
|
||||
|
||||
CLIENTMODE_MONITOR = 1,
|
||||
CLIENTMODE_LOCKDOWN = 2,
|
||||
typedef NS_ENUM(NSInteger, SNTEventState) {
|
||||
// Bits 0-15 bits store non-decision types
|
||||
SNTEventStateUnknown = 0,
|
||||
SNTEventStateBundleBinary = 1,
|
||||
|
||||
CLIENTMODE_MAX
|
||||
} santa_clientmode_t;
|
||||
// Bits 16-23 store deny decision types
|
||||
SNTEventStateBlockUnknown = 1 << 16,
|
||||
SNTEventStateBlockBinary = 1 << 17,
|
||||
SNTEventStateBlockCertificate = 1 << 18,
|
||||
SNTEventStateBlockScope = 1 << 19,
|
||||
SNTEventStateBlockTeamID = 1 << 20,
|
||||
SNTEventStateBlockLongPath = 1 << 21,
|
||||
|
||||
typedef enum {
|
||||
EVENTSTATE_UNKNOWN,
|
||||
// Bits 24-31 store allow decision types
|
||||
SNTEventStateAllowUnknown = 1 << 24,
|
||||
SNTEventStateAllowBinary = 1 << 25,
|
||||
SNTEventStateAllowCertificate = 1 << 26,
|
||||
SNTEventStateAllowScope = 1 << 27,
|
||||
SNTEventStateAllowCompiler = 1 << 28,
|
||||
SNTEventStateAllowTransitive = 1 << 29,
|
||||
SNTEventStateAllowPendingTransitive = 1 << 30,
|
||||
SNTEventStateAllowTeamID = 1 << 31,
|
||||
|
||||
EVENTSTATE_ALLOW_UNKNOWN = 1,
|
||||
EVENTSTATE_ALLOW_BINARY = 2,
|
||||
EVENTSTATE_ALLOW_CERTIFICATE = 3,
|
||||
EVENTSTATE_ALLOW_SCOPE = 4,
|
||||
// Block and Allow masks
|
||||
SNTEventStateBlock = 0xFF << 16,
|
||||
SNTEventStateAllow = 0xFF << 24
|
||||
};
|
||||
|
||||
EVENTSTATE_BLOCK_UNKNOWN = 5,
|
||||
EVENTSTATE_BLOCK_BINARY = 6,
|
||||
EVENTSTATE_BLOCK_CERTIFICATE = 7,
|
||||
EVENTSTATE_BLOCK_SCOPE = 8,
|
||||
typedef NS_ENUM(NSInteger, SNTRuleTableError) {
|
||||
SNTRuleTableErrorEmptyRuleArray,
|
||||
SNTRuleTableErrorInsertOrReplaceFailed,
|
||||
SNTRuleTableErrorInvalidRule,
|
||||
SNTRuleTableErrorRemoveFailed
|
||||
};
|
||||
|
||||
EVENTSTATE_MAX
|
||||
} santa_eventstate_t;
|
||||
// This enum type is used to indicate what should be done with the related bundle events that are
|
||||
// generated when an initiating blocked bundle event occurs.
|
||||
typedef NS_ENUM(NSInteger, SNTBundleEventAction) {
|
||||
SNTBundleEventActionDropEvents,
|
||||
SNTBundleEventActionStoreEvents,
|
||||
SNTBundleEventActionSendEvents,
|
||||
};
|
||||
|
||||
#endif // SANTA__COMMON__COMMONENUMS_H
|
||||
// Indicates where to store event logs.
|
||||
typedef NS_ENUM(NSInteger, SNTEventLogType) {
|
||||
SNTEventLogTypeSyslog,
|
||||
SNTEventLogTypeFilelog,
|
||||
SNTEventLogTypeProtobuf,
|
||||
SNTEventLogTypeNull,
|
||||
};
|
||||
|
||||
// The return status of a sync.
|
||||
typedef NS_ENUM(NSInteger, SNTSyncStatusType) {
|
||||
SNTSyncStatusTypeSuccess,
|
||||
SNTSyncStatusTypePreflightFailed,
|
||||
SNTSyncStatusTypeEventUploadFailed,
|
||||
SNTSyncStatusTypeRuleDownloadFailed,
|
||||
SNTSyncStatusTypePostflightFailed,
|
||||
SNTSyncStatusTypeTooManySyncsInProgress,
|
||||
SNTSyncStatusTypeMissingSyncBaseURL,
|
||||
SNTSyncStatusTypeMissingMachineID,
|
||||
SNTSyncStatusTypeDaemonTimeout,
|
||||
SNTSyncStatusTypeSyncStarted,
|
||||
SNTSyncStatusTypeUnknown,
|
||||
};
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricFormatType) {
|
||||
SNTMetricFormatTypeUnknown,
|
||||
SNTMetricFormatTypeRawJSON,
|
||||
SNTMetricFormatTypeMonarchJSON,
|
||||
};
|
||||
|
||||
static const char *kSantaDPath =
|
||||
"/Applications/Santa.app/Contents/Library/SystemExtensions/"
|
||||
"com.google.santa.daemon.systemextension/Contents/MacOS/com.google.santa.daemon";
|
||||
static const char *kSantaAppPath = "/Applications/Santa.app";
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2015-2022 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.
|
||||
@@ -12,31 +12,257 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#include "SNTCommonEnums.h"
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#import "Source/common/SNTCommonEnums.h"
|
||||
|
||||
@class SNTRule;
|
||||
|
||||
///
|
||||
/// Singleton that provides an interface for managing configuration values on disk
|
||||
/// @note This class is designed as a singleton but that is not strictly enforced.
|
||||
/// @note All properties are KVO compliant.
|
||||
///
|
||||
@interface SNTConfigurator : NSObject
|
||||
|
||||
/// Default config file path
|
||||
extern NSString * const kDefaultConfigFilePath;
|
||||
|
||||
#pragma mark - Daemon Settings
|
||||
|
||||
///
|
||||
/// The operating mode.
|
||||
/// The operating mode. Defaults to MONITOR.
|
||||
///
|
||||
@property(nonatomic) santa_clientmode_t clientMode;
|
||||
@property(readonly, nonatomic) SNTClientMode clientMode;
|
||||
|
||||
///
|
||||
/// Whether or not to log all events, even for whitelisted binaries.
|
||||
/// Set the operating mode as received from a sync server.
|
||||
///
|
||||
@property(nonatomic) BOOL logAllEvents;
|
||||
- (void)setSyncServerClientMode:(SNTClientMode)newMode;
|
||||
|
||||
///
|
||||
/// Enable Fail Close mode. Defaults to NO.
|
||||
/// This controls Santa's behavior when a failure occurs, such as an
|
||||
/// inability to read a file. By default, to prevent bugs or misconfiguration
|
||||
/// from rendering a machine inoperable Santa will fail open and allow
|
||||
/// execution. With this setting enabled, Santa will fail closed if the client
|
||||
/// is in LOCKDOWN mode, offering a higher level of security but with a higher
|
||||
/// potential for causing problems.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL failClosed;
|
||||
|
||||
///
|
||||
/// A set of static rules that should always apply. These can be used as a
|
||||
/// fallback set of rules for management tools that should always be allowed to
|
||||
/// run even if a sync server does something unexpected. It can also be used
|
||||
/// as the sole source of rules, distributed with an MDM.
|
||||
///
|
||||
/// The value of this key should be an array containing dictionaries. Each
|
||||
/// dictionary should contain the same keys used for syncing, e.g:
|
||||
///
|
||||
/// <key>StaticRules</key>
|
||||
/// <array>
|
||||
/// <dict>
|
||||
/// <key>identifier</key>
|
||||
/// <string>binary sha256, certificate sha256, team ID</string>
|
||||
/// <key>rule_type</key>
|
||||
/// <string>BINARY</string> (one of BINARY, CERTIFICATE or TEAMID)
|
||||
/// <key>policy</key>
|
||||
/// <string>BLOCKLIST</string> (one of ALLOWLIST, ALLOWLIST_COMPILER, BLOCKLIST,
|
||||
/// SILENT_BLOCKLIST)
|
||||
/// </dict>
|
||||
/// </array>
|
||||
///
|
||||
/// The return of this property is a dictionary where the keys are the
|
||||
/// identifiers of each rule, with the SNTRule as a value
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary<NSString *, SNTRule *> *staticRules;
|
||||
|
||||
///
|
||||
/// The regex of allowed 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(readonly, nonatomic) NSRegularExpression *allowedPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of allowed paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerAllowedPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of blocked 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(readonly, nonatomic) NSRegularExpression *blockedPathRegex;
|
||||
|
||||
///
|
||||
/// Set the regex of blocked paths as received from a sync server.
|
||||
///
|
||||
- (void)setSyncServerBlockedPathRegex:(NSRegularExpression *)re;
|
||||
|
||||
///
|
||||
/// The regex of paths to log file changes for. Regexes are specified in ICU format.
|
||||
///
|
||||
/// The regex flags IXSM can be used, though the s (dotalL) and m (multiline) flags are
|
||||
/// pointless as a path only ever has a single line.
|
||||
/// If the regex doesn't begin with ^ to match from the beginning of the line, it will be added.
|
||||
///
|
||||
@property(readonly, nonatomic) NSRegularExpression *fileChangesRegex;
|
||||
|
||||
///
|
||||
/// A list of ignore prefixes which are checked in-kernel.
|
||||
/// This is more performant than FileChangesRegex when ignoring whole directory trees.
|
||||
///
|
||||
/// For example adding a prefix of "/private/tmp/" will turn off file change log generation
|
||||
/// in-kernel for that entire tree. Since they are ignored by the kernel, they never reach santad
|
||||
/// and are not seen by the fileChangesRegex. Note the trailing "/", without it any file or
|
||||
/// directory starting with "/private/tmp" would be ignored.
|
||||
///
|
||||
/// By default "/." and "/dev/" are added.
|
||||
///
|
||||
/// Memory in the kernel is precious. A total of MAXPATHLEN (1024) nodes are allowed.
|
||||
/// Using all 1024 nodes will result in santa-driver allocating ~2MB of wired memory.
|
||||
/// An ASCII character uses 1 node. An UTF-8 encoded Unicode character uses 1-4 nodes.
|
||||
/// Prefixes are added to the running config in-order, one by one. The prefix will be ignored if
|
||||
/// (the running config's current size) + (the prefix's size) totals up to more than 1024 nodes.
|
||||
/// The running config is stored in a prefix tree.
|
||||
/// Prefixes that share prefixes are effectively de-duped; their shared node sized components only
|
||||
/// take up 1 node. For example these 3 prefixes all have a common prefix of "/private/".
|
||||
/// They will only take up 21 nodes instead of 39.
|
||||
///
|
||||
/// "/private/tmp/"
|
||||
/// "/private/var/"
|
||||
/// "/private/new/"
|
||||
///
|
||||
/// -> [t] -> [m] -> [p] -> [/]
|
||||
///
|
||||
/// [/] -> [p] -> [r] -> [i] -> [v] -> [a] -> [t] -> [e] -> [/] -> [v] -> [a] -> [r] -> [/]
|
||||
///
|
||||
/// -> [n] -> [e] -> [w] -> [/]
|
||||
///
|
||||
/// Prefixes with Unicode characters work similarly. Assuming a UTF-8 encoding these two prefixes
|
||||
/// are actually the same for the first 3 nodes. They take up 7 nodes instead of 10.
|
||||
///
|
||||
/// "/🤘"
|
||||
/// "/🖖"
|
||||
///
|
||||
/// -> [0xa4] -> [0x98]
|
||||
///
|
||||
/// [/] -> [0xf0] -> [0x9f]
|
||||
///
|
||||
/// -> [0x96] -> [0x96]
|
||||
///
|
||||
/// To disable file change logging completely add "/".
|
||||
/// TODO(bur): Make this default if no FileChangesRegex is set.
|
||||
///
|
||||
/// Filters are only applied on santad startup.
|
||||
/// TODO(bur): Support add / remove of filters while santad is running.
|
||||
///
|
||||
@property(readonly, nonatomic) NSArray *fileChangesPrefixFilters;
|
||||
|
||||
///
|
||||
/// 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;
|
||||
|
||||
///
|
||||
/// Enable bad signature protection, defaults to NO.
|
||||
/// When enabled, a binary that is signed but has a bad signature (cert revoked, binary
|
||||
/// tampered with, etc.) will be blocked regardless of client-mode unless a binary allowlist
|
||||
/// rule exists.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableBadSignatureProtection;
|
||||
|
||||
///
|
||||
/// Defines how event logs are stored. Options are:
|
||||
/// SNTEventLogTypeSyslog "syslog": Sent to ASL or ULS (if built with the 10.12 SDK or later).
|
||||
/// SNTEventLogTypeFilelog "file": Sent to a file on disk. Use eventLogPath to specify a path.
|
||||
/// SNTEventLogTypeNull "null": Logs nothing
|
||||
/// SNTEventLogTypeProtobuf "protobuf": (BETA) Sent to a file on disk, using a maildir-like
|
||||
/// format. Use spoolDirectory to specify a path. Use spoolDirectoryFileSizeThresholdKB,
|
||||
/// spoolDirectorySizeThresholdMB and spoolDirectoryEventMaxFlushTimeSec to configure
|
||||
/// additional settings.
|
||||
/// Defaults to SNTEventLogTypeFilelog.
|
||||
/// For mobileconfigs use EventLogType as the key and syslog or filelog strings as the value.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTEventLogType eventLogType;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to Filelog, eventLogPath will provide the path to save logs.
|
||||
/// Defaults to /var/db/santa/santa.log.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventLogPath;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectory will provide the base path used for
|
||||
/// saving logs using a maildir-like format.
|
||||
/// Defaults to /var/db/santa/spool.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *spoolDirectory;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryFileSizeThresholdKB sets the per-file size
|
||||
/// limit for files saved in the spoolDirectory.
|
||||
/// Defaults to 250.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectoryFileSizeThresholdKB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectorySizeThresholdMB sets the total size
|
||||
/// limit for all files saved in the spoolDirectory.
|
||||
/// Defaults to 100.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger spoolDirectorySizeThresholdMB;
|
||||
|
||||
///
|
||||
/// If eventLogType is set to protobuf, spoolDirectoryEventMaxFlushTimeSec sets the maximum amount
|
||||
/// of time an event will be stored in memory before being written to disk.
|
||||
/// Defaults to 15.0.
|
||||
///
|
||||
/// @note: This property is KVO compliant, but should only be read once at santad startup.
|
||||
///
|
||||
@property(readonly, nonatomic) float spoolDirectoryEventMaxFlushTimeSec;
|
||||
|
||||
///
|
||||
/// Enabling this appends the Santa machine ID to the end of each log line. If nothing
|
||||
/// has been overriden, this is the host's UUID.
|
||||
/// Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableMachineIDDecoration;
|
||||
|
||||
#pragma mark - GUI Settings
|
||||
|
||||
///
|
||||
/// When silent mode is enabled, Santa will never show notifications for
|
||||
/// blocked processes.
|
||||
///
|
||||
/// This can be a very confusing experience for users, use with caution.
|
||||
///
|
||||
/// Defaults to NO.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableSilentMode;
|
||||
|
||||
///
|
||||
/// The text to display when opening Santa.app.
|
||||
/// If unset, the default text will be displayed.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *aboutText;
|
||||
|
||||
///
|
||||
/// The URL to open when the user clicks "More Info..." when opening Santa.app.
|
||||
/// If unset, the button will not be displayed.
|
||||
@@ -46,12 +272,16 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
/// When the user gets a block notification, a button can be displayed which will
|
||||
/// take them to a web page with more information about that event.
|
||||
///
|
||||
/// 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.
|
||||
/// %serial% -- System's serial number.
|
||||
/// %uuid% -- System's UUID.
|
||||
/// %hostname% -- System's full hostname.
|
||||
///
|
||||
/// @note: This is not an NSURL because the format-string parsing is done elsewhere.
|
||||
///
|
||||
@@ -64,6 +294,45 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *eventDetailText;
|
||||
|
||||
///
|
||||
/// 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;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is denied
|
||||
/// from the BlockUSB configuration setting. If not configured, a reasonable
|
||||
/// default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *bannedUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// This is the message shown to the user when a USB storage device's mount is forcibly
|
||||
/// remounted to a different set of permissions from the BlockUSB and RemountUSBMode
|
||||
/// configuration settings. If not configured, a reasonable default is provided.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *remountUSBBlockMessage;
|
||||
|
||||
///
|
||||
/// 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
|
||||
|
||||
///
|
||||
@@ -71,16 +340,65 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *syncBaseURL;
|
||||
|
||||
///
|
||||
/// Proxy settings for syncing.
|
||||
/// This dictionary is passed directly to NSURLSession. The allowed keys
|
||||
/// are loosely documented at
|
||||
/// https://developer.apple.com/documentation/cfnetwork/global_proxy_settings_constants.
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *syncProxyConfig;
|
||||
|
||||
///
|
||||
/// The machine owner.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *machineOwner;
|
||||
|
||||
///
|
||||
/// The last date of a successful full sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *fullSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// The last date of a successful rule sync.
|
||||
///
|
||||
@property(nonatomic) NSDate *ruleSyncLastSuccess;
|
||||
|
||||
///
|
||||
/// If YES a clean sync is required.
|
||||
///
|
||||
@property(nonatomic) BOOL syncCleanRequired;
|
||||
|
||||
///
|
||||
/// USB Mount Blocking. Defaults to false.
|
||||
///
|
||||
@property(nonatomic) BOOL blockUSBMount;
|
||||
|
||||
///
|
||||
/// Comma-seperated `$ mount -o` arguments used for forced remounting of USB devices. Default
|
||||
/// to fully allow/deny without remounting if unset.
|
||||
///
|
||||
@property(nonatomic) NSArray<NSString *> *remountUSBMode;
|
||||
|
||||
///
|
||||
/// If set, this over-rides the default machine ID used for syncing.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *machineID;
|
||||
|
||||
///
|
||||
/// If YES, enables bundle detection for blocked events. This property is not stored on disk.
|
||||
/// Its value is set by a sync server that supports bundles. Defaults to NO.
|
||||
///
|
||||
@property BOOL enableBundles;
|
||||
|
||||
#pragma mark Transitive Allowlist Settings
|
||||
|
||||
///
|
||||
/// If YES, binaries marked with SNTRuleStateAllowCompiler rules are allowed to transitively
|
||||
/// allow any executables that they produce. If NO, SNTRuleStateAllowCompiler rules are
|
||||
/// interpreted as if they were simply SNTRuleStateAllow rules. Defaults to NO.
|
||||
///
|
||||
@property BOOL enableTransitiveRules;
|
||||
|
||||
#pragma mark Server Auth Settings
|
||||
|
||||
///
|
||||
@@ -118,21 +436,106 @@ extern NSString * const kDefaultConfigFilePath;
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *syncClientAuthCertificateIssuer;
|
||||
|
||||
///
|
||||
/// If true, syncs will upload events when a clean sync is requested. Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableCleanSyncEventUpload;
|
||||
|
||||
///
|
||||
/// If true, events will be uploaded for all executions, even those that are allowed.
|
||||
/// Use with caution, this generates a lot of events. Defaults to false.
|
||||
///
|
||||
@property(nonatomic) BOOL enableAllEventUpload;
|
||||
|
||||
///
|
||||
/// If true, events will *not* be uploaded for ALLOW_UNKNOWN events for clients in Monitor mode.
|
||||
///
|
||||
@property(nonatomic) BOOL disableUnknownEventUpload;
|
||||
|
||||
///
|
||||
/// If true, forks and exits will be logged. Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableForkAndExitLogging;
|
||||
|
||||
///
|
||||
/// If true, ignore actions from other endpoint security clients. Defaults to false. This only
|
||||
/// applies when running as a sysx.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL ignoreOtherEndpointSecurityClients;
|
||||
|
||||
///
|
||||
/// If true, debug logging will be enabled for all Santa components. Defaults to false.
|
||||
/// Passing --debug as an executable argument will enable debug logging for that specific
|
||||
/// component.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableDebugLogging;
|
||||
|
||||
///
|
||||
/// If true, compressed requests from "santactl sync" will set "Content-Encoding" to "zlib"
|
||||
/// instead of the new default "deflate". If syncing with Upvote deployed at commit 0b4477d
|
||||
/// or below, set this option to true.
|
||||
/// Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL enableBackwardsCompatibleContentEncoding;
|
||||
|
||||
///
|
||||
/// Contains the FCM project name.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fcmProject;
|
||||
|
||||
///
|
||||
/// Contains the FCM project entity.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fcmEntity;
|
||||
|
||||
///
|
||||
/// Contains the FCM project API key.
|
||||
///
|
||||
@property(readonly, nonatomic) NSString *fcmAPIKey;
|
||||
|
||||
///
|
||||
/// True if fcmProject, fcmEntity and fcmAPIKey are all set. Defaults to false.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL fcmEnabled;
|
||||
|
||||
///
|
||||
/// True if metricsFormat and metricsURL are set. False otherwise.
|
||||
///
|
||||
@property(readonly, nonatomic) BOOL exportMetrics;
|
||||
|
||||
///
|
||||
/// Format to export Metrics as.
|
||||
///
|
||||
@property(readonly, nonatomic) SNTMetricFormatType metricFormat;
|
||||
|
||||
///
|
||||
/// URL describing where metrics are exported, defaults to nil.
|
||||
///
|
||||
@property(readonly, nonatomic) NSURL *metricURL;
|
||||
|
||||
///
|
||||
/// Extra Metric Labels to add to the metrics payloads.
|
||||
///
|
||||
@property(readonly, nonatomic) NSDictionary *extraMetricLabels;
|
||||
|
||||
///
|
||||
/// Duration in seconds of how often the metrics should be exported.
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportInterval;
|
||||
|
||||
///
|
||||
/// Duration in seconds for metrics export timeout. Defaults to 30;
|
||||
///
|
||||
@property(readonly, nonatomic) NSUInteger metricExportTimeout;
|
||||
|
||||
///
|
||||
/// Retrieve an initialized singleton configurator object using the default file path.
|
||||
///
|
||||
+ (instancetype)configurator;
|
||||
|
||||
///
|
||||
/// Designated initializer.
|
||||
/// Clear the sync server configuration from the effective configuration.
|
||||
///
|
||||
/// @param filePath The path to the file to use as a backing store.
|
||||
///
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath;
|
||||
|
||||
///
|
||||
/// Re-read config data from disk.
|
||||
///
|
||||
- (void)reloadConfigData;
|
||||
- (void)clearSyncState;
|
||||
|
||||
@end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
27
Source/common/SNTDeviceEvent.h
Normal file
27
Source/common/SNTDeviceEvent.h
Normal file
@@ -0,0 +1,27 @@
|
||||
/// Copyright 2022 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 <Foundation/Foundation.h>
|
||||
|
||||
@interface SNTDeviceEvent : NSObject <NSSecureCoding>
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname;
|
||||
|
||||
@property NSString *mntonname;
|
||||
@property NSString *mntfromname;
|
||||
@property NSArray<NSString *> *remountArgs;
|
||||
|
||||
- (NSString *)readableRemountArgs;
|
||||
|
||||
@end
|
||||
63
Source/common/SNTDeviceEvent.m
Normal file
63
Source/common/SNTDeviceEvent.m
Normal file
@@ -0,0 +1,63 @@
|
||||
#import "Source/common/SNTDeviceEvent.h"
|
||||
|
||||
@implementation SNTDeviceEvent
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wobjc-literal-conversion"
|
||||
|
||||
#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]
|
||||
|
||||
- (instancetype)initWithOnName:(NSString *)mntonname fromName:(NSString *)mntfromname {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = mntonname;
|
||||
_mntfromname = mntfromname;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
+ (BOOL)supportsSecureCoding {
|
||||
return YES;
|
||||
}
|
||||
|
||||
- (void)encodeWithCoder:(NSCoder *)coder {
|
||||
ENCODE(self.mntonname, @"mntonname");
|
||||
ENCODE(self.mntfromname, @"mntfromname");
|
||||
ENCODE(self.remountArgs, @"remountArgs");
|
||||
}
|
||||
|
||||
- (instancetype)initWithCoder:(NSCoder *)decoder {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_mntonname = DECODE(NSString, @"mntonname");
|
||||
_mntfromname = DECODE(NSString, @"mntfromname");
|
||||
_remountArgs = DECODEARRAY(NSString, @"remountArgs");
|
||||
}
|
||||
return self;
|
||||
}
|
||||
- (NSString *)description {
|
||||
return [NSString stringWithFormat:@"SNTDeviceEvent '%@' -> '%@' (with permissions: [%@]",
|
||||
self.mntfromname, self.mntonname,
|
||||
[self.remountArgs componentsJoinedByString:@", "]];
|
||||
}
|
||||
|
||||
- (NSString *)readableRemountArgs {
|
||||
NSMutableArray<NSString *> *readable = [NSMutableArray array];
|
||||
for (NSString *arg in self.remountArgs) {
|
||||
if ([arg isEqualToString:@"rdonly"]) {
|
||||
[readable addObject:@"read-only"];
|
||||
} else if ([arg isEqualToString:@"noexec"]) {
|
||||
[readable addObject:@"block executables"];
|
||||
} else {
|
||||
[readable addObject:arg];
|
||||
}
|
||||
}
|
||||
return [readable componentsJoinedByString:@", "];
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -12,6 +12,8 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
///
|
||||
/// Simple function to check and drop root privileges.
|
||||
///
|
||||
|
||||
@@ -12,7 +12,7 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTDropRootPrivs.h"
|
||||
#import "Source/common/SNTDropRootPrivs.h"
|
||||
|
||||
BOOL DropRootPrivileges() {
|
||||
if (getuid() == 0 || geteuid() == 0 || getgid() == 0 || getegid() == 0) {
|
||||
|
||||
@@ -1,4 +1,4 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
/// Copyright 2015-2022 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.
|
||||
@@ -12,6 +12,11 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <EndpointSecurity/EndpointSecurity.h>
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
@class MOLCodesignChecker;
|
||||
|
||||
///
|
||||
/// Represents a binary on disk, providing access to details about that binary
|
||||
/// such as the SHA-1, SHA-256, Info.plist and the Mach-O data.
|
||||
@@ -23,14 +28,51 @@
|
||||
///
|
||||
/// @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 esFile Pointer to an es_file_t provided by the EndpointSecurity framework.
|
||||
/// Assumes that the path is a resolved path.
|
||||
///
|
||||
- (instancetype)initWithEndpointSecurityFile:(const es_file_t *)esFile 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;
|
||||
|
||||
///
|
||||
/// Initializer for already resolved paths.
|
||||
///
|
||||
/// @param path The path of the file this instance is to represent. The path will
|
||||
/// not be converted and will be used as is. If the path is not a regular file this method will
|
||||
/// return nil and fill in an error.
|
||||
/// @param error If an error occurred and nil is returned, this will be a pointer to an NSError
|
||||
/// describing the problem.
|
||||
///
|
||||
- (instancetype)initWithResolvedPath:(NSString *)path error:(NSError **)error;
|
||||
|
||||
///
|
||||
/// @return Path of this file.
|
||||
///
|
||||
- (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 +83,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).
|
||||
///
|
||||
@@ -72,6 +108,11 @@
|
||||
///
|
||||
- (BOOL)isDylib;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a bundle executable (QuickLook/Spotlight plugin, etc.)
|
||||
///
|
||||
- (BOOL)isBundle;
|
||||
|
||||
///
|
||||
/// @return YES if this file is a kernel extension.
|
||||
///
|
||||
@@ -82,6 +123,41 @@
|
||||
///
|
||||
- (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 NSString describing the kind of file (executable, bundle, script, etc.)
|
||||
///
|
||||
- (NSString *)humanReadableFileType;
|
||||
|
||||
///
|
||||
/// @return YES if this file has a bad/missing __PAGEZERO .
|
||||
///
|
||||
- (BOOL)isMissingPageZero;
|
||||
|
||||
///
|
||||
/// If set to YES, the bundle* and infoPlist methods will search for and use the highest NSBundle
|
||||
/// found in the tree. Defaults to NO, which uses the first found bundle, if any.
|
||||
///
|
||||
/// @example:
|
||||
/// An SNTFileInfo object that represents
|
||||
/// /Applications/Photos.app/Contents/XPCServices/com.apple.Photos.librarychooserservice.xpc
|
||||
/// useAncestorBundle is set to YES
|
||||
/// /Applications/Photos.app will be used to get data backing all the bundle methods
|
||||
///
|
||||
/// @note: The NSBundle object backing the bundle* and infoPlist methods is cached once found.
|
||||
/// Setting the useAncestorBundle propery will clear this cache and force a re-search.
|
||||
///
|
||||
@property(nonatomic) BOOL useAncestorBundle;
|
||||
|
||||
///
|
||||
/// @return An NSBundle if this file is part of a bundle.
|
||||
///
|
||||
@@ -94,8 +170,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 +196,40 @@
|
||||
- (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;
|
||||
|
||||
///
|
||||
/// @return The underlying file handle.
|
||||
///
|
||||
@property(readonly) NSFileHandle *fileHandle;
|
||||
|
||||
///
|
||||
/// @return Returns an instance of MOLCodeSignChecker initialized with the file's binary path.
|
||||
/// Both the MOLCodesignChecker and any resulting NSError are cached and returned on subsequent
|
||||
/// calls. You may pass in NULL for the error if you don't care to receive it.
|
||||
///
|
||||
- (MOLCodesignChecker *)codesignCheckerWithError:(NSError **)error;
|
||||
|
||||
@end
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
243
Source/common/SNTFileInfoTest.m
Normal file
243
Source/common/SNTFileInfoTest.m
Normal file
@@ -0,0 +1,243 @@
|
||||
/// Copyright 2015 Google Inc. All rights reserved.
|
||||
///
|
||||
/// Licensed under the Apache License, Version 2.0 (the "License");
|
||||
/// you may not use this file except in compliance with the License.
|
||||
/// You may obtain a copy of the License at
|
||||
///
|
||||
/// http://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTFileInfo.h"
|
||||
|
||||
@interface SNTFileInfoTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTFileInfoTest
|
||||
|
||||
- (NSString *)directoryBundle {
|
||||
NSString *rp = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
return [rp stringByAppendingPathComponent:@"testdata/DirectoryBundle"];
|
||||
}
|
||||
|
||||
- (NSString *)bundleExample {
|
||||
NSString *rp = [[NSBundle bundleForClass:[self class]] resourcePath];
|
||||
return [rp stringByAppendingPathComponent:@"testdata/BundleExample.app"];
|
||||
}
|
||||
|
||||
- (void)testPathStandardizing {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/Applications/Safari.app"];
|
||||
XCTAssertNotNil(sut);
|
||||
XCTAssertEqualObjects(sut.path, @"/Applications/Safari.app/Contents/MacOS/Safari");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"../../../../../../../../../../../../../../../bin/ls"];
|
||||
XCTAssertEqualObjects(sut.path, @"/bin/ls");
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/DirectoryService"];
|
||||
XCTAssertEqualObjects(sut.path, @"/usr/libexec/dspluginhelperd");
|
||||
}
|
||||
|
||||
- (void)testSHA1 {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA1);
|
||||
XCTAssertEqual(sut.SHA1.length, 40);
|
||||
XCTAssertEqualObjects(sut.SHA1, @"3a865bf47b4ceba20496e0e66e39e4cfa101ffe6");
|
||||
}
|
||||
|
||||
- (void)testSHA256 {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil(sut.SHA256);
|
||||
XCTAssertEqual(sut.SHA256.length, 64);
|
||||
XCTAssertEqualObjects(sut.SHA256,
|
||||
@"5e089b65a1e7a4696d84a34510710b6993d1de21250c41daaec63d9981083eba");
|
||||
}
|
||||
|
||||
- (void)testExecutable {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/sbin/launchd"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isExecutable);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testPageZero {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"missing_pagezero"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
path = [[NSBundle bundleForClass:[self class]] pathForResource:@"bad_pagezero" ofType:@""];
|
||||
sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertTrue(sut.isMissingPageZero);
|
||||
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/sbin/bless"];
|
||||
XCTAssertFalse(sut.isMissingPageZero);
|
||||
}
|
||||
|
||||
- (void)testKext {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc]
|
||||
initWithPath:@"/System/Library/Extensions/AppleAPIC.kext/Contents/MacOS/AppleAPIC"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isKext);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testDylibs {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/lib/system/libsystem_platform.dylib"];
|
||||
|
||||
XCTAssertTrue(sut.isMachO);
|
||||
XCTAssertTrue(sut.isDylib);
|
||||
XCTAssertTrue(sut.isFat);
|
||||
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isScript);
|
||||
}
|
||||
|
||||
- (void)testScript {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/h2ph"];
|
||||
|
||||
XCTAssertTrue(sut.isScript);
|
||||
|
||||
XCTAssertFalse(sut.isDylib);
|
||||
XCTAssertFalse(sut.isExecutable);
|
||||
XCTAssertFalse(sut.isFat);
|
||||
XCTAssertFalse(sut.isKext);
|
||||
XCTAssertFalse(sut.isMachO);
|
||||
}
|
||||
|
||||
- (void)testBundle {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testAncestorBundle {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
|
||||
NSString *ancestorBundlePath = path;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent];
|
||||
}
|
||||
XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath);
|
||||
}
|
||||
|
||||
- (void)testBundleIsAncestor {
|
||||
NSString *path = [NSBundle bundleForClass:[self class]].bundlePath;
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testDirectoryBundleIsNotAncestor {
|
||||
NSString *path = [self directoryBundle];
|
||||
NSString *directoryBundle = @"/tmp/DirectoryBundle";
|
||||
NSFileManager *fm = [NSFileManager defaultManager];
|
||||
[fm removeItemAtPath:directoryBundle error:NULL];
|
||||
[fm copyItemAtPath:path toPath:directoryBundle error:NULL];
|
||||
path = [directoryBundle stringByAppendingString:@"/Contents/Resources/BundleExample.app"];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
}
|
||||
|
||||
- (void)testBundleCacheReset {
|
||||
NSString *path = [self bundleExample];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleName], @"BundleExample");
|
||||
XCTAssertEqualObjects([sut bundleVersion], @"1");
|
||||
XCTAssertEqualObjects([sut bundleShortVersionString], @"1.0");
|
||||
XCTAssertEqualObjects([sut bundlePath], path);
|
||||
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNotNil([sut bundle]);
|
||||
|
||||
XCTAssertEqualObjects([sut bundleIdentifier], @"com.google.santa.UnitTest.SNTFileInfoTest");
|
||||
XCTAssertNotNil([sut bundleVersion]);
|
||||
XCTAssertNotNil([sut bundleShortVersionString]);
|
||||
|
||||
NSString *ancestorBundlePath = path;
|
||||
for (int i = 0; i < 4; i++) {
|
||||
ancestorBundlePath = [ancestorBundlePath stringByDeletingLastPathComponent];
|
||||
}
|
||||
XCTAssertEqualObjects([sut bundlePath], ancestorBundlePath);
|
||||
}
|
||||
|
||||
- (void)testNonBundle {
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/yes"];
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
|
||||
sut.useAncestorBundle = YES;
|
||||
|
||||
XCTAssertNil([sut bundle]);
|
||||
}
|
||||
|
||||
- (void)testEmbeddedInfoPlist {
|
||||
NSString *path = [[NSBundle bundleForClass:[self class]] pathForResource:@"32bitplist"
|
||||
ofType:@""];
|
||||
SNTFileInfo *sut = [[SNTFileInfo alloc] initWithPath:path];
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
XCTAssertEqualObjects([sut infoPlist][@"CFBundleShortVersionString"], @"1.0");
|
||||
XCTAssertEqualObjects([sut infoPlist][@"CFBundleIdentifier"], @"com.google.i386plist");
|
||||
|
||||
// csreq is installed on all machines with Xcode installed. If you're running these tests,
|
||||
// it should be available..
|
||||
sut = [[SNTFileInfo alloc] initWithPath:@"/usr/bin/csreq"];
|
||||
XCTAssertNotNil([sut infoPlist]);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,34 +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.
|
||||
|
||||
///
|
||||
/// Simple file watching class using dispatch sources. Will automatically
|
||||
/// reload the watch if the file is deleted. Will continue watching for
|
||||
/// events until deallocated.
|
||||
///
|
||||
@interface SNTFileWatcher : NSObject
|
||||
|
||||
///
|
||||
/// Designated initializer
|
||||
/// Initializes the watcher and begins watching for modifications.
|
||||
///
|
||||
/// @param filePath the file to watch.
|
||||
/// @param handler the handler to call when changes happen.
|
||||
///
|
||||
/// @note Shortly after the file has been opened and monitoring has begun, the provided handler
|
||||
/// will be called.
|
||||
///
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler;
|
||||
|
||||
@end
|
||||
@@ -1,100 +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 "SNTFileWatcher.h"
|
||||
|
||||
@interface SNTFileWatcher ()
|
||||
@property NSString *filePath;
|
||||
@property dispatch_source_t monitoringSource;
|
||||
|
||||
@property(strong) void (^eventHandler)(void);
|
||||
@property(strong) void (^internalEventHandler)(void);
|
||||
@property(strong) void (^internalCancelHandler)(void);
|
||||
@end
|
||||
|
||||
@implementation SNTFileWatcher
|
||||
|
||||
- (instancetype)init {
|
||||
[self doesNotRecognizeSelector:_cmd];
|
||||
return nil;
|
||||
}
|
||||
|
||||
- (instancetype)initWithFilePath:(NSString *)filePath handler:(void (^)(void))handler {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_filePath = filePath;
|
||||
_eventHandler = handler;
|
||||
|
||||
if (!_filePath || !_eventHandler) return nil;
|
||||
|
||||
[self beginWatchingFile];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self stopWatchingFile];
|
||||
}
|
||||
|
||||
- (void)beginWatchingFile {
|
||||
__weak __typeof(self) weakSelf = self;
|
||||
int mask = (DISPATCH_VNODE_DELETE | DISPATCH_VNODE_WRITE |
|
||||
DISPATCH_VNODE_EXTEND | DISPATCH_VNODE_RENAME);
|
||||
dispatch_queue_t queue = dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_BACKGROUND, 0);
|
||||
|
||||
self.internalEventHandler = ^{
|
||||
unsigned long l = dispatch_source_get_data(weakSelf.monitoringSource);
|
||||
if (l & DISPATCH_VNODE_DELETE || l & DISPATCH_VNODE_RENAME) {
|
||||
if (weakSelf.monitoringSource) dispatch_source_cancel(weakSelf.monitoringSource);
|
||||
} else {
|
||||
weakSelf.eventHandler();
|
||||
}
|
||||
};
|
||||
|
||||
self.internalCancelHandler = ^{
|
||||
int fd;
|
||||
|
||||
if (weakSelf.monitoringSource) {
|
||||
fd = (int)dispatch_source_get_handle(weakSelf.monitoringSource);
|
||||
close(fd);
|
||||
}
|
||||
|
||||
while ((fd = open([weakSelf.filePath fileSystemRepresentation], O_EVTONLY)) < 0) {
|
||||
usleep(1000);
|
||||
}
|
||||
|
||||
weakSelf.monitoringSource =
|
||||
dispatch_source_create(DISPATCH_SOURCE_TYPE_VNODE, fd, mask, queue);
|
||||
dispatch_source_set_event_handler(weakSelf.monitoringSource, weakSelf.internalEventHandler);
|
||||
dispatch_source_set_cancel_handler(weakSelf.monitoringSource, weakSelf.internalCancelHandler);
|
||||
dispatch_resume(weakSelf.monitoringSource);
|
||||
|
||||
weakSelf.eventHandler();
|
||||
};
|
||||
|
||||
dispatch_async(queue, self.internalCancelHandler);
|
||||
}
|
||||
|
||||
- (void)stopWatchingFile {
|
||||
if (!self.monitoringSource) return;
|
||||
|
||||
int fd = (int)dispatch_source_get_handle(self.monitoringSource);
|
||||
dispatch_source_set_event_handler_f(self.monitoringSource, NULL);
|
||||
dispatch_source_set_cancel_handler(self.monitoringSource, ^{ close(fd); });
|
||||
|
||||
dispatch_source_cancel(self.monitoringSource);
|
||||
self.monitoringSource = nil;
|
||||
}
|
||||
|
||||
@end
|
||||
34
Source/common/SNTKVOManager.h
Normal file
34
Source/common/SNTKVOManager.h
Normal file
@@ -0,0 +1,34 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// https://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 <Foundation/Foundation.h>
|
||||
|
||||
// The callback type when KVO notifications are received for observed key paths.
|
||||
// The first parameter is the previous value, the second paramter is the new value.
|
||||
typedef void (^KVOCallback)(id oldValue, id newValue);
|
||||
|
||||
@interface SNTKVOManager : NSObject
|
||||
|
||||
// Add an observer for the selector on the given object. When a KVO notification
|
||||
// is received, the callback is called. If the notification contains objects that
|
||||
// are not of the expectedType, nil is passed as the argument to the callback.
|
||||
// The observer is removed when the returned instance is deallocated.
|
||||
- (instancetype)initWithObject:(id)object
|
||||
selector:(SEL)selector
|
||||
type:(Class)expectedType
|
||||
callback:(KVOCallback)callback;
|
||||
|
||||
- (instancetype)init NS_UNAVAILABLE;
|
||||
|
||||
@end
|
||||
72
Source/common/SNTKVOManager.mm
Normal file
72
Source/common/SNTKVOManager.mm
Normal file
@@ -0,0 +1,72 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// https://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 "Source/common/SNTKVOManager.h"
|
||||
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
@interface SNTKVOManager ()
|
||||
@property KVOCallback callback;
|
||||
@property Class expectedType;
|
||||
@property NSString *keyPath;
|
||||
@property id object;
|
||||
@end
|
||||
|
||||
@implementation SNTKVOManager
|
||||
|
||||
- (instancetype)initWithObject:(id)object
|
||||
selector:(SEL)selector
|
||||
type:(Class)expectedType
|
||||
callback:(KVOCallback)callback {
|
||||
self = [super self];
|
||||
if (self) {
|
||||
NSString *selectorName = NSStringFromSelector(selector);
|
||||
if (![object respondsToSelector:selector]) {
|
||||
LOGE(@"Attempt to add observer for an unknown selector (%@) for object (%@)", selectorName,
|
||||
[object class]);
|
||||
return nil;
|
||||
}
|
||||
|
||||
_object = object;
|
||||
_keyPath = selectorName;
|
||||
_expectedType = expectedType;
|
||||
_callback = callback;
|
||||
|
||||
[object addObserver:self
|
||||
forKeyPath:selectorName
|
||||
options:(NSKeyValueObservingOptionNew | NSKeyValueObservingOptionOld)
|
||||
context:NULL];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)dealloc {
|
||||
[self.object removeObserver:self forKeyPath:self.keyPath context:NULL];
|
||||
}
|
||||
|
||||
- (void)observeValueForKeyPath:(NSString *)keyPath
|
||||
ofObject:(id)object
|
||||
change:(NSDictionary<NSString *, id> *)change
|
||||
context:(void *)context {
|
||||
id oldValue = [change[NSKeyValueChangeOldKey] isKindOfClass:self.expectedType]
|
||||
? change[NSKeyValueChangeOldKey]
|
||||
: nil;
|
||||
id newValue = [change[NSKeyValueChangeNewKey] isKindOfClass:self.expectedType]
|
||||
? change[NSKeyValueChangeNewKey]
|
||||
: nil;
|
||||
|
||||
self.callback(oldValue, newValue);
|
||||
}
|
||||
|
||||
@end
|
||||
129
Source/common/SNTKVOManagerTest.mm
Normal file
129
Source/common/SNTKVOManagerTest.mm
Normal file
@@ -0,0 +1,129 @@
|
||||
/// Copyright 2022 Google LLC
|
||||
///
|
||||
/// 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
|
||||
///
|
||||
/// https://www.apache.org/licenses/LICENSE-2.0
|
||||
///
|
||||
/// Unless required by applicable law or agreed to in writing, software
|
||||
/// distributed under the License is distributed on an "AS IS" BASIS,
|
||||
/// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTKVOManager.h"
|
||||
|
||||
@interface Foo : NSObject
|
||||
@property NSNumber *propNumber;
|
||||
@property NSArray *propArray;
|
||||
@property id propId;
|
||||
@end
|
||||
|
||||
@implementation Foo
|
||||
@end
|
||||
|
||||
@interface SNTKVOManagerTest : XCTestCase
|
||||
@end
|
||||
|
||||
@implementation SNTKVOManagerTest
|
||||
|
||||
- (void)testInvalidSelector {
|
||||
Foo *foo = [[Foo alloc] init];
|
||||
|
||||
SNTKVOManager *kvo = [[SNTKVOManager alloc] initWithObject:foo
|
||||
selector:NSSelectorFromString(@"doesNotExist")
|
||||
type:[NSNumber class]
|
||||
callback:^(id, id){
|
||||
}];
|
||||
|
||||
XCTAssertNil(kvo);
|
||||
}
|
||||
|
||||
- (void)testNormalOperation {
|
||||
Foo *foo = [[Foo alloc] init];
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
int origVal = 123;
|
||||
int update1 = 456;
|
||||
int update2 = 789;
|
||||
|
||||
foo.propNumber = @(origVal);
|
||||
|
||||
// Store the values from the callback to test against expected values
|
||||
__block int oldVal;
|
||||
__block int newVal;
|
||||
|
||||
SNTKVOManager *kvo =
|
||||
[[SNTKVOManager alloc] initWithObject:foo
|
||||
selector:@selector(propNumber)
|
||||
type:[NSNumber class]
|
||||
callback:^(NSNumber *oldValue, NSNumber *newValue) {
|
||||
oldVal = [oldValue intValue];
|
||||
newVal = [newValue intValue];
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
XCTAssertNotNil(kvo);
|
||||
|
||||
// Ensure an update to the observed property triggers the callback
|
||||
foo.propNumber = @(update1);
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for first observable update");
|
||||
XCTAssertEqual(oldVal, origVal);
|
||||
XCTAssertEqual(newVal, update1);
|
||||
|
||||
// One more time why not
|
||||
foo.propNumber = @(update2);
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for second observable update");
|
||||
XCTAssertEqual(oldVal, update1);
|
||||
XCTAssertEqual(newVal, update2);
|
||||
}
|
||||
|
||||
- (void)testUnexpectedTypes {
|
||||
Foo *foo = [[Foo alloc] init];
|
||||
dispatch_semaphore_t sema = dispatch_semaphore_create(0);
|
||||
|
||||
NSString *origVal = @"any_val";
|
||||
NSString *update = @"new_val";
|
||||
foo.propId = origVal;
|
||||
|
||||
__block id oldVal;
|
||||
__block id newVal;
|
||||
|
||||
SNTKVOManager *kvo = [[SNTKVOManager alloc] initWithObject:foo
|
||||
selector:@selector(propId)
|
||||
type:[NSString class]
|
||||
callback:^(id oldValue, id newValue) {
|
||||
oldVal = oldValue;
|
||||
newVal = newValue;
|
||||
dispatch_semaphore_signal(sema);
|
||||
}];
|
||||
XCTAssertNotNil(kvo);
|
||||
|
||||
// Update to an unexpected type (here, NSNumber instead of NSString)
|
||||
foo.propId = @(123);
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for first observable update");
|
||||
XCTAssertEqualObjects(oldVal, origVal);
|
||||
XCTAssertNil(newVal);
|
||||
|
||||
// Update again with an expected type, ensure oldVal is now nil
|
||||
foo.propId = update;
|
||||
|
||||
XCTAssertEqual(0,
|
||||
dispatch_semaphore_wait(sema, dispatch_time(DISPATCH_TIME_NOW, 10 * NSEC_PER_SEC)),
|
||||
"Failed waiting for first observable update");
|
||||
XCTAssertNil(oldVal);
|
||||
XCTAssertEqualObjects(newVal, update);
|
||||
}
|
||||
|
||||
@end
|
||||
@@ -1,79 +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.
|
||||
|
||||
///
|
||||
/// 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_VNODE_ID_STR 21 // digits in UINT64_MAX + 1 for NULL-terminator
|
||||
|
||||
// Defines the name of the userclient class and the driver bundle ID.
|
||||
#define USERCLIENT_CLASS "com_google_SantaDriver"
|
||||
#define USERCLIENT_ID "com.google.santa-driver"
|
||||
|
||||
// List of methods supported by the driver.
|
||||
enum SantaDriverMethods {
|
||||
kSantaUserClientOpen,
|
||||
kSantaUserClientAllowBinary,
|
||||
kSantaUserClientDenyBinary,
|
||||
kSantaUserClientClearCache,
|
||||
kSantaUserClientCacheCount,
|
||||
|
||||
// Any methods supported by the driver should be added above this line to
|
||||
// ensure this remains the count of methods.
|
||||
kSantaUserClientNMethods,
|
||||
};
|
||||
|
||||
// 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,
|
||||
|
||||
// NOTIFY
|
||||
ACTION_NOTIFY_EXEC_ALLOW_NODAEMON = 30,
|
||||
ACTION_NOTIFY_EXEC_ALLOW_CACHED = 31,
|
||||
ACTION_NOTIFY_EXEC_DENY_CACHED = 32,
|
||||
|
||||
// SHUTDOWN
|
||||
ACTION_REQUEST_SHUTDOWN = 90,
|
||||
|
||||
// ERROR
|
||||
ACTION_ERROR = 99,
|
||||
} santa_action_t;
|
||||
|
||||
#define CHECKBW_RESPONSE_VALID(x) \
|
||||
(x == ACTION_RESPOND_CHECKBW_ALLOW || x == ACTION_RESPOND_CHECKBW_DENY)
|
||||
|
||||
// Message struct that is sent down the IODataQueue.
|
||||
typedef struct {
|
||||
santa_action_t action;
|
||||
uint64_t vnode_id;
|
||||
uid_t userId;
|
||||
pid_t pid;
|
||||
pid_t ppid;
|
||||
char path[MAXPATHLEN];
|
||||
} santa_message_t;
|
||||
|
||||
#endif // SANTA__COMMON__KERNELCOMMON_H
|
||||
@@ -13,46 +13,47 @@
|
||||
/// limitations under the License.
|
||||
|
||||
///
|
||||
/// Logging definitions, for both kernel and user space.
|
||||
/// Logging definitions
|
||||
///
|
||||
|
||||
#ifndef SANTA__COMMON__LOGGING_H
|
||||
#define SANTA__COMMON__LOGGING_H
|
||||
|
||||
#ifdef KERNEL
|
||||
#ifdef __cplusplus
|
||||
extern "C" {
|
||||
#endif
|
||||
|
||||
#ifdef DEBUG
|
||||
#define LOGD(...) IOLog("D santa-driver: " __VA_ARGS__); IOLog("\n");
|
||||
#else // DEBUG
|
||||
#define LOGD(...)
|
||||
#endif // DEBUG
|
||||
#define LOGI(...) IOLog("I santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGW(...) IOLog("W santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#define LOGE(...) IOLog("E santa-driver: " __VA_ARGS__); IOLog("\n")
|
||||
#import <Foundation/Foundation.h>
|
||||
|
||||
#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
|
||||
/// Get the logging level for this process.
|
||||
LogLevel EffectiveLogLevel();
|
||||
|
||||
#ifdef __cplusplus
|
||||
} // extern C
|
||||
#endif
|
||||
|
||||
#endif // SANTA__COMMON__LOGGING_H
|
||||
|
||||
@@ -12,52 +12,101 @@
|
||||
/// See the License for the specific language governing permissions and
|
||||
/// limitations under the License.
|
||||
|
||||
#import "SNTLogging.h"
|
||||
#import "Source/common/SNTLogging.h"
|
||||
|
||||
#import "Source/common/SNTConfigurator.h"
|
||||
|
||||
#import <asl.h>
|
||||
#import <pthread.h>
|
||||
|
||||
void syslogClientDestructor(void *arg) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
asl_close((aslclient)arg);
|
||||
#pragma clang diagnostic pop
|
||||
}
|
||||
|
||||
LogLevel EffectiveLogLevel() {
|
||||
#ifdef DEBUG
|
||||
static int logLevel = LOG_LEVEL_DEBUG;
|
||||
static LogLevel logLevel = LOG_LEVEL_DEBUG;
|
||||
#else
|
||||
static int logLevel = LOG_LEVEL_INFO; // default to info
|
||||
static LogLevel logLevel = LOG_LEVEL_INFO; // default to info
|
||||
#endif
|
||||
static dispatch_once_t onceToken;
|
||||
dispatch_once(&onceToken, ^{
|
||||
if ([SNTConfigurator configurator].enableDebugLogging) {
|
||||
logLevel = LOG_LEVEL_DEBUG;
|
||||
}
|
||||
});
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
void logMessage(int level, FILE *destination, NSString *format, ...) {
|
||||
static NSDateFormatter *dateFormatter;
|
||||
void logMessage(LogLevel level, FILE *destination, NSString *format, ...) {
|
||||
static BOOL useSyslog = NO;
|
||||
static NSString *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];
|
||||
|
||||
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 requested, redirect output to syslog.
|
||||
if ([[[NSProcessInfo processInfo] arguments] containsObject:@"--syslog"] ||
|
||||
[binaryName isEqualToString:@"com.google.santa.daemon"]) {
|
||||
useSyslog = YES;
|
||||
pthread_key_create(&syslogKey, syslogClientDestructor);
|
||||
}
|
||||
});
|
||||
|
||||
if (logLevel < level) return;
|
||||
if (EffectiveLogLevel() < level) return;
|
||||
|
||||
va_list args;
|
||||
va_start(args, format);
|
||||
NSString *s = [[NSString alloc] initWithFormat:format arguments:args];
|
||||
NSMutableString *s = [[NSMutableString 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) {
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
client = asl_open(NULL, "com.google.santa", 0);
|
||||
asl_set_filter(client, ASL_FILTER_MASK_UPTO(ASL_LEVEL_DEBUG));
|
||||
#pragma clang diagnostic pop
|
||||
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_NOTICE; // Maps to ULS Default
|
||||
break;
|
||||
case LOG_LEVEL_DEBUG:
|
||||
levelName = "D";
|
||||
// Log debug messages at the same ASL level as INFO.
|
||||
// While it would make sense to use DEBUG, watching debug-level logs
|
||||
// in Console means enabling all debug logs, which is absurdly noisy.
|
||||
syslogLevel = ASL_LEVEL_NOTICE;
|
||||
break;
|
||||
}
|
||||
|
||||
#pragma clang diagnostic push
|
||||
#pragma clang diagnostic ignored "-Wdeprecated-declarations"
|
||||
asl_log(client, NULL, syslogLevel, "%s %s: %s", levelName, binaryName.UTF8String, s.UTF8String);
|
||||
#pragma clang diagnostic pop
|
||||
} else {
|
||||
[s appendString:@"\n"];
|
||||
size_t len = [s lengthOfBytesUsingEncoding:NSUTF8StringEncoding];
|
||||
fwrite([s UTF8String], len, 1, destination);
|
||||
}
|
||||
}
|
||||
|
||||
202
Source/common/SNTMetricSet.h
Normal file
202
Source/common/SNTMetricSet.h
Normal file
@@ -0,0 +1,202 @@
|
||||
/// Copyright 2021 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 <Foundation/Foundation.h>
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
/**
|
||||
* Provides an abstraction for various metric systems that will be exported to
|
||||
* monitoring systems via the MetricService. This is used to store internal
|
||||
* counters and metrics that can be exported to an external monitoring system.
|
||||
*
|
||||
* `SNTMetricSet` for storing and creating metrics and counters. This is
|
||||
* the externally visible interface
|
||||
* class.
|
||||
*
|
||||
* Metric classes:
|
||||
* * `SNTMetric` to store metric values broken down by "field" dimensions.
|
||||
* * subclasses of `SNTMetric` with suitable setters:
|
||||
* * `SNTMetricCounter`
|
||||
* * `SNTMetricGaugeInt64`
|
||||
* * `SNTMetricGaugeDouble`
|
||||
* * `SNTMetricString`
|
||||
* * `SNTMetricBool`
|
||||
*/
|
||||
|
||||
NS_ASSUME_NONNULL_BEGIN
|
||||
|
||||
typedef NS_ENUM(NSInteger, SNTMetricType) {
|
||||
SNTMetricTypeUnknown = 0,
|
||||
SNTMetricTypeConstantBool = 1,
|
||||
SNTMetricTypeConstantString = 2,
|
||||
SNTMetricTypeConstantInt64 = 3,
|
||||
SNTMetricTypeConstantDouble = 4,
|
||||
SNTMetricTypeGaugeBool = 5,
|
||||
SNTMetricTypeGaugeString = 6,
|
||||
SNTMetricTypeGaugeInt64 = 7,
|
||||
SNTMetricTypeGaugeDouble = 8,
|
||||
SNTMetricTypeCounter = 9,
|
||||
};
|
||||
|
||||
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType);
|
||||
|
||||
@interface SNTMetric : NSObject
|
||||
- (NSDictionary *)export;
|
||||
@end
|
||||
|
||||
@interface SNTMetricCounter : SNTMetric
|
||||
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricInt64Gauge : SNTMetric
|
||||
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricDoubleGauge : SNTMetric
|
||||
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricStringGauge : SNTMetric
|
||||
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
@interface SNTMetricBooleanGauge : SNTMetric
|
||||
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues;
|
||||
@end
|
||||
|
||||
/**
|
||||
* A registry of metrics with associated fields.
|
||||
*/
|
||||
@interface SNTMetricSet : NSObject
|
||||
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username;
|
||||
|
||||
/* Returns a counter with the given name, field names and help
|
||||
* text, registered with the MetricSet.
|
||||
*
|
||||
* @param name The counter name, for example @"/proc/cpu".
|
||||
* @param fieldNames The counter's field names, for example @[@"result"].
|
||||
* @param helpText The counter's help description.
|
||||
* @return A counter with the given specification registered with this root.
|
||||
* The returned counter might have been created earlier with the same
|
||||
* specification.
|
||||
* @throw NSInternalInconsistencyException When trying to register a second
|
||||
* counter with the same name but a different schema as an existing one
|
||||
*/
|
||||
- (SNTMetricCounter *)counterWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text;
|
||||
|
||||
/**
|
||||
* Returns a shared global instance with default root labels and metrics registered.
|
||||
*/
|
||||
+ (instancetype)sharedInstance;
|
||||
|
||||
/**
|
||||
* Resets all the metrics in this set. Intended only for testing.
|
||||
*/
|
||||
- (void)reset;
|
||||
|
||||
/**
|
||||
* Add a root label to the MetricSet.
|
||||
*/
|
||||
- (void)addRootLabel:(NSString *)label value:(NSString *)value;
|
||||
|
||||
/**
|
||||
* Remove a root label from the MetricSet.
|
||||
*/
|
||||
- (void)removeRootLabel:(NSString *)labelName;
|
||||
|
||||
/**
|
||||
* Returns a int64 gauge metric with the given Streamz name and help text,
|
||||
* registered with this MetricSet.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a double gauge metric with the given name and help text,
|
||||
* registered with this root.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a string gauge metric with the given name and help text,
|
||||
* registered with this metric set.
|
||||
*
|
||||
* @param name The metric name, for example @"/santa/mode".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/**
|
||||
* Returns a boolean gauge metric with the given name and help text,
|
||||
* registered with this metric set.
|
||||
*
|
||||
* @param name The metric name, for example @"/memory/free".
|
||||
* @param fieldNames The metric's field names, for example @[@"type"].
|
||||
* @param helpText The metric's help description.
|
||||
*/
|
||||
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText;
|
||||
|
||||
/** Creates a constant metric with a string value and no fields. */
|
||||
- (void)addConstantStringWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(NSString *)value;
|
||||
|
||||
/** Creates a constant metric with an integer value and no fields. */
|
||||
- (void)addConstantIntegerWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(long long)value;
|
||||
|
||||
/** Creates a constant metric with an integer value and no fields. */
|
||||
- (void)addConstantBooleanWithName:(NSString *)name helpText:(NSString *)helpText value:(BOOL)value;
|
||||
|
||||
/** Register a callback to get executed just before each export. */
|
||||
- (void)registerCallback:(void (^)(void))callback;
|
||||
|
||||
/** Export creates an NSDictionary of the state of the metrics */
|
||||
- (NSDictionary *)export;
|
||||
@end
|
||||
|
||||
// Returns a human readble string from an SNTMetricFormat type
|
||||
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format);
|
||||
|
||||
/** Normalizes dates in an exported dictionary to be ISO8601 timestamp strings in
|
||||
* UTC time.
|
||||
*/
|
||||
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics);
|
||||
|
||||
NS_ASSUME_NONNULL_END
|
||||
673
Source/common/SNTMetricSet.m
Normal file
673
Source/common/SNTMetricSet.m
Normal file
@@ -0,0 +1,673 @@
|
||||
/// Copyright 2021 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 "SNTMetricSet.h"
|
||||
#import "SNTCommonEnums.h"
|
||||
|
||||
NSString *SNTMetricMakeStringFromMetricType(SNTMetricType metricType) {
|
||||
NSString *typeStr;
|
||||
switch (metricType) {
|
||||
case SNTMetricTypeConstantBool: typeStr = @"SNTMetricTypeConstantBool"; break;
|
||||
case SNTMetricTypeConstantString: typeStr = @"SNTMetricTypeConstantString"; break;
|
||||
case SNTMetricTypeConstantInt64: typeStr = @"SNTMetricTypeConstantInt64"; break;
|
||||
case SNTMetricTypeConstantDouble: typeStr = @"SNTMetricTypeConstantDouble"; break;
|
||||
case SNTMetricTypeGaugeBool: typeStr = @"SNTMetricTypeGaugeBool"; break;
|
||||
case SNTMetricTypeGaugeString: typeStr = @"SNTMetricTypeGaugeString"; break;
|
||||
case SNTMetricTypeGaugeInt64: typeStr = @"SNTMetricTypeGaugeInt64"; break;
|
||||
case SNTMetricTypeGaugeDouble: typeStr = @"SNTMetricTypeGaugeDouble"; break;
|
||||
case SNTMetricTypeCounter: typeStr = @"SNTMetricTypeCounter"; break;
|
||||
default: typeStr = [NSString stringWithFormat:@"SNTMetricTypeUnknown %ld", metricType]; break;
|
||||
}
|
||||
return typeStr;
|
||||
}
|
||||
|
||||
/**
|
||||
* SNTMetricValue encapsulates the value of a metric along with the creation
|
||||
* and update timestamps. It is thread-safe and has a separate field for each
|
||||
* metric type.
|
||||
*
|
||||
* It is intended to only be used by SNTMetrics;
|
||||
*/
|
||||
@interface SNTMetricValue : NSObject
|
||||
/** Increment the counter by the step value, updating timestamps appropriately. */
|
||||
- (void)addInt64:(long long)step;
|
||||
|
||||
/** Set the Int64 value. */
|
||||
- (void)setInt64:(long long)value;
|
||||
|
||||
/** Set the double value. */
|
||||
- (void)setDouble:(double)value;
|
||||
|
||||
/** Set the string value. */
|
||||
- (void)setString:(NSString *)value;
|
||||
|
||||
/** Set the BOOL string value. */
|
||||
- (void)setBool:(BOOL)value;
|
||||
|
||||
/**
|
||||
* Clears the last update timestamp.
|
||||
*
|
||||
* This makes the metric value always emit the current timestamp as last update timestamp.
|
||||
*/
|
||||
- (void)clearLastUpdateTimestamp;
|
||||
|
||||
/** Getters */
|
||||
- (long long)getInt64Value;
|
||||
- (double)getDoubleValue;
|
||||
- (NSString *)getStringValue;
|
||||
@end
|
||||
|
||||
@implementation SNTMetricValue {
|
||||
/** The int64 value for the SNTMetricValue, if set. */
|
||||
long long _int64Value;
|
||||
|
||||
/** The double value for the SNTMetricValue, if set. */
|
||||
double _doubleValue;
|
||||
|
||||
/** The string value for the SNTMetricValue, if set. */
|
||||
NSString *_stringValue;
|
||||
|
||||
/** The boolean value for the SNTMetricValue, if set. */
|
||||
BOOL _boolValue;
|
||||
|
||||
/** The first time this cell got created in the current process. */
|
||||
NSDate *_creationTime;
|
||||
|
||||
/** The last time that the counter value was changed. */
|
||||
NSDate *_lastUpdate;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_creationTime = [NSDate date];
|
||||
_lastUpdate = _creationTime;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)addInt64:(long long)step {
|
||||
@synchronized(self) {
|
||||
_int64Value += step;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setInt64:(long long)value {
|
||||
@synchronized(self) {
|
||||
_int64Value = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (long long)getInt64Value {
|
||||
@synchronized(self) {
|
||||
return _int64Value;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setDouble:(double)value {
|
||||
@synchronized(self) {
|
||||
_doubleValue = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (double)getDoubleValue {
|
||||
@synchronized(self) {
|
||||
return _doubleValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setString:(NSString *)value {
|
||||
@synchronized(self) {
|
||||
_stringValue = [value copy];
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (NSString *)getStringValue {
|
||||
@synchronized(self) {
|
||||
return [_stringValue copy];
|
||||
}
|
||||
}
|
||||
|
||||
- (void)setBool:(BOOL)value {
|
||||
@synchronized(self) {
|
||||
_boolValue = value;
|
||||
_lastUpdate = [NSDate date];
|
||||
}
|
||||
}
|
||||
|
||||
- (BOOL)getBoolValue {
|
||||
@synchronized(self) {
|
||||
return _boolValue;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)clearLastUpdateTimestamp {
|
||||
@synchronized(self) {
|
||||
_lastUpdate = nil;
|
||||
}
|
||||
}
|
||||
|
||||
- (NSDate *)getLastUpdatedTimestamp {
|
||||
NSDate *updated = nil;
|
||||
@synchronized(self) {
|
||||
updated = [_lastUpdate copy];
|
||||
}
|
||||
return updated;
|
||||
}
|
||||
|
||||
- (NSDate *)getCreatedTimestamp {
|
||||
NSDate *created = nil;
|
||||
@synchronized(self) {
|
||||
created = [_creationTime copy];
|
||||
}
|
||||
return created;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetric {
|
||||
@private
|
||||
/** Fully qualified metric name e.g. /ops/security/santa. */
|
||||
NSString *_name;
|
||||
/** A help text for the metric to be exported to be exported. **/
|
||||
NSString *_help;
|
||||
|
||||
/** Sorted list of the fieldNames **/
|
||||
NSArray<NSString *> *_fieldNames;
|
||||
/** Mapping of field values to actual metric values (e.g. metric /proc/cpu_usage @"mode"=@"user"
|
||||
* -> 0.89 */
|
||||
NSMutableDictionary<NSArray<NSString *> *, SNTMetricValue *> *_metricsForFieldValues;
|
||||
/** the type of metric this is e.g. counter, gauge etc. **/
|
||||
SNTMetricType _type;
|
||||
}
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)help
|
||||
type:(SNTMetricType)type {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_name = [name copy];
|
||||
_help = [help copy];
|
||||
_fieldNames = [fieldNames copy];
|
||||
_metricsForFieldValues = [[NSMutableDictionary alloc] init];
|
||||
_type = type;
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (NSString *)name {
|
||||
return _name;
|
||||
}
|
||||
|
||||
- (BOOL)hasSameSchemaAsMetric:(SNTMetric *)other {
|
||||
if (![other isKindOfClass:[self class]]) {
|
||||
return NO;
|
||||
}
|
||||
return [_name isEqualToString:other->_name] && [_help isEqualToString:other->_help] &&
|
||||
[_fieldNames isEqualTo:other->_fieldNames] && _type == other->_type;
|
||||
}
|
||||
|
||||
/** Retrieves the SNTMetricValue for a given field value.
|
||||
Creates a new SNTMetricValue if none is present. */
|
||||
- (SNTMetricValue *)metricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
NSParameterAssert(fieldValues.count == _fieldNames.count);
|
||||
SNTMetricValue *metricValue = nil;
|
||||
@synchronized(self) {
|
||||
metricValue = _metricsForFieldValues[fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
// Deep copy to prevent mutations to the keys we store in the dictionary.
|
||||
fieldValues = [fieldValues copy];
|
||||
metricValue = [[SNTMetricValue alloc] init];
|
||||
_metricsForFieldValues[fieldValues] = metricValue;
|
||||
}
|
||||
}
|
||||
|
||||
return metricValue;
|
||||
}
|
||||
|
||||
- (NSDictionary *)encodeMetricValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = _metricsForFieldValues[fieldValues];
|
||||
|
||||
NSMutableDictionary *fieldDict = [[NSMutableDictionary alloc] init];
|
||||
|
||||
fieldDict[@"created"] = [metricValue getCreatedTimestamp];
|
||||
fieldDict[@"last_updated"] = [metricValue getLastUpdatedTimestamp];
|
||||
fieldDict[@"value"] = [fieldValues componentsJoinedByString:@","];
|
||||
|
||||
switch (_type) {
|
||||
case SNTMetricTypeConstantBool:
|
||||
case SNTMetricTypeGaugeBool:
|
||||
fieldDict[@"data"] = [NSNumber numberWithBool:[metricValue getBoolValue]];
|
||||
break;
|
||||
case SNTMetricTypeConstantInt64:
|
||||
case SNTMetricTypeCounter:
|
||||
case SNTMetricTypeGaugeInt64:
|
||||
fieldDict[@"data"] = [NSNumber numberWithLongLong:[metricValue getInt64Value]];
|
||||
break;
|
||||
case SNTMetricTypeConstantDouble:
|
||||
case SNTMetricTypeGaugeDouble:
|
||||
fieldDict[@"data"] = [NSNumber numberWithDouble:[metricValue getDoubleValue]];
|
||||
break;
|
||||
case SNTMetricTypeConstantString:
|
||||
case SNTMetricTypeGaugeString: fieldDict[@"data"] = [metricValue getStringValue]; break;
|
||||
default: break;
|
||||
}
|
||||
return fieldDict;
|
||||
}
|
||||
|
||||
- (NSDictionary *)export {
|
||||
NSMutableDictionary *metricDict = [NSMutableDictionary dictionaryWithCapacity:_fieldNames.count];
|
||||
metricDict[@"type"] = [NSNumber numberWithInt:(int)_type];
|
||||
metricDict[@"fields"] = [[NSMutableDictionary alloc] init];
|
||||
metricDict[@"description"] = [_help copy];
|
||||
|
||||
if (_fieldNames.count == 0) {
|
||||
metricDict[@"fields"][@""] = @[ [self encodeMetricValueForFieldValues:@[]] ];
|
||||
} else {
|
||||
NSMutableArray *fieldVals = [[NSMutableArray alloc] init];
|
||||
|
||||
for (NSArray<NSString *> *fieldValues in _metricsForFieldValues) {
|
||||
[fieldVals addObject:[self encodeMetricValueForFieldValues:fieldValues]];
|
||||
}
|
||||
metricDict[@"fields"][[_fieldNames componentsJoinedByString:@","]] = fieldVals;
|
||||
}
|
||||
return metricDict;
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricCounter
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeCounter];
|
||||
}
|
||||
|
||||
- (void)incrementBy:(long long)step forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return;
|
||||
}
|
||||
[metricValue addInt64:step];
|
||||
}
|
||||
|
||||
- (void)incrementForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
[self incrementBy:1 forFieldValues:fieldValues];
|
||||
}
|
||||
|
||||
- (long long)getCountForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getInt64Value];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricInt64Gauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeGaugeInt64];
|
||||
}
|
||||
|
||||
- (void)set:(long long)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setInt64:value];
|
||||
}
|
||||
|
||||
- (long long)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getInt64Value];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricDoubleGauge
|
||||
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:text
|
||||
type:SNTMetricTypeGaugeDouble];
|
||||
}
|
||||
|
||||
- (void)set:(double)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setDouble:value];
|
||||
}
|
||||
|
||||
- (double)getGaugeValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return -1;
|
||||
}
|
||||
|
||||
return [metricValue getDoubleValue];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricStringGauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)text {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:text
|
||||
type:SNTMetricTypeGaugeString];
|
||||
}
|
||||
|
||||
- (void)set:(NSString *)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setString:value];
|
||||
}
|
||||
|
||||
- (NSString *)getStringValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return nil;
|
||||
}
|
||||
|
||||
return [metricValue getStringValue];
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricBooleanGauge
|
||||
- (instancetype)initWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
return [super initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeGaugeBool];
|
||||
}
|
||||
|
||||
- (void)set:(BOOL)value forFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
[metricValue setBool:value];
|
||||
}
|
||||
|
||||
- (BOOL)getBoolValueForFieldValues:(NSArray<NSString *> *)fieldValues {
|
||||
SNTMetricValue *metricValue = [self metricValueForFieldValues:fieldValues];
|
||||
|
||||
if (!metricValue) {
|
||||
return false;
|
||||
}
|
||||
|
||||
return [metricValue getBoolValue];
|
||||
}
|
||||
@end
|
||||
|
||||
/**
|
||||
* SNTMetricSet is the top level container for all metrics and metrics value
|
||||
* its is abstracted from specific implementations but is close to Google's
|
||||
* Monarch and Prometheus formats.
|
||||
*/
|
||||
@implementation SNTMetricSet {
|
||||
@private
|
||||
/** Labels that are used to identify the entity to that all metrics apply to. */
|
||||
NSMutableDictionary<NSString *, NSString *> *_rootLabels;
|
||||
/** Registered metrics keyed by name */
|
||||
NSMutableDictionary<NSString *, SNTMetric *> *_metrics;
|
||||
|
||||
/** Callbacks to update metric values before exporting metrics */
|
||||
NSMutableArray<void (^)(void)> *_callbacks;
|
||||
}
|
||||
|
||||
+ (instancetype)sharedInstance {
|
||||
static SNTMetricSet *sharedMetrics;
|
||||
static dispatch_once_t onceToken;
|
||||
|
||||
dispatch_once(&onceToken, ^{
|
||||
sharedMetrics = [[SNTMetricSet alloc] init];
|
||||
});
|
||||
|
||||
return sharedMetrics;
|
||||
}
|
||||
|
||||
- (instancetype)init {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_rootLabels = [[NSMutableDictionary alloc] init];
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
_callbacks = [[NSMutableArray alloc] init];
|
||||
}
|
||||
return self;
|
||||
}
|
||||
|
||||
- (instancetype)initWithHostname:(NSString *)hostname username:(NSString *)username {
|
||||
self = [super init];
|
||||
if (self) {
|
||||
_rootLabels = [[NSMutableDictionary alloc] init];
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
_callbacks = [[NSMutableArray alloc] init];
|
||||
|
||||
_rootLabels[@"hostname"] = [hostname copy];
|
||||
_rootLabels[@"username"] = [username copy];
|
||||
}
|
||||
|
||||
return self;
|
||||
}
|
||||
|
||||
- (void)reset {
|
||||
_metrics = [[NSMutableDictionary alloc] init];
|
||||
}
|
||||
|
||||
- (void)addRootLabel:(NSString *)label value:(NSString *)value {
|
||||
@synchronized(self) {
|
||||
_rootLabels[label] = value;
|
||||
}
|
||||
}
|
||||
|
||||
- (void)removeRootLabel:(NSString *)label {
|
||||
@synchronized(self) {
|
||||
[_rootLabels removeObjectForKey:label];
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTMetric *)registerMetric:(nonnull SNTMetric *)metric {
|
||||
@synchronized(self) {
|
||||
SNTMetric *oldMetric = _metrics[[metric name]];
|
||||
if ([oldMetric hasSameSchemaAsMetric:metric]) {
|
||||
return oldMetric;
|
||||
}
|
||||
NSAssert(!oldMetric, @"metric registered twice: %@", metric.name);
|
||||
_metrics[metric.name] = metric;
|
||||
}
|
||||
return metric;
|
||||
}
|
||||
|
||||
- (void)registerCallback:(void (^)(void))callback {
|
||||
@synchronized(self) {
|
||||
[_callbacks addObject:callback];
|
||||
}
|
||||
}
|
||||
|
||||
- (SNTMetricCounter *)counterWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricCounter *c = [[SNTMetricCounter alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
return (SNTMetricCounter *)[self registerMetric:c];
|
||||
}
|
||||
|
||||
- (SNTMetricInt64Gauge *)int64GaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricInt64Gauge *g = [[SNTMetricInt64Gauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
return (SNTMetricInt64Gauge *)[self registerMetric:g];
|
||||
}
|
||||
|
||||
- (SNTMetricDoubleGauge *)doubleGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricDoubleGauge *g = [[SNTMetricDoubleGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
return (SNTMetricDoubleGauge *)[self registerMetric:g];
|
||||
}
|
||||
|
||||
- (SNTMetricStringGauge *)stringGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricStringGauge *s = [[SNTMetricStringGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
return (SNTMetricStringGauge *)[self registerMetric:s];
|
||||
}
|
||||
|
||||
- (SNTMetricBooleanGauge *)booleanGaugeWithName:(NSString *)name
|
||||
fieldNames:(NSArray<NSString *> *)fieldNames
|
||||
helpText:(NSString *)helpText {
|
||||
SNTMetricBooleanGauge *b = [[SNTMetricBooleanGauge alloc] initWithName:name
|
||||
fieldNames:fieldNames
|
||||
helpText:helpText];
|
||||
|
||||
return (SNTMetricBooleanGauge *)[self registerMetric:b];
|
||||
}
|
||||
|
||||
- (void)addConstantStringWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(NSString *)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantString];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setString:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
- (void)addConstantIntegerWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(long long)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantInt64];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setInt64:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
- (void)addConstantBooleanWithName:(NSString *)name
|
||||
helpText:(NSString *)helpText
|
||||
value:(BOOL)value {
|
||||
SNTMetric *metric = [[SNTMetric alloc] initWithName:name
|
||||
fieldNames:@[]
|
||||
helpText:helpText
|
||||
type:SNTMetricTypeConstantBool];
|
||||
|
||||
SNTMetricValue *metricValue = [metric metricValueForFieldValues:@[]];
|
||||
[metricValue setBool:value];
|
||||
[self registerMetric:metric];
|
||||
}
|
||||
|
||||
/** Export current state of the SNTMetricSet as an NSDictionary. */
|
||||
- (NSDictionary *)export {
|
||||
NSDictionary *exported = nil;
|
||||
|
||||
// Invoke callbacks to ensure metrics are up to date.
|
||||
for (void (^cb)(void) in _callbacks) {
|
||||
cb();
|
||||
}
|
||||
|
||||
@synchronized(self) {
|
||||
NSMutableDictionary *exportDict = [[NSMutableDictionary alloc] init];
|
||||
exportDict[@"root_labels"] = [_rootLabels copy];
|
||||
exportDict[@"metrics"] = [[NSMutableDictionary alloc] init];
|
||||
|
||||
// TODO(markowsky) Sort the metrics so we always get the same output.
|
||||
for (NSString *metricName in _metrics) {
|
||||
exportDict[@"metrics"][metricName] = [_metrics[metricName] export];
|
||||
}
|
||||
|
||||
exported = [NSDictionary dictionaryWithDictionary:exportDict];
|
||||
}
|
||||
return exported;
|
||||
}
|
||||
|
||||
// Returns a human readble string from an SNTMetricFormat type
|
||||
NSString *SNTMetricStringFromMetricFormatType(SNTMetricFormatType format) {
|
||||
switch (format) {
|
||||
case SNTMetricFormatTypeRawJSON: return @"rawjson";
|
||||
case SNTMetricFormatTypeMonarchJSON: return @"monarchjson";
|
||||
default: return @"Unknown Metric Format";
|
||||
}
|
||||
}
|
||||
|
||||
NSDictionary *SNTMetricConvertDatesToISO8601Strings(NSDictionary *metrics) {
|
||||
NSMutableDictionary *mutableMetrics = [metrics mutableCopy];
|
||||
|
||||
id formatter;
|
||||
|
||||
if (@available(macOS 10.13, *)) {
|
||||
NSISO8601DateFormatter *isoFormatter = [[NSISO8601DateFormatter alloc] init];
|
||||
|
||||
isoFormatter.formatOptions =
|
||||
NSISO8601DateFormatWithInternetDateTime | NSISO8601DateFormatWithFractionalSeconds;
|
||||
formatter = isoFormatter;
|
||||
} else {
|
||||
NSDateFormatter *localFormatter = [[NSDateFormatter alloc] init];
|
||||
[localFormatter setDateFormat:@"yyyy-MM-dd'T'HH:mm:ss.SSS'Z'"];
|
||||
[localFormatter setTimeZone:[NSTimeZone timeZoneWithName:@"UTC"]];
|
||||
formatter = localFormatter;
|
||||
}
|
||||
|
||||
for (NSString *metricName in mutableMetrics[@"metrics"]) {
|
||||
NSMutableDictionary *metric = mutableMetrics[@"metrics"][metricName];
|
||||
|
||||
for (NSString *field in metric[@"fields"]) {
|
||||
NSMutableArray<NSMutableDictionary *> *values = metric[@"fields"][field];
|
||||
|
||||
[values enumerateObjectsUsingBlock:^(id object, NSUInteger index, BOOL *stop) {
|
||||
values[index][@"created"] = [formatter stringFromDate:values[index][@"created"]];
|
||||
values[index][@"last_updated"] = [formatter stringFromDate:values[index][@"last_updated"]];
|
||||
}];
|
||||
}
|
||||
}
|
||||
|
||||
return mutableMetrics;
|
||||
}
|
||||
|
||||
@end
|
||||
706
Source/common/SNTMetricSetTest.m
Normal file
706
Source/common/SNTMetricSetTest.m
Normal file
@@ -0,0 +1,706 @@
|
||||
#import <XCTest/XCTest.h>
|
||||
|
||||
#import "Source/common/SNTMetricSet.h"
|
||||
|
||||
@interface SNTMetricCounterTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricGaugeInt64Test : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricDoubleGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricBooleanGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricStringGaugeTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricSetTest : XCTestCase
|
||||
@end
|
||||
|
||||
@interface SNTMetricSetHelperFunctionsTest : XCTestCase
|
||||
@end
|
||||
|
||||
// Stub out NSDate's date method
|
||||
@implementation NSDate (custom)
|
||||
|
||||
+ (instancetype)date {
|
||||
NSDateFormatter *formatter = NSDateFormatter.new;
|
||||
[formatter setDateFormat:@"yyyy-MM-dd HH:mm:ssZZZ"];
|
||||
return [formatter dateFromString:@"2021-08-05 13:00:10+0000"];
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricCounterTest
|
||||
- (void)testSimpleCounter {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of exec events broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(c, @"Expected returned SNTMetricCounter to not be nil");
|
||||
[c incrementForFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(1, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremented by 1");
|
||||
[c incrementBy:3 forFieldValues:@[ @"certificate" ]];
|
||||
XCTAssertEqual(4, [c getCountForFieldValues:@[ @"certificate" ]],
|
||||
@"Counter not incremented by 3");
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of exec events broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(c);
|
||||
[c incrementForFieldValues:@[ @"certificate" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"description" : @"Count of exec events broken out by rule type.",
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[ @{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([c export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *a = [metricSet counterWithName:@"/santa/counter"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test counter."];
|
||||
|
||||
SNTMetricCounter *b = [metricSet counterWithName:@"/santa/counter"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test counter."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new counter returned.");
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricBooleanGaugeTest
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
XCTAssertNotNil(b);
|
||||
[b set:true forFieldValues:@[]];
|
||||
XCTAssertTrue([b getBoolValueForFieldValues:@[]]);
|
||||
[b set:false forFieldValues:@[]];
|
||||
XCTAssertFalse([b getBoolValueForFieldValues:@[]]);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
XCTAssertNotNil(b);
|
||||
[b set:true forFieldValues:@[]];
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeBool],
|
||||
@"description" : @"Is the daemon connected.",
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:true]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
NSDictionary *output = [b export];
|
||||
XCTAssertEqualObjects(output, expected);
|
||||
}
|
||||
|
||||
- (void)testAddingBooleanWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricBooleanGauge *a = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
|
||||
SNTMetricBooleanGauge *b = [metricSet booleanGaugeWithName:@"/santa/daemon_connected"
|
||||
fieldNames:@[]
|
||||
helpText:@"Is the daemon connected."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new boolean gauge returned.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricGaugeInt64Test
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *g =
|
||||
[metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of rules broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
|
||||
// set from zero
|
||||
[g set:250 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
|
||||
// Increase the gauge
|
||||
[g set:500 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(500, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// Decrease after increase
|
||||
[g set:100 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(100, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// Increase after decrease
|
||||
[g set:750 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(750, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
// TODO: export the tree to JSON and confirm the structure is correct.
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *g =
|
||||
[metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of rules broken out by rule type."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricGaugeInt64 to not be nil");
|
||||
// set from zero
|
||||
[g set:250 forFieldValues:@[ @"binary" ]];
|
||||
XCTAssertEqual(250, [g getGaugeValueForFieldValues:@[ @"binary" ]]);
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"description" : @"Count of rules broken out by rule type.",
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[ @{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:250]
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([g export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricInt64Gauge *a = [metricSet int64GaugeWithName:@"/santa/int64gauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
SNTMetricInt64Gauge *b = [metricSet int64GaugeWithName:@"/santa/int64gauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricDoubleGaugeTest
|
||||
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
|
||||
fieldNames:@[ @"mode" ]
|
||||
helpText:@"CPU time consumed by this process."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
|
||||
// set from zero
|
||||
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.45, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
|
||||
// Increase the gauge
|
||||
[g set:(double)0.90 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.90, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
// Decrease after increase
|
||||
[g set:0.71 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.71, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
// Increase after decrease
|
||||
[g set:0.75 forFieldValues:@[ @"user" ]];
|
||||
XCTAssertEqual(0.75, [g getGaugeValueForFieldValues:@[ @"user" ]]);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *g = [metricSet doubleGaugeWithName:@"/proc/cpu_usage"
|
||||
fieldNames:@[ @"mode" ]
|
||||
helpText:@"CPU time consumed by this process."];
|
||||
|
||||
XCTAssertNotNil(g, @"Expected returned SNTMetricDoubleGauge to not be nil");
|
||||
// set from zero
|
||||
[g set:(double)0.45 forFieldValues:@[ @"user" ]];
|
||||
[g set:(double)0.90 forFieldValues:@[ @"system" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeDouble],
|
||||
@"description" : @"CPU time consumed by this process.",
|
||||
@"fields" : @{
|
||||
@"mode" : @[
|
||||
@{
|
||||
@"value" : @"user",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithDouble:0.45]
|
||||
},
|
||||
@{
|
||||
@"value" : @"system",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithDouble:0.90]
|
||||
}
|
||||
]
|
||||
}
|
||||
};
|
||||
XCTAssertEqualObjects([g export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricDoubleGauge *a = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
SNTMetricDoubleGauge *b = [metricSet doubleGaugeWithName:@"/santa/doublegauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricStringGaugeTest
|
||||
- (void)testSimpleGauge {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
|
||||
fieldNames:@[]
|
||||
helpText:@"String description of the mode."];
|
||||
|
||||
XCTAssertNotNil(s);
|
||||
[s set:@"testValue" forFieldValues:@[]];
|
||||
XCTAssertEqualObjects([s getStringValueForFieldValues:@[]], @"testValue");
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *s = [metricSet stringGaugeWithName:@"/santa/mode"
|
||||
fieldNames:@[]
|
||||
helpText:@"String description of the mode."];
|
||||
|
||||
XCTAssertNotNil(s);
|
||||
[s set:@"testValue" forFieldValues:@[]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeString],
|
||||
@"description" : @"String description of the mode.",
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"testValue"
|
||||
} ]
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([s export], expected);
|
||||
}
|
||||
|
||||
- (void)testAddingMetricWithSameSchema {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricStringGauge *a = [metricSet stringGaugeWithName:@"/santa/stringgauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
SNTMetricStringGauge *b = [metricSet stringGaugeWithName:@"/santa/stringgauge"
|
||||
fieldNames:@[]
|
||||
helpText:@"Test gauge."];
|
||||
|
||||
XCTAssertEqual(a, b, @"Unexpected new gauge returned.");
|
||||
}
|
||||
|
||||
@end
|
||||
|
||||
@implementation SNTMetricSetTest
|
||||
- (void)testRootLabels {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addRootLabel:@"hostname" value:@"localhost"];
|
||||
|
||||
NSDictionary *expected = @{@"root_labels" : @{@"hostname" : @"localhost"}, @"metrics" : @{}};
|
||||
|
||||
XCTAssertEqualObjects(expected, [metricSet export]);
|
||||
|
||||
// ensure that adding a rootLabel with the same name overwrites.
|
||||
expected = @{@"root_labels" : @{@"hostname" : @"localhost2"}, @"metrics" : @{}};
|
||||
[metricSet addRootLabel:@"hostname" value:@"localhost2"];
|
||||
|
||||
XCTAssertEqualObjects(expected, [metricSet export],
|
||||
@"failed to overwrite rootLabel with second call to addRootLabel");
|
||||
|
||||
// ensure that removing a rootLabelWorks
|
||||
expected = @{@"root_labels" : @{}, @"metrics" : @{}};
|
||||
[metricSet removeRootLabel:@"hostname"];
|
||||
}
|
||||
|
||||
- (void)testDoubleRegisteringIncompatibleMetricsFails {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
SNTMetricCounter *c = [metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"field" ]
|
||||
helpText:@"lorem ipsum"];
|
||||
|
||||
XCTAssertNotNil(c);
|
||||
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"incompatible" ]
|
||||
helpText:@"A little help text"],
|
||||
@"Should raise error for incompatible field names");
|
||||
|
||||
XCTAssertThrows([metricSet counterWithName:@"/foo/bar"
|
||||
fieldNames:@[ @"result" ]
|
||||
helpText:@"INCOMPATIBLE"],
|
||||
@"Should raise error for incompatible help text");
|
||||
}
|
||||
|
||||
- (void)testRegisterCallback {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
// Register a callback metric which increments by one before export
|
||||
SNTMetricInt64Gauge *gauge = [metricSet int64GaugeWithName:@"/foo/bar"
|
||||
fieldNames:@[]
|
||||
helpText:@"Number of callbacks done"];
|
||||
__block int count = 0;
|
||||
[metricSet registerCallback:^(void) {
|
||||
count++;
|
||||
[gauge set:count forFieldValues:@[]];
|
||||
}];
|
||||
|
||||
// ensure the callback is called.
|
||||
[metricSet export];
|
||||
|
||||
XCTAssertEqual([gauge getGaugeValueForFieldValues:@[]], 1);
|
||||
}
|
||||
|
||||
- (void)testAddConstantBool {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addConstantBooleanWithName:@"/tautology"
|
||||
helpText:@"The first rule of tautology club is the first rule"
|
||||
value:YES];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/tautology" : @{
|
||||
@"description" : @"The first rule of tautology club is the first rule",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:true]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testAddConstantString {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
|
||||
[metricSet addConstantStringWithName:@"/build/label"
|
||||
helpText:@"Build label for the binary"
|
||||
value:@"20210806.0.1"];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/build/label" : @{
|
||||
@"description" : @"Build label for the binary",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"20210806.0.1"
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testAddConstantInt {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] init];
|
||||
[metricSet addConstantIntegerWithName:@"/deep/thought/answer"
|
||||
helpText:@"Life, the universe, and everything"
|
||||
value:42];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/deep/thought/answer" : @{
|
||||
@"description" : @"Life, the universe, and everything",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithLongLong:42]
|
||||
} ]
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export][@"metrics"], expected);
|
||||
}
|
||||
|
||||
- (void)testExportNSDictionary {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
|
||||
username:@"testUser"];
|
||||
|
||||
// Add constants
|
||||
[metricSet addConstantStringWithName:@"/build/label"
|
||||
helpText:@"Software version running."
|
||||
value:@"20210809.0.1"];
|
||||
[metricSet addConstantBooleanWithName:@"/santa/using_endpoint_security_framework"
|
||||
helpText:@"Is santad using the endpoint security framework."
|
||||
value:TRUE];
|
||||
[metricSet
|
||||
addConstantIntegerWithName:@"/proc/birth_timestamp"
|
||||
helpText:@"Start time of this santad instance, in microseconds since epoch"
|
||||
value:(long long)(0x12345668910)];
|
||||
// Add Metrics
|
||||
SNTMetricCounter *c = [metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Count of events on the host"];
|
||||
|
||||
[c incrementForFieldValues:@[ @"binary" ]];
|
||||
[c incrementBy:2 forFieldValues:@[ @"certificate" ]];
|
||||
|
||||
SNTMetricInt64Gauge *g = [metricSet int64GaugeWithName:@"/santa/rules"
|
||||
fieldNames:@[ @"rule_type" ]
|
||||
helpText:@"Number of rules."];
|
||||
|
||||
[g set:1 forFieldValues:@[ @"binary" ]];
|
||||
[g set:3 forFieldValues:@[ @"certificate" ]];
|
||||
|
||||
// Add Metrics with callback
|
||||
SNTMetricInt64Gauge *virtualMemoryGauge =
|
||||
[metricSet int64GaugeWithName:@"/proc/memory/virtual_size"
|
||||
fieldNames:@[]
|
||||
helpText:@"The virtual memory size of this process."];
|
||||
|
||||
SNTMetricInt64Gauge *residentMemoryGauge =
|
||||
[metricSet int64GaugeWithName:@"/proc/memory/resident_size"
|
||||
fieldNames:@[]
|
||||
helpText:@"The resident set size of this process."];
|
||||
|
||||
[metricSet registerCallback:^(void) {
|
||||
[virtualMemoryGauge set:987654321 forFieldValues:@[]];
|
||||
[residentMemoryGauge set:123456789 forFieldValues:@[]];
|
||||
}];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"root_labels" : @{@"hostname" : @"testHost", @"username" : @"testUser"},
|
||||
@"metrics" : @{
|
||||
@"/build/label" : @{
|
||||
@"description" : @"Software version running.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantString],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : @"20210809.0.1"
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/santa/events" : @{
|
||||
@"description" : @"Count of events on the host",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[
|
||||
@{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
@{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:2],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
@"/santa/rules" : @{
|
||||
@"description" : @"Number of rules.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"rule_type" : @[
|
||||
@{
|
||||
@"value" : @"binary",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
@{
|
||||
@"value" : @"certificate",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:3],
|
||||
}
|
||||
]
|
||||
},
|
||||
},
|
||||
@"/santa/using_endpoint_security_framework" : @{
|
||||
@"description" : @"Is santad using the endpoint security framework.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantBool],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithBool:YES]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/proc/birth_timestamp" : @{
|
||||
@"description" : @"Start time of this santad instance, in microseconds since epoch",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeConstantInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithLong:1250999830800]
|
||||
} ]
|
||||
},
|
||||
},
|
||||
@"/proc/memory/virtual_size" : @{
|
||||
@"description" : @"The virtual memory size of this process.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:987654321]
|
||||
} ]
|
||||
}
|
||||
},
|
||||
@"/proc/memory/resident_size" : @{
|
||||
@"description" : @"The resident set size of this process.",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeGaugeInt64],
|
||||
@"fields" : @{
|
||||
@"" : @[ @{
|
||||
@"value" : @"",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:123456789]
|
||||
} ]
|
||||
},
|
||||
},
|
||||
}
|
||||
};
|
||||
|
||||
XCTAssertEqualObjects([metricSet export], expected);
|
||||
}
|
||||
@end
|
||||
|
||||
@implementation SNTMetricSetHelperFunctionsTest
|
||||
- (void)testMakeMetricString {
|
||||
NSArray<NSDictionary *> *tests = @[
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeUnknown],
|
||||
@"expected" : @"SNTMetricTypeUnknown 0"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantBool],
|
||||
@"expected" : @"SNTMetricTypeConstantBool"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantString],
|
||||
@"expected" : @"SNTMetricTypeConstantString"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantInt64],
|
||||
@"expected" : @"SNTMetricTypeConstantInt64"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeConstantDouble],
|
||||
@"expected" : @"SNTMetricTypeConstantDouble"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeBool],
|
||||
@"expected" : @"SNTMetricTypeGaugeBool"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeString],
|
||||
@"expected" : @"SNTMetricTypeGaugeString"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeInt64],
|
||||
@"expected" : @"SNTMetricTypeGaugeInt64"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeGaugeDouble],
|
||||
@"expected" : @"SNTMetricTypeGaugeDouble"
|
||||
},
|
||||
@{
|
||||
@"input" : [NSNumber numberWithInt:SNTMetricTypeCounter],
|
||||
@"expected" : @"SNTMetricTypeCounter"
|
||||
}
|
||||
];
|
||||
|
||||
for (NSDictionary *test in tests) {
|
||||
NSString *output = SNTMetricMakeStringFromMetricType([test[@"input"] integerValue]);
|
||||
XCTAssertEqualObjects(test[@"expected"], output, @"expected %@ got %@", test[@"expected"],
|
||||
output);
|
||||
}
|
||||
}
|
||||
|
||||
- (void)testEnsureMetricsWithMultipleFieldNamesSerializeOnce {
|
||||
SNTMetricSet *metricSet = [[SNTMetricSet alloc] initWithHostname:@"testHost"
|
||||
username:@"testUser"];
|
||||
|
||||
SNTMetricCounter *c =
|
||||
[metricSet counterWithName:@"/santa/events"
|
||||
fieldNames:@[ @"client", @"event_type" ]
|
||||
helpText:@"Count of events on the host for a given ES client"];
|
||||
[c incrementBy:1 forFieldValues:@[ @"device_manager", @"auth_mount" ]];
|
||||
|
||||
NSDictionary *expected = @{
|
||||
@"/santa/events" : @{
|
||||
@"description" : @"Count of events on the host for a given ES client",
|
||||
@"type" : [NSNumber numberWithInt:(int)SNTMetricTypeCounter],
|
||||
@"fields" : @{
|
||||
@"client,event_type" : @[
|
||||
@{
|
||||
@"value" : @"device_manager,auth_mount",
|
||||
@"created" : [NSDate date],
|
||||
@"last_updated" : [NSDate date],
|
||||
@"data" : [NSNumber numberWithInt:1],
|
||||
},
|
||||
],
|
||||
},
|
||||
},
|
||||
};
|
||||
|
||||
NSDictionary *got = [metricSet export][@"metrics"];
|
||||
XCTAssertEqualObjects(expected, got, @"metrics do not match expected");
|
||||
}
|
||||
@end
|
||||
Some files were not shown because too many files have changed in this diff Show More
Reference in New Issue
Block a user