4706 lines
165 KiB
Perl
Executable File
4706 lines
165 KiB
Perl
Executable File
#!/usr/bin/env perl
|
|
#-----------------------------------------------------------------------------
|
|
# Dr.Web Update Script
|
|
# $Id: fbb3ca1734c5e75ae3008acef02c2f1032ac44cf $
|
|
#-----------------------------------------------------------------------------
|
|
# Copyright 2012, Igor Daniloff.
|
|
#
|
|
# Following source code is the property of Doctor Web, Ltd.
|
|
# Use is subject to license terms.
|
|
# The reproduction in any form without written permission of
|
|
# Doctor Web, Ltd. and proper attribution is strongly prohibited.
|
|
#
|
|
BEGIN {
|
|
unless($^W) {
|
|
my @cmd = ('/usr/bin/env', 'perl', '-w', $0, @ARGV);
|
|
my $cmd_as_str = join ' ',
|
|
map{quotemeta($_)}
|
|
@cmd;
|
|
exec @cmd
|
|
or die "Can not exec command $cmd_as_str ($!)";
|
|
}
|
|
}
|
|
|
|
use Data::Dumper;
|
|
use Time::Local ();
|
|
|
|
my $log_cache = [];
|
|
my $LogLevel = 5;
|
|
my $syslog = 0;
|
|
my %Levels;
|
|
BEGIN {
|
|
|
|
%Levels = (
|
|
quiet => [ 0, 'Q' ],
|
|
error => [ 1, 'E' ],
|
|
warning => [ 2, 'W' ],
|
|
info => [ 3, 'I' ],
|
|
verbose => [ 4, 'V' ],
|
|
debug => [ 5, 'D' ],
|
|
);
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): a level of the logged message
|
|
# IN (STRING): the logged message
|
|
# OUT: no
|
|
sub Log {
|
|
my ( $level, $msg ) = @_;
|
|
|
|
if ( $log_cache ) {
|
|
# delayed logging
|
|
push @{ $log_cache }, sub { &Log( $level, $msg ); };
|
|
return;
|
|
}
|
|
|
|
my $ctime = localtime;
|
|
if( !exists $Levels{ $level } ) {
|
|
&Log( 'warning', 'message with unknown loglevel( ' . $level . ' ) - please send bug report to support@drweb.com' );
|
|
$level = 'warning';
|
|
}
|
|
$msg =~ s/\012\015//g;
|
|
return unless $msg;
|
|
if( $LogLevel and $Levels{ $level }[ 0 ] <= $LogLevel ) {
|
|
if ( fileno( LOGHANDLE ) ) {
|
|
print LOGHANDLE "$ctime {$Levels{ $level }[ 1 ]} updater [$$] $msg\n";
|
|
} elsif ( $syslog ) {
|
|
syslog( [ 'warning', 'err', 'warning', 'notice', 'info', 'debug' ]->[ $Levels{ $level }[ 0 ] ], '%s', $msg );
|
|
} else {
|
|
print STDERR "$ctime {$Levels{ $level }[ 1 ]} [$$] $msg\n";
|
|
}
|
|
}
|
|
if('error' eq $level) {
|
|
my $message = "ERROR: Dr.Web Updater: $msg !\n";
|
|
print STDERR $message;
|
|
}
|
|
}
|
|
|
|
my %event = ( __WARN__ => 'warning', __DIE__ => 'error' );
|
|
foreach my $event ( keys %event ) {
|
|
$SIG{$event} = sub {
|
|
my ( $msg ) = @_;
|
|
foreach my $line ( split /\n+/, $msg ) {
|
|
chomp $line;
|
|
next if '' eq $line;
|
|
&Log( $event{ $event }, $line );
|
|
}
|
|
};
|
|
}
|
|
|
|
$SIG{CHLD} = sub {};
|
|
}
|
|
|
|
use strict;
|
|
use Cwd qw/ abs_path /;
|
|
use FileHandle;
|
|
use Fcntl qw/ :flock F_GETFL F_SETFL O_NONBLOCK O_RDONLY /;
|
|
use POSIX qw/ uname /;
|
|
use Socket;
|
|
use Sys::Syslog;
|
|
use IPC::Open2;
|
|
|
|
|
|
use constant {
|
|
EXIT_STATUS_OK => 0,
|
|
|
|
EXIT_STATUS_SYSTEM_ERROR => 10,
|
|
EXIT_STATUS_INTERRUPTED => 11,
|
|
EXIT_STATUS_LOCK_ERROR => 12,
|
|
|
|
EXIT_STATUS_INVALID_COMMAND_LINE => 20,
|
|
|
|
EXIT_STATUS_INVALID_CONFIG => 30,
|
|
|
|
EXIT_STATUS_INVALID_DRL => 40,
|
|
|
|
EXIT_STATUS_KEY_FILE_ACCESS_ERROR => 50,
|
|
EXIT_STATUS_INVALID_KEY => 51,
|
|
EXIT_STATUS_EXPIRED_KEY => 52,
|
|
|
|
EXIT_STATUS_RELOAD_DAEMON_ERROR => 60,
|
|
EXIT_STATUS_RELOAD_ICAPD_ERROR => 61,
|
|
EXIT_STATUS_RELOAD_MAILD_ERROR => 62,
|
|
EXIT_STATUS_RELOAD_LOTUSD_ERROR => 63,
|
|
|
|
EXIT_STATUS_INVALID_ADDR => 100,
|
|
EXIT_STATUS_RESOLVE_IP_ERROR => 101,
|
|
EXIT_STATUS_INVALID_RESPONSE => 102,
|
|
EXIT_STATUS_CONNECT_ERROR => 103,
|
|
EXIT_STATUS_SEND_REQUEST_ERROR => 104,
|
|
EXIT_STATUS_TIMEOUT_ERROR => 105,
|
|
EXIT_STATUS_CLOSED_CONNECTION => 106,
|
|
EXIT_STATUS_AUTOROZATION_ERROR => 107,
|
|
EXIT_STATUS_FILE_NOT_FOUND => 108,
|
|
EXIT_STATUS_INVALID_CHECKSUM => 109,
|
|
EXIT_STATUS_SERVER_BUSY => 110,
|
|
|
|
EXIT_STATUS_UPDATE_ERROR => 200,
|
|
};
|
|
|
|
|
|
########
|
|
# main #
|
|
########
|
|
# const
|
|
#my $DefaultURL = 'http://update.drweb.com/unix';
|
|
# variables
|
|
# LOGHANDLE - file handle for LogFile
|
|
# LOCKFD - file handle for LockFile
|
|
# environment
|
|
$ENV{PATH} .= ':/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin';
|
|
# signals
|
|
$SIG{PIPE} = $SIG{BUS} = $SIG{INT} = $SIG{QUIT} = $SIG{TERM} = \&ExitHandler;
|
|
# code
|
|
my $EXIT_CODE = &EXIT_STATUS_OK;
|
|
my $UPDATE_ERROR = &EXIT_STATUS_UPDATE_ERROR;
|
|
my $Conf = &LoadConfiguration( @ARGV );
|
|
&DumpConfiguration( $Conf );
|
|
|
|
srand;
|
|
my %reload;
|
|
my $Task;
|
|
my $SumCheker;
|
|
$SIG{__DIE__} = sub { &DrDie(&EXIT_STATUS_SYSTEM_ERROR, @_) };
|
|
|
|
&createNeededPathes($Conf);
|
|
|
|
if ($Conf->{ Commands }{ show_components }) {
|
|
my @components = &getPossibleComponents($Conf);
|
|
my $component_max_length = 0;
|
|
for (@components) {
|
|
$component_max_length = length($_) ? length($_) : $component_max_length
|
|
}
|
|
print STDERR "Available Components:\n";
|
|
for (sort @components) {
|
|
printf STDERR " %-${component_max_length}s%s\n",$_,(&isUpdatesFrozen($Conf, $_) ? ' (frozen)' : '');
|
|
}
|
|
exit &EXIT_STATUS_OK;
|
|
} elsif ($Conf->{ Commands }{ freeze_updates }) {
|
|
&freezeUpdates($Conf, $Conf->{ Commands }{ freeze_updates });
|
|
exit &EXIT_STATUS_OK;
|
|
} elsif ($Conf->{ Commands }{ unfreeze_updates }) {
|
|
&unfreezeUpdates($Conf, $Conf->{ Commands }{ unfreeze_updates });
|
|
exit &EXIT_STATUS_OK;
|
|
} elsif ($Conf->{ Commands }{ restore_backup }) {
|
|
&Lock($Conf);
|
|
my ($restore_info, $component);
|
|
foreach $component (@{$Conf->{ Commands }{ restore_backup }}) {
|
|
|
|
if($restore_info = &restoreBackup($Conf,$component)) {
|
|
&ViewBackupRestoreSummary( $Conf, $restore_info );
|
|
|
|
$reload{ '0.drwebd' } ||= &ReloadDaemon( $Conf, $restore_info );
|
|
$reload{ '1.maild' } ||= &ReloadMaild( $Conf, $restore_info );
|
|
$reload{ '2.icapd' } ||= &ReloadIcapd( $Conf, $restore_info );
|
|
$reload{ '3.lotusd' } ||= &ReloadLotusd( $Conf, $restore_info );
|
|
}
|
|
}
|
|
|
|
foreach my $daemon ( sort keys %reload ) {
|
|
my $reload_code = $reload{ $daemon };
|
|
&$reload_code() if $reload_code;
|
|
}
|
|
|
|
&Unlock( $Conf );
|
|
exit &EXIT_STATUS_OK;
|
|
}
|
|
|
|
my $updatemode = $Conf->{Self}{updatemode};
|
|
if ($updatemode eq 'default') {
|
|
|
|
if (-e $Conf->{ Self }{ eslockfile }) {
|
|
|
|
open(HANDLE, $Conf->{ Self }{ eslockfile })
|
|
or DrDie(&EXIT_STATUS_SYSTEM_ERROR, 'Can not open file \''.$Conf->{ Self }{ eslockfile }.'\' for locking: '.$!);
|
|
my $not_locked = flock(HANDLE, LOCK_EX|LOCK_NB);
|
|
close(HANDLE);
|
|
|
|
unless ($not_locked) {
|
|
my $exit_message = "Found lock file $Conf->{ Self }{ eslockfile }, exiting...";
|
|
warn $exit_message;
|
|
exit &EXIT_STATUS_LOCK_ERROR;
|
|
}
|
|
}
|
|
|
|
&Lock($Conf);
|
|
$SumCheker = &Crc32();
|
|
&loadLastUpdateCacheData($Conf);
|
|
&loadCacheData($Conf);
|
|
&ParseKey($Conf);
|
|
|
|
my ( $CoreURLs, $PluginURLs ) = &BuildURLs( $Conf );
|
|
|
|
DrDie(&EXIT_STATUS_INVALID_DRL, 'Nothing to update')
|
|
if scalar @$CoreURLs == 0 && scalar @$PluginURLs == 0;
|
|
|
|
my $expired_status = isNeedToGetNewKey($Conf);
|
|
if($expired_status) {
|
|
&Log("debug","Trying to update key file");
|
|
if(createNewKey($Conf,\$Task)) {
|
|
&Log("debug","Reparse key file");
|
|
&ParseKey($Conf);
|
|
ViewSummary($Conf,$Task);
|
|
} else {
|
|
warn("Can not update key file, trying to make update with old key file...");
|
|
if ($expired_status < 0) {
|
|
&DrDie(&EXIT_STATUS_EXPIRED_KEY,"Key is expired, update process has been canceled");
|
|
}
|
|
}
|
|
}
|
|
|
|
my $hooks = {
|
|
ft => { filter_task => \&FlyTrapPreprocessTask, sort_to_move => \&FlyTrapSortToMove }
|
|
};
|
|
for my $URLs ( $CoreURLs, map { $_ } @{$PluginURLs} ) {
|
|
undef( $Task );
|
|
$Conf->{Paths}{drl} = $Conf->{Self}{drldir} . '/' if $URLs ne $CoreURLs;
|
|
my $component = '';
|
|
my $urls = $URLs;
|
|
( $component, $urls ) = %$URLs if 'HASH' eq ref $URLs;
|
|
$component = lc($component eq '' ? 'drweb' : $component);
|
|
|
|
if (&isUpdatesFrozen($Conf, $component)) {
|
|
&frozenWarings($Conf, $component);
|
|
next;
|
|
}
|
|
|
|
$Conf->{ Self }{ hooks } = $hooks->{ component } || {};
|
|
next unless scalar @{$urls};
|
|
|
|
&Update( $Conf, $urls, \$Task, $component );
|
|
|
|
&ViewSummary( $Conf, $Task );
|
|
$reload{ '0.drwebd' } ||= &ReloadDaemon( $Conf, $Task );
|
|
$reload{ '1.maild' } ||= &ReloadMaild( $Conf, $Task );
|
|
$reload{ '2.icapd' } ||= &ReloadIcapd( $Conf, $Task );
|
|
$reload{ '3.lotusd' } ||= &ReloadLotusd( $Conf, $Task );
|
|
}
|
|
|
|
foreach my $daemon ( sort keys %reload ) {
|
|
my $reload_code = $reload{ $daemon };
|
|
&$reload_code() if $reload_code;
|
|
}
|
|
|
|
&saveLastUpdateCacheData();
|
|
&saveCacheData();
|
|
&Unlock( $Conf );
|
|
} elsif ($updatemode eq 'agent') {
|
|
&Lock($Conf);
|
|
my $component = 'agent';
|
|
|
|
if (&isUpdatesFrozen($Conf, $component)) {
|
|
&frozenWarings($Conf, $component);
|
|
next;
|
|
} else {
|
|
$SumCheker = \&MD5;
|
|
&loadLastUpdateCacheData($Conf);
|
|
&loadCacheData($Conf);
|
|
|
|
my $DrLstRef = &readfile($Conf->{Self}{lstfile});
|
|
&schedule_unlink($Conf->{Self}{lstfile});
|
|
|
|
my $get_files_callback = \&GetLocalFiles;
|
|
my $preinstall_callback = sub {
|
|
my ($Config,$LstTask) = @_;
|
|
|
|
local $| = 1;
|
|
print "\n";
|
|
&Log( 'info', 'Request has been sent.' );
|
|
&Log( 'info', 'Waiting answer...' );
|
|
<STDIN>;
|
|
&Log( 'info', 'Answer has been gotten!' );
|
|
};
|
|
|
|
my $StartTime = localtime;
|
|
|
|
unless (&UpdateSimple( $Conf, \$Task, $DrLstRef, $get_files_callback, $preinstall_callback, $component) ) {
|
|
$EXIT_CODE = &EXIT_STATUS_UPDATE_ERROR;
|
|
}
|
|
|
|
$Task->{process}{StartTime} = $StartTime;
|
|
$Task->{process}{EndTime} = localtime;
|
|
|
|
&ViewSummary( $Conf, $Task );
|
|
$reload{ '0.drwebd' } ||= &ReloadDaemon( $Conf, $Task );
|
|
$reload{ '1.maild' } ||= &ReloadMaild( $Conf, $Task );
|
|
$reload{ '2.icapd' } ||= &ReloadIcapd( $Conf, $Task );
|
|
$reload{ '3.lotusd' } ||= &ReloadLotusd( $Conf, $Task );
|
|
|
|
foreach my $daemon ( sort keys %reload ) {
|
|
my $reload_code = $reload{ $daemon };
|
|
&$reload_code() if $reload_code;
|
|
}
|
|
|
|
&saveLastUpdateCacheData();
|
|
&saveCacheData();
|
|
&cleanupAgentData( $Conf );
|
|
}
|
|
&Unlock( $Conf );
|
|
} elsif($updatemode eq 'help') {
|
|
&printHelpAndExit($Conf);
|
|
}else {
|
|
DrDie(&EXIT_STATUS_BAD_COMMAND_LINE, 'Unknown update mode: '.$Conf->{Self}{updatemode});
|
|
}
|
|
|
|
if($EXIT_CODE == &EXIT_STATUS_OK) {
|
|
#the update must be successful if we reach this point write there current date to indicate the success
|
|
&Log( 'info', 'Finish Success: '.&GetCurrDate());
|
|
} else {
|
|
&Log('warning', 'Errors occurred while updating!');
|
|
}
|
|
&Log('info', "Socket path is $Conf->{ Self }{ notifysocket }\n");
|
|
if( defined $Conf->{ Self }{ shouldnotify } && 'yes' eq lc $Conf->{ Self }{ shouldnotify } ) {
|
|
&WriteToSocket($Conf->{ Self }{ notifysocket }, "Success");
|
|
}
|
|
#should also write ok to the socket
|
|
|
|
&CloseLog();
|
|
exit $EXIT_CODE;
|
|
|
|
###############
|
|
# subroutines #
|
|
###############
|
|
#-----------------------------------------------------------------------------
|
|
|
|
sub GetCurrDate
|
|
{
|
|
my $num_sec;
|
|
my $num_min;
|
|
my $num_hour;
|
|
my $num_mday;
|
|
my $num_mon;
|
|
my $num_year;
|
|
my $num_wday;
|
|
my $num_yday;
|
|
my $num_isdst;
|
|
($num_sec,$num_min,$num_hour,$num_mday,$num_mon,$num_year,$num_wday, $num_yday, $num_isdst)=localtime(time);
|
|
my $date_str =sprintf("%4d-%02d-%02d %02d:%02d:%02d\n", $num_year+1900,$num_mon+1,$num_mday,$num_hour,$num_min,$num_sec);
|
|
return $date_str;
|
|
}
|
|
|
|
|
|
sub LoadConfiguration
|
|
{
|
|
my %Config = (
|
|
Self => {
|
|
# loadable from ini
|
|
section => 'daemon',
|
|
programpath => '/opt/drweb/drwebd',
|
|
loglevel => 'info',
|
|
syslogfacility=> 'daemon',
|
|
logfilename => '/var/drweb/log/updater.log',
|
|
lockfile => '',
|
|
|
|
eslockfile => '/var/drweb/run/es_updater.lock',
|
|
|
|
locked => 'no',
|
|
drlfile => '/var/drweb/bases/update.drl',
|
|
signedreader => '/opt/drweb/read_signed',
|
|
lzmadecoderpath => '/opt/drweb',
|
|
proxyserver => '',
|
|
proxylogin => '',
|
|
proxypassword => '',
|
|
timeout => '60',
|
|
tries => '1',
|
|
cronsummary => 'no',
|
|
drldir => '/var/drweb/drl',
|
|
updatepluginsonly => undef,
|
|
# hidden options
|
|
skipengine => 'no',
|
|
customdrlfile => '',
|
|
fallbacktodrl => 'no',
|
|
updateself => 'no',
|
|
skipbase => '',
|
|
|
|
shouldnotify => 'no',
|
|
notifysocket => '/var/drweb/run/updateSock',
|
|
|
|
pathtovaderetro => '/var/drweb/lib/libvaderetro.so',
|
|
agentconfpath => '/etc/drweb/agent.conf',
|
|
ftsetupcfgpath => '/etc/drweb/flytrap/setup.cfg',
|
|
|
|
lotusdpidfile => '/var/drweb/run/drweblotusd.pid',
|
|
icapdpidfile => '/var/drweb/run/drweb_icapd.pid',
|
|
maildpidfile => '/var/drweb/run/drweb-maild.pid',
|
|
|
|
not_need_reload_drwebd => 0,
|
|
not_need_reload_maild => 0,
|
|
not_need_reload_icapd => 0,
|
|
not_need_reload_lotusd => 0,
|
|
|
|
dontcachekey => 0,
|
|
expiredtimelimit => 14,
|
|
keyupdatehost => 'products.drweb.com/register/renew/',
|
|
|
|
downloaddir => '',
|
|
workingdir => '/var/drweb/updater/',
|
|
},
|
|
Object => {
|
|
# loadable from ini
|
|
virusbase => '/var/drweb/bases',
|
|
updatepath => '/var/drweb/updates',
|
|
blacklistpath => '/var/drweb/dws',
|
|
enginepath => '/opt/drweb/lib/drweb32.dll',
|
|
key => '/opt/drweb/drweb32.key',
|
|
temppath => '/var/drweb/spool',
|
|
mailcommand => '/bin/cat',
|
|
# [Daemon] section only
|
|
pidfile => '/var/drweb/run/drwebd.pid',
|
|
user => 'drweb',
|
|
group => undef,
|
|
userid => -1,
|
|
groupid => -1,
|
|
},
|
|
Versions => {
|
|
Updater => 'Dr.Web Updater ($Id: fbb3ca1734c5e75ae3008acef02c2f1032ac44cf $)',
|
|
}
|
|
);
|
|
&Log( 'warning', $Config{ Versions }{ Updater }.' started ...' );
|
|
|
|
$Config{ Paths }{ self } = $0;
|
|
for ( $Config{ Paths }{ self } ) {
|
|
last if m!^/! and not m!/\.{1,2}/!;
|
|
$_ = abs_path $_;
|
|
}
|
|
&Log( 'debug', $Config{ Versions }{ Updater } . ' script path is ' . $Config{ Paths }{ self } );
|
|
|
|
my ( $ini, $what, $not_need_reload, $mode, $lstfile, $download_dir, $freeze_updates, $unfreeze_updates, $restore_backup, $show_components, $help)
|
|
= &ParseCmdLine( @_ );
|
|
|
|
$mode = 'help' if $help;
|
|
|
|
unless ( -f $ini ) {
|
|
&Log( 'debug', 'file ' . $ini . ' will not be read as configuration source because it is not a regular file' );
|
|
$Config{ Self }{ updatepluginsonly } = 'yes';
|
|
} else {
|
|
my $fd = new FileHandle;
|
|
open( $fd, "<$ini" )
|
|
or &DrDie(&EXIT_STATUS_INVALID_CONFIG, "can not open configuration file $ini ($!)" );
|
|
|
|
&ParseSection( $fd, 'updater', ';#', \%{ $Config{ Self } }, \&ParseLineStd );
|
|
|
|
$Config{ Self }{ section } = lc( $what || $Config{ Self }{ section } );
|
|
$Config{ Self }{ loglevel } = lc( $Config{ Self }{ loglevel } );
|
|
|
|
seek( $fd, 0, 0 ); # rewind
|
|
|
|
&ParseSection( $fd, $Config{ Self }{ section }, ';#', \%{ $Config{ Object } }, \&ParseLineStd )
|
|
or &DrDie(&EXIT_STATUS_INVALID_CONFIG, "failed to load parameters - section [$Config{ Self }{ section }] not found in $ini" );
|
|
|
|
close( $fd );
|
|
}
|
|
|
|
if (defined $not_need_reload) {
|
|
my @daemon_name_list = qw(drwebd maild icapd lotusd);
|
|
unless ($not_need_reload) {
|
|
$Config{ Self }{ "not_need_reload_$_" } = 1
|
|
foreach @daemon_name_list;
|
|
} else {
|
|
my $exists_flag = 0;
|
|
foreach my $daemon_name (@$not_need_reload) {
|
|
foreach (qw(drwebd maild icapd lotusd)) {
|
|
if ($_ eq lc $daemon_name) {
|
|
$Config{ Self }{ "not_need_reload_$_" } = 1;
|
|
last;
|
|
}
|
|
}
|
|
&Log( 'warning' , "unknown daemon '$daemon_name', has been skipped..." );
|
|
}
|
|
}
|
|
}
|
|
|
|
$Config{ Self }{ updatepluginsonly } ||= 'no';
|
|
&Log( 'warning', $Config{ Versions }{ Updater } . ' self auto-updating is enabled!' ) if 'yes' eq lc( $Config{ Self }{ updateself } );
|
|
|
|
$Config{ Self }{ lstfile } = $lstfile;
|
|
$Config{ Self }{ updatemode } = defined $mode ? $mode : 'default';
|
|
$Config{ Self }{ downloaddir } = $download_dir if defined $download_dir;
|
|
|
|
if (defined $Config{ Object }{ user }) {
|
|
$Config{ Object }{ user } = eval{ getpwnam( $Config{ Object }{ user } ) };
|
|
} else {
|
|
$Config{ Object }{ user } = $Config{ Object }{ userid };
|
|
}
|
|
$Config{ Object }{ userid } = $Config{ Object }{ user };
|
|
$Config{ Object }{ user } = eval{ return 'unchanged' if -1 == $Config{ Object }{ userid }; getpwuid( $Config{ Object }{ userid } ) };
|
|
$Config{ Object }{ user } = '#' . $Config{ Object }{ userid } unless defined $Config{ Object }{ user };
|
|
|
|
if (defined $Config{ Object }{ group }) {
|
|
$Config{ Object }{ group } = eval{ getgrnam( $Config{ Object }{ group } ) };
|
|
} else {
|
|
$Config{ Object }{ group } = $Config{ Object }{ groupid };
|
|
}
|
|
$Config{ Object }{ groupid } = $Config{ Object }{ group };
|
|
$Config{ Object }{ group } = eval{ return 'unchanged' if -1 == $Config{ Object }{ groupid }; getgrgid( $Config{ Object }{ groupid } ) };
|
|
$Config{ Object }{ group } = '#' . $Config{ Object }{ groupid } unless defined $Config{ Object }{ group };
|
|
|
|
&OpenLog( $Config{ Self }{ loglevel }, $Config{ Self }{ syslogfacility }, $Config{ Self }{ logfilename } );
|
|
&ReplayLog();
|
|
|
|
$Config{ Versions }{ UserAgent } = '$Id: fbb3ca1734c5e75ae3008acef02c2f1032ac44cf $';
|
|
$Config{ Versions }{ UserAgent } =~ s!^\$I@{[]}d:\s+(\S+?)\s*\$\z!DrWebUnixUpdater-$1 (HTTP/1.0-compliant; build: ; $^O: !;
|
|
my @os_info;
|
|
#// !!!:a.grotov:20081101 the stuff that fails
|
|
OS:
|
|
for ( lc $^O ) {
|
|
/linux/ && do {
|
|
my %info;
|
|
foreach my $line ( split /\n+/, `lsb_release -a 2>/dev/null` ) {
|
|
my ( $key, $value ) = split( /:/, $line, 2 );
|
|
$value =~ s/^\s+//;
|
|
$value =~ s/\s+$//;
|
|
$info{ lc $key } = $value;
|
|
}
|
|
foreach my $key ( qw/ description release codename / ) {
|
|
push @os_info, $info{ $key } if exists $info{ $key };
|
|
}
|
|
last OS if @os_info;
|
|
&Log( 'info', 'You appear to use some linux distro, but there is no lsb_release command accessible in your system. Either your PATH environment variable is screwed up (due to security considerations, or whatever), or your linux distribution does not fully support Linux Standard Base (proposed by Free Standards Group). It is probably okay. Falling back to another way of discovering the name of your linux distro.' );
|
|
my %dist_info = (
|
|
'Debian GNU/Linux' => '/etc/debian_version'
|
|
, 'Slackware Linux' => '/etc/slackware-version'
|
|
, 'RedHat Linux' => '/etc/redhat-release'
|
|
, 'Mandrake Linux' => '/etc/mandrake-release'
|
|
, 'Gentoo Linux' => '/etc/gentoo-release'
|
|
, 'ASPLinux' => '/etc/asplinux-release'
|
|
, 'Alt Linux' => '/etc/altlinux-release'
|
|
, 'PLD Linux' => '/etc/pld-release'
|
|
, 'SuSE Linux' => '/etc/SuSE-release'
|
|
, 'Arch Linux' => '/etc/arch-release'
|
|
, 'Conectiva Linux' => '/etc/conectiva-release'
|
|
);
|
|
LINUX_DISTRO:
|
|
foreach my $linux ( sort keys %dist_info ) {
|
|
my $file = $dist_info{ $linux };
|
|
stat( $file );
|
|
next unless -e _;
|
|
&Log( 'info', 'You appear to use ' . $linux . ' (the file \'' . $file . '\' exists, which is typical for ' . $linux . '). Trying to discover the exact version and hardware architecture of your linux system.' );
|
|
unless ( -r _ ) {
|
|
&Log( 'warning', 'You have the file \'' . $file . '\' which probably confirms your using of ' . $linux . ', but the file spoken of can not be read. You might make it readable for everyone with "chmod 644 \'' . $file . '\'" command.' );
|
|
push @os_info, $linux;
|
|
last;
|
|
}
|
|
my $fd = new FileHandle;
|
|
unless(open( $fd, '<' . $file )) {
|
|
&Log( 'error', 'Unable to open file ' . $file . ': ' . $! );
|
|
last LINUX_DISTRO;
|
|
}
|
|
my $line = <$fd>;
|
|
unless(defined $line) {
|
|
if($!) {
|
|
&Log( 'error', 'Unable to read file ' . $file . ': ' . $! );
|
|
}
|
|
last LINUX_DISTRO;
|
|
}
|
|
unless( close( $fd ) ) {
|
|
&Log( 'error', 'Unable to close file ' . $file . ': ' . $! );
|
|
last LINUX_DISTRO;
|
|
}
|
|
$line =~ s/^\s+//;
|
|
$line =~ s/\s+$//;
|
|
push @os_info, $linux if $dist_info{$linux} =~ m/version$/i;
|
|
push @os_info, $line;
|
|
last OS;
|
|
}
|
|
&Log( 'warning', 'Unable to reliably determine the exact version and hardware architecture of your linux distro. Please contact our support team via support@drweb.com e-mail address and provide us with the output of "uname -a" command invoked at this host. Please specify your linux distro name as well, along with the way to distinguish it from other linux distros (if you know it). We appreciate your cooperation, thank you!' );
|
|
last OS;
|
|
};
|
|
/bsd$/ && do {
|
|
## Highly experimental
|
|
my @sysctl2uname = (
|
|
[ 'kern.ostype' => 0 ]
|
|
, [ 'kern.osrelease' => 2 ]
|
|
, [ 'hw.machine_arch' => 4 ]
|
|
);
|
|
foreach my $sysctl ( @sysctl2uname ) {
|
|
my $mib = lc( $sysctl->[ 0 ] );
|
|
foreach my $line ( split /\n+/, `sysctl $mib 2>/dev/null` ) {
|
|
my ( $key, $value ) = map { s/^\s+//; s/\s+$//; $_ } split( /:/, $line, 2 );
|
|
unless ( ( '' eq $value ) or ( $mib ne lc( $key ) ) ) {
|
|
push @os_info, $value;
|
|
} else {
|
|
&Log( 'warning', 'The command "sysctl ' . $mib . ' 2>/dev/null" was expected to return non-empty value for ' . $mib . '. Please contact our support team via support@drweb.com e-mail address and provide us with any feedback regarding your operating system, its version and hardware architecture. We appreciate your cooperation, thank you!' );
|
|
$value = ( &uname() )[ $sysctl->[ 1 ] ] || '';
|
|
$value =~ s/^\s+//;
|
|
$value =~ s/\s+$//;
|
|
if ( '' eq $value ) {
|
|
&DrDie(&EXIT_STATUS_SYSTEM_ERROR, 'Unable to get the value of ' . $mib . ' neither from "syscall ' . $mib . ' 2>/dev/null" nor from uname() system call. Please contact our support team via support@drweb.com e-mail address and provide us with any feedback regarding your operating system, its version and hardware architecture. We appreciate your cooperation, thank you!' );
|
|
} else {
|
|
&Log( 'info', 'The value of ' . $mib . ' is taken from uname() system call.' );
|
|
push @os_info, $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
last OS;
|
|
};
|
|
/darwin/ && do
|
|
{
|
|
## Highly experimental
|
|
#current solution is based on the assumption that Mac OS X supports the same interface bsd systems do.
|
|
#The solution results in no obvious errors but more rigorous testing is strongly advised.
|
|
my @sysctl2uname = (
|
|
[ 'kern.ostype' => 0 ]
|
|
, [ 'kern.osrelease' => 2 ]
|
|
, [ 'hw.machine_arch' => 4 ]
|
|
);
|
|
foreach my $sysctl ( @sysctl2uname ) {
|
|
my $mib = lc( $sysctl->[ 0 ] );
|
|
foreach my $line ( split /\n+/, `sysctl $mib 2>/dev/null` ) {
|
|
my ( $key, $value ) = split( /\s*[:=]\s*/, $line, 2 );
|
|
unless ( ( '' eq $value ) or ( $mib ne lc( $key ) ) ) {
|
|
push @os_info, $value;
|
|
} else {
|
|
&Log( 'warning', 'The command "sysctl ' . $mib . ' 2>/dev/null" was expected to return non-empty value for ' . $mib . '. Please contact our support team via support@drweb.com e-mail address and provide us with any feedback regarding your operating system, its version and hardware architecture. We appreciate your cooperation, thank you!' );
|
|
$value = ( &uname() )[ $sysctl->[ 1 ] ] || '';
|
|
$value =~ s/^\s+//;
|
|
$value =~ s/\s+$//;
|
|
if ( '' eq $value ) {
|
|
&DrDie(&EXIT_STATUS_SYSTEM_ERROR, 'Unable to get the value of ' . $mib . ' neither from "syscall ' . $mib . ' 2>/dev/null" nor from uname() system call. Please contact our support team via support@drweb.com e-mail address and provide us with any feedback regarding your operating system, its version and hardware architecture. We appreciate your cooperation, thank you!' );
|
|
} else {
|
|
&Log( 'info', 'The value of ' . $mib . ' is taken from uname() system call.' );
|
|
push @os_info, $value;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
last OS;
|
|
|
|
};
|
|
/^solaris$/ && do {
|
|
if ( -r '/etc/release' ) {
|
|
my $fd = new FileHandle;
|
|
open( $fd, '</etc/release' ) or do { &Log( 'error', 'Unable to open file /etc/release: ' . $! ); last OS; };
|
|
my $line = <$fd>;
|
|
close( $fd ) or do { &Log( 'error', 'Unable to close file /etc/release: ' . $! ); last OS; };
|
|
$line =~ s/^\s+//;
|
|
$line =~ s/\s+$//;
|
|
push @os_info, $line;
|
|
} else {
|
|
&Log( 'warning', 'Your solaris distro appears not to have /etc/release file (which is a common way to keep solaris distribution version). It might be okay - anyway, please contact our support team via support@drweb.com e-mail address and provide us with any feedback regarding your operating system, its version and hardware architecture, along with the way to distinguish your particular solaris distribution from others. We appreciate your cooperation, thank you!' );
|
|
}
|
|
last OS;
|
|
};
|
|
DEFAULT:
|
|
{
|
|
&Log( 'error', 'Unable to determine the exact operating system distro name, version and hardware architecture you are using. Please contact our support team via support@drweb.com e-mail address. Put the string \'$^O = ' . $^O . '\' in your request, and please provide us with the output of "uname -a" command invoked at this host. Specify your distro name, version and hardware architecture as well, along with the way to distinguish it from other operating systems (if you know it). We appreciate your cooperation, thank you!' );
|
|
}
|
|
}
|
|
@os_info = ( &uname() )[ 0, 2, 3, 4 ] unless @os_info;
|
|
&DrDie( &EXIT_STATUS_SYSTEM_ERROR, 'Unabe to get your operating system information (its name, version and hardware architecture) with all conventional means. Please contact our support team via support@drweb.com e-mail address and provide us with any feedback regarding this information. We appreciate your cooperation, thank you!' ) unless @os_info;
|
|
$Config{ Versions }{ UserAgent } .= join( "\t" => map { s/^\s+//; s/\s+$//; s/\s+/ /; $_ } @os_info ) . ')';
|
|
|
|
&DrDie( &EXIT_STATUS_INVALID_CONFIG, 'key file is not defined' ) unless $Config{ Object }{ key };
|
|
&DrDie( &EXIT_STATUS_INVALID_CONFIG, 'engine path is not defined' ) unless $Config{ Object }{ enginepath };
|
|
&DrDie( &EXIT_STATUS_INVALID_CONFIG, 'bases path is not defined' ) unless $Config{ Object }{ virusbase };
|
|
&DrDie( &EXIT_STATUS_INVALID_CONFIG, 'update path is not defined!' ) unless $Config{ Object }{ updatepath };
|
|
|
|
$Config{ Paths }{ bases } =
|
|
-d $Config{ Object }{ virusbase } ?
|
|
&Dirname( $Config{ Object }{ virusbase } . '/' )
|
|
:
|
|
&Dirname( $Config{ Object }{ virusbase } )
|
|
;
|
|
$Config{ Paths }{ update } = &Dirname( $Config{ Object }{ updatepath } . '/' );
|
|
$Config{ Paths }{ drl } = &Dirname( $Config{ Self }{ customdrlfile } or $Config{ Self }{ drlfile } );
|
|
$Config{ Paths }{ blacklist } = &Dirname( $Config{ Object }{ blacklistpath } . '/' );
|
|
$Config{ Paths }{ downloaddir } = &Dirname($Config{ Self }{ downloaddir } . '/');
|
|
|
|
$Config{ Paths }{ workingdir } = &Dirname($Config{ Self }{ workingdir } . '/');
|
|
$Config{ Paths }{ cachefile } = $Config{ Paths }{ workingdir }.'cache';
|
|
$Config{ Paths }{ last_update_cachefile } = $Config{ Paths }{ workingdir }.'last_try_cache';
|
|
|
|
&Log( 'verbose', "Path to bases : $Config{ Paths }{ bases }" );
|
|
&Log( 'verbose', "Path to URL list : $Config{ Paths }{ drl }" );
|
|
&Log( 'verbose', "Path to blacklists : $Config{ Paths }{ blacklist }" );
|
|
&Log( 'verbose', "Path to downloaddir : $Config{ Paths }{ downloaddir }" );
|
|
&Log( 'verbose', "Path to workingdir : $Config{ Paths }{ workingdir }" );
|
|
&Log( 'verbose', "Path to cache file : $Config{ Paths }{ cachefile }" );
|
|
&Log( 'verbose', "Path to file about last update: $Config{ Paths }{ last_update_cachefile }");
|
|
|
|
my %possible_components =
|
|
map {($_ => 1)}
|
|
&getPossibleComponents(\%Config);
|
|
|
|
my ($i, $j, %duplicates);
|
|
for $i (\($freeze_updates, $unfreeze_updates, $restore_backup)) {
|
|
next if !defined ${$i} || ${$i} eq '';
|
|
my @components = ();
|
|
%duplicates = ();
|
|
for $j (split /,/, ${$i}) {
|
|
$j = lc $j;
|
|
next
|
|
if exists $duplicates{$j};
|
|
$duplicates{$j} = 1;
|
|
|
|
unless (exists $possible_components{$j}) {
|
|
&Log( 'warning', "Unknown component '$j', skipping..." );
|
|
next;
|
|
}
|
|
push @components, $j;
|
|
}
|
|
${$i} = scalar @components ? \@components : undef;
|
|
}
|
|
|
|
$Config{ Commands }{ freeze_updates } = $freeze_updates;
|
|
$Config{ Commands }{ unfreeze_updates } = $unfreeze_updates;
|
|
$Config{ Commands }{ restore_backup } = $restore_backup;
|
|
$Config{ Commands }{ show_components} = $show_components;
|
|
|
|
&Log( 'verbose', "Freeze updates : $Config{ Commands }{ freeze_updates }" )
|
|
if defined $Config{ Commands }{ freeze_updates };
|
|
&Log( 'verbose', "Unfreeze updates: $Config{ Commands }{ unfreeze_updates }" )
|
|
if defined $Config{ Commands }{ unfreeze_updates };
|
|
&Log( 'verbose', "Restore backup : $Config{ Commands }{ restore_backup }" )
|
|
if defined $Config{ Commands }{ restore_backup };
|
|
|
|
&Log( 'verbose', "Show Components : $Config{ Commands }{ show_components }" );
|
|
|
|
foreach my $item ('lzma') {
|
|
my $path_to_decoder = $Config{ Self }{ "${item}decoderpath" };
|
|
|
|
$path_to_decoder = defined $path_to_decoder && $path_to_decoder ne '' && -e "$path_to_decoder/$item"
|
|
? quotemeta("$path_to_decoder/$item")
|
|
: $item;
|
|
|
|
$Config{ Self }{ "${item}decoderpath" } = $path_to_decoder;
|
|
&Log( 'verbose', "Path to ${item}: " . $path_to_decoder );
|
|
}
|
|
|
|
$Config{Self}{md5sum} = &FindMD5;
|
|
|
|
return \%Config;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
#IN (HAHS REF): Config
|
|
sub getPossibleComponents
|
|
{
|
|
my ($Conf) = @_;
|
|
|
|
&Log('debug', 'getPossibleComponents has been called');
|
|
|
|
my @result = ('agent');
|
|
push @result, 'drweb'
|
|
if -e $Conf->{ Paths }{ drl };
|
|
|
|
if (-d $Conf->{Self}{drldir}) {
|
|
my $component_drl;
|
|
for $component_drl (glob quotemeta( $Conf->{Self}{drldir} ) . '/*.drl') {
|
|
$component_drl =~ /([^\/]+)\.drl$/;
|
|
push @result, $1;
|
|
}
|
|
}
|
|
|
|
return
|
|
@result;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
#IN (HASH REF): Config
|
|
sub createNeededPathes
|
|
{
|
|
my ($Conf) = @_;
|
|
&Log('debug', 'createNeededPathes has been called');
|
|
|
|
&createDirPath($Conf->{ Paths }{ workingdir })
|
|
unless -d $Conf->{ Paths }{ workingdir };
|
|
|
|
&createDirPath(&generateComponentFilePath($Conf, $_, 'component_base_dir'))
|
|
for (&getPossibleComponents($Conf));
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
#IN (STRING) : Path
|
|
sub createDirPath
|
|
{
|
|
my $path = shift;
|
|
|
|
&Log( 'debug', 'Creating path \''.$path.'\'...' );
|
|
my $current_path = '/';
|
|
foreach (split '/', $path) {
|
|
next if $_ eq '';
|
|
|
|
$current_path .= $_ . '/';
|
|
next if -e $current_path;
|
|
|
|
mkdir($current_path)
|
|
or die "Can not create dir '$current_path' ($!)";
|
|
}
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# OUT: no
|
|
sub DumpConfiguration
|
|
{
|
|
my $Conf = shift;
|
|
&Log( 'debug', 'following configuration has been loaded:' );
|
|
foreach my $sec ( sort keys %{ $Conf } )
|
|
{
|
|
&Log( 'debug', "Section($sec)" );
|
|
foreach my $key ( sort keys %{ $Conf->{$sec} } ) {
|
|
if ( defined( $Conf->{$sec}{$key} ) ) {
|
|
&Log( 'debug', " Name($key) Value($Conf->{$sec}{$key})" );
|
|
} else {
|
|
&Log( 'debug', " Name($key) Value(undef)" );
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_ARRAY) : list of servers
|
|
sub dumpServers
|
|
{
|
|
my ($servers, $type) = @_;
|
|
|
|
&Log('info', $type . ' update servers:');
|
|
foreach (@$servers) {
|
|
&Log('info', ' ' . $_);
|
|
}
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# OUT(ARRAY) : reference to array of core URLs, reference to array of URLs of plugins
|
|
sub BuildURLs
|
|
{
|
|
my $Conf = shift;
|
|
my $CUSTOMURLS = [];
|
|
my $STDURLS = [];
|
|
my $COREURLS = [];
|
|
my $PLUGINURLS = [];
|
|
|
|
unless( 'yes' eq lc $Conf->{ Self }{ updatepluginsonly } ) {
|
|
push @{$COREURLS}, $CUSTOMURLS
|
|
if &ReadDrl( $CUSTOMURLS, $Conf->{Self}{signedreader}, $Conf->{Self}{customdrlfile}, 'custom' ) && scalar @$CUSTOMURLS;
|
|
&ReadDrl( $STDURLS, $Conf->{Self}{signedreader}, $Conf->{Self}{drlfile}, 'Dr.Web' );
|
|
# if( 0 == scalar @{$STDURLS} ) {
|
|
# &Log( 'warning', "no valid URL has been read, using default: $DefaultURL" );
|
|
# push @{$STDURLS}, $DefaultURL;
|
|
# }
|
|
push @{$COREURLS}, $STDURLS if scalar @$STDURLS;
|
|
@{$CUSTOMURLS} ?
|
|
dumpServers($CUSTOMURLS,'custom') :
|
|
&Log( 'info', 'no custom update servers' );
|
|
dumpServers($STDURLS, 'main');
|
|
}
|
|
|
|
unless ( -d $Conf->{Self}{drldir} ) {
|
|
&Log( 'info', 'drldir not found: "' . $Conf->{Self}{drldir} .
|
|
'", assuming there are no plugins to update' );
|
|
return ( $COREURLS, $PLUGINURLS );
|
|
} else {
|
|
my %drl;
|
|
foreach my $drl ( sort glob quotemeta( $Conf->{Self}{drldir} ) . '/*.drl' ) {
|
|
$drl =~ m/.+\/(.+?)\.(custom\.)?drl$/;
|
|
$drl{ $1 }[ !defined $2 ] = $drl;
|
|
}
|
|
foreach my $drl ( sort keys %drl ) {
|
|
my $plugin_urls = [];
|
|
foreach my $i ( 0 .. 1 ) {
|
|
&Log( 'info', 'no ' . [ 'custom', 'main' ]->[ $i ] . ' URLs for plugin ' . $drl ), next unless defined $drl{ $drl }[ $i ];
|
|
my $url_list = [];
|
|
if ( &ReadDrl( $url_list, $Conf->{Self}{signedreader}, $drl{ $drl }[ $i ], ( ( !$i and 'custom ' ) or '' ) . 'plugin ' . $drl ) ) {
|
|
&Log( 'info', 'no ' . [ 'custom', 'main' ]->[ $i ] . ' URLs for plugin ' . $drl ), next unless defined( $url_list ) and @{$url_list};
|
|
push @{$plugin_urls}, $url_list;
|
|
&Log( 'info', 'plugin ' . $drl . ' ' . [ 'custom', 'main' ]->[ $i ] . ' URLs are: ' . join( ', ', @{$url_list} ) );
|
|
}
|
|
}
|
|
push @{$PLUGINURLS}, { $drl => $plugin_urls } if ( @{$plugin_urls} );
|
|
}
|
|
unless ( @{$PLUGINURLS} ) {
|
|
&Log( 'info', 'no plugins\' URLs found' );
|
|
}
|
|
}
|
|
|
|
return ( $COREURLS, $PLUGINURLS );
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_ARRAY): list of updates servers
|
|
# IN (REF_SCALAR): update task
|
|
# IN (REF_CODE): callback to get files
|
|
# IN (REF_CODE): preinstall callback
|
|
# IN (STRING) : component name
|
|
# OUT(SCALAR): process update result
|
|
sub UpdateSimple
|
|
{
|
|
my ($Config, $LstTask, $DrLstRef, $GetFilesCallback, $PreinstallCallback, $component)=@_;
|
|
|
|
${$LstTask} = {} unless defined ${$LstTask};
|
|
|
|
&ParseLst($Config,${$LstTask},$DrLstRef);
|
|
my $hooks = $Config->{ Self }{ hooks };
|
|
my $filter_task = sub {};
|
|
$filter_task = $hooks->{ filter_task } if exists $hooks->{ filter_task };
|
|
$filter_task->( $Config, ${$LstTask} );
|
|
&Log( 'verbose', 'Downloading notifications ...' );
|
|
my $return_status;
|
|
unless ( $return_status = $GetFilesCallback->( \%{${$LstTask}->{notify}}, \%{${$LstTask}->{new_notify}}, 0, ${$LstTask}->{process}, $Conf, $component ) )
|
|
{
|
|
&Log( 'warning', "failed to download notifications" );
|
|
return $return_status;
|
|
}
|
|
&Log( 'verbose', 'Downloading updated files ...' );
|
|
unless ( $return_status = $GetFilesCallback->( \%{${$LstTask}->{update}}, \%{${$LstTask}->{new}}, 1, ${$LstTask}->{process}, $Conf, $component ) )
|
|
{
|
|
&Log( 'warning', "failed to download updated files" );
|
|
return $return_status;
|
|
}
|
|
&Log( 'verbose', 'Downloading new/updated files ...' );
|
|
unless ( $return_status = $GetFilesCallback->( \%{${$LstTask}->{add}}, \%{${$LstTask}->{new}}, 0, ${$LstTask}->{process}, $Conf, $component ) )
|
|
{
|
|
&Log( 'warning', "failed to download new/updated files" );
|
|
return $return_status;
|
|
}
|
|
|
|
$PreinstallCallback->($Config,${$LstTask});
|
|
&createBackupByLstTask($Config,${$LstTask},$component);
|
|
|
|
&Log( 'verbose', 'deleting old files ...' );
|
|
${$LstTask}->{deleted}=[];
|
|
&RemoveFiles( [ sort keys %{${$LstTask}->{remove}}], $Conf, 1, ${$LstTask}->{deleted} );
|
|
&Log( 'verbose', 'moving downloaded files from temporary to working directory ...' );
|
|
&Log( 'debug', "Following files will be added: ".join( ', ', sort keys %{${$LstTask}->{new}} ) );
|
|
${$LstTask}->{moved}=[];
|
|
&MoveFiles($Config, \%{${$LstTask}->{new}}, ${$LstTask}->{moved}, $hooks->{sort_to_move} );
|
|
&Log( 'debug', "Following notifications will be added: ".join( ', ', sort keys %{${$LstTask}->{new_notify}} ) );
|
|
${$LstTask}->{moved_notify}=[];
|
|
&MoveFiles($Config, \%{${$LstTask}->{new_notify}}, ${$LstTask}->{moved_notify} );
|
|
|
|
&deleteComponentInfoFromLastUpdateCacheData($component);
|
|
&updateCacheByLstTask(${$LstTask});
|
|
|
|
&Log( 'verbose', 'sending notifications ...' );
|
|
&SendNotify( $_, $Conf ) foreach ( sort keys %{${$LstTask}->{new_notify}} );
|
|
|
|
return 1;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_ARRAY): list of updates servers
|
|
# IN (REF_SCALAR): update task
|
|
# IN (STRING): component name
|
|
# OUT(SCALAR): process update result
|
|
sub Update
|
|
{
|
|
my ( $Config, $URLSARRAY, $LstTask, $component) = @_;
|
|
|
|
my $URL = '';
|
|
my $lines = 0;
|
|
my $DrLst;
|
|
my $StartTime = localtime;
|
|
my $return_status;
|
|
|
|
my $count;
|
|
LOOP: foreach my $URLS ( @{$URLSARRAY} ) {
|
|
while( ($count = scalar @{$URLS}) > 0 ) {
|
|
$DrLst = '';
|
|
${$LstTask} = {};
|
|
my $key = int rand $count;
|
|
|
|
${$LstTask}->{process}{URL} = $URL = $URLS->[$key];
|
|
&Log( 'info', "Attempting to fetch $URL/drweb32.lst ..." );
|
|
|
|
unless ($return_status = &GetHttpFile($Config,${$LstTask}->{process},"drweb32.lst",\$DrLst)) {
|
|
&Log( 'warning', "failed to download $URL/drweb32.lst ($!)" );
|
|
if ($UPDATE_ERROR == &EXIT_STATUS_INVALID_KEY) {
|
|
&Log( 'error', "Key is wrong or has been blocked" );
|
|
next LOOP;
|
|
}
|
|
}
|
|
|
|
#&CloseSocket($Config,${$LstTask}->{process});
|
|
unless( $DrLst ) {
|
|
&Log( 'warning', "can not fetch $URL/drweb32.lst" );
|
|
$URLS->[$key] = $URLS->[0];
|
|
shift @{$URLS};
|
|
} else {
|
|
&Log( 'verbose', length($DrLst)." bytes received from $URL/drweb32.lst." );
|
|
&Log( 'debug', "using ${$LstTask}->{process}{URL} for downloading updates ..." );
|
|
|
|
my $get_files_callback = sub {
|
|
return &DownloadFiles( @_ );
|
|
};
|
|
my $preinstall_callback = sub {
|
|
my ($Conf, $LstTask) = @_;
|
|
&CloseSocket($Config,$LstTask->{process}) if $LstTask->{process}{socket};
|
|
};
|
|
|
|
unless ( $return_status = UpdateSimple($Conf, $LstTask, \$DrLst, $get_files_callback, $preinstall_callback, $component) ) {
|
|
unless( defined $return_status ) {
|
|
next LOOP;
|
|
}
|
|
|
|
$URLS->[$key] = $URLS->[0];
|
|
shift @{$URLS};
|
|
next;
|
|
}
|
|
|
|
${$LstTask}->{process}{StartTime} = $StartTime;
|
|
${$LstTask}->{process}{EndTime} = localtime;
|
|
|
|
last;
|
|
}
|
|
} continue {
|
|
unless (scalar @{$URLS} > 0) {
|
|
&Log( 'error', "failed to download files" );
|
|
$EXIT_CODE = $UPDATE_ERROR;
|
|
}
|
|
}
|
|
|
|
last if lc($Conf->{Self}{fallbacktodrl}) ne 'yes' || $DrLst;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH) : update task
|
|
# IN (REF_ARRAY) : drweb32.lst lines
|
|
sub ParseLst {
|
|
my ( $Config, $Task, $DrList ) = @_;
|
|
&Log( 'debug', "ParseLst has been called ..." );
|
|
|
|
foreach my $UpdateLine ( split /\r?\n/, $$DrList ) {
|
|
my ( $op, undef, $platform, $filename, $check_sum, $api ) =
|
|
( $UpdateLine =~ /^([\-\+\=\@])(<(\w[\w\*\.\-\/]*)>)?([\w\*\.\-\/\(\)]*)[, \t]*([a-fA-F\d]*)[, \t]*(\d+\.\d+)?/o )
|
|
or next; # skip malformed lines
|
|
$api = '' unless $api;
|
|
$platform = '' unless $platform;
|
|
chomp( $filename, $check_sum, $api );
|
|
&Log( 'debug', 'UpdateLine: ' . $UpdateLine );
|
|
&Log( 'debug', 'UpdateTask: operation = ' . $op . ', file = ' . $filename . ', check sum = ' . $check_sum . ', API = ' . $api . ', platform = ' . $platform );
|
|
if ( $platform and $platform ne lc $^O ) {
|
|
&Log( 'debug', 'wrong platform, skipping insertion to tasklist...' );
|
|
next;
|
|
}
|
|
if ( $Config->{ Self }{ skipengine } eq 'yes' and IsEngine( $filename ) )
|
|
{
|
|
&Log( 'debug', $filename . ' is engine, skipping insertion to tasklist...' );
|
|
next;
|
|
}
|
|
|
|
if ( $Config->{ Self }{ skipbase } and $filename eq $Config->{ Self }{ skipbase } )
|
|
{
|
|
&Log( 'debug', $filename . ' in hold list, skipping insertion to tasklist...' );
|
|
next;
|
|
}
|
|
if ( $op ne '' and $filename ne '' )
|
|
{
|
|
# remove all previous entries of a file
|
|
delete $Task->{ $_ }{ $filename } foreach ( keys %{ $Task } );
|
|
$Task->{ remove }{ $filename } = hex( $check_sum || '' ) || '', next
|
|
if( $op eq '-' );
|
|
local $SIG{__WARN__} = sub {
|
|
&DrDie(
|
|
&EXIT_STATUS_SYSTEM_ERROR,
|
|
join( "\n" => ( 'Bogus check sum for ' . $filename . ': "' . ( defined( $check_sum )? $check_sum : 'undef' ) . '"', 'Perl diagnostics is:', @_ ) )
|
|
)
|
|
};
|
|
|
|
$check_sum = $Conf->{Self}{updatemode} eq 'default' ? hex( $check_sum ) : $check_sum;
|
|
|
|
$Task->{ update }{ $filename } = $check_sum, next
|
|
if( $op eq '=' );
|
|
$Task->{ add }{ $filename } = $check_sum, next
|
|
if( $op eq '+' );
|
|
$Task->{ notify }{ $filename } = $check_sum, next
|
|
if( $op eq '@' );
|
|
}
|
|
}
|
|
my $comparator = sub {
|
|
my $a_class = ( $a =~ m/[*?]/ ) || 0;
|
|
my $b_class = ( $b =~ m/[*?]/ ) || 0;
|
|
return
|
|
( ( $b_class <=> $a_class ) or ( $a cmp $b ) );
|
|
};
|
|
&Log( 'debug', 'Following files will be deleted: ' . join( ', ' => sort $comparator keys %{ $Task->{ remove } } ) );
|
|
|
|
my $max_filename_length = 0;
|
|
foreach my $filename ( keys( %{ $Task->{ update } } ), keys( %{ $Task->{ add } } ), keys( %{ $Task->{ notify } } ) ) {
|
|
$max_filename_length = length( $filename ) if length( $filename ) > $max_filename_length;
|
|
}
|
|
my $format = ' file = %' . $max_filename_length . 's, check sum = %8s';
|
|
|
|
&Log( 'debug', 'Following files will be updated:' );
|
|
&Log( 'debug', sprintf( $format => $_, uc $Task->{ update }{ $_ } ) ) foreach ( sort $comparator keys %{ $Task->{ update } } );
|
|
|
|
&Log( 'debug', 'Following files will be installed/updated:' );
|
|
&Log( 'debug', sprintf( $format => $_, uc $Task->{ add }{ $_ } ) ) foreach ( sort $comparator keys %{ $Task->{ add } } );
|
|
|
|
&Log( 'debug', 'Following notifications will be updated:' );
|
|
&Log( 'debug', sprintf( $format => $_, uc $Task->{ notify }{ $_ } ) ) foreach ( sort $comparator keys %{ $Task->{ notify } } );
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH) : update task
|
|
sub FlyTrapPreprocessTask {
|
|
my ( $Config, $LstTask ) = @_;
|
|
&Log( 'debug', 'preprocessing update task for FlyTrap...' );
|
|
my $fd = new FileHandle;
|
|
open( $fd, "<$Config->{ Self }{ ftsetupcfgpath }" )
|
|
or &DrDie( &EXIT_STATUS_SYSTEM_ERROR, "can not open FlyTrap configuration file $Config->{ Self }{ ftsetupcfgpath } ($!)" );
|
|
my $filepath_to_last;
|
|
my $line;
|
|
while ( defined( $line = <$fd> ) ) {
|
|
chomp $line;
|
|
$line =~ s/^\s+//;
|
|
$line =~ s/\s+$//;
|
|
my ( $key, $value ) = split /\s*=\s*/, $line, 2;
|
|
for ( $key ) {
|
|
/^update$/i && do {
|
|
$value =
|
|
-d $value ?
|
|
&Dirname( $value . '/' )
|
|
:
|
|
&Dirname( $value )
|
|
;
|
|
$Config->{ Paths }{ flytrap } = $value;
|
|
last;
|
|
};
|
|
/^dime_copy$/i && do {
|
|
$value =
|
|
-d $value ?
|
|
&Dirname( $value . '/' )
|
|
:
|
|
&Dirname( $value )
|
|
;
|
|
$filepath_to_last = $value . 'last';
|
|
last;
|
|
};
|
|
}
|
|
}
|
|
close( $fd );
|
|
&DrDie( &EXIT_STATUS_SYSTEM_ERROR, 'Path to FlyTrap updates not found!' ) unless exists $Config->{ Paths }{ flytrap };
|
|
&DrDie( &EXIT_STATUS_SYSTEM_ERROR, 'Path to FlyTrap \'last\' update status file not found!' ) unless defined $filepath_to_last;
|
|
&DrDie( &EXIT_STATUS_SYSTEM_ERROR, 'FlyTrap \'last\' update status file does not exist or is not readable!' ) unless -f $filepath_to_last and -r $filepath_to_last;
|
|
&Log( 'verbose', "Path to FlyTrap updates : $Config->{ Paths }{ flytrap }" );
|
|
&Log( 'verbose', "Path to FlyTrap 'last' update status file : $filepath_to_last" );
|
|
$fd = new FileHandle;
|
|
open( $fd, "<$filepath_to_last" )
|
|
or &DrDie( &EXIT_STATUS_SYSTEM_ERROR, "can not open FlyTrap 'last' update status file $filepath_to_last ($!)" );
|
|
$line = <$fd>;
|
|
chomp $line;
|
|
my @last = ( $line =~ m/^\d{4}END\s+\d{2}\.\d{2}\.\d{6}:\d{2}:\d{2}[0]{9}(\d+)\-(\d+)\-(\d+)/ );
|
|
close( $fd );
|
|
&Log( 'debug', 'Last updates in FlyTrap database: ' . join( ', ' => map { 'class ' . $_ . ' - ' . $last[ $_ - 1 ] } ( 1 .. @last ) ) . '.' );
|
|
my $updates = [ [], [], [] ];
|
|
foreach my $filename ( sort keys %{ $LstTask->{ add } } ) {
|
|
next unless IsFlyTrapUpdate( $filename );
|
|
my ( $update_class, $update_number ) = ( $filename =~ m/\bu\d{4}\-([123])(\d+)$/ );
|
|
$update_class--;
|
|
&Log( 'debug', $filename . ' is eliminated from task list (installed already)' ), delete( $LstTask->{ add }{ $filename } ), next
|
|
unless $update_number > $last[ $update_class ];
|
|
push @{ $updates->[ $update_class ] }, $update_number;
|
|
}
|
|
my $update_class = 1;
|
|
foreach my $update ( @$updates ) {
|
|
next unless @$update;
|
|
if ($update_class == 1) {
|
|
@$update = sort @$update;
|
|
next if ( @$update == ( $update->[ -1 ] - $update->[ 0 ] + 1 ) ) and ( $update->[ 0 ] == ( $last[ $update_class - 1 ] + 1 ) );
|
|
} else {
|
|
my %update_nums = ();
|
|
my $duplicate_flag = 0;
|
|
for my $num (@$update) {
|
|
if(exists $update_nums{$num}) {
|
|
$duplicate_flag = 1;
|
|
last;
|
|
}
|
|
$update_nums{$num}++;
|
|
}
|
|
next unless $duplicate_flag;
|
|
}
|
|
&Log( 'warning', 'FlyTrap updates with class ' . $update_class . ' are not eligible at the moment (update numbers ' . join( ', ' => @$update ) . ' are not consecutive)' );
|
|
foreach my $filename ( sort keys %{ $LstTask->{ add } } ) {
|
|
next unless $filename =~ m/\bu\d{4}\-$update_class\d+$/;
|
|
&Log( 'warning', $filename . ' is eliminated from task list (to avoid installation errors)' ), delete( $LstTask->{ add }{ $filename } );
|
|
}
|
|
} continue {
|
|
$update_class++;
|
|
}
|
|
&Log( 'debug', 'update task for FlyTrap has been preprocessed' );
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (HASH) : files to move
|
|
# OUT: array (list of sorted names of file)
|
|
sub FlyTrapSortToMove
|
|
{
|
|
my ($Files) = shift;
|
|
|
|
my @buffer = ();
|
|
my @result = ();
|
|
for (sort keys %$Files) {
|
|
if (m/\bu\d{4}\-[123]\d+$/) {
|
|
push @buffer, $_;
|
|
} else {
|
|
push @result, $_;
|
|
}
|
|
}
|
|
|
|
@buffer = sort {
|
|
my ($a_class, $a_number) = ($a =~ m/\bu\d{4}\-(\d)(\d+)$/);
|
|
my ($b_class, $b_number) = ($b =~ m/\bu\d{4}\-(\d)(\d+)$/);
|
|
$a_class > $b_class
|
|
? 1
|
|
: $a_class < $b_class
|
|
? -1
|
|
: $a_number > $b_number
|
|
? 1
|
|
: -1
|
|
} @buffer;
|
|
|
|
@result = (@result, @buffer);
|
|
return \@result;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (HASH) : updater configuration
|
|
# IN (HASH) : update task
|
|
# OUT: no
|
|
sub ViewSummary
|
|
{
|
|
my ($Conf, $Task) = @_;
|
|
|
|
&Log( 'debug', "ViewSummary() has been called ..." );
|
|
if( lc($Conf->{Self}{cronsummary}) eq 'yes' and $Task )
|
|
{
|
|
my $n_new = exists $Task->{moved} ? scalar @{$Task->{moved}} : 0;
|
|
my $n_rm = exists $Task->{deleted} ? scalar @{$Task->{deleted}} : 0;
|
|
my $n_not = exists $Task->{moved_notify} ? scalar @{$Task->{moved_notify}} : 0;
|
|
&Log( 'verbose', "summary => updated: $n_new, removed: $n_rm files and $n_not messages" );
|
|
if( $n_new != 0 or $n_rm != 0 or $n_not != 0 )
|
|
{
|
|
print STDERR "Dr.Web update details:\n";
|
|
print STDERR "Update server: ".$Task->{process}{URL}."\n" if defined $Task->{process}{URL};
|
|
print STDERR "Update has begun at ".$Task->{process}{StartTime}."\n";
|
|
print STDERR "Update has finished at ".$Task->{process}{EndTime}."\n";
|
|
print STDERR "\n";
|
|
print STDERR "Following files have been updated:\n\t".join("\n\t", @{$Task->{moved}} )."\n"
|
|
if $n_new ne '0';
|
|
print STDERR "Following files have been removed:\n\t".join("\n\t", @{$Task->{deleted}} )."\n"
|
|
if $n_rm ne '0';
|
|
print STDERR "Following notifications have been sent:\n\t".join("\n\t", @{$Task->{moved_notify}} )."\n"
|
|
if $n_not ne '0';
|
|
print STDERR "\n";
|
|
}
|
|
}
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (HASH) : updater configuration
|
|
# IN (HASH) : update task
|
|
# OUT: (REF_CODE) : reference to anonymous subroutine performing daemon reload
|
|
sub ReloadDaemon
|
|
{
|
|
my ($Conf, $Task) = @_;
|
|
|
|
my $pidFile = $Conf->{Object}{pidfile};
|
|
&Log( 'debug', "ReloadDaemon($pidFile) has been called ..." );
|
|
|
|
# return undef if $Conf->{Self}{section} ne 'daemon';
|
|
|
|
foreach (@{$Task->{moved}},@{$Task->{deleted}}) {
|
|
if (&IsBase($_) or $_ eq $Conf->{Object}{enginepath}) {
|
|
if( -e $pidFile )
|
|
{
|
|
open( FH, "$pidFile" )
|
|
or &DrDie(&EXIT_STATUS_RELOAD_DAEMON_ERROR, "can not open $pidFile ($!) for reloading daemon");
|
|
my $pid = <FH>;
|
|
close( FH );
|
|
chomp( $pid );
|
|
return sub {
|
|
if ($Conf->{ Self }{ not_need_reload_drwebd }) {
|
|
&Log( 'info', 'Not needed to send signal HUP to drwebd...' );
|
|
return undef;
|
|
}
|
|
&Log( 'warning', 'Trying to send signal HUP Dr.Web daemon...' );
|
|
kill 'HUP', $pid
|
|
or &DrDie(&EXIT_STATUS_RELOAD_DAEMON_ERROR, "can not send HUP signal to Dr.Web daemon (pid=$pid, error=$!)");
|
|
&Log( 'warning', 'signal HUP has been sent to Dr.Web daemon...' );
|
|
};
|
|
}
|
|
last;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (HASH) : updater configuration
|
|
# IN (HASH) : update task
|
|
# OUT: (REF_CODE) : reference to anonymous subroutine performing daemon reload
|
|
sub ReloadIcapd
|
|
{
|
|
my ($Conf, $Task) = @_;
|
|
|
|
my $pidFile = $Conf->{Self}{icapdpidfile};
|
|
&Log( 'debug', "ReloadIcapd($pidFile) has been called ..." );
|
|
|
|
#return undef if $Conf->{Self}{section} ne 'icapd';
|
|
|
|
foreach ( @{$Task->{moved}}, @{$Task->{deleted}} ) {
|
|
if ( &IsBlacklist($_) ) {
|
|
if ( -e $pidFile )
|
|
{
|
|
open( FH, "$pidFile" )
|
|
or &DrDie(&EXIT_STATUS_RELOAD_ICAPD_ERROR, "can not open $pidFile ($!) for reloading drweb-icapd");
|
|
my $pid = <FH>;
|
|
close( FH );
|
|
chomp( $pid );
|
|
return sub {
|
|
if ($Conf->{ Self }{ not_need_reload_icapd }) {
|
|
&Log( 'info', 'Not needed to send signal HUP to drweb-icapd...' );
|
|
return undef;
|
|
}
|
|
&Log( 'warning', 'Trying to send signal HUP to drweb-icapd...' );
|
|
kill 'HUP', $pid
|
|
or &DrDie(&EXIT_STATUS_RELOAD_ICAPD_ERROR, "can not send HUP signal to drweb-icapd (pid=$pid, error=$!)");
|
|
&Log( 'warning', 'signal HUP has been sent to drweb-icapd...' );
|
|
};
|
|
}
|
|
else
|
|
{
|
|
&Log( 'info', "drweb-icapd pidfile $pidFile does not exist or unaccessible" );
|
|
}
|
|
last;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (HASH) : updater configuration
|
|
# IN (HASH) : update task
|
|
# OUT: (REF_CODE) : reference to anonymous subroutine performing daemon reload
|
|
sub ReloadMaild
|
|
{
|
|
my ($Conf, $Task) = @_;
|
|
|
|
my $maildPidFile = $Conf->{Self}{maildpidfile};
|
|
&Log( 'debug', "ReloadMaild($maildPidFile) has been called ..." );
|
|
|
|
#return undef if ( $Conf->{Self}{section} ne 'daemon' or ! -f $maildPidFile );
|
|
|
|
foreach (@{$Task->{moved}},@{$Task->{deleted}}) {
|
|
if ( $_ eq $Conf->{Self}{pathtovaderetro} ) {
|
|
if( -e $maildPidFile )
|
|
{
|
|
open( FH, "$maildPidFile" )
|
|
or &DrDie(&EXIT_STATUS_RELOAD_MAILD_ERROR, "can not open $maildPidFile ($!) for reloading drweb-maild");
|
|
my $pid = <FH>;
|
|
close( FH );
|
|
chomp( $pid );
|
|
return sub {
|
|
if ($Conf->{ Self }{ not_need_reload_maild }) {
|
|
&Log( 'info', 'Not needed to send signal HUP to maild...' );
|
|
return undef;
|
|
}
|
|
&Log( 'warning', 'Trying to send signal HUP drweb-maild...' );
|
|
kill 'HUP', $pid
|
|
or &DrDie(&EXIT_STATUS_RELOAD_MAILD_ERROR, "can not send HUP signal to drweb-maild (pid=$pid, error=$!)");
|
|
&Log( 'warning', 'signal HUP has been sent to drweb-maild...' );
|
|
};
|
|
}
|
|
last;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (HASH) : updater configuration
|
|
# IN (HASH) : update task
|
|
# OUT: (REF_CODE) : reference to anonymous subroutine performing daemon reload
|
|
sub ReloadLotusd
|
|
{
|
|
my ($Conf, $Task) = @_;
|
|
|
|
my $lotusdPidFile = $Conf->{Self}{lotusdpidfile};
|
|
|
|
&Log( 'debug', "ReloadLotusd($lotusdPidFile) has been called ..." );
|
|
|
|
foreach (@{$Task->{moved}},@{$Task->{deleted}}) {
|
|
if ( $_ eq $Conf->{Self}{pathtovaderetro} ) {
|
|
if( -e $lotusdPidFile )
|
|
{
|
|
open( FH, "$lotusdPidFile" )
|
|
or &DrDie(&EXIT_STATUS_RELOAD_LOTUSD_ERROR, "can not open $lotusdPidFile ($!) for reloading Lotusd");
|
|
my $pid = <FH>;
|
|
close( FH );
|
|
chomp( $pid );
|
|
return sub {
|
|
if ($Conf->{ Self }{ not_need_reload_lotusd }) {
|
|
&Log( 'info', 'Not needed to send signal HUP to lotusd...' );
|
|
return undef;
|
|
}
|
|
&Log( 'warning', 'Trying to send signal HUP to lotusd...' );
|
|
kill 'HUP', $pid
|
|
or &DrDie(&EXIT_STATUS_RELOAD_LOTUSD_ERROR, "can not send HUP signal to lotusd (pid=$pid, error=$!)");
|
|
&Log( 'warning', 'signal HUP has been sent to lotusd...' );
|
|
};
|
|
}
|
|
else
|
|
{
|
|
&Log( 'info', "lotusd pidfile $lotusdPidFile does not exist or unaccessible" );
|
|
}
|
|
last;
|
|
}
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
|
|
### Parsers ###################################################################
|
|
# IN (STRING) : line for parsing
|
|
# IN (REF_HASH) : hash for parsed values
|
|
# OUT: no
|
|
#------------------------------------------------------------------------------
|
|
sub ParseLineStd
|
|
{
|
|
my( $line, $ref_hash ) = @_;
|
|
|
|
my( $name, $value ) = split /=/, $line, 2;
|
|
$name =~ s/[\t ]*$//g;
|
|
$name = lc($name);
|
|
return unless exists $ref_hash->{$name};
|
|
|
|
if( defined $value and $value ne '' )
|
|
{
|
|
( $value ) = split /\,/, $value, 2;
|
|
$value =~ s/^[\"\t ]*//g;
|
|
$value =~ s/[\"]*$//g;
|
|
}
|
|
else
|
|
{
|
|
$value = '';
|
|
}
|
|
$ref_hash->{$name}=$value;
|
|
&Log( 'debug', "ParseStdLine return(name=<$name> value=<$value>)" );
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (REF_HANDLE) : file-stream for parsing
|
|
# IN (STRING) : name of section
|
|
# IN (STRING) : set of comment chars
|
|
# IN (REF_HASH) : hash for parsed values
|
|
# IN (REF_FUNCTION): line parsing function
|
|
# OUT(BOOL) : was the section found ?
|
|
sub ParseSection
|
|
{
|
|
my ( $ref_handle, $section, $comments, $ref_hash, $ParseLine ) = @_;
|
|
my $is_my_section = 0;
|
|
my $found = 0;
|
|
|
|
&Log( 'debug', "ParseSection($section,$comments) has been called ..." );
|
|
local $_;
|
|
while( <$ref_handle> )
|
|
{
|
|
# Perl Cookbook
|
|
chomp;
|
|
if ( s/\\$// ) {
|
|
$_ .= <$ref_handle>;
|
|
redo unless eof $ref_handle;
|
|
}
|
|
s/[$comments].*//;
|
|
s/^\s+//;
|
|
s/\s+$//;
|
|
next unless length;
|
|
|
|
$is_my_section = ( lc( $1 ) eq $section ), next if /^\[[\s\t]*(.+?)[\s\t]*\]$/;
|
|
next unless $is_my_section;
|
|
$found = 1;
|
|
|
|
$ParseLine->($_, $ref_hash);
|
|
}
|
|
&Log( 'debug', "ParseSection($section,$comments) return( $found )" );
|
|
return $found;
|
|
}
|
|
|
|
### LOG ######################################################################
|
|
#-----------------------------------------------------------------------------
|
|
# IN (STRING): log level
|
|
# IN (STRING): filename of log
|
|
# OUT: no
|
|
sub OpenLog
|
|
{
|
|
my ($Level, $Facility, $File) = @_;
|
|
$LogLevel = $Levels{$Level}[0];
|
|
return unless $LogLevel;
|
|
unless( 'syslog' eq $File ) {
|
|
open LOGHANDLE, ">>$File"
|
|
or &DrDie( &EXIT_STATUS_SYSTEM_ERROR, 'Dr.Web Updater ($Id: fbb3ca1734c5e75ae3008acef02c2f1032ac44cf $) error: can not open logfile '.$File.' for appending - '.$! );
|
|
} else {
|
|
my $program = $0;
|
|
1 while $program =~ s!.+/!!g;
|
|
openlog( $program, 'pid', $Facility );
|
|
$syslog = 1;
|
|
}
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN : no
|
|
# OUT: no
|
|
sub CloseLog {
|
|
closelog if $syslog;
|
|
$syslog = 0;
|
|
return unless $LogLevel;
|
|
close LOGHANDLE;
|
|
$log_cache = [] unless 'main::DrDie' eq ( ( caller( 1 ) )[ 3 ] or '' );
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (STRING): error message
|
|
# IN (INT): no socket
|
|
# OUT: no
|
|
sub DrDie {
|
|
my ($err_code, $error) = @_;
|
|
&Log( 'error', $error );
|
|
|
|
#could write to socket about the failure
|
|
if( defined $Conf->{ Self }{ shouldnotify } && 'yes' eq lc $Conf->{ Self }{ shouldnotify } ) {
|
|
&WriteToSocket($Conf->{ Self }{ notifysocket }, $error);
|
|
}
|
|
|
|
if ( $Task and $Conf ) {
|
|
#&RemoveFiles( [ values %{$Task->{new_notify}},values %{$Task->{new}}], $Conf, 0 );
|
|
&ViewSummary($Conf,$Task);
|
|
}
|
|
&Unlock();
|
|
&saveLastUpdateCacheData();
|
|
&CloseLog();
|
|
print STDERR "See Dr.Web Updater log file for details.\n" unless $log_cache;
|
|
|
|
exit($err_code ? $err_code : &EXIT_STATUS_SYSTEM_ERROR);
|
|
}
|
|
|
|
### Key utils ################################################################
|
|
#-----------------------------------------------------------------------------
|
|
# IN (STRING): filename of key
|
|
# OUT(ARRAY) : list (generator version, user number, expired date)
|
|
sub ReadKeyFile
|
|
{
|
|
my $file = shift;
|
|
|
|
&Log( 'debug', "ReadKeyFile($file) has been called ..." );
|
|
|
|
my $fd = new FileHandle;
|
|
open( $fd, "$file" )
|
|
or &DrDie(&EXIT_STATUS_KEY_FILE_ACCESS_ERROR, "can not open the key file $file ($!) for reading");
|
|
|
|
my %KeyParam = (
|
|
'user' => {
|
|
gv => '',
|
|
number => '',
|
|
},
|
|
'key' => {
|
|
expires => ''
|
|
},
|
|
);
|
|
for(keys %KeyParam) {
|
|
seek($fd,0,0);
|
|
&ParseSection( $fd, $_, ';', $KeyParam{$_} ,\&ParseLineStd )
|
|
or &DrDie(&EXIT_STATUS_INVALID_KEY, "can not find [$_] section in key $file - invalid key");
|
|
}
|
|
|
|
close $fd;
|
|
|
|
&Log( 'debug', "ReadKeyFile($file) return( $KeyParam{user}{gv}, $KeyParam{user}{number}, $KeyParam{key}{expires} )" );
|
|
|
|
return ( $KeyParam{user}{gv}, $KeyParam{user}{number}, $KeyParam{key}{expires} );
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): filename of key
|
|
# OUT(ARRAY) : pair (md5 of key, user number)
|
|
sub ParseKey
|
|
{
|
|
my ($Conf,$keyFile) = ($_[0],$_[0]->{Object}{key});
|
|
|
|
&Log( 'debug', "ParseKey($keyFile) has been called ..." );
|
|
|
|
if( $keyFile eq '' or ! -e $keyFile )
|
|
{
|
|
$Conf->{Self}{key_md5}='e52ee6293550298ec4d7e5c57ade18e5';
|
|
$Conf->{Self}{key_num}='0000000000';
|
|
$Conf->{Self}{plesk_trial_key}=1;
|
|
&Log( 'debug', "ParseKey(PleskTrial) return( e52ee6293550298ec4d7e5c57ade18e5, 0000000000 )" );
|
|
return ( 'e52ee6293550298ec4d7e5c57ade18e5', '0000000000' );
|
|
}
|
|
|
|
( $Conf->{Self}{genver}, $Conf->{Self}{key_num}, $Conf->{Self}{key_expired} ) = &ReadKeyFile($keyFile);
|
|
|
|
$Conf->{Self}{key_md5} = MD5($keyFile);
|
|
&DrDie(&EXIT_STATUS_INVALID_KEY, "can not get md5 sum of $keyFile" ) if $Conf->{Self}{key_md5} eq '';
|
|
|
|
&Log( 'debug', "ParseKey($keyFile) return( $Conf->{Self}{key_md5}, $Conf->{Self}{key_num}, $Conf->{Self}{key_expired} )" );
|
|
return ( $Conf->{Self}{key_md5}, $Conf->{Self}{key_num}, $Conf->{Self}{key_expired} );
|
|
}
|
|
|
|
|
|
### Utils ###################################################################
|
|
#----------------------------------------------------------------------------
|
|
# IN : no
|
|
# OUT(STRING): path to md5
|
|
sub FindMD5
|
|
{
|
|
my $md5sum = &Which("md5sum");
|
|
if( not $md5sum )
|
|
{
|
|
$md5sum = &Which($^O =~ /^solaris$/i ? "digest" : "md5");
|
|
&DrDie( &EXIT_STATUS_SYSTEM_ERROR, 'The md5sum or md5 utilities are not found in ('.$ENV{PATH}.')' ) unless $md5sum;
|
|
&Log( 'verbose', 'md5 found in: '.$md5sum );
|
|
$md5sum .= " -a md5" if $^O =~ /^solaris$/i;
|
|
} else {
|
|
&Log( 'verbose', 'md5sum found in: '.$md5sum );
|
|
}
|
|
&Log( 'debug', "FindMD5: return ($md5sum)" );
|
|
return $md5sum;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (STRING) : path to file
|
|
# OUT(STRING) : filename
|
|
sub Basename {
|
|
my $path = shift;
|
|
$path =~ s!.+/!!o;
|
|
return $path;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (STRING) : path
|
|
# OUT(STRING) : dirname
|
|
sub Dirname {
|
|
my $path = shift;
|
|
-d $path or $path =~ s!/[^/]*$!!;
|
|
$path =~ s!/*$!/!;
|
|
return $path;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): updater configuration
|
|
# OUT(STRING) : path to file
|
|
sub FullPath {
|
|
my ( $file, $conf ) = @_;
|
|
&Log( 'debug', 'FullPath( ' . $file . ' ) has been called...' );
|
|
my $path =
|
|
&IsFlyTrapUpdate( $file ) ? ( $conf->{ Paths }{ flytrap } . $file ) :
|
|
&IsBase( $file ) ? ( $conf->{ Paths }{ bases } . $file ) :
|
|
&IsUpdateDrl( $file ) ? $conf->{ Self }{ drlfile } :
|
|
&IsEngine( $file ) ? $conf->{ Object }{ enginepath } :
|
|
&IsVadeRetro( $file ) ? $conf->{ Self }{ pathtovaderetro } :
|
|
&IsDrl( $file ) ? ( $conf->{ Paths }{ drl } . $file ) :
|
|
&IsBlacklist( $file ) ? ( $conf->{ Paths }{ blacklist } . $file ):
|
|
&IsTimestamp( $file ) ? ( $conf->{Paths }{ bases} . $file) :
|
|
( &IsUpdater( $file ) &&
|
|
( 'yes' eq lc( $conf->{ Self }{ updateself } ) ) )
|
|
? $conf->{ Paths }{ self } :
|
|
( $conf->{ Paths }{ update } . $file ) ;
|
|
&Log( 'debug', 'FullPath( ' . $file . ' ) return( ' . $path . ' )' );
|
|
return $path;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): filename
|
|
# OUT(SCALAR): md5 of filename
|
|
sub MD5
|
|
{
|
|
my $file = shift;
|
|
|
|
my $md5 = `$Conf->{Self}{md5sum} "$file"`;
|
|
chomp $md5;
|
|
$md5 =~ s/\-//go;
|
|
$md5 =~ s/(.*)([0-9a-fA-F]{32})(.*)/$2/o;
|
|
|
|
return $md5;
|
|
}
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): filename
|
|
# OUT(SCALAR): crc32 of filename
|
|
sub Crc32
|
|
{
|
|
my $function;
|
|
if (eval { local $SIG{__DIE__}; require Compress::Zlib })
|
|
{
|
|
require Compress::Zlib;
|
|
|
|
$function = sub
|
|
{
|
|
my $file = shift;
|
|
|
|
my ($c,$s);
|
|
if( !sysopen( F, $file,O_RDONLY ) )
|
|
{
|
|
&Log( 'error', "can not open $file ($!) for calculating CRC32" );
|
|
return 0x0;
|
|
}
|
|
|
|
while( sysread F, $s, 10240 )
|
|
{
|
|
$c = &Compress::Zlib::crc32($s,$c);
|
|
}
|
|
close( F );
|
|
&Log( 'debug', "CRC32( $file ) return( @{[ sprintf '%X', $c ]} )" );
|
|
return sprintf '%u', $c;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
my $code = <<'CODE';
|
|
$function = sub
|
|
{
|
|
my $file = shift;
|
|
&Log( 'debug', "CRC32($file) has been called ..." );
|
|
if( !sysopen( F, $file,O_RDONLY ) )
|
|
{
|
|
&Log( 'error', "can not open $file ($!) for calculating CRC32" );
|
|
return 0x0;
|
|
}
|
|
my $n = 0;
|
|
my $c = 0xffffffff;
|
|
my @crc32table =
|
|
(
|
|
0x00000000, 0x77073096, 0xee0e612c, 0x990951ba, 0x076dc419,
|
|
0x706af48f, 0xe963a535, 0x9e6495a3, 0x0edb8832, 0x79dcb8a4,
|
|
0xe0d5e91e, 0x97d2d988, 0x09b64c2b, 0x7eb17cbd, 0xe7b82d07,
|
|
0x90bf1d91, 0x1db71064, 0x6ab020f2, 0xf3b97148, 0x84be41de,
|
|
0x1adad47d, 0x6ddde4eb, 0xf4d4b551, 0x83d385c7, 0x136c9856,
|
|
0x646ba8c0, 0xfd62f97a, 0x8a65c9ec, 0x14015c4f, 0x63066cd9,
|
|
0xfa0f3d63, 0x8d080df5, 0x3b6e20c8, 0x4c69105e, 0xd56041e4,
|
|
0xa2677172, 0x3c03e4d1, 0x4b04d447, 0xd20d85fd, 0xa50ab56b,
|
|
0x35b5a8fa, 0x42b2986c, 0xdbbbc9d6, 0xacbcf940, 0x32d86ce3,
|
|
0x45df5c75, 0xdcd60dcf, 0xabd13d59, 0x26d930ac, 0x51de003a,
|
|
0xc8d75180, 0xbfd06116, 0x21b4f4b5, 0x56b3c423, 0xcfba9599,
|
|
0xb8bda50f, 0x2802b89e, 0x5f058808, 0xc60cd9b2, 0xb10be924,
|
|
0x2f6f7c87, 0x58684c11, 0xc1611dab, 0xb6662d3d, 0x76dc4190,
|
|
0x01db7106, 0x98d220bc, 0xefd5102a, 0x71b18589, 0x06b6b51f,
|
|
0x9fbfe4a5, 0xe8b8d433, 0x7807c9a2, 0x0f00f934, 0x9609a88e,
|
|
0xe10e9818, 0x7f6a0dbb, 0x086d3d2d, 0x91646c97, 0xe6635c01,
|
|
0x6b6b51f4, 0x1c6c6162, 0x856530d8, 0xf262004e, 0x6c0695ed,
|
|
0x1b01a57b, 0x8208f4c1, 0xf50fc457, 0x65b0d9c6, 0x12b7e950,
|
|
0x8bbeb8ea, 0xfcb9887c, 0x62dd1ddf, 0x15da2d49, 0x8cd37cf3,
|
|
0xfbd44c65, 0x4db26158, 0x3ab551ce, 0xa3bc0074, 0xd4bb30e2,
|
|
0x4adfa541, 0x3dd895d7, 0xa4d1c46d, 0xd3d6f4fb, 0x4369e96a,
|
|
0x346ed9fc, 0xad678846, 0xda60b8d0, 0x44042d73, 0x33031de5,
|
|
0xaa0a4c5f, 0xdd0d7cc9, 0x5005713c, 0x270241aa, 0xbe0b1010,
|
|
0xc90c2086, 0x5768b525, 0x206f85b3, 0xb966d409, 0xce61e49f,
|
|
0x5edef90e, 0x29d9c998, 0xb0d09822, 0xc7d7a8b4, 0x59b33d17,
|
|
0x2eb40d81, 0xb7bd5c3b, 0xc0ba6cad, 0xedb88320, 0x9abfb3b6,
|
|
0x03b6e20c, 0x74b1d29a, 0xead54739, 0x9dd277af, 0x04db2615,
|
|
0x73dc1683, 0xe3630b12, 0x94643b84, 0x0d6d6a3e, 0x7a6a5aa8,
|
|
0xe40ecf0b, 0x9309ff9d, 0x0a00ae27, 0x7d079eb1, 0xf00f9344,
|
|
0x8708a3d2, 0x1e01f268, 0x6906c2fe, 0xf762575d, 0x806567cb,
|
|
0x196c3671, 0x6e6b06e7, 0xfed41b76, 0x89d32be0, 0x10da7a5a,
|
|
0x67dd4acc, 0xf9b9df6f, 0x8ebeeff9, 0x17b7be43, 0x60b08ed5,
|
|
0xd6d6a3e8, 0xa1d1937e, 0x38d8c2c4, 0x4fdff252, 0xd1bb67f1,
|
|
0xa6bc5767, 0x3fb506dd, 0x48b2364b, 0xd80d2bda, 0xaf0a1b4c,
|
|
0x36034af6, 0x41047a60, 0xdf60efc3, 0xa867df55, 0x316e8eef,
|
|
0x4669be79, 0xcb61b38c, 0xbc66831a, 0x256fd2a0, 0x5268e236,
|
|
0xcc0c7795, 0xbb0b4703, 0x220216b9, 0x5505262f, 0xc5ba3bbe,
|
|
0xb2bd0b28, 0x2bb45a92, 0x5cb36a04, 0xc2d7ffa7, 0xb5d0cf31,
|
|
0x2cd99e8b, 0x5bdeae1d, 0x9b64c2b0, 0xec63f226, 0x756aa39c,
|
|
0x026d930a, 0x9c0906a9, 0xeb0e363f, 0x72076785, 0x05005713,
|
|
0x95bf4a82, 0xe2b87a14, 0x7bb12bae, 0x0cb61b38, 0x92d28e9b,
|
|
0xe5d5be0d, 0x7cdcefb7, 0x0bdbdf21, 0x86d3d2d4, 0xf1d4e242,
|
|
0x68ddb3f8, 0x1fda836e, 0x81be16cd, 0xf6b9265b, 0x6fb077e1,
|
|
0x18b74777, 0x88085ae6, 0xff0f6a70, 0x66063bca, 0x11010b5c,
|
|
0x8f659eff, 0xf862ae69, 0x616bffd3, 0x166ccf45, 0xa00ae278,
|
|
0xd70dd2ee, 0x4e048354, 0x3903b3c2, 0xa7672661, 0xd06016f7,
|
|
0x4969474d, 0x3e6e77db, 0xaed16a4a, 0xd9d65adc, 0x40df0b66,
|
|
0x37d83bf0, 0xa9bcae53, 0xdebb9ec5, 0x47b2cf7f, 0x30b5ffe9,
|
|
0xbdbdf21c, 0xcabac28a, 0x53b39330, 0x24b4a3a6, 0xbad03605,
|
|
0xcdd70693, 0x54de5729, 0x23d967bf, 0xb3667a2e, 0xc4614ab8,
|
|
0x5d681b02, 0x2a6f2b94, 0xb40bbe37, 0xc30c8ea1, 0x5a05df1b,
|
|
0x2d02ef8d
|
|
);
|
|
{
|
|
no locale;
|
|
CODE
|
|
$code .= ( $] < 5.006 ? '' : " use bytes;\n");
|
|
$code .= <<'CODE';
|
|
my $s;
|
|
while ($n=sysread F, $s,10240) {
|
|
$c = $crc32table[ ( $c ^ ord( substr( $s, -$n--, 1 ) ) ) & 0xFF ] ^ ( $c >> 8 ) while $n;
|
|
}
|
|
$c = $c ^ 0xffffffff;
|
|
}
|
|
close( F );
|
|
&Log( 'debug', "CRC32( $file ) return( @{[ sprintf '%X', $c ]} )" );
|
|
return sprintf '%u', $c;
|
|
}
|
|
CODE
|
|
eval $code;
|
|
if ($@) {
|
|
die "can not eval ($@)";
|
|
}
|
|
}
|
|
return $function;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (ARRAY) : array for read urls
|
|
# IN (STRING): filename of reader signed files
|
|
# IN (STRING): filename of URL list
|
|
# IN (STRING): name of URL list
|
|
# OUT(SCALAR): 1 - success, 0 - fail
|
|
|
|
sub ReadDrl
|
|
{
|
|
my( $URLS, $SignedReader, $DRL, $comment ) = @_;
|
|
|
|
if( $DRL eq '' )
|
|
{
|
|
&Log( 'verbose', "$comment URL list isn't defined" );
|
|
return 0;
|
|
}
|
|
if( not -r $DRL )
|
|
{
|
|
&Log( 'warning', "$DRL: no such file or no permissions for reading $comment URL list" );
|
|
return 0;
|
|
}
|
|
|
|
&Log( 'info', "try using $comment URL list ($DRL)" );
|
|
@{$URLS} = (), return 0 unless &ReadSigned( $SignedReader, 'drl', $DRL, $URLS );
|
|
|
|
if( 0 == scalar @{$URLS} )
|
|
{
|
|
&Log( 'warning', "no valid URL has been read from $comment URL list ($DRL)" );
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#---------------------------------------------------------------------------
|
|
# IN (STRING) : path to readsigned
|
|
# IN (STRING) : path to file
|
|
# OUT (SCALAR): 1 - success, 0 - fail
|
|
sub checkKeyFile
|
|
{
|
|
my ($SignedReader, $file) = @_;
|
|
|
|
my @content = ();
|
|
my $result;
|
|
unless($result = ReadSigned($SignedReader, 'key', $file, \@content)) {
|
|
foreach (@content) {
|
|
&Log( 'error', $_ );
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): a signs mode
|
|
# IN (STRING): a filename of a signed file
|
|
# IN (ARRAY) : a reference on an array for a signed content
|
|
# OUT(SCALAR): 1 - success, 0 - fail
|
|
sub ReadSigned
|
|
{
|
|
my ( $SignedReader, $mode, $file, $content ) = @_;
|
|
|
|
&Log( 'verbose', "exec($SignedReader $mode $file) ..." );
|
|
if( !open FH, "\"$SignedReader\" $mode \"$file\" 2>&1 |" )
|
|
{
|
|
&Log( 'error', "error during executing: $SignedReader $mode $file ($!)" );
|
|
return 0;
|
|
}
|
|
while ( <FH> )
|
|
{
|
|
s/\r?\n$//; # remove \r\n, \n
|
|
push @{$content}, $_;
|
|
}
|
|
if( !close FH )
|
|
{
|
|
&Log( 'error', $! ? "exec($SignedReader $mode $file) closes pipe with status: $!"
|
|
: "exec($SignedReader $mode $file) exits with error #".($?>>8) );
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): Files for downloading
|
|
# IN (HASH REF): Downloaded files
|
|
# IN (SCALAR) : Update only ?
|
|
# IN (HASH REF): Updater configuration
|
|
# IN (STRING) : filename format
|
|
# IN (STRING) : basename format
|
|
# IN (STRING) : component
|
|
# OUT(HASH REF): Files to download
|
|
sub FilesFilter
|
|
{
|
|
my( $Files, $Downloaded, $UpdateOnly, $Config, $filename_format, $basename_format, $component ) = @_;
|
|
|
|
my %buffer_hash = ();
|
|
foreach my $file ( sort keys %{ $Files } ) {
|
|
my $file_pretty_print = sprintf( $filename_format, $file );
|
|
my $filename = &Basename($file);
|
|
my $filename_pretty_print = sprintf( $basename_format, $filename );
|
|
my $full = &FullPath( $filename, $Conf );
|
|
my $check_sum = $Files->{$file};
|
|
if (
|
|
1 == $UpdateOnly
|
|
and
|
|
(
|
|
!(
|
|
IsVadeRetro( $file )
|
|
or -e $full
|
|
)
|
|
or
|
|
(
|
|
IsVadeRetro( $file )
|
|
and ! -e $full . '.cache'
|
|
)
|
|
)
|
|
)
|
|
{
|
|
&Log( 'info', $full . ' for updating by ' . $file_pretty_print . ' doesn\'t exist, skipped...' );
|
|
next;
|
|
}
|
|
|
|
if (&IsTimestamp( $file )) {
|
|
if (-e $full && $check_sum eq &getCheckSum($full) &&
|
|
-e $Config->{ Paths }{ update }.$file && $check_sum eq &getCheckSum($Config->{ Paths }{ update } . $file)
|
|
) {
|
|
&Log( 'info', $full . ' - ' . $filename_pretty_print . ' with such check sum already exists, skipped...' );
|
|
next;
|
|
}
|
|
} else {
|
|
if(
|
|
$check_sum ne ''
|
|
and (
|
|
(
|
|
(&IsVadeRetro( $file ) or &IsBlacklist( $file ))
|
|
and -e $full . '.cache' and $check_sum eq &getCheckSum($full . '.cache')
|
|
)
|
|
or ( -e $full and $check_sum eq &getCheckSum( $full ) )
|
|
)
|
|
) {
|
|
&Log( 'info', $full . ' - ' . $filename_pretty_print . ' with such check sum already exists, skipped...' );
|
|
next;
|
|
}
|
|
}
|
|
|
|
my $downloaded_file = &getInfoFromLastUpdateCacheData($component, $file);
|
|
if(
|
|
defined $downloaded_file &&
|
|
$downloaded_file->{check_sum} eq $check_sum &&
|
|
-e $downloaded_file->{full}
|
|
) {
|
|
$Downloaded->{$full} = $downloaded_file->{full};
|
|
&Log( 'info', $file_pretty_print . ' has been downloaded in previous tries, skipped...' );
|
|
next;
|
|
}
|
|
|
|
$buffer_hash{$file} = {
|
|
file_pretty_print => $file_pretty_print,
|
|
server_check_sum => $check_sum,
|
|
full => $full,
|
|
filename => $filename,
|
|
};
|
|
}
|
|
|
|
return \%buffer_hash;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH) : Files for downloading
|
|
# IN (HASH) : Downloaded files
|
|
# IN (SCALAR): Update only ?
|
|
# IN (STRING): URL of update server
|
|
# IN (HASH) : Updater configuration
|
|
# IN (STRING): component
|
|
# OUT(SCALAR): 1 - success, 0 or undef - fail
|
|
sub GetLocalFiles
|
|
{
|
|
my( $Files, $Downloaded, $UpdateOnly, $Process, $Config, $component ) = @_;
|
|
|
|
my ( $max_filename_length, $max_basename_length ) = ( ( 0 ) x 2 );
|
|
foreach my $filename ( keys %{ $Files } ) {
|
|
my $basename = &Basename( $filename );
|
|
$max_filename_length = length( $filename ) if length( $filename ) > $max_filename_length;
|
|
$max_basename_length = length( $basename ) if length( $basename ) > $max_basename_length;
|
|
}
|
|
my $filename_format = '%' . $max_filename_length . 's';
|
|
my $basename_format = '%' . $max_basename_length . 's';
|
|
|
|
my $buffer_hash = &FilesFilter($Files, $Downloaded, $UpdateOnly, $Config, $filename_format, $basename_format, $component);
|
|
|
|
foreach my $file ( sort keys %{$buffer_hash} ) {
|
|
my $file_info = $buffer_hash->{$file};
|
|
my $file_pretty_print = $file_info->{file_pretty_print};
|
|
my $full = $file_info->{full};
|
|
|
|
print $file,"\n";
|
|
&Log( 'info', 'File \'' . $file_pretty_print . '\' has been added for processing.' );
|
|
|
|
$Downloaded->{$full} = $Config->{ Paths }{ downloaddir } . $file;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH) : Files for downloading
|
|
# IN (HASH) : Downloaded files
|
|
# IN (SCALAR): Update only ?
|
|
# IN (STRING): URL of update server
|
|
# IN (HASH) : Updater configuration
|
|
# IN (HASH) : Already downloaded files
|
|
# OUT(SCALAR): 1 - success, 0 or undef - fail
|
|
sub DownloadFiles
|
|
{
|
|
my( $Files, $Downloaded, $UpdateOnly, $Process, $Config, $component ) = @_;
|
|
|
|
my ( $max_filename_length, $max_basename_length ) = ( ( 0 ) x 2 );
|
|
foreach my $filename ( keys %{ $Files } ) {
|
|
my $basename = &Basename( $filename );
|
|
$max_filename_length = length( $filename ) if length( $filename ) > $max_filename_length;
|
|
$max_basename_length = length( $basename ) if length( $basename ) > $max_basename_length;
|
|
}
|
|
my $filename_format = '%' . $max_filename_length . 's';
|
|
my $basename_format = '%' . $max_basename_length . 's';
|
|
|
|
my $buffer_hash = &FilesFilter($Files, $Downloaded, $UpdateOnly, $Config, $filename_format, $basename_format, $component);
|
|
|
|
foreach my $file ( sort keys %{$buffer_hash} ) {
|
|
my $file_info = $buffer_hash->{$file};
|
|
my $file_pretty_print = $file_info->{file_pretty_print};
|
|
my $server_check_sum = $file_info->{server_check_sum};
|
|
my $filename = $file_info->{filename};
|
|
my $full = $file_info->{full};
|
|
my $loaded_check_sum = undef;
|
|
|
|
if( not $Downloaded->{$full} = &MkTemp( "$Conf->{Object}{temppath}/$filename.XXXXXXXX" )) {
|
|
&DrDie(&EXIT_STATUS_SYSTEM_ERROR, "can not create a temporary file in $Conf->{Object}{temppath}");
|
|
}
|
|
|
|
&Log( 'debug', "$Process->{URL}/$file will be downloaded to $Downloaded->{$full} ..." );
|
|
&schedule_unlink($Downloaded->{$full});
|
|
|
|
my $return_status = &GetHttpFile($Config,$Process,$file,$Downloaded->{$full},1);
|
|
if( not $return_status )
|
|
{
|
|
&Log( 'warning', 'can not download ' . $file_pretty_print );
|
|
if($UPDATE_ERROR == &EXIT_STATUS_INVALID_KEY) {
|
|
&Log( 'error', "Key is wrong or has been blocked" );
|
|
return undef;
|
|
}
|
|
if(-e $Downloaded->{$full}) {
|
|
unlink $Downloaded->{$full}
|
|
or &Log( 'warning', "can not delete temporary file $Downloaded->{$full} ($!)" );
|
|
}
|
|
delete $Downloaded->{$full};
|
|
return 0;
|
|
}
|
|
$loaded_check_sum = &{$SumCheker}( $Downloaded->{$full} );
|
|
if( $server_check_sum eq '' or $server_check_sum == $loaded_check_sum )
|
|
{
|
|
&Log( 'info', $file_pretty_print . ' has been downloaded (check sum=' . sprintf( '%8X', $loaded_check_sum ) . ')' );
|
|
if ($server_check_sum ne '') {
|
|
&insertInfoIntoLastUpdateCacheData($component, $file, $Downloaded->{$full}, $loaded_check_sum);
|
|
&unschedule_unlink($Downloaded->{$full});
|
|
}
|
|
next;
|
|
}
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_CHECKSUM;
|
|
&Log( 'warning', 'can not get ' . $file_pretty_print . ' with valid check sum (lst=' .
|
|
sprintf( '%8X', $server_check_sum ) . ', actual=' .
|
|
sprintf( '%8X', $loaded_check_sum ) . ')'
|
|
);
|
|
unlink $Downloaded->{$full}
|
|
or &Log( 'warning', "can not delete temporary file $Downloaded->{$full} ($!)" );
|
|
delete $Downloaded->{$full};
|
|
return 0;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): date (example '2009-04-01 (14:17) UTC')
|
|
# OUT(SCALAR): time in GMT
|
|
sub convertDataToGMT
|
|
{
|
|
my $expired_date = shift;
|
|
|
|
&Log("debug", "convertDataToGMT($expired_date) has been called ...");
|
|
|
|
my $year = substr($expired_date,0, 4) - 1900;
|
|
my $month = substr($expired_date,5, 2) - 1;
|
|
my $day = substr($expired_date,8, 2);
|
|
my $hour = substr($expired_date,12,2);
|
|
my $min = substr($expired_date,15,2);
|
|
my $sec = 0;
|
|
|
|
$expired_date = Time::Local::timegm($sec,$min,$hour,$day,$month,$year);
|
|
|
|
&Log("debug", "convertDataToGMT returned($expired_date)");
|
|
return $expired_date;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): config
|
|
# OUT(SCALAR) : 1 -- will be expired soon, 0 -- key is normal, -1 -- key is expired
|
|
sub isNeedToGetNewKey
|
|
{
|
|
my $cfg = shift;
|
|
|
|
&Log("debug", "isNeedToGetNewKey has been called ...");
|
|
|
|
my $expired_date = $cfg->{Self}{key_expired};
|
|
my $expired_time_limit = $cfg->{Self}{expiredtimelimit};
|
|
my $plesk_trial_key_flag = $cfg->{Self}{plesk_trial_key};
|
|
|
|
if($plesk_trial_key_flag) {
|
|
&Log("debug", "Plesk trial key has been detected!");
|
|
&Log("debug", "isNeedToGetNewKey returned(0)");
|
|
return 0;
|
|
}
|
|
|
|
$expired_date = convertDataToGMT($expired_date);
|
|
$expired_time_limit *= 24*60*60;
|
|
|
|
my $result = $expired_date - time();
|
|
|
|
if($result <= 0) {
|
|
$result = -1;
|
|
&Log("warning", "Key is expired, need to update");
|
|
} elsif ($result <= $expired_time_limit) {
|
|
$result = 1;
|
|
&Log("warning", "Key will be expired soon, need to update");
|
|
} else {
|
|
$result = 0;
|
|
&Log("info", "Key file is not needed to update");
|
|
}
|
|
|
|
&Log("debug", "isNeedToGetNewKey returned($result)");
|
|
return $result;
|
|
}
|
|
|
|
{
|
|
my $xml_header;
|
|
my $ident;
|
|
my $optsp;
|
|
my $att1;
|
|
my $att2;
|
|
my $att;
|
|
|
|
#----------------------------------------------------------------------------
|
|
sub initTerms
|
|
{
|
|
&Log("debug", "initTerms has been called ...");
|
|
|
|
$ident = "[:_A-Za-z][:A-Za-z0-9\\-\\._]*";
|
|
$optsp = "\\s*";
|
|
$att1 = "$ident$optsp=$optsp\"[^\"]*\"";
|
|
$att2 = "$ident$optsp=$optsp'[^']*'";
|
|
$att = "(?:$att1|$att2)";
|
|
$xml_header = '<\?xml(\s+'.$att.')*\s*\?>';
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : attributes in string format
|
|
# OUT(HASH REF): attributes as hash
|
|
sub parseAttrs
|
|
{
|
|
my $attrs = shift;
|
|
|
|
&Log("debug", "parseAttrs has been called ...");
|
|
|
|
my %result_data = ();
|
|
if(!defined $attrs || $attrs =~ /^\s*$/) {
|
|
return \%result_data;
|
|
}
|
|
|
|
$attrs =~ s/^\s*//;
|
|
|
|
my ($item,$name,$value);
|
|
while($attrs =~ /^($att)(?:\s+|$)/) {
|
|
$attrs = $';
|
|
|
|
$item = $1;
|
|
$item =~ /^($ident)$optsp=$optsp(.*)/;
|
|
|
|
$name = $1;
|
|
|
|
$value = $2;
|
|
substr($value, 0,1) = '';
|
|
substr($value,-1,1) = '';
|
|
|
|
$result_data{$name} = $value;
|
|
}
|
|
|
|
return \%result_data;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : element name
|
|
# IN (STRING) : attributes in string format
|
|
# OUT(HASH REF): element as hash
|
|
sub newElement
|
|
{
|
|
my $name = shift;
|
|
my $attrs = shift;
|
|
my $chars = shift;
|
|
|
|
&Log("debug", "newElement has been called ...");
|
|
|
|
return {
|
|
name => $name,
|
|
children => [],
|
|
attrs => parseAttrs($attrs),
|
|
chars => $chars,
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : XML as plain text
|
|
# OUT(HASH REF): XML as hash on success or undef
|
|
sub parseXML
|
|
{
|
|
my $data = shift;
|
|
|
|
&Log("debug", "parseXml has been called ...");
|
|
|
|
unless (defined $data) {
|
|
return undef;
|
|
}
|
|
|
|
initTerms();
|
|
|
|
my $parsed_data = newElement();
|
|
my @elements = ($parsed_data);
|
|
my $element;
|
|
my $chars;
|
|
my $start_document = 1;
|
|
while(length $data) {
|
|
if ($data =~ /^$xml_header/) {
|
|
unless($start_document) {
|
|
&Log("error", "XML header was founded in wrong place");
|
|
}
|
|
$data = $';
|
|
} elsif($data =~ /^<($ident)(\s+$att)*\s*\/>/) {
|
|
$data = $';
|
|
push @{$elements[-1]{children}}, newElement($1, $2, undef);
|
|
} elsif ($data =~ /^<($ident)(\s+$att)*\s*>/) {
|
|
$data = $';
|
|
push @{$elements[-1]{children}}, $element = newElement($1, $2, undef);
|
|
push @elements, $element;
|
|
} elsif ($data =~ m!^</($ident)\s*>!) {
|
|
$data = $';
|
|
$element = pop @elements;
|
|
unless($1 eq $element->{name}) {
|
|
&Log("error", "error in XML (name of closed is not equal to name of current opened tag):");
|
|
&Log("error", " closed tag name is '$1', opened tag name is '$element->{name}'");
|
|
return undef;
|
|
}
|
|
} elsif ($data =~ /^<!--/) {
|
|
$data = $';
|
|
if ($data =~ /-->/m) {
|
|
$data = $';
|
|
if($" =~ /--/) {
|
|
&Log("error", "error in XML (found forbidden sequence of symbols '--' in comments)");
|
|
return undef;
|
|
}
|
|
} else {
|
|
&Log("error", "error in XML (can not found end of the comments)");
|
|
return undef;
|
|
}
|
|
} elsif ($data =~ /^[\s]+/) {
|
|
$data = $';
|
|
} elsif ($data =~ /(^[^<>]+)/) {
|
|
$data = $';
|
|
$chars = $1;
|
|
if($chars =~ /\S/ && @elements <= 1) {
|
|
&Log("error", "error in XML (found characters out of tags)");
|
|
return undef;
|
|
}
|
|
$elements[-1]{chars} = $chars;
|
|
} else {
|
|
print STDERR $data, "\n";
|
|
&Log("error", "error in XML (unknown data)");
|
|
return undef;
|
|
}
|
|
$start_document = 0;
|
|
}
|
|
|
|
if(@elements != 1) {
|
|
&Log("error", "error in XML (found unclosed tags)");
|
|
return undef;
|
|
}
|
|
return $parsed_data;
|
|
}
|
|
|
|
sub getFirstChildElementChars
|
|
{
|
|
my ($data, $name) = @_;
|
|
|
|
&Log("debug", "getFirstChildElementChars has been called ...");
|
|
|
|
$name = lc $name;
|
|
|
|
my $result = undef;
|
|
foreach(@{$data->{children}}) {
|
|
if (lc $_->{name} eq $name) {
|
|
$result = $_->{chars};
|
|
last;
|
|
}
|
|
}
|
|
return $result;
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): XML as plaint text
|
|
# OUT(SCALAR): hash with data from XML -- if success, false -- if unsuccess
|
|
sub parseResponse
|
|
{
|
|
my $data = shift;
|
|
|
|
&Log("debug", "parseResponse has been called ...");
|
|
|
|
$data = parseXML($data);
|
|
|
|
unless(defined $data) {
|
|
return undef;
|
|
}
|
|
|
|
$data = $data->{children}[0];
|
|
if($data->{name} ne 'response') {
|
|
return undef;
|
|
}
|
|
|
|
my $key = getFirstChildElementChars($data, 'key');
|
|
my $status = getFirstChildElementChars($data, 'status');
|
|
|
|
return {status => $status, key => $key};
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): config
|
|
# IN (HASH REF): information about process update
|
|
# IN (STRING) : name of temporary key file
|
|
# OUT(SCALAR) : 1 -- if success, false -- if unsuccess
|
|
sub replaceToNewKeyFile
|
|
{
|
|
my ($cfg,$Task,$new_key_file) = @_;
|
|
|
|
&Log("debug", "replaceToNewKeyFile has been called ...");
|
|
|
|
my $suffix = '';
|
|
unless( MoveFile($new_key_file, $cfg->{Object}{key}, $suffix)) {
|
|
&Log("error", "Can not update key file '$cfg->{Object}{key}'");
|
|
return undef;
|
|
}
|
|
|
|
${$Task}->{moved} = [$cfg->{Object}{key}];
|
|
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): config
|
|
# IN (STRING) : text data to write into file
|
|
# OUT(SCALAR) : 1 -- if success, false -- if unsuccess
|
|
sub writeDataIntoFile
|
|
{
|
|
my ($file,$data) = @_;
|
|
|
|
&Log( 'debug', "writeDataIntoFile has been called ..." );
|
|
|
|
my $fd;
|
|
unless( open($fd,'>',$file) ) {
|
|
&Log( 'error', "Can not open file '$file' ($!)" );
|
|
return undef;
|
|
}
|
|
|
|
unless(print( $fd $$data)) {
|
|
&Log( 'error', "Can not print into file '$file' ($!)" );
|
|
return undef;
|
|
}
|
|
|
|
unless( close($fd) ) {
|
|
&Log( 'error', "Can not close file '$file' ($!)" );
|
|
return undef;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): config
|
|
# IN (STRING) : text encoded base64
|
|
# OUT(SCALAR) : file name -- if success, false -- if unsuccess
|
|
sub makeNewKeyFile
|
|
{
|
|
my ($cfg, $key) = @_;
|
|
|
|
&Log( 'debug', "makeNewKeyFile has been called ..." );
|
|
|
|
my $file = $cfg->{ Object }{ key };
|
|
$file =~ /([^\/]+)$/;
|
|
$file = $1;
|
|
$file = $cfg->{ Object }{ temppath } . '/' . $file if $cfg->{ Object }{ temppath } !~ /\/$/;
|
|
|
|
|
|
my $tempfile = MkTemp("$file.XXXXXXXX");
|
|
|
|
unless(defined $tempfile) {
|
|
&Log( 'error', "Can not create temp file");
|
|
return undef;
|
|
}
|
|
|
|
$key = DecodeBase64($key);
|
|
|
|
unless(defined writeDataIntoFile($tempfile, \$key)) {
|
|
return undef;
|
|
}
|
|
|
|
&Log( 'debug', "Check loaded keyfile");
|
|
unless(checkKeyFile($cfg->{Self}{signedreader},$tempfile)) {
|
|
return undef;
|
|
}
|
|
|
|
return $tempfile;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): config
|
|
# OUT(SCALAR) : 1 -- if returned status is wrong, 0 -- if returned status is normal
|
|
sub isWrongStatus
|
|
{
|
|
my $status = shift;
|
|
|
|
&Log( 'debug', "isWrongStatus($status) has been called ..." );
|
|
|
|
$status = lc $status;
|
|
|
|
if ($status eq 'renew') {
|
|
return 0;
|
|
}
|
|
|
|
my $level;
|
|
my $human_readable_answer;
|
|
if ($status eq 'blocked') {
|
|
$level = 'warning';
|
|
$human_readable_answer = "Key is blocked";
|
|
} elsif ($status eq 'no renew') {
|
|
$level = 'warning';
|
|
$human_readable_answer = "No new key";
|
|
} elsif ($status eq 'no renew yet') {
|
|
$level = 'info';
|
|
$human_readable_answer = "Request on new key is accepted. Try again later.";
|
|
} elsif ($status eq 'error') {
|
|
$level = 'error';
|
|
$human_readable_answer = "Server side error. Try again later";
|
|
} elsif($status eq 'key not found') {
|
|
$level = 'warning';
|
|
$human_readable_answer = "No new key";
|
|
} else {
|
|
$level = 'error';
|
|
$human_readable_answer = "Received unknown status '$status' from server";
|
|
}
|
|
|
|
&Log($level, $human_readable_answer);
|
|
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): config
|
|
# IN (HASH REF): information about process update
|
|
# OUT (STRING) : result as string
|
|
sub GetHttpKey
|
|
{
|
|
my ($cfg,$Task) = @_;
|
|
|
|
&Log( 'debug', "GetHttpKey has been called ..." );
|
|
|
|
my $Process = { URL => $cfg->{Self}{keyupdatehost} };
|
|
my $headers = [];
|
|
my $path = '';
|
|
|
|
my $args = [
|
|
['k' => $Conf->{Self}{key_md5}],
|
|
];
|
|
if (defined $cfg->{Self}{dontcachekey}) {
|
|
push @$args, ['a' => 1];
|
|
}
|
|
|
|
my $save_to_buffer = 1;
|
|
my $result;
|
|
|
|
my $returned_header;
|
|
|
|
{
|
|
local $SIG{__DIE__} = 'IGNORE';
|
|
local $SIG{__WARN__} = 'IGNORE';
|
|
eval('require Net::SSLeay');
|
|
}
|
|
my $ssl_flag = $@ ? 0:1;
|
|
|
|
${$Task}->{process}{URL} = ($ssl_flag ? 'https://' : 'http://' ).$cfg->{Self}{keyupdatehost};
|
|
${$Task}->{process}{StartTime} = localtime;
|
|
|
|
for(my $process_tries = 5; $process_tries > 0; $process_tries--){
|
|
if ($ssl_flag) {
|
|
Log('debug','Found Net::SSLeay. Using HTTPS protocol to get key');
|
|
$returned_header = GetHttpsResponse(
|
|
$cfg,
|
|
$Process,
|
|
$headers,
|
|
$path,
|
|
$args,
|
|
$save_to_buffer,
|
|
\$result
|
|
);
|
|
} else {
|
|
$returned_header = GetHttpResponse(
|
|
$cfg,
|
|
$Process,
|
|
$headers,
|
|
$path,
|
|
$args,
|
|
$save_to_buffer,
|
|
\$result,
|
|
);
|
|
CloseSocket($cfg,$Process);
|
|
}
|
|
|
|
if (defined $returned_header) {
|
|
if ( $returned_header->{retval} == 200 ) {
|
|
${$Task}->{process}{EndTime} = localtime;
|
|
return $result;
|
|
}
|
|
|
|
if ($returned_header->{retval} == 403) {
|
|
&Log( 'warning',"Authorization failed" );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
} elsif ($returned_header->{retval} == 407) {
|
|
if (delete $Process->{auth_req}) {
|
|
&Log( 'error',"Proxy server authorization failed" );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
} elsif (not $Conf->{Self}{proxylogin} or not $Conf->{Self}{proxypassword}) {
|
|
&Log( 'error', "Proxy server authentication required" );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
} elsif ($ssl_flag) {
|
|
$Process->{auth_req} = 1;
|
|
} elsif (not &Authenticate($Conf,$Process,$returned_header,$path)) {
|
|
&Log( 'error', delete $Process->{error} );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
}
|
|
next;
|
|
}
|
|
|
|
&Log('error', 'Can not get new key file (' . $returned_header->{retval_string} . ')');
|
|
}
|
|
last;
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF): config
|
|
# IN (HASH REF): information about process update
|
|
# OUT(SCALAR) : 1 -- if success, 0 -- unsuccess
|
|
sub createNewKey
|
|
{
|
|
my ($cfg,$Task) = @_;
|
|
|
|
${$Task} = {};
|
|
|
|
&Log( 'debug', "createNewKey has been called ..." );
|
|
|
|
my $response = GetHttpKey($cfg,$Task);
|
|
|
|
unless (defined $response) {
|
|
return undef;
|
|
}
|
|
|
|
my $parsed_data = parseResponse($response);
|
|
unless(defined $parsed_data) {
|
|
return undef;
|
|
}
|
|
|
|
if(isWrongStatus($parsed_data->{status})) {
|
|
return undef;
|
|
}
|
|
|
|
my $temp_key_file = makeNewKeyFile($cfg,$parsed_data->{key});
|
|
unless($temp_key_file) {
|
|
return undef;
|
|
}
|
|
|
|
return replaceToNewKeyFile($cfg,$Task,$temp_key_file);
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : file path to file which need to copy
|
|
# IN (STRING) : file path to file where need to copy
|
|
# IN (STRING) : safe suffix
|
|
# IN (BOOLEAN): delete or net src-file
|
|
# OUT(SCALAR) : 1 - success, 0 - not success
|
|
sub MoveFile
|
|
{
|
|
my ($from,$to,$safetysuffix,$unlink) = @_;
|
|
|
|
&Log( 'debug', "MoveFile($from,$to,$safetysuffix,$unlink) has been called ..." );
|
|
|
|
unless(defined $safetysuffix) {
|
|
$safetysuffix = '';
|
|
}
|
|
|
|
my $CopyDir=&Dirname($to);
|
|
if( not defined $CopyDir or not -d $CopyDir )
|
|
{
|
|
&Log( 'error', "path $CopyDir does not exist. Can not move $to" );
|
|
return 0;
|
|
}
|
|
if(!-e $from)
|
|
{
|
|
&Log( 'error', "file $$from does not exist. Can not move $to" );
|
|
return 0;
|
|
}
|
|
|
|
if ( !$unlink || ( stat $from )[0] != ( stat $CopyDir )[0] )
|
|
{
|
|
&Log( 'verbose', "copy $from to $to" );
|
|
my ($error,$oldtempfile) = (0,$from);
|
|
if ( not open FH_FROM, "<$from" )
|
|
{
|
|
&Log( 'error', "can not open temporary file $from ($!)" );
|
|
$error = 1;
|
|
}
|
|
elsif ($unlink && not unlink $from)
|
|
{
|
|
&Log( 'warning', "can not unlink temporary file $from ($!)" );
|
|
$error = 1;
|
|
}
|
|
elsif ( not $from = &MkTemp( $to.".XXXXXXXX" ) )
|
|
{
|
|
&Log( 'error', "can not create a temporary file in $CopyDir");
|
|
$error = 1;
|
|
}
|
|
elsif ( not open FH_TO, ">$from")
|
|
{
|
|
&Log( 'error', "can not open temporary file $from ($!)" );
|
|
$error = 1;
|
|
}
|
|
else
|
|
{
|
|
my ($readed,$written,$tw,$buf);
|
|
while ($readed=sysread FH_FROM, $buf, 10240) {
|
|
for ( $written=0,$tw=0 ; $written < $readed and defined $tw; $written += $tw )
|
|
{
|
|
$tw = syswrite(FH_TO, $buf, $readed - $written, $written )
|
|
}
|
|
last unless defined $tw;
|
|
}
|
|
if ( not defined $readed )
|
|
{
|
|
&Log( 'warning', "can not read temporary file $oldtempfile ($!)" );
|
|
$error = 0;
|
|
}
|
|
elsif ( not defined $tw )
|
|
{
|
|
&Log( 'warning', "can not write temporary file $from ($!)" );
|
|
$error = 0;
|
|
}
|
|
close FH_TO;
|
|
}
|
|
close FH_FROM;
|
|
if($error) {
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
&Log( 'verbose', "move $from to $to$safetysuffix" );
|
|
}
|
|
if ( not rename $from, $to.$safetysuffix )
|
|
{
|
|
&Log( 'error', "can not move $from to $to$safetysuffix: $!" );
|
|
return 0;
|
|
} else {
|
|
my $now = time;
|
|
utime $now, $now, $to.$safetysuffix or
|
|
&Log( 'warning', "can not touch $to$safetysuffix" );
|
|
if ( 0 == $> ) {
|
|
&Log( 'verbose', 'Changing permissions on ' . $to.$safetysuffix . '. User ' . $Conf->{ Object }{ user } . ', group ' . $Conf->{ Object }{ group } );
|
|
chown( $Conf->{ Object }{ userid }, $Conf->{ Object }{ groupid }, $to.$safetysuffix ) or &Log( 'error', 'Unable to change permissions on ' . $to.$safetysuffix . ': ' . $! );
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH): Updater configuration
|
|
# IN (HASH): Files for moving
|
|
# IN (REF_ARRAY): Output array
|
|
# IN (REF_SUB) : Callback to sort files
|
|
# IN (REF_SUB) : Callback to process files
|
|
# OUT: no
|
|
sub MoveFiles
|
|
{
|
|
my ($Conf,$Files,$moved,$sort_callback, $process_callback) = @_;
|
|
|
|
my $files = defined $sort_callback
|
|
? $sort_callback->($Files)
|
|
: [sort keys %{$Files}];
|
|
|
|
$process_callback = sub { &MoveFile(@_) }
|
|
unless defined $process_callback;
|
|
|
|
my ($file,$safetysuffix,$unlink);
|
|
foreach $file ( @$files )
|
|
{
|
|
$safetysuffix = '';
|
|
if (&IsTimestamp($file)) {
|
|
my $filename = &Basename($file);
|
|
$unlink = 0;
|
|
foreach (($file, $Conf->{ Paths }{ update }.$filename)) {
|
|
unless( $process_callback->($Files->{$file},$_,$safetysuffix,$unlink) ) {
|
|
delete $Files->{$file} if $unlink;
|
|
} else {
|
|
push @{$moved}, $_ if $moved;
|
|
}
|
|
$unlink++;
|
|
}
|
|
} else {
|
|
$safetysuffix = '.cache' if &IsBlacklist($file);
|
|
$unlink = 1;
|
|
unless( $process_callback->($Files->{$file},$file,$safetysuffix,$unlink) ) {
|
|
delete $Files->{$file};
|
|
} else {
|
|
push @{$moved}, $file if $moved;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH) : files for moving
|
|
# IN (HASH) : updater configuration
|
|
# IN (SCALAR) : do expansion
|
|
# IN (ARRAY) : deleted files
|
|
# IN (SUB REF): callback to process files
|
|
# OUT: no
|
|
sub RemoveFiles
|
|
{
|
|
my $full;
|
|
my ($list, $Conf, $expand, $deleted, $callback ) = @_;
|
|
|
|
$callback = sub {
|
|
my $file = shift;
|
|
if(-e $file) {
|
|
unless(unlink $file) {
|
|
&Log( 'error', "can not delete $file ($!)" );
|
|
return 0;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
unless defined $callback;
|
|
|
|
foreach my $file ( @{$list} )
|
|
{
|
|
if( $expand and $file =~ /\*/ )
|
|
{
|
|
my %Paths = %{$Conf->{Paths}};
|
|
|
|
delete $Paths{downloaddir};
|
|
delete $Paths{workingdir};
|
|
delete $Paths{cachefile};
|
|
delete $Paths{last_update_cachefile};
|
|
|
|
my @files = map { glob( quotemeta( $_ ) . $file ) } values %Paths;
|
|
@files = map {&FullPath(&Basename($_), $Conf)} @files;
|
|
|
|
if ( not @files )
|
|
{
|
|
&Log( 'debug', "entry $file is a mask, no files selected");
|
|
next;
|
|
}
|
|
&Log( 'debug', "entry $file is a mask, selected files: ".join(', ', @files) );
|
|
&RemoveFiles(\@files, $Conf, 0, $deleted, $callback);
|
|
}
|
|
else
|
|
{
|
|
$full = $expand ? FullPath( $file, $Conf ) : $file;
|
|
&Log( 'verbose', "file $file doesn't exist, its deletion has been skipped" ), next unless -e $full;
|
|
if (&IsTimestamp($file)) {
|
|
foreach (($full, $Conf->{ Paths }{ update }.$file)) {
|
|
if( $callback->($_) ) {
|
|
push @{$deleted}, $_ if $deleted;
|
|
}
|
|
}
|
|
} else {
|
|
next unless $callback->($full);
|
|
push @{$deleted}, $full if $deleted;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): filename of message
|
|
# IN (HASH) : updater configuration
|
|
# OUT: no
|
|
sub SendNotify
|
|
{
|
|
my( $file, $Conf ) = @_;
|
|
|
|
my $cmd = $Conf->{Object}{mailcommand};
|
|
return if( $cmd eq '' );
|
|
&Log( 'info', "sending information file($file) to $Conf->{Object}{mailcommand} ..." );
|
|
$cmd =~ s/%S/"An information bulletin ($file) from Doctor Web Ltd."/g;
|
|
|
|
my @Msg;
|
|
return unless &ReadSigned( $Conf->{Self}{signedreader}, 'msg', $file, \@Msg );
|
|
|
|
if( !open FH, "|$cmd" )
|
|
{
|
|
&Log( 'error', "error during executing: $cmd ($!)" );
|
|
return 0;
|
|
}
|
|
print FH join( "\r\n", @Msg );
|
|
print FH "\r\n";
|
|
if( !close FH )
|
|
{
|
|
&Log( 'error', $! ? "exec($cmd) closes pipe with status: $!"
|
|
: "exec($cmd) exits with error #".($?>>8) );
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (ARRAY): command line options
|
|
# OUT(ARRAY): configuaration filename and section name
|
|
sub ParseCmdLine
|
|
{
|
|
my $ini = ($^O eq 'freebsd' ? '/usr/local/' : '') . '/etc/drweb/drweb32.ini';
|
|
my $what= '';
|
|
my $not_need_reload = undef;
|
|
my $mode = undef;
|
|
my $lstfile = undef;
|
|
my $download_dir = undef;
|
|
my $freeze_updates = undef;
|
|
my $unfreeze_updates = undef;
|
|
my $restore_backup = undef;
|
|
my $show_components = 0;
|
|
my $help = 0;
|
|
foreach my $opt (@_) {
|
|
$ini = $1, next if $opt =~ /\-\-?ini=(.*)/;
|
|
$what= $1, next if $opt =~ /\-\-?what=(.*)/;
|
|
$mode = $1, next if $opt =~ /\-\-?mode=(.*)/;
|
|
$lstfile = $1, next if $opt =~ /\-\-?lst=(.*)/;
|
|
$download_dir=$1, next if $opt =~ /\-\-?download\-dir=(.*)/;
|
|
$freeze_updates=$1, next if $opt =~ /\-\-?freeze=(.*)/;
|
|
$unfreeze_updates=$1,next if $opt =~ /\-\-?unfreeze=(.*)/;
|
|
$restore_backup=$1, next if $opt =~ /\-\-?restore=(.*)/;
|
|
$show_components=1, next if $opt =~ /\-\-?components/;
|
|
$help=1, next if $opt =~ /\-\-?help/;
|
|
|
|
if( $opt =~ /\-\-?not\-need\-restart(?:=([^\s]+))?/ || $opt =~ /\-\-?not\-need\-reload(?:=([^\s]+))?/ ) {
|
|
$not_need_reload = defined $1 ? [ split /,/, $1 ] : 0;
|
|
next;
|
|
}
|
|
&DrDie( &EXIT_STATUS_INVALID_COMMAND_LINE, 'ambiguous command-line switch: ' . $opt );
|
|
}
|
|
|
|
return (
|
|
$ini,$what,$not_need_reload,$mode,$lstfile,
|
|
$download_dir,$freeze_updates,$unfreeze_updates,
|
|
$restore_backup,$show_components,$help
|
|
);
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): filename of lock
|
|
# OUT: no
|
|
sub Lock
|
|
{
|
|
my $Conf = shift;
|
|
return unless $Conf->{Self}{lockfile};
|
|
open( LOCKFD, ">>$Conf->{Self}{lockfile}" )
|
|
or &DrDie( &EXIT_STATUS_SYSTEM_ERROR, "can not open lockfile $Conf->{Self}{lockfile} for locking ($!)" );
|
|
flock( LOCKFD, LOCK_EX | LOCK_NB )
|
|
or &DrDie( &EXIT_STATUS_LOCK_ERROR, "can not obtain lockfile $Conf->{Self}{lockfile}, stop to prevent multiply instances ($!)" );
|
|
$Conf->{Self}{locked} = 'yes';
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN: no
|
|
# OUT: no
|
|
sub Unlock
|
|
{
|
|
if( $Conf && $Conf->{Self} && defined $Conf->{Self}{locked} && $Conf->{Self}{locked} eq 'yes' )
|
|
{
|
|
flock( LOCKFD, LOCK_UN );
|
|
close LOCKFD;
|
|
$Conf->{Self}{locked} = 'no';
|
|
unlink $Conf->{Self}{lockfile};
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN: signal recived
|
|
# OUT: no
|
|
sub ExitHandler
|
|
{
|
|
my $recvsig = $_[0];
|
|
&DrDie(&EXIT_STATUS_INTERRUPTED, "Quitting on signal $recvsig");
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if the given filename corresponds to timstamp file, or 0
|
|
# otherwise
|
|
#
|
|
sub IsTimestamp {
|
|
return 1 if $_[0] =~ /timestamp$/i;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if the given filename corresponds to updater itself, or 0
|
|
# otherwise
|
|
sub IsUpdater {
|
|
return 1 if $_[0] =~ /update\.pl$/io;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if the given filename corresponds to FlyTrap update, 0
|
|
# otherwise
|
|
sub IsFlyTrapUpdate {
|
|
return 1 if $_[0] =~ /\bu\d{4}-[123]\d{3}$/io;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if base or 0
|
|
sub IsBase
|
|
{
|
|
return 1 if $_[0] =~ /\.vdb$/io;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if base or 0
|
|
sub IsBlacklist
|
|
{
|
|
return 1 if $_[0] =~ /\.dws$/io;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if engine or 0
|
|
sub IsEngine
|
|
{
|
|
return 1 if $_[0] =~ /drweb32\.dll$/o;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if antispam shared object or 0
|
|
sub IsVadeRetro
|
|
{
|
|
return 1 if $_[0] =~ /libvaderetro[\-\_\w\d]*\.so$/o;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if update.drl or 0
|
|
sub IsUpdateDrl
|
|
{
|
|
return 1 if $_[0] =~ /update\.drl$/o;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename
|
|
# IN (REF_HASH): 1 if drl file or 0
|
|
sub IsDrl
|
|
{
|
|
return 1 if $_[0] =~ /\.drl$/o;
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING): filename for searching
|
|
# OUT: full path of the executables or undef
|
|
sub Which
|
|
{
|
|
my $filename = shift;
|
|
foreach my $directory (split (':',$ENV{PATH}))
|
|
{
|
|
$directory .= '/' if ( not $directory =~ /\/$/ );
|
|
my $fullpath=$directory.$filename;
|
|
return $fullpath if ( -f $fullpath and -x $fullpath );
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : template for temporary filename
|
|
# OUT: temporary file name or undef on error
|
|
sub MkTemp
|
|
{
|
|
my ($path) = @_;
|
|
my ($path,$suffix) = $path =~ /(.+[^X])(X+)$/ or return undef;
|
|
|
|
my $tempdir = &Dirname($path);
|
|
return undef if ( not -d $tempdir or not -x $tempdir or not -w $tempdir);
|
|
|
|
my $MAXRETRIES = 1000;
|
|
|
|
my @chars=('a'..'z','A'..'Z',0..9);
|
|
my $maxrand=$#chars + 1;
|
|
|
|
my $sufflen=length($suffix);
|
|
for ( my $i=0; $i < $MAXRETRIES; $i++ )
|
|
{
|
|
my $testname=$path;
|
|
for ( my $j=0; $j < $sufflen; $j++ )
|
|
{
|
|
$testname .= $chars[ int ( rand $maxrand )];
|
|
}
|
|
next if (-e $testname or not open FH, '>'.$testname);
|
|
close FH;
|
|
if( not chmod 0644, $testname )
|
|
{
|
|
&Log( 'error', "can not set permission on $testname");
|
|
unlink $testname
|
|
or &Log( 'warning', "can not delete temporary file $testname" );
|
|
return undef;
|
|
}
|
|
&schedule_unlink( $testname );
|
|
return $testname;
|
|
}
|
|
return undef;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF) : config
|
|
# IN (HASH REF) : process info
|
|
# IN (ARRAY REF): headers
|
|
# IN (STRING) : path to file/dir
|
|
# IN (ARRAY REF): arguments to pass
|
|
# OUT(SCALAR) : returns headers as text if success or undef
|
|
sub makeRequest
|
|
{
|
|
my ($cfg,$Process,$headers,$path,$args) = @_;
|
|
|
|
&Log("debug", "makeRequest has been called ...");
|
|
|
|
my $args_str = '';
|
|
if( defined $args) {
|
|
$args_str = '?';
|
|
$args_str .=
|
|
join '&',
|
|
map{ join "=", @$_ }
|
|
@$args;
|
|
}
|
|
|
|
$path = '' unless defined $path;
|
|
|
|
my $request = 'GET ';
|
|
$request .= 'http://'.$Process->{host} if $Conf->{Self}{proxyserver};
|
|
$request .= $path.$args_str." HTTP/1.0\r\n";
|
|
|
|
$request .= "Host: " . $Process->{host} . "\r\n";
|
|
|
|
if(defined $headers) {
|
|
$request .= join "\r\n",
|
|
map {join ": ", @$_}
|
|
@$headers;
|
|
$request .= "\r\n";
|
|
}
|
|
|
|
$request .= $Process->{auth_req}."\r\n" if $Process->{auth_req};
|
|
|
|
$request .= "\r\n";
|
|
|
|
&Log("debug", "Dump of request:");
|
|
foreach (split /\r\n/, $request) {
|
|
&Log("debug", ' '.$_);
|
|
}
|
|
|
|
return $request;
|
|
}
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
sub GetHttpsResponse
|
|
{
|
|
my ($cfg, $Process, $headers, $path, $args, $save_to_buffer, $retuned_data) = @_;
|
|
|
|
&Log("debug", "GetHttpsResponse has been called ...");
|
|
|
|
my $port = 443;
|
|
my $url = $Process->{URL};
|
|
|
|
if ( $cfg->{Self}{proxyserver} ) {
|
|
my $proxy_port;
|
|
if ( $cfg->{Self}{proxyserver} =~ /^(http:\/\/)?(\S+?)(:(\d+))?\/*$/ ) {
|
|
$proxy_port = $4 ? $4 : 3128;
|
|
} else {
|
|
&Log('warning', "can not parse proxy address");
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_ADDR;
|
|
return undef;
|
|
}
|
|
my @proxy_auth = ();
|
|
if (defined $cfg->{Self}{proxylogin} && $cfg->{Self}{proxylogin} ne '') {
|
|
@proxy_auth = ($cfg->{Self}{proxylogin},$cfg->{Self}{proxypassword});
|
|
}
|
|
Net::SSLeay::set_proxy($2,$proxy_port,@proxy_auth);
|
|
}
|
|
|
|
unless ($url=~/^(https:\/\/)?([^\/\s]+)(.*)$/) {
|
|
&Log('warning', 'Can not parse url');
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_ADDR;
|
|
return undef;
|
|
}
|
|
my ($host,$path) = ($2,$3.$path);
|
|
|
|
if(defined $args && @$args) {
|
|
$path .=
|
|
'?'.
|
|
join '&',
|
|
map {join '=',@$_}
|
|
@$args;
|
|
}
|
|
my $prepared_headers = defined $headers ? {map {($_->[0] => $_->[1])} @$headers} : {};
|
|
|
|
my ($buf, $response, %reply_headers)
|
|
= Net::SSLeay::get_https($host, $port, $path,
|
|
Net::SSLeay::make_headers(%$prepared_headers)
|
|
);
|
|
|
|
my $returned_header;
|
|
if(defined $response) {
|
|
unless ($response =~ m/^HTTP\/(\d)\.(\d)\s+(\d+)\s+(.*)$/) {
|
|
&Log('warning', 'Can not parse server response');
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_RESPONSE;
|
|
return undef;
|
|
}
|
|
$returned_header->{retval} = $3;
|
|
$returned_header->{retval_string} = $4;
|
|
foreach (keys %reply_headers) {
|
|
$returned_header->{lc $_} = $reply_headers{$_};
|
|
}
|
|
} else {
|
|
&Log('warning', 'Can not parse server response');
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_RESPONSE;
|
|
return undef;
|
|
}
|
|
|
|
if(defined $buf) {
|
|
if($save_to_buffer) {
|
|
$$retuned_data = $buf;
|
|
} else {
|
|
unless( defined writeDataIntoFile($retuned_data,\$buf)) {
|
|
$UPDATE_ERROR = &EXIT_STATUS_SYSTEM_ERROR;
|
|
return undef;
|
|
}
|
|
}
|
|
}
|
|
|
|
return $returned_header;
|
|
}
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (HASH REF) : config
|
|
# IN (HASH REF) : process info
|
|
# IN (ARRAY REF): headers
|
|
# IN (STRING) : path to file/dir
|
|
# IN (ARRAY REF): arguments to pass
|
|
# IN (SCALAR) : 1 - save to buffer, 0 - save to file
|
|
# IN (SCALAR : file name or ref of string with result
|
|
# OUT(SCALAR) : returns headers as hash or undef
|
|
sub GetHttpResponse
|
|
{
|
|
my ($cfg,$Process,$headers,$fpath,$args,$save_to_buffer,$file_to) = @_;
|
|
|
|
my ($descriptor_flag,$FH) = (0,undef);
|
|
|
|
for ( my $i=1; $i <= $Conf->{Self}{tries}; $i++ ) {
|
|
|
|
if (not $Process->{socket}) {
|
|
if ( not &ConnectSocket($Conf,$Process) ) {
|
|
&Log('warning', delete $Process->{error});
|
|
return undef;
|
|
}
|
|
if($descriptor_flag) {
|
|
$descriptor_flag=0;
|
|
close $FH;
|
|
}
|
|
}
|
|
|
|
my $path = $Process->{dir}.$fpath;
|
|
|
|
$Process->{timeout_exceed} = time() + $Conf->{Self}{timeout};
|
|
my $uri = 'http://'.$Process->{host}.$path;
|
|
&Log( 'debug', "Attempting to GET $uri");
|
|
my $fdset = '';
|
|
vec($fdset, fileno $Process->{socket}, 1) = 1;
|
|
my ($readed,$written,$tw,$buf);
|
|
|
|
$buf = makeRequest($cfg,$Process,$headers,$path,$args);
|
|
|
|
$readed=length($buf);
|
|
for ( $written=0,$tw=0 ; $written < $readed and defined $tw; $written += $tw ) {
|
|
$tw = syswrite $Process->{socket}, $buf, $readed - $written, $written;
|
|
}
|
|
if (not defined $tw ) {
|
|
&Log( 'warning', "can not send request ($!)" );
|
|
$UPDATE_ERROR = &EXIT_STATUS_SEND_REQUEST_ERROR;
|
|
&CloseSocket($Conf,$Process);
|
|
next;
|
|
} else {
|
|
&Log( 'info', "request with $readed bytes length was sent to $Process->{host}" );
|
|
}
|
|
|
|
my ($returned_header,$size,$timeout);
|
|
my $offset = 0;
|
|
while (1) {
|
|
if (($timeout=$Process->{timeout_exceed} - time()) < 1 ) {
|
|
last;
|
|
}
|
|
|
|
select $fdset,undef,undef,$timeout;
|
|
$readed=sysread $Process->{socket}, $buf, 2048,$offset;
|
|
last if ( not defined $readed or $readed == 0 );
|
|
if (not defined $returned_header) {
|
|
$returned_header={end => 0};
|
|
if ( $buf and $buf=~/^HTTP\/(\d)\.(\d)\s+(\d+)\s+([^\r]*)\r?\n/g ) {
|
|
($returned_header->{proto_major},$returned_header->{proto_minor},
|
|
$returned_header->{retval}, $returned_header->{retval_string}) = ( $1,$2,$3,$4 );
|
|
&Log( 'info', "$Process->{host} return $returned_header->{retval} $returned_header->{retval_string}" );
|
|
} else {
|
|
&Log('warning', "parsing error $buf");
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_RESPONSE;
|
|
return undef;
|
|
}
|
|
}
|
|
|
|
if ($returned_header->{end} == 0) {
|
|
while ( $buf=~/(.*)\n/g ) {
|
|
my $str=$1;
|
|
if ( $str=~/^\s*$/ ) {
|
|
$returned_header->{end} = 1;
|
|
if($returned_header->{retval} != 200) {
|
|
&CloseSocket($Conf,$Process);
|
|
return $returned_header;
|
|
}
|
|
|
|
$buf=substr($buf,pos($buf));
|
|
$readed=length($buf);
|
|
$size=$returned_header->{'content-length'};
|
|
last;
|
|
}
|
|
$returned_header->{lc $1} = $2 if($str=~/^(\S+)\:\s+([^\r]*)\s*\r?$/);
|
|
}
|
|
}
|
|
if ($returned_header->{end} == 1) {
|
|
if (not $save_to_buffer ) {
|
|
|
|
if (not $descriptor_flag) {
|
|
if (not open $FH, ">$file_to") {
|
|
&DrDie(&EXIT_STATUS_SYSTEM_ERROR, "can not open $file_to ($!) for writing");
|
|
}
|
|
$descriptor_flag = 1;
|
|
}
|
|
|
|
for ( $written=0,$tw=0 ; $written < $readed and defined $tw; $written += $tw ) {
|
|
$tw = syswrite( $FH, $buf, $readed - $written, $written );
|
|
unless(defined $tw) {
|
|
last;
|
|
}
|
|
}
|
|
last unless defined $tw;
|
|
} else {
|
|
$$file_to.=$buf;
|
|
}
|
|
$size-=$readed;
|
|
last unless ($size > 0);
|
|
}
|
|
}
|
|
|
|
if ( $timeout < 1 ) {
|
|
&Log('warning', "timeout getting response from $uri");
|
|
&CloseSocket($Conf,$Process);
|
|
$UPDATE_ERROR = &EXIT_STATUS_TIMEOUT_ERROR;
|
|
next;
|
|
} if ( not defined $readed ) {
|
|
&Log('warning', "can not read file $uri");
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_RESPONSE;
|
|
&CloseSocket($Conf,$Process);
|
|
next;
|
|
} elsif ( $readed == 0 ) {
|
|
&Log('warning', "remote host $Process->{remote_host} closed connection ($!)");
|
|
$UPDATE_ERROR = &EXIT_STATUS_CLOSED_CONNECTION;
|
|
&CloseSocket($Conf,$Process);
|
|
next;
|
|
} elsif ( not defined $tw ) {
|
|
&DrDie(&EXIT_STATUS_SYSTEM_ERROR, "can not write to file $file_to");
|
|
}
|
|
|
|
if($descriptor_flag) {
|
|
close $FH;
|
|
}
|
|
return $returned_header;
|
|
}
|
|
|
|
return undef;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH): update process status hash
|
|
# IN (STRING) : file name to download
|
|
# IN (STRING) : where to store file
|
|
# OUT: 1 on success, 0 or undef on fail
|
|
sub GetHttpFile
|
|
{
|
|
my ($Conf,$Process,$get_file,$file_to) = @_;
|
|
my ($auth,$save_to_buffer)=(0,ref $file_to eq "SCALAR");
|
|
&Log( 'debug', "GetHttpFile has been called ..." );
|
|
|
|
my $headers = [
|
|
['User-Agent' => $Conf->{ Versions }{ UserAgent }],
|
|
['X-DrWeb-Validate' => $Conf->{Self}{key_md5} ],
|
|
['X-DrWeb-KeyNumber' => $Conf->{Self}{key_num} ],
|
|
['Accept' => '*/*' ],
|
|
['Connection' => 'Keep-Alive' ],
|
|
];
|
|
push @$headers, ['Proxy-Connection' => 'Keep-Alive'] if $Conf->{Self}{proxyserver};
|
|
|
|
my $args = [
|
|
[ 'm' => $Conf->{Self}{key_md5} ],
|
|
];
|
|
|
|
my $is_old_lzma = isOldLzma($Conf);
|
|
&Log("warning", "Can not get version of lzma...") unless defined $is_old_lzma;
|
|
|
|
my ($returned_header,$ext);
|
|
foreach my $variant ('lzma', '') {
|
|
next if !defined $is_old_lzma && $variant eq 'lzma';
|
|
|
|
$ext = $variant eq 'lzma' ? '.lzma' : '';
|
|
|
|
my $path = $get_file.$ext;
|
|
if(not $save_to_buffer) {
|
|
&schedule_unlink($file_to.$ext);
|
|
}
|
|
|
|
$returned_header = GetHttpResponse(
|
|
$Conf,
|
|
$Process,
|
|
$headers,
|
|
$path,
|
|
$args,
|
|
$save_to_buffer,
|
|
$save_to_buffer?$file_to:$file_to.$ext
|
|
);
|
|
|
|
if (defined $returned_header) {
|
|
if ($returned_header->{retval} != 200) {
|
|
if ($returned_header->{retval} == 403) {
|
|
&Log( 'warning',"Authorization failed" );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
return 0
|
|
} elsif ($returned_header->{retval} == 407) {
|
|
if (delete $Process->{auth_req}) {
|
|
&Log( 'warning',"Proxy server authorization failed" );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
return 0;
|
|
} elsif (not $Conf->{Self}{proxylogin} or not $Conf->{Self}{proxypassword}) {
|
|
&Log( 'warning', "Proxy server authentication required" );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
return 0;
|
|
} elsif (not &Authenticate($Conf,$Process,$returned_header,$get_file)) {
|
|
&Log( 'warning', delete $Process->{error} );
|
|
$UPDATE_ERROR = &EXIT_STATUS_AUTOROZATION_ERROR;
|
|
return 0;
|
|
}
|
|
redo;
|
|
} elsif ( $returned_header->{retval} == 451 ) {
|
|
&Log( 'warning', 'Unknown license key' );
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_KEY;
|
|
return undef;
|
|
} elsif ( $returned_header->{retval} == 452 ) {
|
|
&Log( 'warning', 'Blocked license key' );
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_KEY;
|
|
return undef;
|
|
} elsif ( $returned_header->{retval} == 404 ) {
|
|
&Log( 'warning', 'File not found (softfail), will try other variant if any' );
|
|
$UPDATE_ERROR = &EXIT_STATUS_FILE_NOT_FOUND;
|
|
next;
|
|
} elsif ( $returned_header->{retval} == 503 ) {
|
|
&Log( 'warning', 'Server is busy, need to try other server' );
|
|
$UPDATE_ERROR = &EXIT_STATUS_SERVER_BUSY;
|
|
return 0;
|
|
} else {
|
|
&Log( 'warning', 'Can not download file ' . $file_to.$ext . ' (' . $returned_header->{retval_string} . ')');
|
|
return 0;
|
|
}
|
|
}
|
|
} else {
|
|
return 0;
|
|
}
|
|
|
|
if ( $variant eq 'lzma' ) {
|
|
|
|
my $decoder = $Conf->{ Self }{ "${variant}decoderpath" };
|
|
|
|
if ( $save_to_buffer ) {
|
|
|
|
$decoder .= $is_old_lzma ? " d -si -so" : " -dcfq";
|
|
$decoder .= ' 2>/dev/null';
|
|
|
|
&Log( 'debug', "Forking decoder: $decoder");
|
|
if ( not open2 *Decoded, *Decoder, $decoder ) {
|
|
&Log('warning',"can not fork decoder");
|
|
$UPDATE_ERROR = &EXIT_STATUS_SYSTEM_ERROR;
|
|
next;
|
|
}
|
|
print Decoder $$file_to;
|
|
close Decoder;
|
|
local $/;
|
|
$$file_to = <Decoded>;
|
|
&Log( 'debug', "Decode complete");
|
|
|
|
} else {
|
|
my $encoded_file_path = $file_to.$ext;
|
|
$decoder .=
|
|
(
|
|
$is_old_lzma
|
|
? " d $encoded_file_path $file_to"
|
|
: " -dfq $encoded_file_path"
|
|
);
|
|
$decoder .= ' 2>/dev/null';
|
|
&Log( 'debug', "Forking decoder: $decoder");
|
|
if ( system $decoder ) {
|
|
&Log('warning',"can not fork decoder (softfail), will try other variant if any");
|
|
$UPDATE_ERROR = &EXIT_STATUS_SYSTEM_ERROR;
|
|
next;
|
|
}
|
|
if (-e $encoded_file_path) {
|
|
unlink $encoded_file_path
|
|
or die('can not unlink file ' . $encoded_file_path . ' : ' . $!);
|
|
}
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
return 0;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH): update process status hash
|
|
# OUT: temporary file name or undef on error
|
|
sub ConnectSocket
|
|
{
|
|
my ($Conf,$Process) = @_;
|
|
&Log( 'debug', "ConnectSocket has been called ..." );
|
|
my $hostinfo=$_[0];
|
|
my ($protocol,$port,$flags,$ipaddr);
|
|
$protocol = getprotobyname('tcp');
|
|
if (not $protocol)
|
|
{
|
|
$Process->{error} = "can not find protocol TCP";
|
|
$UPDATE_ERROR = &EXIT_STATUS_SYSTEM_ERROR;
|
|
return 0;
|
|
}
|
|
|
|
if ($Process->{URL}=~/^(http:\/\/)?([^\/\s]+)(.*)$/ )
|
|
{
|
|
($Process->{host},$Process->{dir})=($2,$3);
|
|
$Process->{dir}.='/' unless $Process->{dir} =~ /\/$/;
|
|
}
|
|
else
|
|
{
|
|
$Process->{error} = "can not parse url";
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_ADDR;
|
|
return 0;
|
|
}
|
|
|
|
$Process->{socket} = new FileHandle;
|
|
if (not socket($Process->{socket}, &PF_INET(), &SOCK_STREAM(), $protocol))
|
|
{
|
|
$Process->{error} = "can not create socket, $!";
|
|
$UPDATE_ERROR = &EXIT_STATUS_SYSTEM_ERROR;
|
|
return 0;
|
|
}
|
|
|
|
if ( $Conf->{Self}{proxyserver} ) {
|
|
if ( $Conf->{Self}{proxyserver} =~ /^(http:\/\/)?(\S+?)(:(\d+))?\/*$/ )
|
|
{
|
|
$port = $4 ? $4 : 3128;
|
|
$Process->{remote_host} = $2;
|
|
}
|
|
else
|
|
{
|
|
$Process->{error} = "can not parse proxy address";
|
|
$UPDATE_ERROR = &EXIT_STATUS_INVALID_ADDR;
|
|
return 0;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
$port = 80;
|
|
$Process->{remote_host} = $Process->{host};
|
|
}
|
|
if (not $ipaddr = gethostbyname($Process->{remote_host}))
|
|
{
|
|
$Process->{error} = "Can not resolve $Process->{remote_host} to IP address";
|
|
$UPDATE_ERROR = &EXIT_STATUS_RESOLVE_IP_ERROR;
|
|
return 0;
|
|
}
|
|
my $sin = sockaddr_in($port, $ipaddr);
|
|
&Log( 'debug', "Attempting to connect to $Process->{remote_host} ..." );
|
|
if (not connect($Process->{socket}, $sin))
|
|
{
|
|
$Process->{error} = "Unable to connect to $Process->{remote_host}: $!";
|
|
$UPDATE_ERROR = &EXIT_STATUS_CONNECT_ERROR;
|
|
return 0;
|
|
}
|
|
if (not $flags = fcntl($Process->{socket}, F_GETFL, 0) or not fcntl($Process->{socket}, F_SETFL, $flags | O_NONBLOCK))
|
|
{
|
|
$Process->{error} = "Unable to set socket to non-blocking mode: $!";
|
|
$UPDATE_ERROR = &EXIT_STATUS_SYSTEM_ERROR;
|
|
&CloseSocket($Conf,$Process);
|
|
return 0;
|
|
}
|
|
&Log( 'debug', "Connect to $Process->{remote_host} successful." );
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH): update process status hash
|
|
# IN (REF_HASH): header fields
|
|
# OUT(SCALAR) : 1 on success or 0
|
|
sub CloseSocket {
|
|
my ( $Conf, $Process, $header ) = @_;
|
|
&Log( 'debug', 'CloseSocket has been called...' );
|
|
return 0
|
|
if ( !exists $Process->{ socket } or $header and not ( ( $header->{ connection } and lc $header->{ connection } eq 'close' )
|
|
or ( $Conf->{ Self }{ proxyserver } and $header->{ 'proxy-connection' }
|
|
and lc $header->{ 'proxy-connection' } eq 'close' ) ) );
|
|
shutdown( delete( $Process->{ socket } ), 2 );
|
|
&Log( 'debug', 'CloseSocket done...' );
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (REF_HASH): file path
|
|
# OUT(SCALAR) : text ref
|
|
sub readfile
|
|
{
|
|
my ($filepath) = @_;
|
|
|
|
open(FH, '<', $filepath) or DrDie(&EXIT_STATUS_SYSTEM_ERROR, "Can not open file '$filepath' for reading ($!)");
|
|
local $/ = undef;
|
|
my $data = <FH>;
|
|
close FH;
|
|
|
|
return \$data;
|
|
}
|
|
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH): update process status hash
|
|
# IN (REF_HASH): header fields
|
|
# IN (SCALAR) : requested file
|
|
# OUT(SCALAR) : 1 on success or 0
|
|
sub Authenticate
|
|
{
|
|
my ($Conf,$Process,$header,$file) = @_;
|
|
&Log( 'debug', "Authenticate has been called ..." );
|
|
my ($method,$realm);
|
|
|
|
if (not $header->{'proxy-authenticate'})
|
|
{
|
|
$Process->{error} = "bad authentication request";
|
|
return 0;
|
|
}
|
|
elsif (($method,$realm) = ($header->{'proxy-authenticate'} =~ /^(basic|digest)\s+(.*)$/i))
|
|
{
|
|
return &Basic($Conf,$Process,$realm) if (lc $method eq 'basic');
|
|
return &Digest($Conf,$Process,$realm,$file) if (lc $method eq 'digest');
|
|
}
|
|
else
|
|
{
|
|
$Process->{error} = "unknown authentication method: method - $method, realm - $realm";
|
|
return 0;
|
|
}
|
|
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH): update process status hash
|
|
# IN (SCALAR) : realm string
|
|
# OUT(SCALAR) : 1 on success or 0
|
|
sub Basic
|
|
{
|
|
my ($Conf,$Process,$realm_string) = @_;
|
|
$Process->{auth_req}="Proxy-Authorization: Basic ".&EncodeBase64("$Conf->{Self}{proxylogin}:$Conf->{Self}{proxypassword}");
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (REF_HASH): configuration of updater
|
|
# IN (REF_HASH): update process status hash
|
|
# IN (SCALAR) : realm string
|
|
# IN (SCALAR) : requested file
|
|
# OUT(SCALAR) : 1 on success or 0
|
|
sub Digest
|
|
{
|
|
my ($Conf,$Process,$realm_string,$file) = @_;
|
|
my $nc = sprintf "%08X", ++$Process->{nonce};
|
|
my $cnonce;
|
|
my @chars=('a'..'z','A'..'Z',0..9);
|
|
my $maxrand=$#chars + 1;
|
|
for ( my $j=0; $j < 16; $j++ )
|
|
{
|
|
$cnonce .= $chars[ int ( rand $maxrand )];
|
|
}
|
|
|
|
my $uri = $Process->{dir};
|
|
|
|
my ($HA1,$HA2,$md5,%realm,$response);
|
|
while( $realm_string =~ /(^|,|\s+)(\w+)=\"([^\"]*)\"/g )
|
|
{
|
|
$realm{$2} = $3;
|
|
}
|
|
if (not $realm{realm} or not $realm{nonce} )
|
|
{
|
|
$Process->{error} = "can not parse digest in response";
|
|
return 0;
|
|
}
|
|
$HA1=`echo -n "$Conf->{Self}{proxylogin}:$realm{realm}:$Conf->{Self}{proxypassword}" | $Conf->{Self}{md5sum}`;
|
|
if ($HA1=~/\s*([0-9a-fA-F]{32})\s+.*/)
|
|
{
|
|
$HA1=$1;
|
|
}
|
|
else
|
|
{
|
|
$Process->{error} = "can not calculate md5 sum for Digest auth";
|
|
return 0;
|
|
}
|
|
$HA2=`echo -n "GET:$Process->{dir}$file" | $Conf->{Self}{md5sum}`;
|
|
if ($HA2=~/\s*([0-9a-fA-F]{32})\s+.*/)
|
|
{
|
|
$HA2=$1;
|
|
}
|
|
else
|
|
{
|
|
$Process->{error} = "can not calculate md5 sum for Digest auth";
|
|
return 0;
|
|
}
|
|
|
|
$md5=$HA1.":".$realm{nonce}.":";
|
|
|
|
if ( $realm{qop} )
|
|
{
|
|
$md5.=$nc.":".$cnonce.":".$realm{qop}.":";
|
|
}
|
|
$md5.=$HA2;
|
|
|
|
$md5=`echo -n "$md5" | $Conf->{Self}{md5sum}`;
|
|
if ($md5=~/\s*([0-9a-fA-F]{32})\s+.*/)
|
|
{
|
|
$md5=$1;
|
|
}
|
|
else
|
|
{
|
|
$Process->{error} = "can not calculate md5 sum for Digest auth";
|
|
return 0;
|
|
}
|
|
|
|
$response = "username=\"$Conf->{Self}{proxylogin}\", realm=\"$realm{realm}\", nonce=\"$realm{nonce}\", uri=\"";
|
|
$response.= "$Process->{dir}$file\", algorithm=\"MD5\", ";
|
|
$response.= "qop=\"$realm{qop}\", cnonce=\"$cnonce\", nc=$nc, " if ( $realm{qop} );
|
|
$response.= "response=\"$md5\"";
|
|
$response.= ", opaque=\"$realm{opaque}" if ($realm{opaque});
|
|
$Process->{auth_req}="Proxy-Authorization: Digest $response";
|
|
return 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (SCALAR) : string to encode
|
|
# OUT(SCALAR) : encoded string
|
|
sub EncodeBase64
|
|
{
|
|
my $res .= substr(pack('u', $_[0]),1,-1);
|
|
$res =~ tr|` -_|AA-Za-z0-9+/|;
|
|
my $padding = (3 - length($_[0]) % 3) % 3;
|
|
$res =~ s/.{$padding}$/'=' x $padding/e if $padding;
|
|
return $res;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (SCALAR) : string to decode
|
|
# OUT(SCALAR) : decoded string
|
|
sub DecodeBase64
|
|
{
|
|
my $d = shift;
|
|
$d =~ tr!A-Za-z0-9+/!!cd;
|
|
$d =~ s/=+$//;
|
|
$d =~ tr!A-Za-z0-9+/! -_!;
|
|
my $r = '';
|
|
while( $d =~ /(.{1,60})/gs ){
|
|
my $len = chr(32 + length($1)*3/4);
|
|
$r .= unpack("u", $len . $1 );
|
|
}
|
|
return $r;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN: no
|
|
# OUT: no
|
|
sub ReplayLog {
|
|
return unless ( $log_cache and 'ARRAY' eq ref $log_cache and @{$log_cache} );
|
|
#replay log
|
|
my @log_cache = @{$log_cache};
|
|
undef( $log_cache );
|
|
my $log_entry;
|
|
foreach $log_entry ( @log_cache ) {
|
|
&{$log_entry}();
|
|
}
|
|
}
|
|
|
|
{
|
|
my %files_to_cleanup;
|
|
END {
|
|
cleanup_files();
|
|
eval { &ReplayLog(); };
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN: no
|
|
# OUT: no
|
|
sub cleanup_files {
|
|
my @files = exists $files_to_cleanup{ $$ }
|
|
? keys %{ $files_to_cleanup{ $$ } }
|
|
: ();
|
|
foreach my $file ( @files ) {
|
|
next unless -f $file;
|
|
unlink $file or &Log( 'warning', 'Error unlinking temporary file ' . $file );
|
|
}
|
|
$files_to_cleanup{ $$ } = {};
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename to be unlinked
|
|
# OUT: no
|
|
sub schedule_unlink {
|
|
my $file = shift;
|
|
$files_to_cleanup{ $$ }{ $file } = 1;
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
# IN (STRING) : filename to be unlinked
|
|
# OUT: no
|
|
sub unschedule_unlink {
|
|
my $file = shift;
|
|
delete $files_to_cleanup{ $$ }{ $file };
|
|
}
|
|
}
|
|
|
|
#----------------------------------------------------------------------------
|
|
#IN: PATH_TO_SOCKET
|
|
#IN: MESSAGE
|
|
#OUT: no
|
|
sub WriteToSocket
|
|
{
|
|
my($sock_path, $message) = @_;
|
|
|
|
if(!( -S $sock_path) ) {
|
|
&Log( 'info', "program not running -> no socket $sock_path");
|
|
} else {
|
|
my $socket_name = sockaddr_un($sock_path);
|
|
if ( socket(SOCK, PF_UNIX, SOCK_STREAM, 0) ) {
|
|
if ( connect(SOCK, $socket_name) ) {
|
|
send(SOCK,$message, 0);
|
|
} else {
|
|
&Log( 'info', "failed to bind socket $sock_path");
|
|
}
|
|
} else {
|
|
&Log( 'info', "failed to init socket $sock_path");
|
|
}
|
|
close(SOCK);
|
|
}
|
|
}
|
|
|
|
#-----------------------------------------------------------------------
|
|
#IN: REF_HASH
|
|
#OUT: UNDEF|0|1
|
|
sub isOldLzma
|
|
{
|
|
my $Conf = shift;
|
|
|
|
my $decoder = $Conf->{ Self }{ "lzmadecoderpath" };
|
|
|
|
my $fd = new FileHandle ();
|
|
if(not open( $fd, "$decoder -h 2>&1 |") ) {
|
|
&Log( 'warning', "Can't run $decoder: $!");
|
|
return undef;
|
|
}
|
|
|
|
my @output = ();
|
|
while(<$fd>) {
|
|
push @output, $_;
|
|
}
|
|
close($fd);
|
|
|
|
if (defined $output[1]) {
|
|
if ($output[1] =~ /^lzma\s+(\d+)\.(\d+)\.(\d+)/i) {
|
|
return 0;
|
|
} elsif ($output[1] =~ /^lzma\s+(\d+)\.(\d+)/i) {
|
|
return 1;
|
|
} elsif ($output[-1] =~ /^XZ/) {
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
&Log( 'warning', "Can't parse output of $decoder: $!");
|
|
return undef;
|
|
}
|
|
|
|
|
|
#-----------------------------------------------------------------------
|
|
# IN(STRING) : file path
|
|
# OUT(STRING): modification date
|
|
sub getFileModificationDate
|
|
{
|
|
my ($file_path) = @_;
|
|
my $mdate = (stat($file_path))[9];
|
|
die "Can not stat file '$file_path'"
|
|
unless defined $mdate;
|
|
return $mdate
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
# CACHE SECTION
|
|
{
|
|
my $_CACHE_FILE;
|
|
my $_UPDATE_PATH;
|
|
my %_CACHE = ();
|
|
my $_LAST_UPDATE_CACHE_FILE;
|
|
my %_LAST_UPDATE_CACHE = ();
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
sub loadLastUpdateCacheData {
|
|
my ($Conf) = @_;
|
|
&Log('info', "Trying to load last update cache..." );
|
|
|
|
$_LAST_UPDATE_CACHE_FILE = $Conf->{ Paths }{ last_update_cachefile };
|
|
|
|
unless (-e $_LAST_UPDATE_CACHE_FILE) {
|
|
&Log('warning', "Cache file '$_LAST_UPDATE_CACHE_FILE' is not exists. Can not load cache data!");
|
|
return;
|
|
}
|
|
|
|
open FH, $_LAST_UPDATE_CACHE_FILE
|
|
or die "Can not open cache file '$_LAST_UPDATE_CACHE_FILE' for reading: $!";
|
|
my ($str,$component,$file_name,$file_path,$check_sum,$mdate,$current_mdate);
|
|
while (defined($str = <FH>)) {
|
|
chomp $str;
|
|
($component,$file_name,$file_path,$check_sum,$mdate) = split(':', $str);
|
|
|
|
&Log('debug', "Found info about file '$file_name'('$file_path') for component '$component' in last update cache." );
|
|
if(! -e $file_path) {
|
|
&Log('debug', "Seems that file '$file_name'('$file_path') for component '$component' has been deleted, skipping..." );
|
|
next;
|
|
}
|
|
|
|
&Log('debug', "Check last modification date for file '$file_name'('$file_path') for component '$component." );
|
|
my $current_mdate = &getFileModificationDate($file_path);
|
|
|
|
if($mdate eq $current_mdate){
|
|
&Log('debug', "Info for file '$file_name'('$file_path') for component '$component has been added into cache." );
|
|
$_LAST_UPDATE_CACHE{$component}{$file_name} = {
|
|
full => $file_path,
|
|
check_sum => $check_sum,
|
|
mdate => $mdate,
|
|
};
|
|
} else {
|
|
&Log('debug', "Last modification date for file '$file_name'('$file_path') for component '$component has been chenged, skipping." );
|
|
unlink $file_path
|
|
or die "Failed to delete file '$file_path' ($!)";
|
|
}
|
|
}
|
|
close(FH);
|
|
|
|
&Log('info', "Last update cache has been loaded." );
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
# IN (STRING): component
|
|
# IN (STRING): file name
|
|
# IN (STRING): path to file
|
|
# IN (STRING): check sum
|
|
sub insertInfoIntoLastUpdateCacheData {
|
|
my ($component, $file_name, $file_path, $check_sum) = @_;
|
|
&Log('debug', "Insert info about '$file_name' for component '$component' into last update cache." );
|
|
|
|
my $mdate = &getFileModificationDate($file_path);
|
|
|
|
$_LAST_UPDATE_CACHE{$component}{$file_name} = {full => $file_path, check_sum => $check_sum, mdate => $mdate};
|
|
&Log('debug', "Info about '$file_name' for component '$component' has been inserted into last update cache." );
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
# IN (STRING): component
|
|
# IN (STRING): file name
|
|
# OUT (HASH REF): file info or undef (keys: full - full file path; check_sum - check sum)
|
|
sub getInfoFromLastUpdateCacheData {
|
|
my ($component, $file_name) = @_;
|
|
&Log('debug', "Trying to find info about file '$file_name' for component '$component' in last update cache..." );
|
|
|
|
if(! exists $_LAST_UPDATE_CACHE{$component} || ! exists $_LAST_UPDATE_CACHE{$component}{$file_name} ) {
|
|
&Log('debug', "No info about file '$file_name' for component '$component' in last update cache!");
|
|
return undef;
|
|
}
|
|
|
|
my $file_info = $_LAST_UPDATE_CACHE{$component}{$file_name};
|
|
if (! -e $file_info->{full}) {
|
|
&Log('debug', "Info about file '$file_name' for component '$component' has been founded, but file had been deleted!");
|
|
delete $_LAST_UPDATE_CACHE{$component}{$file_name};
|
|
return undef;
|
|
}
|
|
|
|
&Log('debug', "Info about '$file_name' for component '$component' has been founded in last update hash:");
|
|
&Log('debug', " full path: $file_info->{full}");
|
|
&Log('debug', " sheck sum: $file_info->{check_sum}");
|
|
return $_LAST_UPDATE_CACHE{$component}{$file_name};
|
|
}
|
|
|
|
|
|
#------------------------------------------------------------------------
|
|
# IN (STRING): component
|
|
# IN (STRING): file name
|
|
sub deleteFileInfoFromLastUpdateCacheData {
|
|
my ($component, $file_name) = @_;
|
|
&Log('debug', "Delete info about file '$file_name' for component '$component' from last update cache." );
|
|
|
|
if(! exists $_LAST_UPDATE_CACHE{$component} || ! exists $_LAST_UPDATE_CACHE{$component}{$file_name} ) {
|
|
&Log('debug', "No info about file '$file_name' for component '$component' in last update cache!");
|
|
return undef;
|
|
}
|
|
|
|
my $full_path = $_LAST_UPDATE_CACHE{$component}{$file_name}{full};
|
|
if(-e $_LAST_UPDATE_CACHE{$component}{$file_name}{full}) {
|
|
unlink $full_path
|
|
or die("Failed to delete file '$full_path' ($!)");
|
|
}
|
|
|
|
delete $_LAST_UPDATE_CACHE{$component}{$file_name};
|
|
|
|
&Log('debug', "Info about file '$file_name' for component '$component' has been deleted from last update cache." );
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
# IN (STRING): component
|
|
sub deleteComponentInfoFromLastUpdateCacheData {
|
|
my ($component) = @_;
|
|
&Log('debug', "Delete info about component '$component' from last update cache." );
|
|
foreach my $file_info (keys %_LAST_UPDATE_CACHE) {
|
|
&deleteFileInfoFromLastUpdateCacheData($component, $file_info);
|
|
}
|
|
delete $_LAST_UPDATE_CACHE{$component};
|
|
&Log('debug', "Info about component '$component' has been deleted from last update cache." );
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
sub saveLastUpdateCacheData {
|
|
&Log('info', 'Saving last update cache data...');
|
|
|
|
unless(defined $_LAST_UPDATE_CACHE_FILE) {
|
|
&Log('info', 'Nothing to save into last update cache!');
|
|
return;
|
|
}
|
|
|
|
open FH, '>', $_LAST_UPDATE_CACHE_FILE
|
|
or die "Can not open file '$_LAST_UPDATE_CACHE_FILE' for writing: ($!)";
|
|
my ($component, $component_info, $file_name, $file_info, $file_path, $check_sum, $mdate);
|
|
foreach $component (keys %_LAST_UPDATE_CACHE) {
|
|
$component_info = $_LAST_UPDATE_CACHE{$component};
|
|
foreach $file_name (keys %{$component_info}) {
|
|
$file_info = $component_info->{$file_name};
|
|
$file_path = $file_info->{full};
|
|
$check_sum = $file_info->{check_sum};
|
|
$mdate = $file_info->{mdate};
|
|
print FH "$component:$file_name:$file_path:$check_sum:$mdate\n"
|
|
or die "Can not print into file $_LAST_UPDATE_CACHE_FILE ($!)";
|
|
}
|
|
}
|
|
close(FH);
|
|
&Log('info', 'Last update cache data has been saved.');
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
sub loadCacheData
|
|
{
|
|
my $Conf = shift;
|
|
|
|
&Log('info', 'Loading cache data...');
|
|
|
|
$_CACHE_FILE = $Conf->{Paths}{cachefile};
|
|
$_UPDATE_PATH = $Conf->{Paths}{update};
|
|
|
|
unless (-e $_CACHE_FILE) {
|
|
&Log('warning', "Cache file '$_CACHE_FILE' is not exists. Can not load cache data!");
|
|
return;
|
|
}
|
|
|
|
open FH, $_CACHE_FILE
|
|
or die "Can not open cache file '$_CACHE_FILE' for reading: $!";
|
|
my ($str,$file_path,$check_sum,$mdate);
|
|
while (defined($str = <FH>)) {
|
|
chomp $str;
|
|
($file_path,$check_sum,$mdate) = split(':', $str);
|
|
&insertDataIntoCache($file_path,$check_sum,$mdate);
|
|
}
|
|
close(FH);
|
|
|
|
&Log('info', 'Cache data has been loaded...');
|
|
}
|
|
|
|
#-----------------------------------------------------------------------
|
|
#IN (HASH_REF): Task list
|
|
sub updateCacheByLstTask
|
|
{
|
|
my $LstTask = shift;
|
|
|
|
&Log('info', 'Updating cache data...');
|
|
|
|
my ($type, $file_path, $file_name);
|
|
|
|
foreach $file_path (@{$LstTask->{deleted}}) {
|
|
delete $_CACHE{$file_path};
|
|
|
|
$file_name = &Basename($file_path);
|
|
|
|
if (&IsTimestamp( $file_name )) {
|
|
delete $_CACHE{"$_UPDATE_PATH$file_name"};
|
|
} elsif (&IsVadeRetro( $file_name ) or &IsBlacklist( $file_name )) {
|
|
delete $_CACHE{$file_path.'.cache'};
|
|
}
|
|
}
|
|
|
|
my %check_sums = ();
|
|
foreach $type (qw(notify update add)) {
|
|
foreach $file_name (keys %{$LstTask->{$type}}) {
|
|
$check_sums{$file_name} = $LstTask->{$type}{$file_name};
|
|
}
|
|
}
|
|
|
|
foreach $type (qw(moved_notify moved)) {
|
|
foreach $file_path (@{$LstTask->{$type}}) {
|
|
$file_name = &Basename($file_path);
|
|
next unless exists $check_sums{$file_name};
|
|
|
|
if (&IsTimestamp( $file_name )) {
|
|
&insertDataIntoCache($file_path,$check_sums{$file_name});
|
|
&insertDataIntoCache("$_UPDATE_PATH$file_name",$check_sums{$file_name});
|
|
} elsif ((&IsVadeRetro( $file_name ) or &IsBlacklist( $file_name )) and -e $file_path.'.cache') {
|
|
&insertDataIntoCache($file_path.'.cache',$check_sums{$file_name});
|
|
} else {
|
|
&insertDataIntoCache($file_path,$check_sums{$file_name});
|
|
}
|
|
|
|
}
|
|
}
|
|
|
|
&Log('info', 'Cache data has been updated.');
|
|
}
|
|
|
|
#-----------------------------------------------------------------------
|
|
sub saveCacheData
|
|
{
|
|
&Log('info', 'Saving cache data...');
|
|
|
|
open FH, '>', $_CACHE_FILE
|
|
or die "Can not open file '$_CACHE_FILE' for writing: ($!)";
|
|
|
|
my ($file_path, $file_info, $check_sum, $mdate);
|
|
foreach $file_path (keys %_CACHE) {
|
|
$file_info = $_CACHE{$file_path};
|
|
$check_sum = $file_info->{check_sum};
|
|
$mdate = $file_info->{mdate};
|
|
|
|
unless(defined $check_sum) {
|
|
$check_sum = '';
|
|
}
|
|
|
|
print FH "$file_path:$check_sum:$mdate\n"
|
|
or die "Can not print into file $_CACHE_FILE ($!)";
|
|
}
|
|
close(FH);
|
|
&Log('info', 'Cache data has been saved.');
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
# IN (STRING) : file path
|
|
# OUT (STRING|UNDEF): check sum
|
|
sub getCheckSum
|
|
{
|
|
my $file_path = shift;
|
|
|
|
&Log('debug', "getCheckSum($file_path) has been called");
|
|
|
|
my ($check_sum, $cache_data);
|
|
if (-e $file_path) {
|
|
&Log('debug', "File '$file_path' exists, trying to find info about file in cache...");
|
|
if (exists $_CACHE{$file_path}) {
|
|
&Log('debug', 'Info about file has been found. Checking modification date...');
|
|
|
|
my $mdate = &getFileModificationDate($file_path);
|
|
|
|
if ($_CACHE{$file_path}{mdate} eq $mdate) {
|
|
&Log('debug', 'File has not been changed.');
|
|
$check_sum = $_CACHE{$file_path}{check_sum};
|
|
} else {
|
|
&Log('debug', 'File has been changed. Trying to calculate check sum...');
|
|
$cache_data = &insertDataIntoCache($file_path);
|
|
$check_sum = $cache_data->{check_sum};
|
|
}
|
|
} else {
|
|
&Log('warning', 'Can not find info about file, trying to calculate check sum...');
|
|
$cache_data = &insertDataIntoCache($file_path);
|
|
$check_sum = $cache_data->{check_sum};
|
|
}
|
|
} else {
|
|
&Log('debug', "Can not calculate check sum of file '$file_path', because file does not exists");
|
|
}
|
|
|
|
&Log('debug', "getCheckSum returned ('".(defined $check_sum?$check_sum:'')."')");
|
|
return $check_sum;
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
# IN (STRING) : file path
|
|
# IN (STRING) : check sum (optional)
|
|
# IN (STRING) : mdate (optional)
|
|
# OUT (HASH_REF): cache data about file
|
|
sub insertDataIntoCache
|
|
{
|
|
my ($file_path, $check_sum, $mdate) = @_;
|
|
|
|
&Log('debug', "insertDataIntoCache($file_path, ".(defined $check_sum? $check_sum : '').", ".(defined $mdate ? $mdate : '').") has been called");
|
|
|
|
$check_sum = &{$SumCheker}($file_path)
|
|
unless defined $check_sum;
|
|
|
|
unless (defined $mdate) {
|
|
$mdate = &getFileModificationDate($file_path);
|
|
}
|
|
|
|
my $prepared_check_sum = defined $check_sum
|
|
? $check_sum
|
|
: '';
|
|
|
|
&Log('debug', "insertDataIntoCache returned {check_sum => $prepared_check_sum, mdate => $mdate}");
|
|
return
|
|
$_CACHE{$file_path} = {check_sum => $check_sum, mdate => $mdate};
|
|
}
|
|
|
|
} #END OF CACHE SECTION
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
sub cleanupAgentData
|
|
{
|
|
my $Conf = shift;
|
|
|
|
&Log('debug', 'cleanupAgentData has been called');
|
|
|
|
my $success_flag = 1;
|
|
|
|
my $dir = $Conf->{ Paths }{ downloaddir };
|
|
my $cmd = "rm -rf $dir*";
|
|
&Log('debug', "Cleaning up dir '$dir'...");
|
|
if(-e $dir && system($cmd)) {
|
|
$success_flag = 0;
|
|
&Log('error', "Can not execute command: $cmd");
|
|
}
|
|
|
|
if ($success_flag) {
|
|
&Log('debug', 'Cleanup - OK');
|
|
} else {
|
|
&Log('debug', 'Cleanup - FAILED');
|
|
}
|
|
|
|
return $success_flag;
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (STRING): File path
|
|
#IN (STRING): DATA
|
|
sub saveDataIntoFileSimple
|
|
{
|
|
my ($file_path, $data, $mode) = @_;
|
|
&Log('info', "Saving data into file '$file_path'...");
|
|
|
|
$mode = '>' unless defined $mode;
|
|
|
|
open FH, $mode, $file_path
|
|
or die "Can not open file '$file_path' for writing: ($!)";
|
|
print FH $data;
|
|
|
|
close(FH);
|
|
&Log('info', 'Data has been saved.');
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (STRING) : Component name
|
|
#IN (ARRAY) ; Path id
|
|
sub generateComponentFilePath
|
|
{
|
|
my ($Conf, $component, @pathesId) = @_;
|
|
&Log( 'debug', "generateComponentFilePath has been called" );
|
|
|
|
my $working_dir = $Conf->{ Paths }{ workingdir };
|
|
my $component_dir = $working_dir.$component.'/';
|
|
|
|
my @result = ();
|
|
my $file;
|
|
for (@pathesId) {
|
|
&Log( 'debug', "Generating path: component '$component', id '$_'" );
|
|
if ($_ eq 'component_base_dir') {
|
|
$file = $component_dir;
|
|
} elsif ($_ eq 'lock') {
|
|
$file = $component_dir.'lock';
|
|
} elsif ($_ eq 'backup_data_dir') {
|
|
$file = $component_dir.'data/';
|
|
} elsif ($_ eq 'backup_diff') {
|
|
$file = $component_dir.'diff';
|
|
} else {
|
|
die "Unknown path: component '$component', id '$_'!";
|
|
}
|
|
push @result, $file;
|
|
&Log( 'info', "Generated file path: " . $file);
|
|
}
|
|
|
|
return
|
|
wantarray ? @result : shift @result;
|
|
}
|
|
|
|
{
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (HASH_REF): Task list
|
|
#IN (STRING) : Component name
|
|
sub createBackupByLstTask
|
|
{
|
|
my ($Conf, $LstTask, $component) = @_;
|
|
&Log('info', 'Creating backup...');
|
|
|
|
my $backup_component_data = &generateComponentFilePath($Conf, $component, 'backup_data_dir');
|
|
|
|
&createDirPath($backup_component_data)
|
|
unless -d $backup_component_data;
|
|
|
|
my $backup_info = &generateBackupInfo($Conf, $LstTask);
|
|
unless ($backup_info) {
|
|
&Log('info', 'Nothing to back up!');
|
|
return;
|
|
}
|
|
|
|
&cleanBackupData($Conf, $component);
|
|
&storeBackupData($Conf, $component, $backup_info);
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (HASH_REF): Task list
|
|
sub generateBackupInfo
|
|
{
|
|
my ($Conf, $LstTask) = @_;
|
|
&Log('info', "Generating backup info...");
|
|
|
|
my @delete = ();
|
|
&RemoveFiles( [keys %{$LstTask->{remove}}], $Conf, 1, \@delete, sub {return 1});
|
|
|
|
my (@add, @update) = ();
|
|
my $callback = sub {
|
|
my (undef, $file, $safetysuffix) = @_;
|
|
if (-e $file) {
|
|
push @update, $file;
|
|
} else {
|
|
push @add, $file;
|
|
}
|
|
return 1;
|
|
};
|
|
&MoveFiles( $Conf, $LstTask->{new}, undef, undef, $callback);
|
|
|
|
return 0
|
|
if !scalar @add && !scalar @update && !scalar @delete;
|
|
|
|
return {
|
|
restore => [@update, @delete],
|
|
remove => \@add,
|
|
}
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (STRING) : Component name
|
|
sub cleanBackupData
|
|
{
|
|
my ($Conf, $component) = @_;
|
|
&Log('info', "Cleaning up backup data...");
|
|
|
|
my ($backup_component_data, $backup_component_diff) =
|
|
&generateComponentFilePath($Conf, $component, 'backup_data_dir', 'backup_diff');
|
|
|
|
my ($files_list, $file);
|
|
foreach $files_list ($backup_component_data.'*', $backup_component_diff) {
|
|
foreach $file (glob( $files_list )) {
|
|
&Log('debug', "Deleting file '$file'...");
|
|
unless (-e $file) {
|
|
&Log('debug', "File '$file' is not exists. Skipping...");
|
|
next;
|
|
}
|
|
if (-d $file) {
|
|
&Log('debug', "'$file' is a directory. Skipping...");
|
|
next;
|
|
}
|
|
unlink $file
|
|
or die "Failed to delete file: $file ($!)";
|
|
}
|
|
}
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (STRING) : Component name
|
|
#IN (HASH_REF): Backup info
|
|
sub storeBackupData
|
|
{
|
|
my ($Conf, $component, $backup_info) = @_;
|
|
&Log('info', "Storing backup data...");
|
|
|
|
my ($backup_component_data, $backup_component_diff) =
|
|
&generateComponentFilePath($Conf, $component, 'backup_data_dir', 'backup_diff');
|
|
|
|
my $file_counter = 0;
|
|
my %file_alias = ();
|
|
my %filter_hash = ();
|
|
|
|
my $diff_file_content = '';
|
|
my ($type, $files);
|
|
foreach ({type => '+', files => $backup_info->{restore}}, {type => '-', files => $backup_info->{remove}}) {
|
|
($type, $files) = @{$_}{qw(type files)};
|
|
$diff_file_content .=
|
|
join "\n",
|
|
map {$type.' ' . $_ . ' ' . ($file_alias{$_} = ++$file_counter)}
|
|
grep !$filter_hash{$_} && ($filter_hash{$_} = 1), @{$files};
|
|
$diff_file_content .= "\n";
|
|
}
|
|
|
|
&saveDataIntoFileSimple($backup_component_diff, $diff_file_content, '+>>');
|
|
|
|
my $file;
|
|
foreach $file (@{$backup_info->{restore}}) {
|
|
die "Can not backup file '$file'"
|
|
unless &MoveFile($file,$backup_component_data.$file_alias{$file},'',0);
|
|
}
|
|
|
|
&Log('info', 'Backup complete');
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
# IN (STRING): path to file
|
|
sub parseBackupInfo
|
|
{
|
|
my ($backup_diff) = @_;
|
|
|
|
&Log('debug', 'Parsing backup...');
|
|
|
|
unless (-e $backup_diff) {
|
|
&Log('warning', "File '$backup_diff' does not exists! Nothing to parse.");
|
|
return;
|
|
}
|
|
|
|
open (FH, $backup_diff)
|
|
or die "Can not open file '$backup_diff' for reading!";
|
|
|
|
my ($line, @restore, @remove, $instance);
|
|
while (defined($line = <FH>)) {
|
|
chomp $line;
|
|
next unless $line =~ /^\s*([\-\+])\s*(\S+)\s*(\d+)$/;
|
|
$instance = $1 eq '+' ? \@restore: \@remove;
|
|
push @{$instance}, {file => $2, alias => $3};
|
|
}
|
|
|
|
close FH;
|
|
&Log('debug', 'Backup has been parsed!');
|
|
|
|
return {
|
|
restore => \@restore,
|
|
remove => \@remove
|
|
};
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
sub restoreBackup
|
|
{
|
|
my ($Conf, $component) = @_;
|
|
|
|
&Log('info', "Restoring backup for component '$component'...");
|
|
print STDERR "Restoring backup for component '$component'...\n";
|
|
|
|
if(-e $Conf->{ Paths }{ cachefile }) {
|
|
unlink $Conf->{ Paths }{ cachefile }
|
|
or die "Can not delete file '$Conf->{ Paths }{ cachefile }': $!";
|
|
}
|
|
|
|
my ($item, $file, $alias, $safetysuffix,
|
|
$backup_component_data, $backup_component_diff);
|
|
my (@moved_files, @deleted_files);
|
|
|
|
($backup_component_data, $backup_component_diff) =
|
|
&generateComponentFilePath($Conf, $component, 'backup_data_dir', 'backup_diff');
|
|
|
|
my $backup_info = &parseBackupInfo($backup_component_diff);
|
|
my $restore_info = undef;
|
|
if (!$backup_info || (!scalar @{$backup_info->{restore}} && !@{$backup_info->{remove}})) {
|
|
&Log('info', 'Nothing to restore!');
|
|
print STDERR "Nothing to restore!\n";
|
|
} else {
|
|
foreach $item (@{$backup_info->{restore}}) {
|
|
$file = $item->{file};
|
|
$alias = $item->{alias};
|
|
$safetysuffix = &IsBlacklist($file) ? '.cache' : '';
|
|
unless(&MoveFile($backup_component_data.$alias,$file,$safetysuffix,0)) {
|
|
&Log('error', "Can not restore file '$file'");
|
|
} else {
|
|
push @moved_files, $file;
|
|
}
|
|
}
|
|
|
|
my @deleted_files;
|
|
foreach $item (@{$backup_info->{remove}}) {
|
|
$file = $item->{file};
|
|
$alias = $item->{alias};
|
|
|
|
next unless -e $file;
|
|
|
|
unless(unlink $file) {
|
|
&Log('error', "Can not unlink file '$file': $!")
|
|
} else {
|
|
push @deleted_files, $file;
|
|
}
|
|
}
|
|
|
|
$restore_info = {
|
|
moved => \@moved_files,
|
|
deleted => \@deleted_files,
|
|
};
|
|
}
|
|
|
|
unless (&isUpdatesFrozen($Conf, $component)) {
|
|
&freezeUpdates($Conf, $component);
|
|
}
|
|
|
|
print STDERR "Backup for component '$component' has been restored!\n";
|
|
&Log('info', "Backup for component '$component' has been restored!");
|
|
|
|
return
|
|
$restore_info;
|
|
}
|
|
|
|
#-----------------------------------------------------------------------------
|
|
# IN (HASH) : updater configuration
|
|
# IN (HASH) : update task
|
|
# OUT: no
|
|
sub ViewBackupRestoreSummary
|
|
{
|
|
my ($Conf, $Task) = @_;
|
|
|
|
&Log( 'debug', "ViewBackupRestoreSummary() has been called ..." );
|
|
my $n_new = exists $Task->{moved} ? scalar @{$Task->{moved}} : 0;
|
|
my $n_rm = exists $Task->{deleted} ? scalar @{$Task->{deleted}} : 0;
|
|
&Log( 'verbose', "summary => restored: $n_new, removed: $n_rm" );
|
|
if( $n_new != 0 or $n_rm != 0 ) {
|
|
print STDERR "Dr.Web restore details:\n";
|
|
print STDERR "\n";
|
|
print STDERR "Following files have been restored:\n\t".join("\n\t", @{$Task->{moved}} )."\n"
|
|
if $n_new ne '0';
|
|
print STDERR "Following files have been removed:\n\t".join("\n\t", @{$Task->{deleted}} )."\n"
|
|
if $n_rm ne '0';
|
|
print STDERR "\n";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
{
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (STRING) : Component name
|
|
sub isUpdatesFrozen
|
|
{
|
|
my ($Conf, $component) = @_;
|
|
|
|
&Log( 'info', "Trying to check if updates are frozen..." );
|
|
|
|
my $lock_file = &generateComponentFilePath($Conf, $component, 'lock');
|
|
|
|
my $result =
|
|
-e $lock_file;
|
|
|
|
if ($result) {
|
|
&Log( 'warning', "Updates are frozen!" );
|
|
} else {
|
|
&Log( 'info', "Updates are not frozen." );
|
|
}
|
|
|
|
return $result;
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (STRING) : Component name
|
|
sub freezeUpdates
|
|
{
|
|
my ($Conf, $components) = @_;
|
|
|
|
&Log( 'debug', "freezeUpdates has been called" );
|
|
|
|
$components = [$components] unless ref $components eq 'ARRAY';
|
|
|
|
my $component;
|
|
for $component (@$components) {
|
|
&Log( 'info', "Trying to freeze updates for component '$component'..." );
|
|
|
|
my $lock_file = &generateComponentFilePath($Conf, $component, 'lock');
|
|
open( FH, '>', $lock_file )
|
|
or die 'Can not open file \''.$lock_file.'\' for writing: '.$!;
|
|
close FH;
|
|
&Log( 'warning', "Updates for component '$component' are frozen." );
|
|
|
|
print STDERR "Updates for component '$component' are frozen.\n";
|
|
print STDERR "Run command '$0 --unfreeze=$component' to start updates again.\n\n";
|
|
}
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (STRING) : Component name
|
|
sub unfreezeUpdates
|
|
{
|
|
my ($Conf, $components) = @_;
|
|
|
|
&Log( 'debug', "unfreezeUpdates has been called" );
|
|
|
|
$components = [$components] unless ref $components eq 'ARRAY';
|
|
|
|
my $component;
|
|
for $component (@$components) {
|
|
&Log( 'info', "Trying to unfreeze updates for component '$component'..." );
|
|
|
|
my $lock_file = &generateComponentFilePath($Conf, $component, 'lock');
|
|
next
|
|
unless -e $lock_file;
|
|
unlink($lock_file)
|
|
or die 'Can not unlink file \''.$lock_file.'\': '.$!;
|
|
|
|
&Log( 'warning', "Updates for component '$component' are no longer frozen." );
|
|
print STDERR "Updates for component '$component' are no longer frozen.\n";
|
|
}
|
|
}
|
|
|
|
#------------------------------------------------------------------------
|
|
#IN (HASH_REF): Config
|
|
#IN (STRING) : Component name
|
|
sub frozenWarings
|
|
{
|
|
my ($Conf, $component) = @_;
|
|
&Log('warning',"Updates for component '$component' are frozen!");
|
|
&Log('warning',"Run command '$0 --unfreeze=$component' to start updates again.");
|
|
if(lc($Conf->{Self}{cronsummary}) eq 'yes') {
|
|
print STDERR "Updates for component '$component' are frozen!\n";
|
|
print STDERR "Run command '$0 --unfreeze=$component' to start updates again.\n";
|
|
}
|
|
}
|
|
|
|
}
|
|
|
|
|
|
#------------------------------------------------------------------------
|
|
sub printHelpAndExit
|
|
{
|
|
my ($Config) = @_;
|
|
|
|
&Log( 'debug', "printHelpAndExit has been called" );
|
|
|
|
my $possible_compinents =
|
|
join ', ', getPossibleComponents($Config);
|
|
|
|
print STDERR <<HELP;
|
|
Usage:
|
|
$0 [--ini=</path/to/ini/file>] [--what=<section name>] [--not-need-reload=<daemon1,daemon2..>]\\
|
|
[--mode=<mode name>] [--download_dir=</path/to/dir>] [--lst=<path/to/lst/file>]\\
|
|
[--freeze=<component1,component2,...>] [--unfreeze=<component1,component2,...>]\\
|
|
[--restore=<component1,component2,...>] [--components] [--help]
|
|
|
|
Available command-line parameters:
|
|
ini -- sets path to configuration file;
|
|
what -- defines configuration file section to be used;
|
|
not-need-reload -- defines daemons that will not reload after updating
|
|
(possible values: drwebd, maild, icapd, lotusd);
|
|
mode -- sets mode for update process
|
|
(possible values: agent, default);
|
|
download_dir -- sets path to download directory (only for agent mode);
|
|
lst -- sets path to lst-file (only for agent mode);
|
|
freeze -- defines frozen components that will not be updated
|
|
(possible values: $possible_compinents);
|
|
unfreeze -- unfreezes components
|
|
(possible values: $possible_compinents);
|
|
restore -- restores components to previous version from backup copies
|
|
(possible values: $possible_compinents);
|
|
components -- shows info about components to be updated;
|
|
help -- prints this help.
|
|
|
|
Examples:
|
|
$0
|
|
$0 --components
|
|
$0 --ini=/etc/drweb/drweb32.ini --not-need-reload=drwebd,maild
|
|
$0 --help
|
|
HELP
|
|
exit &EXIT_STATUS_OK;
|
|
}
|