File: //usr/local/cwaf/scripts/updater.pl
#!/bin/sh
eval 'if [ -x /usr/local/cpanel/3rdparty/bin/perl ]; then exec /usr/local/cpanel/3rdparty/bin/perl -x -- $0 ${1+"$@"}; else exec /usr/bin/perl -x $0 ${1+"$@"}; fi;'
if 0;
#!/usr/bin/perl
#SVN
use strict qw(refs subs);
use warnings;
use IO::Handle;
use File::Path qw(remove_tree);
use File::Find;
use Getopt::Long;
BEGIN { require '/etc/cwaf/use_lib.pl' if -f '/etc/cwaf/use_lib.pl'; }
use Comodo::CWAF::Main;
use Comodo::CWAF::ClientAPI;
use Comodo::CWAF::Excludes;
use POSIX;
no warnings 'redefine';
# prototypes
sub do_console_log($);
sub do_exit($);
#Init vars
our (%conf,$pr_name);
my ($var,$log_file,$tmp_log,$wpanel_flag,%opts);
# LOG file
# undef - STDOUT
# or some path to log file
$log_file = $conf{'log_dir'} . '/' . $conf{'utils_log'};
$tmp_log = $conf{'log_dir'} . '/' . $conf{'updater_log'};
$conf{'pid_dir'} = $conf{'cwaf_path'} . '/run';
######################################## BEGIN ####################################
$| = 1;
# script name for logging
$pr_name = get_name();
# updater not ran by cpanel, set flag (default value)
$wpanel_flag = 0;
# set avail arguments
$var = GetOptions(\%opts,'help|h','version|v','wpanel|w','directadmin-postinstall|p=s','restore|r' );
# argument --directadmin-postinstall, -p
if( $opts{'directadmin-postinstall'} ) {
eval "use Comodo::CWAF::Platform";
eval "use Comodo::CWAF::Excludes";
#if can't load required modules, exits
do_error_exit("Can't load required perl modules, exiting") if($@);
do_error_exit("No DirectAdmin system, exiting") unless (&is_directadmin());
# if no errors, set wpanel_flag to true.
$wpanel_flag = 1;
# set max debug level & print log to $tmp_log
unlink($tmp_log) if( -e $tmp_log );
open(LOGFILE, ">>$tmp_log");
$conf{'debug'} = 10;
my $file = $opts{'directadmin-postinstall'};
do_log("Processing rules archive $file",8);
&do_postdownload($file);
&do_exit(0);
}
# argument --help, -h
if( $opts{'help'} ) { &do_print_help_message; exit(0); }
# argument --version, -v
elsif( $opts{'version'} ) {
print "Plugin version=".get_client_version()."\n";
print "Installed rules version=".get_local_rules_version()."\n";
print "Available rules version=".get_remote_rules_version()."\n";
print "Installed for web platform=".get_web_platform()."\n";
exit(0);
}
# argument --wpanel, -w
elsif( $opts{'wpanel'} ) {
eval "use Comodo::CWAF::Platform";
eval "use Comodo::CWAF::Excludes";
#if can't load required modules, write logs to STDERR
if($@) { open(LOGFILE, ">&STDERR"); }
# if no errors, set wpanel_flag to true.
else {
$wpanel_flag = 1;
# set max debug level & print log to $tmp_log
unlink($tmp_log) if( -e $tmp_log );
open(LOGFILE, ">>$tmp_log");
$conf{'debug'} = 10;
}
}
# argument --restore, -r
elsif( $opts{'restore'} ) {
my $restore_complete = 0;
my ($status, $errmsg) = (0, '');
if(&make_restore_rules()) {
($status, $errmsg) = create_exclude_list(undef,1);
$restore_complete = $status ? 1 : 0;
}
if($restore_complete) {
do_log("Previous version of rules has been restored", 1);
do_console_log("Previous version of rules has been restored");
do_exit(0);
} else {
do_log("Restore failed $errmsg", 1);
do_console_log("Restore failed $errmsg");
do_exit(1);
}
}
# if not set any argument
elsif( $var ) {
# open log handler
if(defined($log_file)) {
unless(open(LOGFILE, ">>$log_file")) {
print STDERR "ERROR: can't open file $log_file\n";
}
# redirect errors to /dev/null if debug < 6
# or redirect errors to logfile
if(int($conf{'debug'}) < 6) {
open(DEVNULL, ">/dev/null");
STDERR->fdopen(\*DEVNULL, 'w');
}
else {
STDERR->fdopen(\*LOGFILE, 'w');
}
}
else {open(LOGFILE, ">&STDERR");}
}
LOGFILE->autoflush(1);
do_log("debug is ON, level = $conf{'debug'}", 9);
# check pid file
do_log("create pid file", 10);
$var = create_pid_file();
if($var == 0 || $var > 1) {
do_log("ERROR: can't create pid file", 0) if($var == 0);
do_log("WARN: another process is started ($var)", 0) if($var > 1);
&do_exit(0);
}
# start work
&main_worker();
##################################### FUNCTIONS ###################################
# &do_exit($return_code)
# log "exit"-message and exit
#
# RETURN: none
sub do_exit($) {
my ($rcode) = @_;
# fix permissions for Plesk and DA if ran as root
&fix_permissions();
do_log("update process finished!",1);
do_console_log("update process finished!");
exit($rcode);
}
# &do_error_exit($exit_msg)
# log exit message and do_exit
#
# RETURN: none
sub do_error_exit {
my ($msg) = @_;
$msg = "Update failed. See logs for details." unless(defined($msg));
do_console_log("$msg\n");
&do_exit(1);
}
# &do_console_log($msg)
# log to console if $wpanel_flag not set
#
# RETURN: none
sub do_console_log($) {
my ($msg) = @_;
print("$msg\n") unless($wpanel_flag);
}
# &restore_rules_broken_update()
# restore previous rules version(if exists), if current rules is broken
#
# RETURN: none
sub restore_rules_broken_update {
my ($work_dir1,$work_dir2,$new_workdir);
$work_dir1 = $conf{'cwaf_path'} . '/tmp/rules/workdir1';
$work_dir2 = $conf{'cwaf_path'} . '/tmp/rules/workdir2';
# get current working directory
$new_workdir = get_backupdir();
do_log('update failed, restoring previous rules version',10);
# check if backupdir contain rules
if( ! -e "$new_workdir/rules/rules.dat") {
do_log("WARNING: can't revert, no previous rules version",5);
return;
}
# set new working directory & make new symlink to it
set_workdir($new_workdir);
make_symlink($new_workdir);
}
# &link_userdata()
# link userdata to rules directory if present
#
# RETURN: 0 - if failed or 1 - if OK
sub do_link_userdata {
my ($rulesdir) = @_;
do_log('link userdata to rules',5);
my $userdata_dir = "$conf{'cwaf_path'}/etc/userdata";
mkdir $userdata_dir unless (-d $userdata_dir);
unless( opendir(DIR, $rulesdir ) ) {
do_log("Can't open rules directory ($rulesdir)",1);
return 0;
}
while(my $file = readdir DIR) {
next unless($file =~ /^userdata_/);
# file->userfile if not stored
rename "$rulesdir/$file", "$userdata_dir/$file" unless( -e "$userdata_dir/$file");
do_log("rename $rulesdir/$file, $userdata_dir/$file",5) unless( -e "$userdata_dir/$file");
# delete file in rulesdir if exists
unlink "$rulesdir/$file" if( -e "$rulesdir/$file");
do_log("unlink $rulesdir/$file",5) if( -e "$rulesdir/$file");
# userfile->file if stored
symlink("$userdata_dir/$file", "$rulesdir/$file") if( -e "$userdata_dir/$file");
do_log("symlink $userdata_dir/$file, $rulesdir/$file",5) if( -e "$userdata_dir/$file");
}
closedir(DIR);
return 1;
}
# &main_worker()
#
# RETURN: none
sub main_worker {
my (%req, %resp);
my ($md5sum_orig,$dfile,$var,$lver,$rules_dir);
# fix symlink to exclude yaml
&do_fix_yaml_links();
# build path to archive with rules
$dfile = $conf{'cwaf_path'} . '/tmp/rules.tgz';
# build request, decide what to do - diff update or new install
&build_req(\%req,$dfile);
# if http request failed - exit with return code 1
unless( get_http_content(\%req,\%resp) ) {
do_log('HTTP response code: '.$resp{'r_code'},1);
do_log('Error reason: '.$resp{'x-error-reason'},1) if($resp{'x-error-reason'});
do_log("ERROR: can't connect to CWAF rules server",0);
do_error_exit("ERROR: can't connect to CWAF rules server. See logs for details.");
}
# check HTTP return code
# if OK(200) => new version exists, start download
if($resp{'r_code'} =~ "200") {
do_log('file has been downloaded successfully: ' . $resp{'filename'},10);
# check downloaded file
do_error_exit() unless( check_downloaded_file($dfile,$resp{'md5_orig'}) );
# post download steps
&do_postdownload($dfile);
do_log("update successful",1);
do_console_log("update successful");
}
# Not Modified(304) - installed version is latest
elsif($resp{'r_code'} =~ "304") {
do_log('current version is up to date',1);
do_console_log("current version is up to date");
do_exit(0);
}
# client or server error => exit
else {
do_log('ERROR: unknown return code (' . $resp{'r_code'} . '), exit',0);
do_error_exit('ERROR: unknown return code (' . $resp{'r_code'} . '), exit');
}
do_exit(0);
}
# &help_message()
# print help message
#
# RETURN: none
sub do_print_help_message {
print <<END;
Usage: $0 [arguments]
Arguments:
-h, --help - this help message
-w, --wpanel - web panel update mode
-r, --restore - restore previous rules
-p, --directadmin-postinstall - do postinstall steps on DirectAdmin
-v, --version - show rules and plugin versions
END
}
# &build_req($request_HASHREF)
# build request for CWAF rules server
#
# RETURN: none
sub build_req($) {
my ($req,$dfile) = @_;
my $lver = get_local_rules_version();
# build request for download file
%$req = ( 'url' => $conf{'cwaf_url'},
'post' => {
'login' => $conf{'cwaf_login'},
'password' => $conf{'cwaf_passwd'},
'client_version' => get_client_version()
},
'content' => [ ':content_file' => $dfile ]
);
# if version defined
if($lver) {
# Enhancement #54347: Always get latest rules version
$req->{'post'}{'version'} = get_remote_rules_version();
$req->{'post'}{'local'} = $lver;
$req->{'post'}{'act'} = 'download';
}
else {
do_log('rules are not installed, try to install',10);
# if rules not installed update not need, download full package
$req->{'post'}{'act'} = 'download';
}
}
# &do_cpanel_steps()
# try to restart Apache HTTPD use Cpanel function and re-index CWAF catalog
#
# RETURN: none
sub do_web_panel_steps {
#update user exclude lists with rules data
#&update_global_exclude();
#create exclude lists & reinit scheme
#&create_exclude_list(undef,1);
# init restart attempts counter
for(my $j=1; $j<=3; $j++) {
if(run_restart_apache()) {
do_log("successful webserver restart",10);
last;
}
else {
do_log("webserver restart failed (try $j)",2);
# restore previous rules after 1st attempt
&restore_rules_broken_update if($j == 1);
}
}
}
# &do_create_dir(list_ARRAYREF)
# create all directories in list_ARRAYREF
#
# RETURN: 0 - error, 1 - ok
sub do_create_dir($) {
my ($need_to_create) = @_;
my ($var,$result);
# if $directory is array, add all it items to @need_to_create array
if( ref($need_to_create) ne 'ARRAY') {
do_log("WARN: argument must be array, exit",8);
return 0;
}
foreach my $item(@$need_to_create) {
# if item is directory and readable - skip it
if(-r $item && -d $item) {
next;
}
# if can't create dir, exit
if( system("mkdir -p $item") ) {
# print more info, if debug=10
$var = "WARN: can't create directory $item";
$var .= "($!)" if($conf{'debug'} == 10);
do_log($var,8);
return 0;
}
}
return 1;
}
# &do_create_backupdir($path_to_directory,$clean_flag)
# create directory scheme and push it to "do_create_dir" funcion
#
# RETURN: none
sub do_create_backupdir($;$) {
my ($dir,$clean_dir_before_make) = @_;
my @dirs = ("$dir","$dir/yml","$dir/rules");
# if set flag, try to clean $dir directory
if($clean_dir_before_make) {
return 0 unless( make_dir_clean('all',$dir) );
}
# try to create directories
do_create_dir(\@dirs);
return 1;
}
# &set_workdir($path_to_working_directory)
# set current working dir
#
# RETURN: 1 - ok, 0 - error
sub do_backup_rules($) {
my ($new_workdir) = @_;
my $work_dir1 = $conf{'cwaf_path'} . '/tmp/rules/workdir1';
my $work_dir2 = $conf{'cwaf_path'} . '/tmp/rules/workdir2';
# try to create directories for new version of rules
&do_create_backupdir($work_dir1);
&do_create_backupdir($work_dir2);
if( my $cur_workdir = get_workdir() ) {
$$new_workdir = get_backupdir();
&do_create_backupdir($$new_workdir, 1);
if( system("cp -fR $cur_workdir/yml/* $$new_workdir/yml/") ) {
do_log("ERROR: can't copy excludes directory $cur_workdir/yml to $$new_workdir/yml($?)", 1);
#do_error_exit("can't copy directory $cur_workdir/yml to $$new_workdir/yml($?)");
}
}
else {
$$new_workdir = $work_dir1;
}
&set_workdir($$new_workdir);
&make_symlink($$new_workdir);
}
# &do_fix_perms($path_to_working_directory)
# fix owner & group for directory
#
# RETURN: 1 - ok, 0 - error
sub do_fix_perms($) {
my ($dirname) = @_;
my ($curuid,$curgid,@filelist,$find_dir);
# get uid & gid of current process
$curuid = POSIX::getuid();
$curgid = POSIX::getgid();
$find_dir = ($dirname =~ /\/$/) ? $dirname : $dirname . "/";
find sub { push(@filelist,$File::Find::name); }, ($find_dir);
eval { chown $curuid, $curgid, @filelist; };
if($@) {
do_log("WARN: can't change permissions ($!)",8);
return 0;
}
return 1;
}
# &do_fix_yaml_links($dirname)
# fix links to exclude.yaml before update
#
# RETURN: 1 - ok, 0 - error
sub do_fix_yaml_links() {
my ($var, $retval);
$retval = 1;
# fix symlink for exclude.yml
if ( -l "$conf{'cwaf_path'}/etc/yml/exclude.yml" ) {
unlink("$conf{'cwaf_path'}/etc/yml/exclude.yml");
$var = system("cp -f $conf{'cwaf_path'}/$conf{'rules_dir'}/exclude.yml $conf{'cwaf_path'}/etc/yml/exclude.yml");
if($var) {
do_log("ERROR: can't fix symlink for exclude.yaml ($!)",0);
$retval = 0;
}
}
return $retval;
}
# &make_yaml_link()
# create symlinks to exclude.yaml
# RETURN: 1 - ok, 0 - error
sub make_yaml_links() {
unless(-e $conf{'cwaf_path'}.'/etc/yml/scheme.yml') {
# copy original exclude yaml, only for 1st install
$var = system("cp -f $conf{'cwaf_path'}/$conf{'rules_dir'}/exclude.yml $conf{'cwaf_path'}/etc/yml");
if($var) {
do_log("ERROR: can't copy original exclude.yaml($!)",0);
}
}
# make symlink for scheme.yml
unlink("$conf{'cwaf_path'}/etc/yml/scheme.yml") if( -e $conf{'cwaf_path'}.'/etc/yml/scheme.yml');
$var = symlink(&get_workdir()."/rules/scheme.yml", "$conf{'cwaf_path'}/etc/yml/scheme.yml");
unless($var) {
do_log("ERROR: can't create symlink for scheme.yaml($!)",0);
}
}
sub do_postdownload($) {
my ($dfile) = @_;
my ($rules_dir, $var);
unless(defined $dfile) {
do_log("No downloaded file provided, exiting",0);
&do_exit(1);
}
unless(-e $dfile) {
do_log("Can't find downloaded rules file, exiting",0);
&do_exit(1);
}
# make rules backup
do_log("make backup for previous rules version",10);
do_error_exit() unless ( &do_backup_rules(\$rules_dir) );
do_log("extract rules",10);
$var = system("tar -zxof $dfile -C $rules_dir/rules");
# get system() return code
if($var) {
do_log("can't extract rules(error $@)",0);
do_console_log("can't extract rules(error $@).\nRestoring previous rules version");
&restore_rules_broken_update;
}
# link userdata to rules dir
&do_link_userdata("$rules_dir/rules");
# fixing directory permissions
&do_fix_perms($rules_dir);
# create yaml links after install
&make_yaml_links();
# update exclude lists
&update_global_exclude();
# if script runned from Cpanel or Plesk, try to restart apache
# &do_web_panel_steps if($wpanel_flag);
}