Automated virus scanning with ClamAV on Mac OS X 10.4
License: The MIT License, Copyright (c) 2008 jv
Description: basic setup to scan files added to specified directories automatically for viruses using ClamAV; for instructions on how to run ClamAV from a system service agent account (non-root) see here; use at your own risk
Platform: Mac OS X 10.4.11 Client
Requirements: sudo port install clamav (after installing MacPorts); to fix the command search path, insert the following statement at the end of your ~/.bash_login file: export PATH="/opt/local/bin:/opt/local/sbin:$PATH" and then: source ~/.bash_login
# download ClamAV sudo port install clamav # requires open firewall port 873 #sudo port uninstall clamav man clamd man clamd.conf man clamdscan man clamscan man freshclam # configure /opt/local/etc/freshclam.conf sudo cp -p /opt/local/etc/example-freshclam.conf /opt/local/etc/freshclam.conf sudo sed -i "" -e 's/^Example/#Example/' /opt/local/etc/freshclam.conf # comment out 'Example' line #sudo sed -i "" -e 's/^#Example/Example/' /opt/local/etc/freshclam.conf # uncomment 'Example' line sudo nano /opt/local/etc/freshclam.conf # make sure you are a member of the wheel and admin group id -G -n $(whoami) | grep -Eo 'wheel|admin' dseditgroup -o checkmember -m $(whoami) wheel; echo $? dseditgroup $(whoami) dseditgroup wheel dseditgroup admin sudo dscl . -append /Groups/wheel GroupMembership $(whoami) # add user to group if necessary #sudo dseditgroup -o edit -a $(whoami) -t user wheel # add user to group #sudo dscl . -delete /Groups/wheel GroupMembership $(whoami) # delete user from group #sudo dseditgroup -o edit -d $(whoami) -t user wheel # delete user from group sudo chown -R root:wheel /opt/local/share/clamav sudo chmod -R 0770 /opt/local/share/clamav freshclam # update virus database # test some clamav commands clamscan /path/to/file sudo clamscan -r /tmp sudo clamscan -r /private/var/tmp clamscan -r ~/Library/Caches clamscan -r ~/Library/Caches/java sudo clamscan -r ~/Library/Mail clamscan -r ~/Library # configure /opt/local/etc/clamd.conf # open /opt/local/etc # sudo nano /opt/local/etc/clamd.conf # cf. http://www.silvester.org.uk/OSX/configuring_clamd.html sudo cp -p /opt/local/etc/clamd.conf /opt/local/etc/clamd.conf.orig sudo sh -c ' cat << EOF > /opt/local/etc/clamd.conf LogFile /private/var/log/clamd.log LogFileMaxSize 10M LogTime yes TemporaryDirectory /private/var/tmp DatabaseDirectory /opt/local/share/clamav LocalSocket /tmp/clamd FixStaleSocket yes TCPAddr 127.0.0.1 MaxConnectionQueueLength 30 MaxThreads 20 ExitOnOOM yes ScanOLE2 yes # Microsoft Office documents and .msi files ScanPDF yes ArchiveMaxFileSize 100M ArchiveMaxCompressionRatio 0 #VirusEvent echo virus: %v >> /path/to/file.txt EOF ' sudo chown root:wheel /opt/local/etc/clamd.conf sudo chmod 750 /opt/local/etc/clamd.conf
Create the launchd item /Library/LaunchDaemons/net.clamav.clamd.plist
sudo nano /Library/LaunchDaemons/net.clamav.clamd.plist <?xml version="1.0" encoding="UTF-8"?> DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabledkey>/> <key>Labelkey> net.clamav.clamd< /string> <key>ProgramArgumentskey>/opt/local/sbin/clamdstring> -c< /string> <string>/opt/local/etc/clamd.confstring> </array> <key>RunAtLoadkey>/> <key>UserNamekey> root< /string> dict> </plist> sudo chown root:wheel /Library/LaunchDaemons/net.clamav.clamd.plist sudo chmod 0644 /Library/LaunchDaemons/net.clamav.clamd.plist sudo launchctl load -w /Library/LaunchDaemons/net.clamav.clamd.plist #sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.clamd.plist
Create watchdirs.sh
# create ClamAV directories mkdir -p "$HOME/Documents/ClamAV/Quarantine" mkdir -p "$HOME/Documents/ClamAV/Downloads" # Safari -> Preferences ... -> Save downloaded files to:mkdir -p "$HOME/Documents/ClamAV/EmailAttachments" sudo chown -R $(whoami):wheel "$HOME/Documents/ClamAV" sudo chmod -R 770 "$HOME/Documents/ClamAV" # create a timestamp file touch ~/.clamav_ timestamp sudo chown $(whoami):wheel "$HOME/.clamav_timestamp" sudo chmod 400 "$HOME/.clamav_timestamp" stat -x $HOME/.clamav_timestamp #--------------------------------- # Create a BASH script that will - controlled by ~/Library/LaunchAgents/net.clamav.dirwatcher.plist below - run # a clamdscan or clamscan command on files that have been changed or modified in the specified directories # nano $HOME/Documents/ClamAV/watchdirs.sh #!/bin/bash exec >/dev/console 2>&1 # write stdout & stderr to console.log in /Library/Logs/Console/ echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): ... WATCHDIRS.SH for ClamAV ... STARTED ...\n" # All files added to the watched directories during the specified sleep period (in seconds) will be scanned for viruses. # Files added to the directories while a virus scan is being done may not be included in the current virus scan, but # they will get scanned next time a virus scan is scheduled to run which can, for example, be determined by the launch agent # variable StartInterval. Increase the value of the specified sleep period (in seconds) if you expect large files or # directories to be copied or downloaded to the watched directories. /bin/sleep 60 SCANDIR1="$HOME/Documents/ClamAV/Downloads" SCANDIR2="$HOME/Documents/ClamAV/EmailAttachments" QUARANTINEDIR="$HOME/Documents/ClamAV/Quarantine" TOUCHFILE="$HOME/.clamav_timestamp" find=/usr/bin/find clamdscan=/opt/local/bin/clamdscan clamscan=/opt/local/bin/clamscan if [[ ! -e "$TOUCHFILE" ]]; then /usr/bin/touch -afm "$TOUCHFILE" /usr/sbin/chown $(whoami):$(whoami) "$TOUCHFILE" /bin/chmod 400 "$TOUCHFILE" $clamdscan --copy="$QUARANTINEDIR" "$SCANDIR1" || $clamscan -i --copy="$QUARANTINEDIR" -r "$SCANDIR1" $clamdscan --copy="$QUARANTINEDIR" "$SCANDIR2" || $clamscan -i --copy="$QUARANTINEDIR" -r "$SCANDIR2" exit 0 fi timestamp=$(/bin/date "+%Y%m%d%H%M.%S") # store timestamp before starting the virus scan if /bin/ps -ax | /usr/bin/grep clamd | /usr/bin/grep -v grep > /dev/null; then # run clamdscan # scan all files that have been changed or modified after the $TOUCHFILE timestamp of the last virus scan # optional: $find -x "$SCANDIR1" "$SCANDIR2" -type f ... (but mind the 'sysctl kern.argmax' limit for xargs!) # optional: to exclude .DS_Store files add: \! -name ".DS_Store" # alternative: first find changed or modified directories # find -x "$SCANDIR1" "$SCANDIR2" -type d \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | while read -d $'\0' scandir; do echo "find -x $scandir -maxdepth 1 -type f ..."; done $find -x "$SCANDIR1" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamdscan --copy="$QUARANTINEDIR" $find -x "$SCANDIR2" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamdscan --copy="$QUARANTINEDIR" else # run clamscan $find -x "$SCANDIR1" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamscan -i --copy="$QUARANTINEDIR" $find -x "$SCANDIR2" -type f \( -newercm "$TOUCHFILE" -or -newermm "$TOUCHFILE" \) -print0 | xargs -0 $clamscan -i --copy="$QUARANTINEDIR" fi # update the $TOUCHFILE timestamp with the pre-scan $timestamp /usr/bin/touch -f -t $timestamp "$TOUCHFILE" echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): ... WATCHDIRS.SH for ClamAV ... DONE ...\n" exit 0 #--------------------------------- sudo chown root:wheel ~/Documents/ClamAV/watchdirs.sh sudo chmod 0770 ~/Documents/ClamAV/watchdirs.sh
Create the ~/Library/LaunchAgents/net.clamav.dirwatcher.plist launch agent
# nano ~/Library/LaunchAgents/net.clamav.dirwatcher.plist # open -e ~/Library/LaunchAgents/net.clamav.dirwatcher.plist # Note: Don't use $HOME or any other variables in the file paths you have to specify in the .plist files below! # Use the full file paths without any variables! # WatchPaths virus scanner launch agent (combined with StartInterval) # scan the directories specified the same in watchdirs.sh and this .plist file when they are modified or after a specified time interval respectively <?xml version="1.0" encoding="UTF-8"?> DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabledkey>/> <key>Labelkey> net.clamav.dirwatcher< /string> <key>LowPriorityIOkey>/> <key>OnDemandkey> /> <key>ProgramArgumentskey> /full/path/to/Documents/ClamAV/watchdirs.shstring> </array> <key>StartIntervalkey> 1800< /integer> <key>WatchPathskey>/full/path/to/Documents/ClamAV/Downloadsstring> /full/path/to/Documents/ClamAV/EmailAttachmentsstring> </array> dict> </plist> #-------------------------------------------------------------------------------- # StartInterval virus scanner launch agent # scan the directories specified in watchdirs.sh at the specified time intervals (given in seconds) <?xml version="1.0" encoding="UTF-8"?> DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabledkey> /> <key>Labelkey> net.clamav.dirwatcher< /string> <key>LowPriorityIOkey>/> <key>ProgramArgumentskey> /full/path/to/Documents/ClamAV/watchdirs.shstring> </array> <key>StartIntervalkey> 7200< /integer> dict> </plist> #-------------------------------------------------------------------------------- # StartCalendarInterval virus scanner launch agent # scan the directories specified in watchdirs.sh at the specified times of the day <?xml version="1.0" encoding="UTF-8"?> DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabledkey>/> <key>Labelkey> net.clamav.dirwatcher< /string> <key>LowPriorityIOkey>/> <key>ProgramArgumentskey> /full/path/to/Documents/ClamAV/watchdirs.shstring> </array> <key>StartCalendarIntervalkey> /key> <integer>7integer> Hour< Minute< /key> <integer>45integer> </dict> <key>StartCalendarIntervalkey>/key> <integer>12integer> Hour< Minute< /key> <integer>30integer> </dict> dict> </plist> #-------------------------------------------------------------------------------- sudo chown root:wheel ~/Library/LaunchAgents/net.clamav.dirwatcher.plist sudo chmod 0770 ~/Library/LaunchAgents/net.clamav.dirwatcher.plist ls -l ~/Library/LaunchAgents/net.clamav.dirwatcher.plist launchctl load -w ~/Library/LaunchAgents/net.clamav.dirwatcher.plist #launchctl unload -w ~/Library/LaunchAgents/net.clamav.dirwatcher.plist sudo reboot open -a Console # update clamd virus database freshclam; sleep 5; sudo clamd RELOAD
Alternative watchdirs.sh for a QueueDirectories virus scanner launch agent
# nano $HOME/Documents/ClamAV/watchdirs.sh #!/bin/bash # check downloaded files or directories in the specified $QueueDirectory for viruses and then move them to $MoveDir # rename the files or folders to be moved to $MoveDir if necessary # remove anything else from the $QueueDirectory exec >/dev/console 2>&1 # write stdout & stderr to console.log in /Library/Logs/Console/ echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): ... WATCHDIRS.SH for ClamAV ... STARTED ...\n" /bin/sleep 5 QUARANTINEDIR="$HOME/Documents/ClamAV/Quarantine" TOUCHFILE="$HOME/.clamav_timestamp" QueueDirectory="$HOME/Documents/ClamAV/Downloads/QueueDirectory" # Safari -> Preferences ... -> Save downloaded files to:/bin/mkdir -p "$QueueDirectory" MoveDir="$HOME/Documents/ClamAV/Downloads" /bin/mkdir -p "$MoveDir" find=/usr/bin/find clamdscan=/opt/local/bin/clamdscan clamscan=/opt/local/bin/clamscan if [[ -f "$QueueDirectory"/.DS_Store ]]; then /bin/rm -f "$QueueDirectory"/.DS_Store; fi # alternative to $TOUCHFILE time stamp #/usr/bin/touch ~/Desktop/test.txt #t1=$(/bin/date +%s) #/bin/sleep 1 #/usr/bin/touch -f -am ~/Desktop/test.txt # ... file is being downloaded ... #t2=$(/usr/bin/stat -f %m ~/Desktop/test.txt) #echo $(($t1 - $t2)) #if [[ $t1 -lt $t2 ]]; then echo "file was modified"; else echo "file was not modified"; fi # directory test DirTest="$($find -x "$QueueDirectory" -type d -maxdepth 1 -not -regex "^$QueueDirectory$")" if [[ -n "$DirTest" ]]; then # download is directory $find -x "$QueueDirectory" -type d -maxdepth 1 -not -regex "^$QueueDirectory$" -print0 | while read -d $'\0' dir; do /usr/bin/touch -f -am "$TOUCHFILE" /bin/sleep 3 unset -v file_modified file_modified=0 # check if a file within $dir has been modified while read -d $'\0' file; do # set file_modified to 1 if at least one file has been modified if [[ "$TOUCHFILE" -ot "$file" ]]; then file_modified=1; break; fi done < <($find -x "$dir" -type f -print0 2>/dev/null) if [[ -d "$dir" ]] && [[ "$TOUCHFILE" -nt "$dir" ]] && [[ $file_modified -eq 0 ]]; then $clamdscan --no-summary --copy="$QUARANTINEDIR" "$dir" || $clamscan -i -r --no-summary --copy="$QUARANTINEDIR" "$dir" bname="$(/usr/bin/basename "$dir")" if [[ ! -d "$MoveDir/$bname" ]]; then /bin/mv "$dir" "$MoveDir" else newMoveDir="$MoveDir/$(/bin/date "+%Y-%m-%d-%H%M.%S")-$bname" /bin/mv "$dir" "$newMoveDir" fi fi done else $find -x "$QueueDirectory" -type f -maxdepth 1 -print0 | while read -d $'\0' file; do # download is file /usr/bin/touch -f -am "$TOUCHFILE" /bin/sleep 3 if [[ -f "$file" ]] && [[ "$TOUCHFILE" -nt "$file" ]]; then $clamdscan --no-summary --copy="$QUARANTINEDIR" "$file" || $clamscan -i --no-summary --copy="$QUARANTINEDIR" "$file" # the mv command preserves metadata and resource forks of files on Extended HFS volumes (Mac OS X 10.4) bname="$(/usr/bin/basename "$file")" if [[ ! -f "$MoveDir/$bname" ]]; then /bin/mv "$file" "$MoveDir" else newMoveDir="$MoveDir/$(/bin/date "+%Y-%m-%d-%H%M.%S")-$bname" /bin/mv "$file" "$newMoveDir" fi fi done fi if [[ -f "$QueueDirectory"/.DS_Store ]]; then /bin/rm -f "$QueueDirectory"/.DS_Store; fi $find -x "$QueueDirectory" -not -type f -not -type d -print0 | while read -d $'\0' item; do /bin/rm -f "$item"; done echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): ... WATCHDIRS.SH for ClamAV ... DONE ...\n" exit 0 #------------------------------------ # nano ~/Library/LaunchAgents/net.clamav.dirwatcher.plist Disabled Label net.clamav.dirwatcher ProgramArguments /full/path/to/Documents/ClamAV/watchdirs.sh QueueDirectories /full/path/to/Documents/ClamAV/Downloads/QueueDirectory
Update clamd virus database automatically
# set permissions #sudo chown -R root:wheel /opt #sudo chmod -R 755 /opt #sudo chown -R root:wheel /opt/local/share/clamav #sudo chmod -R 0770 /opt/local/share/clamav # bash script to update the clamd virus database # sudo nano /usr/local/sbin/update_clamd_db.sh #!/bin/bash /bin/sleep 120 # write stdout & stderr to console.log exec >/dev/console 2>&1 # check if internet connection is alive and database.clamav.net is reachable /usr/bin/curl -I -L -s --max-time 15 database.clamav.net 1>/dev/null if [[ $(echo $?) -eq 0 ]]; then /opt/local/bin/freshclam -u root /bin/sleep 3 #if [[ -e "/tmp/clamd" ]]; then /bin/rm -f /tmp/clamd; fi #/opt/local/sbin/clamd -c /opt/local/etc/clamd.conf # recreates the local socket file /tmp/clamd as specified in /opt/local/etc/clamd.conf above (/bin/sleep 3; echo RELOAD; /bin/sleep 3; echo "exit") | /usr/bin/telnet -u /tmp/clamd >/dev/null 2>&1 #echo RELOAD | /opt/local/bin/socat - /tmp/clamd # an alternative that requires: sudo port install socat /bin/sleep 3 echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): clamd virus database successfully updated\n" exit 0 else echo -e "\n$(/bin/date "+%Y-%m-%d %H:%M:%S %Z"): updating the clamd virus database failed; no internet connection to database.clamav.net established\n" exit 1 fi # set permissions sudo chown root:wheel /usr/local/sbin/update_clamd_db.sh sudo chmod 0770 /usr/local/sbin/update_clamd_db.sh #------------------------ # launchd item to update the clamd virus database using /usr/local/sbin/update_clamd_db.sh # sudo nano /Library/LaunchDaemons/net.clamav.update.clamd.db.plist <?xml version="1.0" encoding="UTF-8"?> DOCTYPE plist PUBLIC "-//Apple Computer//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> <plist version="1.0"> <dict> <key>Disabledkey>/> <key>Labelkey> net.clamav.update.clamd.db< /string> <key>ProgramArgumentskey>/usr/local/sbin/update_clamd_db.shstring> </array> <key>RunAtLoadkey> /> <key>StartIntervalkey> 10800< /integer> <key>UserNamekey>root< /string> <key>GroupNamekey>wheel< /string> dict> </plist> sudo chown root:wheel /Library/LaunchDaemons/net.clamav.update.clamd.db.plist sudo chmod 0644 /Library/LaunchDaemons/net.clamav.update.clamd.db.plist sudo launchctl load -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist #sudo launchctl unload -w /Library/LaunchDaemons/net.clamav.update.clamd.db.plist sudo reboot # simple shell script syntax check # for more see Debugging Bash scripts, http://tldp.org/LDP/Bash-Beginners-Guide/html/sect_02_03.html bash -n $HOME/Documents/ClamAV/watchdirs.sh bash -n /usr/local/sbin/update_clamd_db.sh # optional: convert XML .plist files to binary format sudo plutil -convert binary1 -- /Library/LaunchDaemons/net.clamav.clamd.plist \ ~/Library/LaunchAgents/net.clamav.dirwatcher.plist \ /Library/LaunchDaemons/net.clamav.update.clamd.db.plist # check .plist file syntax plutil -- /Library/LaunchDaemons/net.clamav.clamd.plist \ ~/Library/LaunchAgents/net.clamav.dirwatcher.plist \ /Library/LaunchDaemons/net.clamav.update.clamd.db.plist # list virus signatures sigtool -l | grep -Ei 'adware|spy' | nl sigtool -l | grep -i phish | nl
Further information:
- Run ClamAV from a system service agent account
- ClamAV an open-source anti-virus toolkit
- Configuring Clamav's clamd for enhanced virus-scanning performance
- Updating ClamAV on OS X Server 10.4.7-10.4.11
- Using Open Source Tools to Filter Email on Mac OS X Server
- The Anti-Virus Or Anti-Malware Test File
- Mac OS X Unix Tutorial: Part 4 - Managing Permissions
- launchd