HEX
Server: Apache/2
System: Linux nexus-01 4.18.0-553.120.1.el8_10.x86_64 #1 SMP Mon Apr 20 18:04:27 EDT 2026 x86_64
User: aglcoke (1118)
PHP: 8.2.31
Disabled: mail,exec,system,passthru,shell_exec,proc_close,proc_open,dl,popen,show_source,posix_kill,posix_mkfifo,posix_getpwuid,posix_setpgid,posix_setsid,posix_setuid,posix_setgid,posix_seteuid,posix_setegid,posix_uname
Upload Files
File: //usr/local/cwaf/modules/CPAN/lib/Comodo/CWAF/ClientAPI.pm
package Comodo::CWAF::ClientAPI;
use strict qw(vars refs);

our (%conf);
BEGIN {
  our (@ISA, @EXPORT);
  use Exporter;
  use LWP;
  use LWP::Simple;
  use JSON;
  use Digest::MD5;
  use File::Path qw(remove_tree);
  use utf8;

  # Load CWAF modules
  use Comodo::CWAF::Main;# qw(do_log %conf get_backupdir get_workdir is_apache is_litespeed is_nginx is_cpanel is_plesk is_webmin is_directadmin);
  use Comodo::CWAF::Platform;

  @ISA = qw(Exporter);
  @EXPORT = qw(download_rules get_cwafapi_json test_credentials get_http_content get_http_file make_backup_cpanel_plugin make_backup_rules make_restore_rules make_restore_cpanel_plugin check_downloaded_file get_remote_rules_version send_feedback check_rules_version make_dir_clean get_updater_version get_local_rules_version set_workdir make_symlink get_client_version get_web_platform get_available_version is_dir_empty fix_permissions get_useragent);
  }


# &check_downloaded_file($path_to_file,$md5sum_of_file)
# check that downloaded file is valid
# RETURN:
#  0 - error
#  1 - all ok
sub check_downloaded_file($;$) {
  my ($filename,$md5sum_orig) = @_;
  my ($md5sum);

  # check file is exists and can be read
  if(-e $filename && -r $filename)  {

    # get file md5 sum
    get_md5sum_file($filename,\$md5sum);
    do_log("$filename original md5sum - $md5sum_orig",10);
    do_log("$filename local md5sum - $md5sum",10);

    # check md5 sum of original and downloaded file
    if($md5sum eq $md5sum_orig) {
      do_log("file successfully saved ($filename)",10);
      return 1;
      }
    else {
      do_log("WARN: downloaded file is broken,invalid crc",5);
      }
    }
    
  else {
    do_log("WARN: file doesn't exist or isn't readable ($filename)",5);
    }
    
  return 0;
}

# &get_md5sum_file ($filename, \$md5_sum_of_file_scalarref)
# make MD5 sum of file
# RETURN:
# 0 - if got error
# 1 - all ok
sub get_md5sum_file($$) {
  my ($filename, $md5sum) = @_;
  my ($md5,$md5result);

  unless( -r $filename )  {
    do_log("can't open file to get md5sum ($filename)",10);
    return 0;
    }
    
  open(FILE, "<$filename");

  # enable binary mode
  binmode(FILE);

  $md5 = Digest::MD5->new;
  $md5result = $md5->addfile(FILE);

  # save md5 sum 
  $$md5sum = $md5result->hexdigest;
  }

# &make_restore_cpanel_plugin
# restore cpanel plugin from backup
# RETURN:
# 0 - if got error
# 1 - all ok
sub make_restore_cpanel_plugin {
  my ($cgidir,$plugin_cgi,$var);

  # build path to cgidir
  $cgidir = join('/', $conf{'cwaf_path'}, 'tmp/cwaf');

  # build path to addod_cwaf.cgi
  $plugin_cgi = join('/', $conf{'cwaf_path'} , 'tmp/addon_cwaf.cgi');
  

  # check system() return code
  unless( system("cp -fR $cgidir " . $conf{'cgi_dir'}) ) {
    do_log("WARN: can't copy cgidir from backup directory",5);
    return 0;
    }

  return 0 unless ( &make_dir_clean($cgidir,$cgidir) );
    
  # check system() return code
  unless( system("cp -f $plugin_cgi " . $conf{'cgi_dir'}) ) {
    do_log("WARN: can't copy plugin from backup directory",5);
    return 0;
    }  

  return 1;
  }

