#!/bin/sh -e
#
# Update the McAfee data files.
#
# As at 2010/04/10 the mcafee6-autoupdate and mcafee6-autoupdate scripts
# are identical. The logic to differentiate between versions is built in
# to the script to enable only one version of the script to be maintained.
#
# based on:
#
# $Cambridge: hermes/conf/build/bin/uvscan-update,v 1.52 2004/08/18 19:12:02 fanf2 Exp $
# and patch from:
# http://lists.mailscanner.info/pipermail/mailscanner/2009-November/094019.html

# $PREFIX is the directory where the uvscan binary is (NOT a symlink to
# the binary), which is where it looks for its dat files. You may run
# uvscan via a symlink to this place (e.g. from /usr/local/bin/uvscan)
# and it will still look for the dat files here. If uvscan's library
# dependencies can be found in a standard place (e.g. /usr/local/lib)
# then you don't need a wrapper script to set LD_LIBRARY_PATH before
# running it.
#
# The dat files are installed in a subdirectory of $DATDIR named
# according to their version number, with symlinks from $PREFIX into
# the subdirectory via a current link. The current link is updated
# without locking on the assumption that this is sufficiently unlikely
# to cause a problem.
#

# As of Apr 2010, McAfee is no longer publishing V1 DATs, and is only
# publishing V2 DATs:
#
#   https://kc.mcafee.com/corporate/index?page=content&id=KB60404
#   https://kc.mcafee.com/corporate/index?page=content&id=KB60772
#
# Version 6 of McAfee VirusScan Command Line Scanner for Unix uses V2 DATs.
# Version 5, which uses V1 DATs, is EoL and no longer receives DAT updates.
#
# If this script detects taht we are running VirusScan CLI version 6, we
# extract the DATs from the V2 DAT zip archive (avvdat-XXXX.zip).
# Otherwise, we log an error about EoL scanner and no available updates.
#
# As V1 DATs are no longer published, support for them has been removed
# from this update script.

# defaults
OPTS="-d"
PREFIX=/opt/uvscan
FTPDIR=http://update.nai.com/products/commonupdater 
RETRIES=1
INTERVAL=300
CLIVERSION=6

wgetverbosity="--no-verbose"
tarverbosity=""
unzipverbosity="-q"
unzipopts="-o"

# handle the command line
usage () {
        echo "usage: $0 [-dfrtv] [-Rnnn] [-Innn] [proxy] [prefix] [lock]"
        echo "  -d      delete old files"
        echo "  -e      get extra.dat"
        echo "  -f      force update"
        echo "  -r      show README"
        echo "  -t      timestamp output"
        echo "  -v      verbose"
        echo "  -R      number of retries"
        echo "  -I      retry interval"
        echo "  proxy   URL of FTP/HTTP proxy server"
        echo "  prefix  uvscan installation directory"
	echo "  lock    lockfile location"
        exit 1
}
case $# in
[0123456])
        : ok
        ;;
*)      usage
        ;;
