#!/usr/bin/perl
#
# **** README ****
#
# 14 April 2004
# Note from Jules: This looks awfully complicated, but it is because the
# old version of the --update flag didn't work in bdc.
# This code detects what version of bdc you are running
# and uses the appropriate code for each version.
#
# YOU DON'T HAVE TO TWEAK IT!!! Please don't modify this file.
#
#####################################################################
# Since the --update function of BitDefender doesn't work, I wrote
# this little script to update "manually" via cron task the plugin
# directory from:
# ftp://ftp.bitdefender.com/pub/updates/bitdefender_v7/plugins/
#
# This software is free, use it at your own risk
# Not me nor my company are related in any way to bitdefender's company
#
# This is a VERY basic implementation and it may be implemented ad improved
# in many ways.
#
# Since it seems to me that there is no way of knowing wich files were changed,
# the script connects to bitdefender's ftp site and downloads all the plugins, then
# diffs the files whith the list of known viruses and outputs to the log the newly added
# virus names.
#
#
# HOW TO USE
# Set Up all the variabiles (if everything is standard no chages are required
# Put this script where you may prefer (I keep mine within my bdc/shared dir)
# Add a new cron job to launch it once a day (it may be executed with enough
# privileges to have writing permissions on the bdc/shared/Plugins directory
# and to the log file path.
#
# Any suggestion is welcome.
#
# Feel free to make any change you may need (I'm not a perl guru) but PLEASE
# if you make any change just mail a copy of the chenges to me at
#
# alex@skynet-srl.com
# www.skynet-srl.com
#
# Thank you
#
# Alessandro Bianchi
#
# Borgomanero Italy 06.09.03
############################################################################
#
# C H A N G E H I S T O R Y
#
# 06-09-03 First pubblic release, very basic implementation and poor commenting
# 06-10-03 Added logging functions and diff
# 06-11-03 Added the removed or added labels to the log
# 06-13-03 Added smtp support
# 06-16-03 Fixed a bug in the report that skipped the first $ignore lines
# 06-26-03 Changed the strategy: now download the cumulative.zip file, then
# decompress it in a temporary directory and install upgrade file.
# ZIP files managment is obtained using the Archive-Zip-1.0.5 perl module.
# Get it from www.CPAN.org or from
# http://search.cpan.org/CPAN/authors/id/N/NE/NEDKONZ/Archive-Zip-1.05.tar.gz
# 06-26-03 Added locking code from MailScanner by Julian Field
# 06-27-03 Added rotation function so that you can determine to download only
# the daily file and at a certain interval do a cumulative update.
# Even faster !
# WARNING: according to BitDefender's web site, the cumulative.zip file
# is made available each monday and all the daily.zip are cumulative in
# a week period. So I suggest to:
# a) run bitdefenderUpdater the first time to let it do cumulative upd;
# b) edit /etc/bitdefenderRotation to have the 0 period each monday
# c) run the script once a day to have your definition updated
#
# ROTATION EXAMPLE
# ================
# I set rotation period to 6 that means 7 days (from 0 to 6)
# I get sure to have the number of the weekday accordingly in /etc/...
# Check the log to be sure everything is OK
#
# 06-30-03 Added auto creation of folders needed for download and expansion
# of .zip files
# 07-01-03 Added smart detection of the updates so that the files need to be
# newer than the last update done. Detection is done "on line" so that
# no download takes place if the remote file is older (or equal) to
# last downloaded file. Rotation status is only updated if update of
# the definitions take place
# 07-07-03 Better error handling and minor bug fix
#
# 15-10-03 Bitdefender changed installation directory from /usr/local/bd7 to
# /opt/bdc so cahnged the variabile accordingly
#
# 21-10-03 With version 7.0 of bitdefender the autoupdate function has been
# fixed, so, added the option to use bdc built-in function and also
# added to bitdefende report at the end of the update report to "see"
# the number of viruses includung families otherwise seen as one virus only
#
############################################################################
use POSIX;
use Net::FTP;
use File::Basename;
use IO::File;
use Archive::Zip qw(:ERROR_CODES);
use Sys::Syslog;
my $PackageDir = shift || "/opt/bdc";
# This is the maximum time allowed for the bdc --update command to run.
my $MaximumTime = 60*20; # 20 minutes
# my @ext = qw(.xmd .cvd .ivd .rvd .dat); # extensions used by definitions
my $ftpSite = "ftp.bitdefender.com"; # address of ftp update site
my $ftpDir = "/pub/updates/bitdefender_v7/"; # remote ftp dir containing updates
my $dailyUpdateFile = "daily.zip"; # name of the daily update file, usually "daily.zip"
my $cumulativeUpdateFile = "cumulative.zip"; # name of the cumulative update file, usually "cumulative.zip"
my $ftpUpdateFile = $cumulativeUpdateFile; # update file to be downloaded
my $ftpLogin ="anonymous"; # ftp login to be used (usual anonymous
my $ftpPassword = "me\@here..com"; # ftp password to be sent (usually email address)
my $bitDefenderPath= "$PackageDir/"; # local path to bitdefender
my $destDir = $bitDefenderPath ."Plugins/"; # bitdefender's plugins path
my $downTemporaryFolder = $bitDefenderPath . "test/"; # where to store the zipped update file
my $expandTempFolder = $bitDefenderPath . "temp/"; # where to expand the zipped file
$loggingEnabled = 1; # set to 0 to disable logging
$verbose =0; # log verbose information about files that get updated set to 0 to avoid it
$logFile= "/var/log/bitdefender_updater.log"; # full path to the log file
$rotationFile = "/etc/bitdefenderRotation"; # contains the current status
$rotationPeriod = 6; # number in days before doing cumulative seto to 0 to get always cumulative
my $bitDefBinary = "bdc"; # name of the bitdefender's binary
my $beforeFile = "beforeupdate.txt"; # list of viruses before update
my $afterFile = "afterupdate.txt"; # list of viruses after update
my $ignoreLines = 5; # lines to be ignored in total virus defs
my $ftpPassive = 1; # 1= use passive ftp - 0= normal
my $timeStampFile = "/etc/bdUpdater.data"; # where to store the modification time of the file
####### MAIL VARIABILES ###############
$useSMTP =0 ; # 1= send mail - 0= don't send mail
$smtpFrom = 'Bitdefender console'; # Frm , appears in the mail message
$smtpTo= 'postmaster'; # address who receives the report (p.e. postmaster)
$smtpSubject = "Bitdefender's virus definition update process"; # report's subject
#@smtpMessages = ""; # message holder
$sendmailPath = "/usr/sbin/sendmail"; # full sendmail path
######## LOGFILE SIZE LIMIT ##############
$logFileLimit = 5120; # logfile limit size in bytes - 0 = no limit - 5120 = 5 kb
#JKF This is now calculated $useBDCUpdate = 0; # select the method to use for updating
# 1 = user bdc --update method,
# 0 = download file, unzip and test it
#######################################################################
#
# NO CHANGES ABOVE THIS LINE REQUIRED
#
#######################################################################
my $addedMarker = ">";
my $removedMarker = "<";
my $addedLabel = "added\t";
my $removedLabel = "removed\t";
# JKF This should always be over-written later, see JKF comments
$bdcBinary = $bitDefenderPath . $bitDefBinary ; # full path to binary
$LockFile = "/tmp/BitDefenderBusy.lock";
$LOCK_SH = 1;
$LOCK_EX = 2;
$LOCK_NB = 4;
$LOCK_UN = 8;
eval { Sys::Syslog::setlogsock('unix'); }; # This may fail!
Sys::Syslog::openlog("BitDefender-autoupdate", 'pid, nowait', 'mail');
&checkLogSize;
&updateLog("-----> Starting update...");
if ($PackageDir eq "" || ! -e $PackageDir) {
&updateLog("Installation dir \"$PackageDir\" does not exist!");
exit;
}
# JKF Set $useBDCUpdate according to which version is installed
if (-e $bitDefenderPath . "shared/$bitDefBinary") {
# JKF Old version. --update is broken and bdc is in "shared" directory
$useBDCUpdate = 0;
$bdcBinary = $bitDefenderPath . "shared/$bitDefBinary";
}
if (-e "$bitDefenderPath$bitDefBinary") {
# JKF New version. --update works and bdc is in main package directory
$useBDCUpdate = 1;
$bdcBinary = $bitDefenderPath . $bitDefBinary;
}
# calcolo il numero di virus su cui siamo inizialmente protetti e restituisco il numero direttamente nel log
my $bitDCmd = $bdcBinary . " --vlist";
my $origFile = $bitDefenderPath . $beforeFile;
system "$bitDCmd > $origFile ";
&countViruses($origFile);
if ( $useBDCUpdate == 1)
{
my $bitDCmd = $bdcBinary . " --update";
&updateLog ( "Starting update using BDC built-in function...");
&LockBitDefender; # lock it...
Sys::Syslog::syslog('info', 'BitDefender starting update');
eval {
alarm $MaximumTime;
system $bitDCmd;
&UnlockBitDefender;
alarm 0;
};
if ($@) {
if ($@ =~ /timeout/i) {
# We timed out!
&UnlockBitDefender;
&updateLog("WARNING BitDefender update timed out");
Sys::Syslog::syslog('warn', 'BitDefender update timed out');
alarm 0;
}
} else {
alarm 0;
&updateLog("BitDefender update completed");
Sys::Syslog::syslog('info', 'BitDefender updated');
}
#system ("$bitDCmd"); # update
#&UnlockBitDefender; # .. then unlock it
}
else
{
&determineRotation;
$ftp = Net::FTP->new($ftpSite, Timeout => 30, Debug => 0, Passive =>$ftpPassive) || &updateLog( "Can't connect to ftp server.");
if ( ! defined $ftp)
{
# something went wrong with connection, so abort
&updateLog ( "Cannot connect to ftp site, $@");
&sendMail; # notify the administrator
exit 0;
}
$ftp->login($ftpLogin, $ftpPassword) || &updateLog( "Can't login to ftp server.");
$ftp->cwd($ftpDir) || &updateLog ("Path $ftpDir not found on ftp server.\n");
$ftp->binary();
# carico i files nella directory di destinazione
#foreach $plugin ($ftp->ls())
#{
# ($name, $dir, $extl) = fileparse ($plugin , @ext);
# estraggo il nome del file corrente
#$localFile = $destDir . $name . $extl;
#$ftp->get($plugin,$localFile);
&dirCheck ( $downTemporaryFolder);
$zipName = $downTemporaryFolder . $ftpUpdateFile;
my $modTime = $ftp->mdtm ( $ftpUpdateFile);
# now check that modTime is greater than last done update time
if ( &determineNewerUpdate ( $modTime)== 0 )
{
$ftp->quit;
&updateLog ( "Database is Up to Date - No update is required\n");
Sys::Syslog::syslog('info', 'Bitdefender update not needed');
goto ENDING;
}
$ftp->get ( $ftpUpdateFile, $zipName);
if ( $verbose ==1 )
{
updateLog ("Update file downloaded: $zipName");
}
# chiudo la connessione
$ftp->quit;
&LockBitDefender; # lock it...
&unCompressAndInstall(); # ...install..
&UnlockBitDefender; # .. then unlock it
}
my $destFile = $bitDefenderPath . $afterFile;
# calcolo il numero di virus su cui siamo protetti e restituisco il numero direttamente nel log
system "$bitDCmd > $destFile ";
&updateLog ("Following the changes:");
COMPARE:
my $newsFile = $bitDefenderPath . "news.txt";
$afterFile = $bitDefenderPath . $afterFile;
$beforeFile = $bitDefenderPath . $beforeFile;
system "diff $beforeFile $afterFile > $newsFile";
# include bdc report fr reporting families
my $bitDCmd = $bdcBinary . " --info";
system "$bitDCmd >> $newsFile ";
# get the file and print it in the log...
my $fh = new IO::File "< $newsFile" || &updateLog( "no news file found!");
my @lines = $fh->getlines;
$fh->close;
my $lines = @lines;
if ( $lines > 9)
{
for ($i=0; $i<=$lines; $i++)
{
# aggiungo le diciture added o removed
$lines[$i] =~ s/$addedMarker/$addedLabel/g;
$lines[$i] =~ s/$removedMarker/$removedLabel/g;
&updateLog ( $lines[$i] );
}
}
else
{
&updateLog ( "No new definitions found...");
Sys::Syslog::syslog('info', 'Bitdefender update not needed');
$useSMTP = 0; # avoid mail
}
&countViruses($destFile);
if ( $useBDCUpdate == 0 )
{
&determineRotation (1); # update rotation status
}
&sendMail;
ENDING:
&updateLog("------> Update was succesful...");
exit 0 ;
##########################################################################
#
# Aggiorno il log se il parametro $loggingEnabled = 1
# e se Ë stato definito il parametro $logFile
#
#
# updateLog ( $message );
#
##########################################################################
sub updateLog
{
if ($useSMTP == 1)
{
push @smtpMessages, $_[0];
}
if (!defined $loggingEnabled )
{
return;
}
if ($loggingEnabled != 1)
{
return;
}
$tab = "\t"; # tab for log output delimiter
# aggiorno il log
use POSIX qw(strftime);
# $now_string = strftime "%a %b %e %H:%M:%S %Y", gmtime;
$now_string = strftime "%a %b %e %H:%M:%S %Y", localtime;
$fh = new IO::File ">> $logFile";
if (defined $fh)
{
# scrivo i dati del log
print $fh $now_string, , $tab, $_[0] ,"\n";
$fh->close ;
undef $fh;
}
}
sub countViruses
{
# apro il file e conto il numero dei virus protetti
$fh = new IO::File "< $_[0]" || &updateLog( "no input file found!");
@lines = $fh->getlines;
$fh->close;
my $lines = @lines - $ignoreLines;
&updateLog ("*** You're now protected against $lines viruses ...");
}
##################################################################
# If enabled send mail to the desired address(es) using sendmail
#
# 13-06-03
##################################################################
sub sendMail
{
if ($useSMTP != 1)
{
return;
}
open (SENDMAIL, "| $sendmailPath -t");
print SENDMAIL "TO: $smtpTo\n";
print SENDMAIL "Subject: $smtpSubject\n";
foreach $msg (@smtpMessages)
{
print SENDMAIL "$msg\n";
}
close (SENDMAIL);
}
#####################################################################
# Check log size and il exceeds the defined size resets it to 0 length
# if logSize == 0 no checking takes place
######################################################################
sub checkLogSize
{
if ( $logFileLimit == 0)
{
return;
}
# get the current file size
if ( -s $logFile > $logFileLimit )
{
# truncate it to 0 bytes lenght before starting
$fh = new IO::File "> $logFile";
$fh->close;
}
}
#####################################################################
#
# Uncompress downloaded file and upgrade data, then delete
# temporary file.
# This routine requires Archive::Zip: Get it from CPAN
#
#####################################################################
sub unCompressAndInstall
{
my $zip = Archive::Zip->new();
my $status = $zip->read( $zipName );
# abort if something goes wrong but
# unlock the file first to re-enable mail processing
if ($status != AZ_OK)
{
&updateLog("Read of $zipName failed\n") ;
&UnlockBitDefender;
exit 0;
}
my $finalName ="";
my $removed= "";
&dirCheck ( $expandTempFolder);
my @content = $zip->memberNames();
foreach my $memberName (@content)
{
if ( $verbose == 1)
{
&updateLog ( "Extracting $memberName\n");
}
$finalName = reverse $memberName ;
$removed = chop $finalName;
if ($removed eq ".")
{
$finalName = $expandTempFolder. reverse ($finalName);
}
else
{
$finalName = $expandTempFolder. $memberName;
}
$status = $zip->extractMemberWithoutPaths($memberName , $finalName);
# : $zip->extractMember($memberName);
if ($status != AZ_OK )
{
&updateLog ("Extracting $memberName from $zipName failed\n") ;
&UnlockBitDefender;
&sendMail; # notify the administrator
exit 0;
}
}
# sposto i files nella directory di destinazione
# using -p preserve and -u upgrade
system ("cp -pu $expandTempFolder* $destDir");
# svuoto la directory temporanea
system ("rm -f $expandTempFolder*");
# cancello il file da cui ho eseguito l'aggiornamento
# system "rm -f $zipName ";
}
#########################################################################
#
# Locking code from MailScanner, just copied and adapted
#
#
######################################################################
sub LockBitDefender {
open(LOCK, ">$LockFile") or return;
flock(LOCK, $LOCK_EX);
print LOCK "Locked for updating BitDefender definitions by $$\n";
# &updateLog ( "Locked for updating BitDefender definitions by $$\n");
}
sub UnlockBitDefender {
print LOCK "Unlocked after updating BitDefender definitions by $$\n";
unlink $LockFile;
flock(LOCK, $LOCK_UN);
close LOCK;
# &updateLog ( "Unlocked after updating BitDefender definitions by $$\n");
}
########################################################################
#
# Determine rotation status and current filename
# if 1 is passed as argument, then update the rotation situation
# &determineRotation() <--- don't update rotation status
# &determineRotation(1)<---- UPDATE ROTATION STATUS
#
########################################################################
sub determineRotation
{
#return if $rotationPeriod == 0;
my $update = 0;
my $currentStatus =0;
if (defined $_[0] )
{
$update = $_[0];
}
# get the current rotation status from the status file
my $fh = new IO::File "< $rotationFile";
if (! defined $fh)
{
# initialize status file
my $fh = new IO::File "> $rotationFile";
print $fh $currentStatus;
$fh->close ;
undef $fh;
$ftpUpdateFile = $cumulativeUpdateFile; # do cumulative
}
else
{
$currentStatus = $fh->getline;
if ($currentStatus > $rotationPeriod)
{
$ftpUpdateFile = $cumulativeUpdateFile; # do cumulative
$currentStatus = 0;
}
else
{
$currentStatus += 1;
$ftpUpdateFile = $dailyUpdateFile; # do incremental
}
}
$fh->close ;
undef $fh;
if ($update == 1)
{
# update (overwrite) the file for next round
$fh = new IO::File "> $rotationFile";
print $fh $currentStatus;
$fh->close ;
&updateLog ( "current status is $currentStatus, rotation period is $rotationPeriod");
if ( $currentStatus > $rotationPeriod)
{
&updateLog ("*** NEXT UPDATE IS CUMULATIVE ***");
}
}
else
{
&updateLog ( "***\nNEW JOB\n***\nSelecting $ftpUpdateFile for update");
}
}
##################################################################
#
# Check if the required directory exists an is empty
# if doesn't exist create it
# if exists get sure it is empty
#
###################################################################
sub dirCheck
{
my $dir; # dir to check on
#try to open it
if (not opendir $dir, $_[0])
{
#directory exists, so proceed
mkdir $_[0];
&updateLog( "Directory $_[0] has been created for you\n");
}
else
{
closedir $dir;
# &updateLog ("Directory $_[0] exists\n");
}
}
########################################################################
#
# Determine if current update is newer than latest done update
# the function accepts one parameter that is the time to be checked
# returns true if is newer of last update and false otherwise
# &determineNewerUpdate( $timeToBe Checked)
#
########################################################################
sub determineNewerUpdate
{
my $lastUpdate = 0;
my $timeToCheck = $_[0];
# get the last update date and return true or false according to it
my $fh = new IO::File "< $timeStampFile";
if (! defined $fh)
{
# initialize update file
my $fh = new IO::File "> $timeStampFile";
print $fh $lastUpdate;
$fh->close ;
undef $fh;
return 1;
}
else
{
$lastUpdate = $fh->getline;
$fh->close ;
if ($timeToCheck > $lastUpdate )
{
# update the file and proceed
$fh = new IO::File "> $timeStampFile";
print $fh $timeToCheck;
$fh->close ;
return 1;
}
}
return 0;
}
syntax highlighted by Code2HTML, v. 0.9.1