# &make_restore_rules
# restore CWAF rules from backup
# RETURN: no return
sub make_restore_rules {
  my $work_dir1 = $conf{'cwaf_path'} . '/tmp/rules/workdir1';
  my $work_dir2 = $conf{'cwaf_path'} . '/tmp/rules/workdir2';

  my $cur_workdir = get_workdir();
  my $new_workdir = &get_backupdir();
  # no conf files in dir
  return 0 unless (scalar glob("$new_workdir/rules/*.conf"));
  make_dir_clean('all',$cur_workdir);
  set_workdir($new_workdir);
  make_symlink($new_workdir);
  return 1;
}

sub make_dir_clean($;$) {
  my ($type,$path) = @_;
  my ($dirname);
  if($type eq "rules") {
    $dirname = join('/',$conf{'cwaf_path'},$conf{'rules_dir'});
    }
  elsif($type eq "backuprules") {
    $dirname = join('/',$conf{'cwaf_path'},'tmp',$conf{'rules_dir'});
    }
  else {
    $dirname = $path if($path);
    }
  do_log("prepare to remove directory $dirname",10); 
    
  if(-d $dirname) {
    do_log("remove directory $dirname",10);
    remove_tree($dirname,{ keep_root => 1, error => \my $err });

    if(@$err) {
      do_log("can't clean $dirname directory",10);
      return 0;
      }
                  
    }
  
  # if got error while purge directory => exit
  return 1;
  }  


# &get_cwafapi_json($request_hashref,$response_hashref)
# send data to CWAF, check received data and save it
# return: 
# 0 - if got error or timeout
# 1 - if all ok
#
sub get_cwafapi_json($$)  {
  my ($request, $response) = @_;
  my ($var, %req, %resp, $json, $result);

  # build request
  %req =  (   'url'     =>    $conf{'cwaf_url'}, 
              'post'    =>  {   
                              'login'     =>  $conf{'cwaf_login'}, 
                              'password'  =>  $conf{'cwaf_passwd'}
                            }
          );

  # parse input %request and add it keys & values to %req. 
  while (my ($k, $v) = each(%$request)){
    # build complete request
    $req{'post'}{$k} = $v if($v);
    }

  # try to make connection to http-server
  $var = get_http_content(\%req,\%resp);
  if($var) {
    do_log("parse JSON from CWAF server",10);
    $json = new JSON;

    # try to convert JSON-data to hash.
    eval { $result = $json->allow_nonref->utf8->decode( $resp{'content'} );  };

    if($@)  {
      do_log('WARN: malformed JSON data',9);
      return 0;
      }

    do_log("got answer from CWAF ($result->{'emsg'})",10);

    if(defined($result->{'status'})) {
      # save result hash-ref
      do_log('save response',10);

      while (my ($k, $v) = each(%$result))  { $response->{$k} = $v; }
      
      return 1;
      }
    else  {
      do_log("got error",10);
      }
    }

  else  {
    do_log("can't connect to CWAF server",10);
    $response->{'status'} = 0;
    $response->{'r_code'} = $resp{'r_code'};
    $response->{'x-error-reason'} = $resp{'x-error-reason'};
    }

  return 0;
  }

# &test_cwafapi_credentials($login,$passwd)
# send data to CWAF, check received data and save it
# return: 
# (0, api_response) - if got error or timeout
# (1, api_response) - if all ok
#
sub test_credentials($$)  {
  my ($login, $passwd) = @_;
  my ($var, %req, %resp);

  # build request
  %req =  (   
              'act'     =>    'info',
              'url'     =>    $conf{'cwaf_url'}, 
              'post'    =>  {   
                              'login'     =>  $login, 
                              'password'  =>  $passwd,
                            }
          );

  # try to make connection to http-server
  $var = get_http_content(\%req,\%resp);

  if($var) {
    return (1, "$resp{'r_code'} $resp{'x-error-reason'}");
  } else  {
    return (0, "$resp{'r_code'} $resp{'x-error-reason'}");
  }

}

