#!/usr/bin/perl -w use Getopt::Long; use Pod::Usage; use strict; my $version = '0.5'; my $default_verbosity = 5; # Debug output. You could set it to 3 after # testing your installation. # set defaults my %opt = ( 'help' => 0, 'datdir' => "/usr/local/uvscan", 'dry-run' => 0, 'path' => 0, 'verbose' => $default_verbosity, ); Getopt::Long::Configure ("bundling"); GetOptions( 'help|h|?' => \$opt{'help'}, 'datdir|d=s' => \$opt{'datdir'}, 'dry-run|n' => \$opt{'dry-run'}, 'path|p' => \$opt{'path'}, 'verbose|v=i' => \$opt{'verbose'}, ) or pod2usage(-exitval => 1); $opt{'help'} and pod2usage(-exitval => 0); my $datdir = $opt{'datdir'}; my $dry_run = $opt{'dry-run'}; my $path = $opt{'path'}; my $verbose = $opt{'verbose'}; # Location of the required files my $binary = $path ? `which uvscan` : "$datdir/uvscan"; my $unzip = $path ? `which unzip` : "/usr/bin/unzip"; my $md5sum = $path ? `which md5sum` : "/usr/bin/md5sum"; my $wget = $path ? `which wget` : "/usr/bin/wget"; my $tar = $path ? `which tar` : "/bin/tar"; my $rm = $path ? `which rm` : "/bin/rm"; # gotta chomp: which adds a newline chomp $binary; chomp $unzip; chomp $md5sum; chomp $wget; chomp $tar; chomp $rm; # On some systems (Solaris is one of them), `md5sum' is called `md5'. unless(-x $md5sum) { $md5sum = `which md5`; chomp $md5sum; } my $naiurl = "ftp://ftpde.nai.com"; my $iniurl = "$naiurl/pub/datfiles/english/update.ini"; # update.ini looks like (on Fri Jun 13 18:30:28 CEST 2003) # # [SuperDat-IA32] # EngineVersion=4240 # DATVersion=4271 # FileName=sdat4271.exe # FileSize=5331975 # Checksum=4823,5142 # # [ZIP] # EngineVersion=0 # DATVersion=4271 # FileName=dat-4271.zip # FilePath=/pub/antivirus/datfiles/4.x/ # FileSize=2854854 # Checksum=F576,8628 # MD5=d16fd63a3bce7be32af79b434999698f # # [Incremental] # EngineVersion=0 # DATVersion=4271 # FileName=delta.ini # FileSize=1303 # Checksum=E7CE,194A # # [Engine-LINUX] # EngineVersion=4240 # FileName=elnx4240.zip # FileSize=1006785 # Checksum=781E,B2DE # MD5=d461201cf97ce2ef79b91f6cc34d3202 # FilePath=/pub/antivirus/engine/4.x/ # # [Engine-NETWARE] # EngineVersion=4240 # FileName=nw4240.zip # FileSize=3303598 # Checksum=1C67,E3DA # MD5=de2e9111236428905ce867ddfab41ee7 # FilePath=/pub/antivirus/engine/4.x/ # # Initialize variables my $cur_dat_vers = -1; # make sure we'll have to update by default my $rem_dat_vers = 0; my $cur_eng_vers = 0; my $rem_eng_vers = 0; my $rem_dat_md5 = 0; my $rem_eng_md5 = 0; my $res_md5 = -1; my $res = 0; my $eng_file_name = ''; my $eng_file_path = ''; my $dat_file_name = ''; my $dat_file_path = ''; my $zipfound = 0; my $enginefound = 0; # Tell who we are print "Naiupdt $version , and \n\n" if ($verbose >= 3); # Do some checking on needed binaries # Assuming that rm is in the default location (valid on GNU/Linux, *BDS, Sun Solaris ...) print "Checking for tar ..\t\t\t" if ($verbose >= 3); if (!(-x $tar)) { print "Tar ($tar) not found, can't continue\n" if ($verbose >= 1); exit(1); } else { print "OK\n" if ($verbose == 3); print "OK ($tar)\n" if ($verbose >= 4); } print "Checking for wget ..\t\t\t" if ($verbose >= 3); if (!(-x $wget)) { print "Wget ($wget) not found, can't continue\n" if ($verbose >= 1); exit(1); } else { print "OK\n" if ($verbose == 3); print "OK ($wget)\n" if ($verbose >= 4); } print "Checking for unzip ..\t\t\t" if ($verbose >= 3); if (!(-x $unzip)) { print "Unzip ($unzip) not found, can't continue\n" if ($verbose >= 1); exit(1); } else { print "OK\n" if ($verbose == 3); print "OK ($unzip)\n" if ($verbose >= 4); } print "Checking uvscan binary ..\t\t" if ($verbose >= 3); if (!(-x $binary)) { print "Uvscan binary ($binary) not found, can't continue\n" if ($verbose >= 1); exit(1); } else { print "OK\n" if ($verbose == 3); print "OK ($binary)\n" if ($verbose >= 4); } print "Checking DAT directory ..\t\t" if ($verbose >= 3); if (!(-w $datdir)) { print "DAT dir ($datdir) not found or writable, can't continue\n" if ($verbose >= 1); exit(1); } else { print "OK\n" if ($verbose == 3); print "OK ($datdir)\n" if ($verbose >= 4); } my $cmd = "$binary --version"; if ($dry_run) { print "Would run $cmd\n"; } else { # Get local versions by querying binary open (LOCAL, "$cmd |") or die "Can't open $cmd pipe: $!\n"; while () { if ( $_ =~ /.*data file v(\d+) /) { $cur_dat_vers = $1; } if ( $_ =~ /.*engine v(\d+\.\d+\.\d+) /) { $cur_eng_vers = $1; $cur_eng_vers =~ s/\.//g; } } close (LOCAL) or die "Cant close $cmd pipe: $!\n"; print "Checking local Engine version ..\t$cur_eng_vers\n" if ($verbose == 3); print "Checking local Engine version ..\tOK ($cur_eng_vers)\n" if ($verbose >= 4); print "Checking local DAT version ..\t\t$cur_dat_vers\n" if ($verbose == 3); print "Checking local DAT version ..\t\tOK ($cur_dat_vers)\n" if ($verbose >= 4); print "\n" if ($verbose >= 3); } $cmd = "$wget --passive-ftp -T 300 -q -nc -O - $iniurl"; if ($dry_run) { print "Would run $cmd\n"; } else { print "Priming the firewall...\t\t\t" if ($verbose >= 3); system('/bin/sh', '-c', "$cmd >/dev/null 2>&1"); print "DONE\n" if ($verbose >= 3); # Get remote version by querying update.ini print "Downloading update.ini ..\t\t" if ($verbose >= 3); open (WGET, "$cmd 2>&1 |") or die "Can't run $cmd: $!\n"; while () { chomp; s/ $//; # update.ini is in dos-style. fix it. s/$//; if (/Alert/g) { print "FAIL\n\n"; print "Unable to connect to $iniurl .. maybe more luck on next run\n" if ($verbose >= 1); exit(1); } # A new section has started or the current section ended if (/^\[.*\]$|^$/) { $zipfound = 0; $enginefound = 0; } # The update.ini file has a section called [ZIP] for the DAT file if (/^\[ZIP\]$/) { $zipfound = 1; } if (/^DATVersion=(\d+).*/ and $zipfound) { $rem_dat_vers = $1; } if (/^FileName=(.*)/ and $zipfound ) { #\w*-\d*\.\w* $dat_file_name = $1; } if (/^FilePath=(.*)/ and $zipfound) { $dat_file_path = $1; } if (/^MD5=(.*)/ and $zipfound) { $rem_dat_md5 = $1; } # The update.ini has a section called Engine-LINUX for the engine file # (Of course, this is useful only on GNU/Linux systems!) # it seems NAI has dropped SunOS support... if (`uname` eq "Linux\n") { if (/^\[Engine-LINUX\]$/) { $enginefound = 1; } } if (/^EngineVersion=(\d+).*/ and $enginefound) { $rem_eng_vers = $1; } if (/^FileName=(.*)/ and $enginefound) { $eng_file_name = $1; } if (/^FilePath=(.*)/ and $enginefound) { $eng_file_path = $1; } if (/^MD5=(.*)/ and $enginefound) { $rem_eng_md5 = $1; } } close(WGET) or die "Can't close wget pipe: $!\n"; print "SUCCESS\n" if ($verbose >= 3); print "Checking remote Engine version ..\t$rem_eng_vers\n" if ($verbose == 3); print "Checking remote Engine version ..\tOK ($rem_eng_vers)\n" if ($verbose >= 4); print "Checking remote DAT version ..\t\t$rem_dat_vers\n" if ($verbose == 3); print "Checking remote DAT version ..\t\tOK ($rem_dat_vers)\n" if ($verbose >= 4); print "\n" if ($verbose >= 3); } if ($rem_eng_vers > $cur_eng_vers) { $cmd = "$wget --passive-ftp -T 300 -q -nc $naiurl$eng_file_path$eng_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print "Downloading newer Engine file ..\t" if ($verbose >= 3); print "Executing ..\n$cmd .. " if ($verbose >= 5); $res = system("cd $datdir; $cmd > /dev/null 2>&1"); if ($res != 0) { # Download failed somehow, notify and remove downloaded file print "FAIL\n\n" if ($verbose >= 3); print "Download of Engine file failed .. maybe more luck on next run\n\n" if ($verbose >= 1); print "Cleaning up ..\t\t\t\t" if ($verbose >= 3); $res = system("cd $datdir; $rm -f $eng_file_name > /dev/null 2>&1"); if ($res != 0) { print "FAIL\n\n" if ($verbose >= 3); print "Remove of DAT file failed .. Remove manually\n\n" if ($verbose >= 1); } else { print "DONE\n" if ($verbose >= 3); } exit(1); } # File received OK print("\tSUCCESS\n") if ($verbose >= 3); } if (-x $md5sum) { # you might have to add a `-b' option to the md5sum call on non-unix # systems $cmd = "$md5sum < $datdir/$eng_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print("Checking MD5 signature ..\t\t") if ($verbose >= 3); open (MD5, "$cmd 2>&1 |") or die; while () { if (/^(\w{32})/) { $res_md5 = $1; } } close (MD5); if ($rem_eng_md5 ne $res_md5) { print "FAIL\n\n" if ($verbose >= 3); print "ENG file MD5 signature does not match " . "($rem_eng_md5 (as found on website) is not $res_md5 (as " . "calculated from local $eng_file_name))\n\n" if ($verbose >= 1); exit(1); } print "OK\n" if ($verbose == 3); print "OK ($res_md5)\n" if ($verbose >= 4); } } else { print "MD5 signature of Engine file ignored, $md5sum not found ..\n" if ($verbose >= 1); } print("Unpacking Engine file ..\t\t") if ($verbose >= 3); $cmd = "$unzip -o $eng_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print("Executing ..\n$cmd .. ") if ($verbose >= 5); $res = system("cd $datdir; $cmd"); if ( ($res != 2304) and ($res !=0) ) { # WARNING: Very weird return code for success!! print "FAIL\n\n" if ($verbose >= 3); print "Unpacking of Engine file failed ..\n\n" if ($verbose >= 1); print "Cleaning up .. \t\t\t\t" if ($verbose >= 3); $res = system("cd $datdir; $rm -f $eng_file_name > /dev/null 2>&1"); if ($res != 0) { print "FAIL\n\n" if ($verbose >= 3); print "Remove of Engine file failed .. Remove manually\n\n" if ($verbose >= 1); } else { print "DONE\n" if ($verbose >= 3); } exit(1); } print "\tSUCCESS\n" if ($verbose >= 3); } $cmd = "$rm -f $eng_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print("Cleaning up Engine file ..\t\t") if ($verbose >= 3); $res = system("cd $datdir; $cmd > /dev/null 2>&1"); if ($res != 0) { print "FAIL\n\n" if ($verbose >= 3); print "Remove of Engine file failed .. Remove manually\n\n" if ($verbose >= 1); exit(2); } print "DONE\n" if ($verbose >= 3); print "Engine updated from $cur_eng_vers to $rem_eng_vers ..\tSUCCESSFUL\n" if ($verbose >= 2); } } else { print ("Your Engine file is up to date ..\tOK\n") if ($verbose == 3); print ("Your Engine file is up to date ..\tOK ($cur_eng_vers)\n") if ($verbose >= 4); } print "\n" if ($verbose >= 3); if ($rem_dat_vers > $cur_dat_vers) { $cmd = "$wget --passive-ftp -T 300 -q -nc $naiurl$dat_file_path$dat_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print "Downloading newer DAT file ..\t\t" if ($verbose >= 3); print "\nExecuting $cmd .. " if ($verbose >= 5); $res = system("cd $datdir; $cmd > /dev/null 2>&1"); if ($res != 0) { # Download failed somehow, notify and remove downloaded file print "FAIL\n\n" if ($verbose >= 3); print "Download of DAT file failed .. maybe more luck on next run\n\n" if ($verbose >= 1); print "Cleaning up ..\t\t\t\t" if ($verbose >= 3); $res = system("cd $datdir; $rm -f $dat_file_name > /dev/null 2>&1"); if ($res != 0) { print "FAIL\n\n" if ($verbose >= 3); print "Remove of DAT file failed .. Remove manually\n\n" if ($verbose >= 1); } else { print "DONE\n" if ($verbose >= 3); } exit (1); } # File received OK print("SUCCESS\n") if ($verbose >= 3); } if (-x $md5sum) { $cmd = "$md5sum < $datdir/$dat_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print("Checking MD5 signature ..\t\t") if ($verbose >= 3); open (MD5, "$cmd 2>&1 |") or die; while () { if (/^(\w{32})/) { $res_md5 = $1; } } close (MD5); if ($rem_dat_md5 ne $res_md5) { print "FAIL\n\n" if ($verbose >= 3); print "DAT file MD5 signature does not match ". "($rem_eng_md5 (as found on website) is not $res_md5 (as " . "calculated from local $eng_file_name))\n\n" if ($verbose >= 1); exit(1); } print "OK\n" if ($verbose == 3); print "OK ($res_md5)\n" if ($verbose >= 4); } } else { print("MD5 signature of DAT file ignored, $md5sum not found ..\n") if ($verbose >= 1); } $cmd = "$unzip -o $dat_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print("Unpacking DAT file ..\t\t\t") if ($verbose >= 3); print("Executing ..\n$cmd .. ") if ($verbose >= 5); $res = system("cd $datdir; $cmd > /dev/null 2>&1"); if ( ($res != 2304) and ($res !=0) ) { # WARNING: Very weird return code for success!! print "FAIL\n\n" if ($verbose >= 3); print "Unpacking of DAT file failed ..\n\n" if ($verbose >= 1); print "Cleaning up .. \t\t\t\t" if ($verbose >= 3); $res = system("cd $datdir; $rm -f $dat_file_name > /dev/null 2>&1"); if ($res != 0) { print "FAIL\n\n" if ($verbose >= 3); print "Remove of DAT file failed .. Remove manually\n\n" if ($verbose >= 1); } else { print "DONE\n" if ($verbose >= 3); } exit(1); } # we've found cruft like this after unzipping: # -rw---Sr-t 1 system nogroup 451615 Mar 8 04:32 names.dat # -rw------x 1 system nogroup 3224638 Mar 8 04:32 scan.dat # clean.dat system("chmod 644 $datdir/*.dat") == 0 or die "Chmod 644 of .dat files in $datdir failed\n"; print "\tSUCCESS\n" if ($verbose >= 3); } $cmd = "$rm -f $dat_file_name"; if ($dry_run) { print "Would execute $cmd\n"; } else { print("Cleaning up DAT file ..\t\t\t") if ($verbose >= 3); $res = system("cd $datdir; $cmd > /dev/null 2>&1"); if ($res != 0) { print "FAIL\n\n" if ($verbose >= 3); print "Remove of DAT file failed .. Remove manually\n\n" if ($verbose >= 1); exit(2); } print "DONE\n" if ($verbose >= 3); } print "DAT updated from $cur_dat_vers to $rem_dat_vers ..\t\tSUCCESSFUL\n" if ($verbose >= 2); if ($verbose >= 2) { open (README, "$datdir/readme.txt"); my $tmp; read (README, $tmp, 50000); if ($tmp =~ /={40}.*?(\w{4} Emergency Dat Release.*?)[^=]{2}={40}/s) { print "\n************* EMERGENCY DAT RELEASE **************\n\n"; print "$1"; print "\n************* EMERGENCY DAT RELEASE **************\n"; } close (README); } } else { print ("Your DAT file is up to date ..\t\tOK\n") if ($verbose == 3); print ("Your DAT file is up to date ..\t\tOK ($cur_dat_vers)\n") if ($verbose >= 4); } if ($verbose >= 1) { my $eicar_file = "$datdir/eicar.com"; if ($dry_run) { print "Would create $eicar_file\n"; } else { print "\nPerforming a scan test ..\t\t" if ($verbose >= 3); open (VIRUS,"> $eicar_file"); print VIRUS "X5O!P%\@AP[4\\PZX54(P^)7CC)7}\$EICAR-STANDARD-ANTIVIRUS-TEST-FILE!\$H+H*"; close VIRUS; } $cmd = "$binary $eicar_file"; if ($dry_run) { print "Would run $cmd\n"; } else { $res = 1; open (VIRUS, "$cmd 2>&1 |"); while () { $res = 0 if (/EICAR/); } close (VIRUS); if ($res == 1) { print "FAIL\n\n" if ($verbose >= 3); print "************** SYSTEM IS NOT SECURE **************\n"; print "*** ***\n"; print "*** Standard EICAR test fail ***\n"; print "*** Your system is not virus secure ***\n"; print "*** ***\n"; print "************** SYSTEM IS NOT SECURE **************\n\n"; } else { print "SUCCESSFUL\n" if ($verbose >= 3); } } $cmd = "$rm -f $eicar_file"; if ($dry_run) { print "Would run $cmd\n"; } else { system("$cmd"); } } __END__ =head1 NAME naiupdt - NAI VirusScan DAT file auto updater =head1 SYNOPSIS naiupdt [options] Options: --dry-run don't actually get real stuff done, just print what we'd be up to --help brief help message --verbose set verbosity level =head1 OPTIONS =over 8 =item B<--datdir=directory|-d=directory> Where to look for datfiles and the uvscan(1) binary. Defaults to /usr/local/uvscan. =item B<--dry-run|-n> Just print what commands we would be running, but do not execute them. Useful for debugging. =item B<--help|-h> Print a brief help message and exits. =item B<--path|-p> Wether to use the $PATH to look for additional binaries: 'uvscan', 'unzip', 'md5sum', 'wget', 'tar', 'rm'. If --path is not set, hardcoded default locations are used for these utilities. =item B<--verbose=n|-v=n> Set verbosity level to tune output. Useful when running this script as a cron(1) job. Supported levels: --verbose=0 No output at all! Not even on the most serious warning, not recommended --verbose=1 Serious warnings --verbose=2 On update only --verbose=3 Verbose output --verbose=4 Even more --verbose=5 Debug output, for checking one's installation The default verbosity is set to 5. =back =head1 DESCRIPTION naiupdt will check the version of the locally installed NAI DAT/Engine file. Then find the version on the NAI FTP site. If there is a newer version available, it will retrieve it and install it. Generally, the files VALIDATE.EXE, clean.dat, file_id.diz, internet.dat, names.dat packing.lst, pkgdesc.ini, readme.txt, reseller.txt, scan.dat are updated in the dat directory. A lot of checks are done to make very sure you will always have a working DAT file. Including a test with the Eicar test virus. =head1 SEE ALSO http://www.amavis.org/contrib/ for a bunch of other scripts with similar functionality. =head1 VERSION Under version control at perlharbor.uvt.nl, in $URL: https://pong.uvt.nl/its-unix/group/mailhost/usr/local/bin/naiupdt $ , version $Id: naiupdt 4306 2004-03-09 09:23:14Z joostvb $. Beware: this script is based upon Bas's version 0.3 . Bas has released a 0.4, february 2003. =head1 COPYRIGHT Copyright (C) 2002 Bas Rijniersce, bas@brijn.nu This program is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation on http://www.gnu.org/copyleft/gpl.html ; either version 2 of the License, or (at your option) any later version. There is NO WARRANTY. =head1 AUTHOR Bas Rijniersce, bas@brijn.nu. Minor contributions by soli@soli.ca. Manpage, commandline options and many other improvements added by Joost van Baal . Bas's version is at http://www.brijn.nu/Programming/index.html . =cut