esac
for arg in "$@"
do
        case $arg in
        -I*)    INTERVAL=${arg#-I}
                ;;
        -R*)    RETRIES=${arg#-R}
                ;;
        -*)     OPTS=$arg
                ;;
	*lock)	LOCKFILE=$arg
		;;
        /*)     PREFIX=$arg
                ;;
        http://*)  ftp_proxy=$arg
                http_proxy=$arg
                export ftp_proxy
                export http_proxy
                ;;
        *)      usage
                ;;
        esac
done
case $OPTS in
*[!-dfrtv]*)
        usage
esac
option () {
        case $OPTS in
        -*$1*)  eval $2=yes
                ;;
        *)      eval $2=no
                ;;
        esac
}
option d DELETE
option e EXTRA
option f FORCE
option r README
option t TIME
option v VERBOSE
case $FORCE in
yes)    VERBOSE=yes
	wgetverbosity=""
	tarverbosity="v"
	unzipverbosity=""
esac

# look for binaries and libraries in plausible places
PATH=$PREFIX:/usr/local/bin:/usr/bin:/bin
# this is only necessary for broken setups
LD_LIBRARY_PATH=$PREFIX
export PATH LD_LIBRARY_PATH

#setup sane umask, just in case...
umask 022

# where this script finds things
DATDIR=$PREFIX/datfiles

# These are for CLI v6+:
# Note that runtime.dat is not distributed; it is generated by uvscan the
# first time it runs (including with "uvscan --version").
DATFILES6="avvclean.dat avvnames.dat avvscan.dat runtime.dat extra.dat" 

LINKNAME=current
LINKREL=datfiles/$LINKNAME


# wrapper functions for echo etc.
timestamp () {
        case $TIME in
        yes)    date "+%Y-%m-%d %H:%M:%S "
        esac
}
say () {
        case $VERBOSE in
        yes)    echo "`timestamp`$*"
        esac
}
run () {
        say "> $*"
        "$@"
}
testeval () {
        # ugly workaround
        say "> $*"
        set +e
        eval "$*"
        ret=$?
        set -e
        return $ret
}
is () {
        test "$@" 2>/dev/null
}
say Starting $0
say DELETE=$DELETE
say FORCE=$FORCE
say README=$README
say TIME=$TIME
say VERBOSE=$VERBOSE
say RETRIES=$RETRIES
say INTERVAL=$INTERVAL
say PROXY=$ftp_proxy
say PREFIX=$PREFIX

# check directory setup is correct
# At this point we do not know whether this is a CLI version 6 or version 5
# installation, and more particularly what the filenames for the DAT files
# are.
#for link in $LINKREL $DATFILES
for link in $LINKREL
do
        if ! is -h $PREFIX/$link
        then
                say $PREFIX/$link is not set up
                INIT=yes
        fi
done
if ! is -d $DATDIR
then
        say $DATDIR is not set up
        INIT=yes
fi
case $INIT in
yes)
        VERBOSE=yes
        say Doing initial setup of $0
        run mkdir -p $DATDIR
esac
run cd $DATDIR

getver () {
        match="[0-9][0-9][0-9][0-9]"
        err="version.err"
        cmd="$1" out="$2" txt="$3"
        if testeval "$cmd 2>$err 1>&2"
        then
                VER=`cat $out | sed "/^$txt\($match\).*$/!d;s//\1/;q"`
                case $VER in
                $match) run rm -f $out $err
                        return
                esac
        fi
        cat $err
        VER=UNKNOWN
        run rm -f $out $err
}

#this parses an ini file for the occurence of a value in the specified section
#parseini INIFILE SECTION ITEM
parseini () {
  myINIFILE="$1"
  mySECTION="$2"
  myITEM="$3"
  if [ ! -s "${myINIFILE}" ]
  then
    echo "UNKNOWN"
    return 1
  fi

  myINSEC="no"
  while read line
  do
    #just incase input is in DOS format... (Is the case for avvdat.ini)
    line="`echo $line|sed 's/\r//'`"

    if [ "${line}" = "[${mySECTION}]" ]
    then
      myINSEC="yes"
      continue 
    fi
    if [ "`echo ${line}|cut -c1`"  = "[" ]
    then
      myINSEC="no"
      continue 
    fi
    [ "${myINSEC}" = "yes" ] || continue
    if [ "`echo ${line}|cut -d= -f1`" = "${myITEM}" ]
    then
      echo "`echo ${line}|sed 's/^'"${myITEM}="'//'`"
      return 0
    fi
  done < ${myINIFILE}
  echo "UNKNOWN"
  return 1
}

# work out latest dat version
try=$RETRIES
while :
do
        rm -f avvdat.ini
        if is $? != 0
        then
          say "Error deleting avvdat.init... update may not be successful."
        fi

        run wget --tries=$try --waitretry=$INTERVAL --passive-ftp $FTPDIR/avvdat.ini
	NEWVER=`parseini avvdat.ini AVV-ZIP DATVersion`
	say New version is $NEWVER
        case $NEWVER in
        UNKNOWN)
                if ! try=`expr $try - 1`
                then break
                fi
                say Problem with McAfee datfile update from $FTPDIR
                say Sleeping for $INTERVAL seconds before retrying
                sleep $INTERVAL
                ;;
        *)      break
                ;;
        esac
done

# work out installed dat version
# CLI v5 is EoL so no point in checking for it first,
# as no one should still be using it
getver "uvscan --version" version.err "Dat set version: "
if is $VER = UNKNOWN
then
	# Might be CLI pre-v6:
	getver "uvscan --version" version.err "Virus data file v"
        if is $VER != UNKNOWN
        then
          VERBOSE=yes
	  say "uvscan earlier than v6 found. No DATs available. ABORTING."
          say "Please upgrade uvscan to at least v6.0.0"
          if is $VER != 5937
          then
            say ""
            say "You are not running the last released v1 DAT."
            say "Please manually upgrade to DAT v5937 if possible."
          fi
          run exit 1
        fi
fi
PREVIOUS=$VER

case $FORCE in
yes)    say Forced update from $PREVIOUS
        PREVIOUS=0000
        ;;
*)      if is $NEWVER -eq $PREVIOUS
        then    say Already have $NEWVER
                run exit 0
        fi
esac

# select appropriate archive name and DAT filenames
# if this is CLI v6, we use V2 DAT archive
if is $CLIVERSION = 6
then
        DISTARC=avvdat-$NEWVER.zip
        DATFILES="$DATFILES6"
else
        say "Fatal Error. Unsupported CLI version found..."
        exit 1 
fi

VERBOSE=yes

# We are performing an update, so be chatty (as opposed to explicitly
# verbose as requested)
CHATTY=yes

say Installed dat file is $PREVIOUS
say Latest dat file is $NEWVER

if is $NEWVER = UNKNOWN
then    say Problem with McAfee datfile update from $FTPDIR
        run exit 1
elif is $NEWVER -lt $PREVIOUS
then    say Remote version $VERSION older than installed version $PREVIOUS
        run exit 1
elif is -d $NEWVER
then    say Cleaning away $NEWVER directory
        run rm -rf $NEWVER
fi

retry () {
        echo "$OUT"
        say Fetch or test failed -- removing bad McAfee data files
        run cd $DATDIR
        run rm -rf $NEWVER
        if ! try=`expr $try - 1`
        then    say Giving up
                run exit 1
        fi
        say Sleeping for $INTERVAL seconds before retrying
        sleep $INTERVAL
        continue
}

try=$RETRIES
while :
do
        # fetch and extract dat files
        run mkdir $NEWVER
        run cd $NEWVER
        run chmod 700 .
        if ! run wget $wgetverbosity --tries=$try --waitretry=$INTERVAL --passive-ftp --progress=dot:mega $FTPDIR/$DISTARC
        then retry
        fi
	if is ! $CLIVERSION 6
	then
	        run tar x${tarverbosity}f $DISTARC
	else
	        run unzip $unzipverbosity $unzipopts $DISTARC
	fi
        run chmod 644 *
        run chmod 755 .

        # verify the contents
	# this will create runtime.dat too
        # we use --decompress to speed up future runs...
        CMD="uvscan --version --dat . --decompress"
        say "> $CMD"
        if ! OUT=`$CMD 2>&1`
        then    retry
        else    break
        fi
done

echo "$OUT"
say Update OK

# show information on this update?
case $README in
yes)    run sed 's/[[:cntrl:]]//g
                1,/^====================/d
                /^====================/,/^NEW VIRUSES DETECTED/d
                /^UNDERSTANDING VIRUS NAMES/,$d
                s/^/# /;/@MM/s/$/ <--/' readme.txt
esac
# remove some crap
run rm -f *.diz *.exe *.ini *.lst *.tar *.txt *.zip

# Make sure symlinks are in place
for file in $DATFILES
do
	run rm -f $PREFIX/$file
	run ln -s $LINKREL/$file $PREFIX/$file
done

# update the current version link
run cd $DATDIR
run ln -s $NEWVER $NEWVER/$LINKNAME
run mv $NEWVER/$LINKNAME .

# maybe delete old dat files
case $DELETE in
yes)    run cd $DATDIR
        run rm -rf $PREVIOUS
esac

say Completed OK
run exit 0

# done