# &get_http_content($request_hashref,$response_hashref)
# download file to spooldir, return answer to $response_hashref
# return: 0 - if got error or timeout
#         1 - if all ok
sub get_http_content($$)  {
  my ($req,$resp) = @_;
  my ($response,$lwp_timeout,$lwp,$s_flag);
 
  # set defaukt timeout
  $lwp_timeout = defined($conf{'http_timeout'}) ? $conf{'http_timeout'} : 60;

  # disable hostname check in certificates(HTTPS)
  $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
  # $lwp = LWP::UserAgent->new();
  # enable this line for IO::Socket::SSL > 1.950 < 1.988-1
  $lwp = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0 });
  $lwp->timeout($lwp_timeout);
  $lwp->cookie_jar({
    file => $conf{'http_cookie_file'},
    autosave => 1
  });

  # new user agent
  #$lwp->agent('Mozilla/5.0 (compatible; ComodoCWAFbot/1.0; +http://www.comodo.com/)');
  #$lwp->agent('CWAF_Client/'.get_client_version());
  $lwp->agent(&get_useragent());
  $lwp->default_header('Accept-Language' => "en-us,en;q=0.5");
  $lwp->default_header('Accept' => "text/html;q=0.9,*/*;q=0.8");
  $lwp->default_header('Accept-Charset' => "ISO-8859-1,utf-8;q=0.7,*;q=0.7");

  # set save to file flag, if not set - try to decode content
  $s_flag = grep (':content_file', @{ $req->{'content'} } ) ?  1 : 0;


  do_log("lwp_params: timeout=$lwp_timeout sec, save_to_file flag: $s_flag", 10);
  
  # add web paltform flag 0 - apache, 1 - litespeed, 2 - nginx, 4 - modsecurity 3
  $req->{'post'}{'source'} = '0' if &is_apache();
  $req->{'post'}{'source'} = '1' if &is_litespeed();
  $req->{'post'}{'source'} = '2' if &is_nginx();
  $req->{'post'}{'source'} = '4' if &is_modsec3(); # is_nginx3()

  # make POST request
  $response = $lwp->post( $req->{'url'}, $req->{'post'}, @{$req->{'content'}} );

  # save return code & content type
  $resp->{'r_code'} = $response->code;
  $resp->{'content-type'} = $response->content_type;
  if($response->header('X-Error-Reason')) {
    $resp->{'x-error-reason'} = $response->header('X-Error-Reason');
  } else {
    $resp->{'x-error-reason'} = $response->message;
  }


  # save MD5 hash for downloaded file
  $resp->{'md5_orig'} = $response->header('X-Arch-MD5') if($response->header('X-Arch-MD5'));

  # save filename of download file
  if($response->header('Content-Disposition')) {
    $resp->{'filename'} = $1 if($response->header('Content-Disposition') =~ /filename=\"([^"]+)\"/);
    }

  # success 200 OK
  if($response->code =~ "200" || $response->code =~ "304")	{
    # if not set save flag - not try check decoded_content
    return 1 if($s_flag);
    
    if( defined($response->decoded_content) ) {
      do_log('normalize content', 10);
      
      eval {
        $resp->{'content'} = $response->decoded_content(default_charset => 'utf-8');
        };

      if($@) {
        do_log('unknown charset in http response, truncate body', 10);
        $resp->{'content'} = '';
        }
      }  
    return 1;
    }
  return 0;
}

# &get_http_file($url,$file)
# download file from $url to $file
# return: HTTP error code (200 is ok)
sub get_http_file($$)  {
  my ($url,$file) = @_;
  my ($response,$lwp_timeout,$lwp);
 
  do_log("Downloading file url=$url, save to file: $file", 10);
  
  # set default timeout
  $lwp_timeout = defined($conf{'http_timeout'}) ? $conf{'http_timeout'} : 60;

  # disable hostname check in certificates(HTTPS)
  $ENV{'PERL_LWP_SSL_VERIFY_HOSTNAME'} = 0;
  # $lwp = LWP::UserAgent->new();
  # enable this line for IO::Socket::SSL > 1.950 < 1.988-1
  $lwp = LWP::UserAgent->new(ssl_opts => { verify_hostname => 0, SSL_verify_mode => 0 });
  $lwp->timeout($lwp_timeout);  
  $lwp->cookie_jar({
    file => $conf{'http_cookie_file'},
    autosave => 1
  });

  # new user agent
  #$lwp->agent('Mozilla/5.0 (compatible; ComodoCWAFbot/1.0; +http://www.comodo.com/)');
  #$lwp->agent('CWAF_Client/'.get_client_version());
  $lwp->agent(&get_useragent());
  $lwp->default_header('Accept-Language' => "en-us,en;q=0.5");
  $lwp->default_header('Accept' => "text/html;q=0.9,*/*;q=0.8");
  $lwp->default_header('Accept-Charset' => "ISO-8859-1,utf-8;q=0.7,*;q=0.7");

  # make file request
  $response = $lwp->get( $url, ':content_file' => $file );

  return $response->code;
}


