|
|
@@ -0,0 +1,879 @@ |
|
|
#!/usr/bin/perl |
|
|
use strict; |
|
|
use warnings; |
|
|
|
|
|
my $version = "2.0.54"; |
|
|
our $maxGCE = 7; |
|
|
our $OPT_TIMEOUT; |
|
|
our $trCheck = 1; |
|
|
our $verify_license_data; |
|
|
our $L3 = 0; |
|
|
our $CS = 0; |
|
|
use Readonly; |
|
|
Readonly my $LICENSE_LOG => '/var/log/plesk/license_log'; |
|
|
|
|
|
use Exporter; |
|
|
our @EXPORT_OK; |
|
|
use Socket; |
|
|
use IO::Socket::INET; |
|
|
use IO::Socket::SSL; |
|
|
use Sys::Hostname; |
|
|
use Getopt::Long; |
|
|
use Term::ANSIColor qw(:constants); |
|
|
use JSON::MaybeXS qw(encode_json decode_json); |
|
|
use NetAddr::IP; |
|
|
use Text::CSV; |
|
|
use Data::Dump::Streamer; |
|
|
use DateTime; |
|
|
use Time::Piece; |
|
|
use Time::Seconds; |
|
|
use List::Util 'first'; |
|
|
$Term::ANSIColor::AUTORESET = 1; |
|
|
local $| = 1; |
|
|
|
|
|
my ( $distro, $distro_version, $distro_major, $distro_minor ); |
|
|
|
|
|
# Placeholder for Plesk-specific OS detection (modify accordingly) |
|
|
$distro = `lsb_release -si`; |
|
|
$distro_version = `lsb_release -sr`; |
|
|
($distro_major, $distro_minor) = split(/\./, $distro_version); |
|
|
|
|
|
_init_run_state(); |
|
|
|
|
|
if ( exists $ENV{'PACHA_AUTOFIXER'} ) { |
|
|
_set_run_type('plesk'); |
|
|
} |
|
|
elsif ( defined $ENV{'HISTFILE'} and index( $ENV{'HISTFILE'}, 'plesk_ticket' ) != -1 ) { |
|
|
_set_run_type('plesk'); |
|
|
} |
|
|
else { |
|
|
foreach ( @ENV{ 'SSH_CLIENT', 'SSH_CONNECTION' } ) { |
|
|
next unless defined $_; |
|
|
|
|
|
next unless m{\A (184\.94\.197\.[2-6]|208\.74\.123\.98)}xms; |
|
|
_set_run_type('plesk'); |
|
|
last; |
|
|
} |
|
|
} |
|
|
|
|
|
my ( $skipdate, $withlogs, $verifypage, $verbose, $help ); |
|
|
our @WARNINGS; |
|
|
our @HISTORY; |
|
|
our $histCnt; |
|
|
our $external_ip_address; |
|
|
our %license; |
|
|
our $buffer; |
|
|
our $HOSTNAME; |
|
|
our $RUN_STATE; |
|
|
our $Trial = 0; |
|
|
our $host = 'verify.plesk.com'; |
|
|
our $helper_url = "https://" . $host . "/app/verify?ip="; |
|
|
our $cgls = 0; |
|
|
our $timenow = time(); |
|
|
our $isGCE_IP; |
|
|
our $isAWS_IP; |
|
|
|
|
|
get_external_ip(); |
|
|
|
|
|
GetOptions( |
|
|
"skipdate" => \$skipdate, |
|
|
"verbose" => \$verbose, |
|
|
"verifypage" => \$verifypage, |
|
|
"withlogs" => \$withlogs, |
|
|
"help" => \$help, |
|
|
); |
|
|
|
|
|
our $pleskLicenseFile = '/etc/sw/keys/keys/keyXX.xml'; # Placeholder path, adjust accordingly |
|
|
our $DEVinConf; |
|
|
our $envtype; |
|
|
|
|
|
print MAGENTA "Plesk License Troubleshooter - Version: $version\n"; |
|
|
module_sanity_check(); |
|
|
if ($withlogs) { |
|
|
read_last_50_lines_of_license_log(); |
|
|
exit; |
|
|
} |
|
|
Usage() if ($help); |
|
|
|
|
|
check_for_centOS5(); |
|
|
print BOLD MAGENTA "--help show usage information\n"; |
|
|
print BOLD MAGENTA "--verbose show everything [Default: show warnings only].\n\n"; |
|
|
get_license_data($external_ip_address); |
|
|
my ( $package, $partner, $isSolo, $AllowedCnt ) = verify_license($external_ip_address); |
|
|
|
|
|
my $ExpiredLicense = ""; |
|
|
my $LicStatus = 0; |
|
|
my $valid_products = ""; |
|
|
if ( !$package && !$partner && !$isSolo && !$AllowedCnt ) { |
|
|
$ExpiredLicense = " [ EXPIRED? ] or [ INACTIVE ]"; |
|
|
$CS = 1; |
|
|
} |
|
|
else { |
|
|
$LicStatus = 1; |
|
|
$valid_products = check_valid_products($external_ip_address); |
|
|
} |
|
|
if ($valid_products) { |
|
|
$LicStatus = 1; |
|
|
} |
|
|
|
|
|
loadHistory(); |
|
|
my $validFQDN = is_hostname_fqdn(); |
|
|
print_working( "Servers External IP Address Detected As: " . CYAN $external_ip_address ); |
|
|
my $LicStatusMsg = ($LicStatus) ? "Active" : "Inactive/Expired"; |
|
|
if ($LicStatus) { |
|
|
print_working( "License Status: " . CYAN $LicStatusMsg . GREEN " [ " . $helper_url . $external_ip_address . " ]" ); |
|
|
print_working( "License Valid For: " . CYAN $valid_products ) |
|
|
unless ( !$valid_products ); |
|
|
} |
|
|
else { |
|
|
print_working( "License Status: " . CYAN $LicStatusMsg . RED " License not found on verify.plesk.com, or expired or request timed out." . GREEN "\n\t\\_ Check manually at " . $helper_url . $external_ip_address ); |
|
|
print_working( "License Valid For: " . RED "COULD NOT BE DETERMINED! $ExpiredLicense" ); |
|
|
push( @WARNINGS, "Could not verify license status via $helper_url" . $external_ip_address ); |
|
|
push( |
|
|
@WARNINGS, |
|
|
"Could not determine the services/products this license covers $ExpiredLicense" |
|
|
); |
|
|
} |
|
|
|
|
|
if ( defined $package && $package =~ m/test/i ) { $Trial = 1; } |
|
|
print_working( "Company/Partner: " . CYAN $partner ) unless ( !$package ); |
|
|
print_working( "Package: " . CYAN $package ) unless ( !$package ); |
|
|
my $UserCnt = usercount(); |
|
|
print_working( "Total Users: " . CYAN $UserCnt ); |
|
|
my $ActiveProfileNode = "UNKNOWN"; |
|
|
if ( -e $pleskLicenseFile ) { |
|
|
$ActiveProfileNode = getProfileNode() // "UNKNOWN"; |
|
|
} |
|
|
|
|
|
$AllowedCnt = 30 if ($Trial); |
|
|
my $totAllowedMsg = "Unknown"; |
|
|
if ( $AllowedCnt == 0 ) { |
|
|
$totAllowedMsg = "Unlimited"; |
|
|
} |
|
|
else { |
|
|
$totAllowedMsg = $AllowedCnt; |
|
|
} |
|
|
print_working( "Allowed Users: " . CYAN $totAllowedMsg ); |
|
|
if ( $UserCnt > $AllowedCnt ) { |
|
|
print_warn("\t \\_ User Count Exceeded - Please contact Customer Service!") unless ( $totAllowedMsg eq "Unlimited" || $AllowedCnt == 0 ); |
|
|
} |
|
|
check_for_solo(); |
|
|
check_for_development(); |
|
|
check_for_onetime(); |
|
|
print_working( "Active Profile Node " . CYAN $ActiveProfileNode ) unless ( $ActiveProfileNode eq "" ); |
|
|
check_for_GCE(); |
|
|
check_for_AWS(); |
|
|
chk_for_cloud_ready(); |
|
|
print_working( "Hostname is FQDN: " . $validFQDN . CYAN " [$HOSTNAME]" ); |
|
|
check_for_accountinglog(); |
|
|
get_envtype(); |
|
|
|
|
|
# END OF INFORMATIONAL! - Start checks and only display if verbose or warning/errors are found. |
|
|
|
|
|
check_for_lisc_lock(); |
|
|
check_for_trial(); |
|
|
check_hostsfile(); |
|
|
get_mainip(); |
|
|
get_ipinfo($external_ip_address); |
|
|
get_logStats(); |
|
|
check_for_multiple_defroute(); |
|
|
get_devices(); |
|
|
run_check_valid_server_hostname(); |
|
|
check_kernel_hostname(); |
|
|
display_etc_hostname(); |
|
|
get_network_hostname(); |
|
|
get_ip_of_hostname(); |
|
|
check_file_for_odd_chars("/etc/hosts"); |
|
|
check_file_for_odd_chars("/etc/sysconfig/network"); |
|
|
check_if_hostname_resolves_locally(); |
|
|
check_resolvconf(); |
|
|
check_for_cloudcfg(); |
|
|
check_for_dhclient_exit_hook(); |
|
|
check_for_license_error(); |
|
|
get_hostname_at_install(); |
|
|
check_for_hostname_changes(); |
|
|
check_for_cpkeyclt_from_cli(); |
|
|
display_license_log_last_20(); |
|
|
get_date(); |
|
|
check_crons(); |
|
|
check_for_cpnat(); |
|
|
check_routing(); |
|
|
check_root_servers(); |
|
|
check_auth_cpanel_resolution(); |
|
|
check_firewall(); |
|
|
check_other_ports(); |
|
|
shenanigans(); |
|
|
check_for_license_status_json(); |
|
|
check_plesk_license_file(); |
|
|
run_rdate(); |
|
|
chkCreds(); |
|
|
display_route(); |
|
|
get_cpsrvd_restarts(); |
|
|
get_last_reboots(); |
|
|
|
|
|
my $warncnt = @WARNINGS; |
|
|
if ( $warncnt > 0 ) { |
|
|
if ($L3) { |
|
|
push @WARNINGS, RED "Send to L3" unless ( !iam('plesk') ); |
|
|
} |
|
|
if ($CS) { |
|
|
my $CSA = CSA(); |
|
|
if ($CSA) { |
|
|
|
|
|
# CS is on shift and nothing suspicious was found. |
|
|
push @WARNINGS, RED "Send to Customer Service" unless ($L3); |
|
|
} |
|
|
else { |
|
|
# CS is not on shift |
|
|
push @WARNINGS, RED "Send to L3" unless ( !iam('plesk') ); |
|
|
} |
|
|
} |
|
|
print "\n"; |
|
|
print YELLOW "Found the following (possibly related) issues:\n"; |
|
|
foreach my $warnmess (@WARNINGS) { |
|
|
chomp($warnmess); |
|
|
print RED "\t\\_ $warnmess\n"; |
|
|
} |
|
|
} |
|
|
exit; |
|
|
|
|
|
sub check_for_centOS5 { |
|
|
my $sysinfo_config = '/etc/os-release'; |
|
|
return if !-f $sysinfo_config; |
|
|
my $rpm_dist_ver; |
|
|
open my $fh, '<', $sysinfo_config or return; |
|
|
while (<$fh>) { |
|
|
if (/^VERSION_ID="(\d+)"/) { |
|
|
$rpm_dist_ver = $1; |
|
|
last; |
|
|
} |
|
|
} |
|
|
close $fh or return; |
|
|
return if !$rpm_dist_ver; |
|
|
return |
|
|
|
|
|
if ( $rpm_dist_ver > 5 ); |
|
|
print_warn("Sorry, this cannot run on your version of OS!"); |
|
|
exit; ## no critic (NoExitsFromSubroutines) |
|
|
} |
|
|
|
|
|
sub module_sanity_check { |
|
|
my @required_mods = qw( IO::Socket::PortState IO::Interface::Simple ); |
|
|
if ($verbose) { |
|
|
print_working("\nChecking if required Perl Modules are installed:"); |
|
|
} |
|
|
local $@; |
|
|
foreach my $reqmod (@required_mods) { |
|
|
eval("use $reqmod"); ## no critic (ProhibitStringyEval) |
|
|
if ($@) { |
|
|
print_warn( "\t \\_ " . $reqmod . " No - Installing!" ) |
|
|
unless ( !$verbose ); |
|
|
|
|
|
my $modinstall = `cpan -i $reqmod`; |
|
|
} |
|
|
else { |
|
|
print_OK( "\t \\_ " . $reqmod . " OK!\n" ) unless ( !$verbose ); |
|
|
} |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_external_ip { |
|
|
$external_ip_address = `curl -s https://api.ipify.org`; |
|
|
chomp($external_ip_address); |
|
|
if ( $external_ip_address eq "" ) { |
|
|
print RED "Failed to retrieve the external IP address from https://api.ipify.org\n"; |
|
|
print CYAN "Try running again or check manually for a firewall!\n"; |
|
|
exit; ## no critic (Cpanel::NoExitsFromSubroutines) |
|
|
} |
|
|
} |
|
|
|
|
|
sub print_working { |
|
|
my $text = shift; |
|
|
print BOLD YELLOW ON_BLACK . $text . "\n"; |
|
|
} |
|
|
|
|
|
sub print_warn { |
|
|
my $text = shift; |
|
|
print BOLD RED ON_BLACK . $text . "\n"; |
|
|
} |
|
|
|
|
|
sub print_OK { |
|
|
my $text = shift; |
|
|
print BOLD GREEN ON_BLACK . $text; |
|
|
} |
|
|
|
|
|
sub system_formatted { |
|
|
my $command = shift; |
|
|
open( my $cmd, "-|", "$command" ); |
|
|
while (<$cmd>) { |
|
|
print_formatted("$_"); |
|
|
} |
|
|
close $cmd; |
|
|
} |
|
|
|
|
|
sub print_formatted { |
|
|
my @input2 = shift; |
|
|
my @input = split /\n/, @input2; |
|
|
foreach (@input2) { print CYAN " $_"; } |
|
|
} |
|
|
|
|
|
sub verify_license { |
|
|
my $tcIPAddress = shift; |
|
|
my ( $tcPackage, $tcPartner, $tlisSolo ); |
|
|
for my $licenseline ( @{ $verify_license_data->{current} } ) { |
|
|
#print "DEBUG: $licenseline->{producttype} - $licenseline->{package}\n"; |
|
|
next unless ( $licenseline->{producttype} == 1 || $licenseline->{producttype} == 1048576 ); |
|
|
$tcPackage = $licenseline->{package}; |
|
|
$tcPartner = $licenseline->{company}; |
|
|
} |
|
|
my $lnAllowedCnt = 0; |
|
|
if ( $tcPackage && $tcPartner ) { |
|
|
if ( $tcPackage =~ m/solo/i ) { $lnAllowedCnt = 1; $tlisSolo = 1; } |
|
|
if ( $tcPackage =~ m/admin/i ) { $lnAllowedCnt = 5; } |
|
|
if ( $tcPackage =~ m/pro/i ) { $lnAllowedCnt = 30; } |
|
|
if ( $tcPackage =~ m/plus/i ) { $lnAllowedCnt = 50; } |
|
|
|
|
|
# SEE TECH-1298 |
|
|
#if ( $tcPackage =~ m/premier/i ) { $lnAllowedCnt = 100; } |
|
|
if ( $tcPackage =~ m/premier/i ) { $lnAllowedCnt = 0; } |
|
|
if ( $tcPackage =~ m/premier/i && $tcPackage =~ m/(\d+)/ ) { $lnAllowedCnt = $1; } |
|
|
if ( $tcPackage =~ m/autoscale/i ) { $lnAllowedCnt = 0; } |
|
|
} |
|
|
return $tcPackage, $tcPartner, $tlisSolo, $lnAllowedCnt; |
|
|
} |
|
|
|
|
|
sub check_valid_products { |
|
|
my $tcIPAddress = shift; |
|
|
my $valid = "[ "; |
|
|
for my $licenseline ( @{ $verify_license_data->{current} } ) { |
|
|
$valid .= $licenseline->{product} . " "; |
|
|
} |
|
|
$valid .= "]"; |
|
|
return $valid; |
|
|
} |
|
|
|
|
|
sub get_license_data { |
|
|
my $tcIPAddress = shift; |
|
|
my $e; { |
|
|
local $@; |
|
|
eval(get_licenses($tcIPAddress)); |
|
|
$@ =~ /Failure to reach Verify service/ and $e = $@; |
|
|
if ( defined $e ) { |
|
|
$L3 = 1; |
|
|
push( @WARNINGS, "Found evidence related to CPANEL-22182 Could not verify license!" ) unless ( !iam('plesk') ); |
|
|
} |
|
|
} |
|
|
$verify_license_data = get_licenses($tcIPAddress); |
|
|
} |
|
|
|
|
|
sub get_licenses { |
|
|
my $ip = shift; |
|
|
# Placeholder for Plesk license verification |
|
|
my $response = `curl -s https://verify.plesk.com/license?ip=$ip`; |
|
|
return decode_json($response); |
|
|
} |
|
|
|
|
|
sub is_hostname_fqdn { |
|
|
$HOSTNAME = get_hostname(); |
|
|
|
|
|
chomp($HOSTNAME); |
|
|
if ( $HOSTNAME !~ /([\w-]+)\.([\w-]+)\.(\w+)/ ) { |
|
|
push( @WARNINGS, "Hostname [ $HOSTNAME ] may not be a valid FQDN\n\t\t\\_ SEE: https://en.wikipedia.org/wiki/Fully_qualified_domain_name" ); |
|
|
return BOLD RED "No"; |
|
|
} |
|
|
else { |
|
|
return GREEN "Yes"; |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_for_lisc_lock { |
|
|
return if ( !( -e ("/var/lock/subsys/plesk.key.lock") ) ); |
|
|
push( @WARNINGS, "The /var/lock/subsys/plesk.key.lock file present!" ); |
|
|
} |
|
|
|
|
|
sub check_for_trial { |
|
|
return unless ( -e ("/var/lock/subsys/plesk.trial") ); |
|
|
if ($Trial) { |
|
|
print YELLOW "[INFO] - Trial touchfile detected - If you just purchased a license a hard restart of plesk may be required before key update will work!\n"; |
|
|
} |
|
|
else { |
|
|
push( |
|
|
@WARNINGS, |
|
|
"The /var/lock/subsys/plesk.trial touchfile found and license is not a trial license" |
|
|
) unless ($Trial); |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_hostsfile { |
|
|
print_working("Checking /etc/hosts for $HOSTNAME") unless ( !$verbose ); |
|
|
|
|
|
my $hostsfile = `grep "$HOSTNAME" /etc/hosts`; |
|
|
if ( substr( $hostsfile, 0, 1 ) eq "#" ) { |
|
|
push( |
|
|
@WARNINGS, |
|
|
"The $HOSTNAME appears to be commented out in the /etc/hosts file." |
|
|
); |
|
|
return; |
|
|
} |
|
|
if ($hostsfile) { |
|
|
if ($verbose) { |
|
|
print BOLD GREEN "\t\\_ $HOSTNAME was found in the /etc/hosts file\n"; |
|
|
print BOLD CYAN "\t\t\\_ $hostsfile"; |
|
|
} |
|
|
} |
|
|
else { |
|
|
push( |
|
|
@WARNINGS, |
|
|
"The $HOSTNAME was not found in the /etc/hosts file." |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_for_accountinglog { |
|
|
print_working("Checking accounting.log file for first created account"); |
|
|
if ( -e ("/var/log/plesk/accounting.log") ) { |
|
|
my $FirstAcct = `grep ":CREATE:" /var/log/plesk/accounting.log`; |
|
|
my $FirstAcctEnd = index( $FirstAcct, ":CREATE:", 0 ); |
|
|
my $FirstAcctDate = substr( $FirstAcct, 0, $FirstAcctEnd - 0 ); |
|
|
print BOLD CYAN "\t\\_ First account created on: " . YELLOW $FirstAcctDate . "\n"; |
|
|
} |
|
|
else { |
|
|
print BOLD CYAN "\t\\_ None - Possible new install\n"; |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_envtype { |
|
|
$envtype = `virt-what`; |
|
|
chomp($envtype); |
|
|
if ( !$envtype ) { |
|
|
$envtype = "Unknown"; |
|
|
push( @WARNINGS, "Unknown envtype for this server" ); |
|
|
} |
|
|
print_working( "This server's environment (envtype) is: " . CYAN $envtype); |
|
|
} |
|
|
|
|
|
sub get_mainip { |
|
|
if ( !-s "/var/psa/pleskmainip" ) { |
|
|
print RED "[WARN] /var/psa/pleskmainip file is empty.\n" unless ( !$verbose ); |
|
|
print YELLOW "\t \\_ Might be fixed by running /usr/local/psa/bin/ipmanage\n" unless ( !$verbose ); |
|
|
push( @WARNINGS, "/var/psa/pleskmainip file is empty.\n\t\t \\_ Might be fixed by running /usr/local/psa/bin/ipmanage" ); |
|
|
return; |
|
|
} |
|
|
print_working("Obtaining contents of /var/psa/pleskmainip:") unless ( !$verbose ); |
|
|
open my $fh, '<', '/var/psa/pleskmainip'; |
|
|
my $mainip; |
|
|
while (<$fh>) { |
|
|
$mainip = $_; |
|
|
} |
|
|
close($fh); |
|
|
chomp($mainip); |
|
|
if ($mainip) { |
|
|
print_OK( "\t\\_ " . $mainip . "\n" ) unless ( !$verbose ); |
|
|
#my $isOnServer = qx[ ip |
|
|
|
|
|
addr show | grep $mainip ]; ## no critic (Cpanel::ProhibitQxAndBackticks) |
|
|
my $ipaddrshow = `ip addr show`; |
|
|
my $isOnServer = ( grep { /$mainip/ } $ipaddrshow ); |
|
|
if ( !($isOnServer) ) { |
|
|
print RED "[WARN] /var/psa/pleskmainip [$mainip] is not bound to this server.\n"; |
|
|
print YELLOW "\t \\_ Might be fixed by running /usr/local/psa/bin/ipmanage\n"; |
|
|
push( |
|
|
@WARNINGS, |
|
|
"/var/psa/pleskmainip [$mainip] not bound to this server.\n\t\t \\_ Might be fixed by running /usr/local/psa/bin/ipmanage" |
|
|
); |
|
|
} |
|
|
} |
|
|
else { |
|
|
push( |
|
|
@WARNINGS, |
|
|
"The mainip in /var/psa/pleskmainip [$mainip] seems to be missing" |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_ipinfo { |
|
|
return unless ($verbose); |
|
|
my $ipinfoIP = shift; |
|
|
print_working("Getting ipinfo for $ipinfoIP"); |
|
|
my $ipinfo = `curl -s ip-api.com/$ipinfoIP`; |
|
|
my @IPINFO = split /\n/, $ipinfo; |
|
|
foreach my $ipinfoline (@IPINFO) { |
|
|
chomp($ipinfoline); |
|
|
next if ( $ipinfoline =~ m/status|continentCode|countryCode|region|district|currency|mobile|proxy|query|asname|hosting|offset|{|}/ ); |
|
|
$ipinfoline =~ s/,$//g; |
|
|
$ipinfoline =~ s/\"as\"/AS-Name /; |
|
|
$ipinfoline =~ s/\"org\"/Organization/; |
|
|
$ipinfoline =~ s/\"isp\"/ISP /; |
|
|
$ipinfoline =~ s/\"timezone\"/TimeZone /; |
|
|
$ipinfoline =~ s/\"lat\"/Latitude /; |
|
|
$ipinfoline =~ s/\"lon\"/Longitude /; |
|
|
$ipinfoline =~ s/\"zip\"/Postal /; |
|
|
$ipinfoline =~ s/\"city\"/City /; |
|
|
$ipinfoline =~ s/\"country\"/Country /; |
|
|
$ipinfoline =~ s/\"continent\"/Continent /; |
|
|
$ipinfoline =~ s/\"//g; |
|
|
print BOLD CYAN "\t\\_$ipinfoline\n"; |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_wwwacctconf_ip { |
|
|
my $conf = loadwwwacctconf(); ## no critic (ProhibitCallsToUnexportedSubs) |
|
|
$DEVinConf = $conf->{'ETHDEV'}; |
|
|
my $wwwacctIP = $conf->{'ADDR'}; |
|
|
return unless ($verbose); |
|
|
print_working( "Obtaining ADDR from /etc/wwwacct.conf file: " . CYAN $wwwacctIP); |
|
|
} |
|
|
|
|
|
sub get_logStats { |
|
|
if ( !-e $LICENSE_LOG ) { |
|
|
push @WARNINGS, "license_log file is missing" unless ( !iam('plesk') ); |
|
|
$L3 = 1; |
|
|
} |
|
|
else { |
|
|
return unless ($verbose); |
|
|
my $license_log_file = $LICENSE_LOG; |
|
|
my $rootOwnedCnt = `grep -c 'confirm this connection was from a root owned process' $license_log_file | grep -v 'The exact message was: ' `; |
|
|
my $expireCnt = `grep -c "^The license is expired" $license_log_file`; |
|
|
my $activeCnt = `grep -c "^The license has been activated too many times" $license_log_file`; |
|
|
my $failureCnt = `grep -c "License update failed" $license_log_file`; |
|
|
my $successCnt = `grep -c "License update succeeded" $license_log_file`; |
|
|
my $trialCnt = `grep -c "Already Used Plesk Trial License for this IP" $license_log_file`; |
|
|
my $noValLic = `grep -c "^No valid Plesk license found" $license_log_file`; |
|
|
my $noPaidLic = `grep -c "^No Paid License for this server" $license_log_file`; |
|
|
my $TotTWRestarts = `grep -c "Restarting psa" /var/log/chkservd.log`; |
|
|
chomp($rootOwnedCnt); |
|
|
chomp($expireCnt); |
|
|
chomp($activeCnt); |
|
|
chomp($failureCnt); |
|
|
chomp($successCnt); |
|
|
chomp($trialCnt); |
|
|
chomp($noValLic); |
|
|
chomp($noPaidLic); |
|
|
chomp($TotTWRestarts); |
|
|
print_working("Obtaining stats from $license_log_file file:"); |
|
|
print BOLD CYAN "\t\\_ Total number of times the following occurs in the $license_log_file file:\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "The license has been activated too many times " . MAGENTA $activeCnt . "\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "License update failed " . MAGENTA $failureCnt . "\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "License update succeeded " . MAGENTA $successCnt . "\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "Already Used Plesk Trial License for this IP " . MAGENTA $trialCnt . "\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "Can't confirm this connection was from a root owned process " . MAGENTA $rootOwnedCnt . "\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "No valid Plesk license found " . MAGENTA $noValLic . "\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "No Paid License for this server " . MAGENTA $noPaidLic . "\n"; |
|
|
print BOLD CYAN "\t\t\\_ " . YELLOW "chkservd has restarted psa: " . MAGENTA $TotTWRestarts . "\n"; |
|
|
} |
|
|
return; |
|
|
} |
|
|
|
|
|
sub get_devices { |
|
|
my ( $device, $nicIP ); |
|
|
print_working("Obtaining NIC Devices:") unless ( !$verbose ); |
|
|
my $devices = `ip -o link show`; |
|
|
my @DEVICES = split /\n/, $devices; |
|
|
my $inConf = 0; |
|
|
foreach my $deviceline (@DEVICES) { |
|
|
chomp($deviceline); |
|
|
next if ( $deviceline =~ /DOWN/ ); |
|
|
($device) = ( split( /\s+/, $deviceline ) )[1]; |
|
|
chop($device); ## Remove trailing colon |
|
|
if ( $device eq "lo" ) { next; } |
|
|
if ( $device eq "venet0" ) { $device = "venet0:0"; } |
|
|
if ( $device =~ m/\@/ ) { |
|
|
($device) = ( split( /\@/, $device ) )[0]; |
|
|
} |
|
|
my $if = IO::Interface::Simple->new($device); |
|
|
$nicIP = $if->address; |
|
|
print BOLD MAGENTA ON_BLACK . "\t \\_ Ethernet Device Name: " . CYAN $device . "\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ Address: " . CYAN $nicIP . "\n" unless ( !$verbose ); |
|
|
my $MACVendor = getMAC( $if->hwaddr ); |
|
|
print BOLD YELLOW . "\t\t \\_ MAC: " . CYAN $if->hwaddr . " [" . $MACVendor . "]\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ Broadcast: " . CYAN $if->broadcast . "\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ Netmask: " . CYAN $if->netmask . "\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ MTU: " . CYAN $if->mtu . "\n" unless ( !$verbose ); |
|
|
arping_check( $device, $nicIP ); |
|
|
|
|
|
if ( $device eq "eth0" and $nicIP eq "" ) { |
|
|
push( @WARNINGS, "Device eth0 has no address - Seeing Waiting for devices to settle errors in license_log?"); |
|
|
} |
|
|
if ( $device eq $DEVinConf ) { |
|
|
$inConf = 1; |
|
|
} |
|
|
} |
|
|
if ( $inConf = 0 || $DEVinConf eq "" ) { |
|
|
push( @WARNINGS, RED "ETHDEV in /etc/wwwacct.conf (" . WHITE $DEVinConf . RED ") missing or undefined (blank) as an active device!\n\t\t \\_ Should be set to " . CYAN . $device) unless ( $DEVinConf eq $device ); |
|
|
} |
|
|
my $ETHDEVcnt = `grep -c 'ETHDEV' /etc/wwwacct.conf`; |
|
|
if ( $ETHDEVcnt > 1 ) { |
|
|
push( @WARNINGS, RED "Multiple ETHDEV lines found in /etc/wwwacct.conf!"); |
|
|
} |
|
|
} |
|
|
|
|
|
sub arping_check { |
|
|
my $nicdevice = shift; |
|
|
my $nicIPAddr = shift; |
|
|
return unless( -e '/usr/sbin/arping' ); |
|
|
return if |
|
|
|
|
|
( -e '/usr/sbin/arping' and $nicdevice =~ /lo|venet/ ); |
|
|
my $arping; |
|
|
if ( $nicIPAddr ) { |
|
|
$arping = `/usr/sbin/arping -c 2 $nicIPAddr -I $nicdevice`; |
|
|
} |
|
|
my @arpingOut = split( /\n/, $arping ); |
|
|
foreach my $arpingLine (@arpingOut) { |
|
|
chomp $arpingLine; |
|
|
if ( $arpingLine =~ /bytes from/ and $arpingLine =~ /time=/ ) { |
|
|
print BOLD MAGENTA ON_BLACK "\t\\_ " . YELLOW "ARPing $nicdevice at $nicIPAddr successful!\n"; |
|
|
last; |
|
|
} |
|
|
if ( $arpingLine =~ /Unicast reply from/ ) { |
|
|
print BOLD MAGENTA ON_BLACK "\t\\_ " . YELLOW "ARPing $nicdevice at $nicIPAddr successful!\n"; |
|
|
last; |
|
|
} |
|
|
} |
|
|
print BOLD RED ON_BLACK "\t\\_ " . YELLOW "ARPing $nicdevice at $nicIPAddr failed!" if ( $arpingLine !~ /Unicast reply from/ and $arpingLine !~ /bytes from/ and $nicIPAddr ); |
|
|
} |
|
|
|
|
|
sub run_check_valid_server_hostname { |
|
|
my $errors = 0; |
|
|
my $ValidServerHostname = `host -t A $HOSTNAME | grep "has address"`; |
|
|
my @ValidServerHostname = split /\n/, $ValidServerHostname; |
|
|
if ($ValidServerHostname) { |
|
|
print_working("Running host -t A $HOSTNAME to validate Server FQDN resolves to an IP address:"); |
|
|
foreach my $validhostline (@ValidServerHostname) { |
|
|
chomp($validhostline); |
|
|
print BOLD GREEN "\t\\_ " . YELLOW "$validhostline\n" unless ( !$verbose ); |
|
|
$errors++; |
|
|
} |
|
|
if ( $errors == 0 ) { |
|
|
push( @WARNINGS, RED "The $HOSTNAME failed to resolve via host command!\n\t\t \\_ This server is expected to have a valid DNS record." ); |
|
|
} |
|
|
} |
|
|
else { |
|
|
push( @WARNINGS, RED "The $HOSTNAME failed to resolve via host command!\n\t\t \\_ This server is expected to have a valid DNS record." ); |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_devices { |
|
|
my ( $device, $nicIP ); |
|
|
print_working("Obtaining NIC Devices:") unless ( !$verbose ); |
|
|
my $devices = `ip -o link show`; |
|
|
my @DEVICES = split /\n/, $devices; |
|
|
my $inConf = 0; |
|
|
foreach my $deviceline (@DEVICES) { |
|
|
chomp($deviceline); |
|
|
next if ( $deviceline =~ /DOWN/ ); |
|
|
($device) = ( split( /\s+/, $deviceline ) )[1]; |
|
|
chop($device); ## Remove trailing colon |
|
|
if ( $device eq "lo" ) { next; } |
|
|
if ( $device eq "venet0" ) { $device = "venet0:0"; } |
|
|
if ( $device =~ m/\@/ ) { |
|
|
($device) = ( split( /\@/, $device ) )[0]; |
|
|
} |
|
|
my $if = IO::Interface::Simple->new($device); |
|
|
$nicIP = $if->address; |
|
|
print BOLD MAGENTA ON_BLACK . "\t \\_ Ethernet Device Name: " . CYAN $device . "\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ Address: " . CYAN $nicIP . "\n" unless ( !$verbose ); |
|
|
my $MACVendor = getMAC( $if->hwaddr ); |
|
|
print BOLD YELLOW . "\t\t \\_ MAC: " . CYAN $if->hwaddr . " [" . $MACVendor . "]\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ Broadcast: " . CYAN $if->broadcast . "\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ Netmask: " . CYAN $if->netmask . "\n" unless ( !$verbose ); |
|
|
print BOLD YELLOW . "\t\t \\_ MTU: " . CYAN $if->mtu . "\n" unless ( !$verbose ); |
|
|
arping_check( $device, $nicIP ); |
|
|
|
|
|
if ( $device eq "eth0" and $nicIP eq "" ) { |
|
|
push( @WARNINGS, "Device eth0 has no address - Seeing Waiting for devices to settle errors in license_log?"); |
|
|
} |
|
|
if ( $device eq $DEVinConf ) { |
|
|
$inConf = 1; |
|
|
} |
|
|
} |
|
|
if ( $inConf = 0 || $DEVinConf eq "" ) { |
|
|
push( @WARNINGS, RED "ETHDEV in /etc/wwwacct.conf (" . WHITE $DEVinConf . RED ") missing or undefined (blank) as an active device!\n\t\t \\_ Should be set to " . CYAN . $device) unless ( $DEVinConf eq $device ); |
|
|
} |
|
|
my $ETHDEVcnt = `grep -c 'ETHDEV' /etc/wwwacct.conf`; |
|
|
if ( $ETHDEVcnt > 1 ) { |
|
|
push( @WARNINGS, RED "Multiple ETHDEV lines found in /etc/wwwacct.conf!"); |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_other_ports { |
|
|
my @ports = qw( 465 587 10000 2096 2078 2077 2083 2087 2089 ); |
|
|
my $isFirewallBlocking; |
|
|
print_working("Checking for blocked mail ports:") unless ( !$verbose ); |
|
|
foreach my $port (@ports) { |
|
|
my $portscan = `iptables -vnL INPUT --line-numbers | grep "dpt:$port"`; |
|
|
my @Portscan = split /\n/, $portscan; |
|
|
foreach my $scanline (@Portscan) { |
|
|
chomp($scanline); |
|
|
if ( index( $scanline, "REJECT" ) != -1 || index( $scanline, "DROP" ) != -1 ) { |
|
|
print BOLD RED "\t\\_ Port $port may be blocked - $scanline\n"; |
|
|
$isFirewallBlocking = 1; |
|
|
next; |
|
|
} |
|
|
} |
|
|
} |
|
|
if ( !defined $isFirewallBlocking ) { |
|
|
print BOLD GREEN "\t\\_ No mail ports appear to be blocked via iptables\n" unless ( !$verbose ); |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_for_cpkeyclt_from_cli { |
|
|
print_working("Checking if /usr/local/psa/bin/plesk_key --update is in the last 50 commands run:"); |
|
|
my $grep50hist = `tail -50 \$(ls -t /root/.bash_history*) | grep "/usr/local/psa/bin/plesk_key --update"`; |
|
|
if ($grep50hist) { |
|
|
print_warn("\t\\_ Detected Plesk key update attempt via cli - Proceed with caution!"); |
|
|
} |
|
|
else { |
|
|
print OK "\t\\_ No evidence of key update attempts via cli\n"; |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_date { |
|
|
print_working("Getting System Date and Time:"); |
|
|
my $date = `date`; |
|
|
chomp($date); |
|
|
print OK "\t\\_ " . CYAN "$date\n"; |
|
|
} |
|
|
|
|
|
sub check_for_cpkeyclt_from_cli { |
|
|
print_working("Checking if /usr/local/psa/bin/plesk_key --update is in the last 50 commands run:"); |
|
|
my $grep50hist = `tail -50 \$(ls -t /root/.bash_history*) | grep "/usr/local/psa/bin/plesk_key --update"`; |
|
|
if ($grep50hist) { |
|
|
print_warn("\t\\_ Detected Plesk key update attempt via cli - Proceed with caution!"); |
|
|
} |
|
|
else { |
|
|
print OK "\t\\_ No evidence of key update attempts via cli\n"; |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_date { |
|
|
print_working("Getting System Date and Time:"); |
|
|
my $date = `date`; |
|
|
chomp($date); |
|
|
print OK "\t\\_ " . CYAN "$date\n"; |
|
|
} |
|
|
|
|
|
sub check_for_GCE { |
|
|
print_working("Checking for GCE:") unless ( !$verbose ); |
|
|
my $GCEenv = `hostname | grep c.compute.internal`; |
|
|
if ($GCEenv) { |
|
|
print OK "\t\\_ This server is a GCE Instance\n" unless ( !$verbose ); |
|
|
$isGCE_IP = 1; |
|
|
return 1; |
|
|
} |
|
|
else { |
|
|
print_warn("\t\\_ This server does not appear to be a GCE Instance"); |
|
|
$isGCE_IP = 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_for_AWS { |
|
|
print_working("Checking for AWS:") unless ( !$verbose ); |
|
|
my $AWSSysInfo = `dmidecode | grep Amazon`; |
|
|
my $AWSenv = `hostname -f | grep ec2.internal`; |
|
|
if ($AWSenv || $AWSSysInfo) { |
|
|
print OK "\t\\_ This server is an AWS EC2 Instance\n" unless ( !$verbose ); |
|
|
$isAWS_IP = 1; |
|
|
return 1; |
|
|
} |
|
|
else { |
|
|
print_warn("\t\\_ This server does not appear to be an AWS EC2 Instance"); |
|
|
$isAWS_IP = 0; |
|
|
return 0; |
|
|
} |
|
|
} |
|
|
|
|
|
sub loadHistory { |
|
|
if ( -f ("/etc/plesk/history") ) { |
|
|
|
|
|
|
|
|
open( my $fh, '<', "/etc/plesk/history" ) or die $!; |
|
|
while (<$fh>) { |
|
|
chomp; |
|
|
push @HISTORY, $_; |
|
|
} |
|
|
close($fh); |
|
|
} |
|
|
} |
|
|
|
|
|
sub get_mac { |
|
|
my $mac = shift; |
|
|
my $url = 'http://api.macvendors.com/' . $mac; |
|
|
my $ua = LWP::UserAgent->new( agent => 'Mozilla/5.0' ); |
|
|
my $res = $ua->get($url); |
|
|
return $res->is_success ? $res->content : 'UNKNOWN'; |
|
|
} |
|
|
|
|
|
sub get_license_type { |
|
|
my $license_data = shift; |
|
|
my $type = $license_data->{data}->{attributes}->{type} || 'UNKNOWN'; |
|
|
return $type; |
|
|
} |
|
|
|
|
|
sub check_hostsfile { |
|
|
print_working("Checking /etc/hosts for $HOSTNAME") unless ( !$verbose ); |
|
|
|
|
|
my $hostsfile = `grep "$HOSTNAME" /etc/hosts`; |
|
|
if ( substr( $hostsfile, 0, 1 ) eq "#" ) { |
|
|
push( |
|
|
@WARNINGS, |
|
|
"The $HOSTNAME appears to be commented out in the /etc/hosts file." |
|
|
); |
|
|
return; |
|
|
} |
|
|
if ($hostsfile) { |
|
|
if ($verbose) { |
|
|
print BOLD GREEN "\t\\_ $HOSTNAME was found in the /etc/hosts file\n"; |
|
|
print BOLD CYAN "\t\t\\_ $hostsfile"; |
|
|
} |
|
|
} |
|
|
else { |
|
|
push( |
|
|
@WARNINGS, |
|
|
"The $HOSTNAME was not found in the /etc/hosts file." |
|
|
); |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_for_malformed_access_log { |
|
|
my $access_log_path = "/var/log/httpd/access_log"; |
|
|
return unless (-f $access_log_path); |
|
|
|
|
|
print_working("Checking for malformed lines in $access_log_path"); |
|
|
open my $fh, '<', $access_log_path or do { |
|
|
push @WARNINGS, "Unable to open $access_log_path: $!"; |
|
|
return; |
|
|
}; |
|
|
while (my $line = <$fh>) { |
|
|
chomp $line; |
|
|
if ($line !~ /^(\S+) (\S+) (\S+) \[([^\]]+)\] "([^"]*)" (\S+) (\S+) "([^"]*)" "([^"]*)"/) { |
|
|
push @WARNINGS, "Malformed log line: $line"; |
|
|
} |
|
|
} |
|
|
close $fh; |
|
|
} |
|
|
|
|
|
sub check_for_security_updates { |
|
|
print_working("Checking for security updates:"); |
|
|
my $updates = `yum check-update --security`; |
|
|
if ($updates) { |
|
|
print_warn("Security updates available:\n$updates"); |
|
|
} else { |
|
|
print_OK("No security updates available."); |
|
|
} |
|
|
} |
|
|
|
|
|
sub check_for_selinux { |
|
|
print_working("Checking SELinux status:"); |
|
|
my $status = `getenforce`; |
|
|
chomp($status); |
|
|
if ($status eq 'Enforcing') { |
|
|
print_warn("SELinux is enabled and in enforcing mode."); |
|
|
} else { |
|
|
print_OK("SELinux is not in enforcing mode."); |
|
|
} |
|
|
} |
|
|
|
|
|
1; |
|
|
__END__ |