#!/usr/bin/perl # # MailScanner - SMTP E-Mail Virus Scanner # Copyright (C) 2002 Julian Field # # $Id: f-prot-autoupdate 1958 2003-09-26 08:42:17Z jkf $ # # 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; either version 2 of the License, or # (at your option) any later version. # # This program is distributed in the hope that it will be useful, # but WITHOUT ANY WARRANTY; without even the implied warranty of # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # GNU General Public License for more details. # # You should have received a copy of the GNU General Public License # along with this program; if not, write to the Free Software # Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA # # The author, Julian Field, can be contacted by email at # Jules@JulianField.net # or by paper mail at # Julian Field # Dept of Electronics & Computer Science # University of Southampton # Southampton # SO17 1BJ # United Kingdom # use Sys::Syslog; use IO::File; # Stop syslogd from needing external access (or -r) eval { Sys::Syslog::setlogsock('unix'); }; $PackageDir = shift || "/usr/local/f-prot"; #################################### # # You can set your HTTP proxy server / web-cache here if you want to, # otherwise you will have to set it in the environment or wget's # startup file. # If you don't want to specify it here, comment out the next line. # #$HttpProxy = 'www-cache.soton.ac.uk:3128'; #$FtpProxy = ''; # #################################### $FProtRoot = $PackageDir; # N.B. TempDir DIRECTORY WILL BE CLEARED so # you *really* don't want to share it with # anything else. $TempDir = "$FProtRoot/tmp"; $DefDir = $FProtRoot; #$FallbackServer = 'http://updates.f-prot.com/files/'; $FallbackServer = 'ftp://ftp.f-prot.com/pub/'; $LockFile = "/tmp/FProtBusy.lock"; $LOCK_SH = 1; $LOCK_EX = 2; $LOCK_NB = 4; $LOCK_UN = 8; $cron = 0; $quiet = 0; $updated = 0; $FProtIsLocked = 0; $HaveDownloadedSign = 0; $TmpFile = "tmp-web"; $HttpReturn = 10; @FilesToCopy = (); $File = ""; BailOut("Installation dir \"$PackageDir\" does not exist!") unless -e $PackageDir; # # Check command-line parameters # foreach (@ARGV) { if (/cron/i) { $cron = 1; } elsif (/quiet/i) { $quiet = 1; } else { BailOut("Invalid command-line option \"$_\""); } } # If they have specified an http/ftp proxy server / web-cache, then use it $ENV{'http_proxy'} = $HttpProxy if $HttpProxy; $ENV{'ftp_proxy'} = $FtpProxy if $FtpProxy; # # Check if TempDir exists and is a directory # stat($TempDir); if (-e _) { BailOut("$TempDir needs to be a directory") if ! -d _; } else { mkdir $TempDir, 0700 or BailOut("Could not create $TempDir directory, $!"); } # Check file permissions of TempDir are correct chmod 0700, $TempDir or BailOut("Could not set perms of $TempDir. Check you own it"); CleanTempDir(); # Clean up the contents of TempDir # # Check we can find all the external programs we need # for $program (qw/cp grep head wget unzip/) { $result = system("$program --version < /dev/null > /dev/null 2>&1"); BailOut("Could not find $program on your path. Please install it " . "or fix your path") if $result==127; } # # Download update information from the update server # $result = system("wget --output-document=$TempDir/$TmpFile --tries=3 " . "'http://updates.f-prot.com/cgi-bin/check-updates?" . "protocol=1&run_as=check_updates' > /dev/null 2>&1"); BailOut("wget command failed. You need the latest version installed, $!") if $result==127; BailOut("Updates download from http://updates.f-prot.com failed. Suspect server could not be reached, $!") if $result!=0; # Get HTTP return value from checking for updates open(TEMPFILE, "$TempDir/$TmpFile") or BailOut("Could not read temp file $TmpFile, $!"); $HttpReturn = ; chomp $HttpReturn; $HttpReturn =~ s/\s*$//g; if ($HttpReturn!=2) { BailOut("Invalid parameters used in http URL, exiting, $!") if $HttpReturn==3; BailOut("Invalid protocol used in http URL, exiting, $!") if $HttpReturn==4; BailOut("Server error on remote machine, exiting, $!") if $HttpReturn==5; BailOut("Unknown error while downloading update information, " . "do you need to specify your HTTP/FTP proxy / web-cache at " . "the top of this script? Exiting, $!"); } # # Read the file once to pull out the ftp URL of the update server # while() { chomp; next unless s/^S://; # Delete trailing newlines and stuff like that s/\s*$//g; $Server = $_; } close(TEMPFILE); print STDERR "FTP address for retrieving files is $Server\n" unless $quiet || $cron; # # Lock out all other users of F-Prot until update is complete. # # Timeout prevention $SIG{ALRM} = sub { die "timeout"}; # # Now read and compare checksums of the files on the update server and # the local def files. # eval { alarm 600; open(TEMPFILE, "$TempDir/$TmpFile"); while() { chomp; s/\s*$//g; # Delete trailing whitespace (^M and such like) next unless /^C/; next unless /DEF=/; s/^[^:]*://; # Delete everything up to and including ":" ($FileToCheck, $RemoteChecksum) = split(/=/, $_, 2); $FileChecksum = Checksum("$DefDir/$FileToCheck"); BailOut("$FProtRoot/checksum was not found. It should be in your " . "F-Prot package, $!") if $FileChecksum==127; # Current file different from remote file? if ($FileChecksum ne $RemoteChecksum) { print STDERR "F-Prot signature file update script\n" unless $updated || $quiet; print STDERR "There is a new version of $FileToCheck, starting download.\n" unless $quiet; $updated = 1; # Download it from the server DownloadFile($Server, $FileToCheck); # Check we downloaded the file we wanted $FileChecksum = Checksum("$TempDir/$FileToCheck"); if ($FileChecksum eq $RemoteChecksum) { # Copy file from temp dir to f-prot dir #system("cp $TempDir/$FileToCheck $FProtRoot"); push @FilesToCopy, $FileToCheck; print STDERR "Updated $FileToCheck.\n" unless $quiet; } else { # If not, then try fallback server instead DownloadFile($FallbackServer, $FileToCheck); # If that fails too, then error $FileChecksum = Checksum("$TempDir/$FileToCheck"); if ($FileChecksum eq $RemoteChecksum) { # Copy file from temp dir to f-prot dir #system("cp $TempDir/$FileToCheck $FProtRoot"); push @FilesToCopy, $FileToCheck; print STDERR "Updated $FileToCheck from fallback server.\n" unless $quiet; } else { BailOut("Could not find correct version of $FileToCheck, exiting, $!"); } } } else { print STDERR "File $FileToCheck is already up to date.\n" unless $quiet || $cron; } } if ($updated) { print STDERR "Update completed.\n" unless $quiet; } else { print STDERR "Nothing to be done.\n" unless $cron; } # Clean up and exit. alarm 0; }; if ($@) { if ($@ =~ /timeout/) { # We timed out! alarm 0; } } else { alarm 0; # Copy all the temp files into the real F-Prot direcrtory &LockFProt(); foreach $File (@FilesToCopy) { system("cp $TempDir/$File $FProtRoot"); } Sys::Syslog::openlog("F-Prot autoupdate", 'pid, nowait', 'mail'); Sys::Syslog::syslog('info', $updated?"F-Prot successfully updated.": "F-Prot did not need updating."); } CleanTempDir(); &UnlockFProt(); Sys::Syslog::closelog(); exit 0; ######################################################################### # # Clean up the contents of TempDir # sub CleanTempDir { opendir(TEMPDIR, $TempDir) or BailOut("Could not read directory $TempDir, $!"); foreach (readdir(TEMPDIR)) { next if /^\.\.?$/; # Skip . and .. unlink "$TempDir/$_"; } closedir(TEMPDIR); } # Find the checksum of a given filename sub Checksum { my($Filename) = @_; my($FileChecksum, $Result); # Catch case where file does not exist return 0 unless -f $Filename; if (-x "$FProtRoot/checksum") { $FileChecksum = `$FProtRoot/checksum $Filename 0`; $Result = $?; chomp $FileChecksum; $FileChecksum =~ s/^[^=]*=//; # Chop off up to and including "=" BailOut("$FProtRoot/checksum was not found. It should be in your " . "F-Prot package, $!") if $Result==127; BailOut("Unknown fatal error calling \"checksum\", exiting, $!") if $Result; return $FileChecksum; } else { return create_compare_string_for_defs($Filename); } } # Perl code for new version of checksum sub create_compare_string_for_defs { my ($filename) = @_; if (my $file = new IO::File $filename) { my $buff = ''; return undef if ($file->read($buff, 32) != 32); # Get file size my @fstat = $file->stat(); my $fsize = $fstat[7]; $file->close(); return uc( unpack('H*', $buff) . sprintf("%8.8X", $fsize) ); } return undef; } sub DownloadFile { my($host, $file) = @_; my($result); if ($file =~ /^SIGN/) { if (!$HaveDownloadedSign) { $HaveDownloadedSign = 1; chdir $TempDir; Fetch($host, 'fp-def.zip'); print STDERR "Download completed.\n" unless $quiet; $result = system("unzip -o fp-def.zip /dev/null 2>&1"); BailOut("Fatal error while unzipping fp-def.zip, $!") if ($result>>8); } } else { chdir $TempDir; Fetch($host, 'macrdef2.zip'); print STDERR "Download completed.\n" unless $quiet; $result = system("unzip -o macrdef2.zip /dev/null 2>&1"); BailOut("Fatal error while unzipping macrdef2.zip, $!") if ($result>>8); } } sub Fetch { my($ip, $filename) = @_; my($r); $r = system("wget --passive-ftp --tries=3 $ip$filename > /dev/null 2>&1"); if ($r>>8) { # Download failed so try fallback server BailOut("Download of $ip$filename failed, exiting, $!") if $ip eq $FallbackServer; Fetch($FallbackServer, $filename); } } sub BailOut { &UnlockFProt(); Sys::Syslog::openlog("F-Prot autoupdate", 'pid, nowait', 'mail'); Sys::Syslog::syslog('err', @_); Sys::Syslog::closelog(); warn "@_\n"; chdir $FProtRoot or die "Cannot cd $FProtRoot, $!"; exit 1; } sub LockFProt { open(LOCK, ">$LockFile") or return; flock(LOCK, $LOCK_EX); print LOCK "Locked for updating F-Prot virus files by $$\n"; $FProtIsLocked = 1; } sub UnlockFProt { return unless $FProtIsLocked; print LOCK "Unlocked after updating F-Prot virus files by $$\n"; unlink $LockFile; flock(LOCK, $LOCK_UN); close LOCK; }