# &get_remote_rules_version()
# download file to spooldir, return answer to $response_hashref
# return:
# 0 - error, if all ok - rules version

sub get_remote_rules_version() {
  return Comodo::CWAF::DAdmin::da_get_available_version() if(&is_directadmin());

  my (%req,%resp);
  
  do_log("try to get data from CWAF server",10);
    
  # add  argument "act" to POST method
  $req{'act'} = 'info';

  get_cwafapi_json(\%req,\%resp);
          
  # if broken json - exit  
  return (0, $resp{'x-error-reason'}) unless( $resp{'status'} );
  return ($resp{'info'}{'current_version'});
  }
  
# send feedback to CWAF server
# send_feedback($feedback_hashref)
# RETURN:
# 0 - error, 1 - ok
#
sub send_feedback($) {
  my ($feedback) = @_;
  my (%resp,%action_descr);
  
  do_log("try to send feedback to CWAF server",10);
    
  # add  argument "act" to request
  $feedback->{'act'} = 'feedback';
  
  # add client version if absent
  $feedback->{'client_version'} = &get_client_version() unless $feedback->{'client_version'};
        
  get_cwafapi_json($feedback,\%resp);
          
  # if broken json - exit  
  return $resp{'status'};
  }

# check exists rules version entered in feedback panel  
# check_rules_version($version)
# RETURN:
# 0 - error, 1 - ok
#
sub check_rules_version($) {
  my ($version) = @_;
  my (%req,%resp);
  
  # if version not contains digits -> exit  
  return 0 unless( $version =~ /^([0-9\.]+)$/ );

  %req = ( 'act' => 'info', 'version' => $version );

  if( get_cwafapi_json(\%req,\%resp) ) {
    return 1 if( int($resp{'info'}{'version'}) == int($version) );  
    }

  return 0;        
  }

# &get_updater_version()
# return updater version
# RETURN: version or undef(if updater can't run)
sub get_updater_version{
  my $var = `$conf{'cwaf_path'}/$conf{'updater_bin'} --version` or undef;

  unless( defined($var) ) {
    do_log("ERROR: can't get updater version: $!",1);
    return undef;
    }

  chomp($var);
  return $var;
  }

# get version of client available
# get_available_version
# RETURN:
# 0 - error, if all ok - available client version
sub get_available_version() {
   my $version_url = $conf{'script_version'};
   my (%req, %resp, $file, $remote_file, $var, $version);
   
   unless(defined($version_url)) {
     do_log("ERROR: script_version is not defined in configuration", 0);
     return (0, 'internal error');
   }

   #%req =  ( 'url'  => $version_url );
   $remote_file = $conf{'cwaf_path'}.'/tmp/remote.dat';
   
   if($var = get_http_file($version_url, $remote_file)) {
  if(open($file, "< ${remote_file}")) {
      while ($version = <$file>) {
        chomp($version);
        $version =~ s/^\s+|\s+$//g;
        if(defined($version) && $version ne "") {
            last;
        }
      }
      close($file);
      unlink($remote_file);
    }
    
      #$version = $resp{'content'};

	   # check if version file downloaded
	   if (defined($version)) {
		 # return 0 if version not contains digits
                 return (0, $resp{'x-error-reason'}) unless( $version =~ /^([0-9\.]+)$/ );

		 return $version;
	   }
   }
  return (0, $resp{'x-error-reason'});
}

