#!/bin/bash
#
# Bypass the DNS cache when checking for ClamAV updates.
#
# $Cambridge: hermes/conf/clamav/sbin/fresherclam,v 1.48 2010/05/14 16:17:17 fanf2 Exp $

fresh_dir=/spool/clamav/freshclam
sane_dir=/spool/clamav/sanesec
clamd_dbdir=/spool/clamav/db
clamd_dbdir2=/spool/clamav/db2
gpg_dir=/spool/clamav/gpg

PATH=/opt/clamav/bin:$PATH

case "$1" in
-v|--verbose)
	qv="--verbose"
	;;
*)	qv="--quiet"
       ;;
esac

verbose() {
	case $qv in
	--verbose)
		echo "$@"
	esac
}

verbose ''

tries=0
while :
do	if ns=$(dig +short ns clamav.net. | grep -v '^;' | head -1)
	   master_dns=$(dig +short txt current.cvd.clamav.net. @$ns)
	then break
	fi
	verbose ERROR $master_dns
	if tries=$((tries+1))
	   [ $tries -eq 10 ]
	then exit 1
	fi
done

if ! local_dns=$(dig +short txt current.cvd.clamav.net. @localhost)
then
	verbose ERROR $local_dns
	exit 1
fi

verbose MASTER DNS $master_dns
verbose LOCAL DNS $local_dns

master_ver=:$(echo $master_dns | sed 's/"//g' | cut -d: -f 2,3,7,8)
local_ver=:$(echo $local_dns | sed 's/"//g' | cut -d: -f 2,3,7,8)

verbose MASTER VERSION $master_ver
verbose LOCAL VERSION $local_ver

unpack() {(
	# unpack freshclam databases for PUA signatures
	cd "$fresh_dir"
	sigtool --unpack daily.c?d
	sigtool --unpack main.c?d
	# ensure time stamps of extracted files match the archive
	touch -r daily.c?d daily.*
	touch -r main.c?d main.*
)}

# this run of freshclam deals with any difference between local dns and disk
cur=$(freshclam 2>/dev/null && unpack)
cur_ver=""
for db in main daily safebrowsing bytecode
do
	cur_ver=$cur_ver:$(echo "$cur" | sed '/^'$db'.*version: /!d;s///;s/,.*//;q')
done

verbose INSTALLED $cur_ver

case $master_ver in
$cur_ver)
	verbose "No need to run freshclam"
	;;
*)	(freshclam $qv --no-dns && unpack) 2>&1 |
	grep -v 'ERROR: Unknown response from remote server' |
	grep -v '[*][*][*]'
	;;
esac

# needed for ignoring known files unpacked from the official database
shopt -s extglob

