├── .gitattributes
├── .gitignore
├── LICENSE
├── README.md
├── img
├── .gitignore
├── jb-img.png
└── screengrab.jpg
├── local.lcars.macOSSecurityUpdates.plist
└── macsu.zsh
/.gitattributes:
--------------------------------------------------------------------------------
1 | # Auto detect text files and perform LF normalization
2 | * text=auto
3 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .CS_Store
3 | TODO
4 | OUT
--------------------------------------------------------------------------------
/LICENSE:
--------------------------------------------------------------------------------
1 | MIT License
2 |
3 | Copyright (c) 2018–20 Joss Brown (pseud.) -- German laws apply -- Place of jurisdiction: Berlin, Germany
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy
6 | of this software and associated documentation files (the "Software"), to deal
7 | in the Software without restriction, including without limitation the rights
8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9 | copies of the Software, and to permit persons to whom the Software is
10 | furnished to do so, subject to the following conditions:
11 |
12 | The above copyright notice and this permission notice shall be included in all
13 | copies or substantial portions of the Software.
14 |
15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21 | SOFTWARE.
22 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | 
2 | 
3 | [](https://github.com/JayBrown/macOS-Security-Updates/blob/master/LICENSE)
4 |
5 | # macOS Security Updates (macSU)
6 |
7 | **macOS Security Updates (macSU) is a LaunchAgent and shell script for macOS 10.15 (Catalina). It will run a scan every four hours and notify the user if any of the following macOS Security components has been been updated:**
8 | * **Gatekeeper**
9 | * **Gatekeeper E**
10 | * **Incompatible Kernel Extensions (KEXT Exclusions)**
11 | * **Malware Removal Tool (MRT)**
12 | * **TCC**
13 | * **XProtect**
14 |
15 | **Plus:**
16 | * **App Exceptions**
17 | * **Compatibility Notification Data**
18 | * **Core LSKD (kdrl)**
19 | * **Core Suggestions**
20 | * **Incompatible Apps**
21 |
22 | **Plus:**
23 | * **System**
24 | * **System build**
25 | * **EFI (Boot ROM)**
26 | * **iBridge**
27 | * **rootless.conf**
28 |
29 | **macSU now also checks against a remote database (hosted on GitHub) containing the current version numbers of the more important macOS security components. They are the first six in the list above. If any of them is outdated, the user will be notified. macSU will not notify the user when the system itself (which mostly includes EFI and iBridge) is out-of-date, to account for users who do not wish to update to a new system (immediately).**
30 |
31 | 
32 |
33 | ## Installation
34 | * clone repo
35 | * `chmod +x macsu.zsh && ln -s macsu.zsh /usr/local/bin/macsu.zsh`
36 | * `cp local.lcars.macOSSecurityUpdates.plist $HOME/Library/LaunchAgents/local.lcars.macOSSecurityUpdates.plist`
37 | * `launchctl load $HOME/Library/LaunchAgents/local.lcars.macOSSecurityUpdates.plist`
38 | * optional: install **[terminal-notifier](https://github.com/julienXX/terminal-notifier)**
39 |
40 | ### Testing
41 | **Execute `macsu.zsh` at least once**, e.g. by running the LaunchAgent with `launchctl start local.lcars.macOSSecurityUpdates`, or by calling the script directly: `./macsu.zsh`
42 |
43 | Then you can test the update notification functionality i.a. by entering the following command sequence:
44 |
45 | `plutil -replace CFBundleShortVersionString -integer 2098 "$HOME/.cache/macSU/XP-version.plist" && launchctl start local.lcars.macOSSecurityUpdates`
46 |
47 | ### Notes
48 | * The agent (and thereby the script) will run every 4 hours.
49 | * **macSU** is only compatible with macOS 10.15 (Catalina).
50 |
51 | ## Uninstall
52 | * `launchctl unload $HOME/Library/LaunchAgents/local.lcars.macOSSecurityUpdates.plist`
53 | * remove the cloned `macOS-Security-Updates` GitHub repository
54 | * `rm -f /usr/local/bin/macsu.zsh`
55 | * `rm -rf $HOME/.cache/macSU`
56 | * `rm -f $HOME/Library/Logs/local.lcars.macOSSecurityUpdates.log`
57 | * `rm -f /tmp/local.lcars.macOSSecurityUpdates.stdout`
58 | * `rm -f /tmp/local.lcars.macOSSecurityUpdates.stderr`
59 |
60 | ## Future
61 | * find a way to read the System Integrity Protection (SIP) version number on Catalina
62 |
63 | ## Thanks
64 | * **Howard Oakley** (@hoakleyelc) of **[EclecticLight](https://eclecticlight.co/)** for providing the [databases](https://github.com/hoakleyelc/updates) of current version numbers
65 |
--------------------------------------------------------------------------------
/img/.gitignore:
--------------------------------------------------------------------------------
1 | .DS_Store
2 | .CS_Store
3 | TODO
--------------------------------------------------------------------------------
/img/jb-img.png:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JayBrown/macOS-Security-Updates/9b98595bd1cf4ba9c83ea8e080c8a1e5b1b42ddb/img/jb-img.png
--------------------------------------------------------------------------------
/img/screengrab.jpg:
--------------------------------------------------------------------------------
https://raw.githubusercontent.com/JayBrown/macOS-Security-Updates/9b98595bd1cf4ba9c83ea8e080c8a1e5b1b42ddb/img/screengrab.jpg
--------------------------------------------------------------------------------
/local.lcars.macOSSecurityUpdates.plist:
--------------------------------------------------------------------------------
1 |
2 |
3 |
4 |
5 | Label
6 | local.lcars.macOSSecurityUpdates
7 | LowPriorityBackgroundIO
8 |
9 | LowPriorityIO
10 |
11 | Nice
12 | 20
13 | ProcessType
14 | Background
15 | Program
16 | /usr/local/bin/macsu.zsh
17 | StandardErrorPath
18 | /tmp/local.lcars.macOSSecurityUpdates.stderr
19 | StandardOutPath
20 | /tmp/local.lcars.macOSSecurityUpdates.stdout
21 | StartInterval
22 | 14400
23 |
24 |
25 |
--------------------------------------------------------------------------------
/macsu.zsh:
--------------------------------------------------------------------------------
1 | #!/bin/zsh
2 | # shellcheck shell=bash
3 |
4 | # macOS Security Updates (macSU)
5 | # shell script: macsu.zsh / LaunchAgent: local.lcars.macOSSecurityUpdates
6 | # v2.1.4
7 | # Copyright (c) 2018–20 Joss Brown (pseud.)
8 | # license: MIT+
9 | # info: https://github.com/JayBrown/macOS-Security-Updates
10 | # thanks to Howard Oakley: https://eclecticlight.co / https://github.com/hoakleyelc/updates
11 |
12 | export LANG=en_US.UTF-8
13 | export SYSTEM_VERSION_COMPAT=0
14 |
15 | macsuv="2.1.4"
16 | macsumv="2"
17 | scrname=$(basename "$0")
18 | process="macOS Security"
19 | account=$(id -u)
20 |
21 | _sysbeep () {
22 | osascript -e "beep" &>/dev/null
23 | }
24 |
25 | _beep () {
26 | afplay "/System/Library/Components/CoreAudio.component/Contents/SharedSupport/SystemSounds/system/Acknowledgement_ThumbsUp.caf" &>/dev/null
27 | }
28 |
29 | sysv=$(sw_vers -productVersion)
30 | sysmv=$(echo "$sysv" | awk -F. '{print $2}')
31 | if [[ $sysv != "11"* ]] ; then
32 | if [[ "$sysmv" -lt 15 ]] ; then
33 | _sysbeep &
34 | osascript &>/dev/null << EOT
35 | tell application "System Events"
36 | display notification "macOS 10.15 (Catalina) required!" with title "$process [" & "$account" & "]" subtitle "⚠️ Error: incompatible system!"
37 | end tell
38 | EOT
39 | echo -e "Error! Incompatible system.\n$scrname v$macsuv needs at least macOS 10.15 (Catalina).\n*** Exiting... ***" >&2
40 | exit 1
41 | fi
42 | fi
43 |
44 | icon_loc="/System/Library/PreferencePanes/Security.prefPane/Contents/Resources/FileVault.icns"
45 |
46 | _notify () {
47 | if [[ "$tn_status" == "osa" ]] ; then
48 | osascript &>/dev/null << EOT
49 | tell application "System Events"
50 | display notification "$2" with title "$process [" & "$account" & "]" subtitle "$1"
51 | end tell
52 | EOT
53 | elif [[ "$tn_status" == "tn-app-new" ]] || [[ "$tn_status" == "tn-app-old" ]] ; then
54 | "$tn_loc/Contents/MacOS/terminal-notifier" \
55 | -title "$process [$account]" \
56 | -subtitle "$1" \
57 | -message "$2" \
58 | -appIcon "$icon_loc" \
59 | >/dev/null
60 | elif [[ "$tn_status" == "tn-cli" ]] ; then
61 | "$tn" \
62 | -title "$process [$account]" \
63 | -subtitle "$1" \
64 | -message "$2" \
65 | -appIcon "$icon_loc" \
66 | >/dev/null
67 | fi
68 | }
69 |
70 | accountname=$(id -un)
71 | HOMEDIR=$(eval echo "~$accountname")
72 |
73 | # look for terminal-notifier (only on Yosemite and later)
74 | tn=$(command -v terminal-notifier 2>/dev/null)
75 | if ! [[ $tn ]] ; then
76 | tn_loc=$(mdfind \
77 | -onlyin /Applications/ \
78 | -onlyin "$HOMEDIR"/Applications/ \
79 | -onlyin /Developer/Applications/ \
80 | -onlyin "$HOMEDIR"/Developer/Applications/ \
81 | -onlyin /Network/Applications/ \
82 | -onlyin /Network/Developer/Applications/ \
83 | -onlyin /AppleInternal/Applications/ \
84 | -onlyin /usr/local/Cellar/terminal-notifier/ \
85 | -onlyin /opt/local/ \
86 | -onlyin /sw/ \
87 | -onlyin "$HOMEDIR"/.local/bin \
88 | -onlyin "$HOMEDIR"/bin \
89 | -onlyin "$HOMEDIR"/local/bin \
90 | "kMDItemCFBundleIdentifier == 'fr.julienxx.oss.terminal-notifier'" 2>/dev/null | LC_COLLATE=C sort | awk 'NR==1')
91 | if ! [[ $tn_loc ]] ; then
92 | tn_loc=$(mdfind \
93 | -onlyin /Applications/ \
94 | -onlyin "$HOMEDIR"/Applications/ \
95 | -onlyin /Developer/Applications/ \
96 | -onlyin "$HOMEDIR"/Developer/Applications/ \
97 | -onlyin /Network/Applications/ \
98 | -onlyin /Network/Developer/Applicationsv \
99 | -onlyin /AppleInternal/Applications/ \
100 | -onlyin /usr/local/Cellar/terminal-notifier/ \
101 | -onlyin /opt/local/ \
102 | -onlyin /sw/ \
103 | -onlyin "$HOMEDIR"/.local/bin \
104 | -onlyin "$HOMEDIR"/bin \
105 | -onlyin "$HOMEDIR"/local/bin \
106 | "kMDItemCFBundleIdentifier == 'nl.superalloy.oss.terminal-notifier'" 2>/dev/null | LC_COLLATE=C sort | awk 'NR==1')
107 | if ! [[ $tn_loc ]] ; then
108 | tn_status="osa"
109 | else
110 | tn_status="tn-app-old"
111 | fi
112 | else
113 | tn_status="tn-app-new"
114 | fi
115 | else
116 | tn_vers=$("$tn" -help | head -1 | awk -F'[()]' '{print $2}' | awk -F. '{print $1"."$2}')
117 | if (( $(echo "$tn_vers >= 1.8" | bc -l) )) && (( $(echo "$tn_vers < 2.0" | bc -l) )) ; then
118 | tn_status="tn-cli"
119 | else
120 | tn_loc=$(mdfind \
121 | -onlyin /Applications/ \
122 | -onlyin "$HOMEDIR"/Applications/ \
123 | -onlyin /Developer/Applications/ \
124 | -onlyin "$HOMEDIR"/Developer/Applications/ \
125 | -onlyin /Network/Applications/ \
126 | -onlyin /Network/Developer/Applications/ \
127 | -onlyin /AppleInternal/Applications/ \
128 | -onlyin /usr/local/Cellar/terminal-notifier/ \
129 | -onlyin /opt/local/ \
130 | -onlyin /sw/ \
131 | -onlyin "$HOMEDIR"/.local/bin \
132 | -onlyin "$HOMEDIR"/bin \
133 | -onlyin "$HOMEDIR"/local/bin \
134 | "kMDItemCFBundleIdentifier == 'fr.julienxx.oss.terminal-notifier'" 2>/dev/null | LC_COLLATE=C sort | awk 'NR==1')
135 | if ! [[ $tn_loc ]] ; then
136 | tn_loc=$(mdfind \
137 | -onlyin /Applications/ \
138 | -onlyin "$HOMEDIR"/Applications/ \
139 | -onlyin /Developer/Applications/ \
140 | -onlyin "$HOMEDIR"/Developer/Applications/ \
141 | -onlyin /Network/Applications/ \
142 | -onlyin /Network/Developer/Applications/ \
143 | -onlyin /AppleInternal/Applications/ \
144 | -onlyin /usr/local/Cellar/terminal-notifier/ \
145 | -onlyin /opt/local/ \
146 | -onlyin /sw/ \
147 | -onlyin "$HOMEDIR"/.local/bin \
148 | -onlyin "$HOMEDIR"/bin \
149 | -onlyin "$HOMEDIR"/local/bin \
150 | "kMDItemCFBundleIdentifier == 'nl.superalloy.oss.terminal-notifier'" 2>/dev/null | LC_COLLATE=C sort | awk 'NR==1')
151 | if ! [[ $tn_loc ]] ; then
152 | tn_status="osa"
153 | else
154 | tn_status="tn-app-old"
155 | fi
156 | else
157 | tn_status="tn-app-new"
158 | fi
159 | fi
160 | fi
161 |
162 | echo "***********************************************"
163 | echo "*** Starting macOS Security components scan ***"
164 | echo "***********************************************"
165 | echo "$process ($scrname v$macsuv)"
166 | echo "Executing user: $accountname ($account)"
167 | localdate=$(date)
168 | echo "Local date: $localdate"
169 |
170 | # check for cache directory
171 | cachedir="$HOMEDIR/.cache/macSU"
172 | if ! [[ -d "$cachedir" ]] ; then
173 | echo -e "macOS Security Updates initial run\nNo cache directory detected: creating..."
174 | if ! mkdir -p "$cachedir" &>/dev/null ; then
175 | _sysbeep &
176 | echo -e "Error creating cache directory: $cachedir\n*** Exiting... ***" >&2
177 | exit 1
178 | else
179 | echo -n "$macsumv" > "$cachedir/macsumv.txt"
180 | echo "Cache directory created"
181 | fi
182 | fi
183 | if ! [[ -f "$cachedir/macsumv.txt" ]] ; then
184 | if ! [[ -f "$cachedir/AppE-version.plist" ]] ; then
185 | find "$cachedir" -type f -exec rm -f {} \; 2>/dev/null
186 | fi
187 | echo -n "$macsumv" > "$cachedir/macsumv.txt"
188 | fi
189 |
190 | # list of components variables
191 | read -d '' macsulist <<"EOF"
192 | App Exceptions@/System/Library/CoreServices/CoreTypes.bundle/Contents/Library/AppExceptions.bundle/version.plist@AppE-version.plist@CFBundleShortVersionString@/System/Library/CoreServices/CoreTypes.bundle/Contents/Library/AppExceptions.bundle@/System/Library/CoreServices/CoreTypes.bundle/Contents/Library/AppExceptions.bundle/Exceptions.plist@none
193 | Compatibility Notification Data@/Library/Apple/Library/Bundles/CompatibilityNotificationData.bundle/Contents/version.plist@CND-version.plist@CFBundleShortVersionString@/Library/Apple/Library/Bundles/CompatibilityNotificationData.bundle@/Library/Apple/Library/Bundles/CompatibilityNotificationData.bundle/Contents/Resources/CompatibilityNotificationData.plist@none
194 | Core LSKD (kdrl)@/usr/share/kdrl.bundle/version.plist@kdrl-version.plist@CFBundleShortVersionString@/usr/share/kdrl.bundle@/usr/share/kdrl.bundle/lskd.rl@none
195 | Core Suggestions@/System/Library/PrivateFrameworks/CoreSuggestionsInternals.framework/Versions/A/Resources/Assets.suggestionsassets/version.plist@CS-version.plist@CFBundleShortVersionString@/System/Library/PrivateFrameworks/CoreSuggestionsInternals.framework@/System/Library/PrivateFrameworks/CoreSuggestionsInternals.framework/Versions/A/Resources/Assets.suggestionsassets/AssetData@none
196 | Gatekeeper@/private/var/db/gkopaque.bundle/Contents/version.plist@GK-version.plist@CFBundleShortVersionString@/private/var/db/gkopaque.bundle@/private/var/db/gkopaque.bundle/Contents/Resources/gkopaque.db@none
197 | Gatekeeper E@/private/var/db/gke.bundle/Contents/version.plist@GKE-version.plist@CFBundleShortVersionString@/private/var/db/gke.bundle@/private/var/db/gke.bundle/Contents/Resources/gk.db@none
198 | Incompatible Apps@/Library/Apple/Library/Bundles/IncompatibleAppsList.bundle/Contents/version.plist@IncApps-version.plist@CFBundleShortVersionString@/Library/Apple/Library/Bundles/IncompatibleAppsList.bundle@/Library/Apple/Library/Bundles/IncompatibleAppsList.bundle/Contents/Resources/IncompatibleAppsList.plist@BuildVersion
199 | KEXT Exclusions@/Library/Apple/System/Library/Extensions/AppleKextExcludeList.kext/Contents/version.plist@KE-version.plist@CFBundleShortVersionString@/Library/Apple/System/Library/Extensions/AppleKextExcludeList.kext@/Library/Apple/System/Library/Extensions/AppleKextExcludeList.kext/Contents/Resources/ExceptionLists.plist@none
200 | Malware Removal Tool@/Library/Apple/System/Library/CoreServices/MRT.app/Contents/version.plist@MRT-version.plist@CFBundleShortVersionString@/Library/Apple/System/Library/CoreServices/MRT.app@none@none
201 | TCC@/Library/Apple/Library/Bundles/TCC_Compatibility.bundle/Contents/version.plist@TCC-version.plist@CFBundleShortVersionString@/Library/Apple/Library/Bundles/TCC_Compatibility.bundle@/Library/Apple/Library/Bundles/TCC_Compatibility.bundle/Contents/Resources/AllowApplicationsList.plist@none
202 | XProtect@/Library/Apple/System/Library/CoreServices/XProtect.bundle/Contents/version.plist@XP-version.plist@CFBundleShortVersionString@/Library/Apple/System/Library/CoreServices/XProtect.bundle@none@none
203 | EOF
204 | # System Integrity Protection@/System/Library/Sandbox/Compatibility.bundle/Contents/version.plist@SIP-version.plist@CFBundleShortVersionString@/System/Library/Sandbox/Compatibility.bundle@none@none
205 |
206 | # check for plist backups
207 | while IFS='@' read -r cname cplpath cbname ckey cinfo cplpathalt ckeyalt
208 | do
209 | if ! [[ -f "$cachedir/$cbname" ]] ; then
210 | if [[ $cplpathalt != "none" ]] ; then
211 | ipldate=$(stat -f %Sc -t %F" "%T "$cplpathalt")
212 | else
213 | ipldate=$(stat -f %Sc -t %F" "%T "$cplpath")
214 | fi
215 | ixpversion=$(defaults read "$cplpath" "$ckey" 2>/dev/null)
216 | ! [[ $ixpversion ]] && ixpversion="n/a"
217 | if [[ $ckeyalt != "none" ]] ; then
218 | build=$(defaults read "$cplpath" "$ckeyalt" 2>/dev/null)
219 | ! [[ $build ]] && build="n/a"
220 | buildstr=" ($build)"
221 | else
222 | buildstr=""
223 | fi
224 | echo "Backing up $cname: $ixpversion$buildstr [$ipldate]"
225 | cp "$cplpath" "$cachedir/$cbname"
226 | else
227 | echo "$cname backup detected"
228 | fi
229 | done < <(echo "$macsulist" | grep -v "^$")
230 |
231 | # check for initial system data backups
232 | if ! [[ -f "$cachedir/sysv.txt" ]] ; then
233 | echo "Saving current system version: $sysv"
234 | echo -n "$sysv" > "$cachedir/sysv.txt"
235 | fi
236 | if ! [[ -f "$cachedir/sysbuildv.txt" ]] ; then
237 | sysbuildv=$(sw_vers -buildVersion)
238 | echo "Saving current system build version: $sysbuildv"
239 | echo -n "$sysbuildv" > "$cachedir/sysbuildv.txt"
240 | fi
241 | hwdata_raw=$(system_profiler SPHardwareDataType)
242 | hwdata=$(echo "$hwdata_raw" | grep "Boot ROM Version")
243 | if ! [[ -f "$cachedir/efiv.txt" ]] ; then
244 | efiv=$(echo "$hwdata" | awk '{print $4}')
245 | echo "Saving current EFI (Boot ROM) version: $efiv"
246 | echo -n "$efiv" > "$cachedir/efiv.txt"
247 | fi
248 | if ! [[ -f "$cachedir/ibridgev.txt" ]] ; then
249 | ibridgev=$(echo "$hwdata" | awk -F"[()]" '{print $2}' | awk -F"iBridge: " '{print $2}' | awk -F, '{print $1}')
250 | ! [[ $ibridgev ]] && ibridgev="n/a"
251 | echo "Saving current iBridge version: $ibridgev"
252 | echo -n "$ibridgev" > "$cachedir/ibridgev.txt"
253 | fi
254 | if ! [[ -f "$cachedir/rootless.conf" ]] ; then
255 | echo "Backing up rootless.conf"
256 | cp /System/Library/Sandbox/rootless.conf "$cachedir/rootless.conf"
257 | fi
258 |
259 | # curl databases on https://github.com/hoakleyelc/updates
260 | if ! [[ -d "$cachedir/tmp" ]] ; then
261 | mkdir -p "$cachedir/tmp" 2>/dev/null
262 | fi
263 | eclbaseurl="https://raw.githubusercontent.com/hoakleyelc/updates/master"
264 | securl="$eclbaseurl/sysupdates.plist"
265 | rcsec_tmp="$cachedir/tmp/sysupdates.plist"
266 | rm -f "$rcsec_tmp" 2>/dev/null
267 | echo "Trying to download sysupdates.plist..."
268 | curl -q -f -L -s --connect-timeout 30 --max-time 30 --retry 1 "$securl" -o "$rcsec_tmp" &>/dev/null
269 | rcsec="$cachedir/sysupdates.plist"
270 | if [[ -f "$rcsec_tmp" ]] ; then
271 | echo "Success!"
272 | rm -f "$rcsec" 2>/dev/null
273 | mv "$rcsec_tmp" "$rcsec" 2>/dev/null
274 | else
275 | echo "ERROR downloading sysupdates.plist!" >&2
276 | fi
277 | modelid=$(echo "$hwdata_raw" | grep "Model Identifier" | awk -F": " '{print $2}')
278 | modelname=$(echo "$modelid" | tr -d '[:digit:]' | sed 's/,$//')
279 | # modelnumber=$(echo "$modelid" | tr -d 'a-zA-Z')
280 | hwurl="$eclbaseurl/$modelname.plist"
281 | rchw_tmp="$cachedir/tmp/$modelname.plist"
282 | rm -f "$rchw_tmp" 2>/dev/null
283 | curl -q -f -L -s --connect-timeout 30 --max-time 30 --retry 1 "$hwurl" -o "$rchw_tmp" &>/dev/null
284 | echo "Trying to download $modelname.plist..."
285 | rchw="$cachedir/$modelname.plist"
286 | if [[ -f "$rchw_tmp" ]] ; then
287 | echo "Success!"
288 | rm -f "$rchw" 2>/dev/null
289 | mv "$rchw_tmp" "$rchw" 2>/dev/null
290 | else
291 | echo "ERROR downloading $modelname.plist!" >&2
292 | fi
293 |
294 | _version () {
295 | ver1="$1"
296 | ver2="$2"
297 | if ! [[ $ver1 ]] || ! [[ $ver2 ]] ; then
298 | echo "ERROR: incomplete input" >&2
299 | return
300 | fi
301 |
302 | ver1count=$(echo "$ver1" | grep -o "\." | wc -l)
303 | ver2count=$(echo "$ver2" | grep -o "\." | wc -l)
304 | if [[ $ver1count != "$ver2count" ]] ; then
305 | echo -e "ERROR: different formats\n$ver1 != $ver2" >&2
306 | return
307 | fi
308 | ((ver1count++))
309 | vcounter=1
310 |
311 | major1=$(echo "$ver1" | awk -F\. '{print $1}')
312 | major2=$(echo "$ver2" | awk -F\. '{print $1}')
313 | if [[ $major1 -gt $major2 ]] ; then
314 | echo "greater"
315 | return
316 | elif [[ $major1 -lt $major2 ]] ; then
317 | echo "lesser"
318 | return
319 | fi
320 | if [[ $vcounter == "$ver1count" ]] ; then
321 | echo "same"
322 | return
323 | fi
324 | ((vcounter++))
325 |
326 | minor1=$(echo "$ver1" | awk -F\. '{print $2}')
327 | minor2=$(echo "$ver2" | awk -F\. '{print $2}')
328 | if [[ $minor1 -gt $minor2 ]] ; then
329 | echo "greater"
330 | return
331 | elif [[ $minor1 -lt $minor2 ]] ; then
332 | echo "lesser"
333 | return
334 | fi
335 | if [[ $vcounter == "$ver1count" ]] ; then
336 | echo "same"
337 | return
338 | fi
339 | ((vcounter++))
340 |
341 | patch1=$(echo "$ver1" | awk -F\. '{print $3}')
342 | patch2=$(echo "$ver2" | awk -F\. '{print $3}')
343 | if [[ $patch1 -gt $patch2 ]] ; then
344 | echo "greater"
345 | return
346 | elif [[ $patch1 -lt $patch2 ]] ; then
347 | echo "lesser"
348 | return
349 | fi
350 | if [[ $vcounter == "$ver1count" ]] ; then
351 | echo "same"
352 | return
353 | fi
354 | ((vcounter++))
355 |
356 | majbuild1=$(echo "$ver1" | awk -F\. '{print $4}')
357 | majbuild2=$(echo "$ver2" | awk -F\. '{print $4}')
358 | if [[ $majbuild1 -gt $majbuild2 ]] ; then
359 | echo "greater"
360 | return
361 | elif [[ $majbuild1 -lt $majbuild2 ]] ; then
362 | echo "lesser"
363 | return
364 | fi
365 | if [[ $vcounter == "$ver1count" ]] ; then
366 | echo "same"
367 | return
368 | fi
369 | ((vcounter++))
370 |
371 | minbuild1=$(echo "$ver1" | awk -F\. '{print $5}')
372 | minbuild2=$(echo "$ver2" | awk -F\. '{print $5}')
373 | if [[ $minbuild1 -gt $minbuild2 ]] ; then
374 | echo "greater"
375 | return
376 | elif [[ $minbuild1 -lt $minbuild2 ]] ; then
377 | echo "lesser"
378 | return
379 | fi
380 | if [[ $vcounter == "$ver1count" ]] ; then
381 | echo "same"
382 | return
383 | fi
384 | ((vcounter++))
385 |
386 | pbuild1=$(echo "$ver1" | awk -F\. '{print $6}')
387 | pbuild2=$(echo "$ver2" | awk -F\. '{print $6}')
388 | if [[ $pbuild1 -gt $pbuild2 ]] ; then
389 | echo "greater"
390 | return
391 | elif [[ $pbuild1 -lt $pbuild2 ]] ; then
392 | echo "lesser"
393 | return
394 | fi
395 | if [[ $vcounter == "$ver1count" ]] ; then
396 | echo "same"
397 | return
398 | fi
399 |
400 | echo "Out of range" >&2
401 | }
402 |
403 | # check current EFI/iBridge versions
404 | counter=0
405 | while true
406 | do
407 | dictmodel=$(/usr/libexec/PlistBuddy -c "Print :$counter:MacModel" "$cachedir/$modelname.plist" 2>/dev/null)
408 | if [[ $dictmodel ]] ; then
409 | if [[ $dictmodel == "$modelid" ]] ; then
410 | break
411 | fi
412 | fi
413 | ((counter++))
414 | done
415 | fulldict=$(/usr/libexec/PlistBuddy -c "Print :$counter" "$cachedir/$modelname.plist" 2>/dev/null)
416 | if [[ $fulldict ]] ; then
417 | efiv_current=$(echo "$fulldict" | awk -F"EFIversion$sysmv = " '{print $2}' | grep -v "^$")
418 | ibridgev_current=$(echo "$fulldict" | awk -F"iBridge$sysmv = " '{print $2}' | grep -v "^$")
419 | else
420 | efiv_current="n/a"
421 | ibridgev_current="n/a"
422 | fi
423 |
424 | logbody=""
425 | updated=false
426 |
427 | # check auxiliary components
428 | sysv_previous=$(cat "$cachedir/sysv.txt")
429 | if [[ $sysv_previous == "$sysv" ]] ; then
430 | echo "System: unchanged ($sysv)"
431 | else
432 | _beep &
433 | updated=true
434 | echo "System: UPDATED from $sysv_previous to $sysv"
435 | logbody="$logbody\nSystem: $sysv_previous > $sysv"
436 | echo -n "$sysv" > "$cachedir/sysv.txt"
437 | _notify "System" "$sysv_previous > $sysv"
438 | fi
439 | sysbuildv=$(sw_vers -buildVersion)
440 | sysbuildv_previous=$(cat "$cachedir/sysbuildv.txt")
441 | if [[ $sysbuildv_previous == "$sysbuildv" ]] ; then
442 | echo "System build: unchanged ($sysbuildv)"
443 | else
444 | _beep &
445 | updated=true
446 | echo "System build: UPDATED from $sysbuildv_previous to $sysbuildv"
447 | logbody="$logbody\nSystem build: $sysbuildv_previous > $sysbuildv"
448 | echo -n "$sysbuildv" > "$cachedir/sysbuildv.txt"
449 | _notify "System build" "$sysbuildv_previous > $sysbuildv"
450 | fi
451 | efiv=$(echo "$hwdata" | awk '{print $4}')
452 | efiv_previous=$(cat "$cachedir/efiv.txt")
453 | if [[ $efiv_previous == "$efiv" ]] ; then
454 | echo "EFI (Boot ROM): unchanged ($efiv)"
455 | else
456 | _beep &
457 | updated=true
458 | echo "EFI (Boot ROM): UPDATED from $efiv_previous to $efiv"
459 | logbody="$logbody\nEFI (Boot ROM): $efiv_previous > $efiv"
460 | echo -n "$efiv" > "$cachedir/efiv.txt"
461 | _notify "EFI (Boot ROM)" "$efiv_previous > $efiv"
462 | fi
463 | if [[ $efiv != "n/a" ]] ; then
464 | eficomp=$(_version "$efiv_current" "$efiv" 2>&1)
465 | if [[ $eficomp == "greater" ]] ; then
466 | echo "EFI (Boot ROM): a NEWER version is available: $efiv < $efiv_current"
467 | logbody="$logbody\nEFI (Boot ROM): out-of-date [available: $efiv_current]"
468 | elif [[ $eficomp == "same" ]] ; then
469 | echo "EFI (Boot ROM): the current version is installed"
470 | elif [[ $eficomp == "lesser" ]] ; then
471 | echo "EFI (Boot ROM): a newer version is already installed"
472 | logbody="$logbody\nEFI (Boot ROM): newer version already installed"
473 | else
474 | echo -e "ERROR comparing EFI (Boot ROM) versions!\n$eficomp" >&2
475 | fi
476 | fi
477 | ibridgev=$(echo "$hwdata" | awk -F"[()]" '{print $2}' | awk -F"iBridge: " '{print $2}' | awk -F, '{print $1}')
478 | ! [[ $ibridgev ]] && ibridgev="n/a"
479 | ibridgev_previous=$(cat "$cachedir/ibridgev.txt")
480 | if [[ $ibridgev_previous == "$ibridgev" ]] ; then
481 | echo "iBridge: unchanged ($ibridgev)"
482 | else
483 | _beep &
484 | updated=true
485 | echo "iBridge: UPDATED from $ibridgev_previous to $ibridgev"
486 | logbody="$logbody\niBridge: $ibridgev_previous > $ibridgev"
487 | echo -n "$ibridgev" > "$cachedir/ibridgev.txt"
488 | _notify "iBridge" "$ibridgev_previous > $ibridgev"
489 | fi
490 | if [[ $ibridgev != "n/a" ]] ; then
491 | ibridgecomp=$(_version "$ibridgev_current" "$ibridgev" 2>&1)
492 | if [[ $ibridgecomp == "greater" ]] ; then
493 | echo "iBridge: a NEWER version is available: $ibridgev < $ibridgev_current"
494 | logbody="$logbody\niBridge: out-of-date [available: $ibridgev_current]"
495 | elif [[ $ibridgecomp == "same" ]] ; then
496 | echo "iBridge: the current version is installed"
497 | elif [[ $ibridgecomp == "lesser" ]] ; then
498 | echo "iBridge: a newer version is already installed"
499 | logbody="$logbody\niBridge: newer version already installed"
500 | else
501 | echo -e "ERROR comparing iBridge versions!\n$ibridgecomp" >&2
502 | fi
503 | fi
504 |
505 | pldate=$(stat -f %Sc -t %F" "%T /System/Library/Sandbox/rootless.conf)
506 | if [[ $(md5 -q /System/Library/Sandbox/rootless.conf) == $(md5 -q "$cachedir/rootless.conf") ]] ; then
507 | echo "SIP Configuration: unchanged [$pldate]"
508 | else
509 | _beep &
510 | updated=true
511 | echo "SIP Configuration: rootless.conf UPDATED on $pldate"
512 | logbody="$logbody\nSIP Configuration (rootless.conf): $pldate"
513 | rm -f "$cachedir/rootless.conf" 2>/dev/null
514 | cp /System/Library/Sandbox/rootless.conf "$cachedir/rootless.conf" 2>/dev/null
515 | _notify "SIP Configuration" "$pldate"
516 | fi
517 |
518 | sysup=$(/usr/libexec/PlistBuddy -c "Print" "$cachedir/sysupdates.plist")
519 |
520 | # check main components
521 | while IFS='@' read -r cname cplpath cbname ckey cinfo cplpathalt ckeyalt
522 | do
523 | if [[ $cplpathalt != "none" ]] ; then
524 | pldate=$(stat -f %Sc -t %F" "%T "$cplpathalt")
525 | else
526 | pldate=$(stat -f %Sc -t %F" "%T "$cplpath")
527 | fi
528 | nxpversion=$(defaults read "$cplpath" "$ckey" 2>/dev/null)
529 | ! [[ $nxpversion ]] && nxpversion="n/a"
530 | if [[ $ckeyalt != "none" ]] ; then
531 | nxpbuild=$(defaults read "$cplpath" "$ckeyalt" 2>/dev/null)
532 | ! [[ $nxpbuild ]] && nxpbuild="n/a"
533 | nxpbuildstr=" ($nxpbuild)"
534 | else
535 | nxpbuildstr=""
536 | fi
537 | if [[ $(md5 -q "$cplpath") == $(md5 -q "$cachedir/$cbname") ]] ; then
538 | echo "$cname: unchanged ($nxpversion$nxpbuildstr) [$pldate]"
539 | else
540 | oxpversion=$(defaults read "$cachedir/$cbname" "$ckey" 2>/dev/null)
541 | ! [[ $oxpversion ]] && oxpversion="n/a"
542 | if [[ $ckeyalt != "none" ]] ; then
543 | oxpbuild=$(defaults read "$cplpath" "$ckeyalt" 2>/dev/null)
544 | ! [[ $oxpbuild ]] && oxpbuild="n/a"
545 | oxpbuildstr=" ($oxpbuild)"
546 | else
547 | oxpbuildstr=""
548 | fi
549 | _beep &
550 | updated=true
551 | echo "$cname: UPDATED from $oxpversion$oxpbuildstr to $nxpversion$nxpbuildstr [$pldate]"
552 | logbody="$logbody\n$cname: $oxpversion$oxpbuildstr > $nxpversion$nxpbuildstr [$pldate] ($cinfo)"
553 | _notify "$cname" "$oxpversion$oxpbuildstr > $nxpversion$nxpbuildstr [$pldate] "
554 | rm -f "$cachedir/$cbname" 2>/dev/null
555 | cp "$cplpath" "$cachedir/$cbname" 2>/dev/null
556 | fi
557 | if [[ $nxpversion != "n/a" ]] ; then
558 | skipcomp=false
559 | tonotify=true
560 | if [[ $cname == "Gatekeeper" ]] ; then
561 | sec_current=$(echo "$sysup" | awk -F"Gatekeeper = " '{print $2}')
562 | elif [[ $cname == "Gatekeeper E" ]] ; then
563 | tonotify=false
564 | sec_current=$(echo "$sysup" | awk -F"GatekeepDE = " '{print $2}')
565 | elif [[ $cname == "KEXT Exclusions" ]] ; then
566 | tonotify=false
567 | sec_current=$(echo "$sysup" | awk -F"KEXT$sysmv = " '{print $2}')
568 | elif [[ $cname == "Malware Removal Tool" ]] ; then
569 | sec_current=$(echo "$sysup" | awk -F"MRT = " '{print $2}')
570 | elif [[ $cname == "TCC" ]] ; then
571 | tonotify=false
572 | sec_current=$(echo "$sysup" | awk -F"TCC$sysmv = " '{print $2}')
573 | elif [[ $cname == "XProtect" ]] ; then
574 | sec_current=$(echo "$sysup" | awk -F"XProtect$sysmv = " '{print $2}')
575 | else
576 | skipcomp=true
577 | fi
578 | if ! $skipcomp ; then
579 | seccomp=$(_version "$sec_current" "$nxpversion" 2>&1)
580 | if [[ $seccomp == "greater" ]] ; then
581 | _sysbeep &
582 | echo "$cname: a NEWER version is available: $nxpversion < $sec_current"
583 | logbody="$logbody\n$cname: out-of-date [available: $sec_current]"
584 | $tonotify && _notify "$cname" "Out-of-date: v$sec_current available!"
585 | elif [[ $seccomp == "same" ]] ; then
586 | echo "$cname: the current version is installed"
587 | elif [[ $seccomp == "lesser" ]] ; then
588 | echo "$cname: a newer version is already installed"
589 | logbody="$logbody\n$cname: newer version already installed"
590 | else
591 | echo -e "ERROR comparing $cname version numbers!\n$seccomp" >&2
592 | fi
593 | fi
594 | fi
595 | done < <(echo "$macsulist" | grep -v "^$")
596 |
597 | # log results
598 | if [[ -d "$HOMEDIR/Library/Logs/local.lcars.macOSSecurityUpdates" ]] ; then
599 | rm -rf "$HOMEDIR/Library/Logs/local.lcars.macOSSecurityUpdates" 2>/dev/null
600 | fi
601 | logloc="$HOMEDIR/Library/Logs/local.lcars.macOSSecurityUpdates.log"
602 | if $updated ; then
603 | logbody=$(echo -e "$logbody" | grep -v "^$")
604 | logger -i -s -t "macOS Security Updates" "$logbody" 2>> "$logloc"
605 | else
606 | logbody="No recent system updates"
607 | logger -i -s -t "macOS Security Updates" "$logbody" 2>> "$logloc"
608 | fi
609 |
610 | exit
611 |
--------------------------------------------------------------------------------