# get version of local client
# get_client_version
# RETURN:
# 0 - error, if all ok - local client version
sub get_client_version() {
  my ($file, $retval, $ver_file);
  $ver_file = $conf{'cwaf_path'}."/etc/version.dat";

  if(open($file, "< ${ver_file}")) {
    while ($retval = <$file>) {
      chomp($retval);
      $retval =~ s/^\s+|\s+$//g;
      if(defined($retval) && $retval ne "") {
          last;
      }
    }
    close($file);

   if (defined($retval)) {
     # return 0 if version not contains digits
     return 0 unless( $retval =~ /^([0-9\.]+)$/ );
     return $retval;
   }

  }
  return 0;
}

# get installed web platform
# get_web_platform
# RETURN:
# installed web platform
sub get_web_platform() {
    if(defined($conf{'cwaf_platform'})) {
        return 'Apache' if &is_apache();
        return 'LiteSpeed' if &is_litespeed();
        return 'Nginx' if &is_nginx();
        return 'Unknown web platform: '.$conf{'cwaf_platform'};
    } else {
        return 'Apache';
    }
}

# get version of local rules
# get_rules_version
# RETURN:
# 0 - error, if all ok - rules version
sub get_local_rules_version() {
  my ($file, $var, $rv_file);
  $rv_file = $conf{'cwaf_path'}."/rules/rules.dat";

  if(open($file, "< ${rv_file}")) {
    $var = <$file>;
    chomp($var);
    close($file);
    return $var;
    }
      
  return 0;
  }

# &is_dir_empty($checkdir)
# check if $checkdir is empty
#
# RETURN: 0 or 1
sub is_dir_empty($) {
 my ($checkdir) = @_;
 if(opendir(DIR,$checkdir)){
    my @files = grep { !m/\A\.{1,2}\Z/} readdir(DIR);
    closedir(DIR);
    return @files ? 0 : 1;
 } else {
    do_log("Can't check directory $checkdir ($!)", 10);
    return 0;
 }
}

# &set_workdir($path_to_working_directory)
# set current working dir 
#
# RETURN: 1 - ok, 0 - error
sub set_workdir {
	my ($workdir) = @_;
	if( open(FILE,">".$conf{'cwaf_path'} . "/tmp/workdir.dat") ) {
		do_log("set work directory ($workdir)",10);
		print FILE "$workdir\n";
		close(FILE);
		return 1;
		}
	return 0;
	}

# &make_symlink($)
# make symlink "working directory" -> "project directory"
#
# RETURN: none
sub make_symlink($) {
	my ($work_dir) = @_;
	my ($symlink_result);
	my ($yaml_symlink, $rules_symlink);

	$yaml_symlink = $conf{'cwaf_path'} . '/etc/yml';
	$rules_symlink = $conf{'cwaf_path'}.'/'.$conf{'rules_dir'};
	
	# delete symlinks
	if( -l $yaml_symlink ) {
		unlink($yaml_symlink);
	} else {
		if( -f $yaml_symlink ) {
			rename $yaml_symlink, $yaml_symlink . '.backup';
			do_log("Found ancient version of YAML dir. Renaming to $yaml_symlink.backup",10);
		}
	}
	if( -l $rules_symlink ) {
		unlink($rules_symlink); 
	} else {
		if( -f $rules_symlink ) {
			rename $rules_symlink, $rules_symlink . '.backup';
			do_log("Found ancient version of rules dir. Renaming to $rules_symlink.backup",10);
		}
	}

	# try to create symlink to YAML configuration directory
	$symlink_result = symlink("$work_dir/yml", $yaml_symlink);
	unless($symlink_result) {
		do_log("can't create symlink to yml $work_dir/yml($!)",10);
		return 0;       
		}

	# try to create symlink to rules directory
	$symlink_result = symlink("$work_dir/rules", $rules_symlink);
	unless($symlink_result) { 
					do_log("can't create symlink to rules $work_dir/rules($!)",10);
					return 0;
					}

	return 1;
}

# &fix_permissions()
# fix permissions for all webpanels excluding cPanel
#
# RETURN: none
sub fix_permissions() {
# this is cPanel, exiting
return if(&is_cpanel());

if(&is_plesk()) {
  system("chown -R psaadm $conf{'cwaf_path'}");
  system("chown -R psaadm $conf{'log_dir'}");
}

if(&is_directadmin()) {
  system("chown -R cwaf_plugin:cwaf_plugin $conf{'cwaf_path'}");
  system("chown -R cwaf_plugin:cwaf_plugin $conf{'log_dir'}");
}

# satandalone GUI
if(&is_standalone_gui()) {
  system("chown -R $conf{'web_user'} $conf{'cwaf_path'}");
  system("chown -R $conf{'web_user'} $conf{'log_dir'}");
}

#system("chown -R user $conf{'cwaf_path'}") if(&is_webmin());
&fix_wrapper();
}

