#!/usr/local/cpanel/3rdparty/bin/perl # Copyright 2023, cPanel, L.L.C. # All rights reserved. # http://cpanel.net # # Redistribution and use in source and binary forms, with or without # modification, are permitted provided that the following conditions are met: # # 1. Redistributions of source code must retain the above copyright notice, # this list of conditions and the following disclaimer. # # 2. Redistributions in binary form must reproduce the above copyright notice, # this list of conditions and the following disclaimer in the documentation # and/or other materials provided with the distribution. # # 3. Neither the name of the owner nor the names of its contributors may be # used to endorse or promote products derived from this software without # specific prior written permission. # # THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND # ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED # WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE # DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE # FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL # DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR # SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER # CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, # OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE # OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. # # SCRIPT: cplicensets # PURPOSE: Run some tests to determine what may be causing a cPanel license failure. # AUTHOR: Peter Elsner # CURRENT MAINTAINER: Peter Elsner use strict; my $version = "2.0.61"; our $maxGCE = 7; our $OPT_TIMEOUT; our $trCheck = 1; our $verify_license_data; our $TSE4 = 0; our $CS = 0; use Readonly; Readonly my $LICENSE_LOG => '/usr/local/cpanel/logs/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 Cpanel::Config::LoadWwwAcctConf (); use Cpanel::Validate::Hostname; use Cpanel::Validate::IP (); use Cpanel::Config::LoadUserOwners (); use Cpanel::Config::LoadUserDomains (); use Cpanel::SafeRun::Timed (); use Cpanel::SafeRun::Errors(); use Cpanel::JSON (); use JSON::MaybeXS qw(encode_json decode_json); use Cpanel::Verify (); use NetAddr::IP; use Text::CSV; use Cpanel::OS; use Cpanel::FindBin (); use Cpanel::OSSys::Env; use Data::Dump::Streamer; use Cpanel::Usage (); use DateTime; use Time::Piece; use Time::Seconds; use List::Util 'first'; $Term::ANSIColor::AUTORESET = 1; local $| = 1; my $distro = Cpanel::OS->_instance->distro; ## no critic (Cpanel::CpanelOS) my $distro_major = Cpanel::OS->_instance->major; ## no critic (Cpanel::CpanelOS) my $distro_minor = Cpanel::OS->_instance->minor; ## no critic (Cpanel::CpanelOS) my $distro_version = $distro_major . "." . $distro_minor; _init_run_state(); _set_run_type('cptech'); if ( exists $ENV{'PACHA_AUTOFIXER'} ) { _set_run_type('cptech'); } elsif ( defined $ENV{'HISTFILE'} and index( $ENV{'HISTFILE'}, 'cpanel_ticket' ) != -1 ) { _set_run_type('cptech'); } 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('cptech'); last; } } my ( $skipdate, $withlogs, $verifypage, $verbose, $help ); #my $wikilink="https://webpros.atlassian.net/wiki/x/boDwAAE"; my $wikilink="https://webpros.atlassian.net/wiki/x/NQGjBQE"; 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.cpanel.net'; 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 $cPLiscFile = '/usr/local/cpanel/cpanel.lisc'; our $DEVinConf; our $envtype; print MAGENTA "cPanel 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.cpanel.net, 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(); my $message=""; if ( iam('dnsonly')) { $message = RED " WARNING - DNSOnly License with user count > 0!!!" if ( $UserCnt > 0 && iam('cptech')); $TSE4 = 1; } print_working( "Total Users: " . CYAN $UserCnt ) unless(iam('cptech')); print_working( "Total Users: " . CYAN $UserCnt . $message) if(iam('cptech')); my $ActiveProfileNode = "UNKNOWN"; if ( -e "/usr/local/cpanel/cpanel.lisc" ) { $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(); check_cpsrvd(); get_mainip(); get_ipinfo($external_ip_address); get_wwwacctconf_ip(); 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_cpsources_conf_file(); check_routing(); check_root_servers(); check_auth_cpanel_resolution(); check_firewall(); check_other_ports(); shenanigans(); check_for_license_status_json(); check_cpanel_lisc_file(); run_rdate(); chkCreds(); display_route(); get_cpsrvd_restarts(); get_last_reboots(); my $warncnt = @WARNINGS; if ( $warncnt > 0 ) { if ($TSE4) { push @WARNINGS, RED "Send to TSE4" unless ( !iam('cptech') ); } if ($CS) { my $CSA = CSA(); if ($CSA) { # CS is on shift and nothing suspicious was found. push @WARNINGS, RED "Send to Customer Service" unless ($TSE4); } else { # CS is not on shift push @WARNINGS, RED "Send to TSE4" unless ( !iam('cptech') ); } } 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 = '/var/cpanel/sysinfo.config'; return if !-f $sysinfo_config; my $rpm_dist_ver; open my $fh, '<', $sysinfo_config or return; while (<$fh>) { if (/^rpm_dist_ver=(\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 $cPPerl = glob("/usr/local/cpanel/3rdparty/perl/*/bin/cpanm"); 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 = Cpanel::SafeRun::Timed::timedsaferun( 0, $cPPerl, "$reqmod" ); } else { print_OK( "\t \\_ " . $reqmod . " OK!\n" ) unless ( !$verbose ); } } } sub get_external_ip { $external_ip_address = Cpanel::SafeRun::Timed::timedsaferun( 0, 'curl', '-s', "https://myip.cpanel.net/v1.0/" ); my $external_ip_address_2089 = Cpanel::SafeRun::Timed::timedsaferun( 0, 'curl', '-s', "http://myip.cpanel.net:2089/v1.0/" ); chomp($external_ip_address); chomp($external_ip_address_2089); if ( $external_ip_address ne $external_ip_address_2089 ) { push( @WARNINGS, "External IP Address [$external_ip_address] on port 80 does not match that on port 2089 [$external_ip_address_2089]" ); } if ( $external_ip_address eq "" ) { print RED "Failed to retrieve the external IP address from https://myip.cpanel.net/v1.0/\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} } ) { 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 = 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(Cpanel::Verify::get_licenses($tcIPAddress)); $@ =~ /Failure to reach Verify service/ and $e = $@; if ( defined $e ) { $TSE4 = 1; push( @WARNINGS, "Could not verify license! - Please update $wikilink" ) unless ( !iam('cptech') ); } } $verify_license_data = Cpanel::Verify::get_licenses($tcIPAddress); } #sub get_license_data { # my $tcIPAddress = shift; # $verify_license_data = Cpanel::Verify::get_licenses($tcIPAddress); #} sub is_hostname_fqdn { $HOSTNAME = get_hostname(); # IF $HOSTNAME is empty here, it means the hostname -f command failed or timedout. We need to find an alternative # method to obtain the hostname then. Or all kinds of errors will happen # CAN USE Cpanel::Sys::Hostname and my $HOSTNAME = Cpanel::Sys::Hostname::gethostname(); 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 ("/usr/local/cpanel/lisc.lock") ) ); push( @WARNINGS, "The /usr/local/cpanel/lisc.lock file present!" ); } sub check_for_trial { return unless ( -e ("/var/cpanel/trial") ); if ($Trial) { print YELLOW "[INFO] - Trial touchfile detected - If you just purchased a license a hard restart of cpsrvd may be required before cpkeyclt will work!\n"; } else { push( @WARNINGS, "The /var/cpanel/trial touchfile found and license is not a trial license" ) unless ($Trial); } } sub check_cpsrvd { return unless ( iam('cptech' )); print_working("Checking /usr/local/cpanel/cpsrvd.so for modifications.") unless ( !$verbose ); my $results = Cpanel::SafeRun::Timed::timedsaferun( 3, 'grep', '-a', 'syscare.ir', '/usr/local/cpanel/cpsrvd.so' ); return unless($results); push( @WARNINGS, "Found license circumvention auth links within the cpsrvd.so file. Please update $wikilink"); $TSE4 = 1; } sub check_hostsfile { print_working("Checking /etc/hosts for $HOSTNAME") unless ( !$verbose ); my $hostsfile = Cpanel::SafeRun::Timed::timedsaferun( 3, '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/cpanel/accounting.log") ) { my $FirstAcct = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', ":CREATE:", '/var/cpanel/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 = Cpanel::OSSys::Env::get_envtype(); ## no critic (ProhibitCallsToUnexportedSubs) 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/cpanel/mainip" ) { print RED "[WARN] /var/cpanel/mainip file is empty.\n" unless ( !$verbose ); print YELLOW "\t \\_ Might be fixed by running /usr/local/cpanel/scripts/mainipcheck\n" unless ( !$verbose ); push( @WARNINGS, "/var/cpanel/mainip file is empty.\n\t\t \\_ Might be fixed by running /usr/local/cpanel/scripts/mainipcheck" ); return; } print_working("Obtaining contents of /var/cpanel/mainip:") unless ( !$verbose ); open my $fh, '<', '/var/cpanel/mainip'; 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 = Cpanel::SafeRun::Timed::timedsaferun( 2, 'ip', 'addr','show' ); my $isOnServer = ( grep { /$mainip/ } $ipaddrshow ); if ( !($isOnServer) ) { print RED "[WARN] /var/cpanel/mainip [$mainip] is not bound to this server.\n"; print YELLOW "\t \\_ Might be fixed by running /usr/local/cpanel/scripts/mainipcheck\n"; push( @WARNINGS, "/var/cpanel/mainip [$mainip] not bound to this server.\n\t\t \\_ Might be fixed by running /usr/local/cpanel/scripts/mainipcheck" ); } } else { push( @WARNINGS, "The mainip in /var/cpanel/mainip [$mainip] seems to be missing" ); } } sub get_ipinfo { return unless ($verbose); my $ipinfoIP = shift; print_working("Getting ipinfo for $ipinfoIP"); my $ipinfo = Cpanel::SafeRun::Timed::timedsaferun( 0, '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 = Cpanel::Config::LoadWwwAcctConf::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('cptech') ); $TSE4 = 1; } else { return unless ($verbose); my $license_log_file = $LICENSE_LOG; my $rootOwnedCnt = qx[ grep -c 'confirm this connection was from a root owned process' $license_log_file | grep -v 'The exact message was: ' ]; ## no critic (Cpanel::ProhibitQxAndBackticks) my $expireCnt = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-c', "^The license is expired", "$license_log_file" ); my $activeCnt = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-c', "^The license has been activated too many times", "$license_log_file" ); my $failureCnt = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-c', "License update failed", "$license_log_file" ); my $successCnt = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-c', "License update succeeded", "$license_log_file" ); my $trialCnt = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-c', "Already Used cPanel/WHM Trial License for this IP", "$license_log_file" ); my $noValLic = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-c', "^No valid cPanel/WHM license found", "$license_log_file" ); my $noPaidLic = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-c', "^No Paid License for this server", "$license_log_file" ); my $TotTWRestarts = Cpanel::SafeRun::Timed::timedsaferun( 6, 'grep', '-c', "Restarting cpsrvd", "/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 cPanel/WHM 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 cPanel/WHM 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 cpsrvd: " . MAGENTA $TotTWRestarts . "\n"; } return; } sub get_devices { my ( $device, $nicIP ); print_working("Obtaining NIC Devices:") unless ( !$verbose ); my $devices = Cpanel::SafeRun::Timed::timedsaferun( 4, '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; } check_for_dhcp($device) unless( $distro eq 'ubuntu' ); } 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 = Cpanel::SafeRun::Timed::timedsaferun( 4, '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( Cpanel::FindBin::findbin('arping') ); return if ( $envtype eq "virtuozzo" ); return if ( $envtype eq "vzcontainer" ); return if ( $nicIPAddr eq "" or $nicdevice eq "" ); my $ARPINGCMD = Cpanel::SafeRun::Timed::timedsaferun( 0, 'arping', '-D', '-I', "$nicdevice", '-c', '2', "$nicIPAddr" ); my @ARPINGCMD = split /\n/, $ARPINGCMD; my $allgood = 0; foreach my $arpinginfo (@ARPINGCMD) { chomp($arpinginfo); if ( $arpinginfo =~ m/Received 0|100% packet loss/ ) { print_working( "Checking $nicdevice for multiple devices responding to $nicIPAddr: " . GREEN . "None" ) unless ( !$verbose ); $allgood = 1; } } return if ($allgood); push( @WARNINGS, "The IP address $nicIPAddr may be listening on other devices! ($ARPINGCMD)" ); } sub check_for_multiple_defroute { return unless ( -e "/etc/sysconfig/network-scripts" ); print_working("Checking for multiple DEFROUTE=yes lines:") unless ( !$verbose ); my @defroutes; opendir( my $dir_fh, "/etc/sysconfig/network-scripts/" ); @defroutes = grep { !/^\.\.?$/ } readdir $dir_fh; closedir $dir_fh; my $defroutecnt = 0; my $defrouteline = "[ "; foreach my $NetScriptFile (@defroutes) { chomp($NetScriptFile); next unless ( $NetScriptFile =~ m/ifcfg-/ ); my $DFCnt = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', "^DEFROUTE=yes", "/etc/sysconfig/network-scripts/$NetScriptFile" ); if ($DFCnt) { $defroutecnt++; $defrouteline .= $NetScriptFile . " "; } } if ( $defroutecnt > 1 ) { print RED . "\t \\_ Found " . WHITE $defroutecnt . " " . CYAN $defrouteline . "]" unless ( !$verbose ); push( @WARNINGS, "Found multiple DEFROUTE=yes lines within the /etc/sysconfig/network-scripts/ifcfg-* files.\n\t\t \\_ Only the main NIC device should have a DEFROUTE defined. [$defrouteline]" ); } else { print BOLD GREEN . "\t \\_ None" unless ( !$verbose ); } print "\n" unless ( !$verbose ); } sub check_for_dhcp { my $nicdevice = shift; return unless( $distro_major < 9 ); print_working("Checking Networking Configs For DHCP:") unless ( !$verbose ); my $grep4dhcp = Cpanel::SafeRun::Timed::timedsaferun( 4, 'grep', '-i', "dhcp", "/etc/sysconfig/network-scripts/ifcfg-$nicdevice" ); my $Look4NM; $Look4NM = qx[ ps faux | grep 'NetworkManager' | grep -v grep ]; ## no critic (Cpanel::ProhibitQxAndBackticks) if ($grep4dhcp) { print RED "\t\\_ NIC Device " . $nicdevice . " set for DHCP.\n" unless ( !$verbose ); push( @WARNINGS, "NIC Device " . $nicdevice . " has DHCP config. May cause automatic IP/Hostname changes." ); } if ($Look4NM) { print RED "\t\\_ NetworkManager process found.\n" unless ( !$verbose ); push( @WARNINGS, "Found NetworkManager processes running. May cause automatic IP/Hostname changes.\n\t\t\\_ [ See: https://docs.cpanel.net/knowledge-base/general-systems-administration/how-to-disable-network-manager/ ]" ); } if ( !$grep4dhcp && !$Look4NM ) { print BOLD GREEN "\t \\_ None Found\n" unless ( !$verbose ); } } sub getMAC { my $macaddr = shift; my $macaddrencoded = url_encode($macaddr); my $MACchkURL = "https://cpaneltech.ninja/cgi-bin/getvendor.cgi"; my $result = Cpanel::SafeRun::Timed::timedsaferun( 0, 'curl', '-s', "$MACchkURL?$macaddr" ); chomp($result); $result =~ s/\s+$//; return $result; } sub url_encode { my $rv = shift; $rv =~ s/([^a-z\d\Q.-_~ \E])/sprintf("%%%2.2X", ord($1))/geix; $rv =~ tr/ /+/; return $rv; } sub run_check_valid_server_hostname { my $HostnameValid = Cpanel::Validate::Hostname::is_valid($HOSTNAME); ## no critic (ProhibitCallsToUnexportedSubs) if ($HostnameValid) { print_working( "Checking For Valid Server Hostname: " . GREEN "OK" ) unless ( !$verbose ); } else { push( @WARNINGS, "The " . $HOSTNAME . " does not appear to be a valid hostname!" ); } my ($FirstHost) = ( split( /\./, $HOSTNAME ) )[0]; my $FirstHostWarn = ""; if ( $FirstHost =~ m/^whm$|^cpanel$|^webmail$|^webdisk$|^mail$|^autodiscover$/ ) { $FirstHostWarn = RED "$HOSTNAME should not begin with a service subdomain ($FirstHost)\n\t\t \\_ See: " . CYAN "https://docs.cpanel.net/whm/networking-setup/change-hostname/"; push( @WARNINGS, $FirstHostWarn ); } } sub check_kernel_hostname { my ($KERN_HOSTNAME) = ( split( /= /, Cpanel::SafeRun::Timed::timedsaferun( 4, 'sysctl', 'kernel.hostname' ) ) )[1]; chomp($KERN_HOSTNAME); if ( $KERN_HOSTNAME eq $HOSTNAME ) { print_working( "Verifying if sysctl kernel.hostname matches " . $HOSTNAME . ": " . GREEN "OK" ) unless ( !$verbose ); } else { push( @WARNINGS, "The value in sysctl kernel.hostname does not match $HOSTNAME" ); } } sub display_etc_hostname { return if ( !( -e ("/etc/hostname") ) ); open my $fh, '<', '/etc/hostname'; my $etchostname; while (<$fh>) { $etchostname = $_; } close($fh); chomp($etchostname); print_working("Checking for an /etc/hostname file: Present - Contents are:") unless ( !$verbose ); print BOLD GREEN "\t\\_ " . $etchostname . "\n" unless ( !$verbose ); if ( $etchostname ne $HOSTNAME ) { push( @WARNINGS, "Value in /etc/hostname [$etchostname] does not match servers hostname [$HOSTNAME]" ); } } sub get_network_hostname { return if ( !( -e ("/etc/sysconfig/network") ) ); my $networkhost = ""; open( my $fh, '<', '/etc/sysconfig/network' ); while (<$fh>) { if ( $_ =~ m/HOSTNAME=/ ) { $networkhost = $_; last; } } close($fh); return unless ($networkhost); chomp($networkhost); if ($networkhost) { print_working("Obtaining the HOSTNAME setting from /etc/sysconfig/network: Present - Contents are:") unless ( !$verbose ); print BOLD GREEN "\t\\_ " . $networkhost . "\n" unless ( !$verbose ); } ($networkhost) = ( split( /=/, $networkhost ) )[1]; $networkhost =~ s/\"//g; if ( $networkhost ne $HOSTNAME ) { push( @WARNINGS, "HOSTNAME value in /etc/sysconfig/network [$networkhost] does not match servers hostname [$HOSTNAME]" ); } } sub get_ip_of_hostname { my $HOSTNAME_IP = qx[ dig +tries=2 +time=5 \@208.67.222.222 $HOSTNAME +short | head -1 2>/dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) chomp($HOSTNAME_IP); return if ( $HOSTNAME_IP eq "" ); my $IPValid = Cpanel::Validate::IP::is_valid_ip($HOSTNAME_IP); ## no critic (ProhibitCallsToUnexportedSubs) if ( !$IPValid ) { push( @WARNINGS, "Could not reverse $HOSTNAME to a valid IP address." ); } if ( $HOSTNAME_IP eq $external_ip_address ) { print_working("IP for $HOSTNAME [$HOSTNAME_IP] matches servers IP [$external_ip_address]") unless ( !$verbose ); } else { push( @WARNINGS, "IP address for $HOSTNAME [$HOSTNAME_IP] does not match servers IP [$external_ip_address]" ); } if ($HOSTNAME_IP) { my $REVERSED_IP = qx[ dig +tries=2 +time=5 \@208.67.220.220 -x $HOSTNAME_IP +short 2>/dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) chomp($REVERSED_IP); chop($REVERSED_IP); if ($REVERSED_IP) { print_working( "Reversing IP [$HOSTNAME_IP]: " . GREEN $REVERSED_IP) unless ( !$verbose ); if ( $REVERSED_IP ne $HOSTNAME ) { push( @WARNINGS, "Reverse of $HOSTNAME_IP [$REVERSED_IP] does not match $HOSTNAME" ); } } else { push( @WARNINGS, "Failed to reverse $HOSTNAME_IP back to $HOSTNAME" ); } } } sub check_if_hostname_resolves_locally { my $LOCAL_HOSTNAME_IP = qx[ dig \@$external_ip_address +tries=2 +time=5 $HOSTNAME +short | head -1 2>/dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) chomp($LOCAL_HOSTNAME_IP); return if ( $LOCAL_HOSTNAME_IP eq "" ); print_working("Checking if $HOSTNAME resolves locally") unless ( !$verbose ); my $IPValid = Cpanel::Validate::IP::is_valid_ip($LOCAL_HOSTNAME_IP); ## no critic (ProhibitCallsToUnexportedSubs) if ( !$IPValid ) { push( @WARNINGS, "Could not resolve $HOSTNAME locally" ); print RED "\t \\_ No\n" unless ( !$verbose ); return; } else { if ($verbose) { print BOLD GREEN "\t \\_ Yes [$LOCAL_HOSTNAME_IP]\n" unless ( !$verbose ); } print_working("Checking if $LOCAL_HOSTNAME_IP reverses back to $HOSTNAME: ") unless ( !$verbose ); my $LOCAL_HOSTNAME_REVERSED = qx[ dig +tries=2 +time=5 -x $LOCAL_HOSTNAME_IP +short 2>/dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) chomp($LOCAL_HOSTNAME_REVERSED); chop($LOCAL_HOSTNAME_REVERSED); if ( $LOCAL_HOSTNAME_REVERSED eq $HOSTNAME ) { print BOLD GREEN "\t \\_ Yes\n" unless ( !$verbose ); } else { print RED "\t \\_ No\n" unless ( !$verbose ); push( @WARNINGS, "$LOCAL_HOSTNAME_IP does not reverse back to $HOSTNAME" ); } } } sub check_file_for_odd_chars { my $TheFile = shift; return if !-e $TheFile; my @invalid; print_working("Checking $TheFile for non-ascii characters:") unless ( !$verbose ); open my $fh, '<', $TheFile; my @DATA = <$fh>; close($fh); my $cnt = 0; foreach my $line (@DATA) { chomp($line); if ( $line =~ /[^!-~\s]/g ) { push( @invalid, "$line contains => [ $& ]" ); } } splice( @invalid, 0, 1 ); $cnt = @invalid; if ( $cnt > 0 ) { push( @WARNINGS, "The $TheFile file has $cnt line(s) containing non-ascii characters - use --verbose to view." ); if ($verbose) { foreach my $invalid (@invalid) { chomp($invalid); print MAGENTA "\t\\_ $invalid\n"; } } } else { print_OK("\t\\_ All Good\n") unless ( !$verbose ); } } sub check_resolvconf { return unless ( -s "/etc/resolv.conf" ); my $NMinResolv = 0; my $invalidNS = 0; open( my $fh, '<', '/etc/resolv.conf' ); while (<$fh>) { if ( $_ =~ m/NetworkManager/ ) { $NMinResolv = 1 unless( $distro_major > 8 ); } if ( $_ =~ m/nameserver 127.0.0./ ) { $invalidNS = 1; } } close($fh); if ($NMinResolv) { push( @WARNINGS, "/etc/resolv.conf was possibly Generated by NetworkManager - may cause resolution issues!" ); } if ($invalidNS) { push( @WARNINGS, "/etc/resolv.conf has invalid nameserver value (127.0.0.*) - may cause resolution issues!" ); } if ( !$NMinResolv && !$invalidNS && $verbose ) { print_working("Checking /etc/resolv.conf for anomalies: "); print BOLD GREEN "\t\\_ Looks Good!\n"; } } sub check_for_cloudcfg { return if ( !( -d ("/etc/cloud/") ) ); print_working("Checking cfg files within /etc/cloud/ for specific settings...") unless ( !$verbose ); my $preserve_hostname = 0; my $manage_etc_hosts = 0; my @etc_cloud_configs = glob(q{ /etc/cloud/cloud.cfg.d/*.cfg }); if ( -e '/etc/cloud/cloud.cfg' ) { push @etc_cloud_configs, "/etc/cloud/cloud.cfg"; } foreach my $etc_cloud_config (@etc_cloud_configs) { last if ( $preserve_hostname || $manage_etc_hosts ); chomp($etc_cloud_config); open( my $fh, '<', "$etc_cloud_config" ); while (<$fh>) { if ( $_ =~ m/preserve_hostname: true/ ) { $preserve_hostname = 1; } if ( $_ =~ m/manage_etc_hosts: false/ ) { $manage_etc_hosts = 1; } } close($fh); } if ($preserve_hostname) { print BOLD GREEN "\t\\_ preserve_hostname is set to true within a cfg file in /etc/cloud/*\n" unless ( !$verbose ); } else { print RED "\t\\_ preserve_hostname is NOT set to true within any cfg file in /etc/cloud/*\n" unless ( !$verbose ); push( @WARNINGS, "preserve_hostname not set within any file in /etc/cloud/cloud.cfg.d/" ); } if ($manage_etc_hosts) { print BOLD GREEN "\t\\_ manage_etc_hosts is set to false within a cfg file in /etc/cloud/*\n" unless ( !$verbose ); } else { print RED "\t\\_ manage_etc_hosts is NOT set to false within any cfg file in /etc/cloud/*\n" unless ( !$verbose ); push( @WARNINGS, "manage_etc_hosts not set within any file in /etc/cloud/cloud.cfg.d/" ); } } sub check_for_dhclient_exit_hook { return unless ( -d "/etc/dhcp/dhclient-exit-hooks.d/" ); print_working("Checking for /etc/dhcp/dhclient-exit-hooks.d/ scripts") unless ( !$verbose ); my $zzzsethostname = 0; my $sethostname = 0; my $dheh_hostname = ""; my @dhclient_exit_hooks = glob(q{ /etc/dhcp/dhclient-exit-hooks.d/*}); foreach my $dhclient_exit_hook (@dhclient_exit_hooks) { chomp($dhclient_exit_hook); if ( $dhclient_exit_hook =~ m/hostname/i ) { if ( $isGCE_IP ) { ## CX-880 - Google Cloud instances ignore .sh extension on dhclient-exit-hook scripts. $zzzsethostname = ($dhclient_exit_hook) =~ m/\/zzz-set-hostname/ ? 1 : 0; $sethostname = ($dhclient_exit_hook) =~ m/\/set-hostname/ ? 1 : 0; } else { $zzzsethostname = ($dhclient_exit_hook) =~ m/\/zzz-set-hostname.sh/ ? 1 : 0; $sethostname = ($dhclient_exit_hook) =~ m/\/set-hostname.sh/ ? 1 : 0; } open( my $fh, '<', "$dhclient_exit_hook" ); while (<$fh>) { chomp($_); if ( substr( $_, 0, 8 ) eq 'hostname' ) { $dheh_hostname = substr( $_, 9 ); chomp($dheh_hostname); last; } } close($fh); } } if ($zzzsethostname) { print BOLD GREEN "\t\\_ zzz-set-hostname.sh file is present\n" unless ( !$verbose ); if ( $dheh_hostname ne $HOSTNAME ) { print RED "\t\\_ $HOSTNAME is not set within the zzz-set-hostname.sh file\n" unless ( !$verbose ); push( @WARNINGS, "The /etc/dhcp/dhclient-exit-hooks.d/zzz-set-hostname.sh file does not contain $HOSTNAME" ) unless ($sethostname); } } else { print RED "\t\\_ zzz-set-hostname.sh file is not present\n" unless ( !$verbose ); } if ($sethostname) { print BOLD GREEN "\t\\_ set-hostname.sh file is present\n" unless ( !$verbose ); if ( $dheh_hostname ne $HOSTNAME ) { print RED "\t\\_ $HOSTNAME is not set within the set-hostname.sh file\n" unless ( !$verbose ); push( @WARNINGS, "The /etc/dhcp/dhclient-exit-hooks.d/set-hostname.sh file does not contain $HOSTNAME" ) unless ($zzzsethostname); } } else { print RED "\t\\_ set-hostname.sh file is not present\n" unless ( !$verbose ); } if ($isGCE_IP) { push( @WARNINGS, "IP belongs to Google and is a Google Compute Engine (GCE)" ); if ( !$sethostname && !$zzzsethostname ) { push( @WARNINGS, "Neither zzz-set-hostname.sh nor set-hostname.sh script was found - See: https://go.cpanel.net/cloudhostname" ); } } if ( -e ("/etc/dhcp/dhclient.d/google_hostname.sh") ) { push( @WARNINGS, "Found /etc/dhcp/dhclient.d/google_hostname.sh file - consider disabling this. Causes license issues!" ); } } sub getGCE_IPs { my $chkIP = shift; chomp($chkIP); my $startGCE = 1; my $GCEsubnet; my $is_google_ip = 0; while ( $startGCE <= $maxGCE ) { my $GCEsubnets = Cpanel::SafeRun::Timed::timedsaferun( 0, 'dig', 'txt', "_cloud-netblocks$startGCE.googleusercontent.com", '+short' ); my @GCEsubnets = split /\s+/, $GCEsubnets; foreach my $GCEsubnet (@GCEsubnets) { chomp($GCEsubnet); next unless ( substr( $GCEsubnet, 0, 4 ) eq "ip4:" ); my ($gceline) = ( split( /\:/, $GCEsubnet ) )[1]; my $network = NetAddr::IP->new($gceline); my $ip = NetAddr::IP->new($chkIP); if ( $ip->within($network) ) { $is_google_ip = 1; } } $startGCE++; } return $is_google_ip; } sub getAWS_IPs { my $chkIP = shift; chomp($chkIP); my $AWSsubnets = Cpanel::SafeRun::Timed::timedsaferun( 0, 'curl', '-s', 'https://ip-ranges.amazonaws.com/ip-ranges.json' ); my $output = decode_json( $AWSsubnets ); for my $prefix ( @{ $output->{prefixes} // q{} } ) { my $ip_prefix=$prefix->{ip_prefix}; chomp($ip_prefix); my $network = NetAddr::IP->new($ip_prefix); my $ip = NetAddr::IP->new($chkIP); if ( $ip->within($network) ) { return 1; } } } sub check_for_license_error { return unless ( -e ("/usr/local/cpanel/logs/license_error.display") ); print_working("Found a license error: ") unless ( !$verbose ); open( my $fh, '<', "/usr/local/cpanel/logs/license_error.display" ); my @LICERR = <$fh>; close($fh); my $toomany = 0; foreach my $errline (@LICERR) { chomp($errline); print RED "\t\\_ $errline\n" unless ( !$verbose ); if ( $errline =~ m/activated too many times on different machines/ ) { $TSE4 = 0; $CS = 1; } } if ( $toomany == 0 ) { push( @WARNINGS, "The /usr/local/cpanel/logs/license_error.display file contains additional license errors. " ); } } sub get_hostname_at_install { return if !-s "/var/log/cpanel-install.log"; return unless ($verbose); print_working("Hostname at time of cPanel install was set to: "); open( my $fh, '<', '/var/log/cpanel-install.log' ); my @install_log = <$fh>; close($fh); my $HOSTNAME_AT_INSTALL = ""; foreach my $install_log_line (@install_log) { chomp($install_log_line); next unless ( $install_log_line =~ m/Validating that the system hostname/ ); ($HOSTNAME_AT_INSTALL) = ( split( /\'/, $install_log_line ) )[1]; last; } chomp($HOSTNAME_AT_INSTALL); if ( !($HOSTNAME_AT_INSTALL) ) { print BOLD CYAN "\t\\_ Unknown\n"; } else { print BOLD CYAN "\t\\_ " . $HOSTNAME_AT_INSTALL . "\n"; } } sub check_for_hostname_changes { return unless ($verbose); print_working("Checking access_log for hostname changes:"); my ( @HOSTNAME_CHANGES, $histchange, $hostname_change, $hostname_date_change1, $hostname_date_change2 ); open( my $fh, '<', '/usr/local/cpanel/logs/access_log' ); my @ACCESS_LOG_DATA = <$fh>; close($fh); my @accesslog_hostnames; foreach my $access_log_line (@ACCESS_LOG_DATA) { chomp($access_log_line); next unless ( $access_log_line =~ m/dochangehostname\?hostname/ ); push( @accesslog_hostnames, $access_log_line ); } my $alhostcnt = @accesslog_hostnames; if ( $alhostcnt == 0 ) { print GREEN "\t \\_ None\n"; } else { foreach my $allhostnames (@accesslog_hostnames) { chomp($allhostnames); ($hostname_change) = ( split( /\s+/, $allhostnames ) )[6]; ( $hostname_date_change1, $hostname_date_change2 ) = ( split( /\s+/, $allhostnames ) )[ 3, 4 ]; ($hostname_change) = ( split( /=/, $hostname_change ) )[1]; if ($hostname_change) { print BOLD CYAN "\t\\_ Changed to: " . $hostname_change . " on " . $hostname_date_change1 . " " . $hostname_date_change2 . "\n" unless ( $hostname_change eq "1" ); } } } print_working("Checking /root/.bash_history for hostname changes:"); my @hostchangeHist; my $histtime; if ( $histCnt == 0 ) { print GREEN "\t \\_ None\n"; } else { foreach my $histline (@HISTORY) { chomp($histline); if ( substr( $histline, 0, 1 ) eq "#" ) { $histtime = substr( $histline, 1 ); } if ( $histline =~ m/\/usr\/local\/cpanel\/bin\/set_hostname |hostnamectl set-hostname |hostname / ) { print BOLD CYAN "\t \\_ On " . scalar localtime($histtime) . " - " . $histline . "\n" unless ( $histline =~ m/grep|vim \/etc\/hostname|vi \/etc\/hostname|rm|\-f/ ); } } } print_working("Checking /var/log/messages for hostname changes:"); my $messfile = ( $distro eq "ubuntu" ) ? "/var/log/kern.log" : "/var/log/messages"; open( my $fh2, '<', "$messfile" ); my @messlogRestarts; while (<$fh2>) { chomp($_); next unless ( $_ =~ m/systemd-hostnamed: Changed host name to/ ); push @messlogRestarts, $_; } close($fh2); my $messcnt = @messlogRestarts; if ( $messcnt == 0 ) { print BOLD GREEN "\t \\_ None\n"; } else { foreach my $messhost (@messlogRestarts) { chomp($messhost); print BOLD CYAN "\t\\_ On: $messhost\n"; } } } sub check_for_cpkeyclt_from_cli { return if ( !$verbose ); print_working("Checking /root/.bash_history for cpkeyclt:"); my @cpkeycltINHist; my $histtime; foreach my $histline (@HISTORY) { chomp($histline); if ( substr( $histline, 0, 1 ) eq "#" ) { $histtime = substr( $histline, 1 ); } if ( $histline =~ m/cpkeyclt|run_cpkeyclt/ ) { push( @cpkeycltINHist, "\t\\_ On " . BOLD GREEN scalar localtime($histtime) . " - " . $histline ); } } if (@cpkeycltINHist) { foreach my $cpkeyInHistLine (@cpkeycltINHist) { chomp($cpkeyInHistLine); print "$cpkeyInHistLine\n"; } } else { print BOLD CYAN "\t\\_ None\n"; } return unless ( -e $LICENSE_LOG ); } sub display_license_log_last_20 { print_working("Displaying last 20 cpkeyclt calls from license_log: ") unless ( !$verbose ); open( my $fh, '<', '/usr/local/cpanel/logs/license_log' ); my @Last20cpkeyclt; my $seen = 0; while (<$fh>) { chomp($_); if ($verbose) { if ( $_ =~ m/License Update Request/ ) { push @Last20cpkeyclt, $_ . "\n"; } } if ( $_ =~ m/xml-api - run_cpkeyclt/ && -x '/usr/sbin/zabbix_agentd' ) { push @WARNINGS, "Zabbix agent present and cpkeyclt restarts detected in $LICENSE_LOG" unless ( $seen > 0 ); $seen++; } } close($fh); if ($verbose) { my $total_lines = ( scalar @Last20cpkeyclt > 20 ) ? 20 : @Last20cpkeyclt; my @reversed = reverse(@Last20cpkeyclt); my $cnt = 0; while ( $cnt < $total_lines ) { print BOLD CYAN "\t\\_ " . $reversed[$cnt]; $cnt++; } } } sub loadHistory { $histCnt = 0; return if ( !( -e "/root/.bash_history" ) ); open( my $fh, '<', "/root/.bash_history" ); @HISTORY = <$fh>; close($fh); $histCnt = @HISTORY; my $linecnt=0; while( $linecnt < 20 ) { $linecnt++; if ( @HISTORY[$linecnt] =~ m{Still logged into remote} ) { $TSE4 = 1; push( @WARNINGS, "Please update $wikilink /root/.bash_history (within the first 20 lines) See CX-771" ) unless ( !iam('cptech') ); last; } } } sub shenanigans { getHist(); chk_mounts(); chk_upcp_posthook(); chk_etc_hosts(); chk_fwdip(); chk_etc_ips(); chk_cgls(); chk_cgls2(); chk_cgls3(); chk_rclocal(); proxy_chk(); check_csf_conf(); check_sources_pm(); } sub chk_fwdip { return if ( !( -e ("/var/cpanel/domainfwdip") ) ); open( my $fh, '<', "/var/cpanel/domainfwdip" ); my @FWDIP = <$fh>; close($fh); foreach my $fwdip (@FWDIP) { chomp($fwdip); if ( $fwdip =~ m/208.74./ ) { $TSE4 = 1; push( @WARNINGS, "Found $fwdip in /var/cpanel/domainfwdip file" ) unless ( !iam('cptech') ); } } } sub getHist { my @lookfor = ( 'bigconfig.com', 'cpsanitycheck.so', 'cPanelCentral', 'sectools', '3jenan', 'parstech.com.tr', 'lic4all.ga', 'lisans4.com', 'gplpixel.com', 'gblicense.net', 'begpl.com', 'syslic.org', 'secinstall', 'update_cpanelv2', 'onlinelic.net', '\./s.sh', 'tsocks', 'fviatool', 'time4vps.com', 'sosaystartcom', 'verifycpanel.com', 'cpanelcity.com', 'dotshift.net', 'syslicense.net', 'cheaplicensebd.com', 'licensedash.com', 'licenseboy.com', 'licensehost.net', 'licenseall.org', 'highhosters.net', 'cplicenses.com', 'licences4server.com', 'razorhost.in', 'hostseo.uk', 'cpanelcentral.com', 'licensepro.net', 'label-whoite.com', 'yoncu.com', 'resellercenter.ir', 'add blackhole 208\.74\.', 'Lisans -Zorla', 'Lisans -Guncelle', 'Lisans -Kaldır', 'Lisans -Pasif', 'Lisans -Aktif', 'Lisans -5min', 'sistem/Kurulum', 'buylicense.software', 'cyberlic', 'tactu_cpanel', 'asanlicense.ir', '/usr/local/csp', '/usr/bin/update_cloudv2', 'update_cpanelv2', 'licensedl.com', 'lisanstr.com', 'licensemaker.com', 'webnolog.net', 'hostcycle.in', 'securecpanel.net', 'tikweb.ir', 'ehabdiab.com', 'tqniait.com', 'warezservers.com', 'hostingmango.com', 'licensehosts.com', 'easyconfig.net', 'promohosts.com', 'PhLicenseCP', 'cheapcpanel.net', 'remove_reseller.sh', 'licenseman.net', 'goviralhost.com', 'natoreit.com', 'clubhosty.com', 'licensefarm.com', 'licenseportal.in', 'solicense.com', 'licensepanel.io', 'cpanel -D', 'licensedl.com', 'license.cheap', 'cc-get.com', 'licenseweez.com', 'serverlicenses.com', 'regularhost.com', 'license-monster', 'freshlicense.com', 'cheapcpanellicense.com', 'licenses.center', 'cpanelsave.com', 'hostbeet.com', 'cheap-cpanel.com', 'sharedlicense.com', 'sharedlicense.io', 'lic.click', 'licensemonster.xyz', 'licensemesh.com', 'sharedlicensecp.com', 'sharedlicenses.com', 'sharedlicensing.com', ); foreach my $lookfor (@lookfor) { chomp($lookfor); my $match = grep { /$lookfor/ } @HISTORY; chomp($match); next unless ($match); if ($match) { push( @WARNINGS, "Please update $wikilink Found /root history [$lookfor]" ) unless ( !iam('cptech') || ($TSE4) ); $TSE4 = 1; } } } sub check_sources_pm { my $found = 0; my @files=qw( /usr/local/cpanel/Cpanel/Security/Advisor/Assessors/Kernel.pm /usr/local/cpanel/Cpanel/Config/Sources.pm /usr/local/cpanel/Whostmgr/Store/Product/ImunifyAVPlus.pm /usr/local/cpanel/Whostmgr/Imunify360.pm ); for my $file(@files) { next unless( -f $file ); open( my $fh, '<', $file ); while( <$fh> ) { if ( $_ =~ m/csp.name/ && iam('cptech')) { push( @WARNINGS, "Please update $wikilink Found in $file file" ); $TSE4 = 1; $found = 1; last; } } close( $fh ); if ( $found ) { return; } } } sub chk_upcp_posthook { return unless ( -s "/usr/local/cpanel/scripts/postupcp" ); my @lookfor = ( 'license.configserver.pro', 'cpsanitycheck.so', 'cpanel.lisc' ); open my $fh, '<:encoding(UTF-8)', "/usr/local/cpanel/scripts/postupcp"; while ( my $match = <$fh> ) { chomp($match); foreach my $lookfor (@lookfor) { chomp($lookfor); if ( $match =~ /$lookfor/ ) { push( @WARNINGS, "Please update $wikilink Found in the /usr/local/cpanel/scripts/postupcp hook" ) unless ( !iam('cptech') ); $TSE4 = 1; last; } } } close($fh); } sub chk_etc_hosts { open( my $fh, '<', "/etc/hosts" ); my @HOSTS = <$fh>; close($fh); foreach my $hostline (@HOSTS) { chomp($hostline); if ( $hostline =~ m/auth.cpanel.net|auth2.cpanel.net|auth3.cpanel.net|dev.cpanel.net|license.cheap|resellercenter.ir/ ) { push( @WARNINGS, "Found $hostline in /etc/hosts" ) unless ( !iam('cptech') ); $TSE4 = 1; } } if ( -e "/etc/hosts.allow" ) { open( my $fh2, '<', '/etc/hosts.allow' ); while (<$fh2>) { chomp($_); if ( $_ =~ m/88.198.132.70|68.183.71.131|88.198.132.68/ ) { push( @WARNINGS, "Please update $wikilink Found in /etc/hosts.allow" ) unless ( !iam('cptech') ); $TSE4 = 1; last; } } close($fh2); } } sub chk_mounts { my $liscMounted = Cpanel::SafeRun::Timed::timedsaferun( 5, 'mount' ); my @liscMounted = split /\n/, $liscMounted; return unless (@liscMounted); foreach my $mount_line (@liscMounted) { if ( $mount_line =~ m/cpanel.lisc|cpsanitycheck.so/ ) { push( @WARNINGS, "cpanel.lisc or cpsanitycheck.so is possibly mounted" ) unless ( !iam('cptech') ); $TSE4 = 1; } } } sub chk_etc_ips { return unless ( -s '/etc/ips' ); open( my $fh, '<', '/etc/ips' ); while (<$fh>) { chomp($_); if ( $_ =~ m/$external_ip_address/ ) { push( @WARNINGS, "$external_ip_address found in /etc/ips file. You should not add the licensed IP as an alias!" ); } if ( $_ =~ m{^208\.74\.} ) { push( @WARNINGS, "Found cPanel IP's within /etc/ips file" ) unless ( !iam('cptech') ); $TSE4 = 1; } } close($fh); } sub chk_cgls { use List::Util qw[shuffle]; my $URL = eval unpack u => q{_(FAT='!S.B\O'0B.P}; ## no critic (ProhibitStringyEval) my $files = Cpanel::SafeRun::Timed::timedsaferun( 6, 'curl', '-s', "$URL" ); my @files = split /\n/, $files; my $ignoreHash; my @shenanigans; my $statfile; foreach my $file (@files) { chomp($file); my @statfile = qx[ stat $file 2>/dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) if (@statfile) { foreach my $statfile2 (@statfile) { chomp($statfile2); next unless ( $statfile2 =~ m{File: } ); ($statfile) = ( split( /\s+/, $statfile2 ) )[2]; push @shenanigans, "[" . $statfile . "] "; next; } } if ( -f $file or -d $file and not -z $file and not -l $file ) { $ignoreHash = ignoreHashes($file) unless ($statfile); my $filetype = Cpanel::SafeRun::Timed::timedsaferun( 5, 'file', "$file" ); next if ( $filetype =~ m/data/ && $file eq "/home/installer" ); next if ( $file eq '/root/installer.1' && -d '/var/cpanel/addons/SMTP2GO' ); next if ( $file eq '/root/installr.sh' && -d '/usr/local/cpanel/whostmgr/docroot/whmsonic/' && grep { "WHMSonic Setup" } '/root/installr.sh' ); push @shenanigans, "[" . $file . "] " unless ($ignoreHash); } } my @shenaniganlist; if ( scalar(@shenanigans) > 1 ) { @shenaniganlist = ( shuffle @shenanigans )[ 0 .. ( 2 - 1 ) ]; } else { @shenaniganlist = @shenanigans; } if ( scalar(@shenaniganlist) > 0 ) { $TSE4 = 1; push( @WARNINGS, "Please update $wikilink" ) unless ( !iam('cptech') ); push( @WARNINGS, "\t " . WHITE @shenaniganlist ) unless ( !iam('cptech') ); $cgls = 1; } } sub chk_cgls2 { my $isASCII = Cpanel::SafeRun::Timed::timedsaferun( 5, 'file', '/usr/local/cpanel/cpkeyclt' ); return unless ( $isASCII =~ m/ASCII/ ); $TSE4 = 1; push( @WARNINGS, "Please update $wikilink" ) unless ( $cgls || !iam('cptech') ); $cgls = 1; } sub chk_rclocal { return unless ( -e "/etc/rc.local" ); open( my $fh, '<', '/etc/rc.local' ); while (<$fh>) { chomp($_); if ( $_ =~ m/cpkeyclt/ ) { $TSE4 = 1; push( @WARNINGS, "Please update $wikilink Found in /etc/rc.local" ) unless ( !iam('cptech') ); } } close($fh); } sub check_csf_conf { return unless ( -d '/etc/csf/' ); opendir( my $dir_fh, "/etc/csf" ); my @files = grep { !/^\.\.?$/ } readdir $dir_fh; closedir $dir_fh; foreach my $file (@files) { next unless ( -f "/etc/csf/$file" ); open( my $fh, '<', "/etc/csf/$file" ); while (<$fh>) { chomp($_); if ( $_ =~ m{CSP Licensing Servers|/usr/bin/cspfwd|/usr/bin/CSPUpdate} ) { $TSE4 = 1; push( @WARNINGS, "Please update $wikilink Found in /etc/csf directory" ) unless ( !iam('cptech') ); } } close($fh); } } sub check_crons { print_working("Checking for cpkeyclt in cron related files") unless ( !$verbose ); my ( $ExistsInCronLog, $ExistsInCron, $CronPositive ); my $cronservice = 0; my $cronlog = ( $distro eq "ubuntu" ) ? "/var/log/syslog" : "/var/log/cron"; my $servicename = ( $distro eq "ubuntu" ) ? "cron.service" : "crond.service"; if ( -e ("/usr/bin/systemctl") ) { my $cronservice_status = Cpanel::SafeRun::Timed::timedsaferun( 5, 'systemctl', 'status', "$servicename" ); if ( $cronservice_status =~ m/active \(running\)/ ) { $cronservice = 1; } } if ( $cronservice && !-e ("$cronlog") ) { $ExistsInCronLog = Cpanel::SafeRun::Timed::timedsaferun( 5, '/usr/bin/journalctl', '-u', 'cron' ); if ( $ExistsInCronLog =~ m/cpkeyclt/ ) { print RED "\t\\_ Found cpkeyclt in systemd cron [journalctl]\n" unless ( !$verbose ); push( @WARNINGS, "Found cpkeyclt in cron log file [journalctl]" ); $CronPositive = 1; } } elsif ( -e ("$cronlog") ) { my @cron_log_files; if ( $distro eq "ubuntu" ) { @cron_log_files = glob(q{/var/log/syslog*}); } else { @cron_log_files = glob(q{/var/log/cron*}); } foreach my $cron_log_file (@cron_log_files) { chomp($cron_log_file); open( my $fh, '<', "$cron_log_file" ); while (<$fh>) { chomp($_); if ( $_ =~ m/cpkeyclt/ ) { print RED "\t\\_ Found cpkeyclt within the $cron_log_file file\n" unless ( !$verbose ); push( @WARNINGS, "Found cpkeyclt within the $cron_log_file file" ); $CronPositive = 1; last; } if ( $_ =~ m/CSPUpdate/ ) { push( @WARNINGS, "Please update $wikilink Found in $cron_log_file file contains CSP" ) unless ( !iam('cptech') || ($TSE4) ); $TSE4 = 1; } } close($fh); } } else { print RED "\t\\_ No /var/log/cron file found and systemd crond.service not configured!\n" unless ( !$verbose ); push( @WARNINGS, "No /var/log/cron file found and systemd cron.service not configured!" ); $CronPositive = 1; } my @cron_spool_files; if ( $distro eq "ubuntu" ) { @cron_spool_files = glob(q{/var/spool/cron/crontabs/*}); } else { @cron_spool_files = glob(q{/var/spool/cron/*}); } foreach my $cron_spool_file (@cron_spool_files) { chomp($cron_spool_file); open( my $fh, '<', "$cron_spool_file" ); while (<$fh>) { chomp($_); if ( $_ =~ m/cpkeyclt/ ) { print RED "\t\\_ cpkeyclt found in $cron_spool_file file \n\t\t \\_ [ $_ ]\n" unless ( !$verbose ); push( @WARNINGS, "cpkeyclt found in $cron_spool_file file \n\t\t \\_ [ $_ ]" ); $CronPositive = 1; } } close($fh); } my @etc_cron_files = glob(q{/etc/cron.*/*}); foreach my $etc_cron_file (@etc_cron_files) { chomp($etc_cron_file); open( my $fh, '<', "$etc_cron_file" ); while (<$fh>) { chomp($_); if ( $_ =~ m/cpkeyclt/ ) { print RED "\t\\_ $_ in $etc_cron_file\n" unless ( !$verbose ); push( @WARNINGS, "Found $_ in $etc_cron_file" ); $CronPositive = 1; } } close($fh); } open( my $fh, '<', '/etc/crontab' ); while (<$fh>) { chomp($_); if ( $_ =~ m/cpkeyclt/ ) { print RED "\t\\_ cpkeyclt found in /etc/crontab\n" unless ( !$verbose ); push( @WARNINGS, "cpkeyclt found in some /etc/crontab" ); $CronPositive = 1; } } close($fh); if ( !$CronPositive ) { print BOLD GREEN "\t \\_ None\n" unless ( !$verbose ); } } sub check_for_cpnat { print_working("Checking for existence of cpnat file (1:1 NAT)") unless ( !$verbose ); if ( -e ("/var/cpanel/cpnat") ) { open( my $fh, '<', "/var/cpanel/cpnat" ); my @CPNAT = <$fh>; close($fh); print BOLD GREEN "\t\\_ Found one - contents are:\n" unless ( !$verbose ); foreach my $cpnatline (@CPNAT) { chomp($cpnatline); print BOLD CYAN "\t\t\\_ " . $cpnatline . "\n" unless ( !$verbose ); my ( $publicIP, $privateIP ) = ( split( /\s+/, $cpnatline ) ); chomp($publicIP); chomp($privateIP); if ( $publicIP eq $privateIP ) { push( @WARNINGS, "cpnat file found but all publicly routeable addresses are the same as the local address - Not a NAT system" ); } } } else { print BOLD GREEN "\t\\_ None\n" unless ( !$verbose ); } } sub check_root_servers { print_working("Checking if this server can resolve ROOT servers:") unless ( !$verbose ); my @ROOT = qw( a b c d e f g h i j k l m ); my $ROOT_HOST = ".root-servers.net"; foreach my $rootserver (@ROOT) { $rootserver = $rootserver . $ROOT_HOST; my $GOOD = qx[ dig +tries=2 +time=5 $rootserver +short 2> /dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) if ($GOOD) { print_OK("\t \\_ $rootserver - Good!\n") unless ( !$verbose ); } else { print_warn("\t \\_ $rootserver - Failed!") unless ( !$verbose ); push( @WARNINGS, "Failed to resolve $rootserver" ); } } } sub check_cpsources_conf_file { return unless( -e '/etc/cpsources.conf' ); open( my $fh, '<', '/etc/cpsources.conf' ); while( <$fh> ) { chomp; if ( $_ =~ /MYIP/ ) { push( @WARNINGS, "Found invalid MYIP line within /etc/cpsources.conf file." ) if ( $_ =~ m/127.0.0.1/ ); } } } sub check_routing { return if ( !$verbose ); print_working("Displaying IP routing info (if any)"); my @ROUTINGINFO = qx[ ip addr | grep 'inet ' 2> /dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) foreach my $routeline (@ROUTINGINFO) { chomp($routeline); my ($internal1) = ( split( /\s+/, $routeline ) )[2]; my ($internal) = ( split( /\//, $internal1 ) )[0]; if ( $internal eq "127.0.0.1" ) { next; } print BOLD MAGENTA ON_BLACK . "\t \\_ Internal: " . CYAN $internal; my $external = qx[ wget -O - -q --tries=1 --timeout=2 --bind-address=$internal http://myip.cpanel.net/v1.0/ 2> /dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) if ($external) { chomp($external); print BOLD MAGENTA ON_BLACK " / External: " . CYAN $external . "\n"; } else { print RED " / No Reply!\n"; } } } sub check_auth_cpanel_resolution { print_working("Checking to see if auth.cpanel.net can resolve: ") unless ( !$verbose ); my @AUTH_RESOLUTION = qx[ dig +tries=2 +time=5 auth.cpanel.net +short 2>/dev/null ]; ## no critic (Cpanel::ProhibitQxAndBackticks) my $cnt = @AUTH_RESOLUTION; if ( $cnt == 0 ) { print RED "\t\\_ Failed!\n" unless ( !$verbose ); push( @WARNINGS, "This server cannot reach or resolve auth.cpanel.net" ); } else { foreach my $authline (@AUTH_RESOLUTION) { chomp($authline); print BOLD GREEN "\t\\_ " . $authline . "\n" unless ( !$verbose ); } } } sub check_firewall { print_working("Checking if cPanel IP's (208.74.x.x) are blocked") unless ( !$verbose ); my $has_nft = Cpanel::FindBin::findbin('nft'); my $cpanel_auth_ips = Cpanel::SafeRun::Timed::timedsaferun( 6, 'dig', '+short', 'auth.cpanel.net' ); my @cpanel_auth_ips = split /\n/, $cpanel_auth_ips; my $firewall_rules; if ( $has_nft ) { #$firewall_rules = Cpanel::SafeRun::Timed::timedsaferun( 0, 'nft', 'list', 'ruleset' ); $firewall_rules = Cpanel::SafeRun::Errors::saferunnoerror( 0, 'nft', 'list', 'ruleset' ); } else { #$firewall_rules = Cpanel::SafeRun::Timed::timedsaferun( 0, 'iptables', '-L', '-n' ); $firewall_rules = Cpanel::SafeRun::Errors::saferunnoerror( 0, 'iptables', '-L', '-n' ); } my @firewall_rules = split /\n/, $firewall_rules; my $ip_blocked = 0; my $port_blocked = 0; foreach my $cpanel_auth_ip (@cpanel_auth_ips) { chomp($cpanel_auth_ip); foreach my $ip_rule (@firewall_rules) { chomp($ip_rule); if ( $ip_rule =~ m/$cpanel_auth_ip/ && $ip_rule =~ m/drop|reject|deny/i ) { $ip_blocked = 1; } if ( $ip_rule =~ m/2083|2082|2095|2096|2086|2087|2089/ && $ip_rule =~ m/drop|reject|deny/i ) { $port_blocked = 1; } } } if ($ip_blocked) { print RED "\t\\_ cPanel IP's may be blocked via IPTABLES/NFT\n" unless ( !$verbose ); push( @WARNINGS, "cPanel IP's may be blocked via IPTABLES/NFT" ); } else { print BOLD GREEN "\t\\_ Looks good!\n" unless ( !$verbose ); } if ($port_blocked) { print RED "\t\\_ Some cPanel ports may be blocked via IPTABLES/NFT\n" unless ( !$verbose ); push( @WARNINGS, "cPanel ports may be blocked via IPTABLES/NFT Can cause cpsrvd to restart too often!" ); } # Check NAT table too my $firewall_nat_rules; if ( $has_nft ) { #$firewall_nat_rules = Cpanel::SafeRun::Timed::timedsaferun( 0, 'nft', 'list', 'ruleset' ); $firewall_nat_rules = Cpanel::SafeRun::Errors::saferunnoerror( 0, 'nft', 'list', 'ruleset' ); } else { #$firewall_nat_rules = Cpanel::SafeRun::Timed::timedsaferun( 0, 'iptables', '-L', '-t', 'nat' ); $firewall_nat_rules = Cpanel::SafeRun::Errors::saferunnoerror( 0, 'iptables', '-L', '-t', 'nat' ); } my @firewall_nat_rules = split /\n/, $firewall_nat_rules; foreach my $ip_rule (@firewall_nat_rules) { chomp($ip_rule); next unless( $ip_rule =~ m{type nat} ); if ( $ip_rule =~ m/REDIRECT|REDSOCKS/i && $ip_rule =~ m/2089|208.74.120./ ) { print RED "\t\\_ Found a proxy redirect in the firewall (nat)\n" unless ( !$verbose && i_am('cptech') ); push( @WARNINGS, "Found a proxy redirect in the firewall (nat)" ) unless ( i_am('cptech') ); $TSE4 = 1; } } } sub check_other_ports { print_working("Checking firewall (if license ports can access auth.cpanel.net)") unless ( !$verbose ); eval "use IO::Socket::PortState qw( check_ports )"; ## no critic (ProhibitStringyEval) my %port_hash = ( tcp => { 2089 => {}, 80 => {}, 110 => {}, 143 => {}, 25 => {}, 23 => {}, 993 => {}, 995 => {}, } ); my $timeout = 5; my $authhost = 'auth.cpanel.net'; chomp($authhost); my $host_hr = check_ports( $authhost, $timeout, \%port_hash ); for my $port ( sort { $a <=> $b } keys %{ $host_hr->{tcp} } ) { my $yesno = $host_hr->{tcp}{$port}{open} ? GREEN "OK" : RED "Failed!"; chomp($yesno); if ( $yesno =~ m/Failed!/ ) { if ( $port eq "2089" ) { $trCheck = 0; } push( @WARNINGS, "Could not reach port $port on auth.cpanel.net from this server -- connections may be throttled." ); } print_OK( "\t \\_ " . $port . " - " . $yesno . "\n" ) unless ( !$verbose ); } } sub check_cpanel_lisc_file { if ( !-e ("$cPLiscFile") ) { push( @WARNINGS, "cpanel.lisc file is missing" ); return; } print_working("Checking permissions on cpanel.lisc file: ") unless ( !$verbose ); my $statmode = Cpanel::SafeRun::Timed::timedsaferun( 4, 'stat', '-c', '%a', "$cPLiscFile" ); chomp($statmode); if ( $statmode != 644 ) { push( @WARNINGS, "Invalid permissions on /usr/local/cpanel/cpanel.lisc [" . $statmode . "] - Should be 644" ); } else { print BOLD GREEN "\t\\_ OK!\n" unless ( !$verbose ); } my $age; $age = ( stat("$cPLiscFile") )[9]; my $TimeDiff = $timenow - $age; if ( $TimeDiff > 604800 ) { push( @WARNINGS, "$cPLiscFile file is older than 7 days!" ); } my $attr = Cpanel::SafeRun::Timed::timedsaferun( 4, '/usr/bin/lsattr', "$cPLiscFile" ); if ( $attr =~ m/^\s*\S*[ai]/ ) { $TSE4 = 1; push( @WARNINGS, "$cPLiscFile is immutable" ) unless ( !iam('cptech') ); } print_working("Checking if cpanel.lisc can be locked: ") unless ( !$verbose ); my $lockable = Cpanel::SafeRun::Timed::timedsaferun( 0, 'flock', '-w', '5', "$cPLiscFile", '-c', 'echo 1 || echo 0' ); if ($lockable) { print BOLD GREEN "\t\\_ Yes\n" unless ( !$verbose ); } else { print RED "\t\\_ No\n" unless ( !$verbose ); $TSE4 = 1; push( @WARNINGS, "Could not obtain lock on $cPLiscFile" ) unless ( !iam('cptech') ); } } sub run_rdate { return if ( $envtype eq "virtuozzo" ); return if ( $envtype eq "vzcontainer" ); return unless ( -x "/usr/local/cpanel/bin/rdate" ); print_working("Checking if rdate completes without error:") unless ( !$verbose ); my $rdatesuccess = Cpanel::SafeRun::Timed::timedsaferun( 5, '/usr/local/cpanel/bin/rdate', '-s', 'rdate.cpanel.net' ); if ($rdatesuccess) { print RED "\t\\_ Error - $rdatesuccess [ Make sure port 37 outbound is open ]\n" unless ( !$verbose ); push( @WARNINGS, "rdate command failed! [$rdatesuccess] [ Make sure port 37 outbound is open ]" ); } else { print BOLD GREEN "\t\\_ Yes\n" unless ( !$verbose ); } } sub chkCreds { return if ( -e ("/var/cpanel/licenseid_credentials.json") ); push( @WARNINGS, "licenseid_credentials.json file missing - is port 2083 inbound open?" ); } sub display_route { return if ( !$verbose ); print_working("Displaying route -n:"); print BOLD YELLOW "\t \\ \n"; system_formatted("route -n"); print_working("Displaying ip route:"); print BOLD YELLOW "\t \\ \n"; system_formatted("ip route"); print_working("Displaying ip addr show:"); print BOLD YELLOW "\t \\ \n"; system_formatted("ip addr show"); } sub get_cpsrvd_restarts { return if ( !$verbose ); print_working("Displaying last 20 cpsrvd restarts:"); my @CPSRVD_RESTARTS; open( my $fh, '<', '/usr/local/cpanel/logs/error_log' ); while (<$fh>) { if ( $_ =~ m/Restarting cpsrvd daemon process/ ) { push @CPSRVD_RESTARTS, $_; } } close($fh); my $total_lines = ( scalar @CPSRVD_RESTARTS > 20 ) ? 20 : @CPSRVD_RESTARTS; my @reversed = reverse(@CPSRVD_RESTARTS); my $cnt = 0; while ( $cnt < $total_lines ) { $reversed[$cnt] =~ s/\.//g; my ( $restartdate, $restarttime, $restartdst ) = ( split( /\s+/, $reversed[$cnt] ) )[ 0, 1, 2 ]; print YELLOW "\t \\_ " . $restartdate . " " . $restarttime . " " . $restartdst . "\n"; $cnt++; } print_working("Checking /root/.bash_history for restarts:"); my $found=0; foreach my $histline (@HISTORY) { chomp($histline); if ( $histline =~ m/cpanel\.service/ or $histline =~ m/scripts\/restartsrv_cpsrvd/ or $histline =~ m/service cpanel restart/ or $histline =~ m/etc\/init.d\/cpanel/ ) { print YELLOW "\t \\_ Found: " . BOLD GREEN $histline . "\n" unless ( $histline =~ m/grep/ ); $found=1; } if ( $histline =~ m/cgls/ ) { $TSE4 = 1; push( @WARNINGS, "Please update $wikilink" ) unless ( $cgls || !iam('cptech') ); $cgls = 1; } } if ( $found == 0 ) { print BOLD GREEN "\t \\_ None\n" unless ( !$verbose ); } } sub get_last_reboots { return if ( !$verbose ); print_working("Getting Last 20 reboots:"); my @LastReboots; my $Last = Cpanel::SafeRun::Timed::timedsaferun( 4, 'last' ); my @Last = split /\n/, $Last; foreach my $last_line (@Last) { chomp($last_line); if ( $last_line =~ m/reboot/ ) { push @LastReboots, $last_line; } } my $cnt = 0; my ( $kernel, $mday, $mon, $day, $hhmm ); foreach my $rebootline (@LastReboots) { chomp($rebootline); ( $kernel, $mday, $mon, $day, $hhmm ) = ( split( /\s+/, $rebootline ) )[ 3, 4, 5, 6, 7 ]; print YELLOW "\t \\_ Reboot detected with kernel $kernel On: $mday $mon $day $hhmm\n"; $cnt++; if ( $cnt >= 20 ) { last; } } } sub Usage { print WHITE "cplicensets [no options is default] displays basic info and any warnings found\n"; print WHITE "cplicensets --verbose displays all info\n"; print WHITE "cplicensets --withlogs displays last 50 lines of license_log file\n"; print WHITE "cplicensets --skipdate skips date check which can sometimes fail\n"; print WHITE "cplicensets --help You're looking at it!\n"; exit; ## no critic (NoExitsFromSubroutines) } sub read_last_50_lines_of_license_log { return unless ( -e $LICENSE_LOG ); print_working("Displaying last 50 lines of /usr/local/cpanel/logs/license_log:"); my $lineswanted = 50; my ( $line, $filesize, $seekpos, $numread, @lines ); open my $fh, '<', $LICENSE_LOG or die "Can't read license_log: $!\n"; $filesize = -s $LICENSE_LOG; $seekpos = 50 * $lineswanted; $numread = 0; while ( $numread < $lineswanted ) { @lines = (); $numread = 0; seek( $fh, $filesize - $seekpos, 0 ); <$fh> if $seekpos < $filesize; while ( defined( $line = <$fh> ) ) { push @lines, $line; shift @lines if ++$numread > $lineswanted; } if ( $numread < $lineswanted ) { if ( $seekpos >= $filesize ) { die "There aren't even $lineswanted lines in license_log - I got $numread\n"; } $seekpos *= 2; $seekpos = $filesize if $seekpos >= $filesize; } } close($fh); print CYAN @lines; } sub get_date { return if ($skipdate); my $servertime = Time::Piece->new; my $servertimeepoch = $servertime->epoch; my ($utctime) = ( split( /\./, Cpanel::SafeRun::Timed::timedsaferun( 5, '/usr/bin/curl', '-s', "https://cpaneltech.ninja/cgi-bin/date.pl" ) ) )[0]; if ( $utctime eq "" ) { ($utctime) = ( split( /\./, Cpanel::SafeRun::Timed::timedsaferun( 5, '/usr/bin/curl', '-sk', "https://cpaneltech.ninja/cgi-bin/date.pl" ) ) )[0]; } # RETURN IF $utctime is undefined - no need to go further). return if ( !defined($utctime) ); my $strutctime = scalar localtime($utctime); print_working("Checking Date/Time:") unless ( !$verbose ); print BOLD GREEN ON_BLACK "\t \\_ Server Time: " . $servertime . " (" . $servertimeepoch . ")\n" unless ( !$verbose ); print BOLD GREEN ON_BLACK "\t \\_ Outside Location: " . $strutctime . " (" . $utctime . ")\n" unless ( !$verbose ); my $timediff = $strutctime - $servertime; my $timewarn = "\n"; if ( $timediff < -5 or $timediff > 5 ) { $timewarn = " [ You should correct this to prevent licensing issues. ]\n"; my $prettytimediff = $timediff->pretty; print_warn("\t \\_ [WARN] - Date/Time is off by $prettytimediff $timewarn") unless ( !$verbose ); push( @WARNINGS, "Date/Time is off by $prettytimediff $timewarn" ); } else { print_OK("\t \\_ [OK] - Date/Time is within 5 seconds!\n") unless ( !$verbose ); } } sub check_for_solo { my $isSolomsg = ($isSolo) ? "Yes" : "No"; print_working( "Is this a SOLO license: " . CYAN $isSolomsg); } sub check_for_development { my $isDevelmsg; if ($package) { $isDevelmsg = ( $package =~ m/development/i ) ? "Yes" : "No"; } else { $isDevelmsg = "No"; } print_working( "Is this a DEVELOPMENT license: " . CYAN $isDevelmsg ); } sub check_for_onetime { my $isOneTimemsg; if ($package) { $isOneTimemsg = ( $package =~ m/one-time/i ) ? "Yes" : "No"; } else { $isOneTimemsg = "No"; } print_working( "Is this a ONE-TIME license: " . CYAN $isOneTimemsg ); } sub check_for_GCE { $isGCE_IP = getGCE_IPs($external_ip_address); my $isGCE = ($isGCE_IP) ? "Yes" : "No"; print BOLD YELLOW "Running on Google Compute Engine (GCE): " . CYAN $isGCE . "\n"; if ( $isGCE eq "Yes" ) { # What does Google say the hostname is set to? my $GCEHostname = Cpanel::SafeRun::Timed::timedsaferun( 6, 'curl', '-s', "http://metadata.google.internal/computeMetadata/v1/instance/attributes/hostname", '-H', "Metadata-Flavor: Google" ); chomp($GCEHostname); if ( $GCEHostname =~ m/Error|404|DOCTYPE/ ) { return; } else { if ($GCEHostname) { if ( $GCEHostname ne $HOSTNAME ) { push( @WARNINGS, "Google set the hostname to: $GCEHostname which does not match $HOSTNAME" ); } } } } } sub check_for_AWS { $isAWS_IP = getAWS_IPs($external_ip_address); my $isAWS = ($isAWS_IP) ? "Yes" : "No"; print BOLD YELLOW "Running on Amazon Web Services (AWS): " . CYAN $isAWS . "\n"; } sub _init_run_state { return if defined $RUN_STATE; $RUN_STATE = { STATE => 0, type => { cpanel => 1 << 0, solo => 1 << 1, dnsonly => 1 << 2, cptech => 1 << 3, }, }; return 1; } sub _set_run_type { my ($type) = @_; print STDERR "Runtime type ${type} doesn't exist\n" and return unless exists $RUN_STATE->{type}->{$type}; return $RUN_STATE->{STATE} |= $RUN_STATE->{type}->{$type}; } sub iam { ## no critic (RequireArgUnpacking) my $want = 0; grep { return 0 unless exists $RUN_STATE->{type}->{$_}; $want |= $RUN_STATE->{type}->{$_} } @_; return $want == ( $want & $RUN_STATE->{STATE} ); } sub CSA { my $dt = DateTime->now( time_zone => 'America/Chicago' ); my $DAY = $dt->strftime("%a"); my $HOUR = $dt->hour; my $STIME = 6; my $QTIME; if ( $DAY eq "Sat" or $DAY eq "Sun" ) { $QTIME = 16; } else { $QTIME = 18; } if ( $HOUR >= $STIME and $HOUR < $QTIME ) { return 1; ## Customer Services is available (Business Hours) } else { return 0; ## Customer Service is NOT available (after hours) } } sub proxy_chk { my @ports2chk = qw( 43467 13555 ); my $is_listening; foreach my $port (@ports2chk) { $is_listening = Cpanel::SafeRun::Timed::timedsaferun( 6, 'lsof', "-itcp:$port" ); if ($is_listening) { $TSE4 = 1; push( @WARNINGS, "Possible proxy found listening on port: $port" ) unless ( !iam('cptech') ); } } if ( -d "/root/pc4" ) { if ( -e "/root/pc4/libproxychains4.so" or -e "/root/pc4/proxychains4" or -e "/root/pc4/cpsys2.conf" or -e "/root/pc4/proxychains-ng-master" ) { $TSE4 = 1; push( @WARNINGS, "Possible proxy found /root/pc4 directory exists" ) unless ( !iam('cptech') ); } } if ( -d "/root/redsocks" ) { $TSE4 = 1; push( @WARNINGS, "Possible proxy found /root/redsocks directory exists" ) unless ( !iam('cptech') ); } return if ( !-e "/usr/bin/traceroute" ); return if ( !$trCheck ); my $troute = qx[ traceroute -T -p 2089 auth.cpanel.net 2>/dev/null | grep -v 'traceroute to' | wc -l ]; ## no critic (Cpanel::ProhibitQxAndBackticks) if ( $troute < 3 ) { $TSE4 = 1; push( @WARNINGS, "Possible proxy found - (traceroute test)" ) unless ( !iam('cptech') ); } } sub chk_for_cloud_ready { return if ( -e "/usr/local/cpanel/.cloud-ready" ); my $messfile = ( $distro eq "ubuntu" ) ? "/var/log/kern.log" : "/var/log/messages"; return if (! -e $messfile ); open( my $fh, '<', "$messfile" ); while (<$fh>) { if ( $_ =~ m/rc.local/ && $_ =~ m/Unknown error/ ) { push( @WARNINGS, ".cloud-ready touchfile not found under /usr/local/cpanel. Is hostname reverting? [NO-1573]" ); last; } } close($fh); } sub get_hostname { my $h = Cpanel::SafeRun::Timed::timedsaferun( 10, 'hostname', '-f' ); chomp $h if defined $h; if ( not length($h) ) { # Fall back to Sys::Hostname $h = hostname(); } return $h; } sub usercount { my $totUsers = Cpanel::Config::LoadUserDomains::counttrueuserdomains(); ## no critic (ProhibitCallsToUnexportedSubs) return $totUsers; } sub allowed { my $allowedJSON = get_whmapi1('get_maximum_users'); my $totAllowed = $allowedJSON->{data}->{maximum_users}; return $totAllowed; } sub getProfileNode { my $ProfileNodeJSON = get_whmapi1('get_current_profile'); my $ProfileNode = $ProfileNodeJSON->{data}->{name}; return $ProfileNode; } sub get_json_from_command { my @cmd = @_; return Cpanel::JSON::Load( Cpanel::SafeRun::Timed::timedsaferun( 30, @cmd ) ); ## no critic (ProhibitCallsToUnexportedSubs) } sub get_whmapi1 { ## no critic (RequireArgUnpacking) return get_json_from_command( 'whmapi1', '--output=json', @_ ); } sub chk_cgls3 { my $attr = Cpanel::SafeRun::Timed::timedsaferun( 5, '/usr/bin/lsattr', '/usr/local/cpanel/cpkeyclt' ); if ( $attr =~ m/^\s*\S*[ai]/ ) { $TSE4 = 1; push( @WARNINGS, "Please update $wikilink" ) unless ( $cgls || !iam('cptech') ); push( @WARNINGS, "\t/usr/local/cpanel/cpkeyclt is set to [IMMUTABLE/APPEND-ONLY]" ) unless ( $cgls || !iam('cptech') ); $cgls = 1; } } sub check_for_license_status_json { return if !-e "/var/cpanel/license.status.json"; return if $TSE4; my $licstatus; if ( open my $file_fh, '<', "/var/cpanel/license.status.json" ) { while (<$file_fh>) { $licstatus = decode_json $_; } close($file_fh); } return unless ( defined $licstatus->{code} ); return if ( $licstatus->{code} == 200 ); if ( $licstatus->{code} == 600 ) { # Locked License - Send to CS during business hours. $CS = 1; } elsif ( $licstatus->{code} == 666 ) { # STANDARD (requires metal license not VPS license - Send to CS $CS = 1; } else { # All other codes (other than 200, 600 or 666) $TSE4 = 1; } print_working( 'Last cpkeyclt check: ' . BOLD RED "(" . $licstatus->{code} . ") " . $licstatus->{message} ); push( @WARNINGS, "Last cpkeyclt check: (" . $licstatus->{code} . ") " . $licstatus->{message} ); return; } sub ignoreHashes { my $FileToIgnore = shift; return if -d $FileToIgnore; my ($HashToIgnore) = ( split( /\s+/, Cpanel::SafeRun::Timed::timedsaferun( 5, 'sha256sum', "$FileToIgnore" ) ) ); my @hashes2ignore = qw( c9dd336748b4fc2ab4bac2cb5a4690e13e03eb64d51cd000584e6da253145d11 0290562d8299414dfb276d534000d122dbc1c514f49ca7ca0757ddd519880636 ); return 1 if ( grep { /$HashToIgnore/ } @hashes2ignore ); return 0; }