├── .github
└── workflows
│ └── shellcheck.yml
├── LICENSE.txt
├── README.md
└── MacClam.sh
/.github/workflows/shellcheck.yml:
--------------------------------------------------------------------------------
1 | name: 'ShellCheck'
2 | on: [push, pull_request]
3 |
4 | jobs:
5 | shellcheck:
6 | name: Shellcheck
7 | runs-on: ubuntu-latest
8 | steps:
9 | - name: Checkout
10 | uses: actions/checkout@v2
11 | - name: Run ShellCheck
12 | uses: ludeeus/action-shellcheck@master
13 |
--------------------------------------------------------------------------------
/LICENSE.txt:
--------------------------------------------------------------------------------
1 | The MIT License (MIT)
2 |
3 | Copyright (c) 2021 Russell Black
4 |
5 | Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:
6 |
7 | The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.
8 |
9 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
10 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | # MacClam
2 |
3 | The Non-Graphical ClamAV Antivirus Solution for Mac OS X
4 |
5 | I wrote this as a free alternative to the excellent ClamXav. MacClam
7 | sets up real-time directory monitoring and schedules periodic scans.
8 | It uses ClamAV as
9 | an AntiVirus engine and fswatch to actively monitor directories for new or
12 | changed files, which are then sent to clamd for scanning. Periodic
13 | full scans are scheduled with cron. It also provides a way to scan
14 | individual files or directories on demand from the command line.
15 |
16 | I have tested MacClam on Mojave (macOS 10.14). but it may also work
17 | in other versions of macOS.
18 |
19 | ## Prerequisites ##
20 |
21 | You will need to have Apple's Xcode
23 | command line tools which can be installed with
24 |
25 | xcode-select --install
26 |
27 | Then click "Install". After you have installed the command line tools,
28 | if you are on Mojave, you will also need to install the macOS headers
29 | package with
30 |
31 | sudo installer -pkg /Library/Developer/CommandLineTools/Packages/macOS_SDK_headers_for_macOS_10.14.pkg -target /
32 |
33 | ## Installation ##
34 |
35 | Installation is very simple. After installing prerequisite tools, type the following in a terminal.
36 |
37 | curl -O https://raw.githubusercontent.com/killdash9/MacClam/master/MacClam.sh
38 | chmod +x MacClam.sh
39 | ./MacClam.sh
40 |
41 | This will bootstrap MacClam by building the lastest versions of ClamAV
42 | and fswatch from source. It will schedule a full file system scan
43 | once a week and update signatures once a day. It also sets up live
44 | monitoring for the $HOME and /Applications directories. Each of these
45 | things can be configured by modifying script variables and re-running
46 | the script.
47 |
48 | By default, the installation directory is `~/MacClam`. This directory
49 | contains all the source, binaries, log files, and quarantine folder.
50 | The only artifact of the installation outside this directory is the
51 | crontab and the `MacClam.sh` script itself, which is required for
52 | MacClam to function. If you want to move the `MacClam.sh` script to
53 | another location, just re-run it from the new location and the crontab
54 | references will be updated. It can be totally uninstalled by
55 | running `./MacClam.sh uninstall`.
56 |
57 | ## Usage ##
58 |
59 | `./MacClam.sh` does the following:
60 |
61 | * Builds clamd and fswatch from source if needed
62 | * Sets up regular signature updates and full scans in crontab
63 | * Updates clamd signatures
64 | * Starts active monitoring services clamd and fswatch if not already running
65 | * Sets active monitoring to run on startup (also done in crontab)
66 | * If run from a terminal, it will show any current scanning activity
67 |
68 | The following command
69 |
70 | ./MacClam.sh /path/file_or_directory ...
71 |
72 | does everything previously listed, and then runs clamscan on the files
73 | or directories. Multiple files or directories can be specified.
74 |
75 | ./MacClam.sh quarantine
76 |
77 | Opens the quarantine folder in Finder. By default, this is
78 | `~/MacClam/quarantine`
79 |
80 | ./MacClam.sh help
81 |
82 | Displays a help message.
83 |
84 | ./MacClam.sh uninstall
85 |
86 | Uninstalls MacClam. More specifically, it stops clamd and fswatch.
87 | It removes MacClam entries from the crontab. It moves the quarantine
88 | directory from the MacClam installation directory to
89 | ~/MacClam_quarantine, just in case there's something in there you
90 | want. It deletes the MacClam installation directory which contains
91 | clamav and fswatch. It does not delete the MacClam.sh file, and you
92 | can reinstall MacClam by running it again.
93 |
94 | ## Customization ##
95 |
96 | Scheduled scans, monitoring and installation location can be
97 | configured by editing configuration variables at the beginning of the
98 | script, and then running the script again to apply your changes.
99 |
100 | ## Design Principle ##
101 |
102 | MacClam.sh is designed to have a very simple interface -- one command
103 | to do everything. It is idempotent, meaning that re-running
104 | MacClam.sh will do nothing if everything is set up correctly and
105 | services are running. If there are changes in the configuration
106 | variables, it will make sure they are applied, and restart services as
107 | necessary.
108 |
109 | ## Virus Scans ##
110 |
111 | MacClam performs three types of scans:
112 |
113 | 1. Active monitoring: MacClam will monitor any directories you specify
114 | for activity. When a file is changed or created, it will be
115 | scanned immediately. By default, the $HOME and Applications
116 | directories are monitored.
117 | 2. Scheduled scanning: MacClam will perform recursive scans of
118 | directories at scheduled times. By default, the entire hard drive
119 | is scanned once a week. Scheduling is done with cron.
120 | 3. On-demand scanning: Running `MacClam.sh` with one or more file or
121 | directory arguments will scan the files or directories specified.
122 |
123 | In all cases, when a virus is found, it is moved to the quarantine
124 | folder. For active monitoring, when a virus is identified, a brief
125 | graphical notification is shown in the top-right corner.
126 |
--------------------------------------------------------------------------------
/MacClam.sh:
--------------------------------------------------------------------------------
1 | #!/bin/bash
2 |
3 | pushd "$(dirname "$0")" > /dev/null
4 | SCRIPTPATH=$(pwd)/$(basename "$0")
5 | popd > /dev/null
6 |
7 | # This script is an all-in-one solution for ClamAV scanning on a Mac.
8 | # Run this script to install it, and run it again to check for updates
9 | # to clamav or virus definitions. The installation does the following:
10 | #
11 | # 1) Downloads the latest versions of clamav and fswatch and builds
12 | # them from source.
13 | #
14 | # 2) Sets the program to actively monitor the directories specified by
15 | # $MONITOR_DIRS, and move any viruses it finds to a folder specified by
16 | # $QUARANTINE_DIR.
17 | #
18 | # 3) Installs a crontab that invokes this same script once a day to
19 | # check for virus definition updates, and once a week to perform a
20 | # full system scan. These scheduled times can be customized by
21 | # editing the CRONTAB variable below. The crontab also schedules
22 | # MacClam to run on startup.
23 | #
24 | # If you pass one or more arguments to this file, all of the above
25 | # steps are performed. In addition, each of the arguments passed in
26 | # will be interpreted as a file or directory to be scanned.
27 | #
28 | # To uninstall MacClam.sh, run `MacClam.sh uninstall'.
29 | #
30 | # You can customize the following variables to suit your tastes. If
31 | # you change them, run this script again to apply your settings.
32 | #
33 |
34 | #The top level installation directory. It must not contain spaces or the builds won't work.
35 | INSTALLDIR="$HOME/MacClam"
36 |
37 | # Directories to monitor
38 | MONITOR_DIRS=(
39 | "$HOME"
40 | "/Applications"
41 | )
42 |
43 | # Directory patterns to exclude from scanning (this is a substring match)
44 | EXCLUDE_DIR_PATTERNS=(
45 | "/clamav-[^/]*/test/" #leave test files alone
46 | #"^$HOME/Library/"
47 | "^/mnt/"
48 | )
49 |
50 | # File patterns to exclude from scanning
51 | EXCLUDE_FILE_PATTERNS=(
52 | '\.txt$'
53 | )
54 |
55 | # Pipe-separated list of filename patterns to exclude
56 | QUARANTINE_DIR="$INSTALLDIR/quarantine"
57 |
58 | # Log file directory
59 | MACCLAM_LOG_DIR="$INSTALLDIR/log"
60 | CRON_LOG="$MACCLAM_LOG_DIR/cron.log"
61 | MONITOR_LOG="$MACCLAM_LOG_DIR/monitor.log"
62 | CLAMD_LOG="$MACCLAM_LOG_DIR/clamd.log"
63 |
64 | CRONTAB='
65 | #Start everything up at reboot
66 | @reboot '$SCRIPTPATH' >> '$CRON_LOG' 2>&1
67 |
68 | #Check for updates daily
69 | @daily '$SCRIPTPATH' >> '$CRON_LOG' 2>&1
70 |
71 | #Scheduled scan, every Sunday morning at 00:00.
72 | @weekly '$SCRIPTPATH' / >> '$CRON_LOG' 2>&1
73 | '
74 | # End of customizable variables
75 |
76 | set -e
77 |
78 | if [ "$1" == "help" ] || [ "$1" == "-help" ] || [ "$1" == "--help" ]
79 | then
80 | echo "Usage:
81 |
82 | MacClam.sh Show current scanning activity, installing clamav and fswatch if needed
83 | MacClam.sh quarantine Open the quarantine folder
84 | MacClam.sh uninstall Uninstall MacClam
85 | MacClam.sh help Display this message
86 |
87 | MacClam.sh [clamdscan args] [FILE|DIRECTORY]...
88 |
89 | The last form launches clamdscan on specific files or directories, installing if needed.
90 |
91 | Get more information from https://github.com/killdash9/MacClam
92 | "
93 | exit
94 | fi
95 |
96 | if [ "$1" == "uninstall" ]
97 | then
98 | read -r -p "Are you sure you want to uninstall MacClam? [y/N] " response
99 | if [[ "$response" =~ ^([yY][eE][sS]|[yY])$ ]]
100 | then
101 | echo "Uninstalling MacClam"
102 | echo "Stopping services"
103 | sudo killall clamd fswatch || true
104 | echo "Uninstalling from crontab"
105 | crontab <(cat <(crontab -l|sed '/# BEGIN MACCLAM/,/# END MACCLAM/d;/MacClam/d'));
106 | if [ -d "$QUARANTINE_DIR" ] && [ "$(ls "$QUARANTINE_DIR" 2>/dev/null)" ]
107 | then
108 | echo "Moving $QUARANTINE_DIR to $HOME/MacClam_quarantine in case there's something you want in there."
109 | if [ -d "$HOME/MacClam_quarantine" ]
110 | then
111 | mv "$QUARANTINE_DIR/"* "$HOME/MacClam_quarantine"
112 | else
113 | mv "$QUARANTINE_DIR" "$HOME/MacClam_quarantine"
114 | fi
115 | fi
116 | echo "Deleting installation directory $INSTALLDIR"
117 | sudo rm -rf "$INSTALLDIR"
118 | echo "Uninstall complete. Sorry to see you go!"
119 | else
120 | echo "Uninstall cancelled"
121 | fi
122 | exit
123 | fi
124 |
125 | if [ ! -t 0 ]
126 | then
127 | echo
128 | echo "--------------------------------------------------"
129 | echo " Starting MacClam.sh $(date)"
130 | echo "--------------------------------------------------"
131 | echo
132 | fi
133 |
134 | chmod +x "$SCRIPTPATH"
135 |
136 | test -d "$INSTALLDIR" || { echo "Creating installation directory $INSTALLDIR"; mkdir -p "$INSTALLDIR"; }
137 | test -d "$MACCLAM_LOG_DIR" || { echo "Creating log directory $MACCLAM_LOG_DIR"; mkdir -p "$MACCLAM_LOG_DIR"; }
138 | test -f "$CRON_LOG" || touch "$CRON_LOG"
139 | test -f "$CLAMD_LOG" || touch "$CLAMD_LOG"
140 | test -f "$MONITOR_LOG" || touch "$MONITOR_LOG"
141 | test -d "$QUARANTINE_DIR" || { echo "Creating quarantine directory $QUARANTINE_DIR"; mkdir -p "$QUARANTINE_DIR"; }
142 | test -f "$INSTALLDIR/clamav.ver" && CLAMAV_INS="$INSTALLDIR/clamav-installation-$(cat "$INSTALLDIR/clamav.ver")"
143 |
144 | test -f "$INSTALLDIR/fswatch.ver" && FSWATCH_INS="$INSTALLDIR/fswatch-installation-$(cat "$INSTALLDIR/fswatch.ver")"
145 |
146 | if [ "$1" == "quarantine" ]
147 | then
148 | echo "Opening $QUARANTINE_DIR"
149 | open "$QUARANTINE_DIR"
150 | exit
151 | fi
152 |
153 |
154 |
155 | if [ -t 0 ] #don't do this when we're run from cron
156 | then
157 |
158 | echo
159 | echo "-----------------------"
160 | echo " Checking Installation"
161 | echo "-----------------------"
162 | echo
163 | echo -n "What is the latest version of openssl?..."
164 |
165 | OPENSSL_DOWNLOAD_LINK=https://www.openssl.org/source/$(curl -s https://www.openssl.org/source/|grep -Eo 'openssl-1\.1\.1.{0,2}\.tar.gz'|head -1)
166 | OPENSSL_VER="${OPENSSL_DOWNLOAD_LINK#https://www.openssl.org/source/openssl-}"
167 | OPENSSL_VER="${OPENSSL_VER%.tar.gz}"
168 |
169 | if [[ ! "$OPENSSL_VER" =~ ^[0-9]+\.[0-9]+\.[0-9]+[a-z]?$ ]]
170 | then
171 | OPENSSL_VER='' #we didn't get a version number
172 | fi
173 |
174 | if [ ! "$OPENSSL_VER" ]
175 | then
176 | echo "Can't lookup latest openssl version. Looking for already-installed version."
177 | OPENSSL_VER=$(cat "$INSTALLDIR/openssl.ver")
178 | else
179 | echo "$OPENSSL_VER"
180 | echo "$OPENSSL_VER" > "$INSTALLDIR/openssl.ver"
181 | fi
182 |
183 | if [ ! "$OPENSSL_VER" ]
184 | then
185 | echo "No openssl installed and can't update. Can't proceed."
186 | exit 1
187 | fi
188 |
189 | OPENSSL_TAR="$INSTALLDIR/openssl-$OPENSSL_VER.tar.gz"
190 | OPENSSL_SRC="$INSTALLDIR/openssl-$OPENSSL_VER"
191 | OPENSSL_INS="$INSTALLDIR/openssl-installation-$OPENSSL_VER"
192 |
193 | echo -n "Has openssl-$OPENSSL_VER been downloaded?..."
194 | if [ -f "$OPENSSL_TAR" ] && tar -tf "$OPENSSL_TAR" > /dev/null
195 | then
196 | echo "Yes"
197 | else
198 | echo "No. Downloading $OPENSSL_DOWNLOAD_LINK to $OPENSSL_TAR"
199 | curl --connect-timeout 3 -L -o "$OPENSSL_TAR" "$OPENSSL_DOWNLOAD_LINK"
200 | fi
201 |
202 | echo -n "Has openssl-$OPENSSL_VER been extracted?..."
203 | if [ -d "$OPENSSL_SRC" ]
204 | then
205 | echo "Yes"
206 | else
207 | echo "No. Extracting it."
208 | cd "$INSTALLDIR"
209 | tar -xf "$OPENSSL_TAR"
210 | fi
211 |
212 | echo -n "Has openssl-$OPENSSL_VER been built?..."
213 | if [ -f "$OPENSSL_SRC/libcrypto.a" ]
214 | then
215 | echo "Yes"
216 | else
217 | echo "No. Building it."
218 | cd "$OPENSSL_SRC"
219 | ./Configure darwin64-x86_64-cc enable-ec_nistp_64_gcc_128 no-ssl3 no-comp --openssldir="$OPENSSL_INS" --prefix="$OPENSSL_INS" &&
220 | make -j8
221 | fi
222 |
223 | echo -n "Has openssl-$OPENSSL_VER been installed?..."
224 | if [ "$OPENSSL_INS/lib/libcrypto.a" -nt "$OPENSSL_SRC/libcrypto.a" ]
225 | then
226 | echo "Yes"
227 | else
228 | echo "No. Installing it."
229 | cd "$OPENSSL_SRC"
230 | make install_sw
231 | fi
232 |
233 | echo -n What is the latest version of pcre?...
234 | PCRE_VER=$(curl -s --connect-timeout 15 https://www.pcre.org/|grep 'is now at version '|grep -Eo '10\.[0-9]+')
235 | PCRE_DOWNLOAD_LINK="https://github.com/PhilipHazel/pcre2/releases/download/pcre2-$PCRE_VER/pcre2-$PCRE_VER.tar.gz"
236 |
237 | if [[ ! "$PCRE_VER" =~ ^[0-9]+\.[0-9]+$ ]]
238 | then
239 | PCRE_VER='' #we didn't get a version number
240 | fi
241 |
242 | if [ ! "$PCRE_VER" ]
243 | then
244 | echo "Can't lookup latest pcre version. Looking for already-installed version."
245 | PCRE_VER=$(cat "$INSTALLDIR/pcre.ver")
246 | else
247 | echo "$PCRE_VER"
248 | echo "$PCRE_VER" > "$INSTALLDIR/pcre.ver"
249 | fi
250 |
251 | if [ ! "$PCRE_VER" ]
252 | then
253 | echo "No pcre installed and can't update. Can't proceed."
254 | exit 1
255 | fi
256 |
257 | PCRE_TAR="$INSTALLDIR/pcre2-$PCRE_VER.tar.gz"
258 | PCRE_SRC="$INSTALLDIR/pcre2-$PCRE_VER"
259 | PCRE_INS="$INSTALLDIR/pcre2-installation-$PCRE_VER"
260 |
261 | echo -n "Has pcre-$PCRE_VER been downloaded?..."
262 | if [ -f "$PCRE_TAR" ] && tar -tf "$PCRE_TAR" > /dev/null
263 | then
264 | echo "Yes"
265 | else
266 | echo "No. Downloading $PCRE_DOWNLOAD_LINK to $PCRE_TAR"
267 | curl --connect-timeout 15 -L -o "$PCRE_TAR" "$PCRE_DOWNLOAD_LINK"
268 | fi
269 |
270 | echo -n "Has pcre-$PCRE_VER been extracted?..."
271 | if [ -d "$PCRE_SRC" ]
272 | then
273 | echo "Yes"
274 | else
275 | echo "No. Extracting it."
276 | cd "$INSTALLDIR"
277 | tar -xf "$PCRE_TAR"
278 | fi
279 |
280 | echo -n "Has pcre-$PCRE_VER been built?..."
281 | if [ -f "$PCRE_SRC/.libs/libpcre2-8.a" ]
282 | then
283 | echo "Yes"
284 | else
285 | echo "No. Building it."
286 | cd "$PCRE_SRC"
287 | ./configure --prefix="$PCRE_INS" &&
288 | make -j8
289 | fi
290 |
291 | echo -n "Has pcre-$PCRE_VER been installed?..."
292 | if [ "$PCRE_INS/lib/libpcre2-8.a" -nt "$PCRE_SRC/.libs/libpcre2-8.a" ]
293 | then
294 | echo "Yes"
295 | else
296 | echo "No. Installing it."
297 | cd "$PCRE_SRC"
298 | make install
299 | fi
300 |
301 | echo -n "What is the latest version of clamav?..."
302 |
303 |
304 | #ClamAV stores its version in dns
305 | CLAMAV_VER=$(dig TXT +noall +answer +time=3 +tries=1 current.cvd.clamav.net| sed 's,.*"\([^:]*\):.*,\1,')
306 | if [[ ! "$CLAMAV_VER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
307 | then
308 | CLAMAV_VER='' #we didn't get a version number
309 | fi
310 |
311 | if [ ! "$CLAMAV_VER" ]
312 | then
313 | echo "Can't lookup latest clamav version. Looking for already-installed version."
314 | CLAMAV_VER=$(cat "$INSTALLDIR/clamav.ver")
315 | else
316 | echo "$CLAMAV_VER"
317 | echo "$CLAMAV_VER" > "$INSTALLDIR/clamav.ver"
318 | fi
319 |
320 | if [ ! "$CLAMAV_VER" ]
321 | then
322 | echo "No clamav installed and can't update. Can't proceed."
323 | exit 1
324 | fi
325 |
326 | CLAMAV_TAR="$INSTALLDIR/clamav-$CLAMAV_VER.tar.gz"
327 | CLAMAV_SRC="$INSTALLDIR/clamav-$CLAMAV_VER"
328 | CLAMAV_INS="$INSTALLDIR/clamav-installation-$CLAMAV_VER"
329 |
330 | #CLAMAV_DOWNLOAD_LINK=http://sourceforge.net/projects/clamav/files/clamav/$CLAMAV_VER/clamav-$CLAMAV_VER.tar.gz/download
331 | CLAMAV_DOWNLOAD_LINK="https://www.clamav.net/downloads/production/clamav-$CLAMAV_VER.tar.gz"
332 | #CLAMAV_DOWNLOAD_LINK="http://nbtelecom.dl.sourceforge.net/project/clamav/clamav/$CLAMAV_VER/clamav-$CLAMAV_VER.tar.gz"
333 |
334 | echo -n "Has clamav-$CLAMAV_VER been downloaded?..."
335 | if [ -f "$CLAMAV_TAR" ] && tar -tf "$CLAMAV_TAR" > /dev/null
336 | then
337 | echo "Yes"
338 | else
339 | echo "No. Downloading $CLAMAV_DOWNLOAD_LINK to $CLAMAV_TAR"
340 | curl --connect-timeout 3 -L -o "$CLAMAV_TAR" "$CLAMAV_DOWNLOAD_LINK"
341 | fi
342 |
343 | echo -n "Has clamav-$CLAMAV_VER been extracted?..."
344 | if [ -d "$CLAMAV_SRC" ]
345 | then
346 | echo "Yes"
347 | else
348 | echo "No. Extracting it."
349 | cd "$INSTALLDIR"
350 | tar -xf "$CLAMAV_TAR"
351 | fi
352 |
353 | #CFLAGS="-O2 -g -D_FILE_OFFSET_BITS=64"
354 | #CXXFLAGS="-O2 -g -D_FILE_OFFSET_BITS=64"
355 |
356 | echo -n "Has the clamav-$CLAMAV_VER build been configured?..."
357 | if [ -f "$CLAMAV_SRC/Makefile" ]
358 | then
359 | echo "Yes"
360 | else
361 | echo "No. Configuring it."
362 | cd "$CLAMAV_SRC"
363 | ./configure --disable-dependency-tracking --enable-llvm=no --enable-clamdtop --with-user=_clamav --with-group=_clamav --enable-all-jit-targets --with-openssl="$OPENSSL_INS" --with-pcre="$PCRE_INS" --prefix="$CLAMAV_INS"
364 | fi
365 |
366 | echo -n "Has clamav-$CLAMAV_VER been built?..."
367 | if [ "$CLAMAV_SRC/Makefile" -nt "$CLAMAV_SRC/clamscan/clamscan" ]
368 | then
369 | echo "No. Building it."
370 | cd "$CLAMAV_SRC"
371 | make -j8
372 |
373 | else
374 | echo "Yes"
375 |
376 | fi
377 |
378 | echo -n "Has clamav-$CLAMAV_VER been installed?..."
379 | if [ "$CLAMAV_SRC/clamscan/clamscan" -nt "$CLAMAV_INS/bin/clamscan" ]
380 | then
381 | echo "No. Installing it."
382 | cd "$CLAMAV_SRC"
383 |
384 | make -j8 #run make again just in case
385 | echo "Password needed to run \"sudo make install\" for clamav"
386 | sudo make install
387 |
388 | if [ ! "$CLAMAV_INS" ]
389 | then
390 | echo "The variable CLAMAV_INS should be set here! Not proceeding, so we don't screw things up"
391 | fi
392 |
393 | cd "$CLAMAV_INS"
394 |
395 | sudo chown -R root:wheel ./etc
396 | sudo chmod 0775 ./etc
397 | sudo chmod 0664 ./etc/*
398 |
399 | sudo chown -R root:wheel ./bin
400 | sudo chmod -R 0755 ./bin
401 | sudo chown clamav ./bin/freshclam
402 | sudo chmod u+s ./bin/freshclam
403 | sudo mkdir -p ./share/clamav
404 | sudo chown -R clamav:clamav ./share/clamav
405 | sudo chmod 0775 ./share/clamav
406 | sudo chmod 0664 ./share/clamav/* || true
407 |
408 | sudo chown -R clamav:clamav ./share/clamav/daily* || true
409 | sudo chmod -R a+r ./share/clamav/daily* || true
410 |
411 | sudo chown -R clamav:clamav ./share/clamav/main* || true
412 | sudo chmod -R a+r ./share/clamav/main.* || true
413 | #sudo touch ./share/clamav/freshclam.log
414 | #sudo chmod a+rw ./share/clamav/freshclam.log
415 | sudo chmod u+s ./sbin/clamd
416 | else
417 | echo "Yes"
418 | fi
419 |
420 | CLAMD_CONF="$CLAMAV_INS/etc/clamd.conf"
421 | FRESHCLAM_CONF="$CLAMAV_INS/etc/freshclam.conf"
422 |
423 | function kill_clamd {
424 | sudo killall clamd
425 | echo Giving it time to stop...
426 | sleep 3;
427 | if pgrep clamd
428 | then
429 | echo "It's still running, using kill -9"
430 | sudo killall -9 clamd
431 | echo "Waiting one second"
432 | sleep 1;
433 | fi
434 | }
435 | echo -n "Is clamd.conf up to date?..."
436 | TMPFILE=$(mktemp -dt "MacClam")/clamd.conf
437 | sed "
438 | /^Example/d
439 | \$a\\
440 | LogFile $CLAMD_LOG\\
441 | LogTime yes\\
442 | MaxDirectoryRecursion 30\\
443 | LocalSocket /tmp/clamd.socket\\
444 | " "$CLAMD_CONF.sample" > "$TMPFILE"
445 |
446 | for p in "${EXCLUDE_DIR_PATTERNS[@]}"
447 | do
448 | echo ExcludePath "$p" >> "$TMPFILE"
449 | done
450 |
451 | if cmp -s "$TMPFILE" "$CLAMD_CONF"
452 | then
453 | echo Yes
454 | else
455 | echo "No. Updating $CLAMD_CONF"
456 | sudo cp "$TMPFILE" "$CLAMD_CONF"
457 | echo "Killing clamd if it's running"
458 | kill_clamd
459 | fi
460 | rm "$TMPFILE"
461 |
462 | echo -n "Is freshclam.conf up to date?..."
463 | TMPFILE=$(mktemp -dt "MacClam")/freshclam.conf
464 | sed "
465 | /^Example/d
466 | \$a\\
467 | NotifyClamd $CLAMD_CONF\\
468 | MaxAttempts 1\\
469 | " "$FRESHCLAM_CONF.sample" > "$TMPFILE"
470 | if cmp -s "$TMPFILE" "$FRESHCLAM_CONF"
471 | then
472 | echo Yes
473 | else
474 | echo "No. Updating $FRESHCLAM_CONF"
475 | sudo cp "$TMPFILE" "$FRESHCLAM_CONF"
476 | fi
477 | rm "$TMPFILE"
478 |
479 | echo -n "What is the latest version of fswatch?..."
480 | FSWATCH_DOWNLOAD_LINK=https://github.com$(curl -L -s 'https://github.com/emcrisostomo/fswatch/releases/latest'| grep "/emcrisostomo/fswatch/releases/download/.*tar.gz"|sed 's,.*href *= *"\([^"]*\).*,\1,')
481 | FSWATCH_VER="${FSWATCH_DOWNLOAD_LINK#https://github.com/emcrisostomo/fswatch/releases/download/}"
482 | FSWATCH_VER="${FSWATCH_VER%/fswatch*}"
483 |
484 | if [[ ! "$FSWATCH_VER" =~ ^[0-9]+\.[0-9]+\.[0-9]+$ ]]
485 | then
486 | FSWATCH_VER='' #we didn't get a version number
487 | fi
488 |
489 | if [ ! "$FSWATCH_VER" ]
490 | then
491 | echo "Can't lookup latest fswatch version. Looking for already-installed version."
492 | FSWATCH_VER=$(cat "$INSTALLDIR"/fswatch.ver)
493 | else
494 | echo "$FSWATCH_VER"
495 | echo "$FSWATCH_VER" > "$INSTALLDIR/fswatch.ver"
496 | fi
497 |
498 | if [ ! "$FSWATCH_VER" ]
499 | then
500 | echo "No fswatch installed and can't update. Can't proceed."
501 | exit 1
502 | fi
503 |
504 | FSWATCH_TAR="$INSTALLDIR/fswatch-$FSWATCH_VER.tar.gz"
505 | FSWATCH_SRC="$INSTALLDIR/fswatch-$FSWATCH_VER"
506 | FSWATCH_INS="$INSTALLDIR/fswatch-installation-$FSWATCH_VER"
507 |
508 | echo -n "Has the latest fswatch been downloaded?..."
509 | if [ -f "$FSWATCH_TAR" ] && tar -tf "$FSWATCH_TAR" > /dev/null
510 | then
511 | echo "Yes"
512 | else
513 | echo "No. Downloading $FSWATCH_DOWNLOAD_LINK"
514 | curl -L -o "$FSWATCH_TAR" "$FSWATCH_DOWNLOAD_LINK"
515 | fi
516 |
517 | echo -n "Has fswatch been extracted?..."
518 | if [ -d "$FSWATCH_SRC" ]
519 | then
520 | echo "Yes"
521 | else
522 | echo "No. Extracting it."
523 | cd "$INSTALLDIR"
524 | tar -xf "$FSWATCH_TAR"
525 | fi
526 |
527 | echo -n "Has fswatch been configured?..."
528 | if [ -f "$FSWATCH_SRC/Makefile" ]
529 | then
530 | echo "Yes"
531 | else
532 | echo "No. Configuring it."
533 | cd "$FSWATCH_SRC"
534 | ./configure --prefix="$FSWATCH_INS"
535 | fi
536 |
537 | echo -n "Has fswatch been installed?..."
538 | if [ -d "$FSWATCH_INS" ]
539 | then
540 | echo "Yes"
541 | else
542 | echo "No. Building and installing it."
543 | cd "$FSWATCH_SRC"
544 |
545 | make -j8
546 | echo "Password needed to run sudo make install"
547 | sudo make install
548 | sudo chown root:wheel "$FSWATCH_INS/bin/fswatch"
549 | sudo chmod u+s "$FSWATCH_INS/bin/fswatch"
550 |
551 | fi
552 |
553 | echo Creating scaniffile
554 | cat > "$INSTALLDIR/scaniffile" <> "$QUARANTINE_DIR/quarantine.log"
563 | osascript -e "display notification \"\$output\" with title \"MacClam\" subtitle \"\$1\"" &
564 | fi
565 | echo \$output
566 | fi
567 | EOF
568 | chmod +x "$INSTALLDIR/scaniffile"
569 |
570 | fi #end if [ -t 0 ]
571 |
572 | CLAMD_CONF="$CLAMAV_INS/etc/clamd.conf"
573 | FRESHCLAM_CONF="$CLAMAV_INS/etc/freshclam.conf"
574 |
575 | echo -n Is crontab up to date?...
576 | CURRENT_CRONTAB=$(crontab -l |awk '/# BEGIN MACCLAM/,/# END MACCLAM/')
577 | EXPECTED_CRONTAB="# BEGIN MACCLAM
578 | $CRONTAB
579 | # END MACCLAM"
580 | if [ "$CURRENT_CRONTAB" == "$EXPECTED_CRONTAB" ]
581 | then
582 | echo Yes
583 | else
584 | if [ -t 0 ]
585 | then
586 | echo No. Updating it.
587 | crontab <(cat <(crontab -l|sed '/# BEGIN MACCLAM/,/# END MACCLAM/d;/MacClam/d'); echo "$EXPECTED_CRONTAB")
588 | else
589 | echo No. Run "$0" from the command line to update it.
590 | fi
591 | fi
592 |
593 | echo
594 | echo "---------------------------------------"
595 | echo " Checking for ClamAV Signature Updates "
596 | echo "---------------------------------------"
597 | echo
598 |
599 | if [ -t 0 ]
600 | then
601 | "$CLAMAV_INS/bin/freshclam" --config-file="$FRESHCLAM_CONF" || true
602 | else
603 | "$CLAMAV_INS/bin/freshclam" --quiet --config-file="$FRESHCLAM_CONF" || true
604 | fi
605 |
606 | echo
607 | echo "-----------------------------"
608 | echo " Ensure Services are Running"
609 | echo "-----------------------------"
610 | echo
611 | echo -n Is clamd runnning?...
612 |
613 | CLAMD_CMD_ARGS=(
614 | "$CLAMAV_INS/sbin/clamd"
615 | "--config-file=$CLAMD_CONF"
616 | )
617 | CLAMD_CMD="$(printf " %q" "${CLAMD_CMD_ARGS[@]}")"
618 |
619 | #CLAMD_CMD='$CLAMAV_INS/sbin/clamd --config-file=$CLAMD_CONF'
620 | if PID=$(pgrep clamd)
621 | then
622 | echo Yes
623 | echo -n Is it the current version?...
624 | if [ "$(ps -o command= "$PID")" == "$(eval echo "$CLAMD_CMD")" ]
625 | then
626 | echo Yes
627 | else
628 | if [ -t 0 ]
629 | then
630 | echo No. Killing it.
631 | kill_clamd
632 | echo "Starting clamd"
633 | eval "$CLAMD_CMD"
634 | else
635 | echo No. Run "$0" from the command line to update it.
636 | fi
637 | fi
638 | else
639 | echo No. Starting it.
640 | eval "$CLAMD_CMD"
641 | fi
642 |
643 | echo -n Is fswatch running?...
644 | FSWATCH_CMD_ARGS=(
645 | "$FSWATCH_INS/bin/fswatch"
646 | -E
647 | -e "$QUARANTINE_DIR"
648 | "${EXCLUDE_DIR_PATTERNS[@]/#/-e}"
649 | "${EXCLUDE_FILE_PATTERNS[@]/#/-e}"
650 | -e "$MONITOR_LOG"
651 | -e "$CLAMD_LOG"
652 | -e "$CRON_LOG"
653 | "${MONITOR_DIRS[@]}"
654 | )
655 |
656 | #FSWATCH_CMD='"'$FSWATCH_INS'/bin/fswatch" -E -e "'$QUARANTINE_DIR'" "'${EXCLUDE_DIR_PATTERNS[@]/#/-e}'" "'${EXCLUDE_FILE_PATTERNS[@]/#/-e}'" -e "'$MONITOR_LOG'" -e "'$CLAMD_LOG'" -e "'$CRON_LOG'" "'${MONITOR_DIRS[@]}'"'
657 |
658 | FSWATCH_CMD="$(printf " %q" "${FSWATCH_CMD_ARGS[@]}")"
659 |
660 | function runfswatch {
661 | cat > "$INSTALLDIR/runfswatch" <> "$MONITOR_LOG" 2>&1
665 | EOF
666 |
667 | chmod +x "$INSTALLDIR/runfswatch"
668 | script -q /dev/null "$INSTALLDIR/runfswatch"
669 | }
670 |
671 | if PID=$(pgrep fswatch)
672 | then
673 | echo Yes
674 |
675 | echo -n Is it running the latest version and configuration?...
676 | if [ "$(ps -o command= "$PID")" == "$(eval echo "$FSWATCH_CMD")" ]
677 | then
678 | echo Yes
679 | else
680 | if [ -t 0 ]
681 | then
682 | echo No. Restarting.
683 | sudo killall fswatch
684 | runfswatch &
685 | else
686 | echo No. Run "$0" from the command line to update it.
687 | fi
688 | fi
689 | else
690 | echo No. Starting it.
691 | runfswatch &
692 | fi
693 |
694 | echo
695 | echo Monitoring "${MONITOR_DIRS[@]}"
696 | echo
697 | if [ "$1" ]
698 | then
699 | set -x
700 | "$CLAMAV_INS/bin/clamdscan" --move="$QUARANTINE_DIR" "$@"
701 | exit
702 | elif [ -t 0 ]
703 | then
704 | echo
705 | echo "------------------"
706 | echo " Current Activity "
707 | echo "------------------"
708 | echo
709 | echo "You can press Control-C to stop viewing activity. Scanning services will continue running."
710 | echo
711 | {
712 | tput colors > /dev/null &&
713 | green="$(tput setaf 2)" &&
714 | red="$(tput setaf 1)" &&
715 | yellow="$(tput setaf 3)" &&
716 | cyan="$(tput setaf 6)" &&
717 | normal="$(tput sgr0)"
718 | } || true
719 |
720 | (tail -0F "$CLAMD_LOG" "$CRON_LOG" "$MONITOR_LOG" | awk '
721 | BEGIN {
722 | tmax=max(30,'"$(tput cols)"')
723 | e="\033["
724 |
725 | # Find provides better support than ls for non-alphanumeric filenames
726 | # Excludes dot files and the quarantine log in virus count
727 | viruscnt='"$(find "$QUARANTINE_DIR" ! -name '.*' ! -name 'quarantine.log' -type f | grep -c /)"'
728 |
729 |
730 | r="'"$red"'"
731 | g="'"$green"'"
732 | y="'"$yellow"'"
733 | c="'"$cyan"'"
734 | n="'"$normal"'"
735 | }
736 |
737 | /^\/.* FOUND/ {
738 | sub(/ FOUND$/,r" FOUND"n)
739 | cnt++
740 | viruscnt++
741 | }
742 | /^\/.* (Empty file|OK)/ {
743 | cnt++
744 | l=length
745 | filename()
746 | countstr=sprintf("%d scanned ",cnt)
747 | virusstr=viruscnt? virusstr=sprintf("%d vir ",viruscnt):""
748 | printf e"K" y countstr r virusstr n
749 | tmax_ = tmax-length(countstr)-length(virusstr)
750 | dmax=max(tmax_-30,tmax_/2);
751 | if (l / {
771 | if (pf) {
772 | printf c e"A"e"K%."tmax"s\r"e"B" n,$0
773 | }
774 | else {
775 | printf c "%."tmax"s\n" n,f=$0
776 | pf=1
777 | }
778 | fflush;next
779 | }
780 | !/^ *$/ {
781 | sub(/ERROR/,r"ERROR"n)
782 | sub(/WARNING/,y"WARNING"n)
783 | print e"K"$0
784 | pf=0
785 | }
786 | function filename(){
787 | if (!pf) {
788 | printf "'"$cyan"'" "%." tmax"s\n" "'"$normal"'",f
789 | pf=1
790 | }
791 | }
792 | function min(a,b){return ab?a:b}
794 | ') || {
795 | echo
796 | echo
797 | echo "Stopped showing activity. Scan services continue to run."
798 | echo "Run the script again at any time to view activity."
799 | echo "Run 'MacClam.sh help' for more commands."
800 | }
801 | fi
802 |
803 | if [ ! -t 0 ]
804 | then
805 | echo
806 | echo "--------------------------------------------------"
807 | echo " Finished MacClam.sh $(date)"
808 | echo "--------------------------------------------------"
809 | echo
810 | fi
811 |
--------------------------------------------------------------------------------