sub fix_wrapper() {
return if(&is_cpanel() || &is_webmin());
return unless (-f "$conf{'cwaf_path'}/scripts/suid");
system("chown -R root $conf{'cwaf_path'}/scripts/suid");
system("chmod 4111 $conf{'cwaf_path'}/scripts/suid");
}

###  Checker Functions For Hosting Platform/ Web Server  ###
# return cpanel hosting platform version string or 0.0
sub get_cpanel_version() {
  my $ver = run_shellcmd('/usr/local/cpanel/cpanel -V | sed -r \'s=^([0-9.]+)(.*)=\1=g\'');
  $ver =~ /^\d+\.\d+$/ ? return $ver : return '0.0';
}

# return plesk hosting platform version string or 0.0
sub get_plesk_version() {
  my $ver = run_shellcmd('/usr/sbin/plesk version | grep "Product version" | grep -oE "[0-9\.]{4}"');
  $ver =~ /^\d+\.\d+$/ ? return $ver : return '0.0';
}

# return webmin hosting platform version string or 0.0
sub get_webmin_version() {
  my $ver = run_shellcmd('cat /etc/webmin/version');
  $ver =~ /^\d+\.\d+$/ ? return $ver : return '0.0';
}

# return directadmin hosting platform version string or 0.0
sub get_directadmin_version() {
  my $ver = call_wrapper_out('directadmin_version');
  
  if ($ver =~ m/^v\.(.+)\.\d$/) {
    $ver = "$1";
  }
  $ver =~ /^\d+\.\d+$/ ? return $ver : return '0.0';
}

# return hosting platform version string or Unknown
sub get_wh_panel_version() {
  return 'cPanel/'.&get_cpanel_version() if &is_cpanel();
  return 'Plesk/'.&get_plesk_version() if &is_plesk();
  return 'Webmin/'.&get_webmin_version() if &is_webmin();
  return 'DirectAdmin/'.&get_directadmin_version() if &is_directadmin();
  return 'Standalone';
}

sub get_apache_version {
  my $apachectl_bin = &get_apachectl();
  return '0.0.0' unless ($apachectl_bin);
  my $ver = run_shellcmd("$apachectl_bin -v 2>&1 | grep '^Server version: Apache' | sed -r 's=^(Server version.+)(/)([0-9.]+)(.*)=\\3=g'");
  $ver =~ /^\d+\.\d+\.\d+$/ ? return $ver : return '0.0.0';
}

sub get_litespeed_version {
  my $ver = '0.0.0';
  my $lsver = call_wrapper_out('litespeed_version');
  if ($lsver =~ m/^LiteSpeed\/(.+)/) {
     $ver = "$1";
  }
  $ver =~ /^\d+\.\d+(.*)$/ ? return $ver : return '0.0.0';
}

sub get_nginx_version {
  my $nginx_bin = &get_nginx_binary();
  return '0.0.0' unless $nginx_bin;
  my $ver = run_shellcmd($nginx_bin.' -v 2>&1 | grep -F "nginx version" |'." sed -r -e 's/^nginx version: nginx\\/(.*)/\\1/'");
  $ver =~ /^\d+\.\d+\.\d+(.*)$/ ? return $ver : return '0.0.0';
}

# return web server version string or Unknown
sub get_webserver_version() {
  return 'Apache/'.&get_apache_version() if &is_apache();
  return 'LiteSpeed/'.&get_litespeed_version() if &is_litespeed();
  return 'Nginx/'.&get_nginx_version() if &is_nginx();
  return 'Unknown';
}

# return User-Agent string
sub get_useragent() {
  my $ua = 'CWAF_Client/'.get_client_version().' ('.get_webserver_version().'; '.get_wh_panel_version().') Rules/'.get_local_rules_version();
  do_log("User-Agent: $ua", 11);
  return $ua;
}


1;