# Iterate through the downloaded databases
installed=0
for db in "$fresh_dir"/*.* "$sane_dir"/*.?db
do
	db_name=$(basename "$db")
	db_safe="$clamd_dbdir/$db_name"
	db_dodgy="$clamd_dbdir2/$db_name"

	# categorize signature databases as safe or unsafe
	# and silently ignore some of them
	# see http://www.sanesecurity.co.uk/databases.htm
	# and /opt/spamd/etc/local.cf
	db_live="ignore"
	db_obs=""
	do_gpg=0
	case "$db_name" in
	# official - compressed
	daily.cvd)                         db_live="$db_safe";  db_obs="$clamd_dbdir/daily.cld";;
	main.cvd)                          db_live="$db_safe";  db_obs="$clamd_dbdir/main.cld";;
	bytecode.cvd)                      db_live="$db_safe";  db_obs="$clamd_dbdir/bytecode.cld";;
	safebrowsing.cvd)                  db_live="$db_dodgy"; db_obs="$clamd_dbdir2/safebrowsing.cld";;
	# official - incremental
	daily.cld)                         db_live="$db_safe";  db_obs="$clamd_dbdir/daily.cvd";;
	main.cld)                          db_live="$db_safe";  db_obs="$clamd_dbdir/main.cvd";;
	bytecode.cld)                      db_live="$db_safe";  db_obs="$clamd_dbdir/bytecode.cvd";;
	safebrowsing.cld)                  db_live="$db_dodgy"; db_obs="$clamd_dbdir2/safebrowsing.cvd";;
	# official unpacked PUA
	@(daily|main).?du)                 db_live="$db_dodgy";;
	# official unpacked phishing URLs
	@(daily|main).[pw]db)              db_live="$db_dodgy";;
	# official unpacked files to ignore
	mirrors.dat)                       ;;
        @(daily|main).@(cfg|db|fp|ftm|hdb|hdu|idb|ign|ign2|info|ldb|mdb|ndb|zmd)) ;;
	# unofficial
	doppelstern.hdb)                   db_live="$db_safe";  do_gpg=1;;
	doppelstern.ndb)                   db_live="$db_safe";  do_gpg=1;;
	junk.ndb)                          db_live="$db_safe";  do_gpg=1;;
	jurlbl.ndb)                        db_live="$db_dodgy"; do_gpg=1;;
	jurlbla.ndb)                       db_live="$db_dodgy"; do_gpg=1;;
	lott.ndb)                          db_live="$db_dodgy"; do_gpg=1;;
	phish.ndb)                         db_live="$db_safe";  do_gpg=1;;
	rogue.hdb)                         db_live="$db_safe";  do_gpg=1;;
	scam.ndb)                          db_live="$db_safe";  do_gpg=1;;
	scamnailer.ndb)                    db_live="$db_safe";  do_gpg=1;;
	spam.ldb)                          db_live="$db_dodgy"; do_gpg=1;;
	spamattach.hdb)                    db_live="$db_safe";  do_gpg=1;;
	spamimg.hdb)                       db_live="$db_safe";  do_gpg=1;;
	spear.ndb)                         ;;
	spearl.ndb)                        db_live="$db_safe";  do_gpg=1;;
	INetMsg-SpamDomains-2w.ndb)        ;;
	INetMsg-SpamDomains-2m.ndb)        db_live="$db_dodgy"; do_gpg=1;;
	winnow.attachments.hdb)            db_live="$db_safe";  do_gpg=1;;
	winnow.complex.patterns.ldb)       db_live="$db_dodgy"; do_gpg=1;;
	winnow_extended_malware.hdb)       db_live="$db_safe";  do_gpg=1;;
	winnow_extended_malware_links.ndb) db_live="$db_dodgy"; do_gpg=1;;
	winnow_malware.hdb)                db_live="$db_safe";  do_gpg=1;;
	winnow_malware_links.ndb)          db_live="$db_safe";  do_gpg=1;;
	winnow_spam_complete.ndb)          db_live="$db_dodgy"; do_gpg=1;;
	winnow_phish_complete.ndb)         ;;
	winnow_phish_complete_url.ndb)     db_live="$db_dodgy"; do_gpg=1;;
	*)
		echo "WARNING: $db_name is unknown; skipping"
	esac

	if [ "$db_live" = "ignore" ]
	then
		# remove any previously-installed databases
		verbose "Removing obsolete $db_safe"
		rm -f "$db_safe"
		verbose "Removing obsolete $db_dodgy"
		rm -f "$db_dodgy"
		continue
	fi

	# Only pay any attention to this database if it's newer than the installed version
	if [ -e "$db_live" -a ! "$db" -nt "$db_live" ]
	then
		verbose "$db_name is already up-to-date; skipping"
		continue
	fi

	# Zero-length databases have no value and confuse the test below
	if [ ! -s "$db" ]
	then
		echo "ERROR: $db_name is zero-length; discarding"
		continue
	fi

	# Check that the GnuPG signature is correct
	if [ $do_gpg = 1 ] &&
           ! gpg_out=$(gpg --no-options --homedir="$gpg_dir" --verify "$db.sig" "$db" 2>&1)
	then
		echo "SECURITY ERROR: $db_name has a bad GnuPG signature; discarding:"
		echo "$gpg_out"
		continue
	fi

	# Test the database by asking ClamAV to check something with it
	if ! clam_out=$(clamscan --quiet --database="$db" - </dev/null 2>&1)
	then
		case $db_name in
		bytecode.*)
			continue
			;;
		*)
			echo "ERROR: $db_name fails a simple test; skipping" >&2
			printf "%s\n" "$clam_out" | fgrep -v '***'
			continue
		esac
	fi

	# Now we can actually install this database
	verbose "Installing $db_name into $db_live"
	if cp -p "$db" "$db.tmp" &&
	   mv "$db.tmp" "$db_live"
	then
		installed=$((installed+1))
	fi

	# Remove old databases that have been installed in the wrong
	# place, for use when we change the install locations above.
	if [ "$db_live" != "$db_safe" ] && [ -f "$db_safe" ]
	then
		verbose "Removing obsolete $db_safe"
		rm -f "$db_safe"
	fi
	if [ "$db_live" != "$db_dodgy" ] && [ -f "$db_dodgy" ]
	then
		verbose "Removing obsolete $db_dodgy"
		rm -f "$db_dodgy"
	fi
	# Deal with switches between compressed and incremental formats.
	if [ -n "$db_obs" ]
	then
		verbose "Removing obsolete $db_obs"
		rm -f "$db_obs"
	fi
done

# Finished; display summary.
verbose ''
if [ "$installed" -gt 0 ]
then
	if [ "$installed" -eq 1 ]
	then
		s=""
	else
		s="s"
	fi
	verbose "Installed $installed database$s"
else
	verbose "No databases installed."
fi

# eof
