├── .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 | --------------------------------------------------------------------------------