#!/usr/local/cpanel/3rdparty/bin/perl # Copyright 2014 - 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: acctinfo # PURPOSE: Get as much information for a username or domain entered at command line as possible. # AUTHOR: Peter Elsner # CREATED: 06/17/2007 # CURRENT MAINTAINER: Peter Elsner use strict; my $VERSION = "2.5.43"; use Getopt::Long; use Cpanel::MysqlUtils; use Term::ANSIColor qw(:constants); use Cpanel::FindBin (); use Cpanel::Sys::Hostname (); use Cpanel::Config::LoadCpUserFile (); use Cpanel::Config::LoadUserOwners (); use Cpanel::Config::LoadUserDomains (); use Cpanel::Config::Users (); use Cpanel::Config::LoadWwwAcctConf (); use Cpanel::Config::LoadCpConf (); use Cpanel::SafeRun::Timed (); use Cpanel::JSON (); use JSON::MaybeXS qw(encode_json decode_json); use Cpanel::ResellerFunctions (); use Cpanel::Usage (); use Cpanel::Validate::IP (); use Cpanel::Version (); use Cpanel::PwCache (); use Cpanel::LoginDefs (); use Cpanel::Config::CpUser (); use Cpanel::PwCache (); use Cpanel::PwCache::Get (); use LWP::UserAgent; use File::Basename; use Time::Piece; use Time::Seconds; use String::Random; #use Path::Iterator::Rule; #use NetAddr::IP; use Math::Round; use NetAddr::IP; use DateTime; use Date::Parse; use Text::Tabs; $tabstop = 4; use Net::DNS; use Socket; use DBI; use integer; #use Cpanel::CountryCodes; our $sslsyscertdir; our $stderr; our $stdout; our $OPT_TIMEOUT; our $success; our $command; our $sslsubject; our $startdate; our $expiredate; our $isExpired; our $isSelfSigned; our $result; our @FilesToScan; our @InstalledMods = timed_run_noerr( 0, 'httpd', '-M' ); our $RUID2Enabled; our $ProfileNode; $Term::ANSIColor::AUTORESET = 1; my $all = undef; my $listdbs = undef; my $listssls = undef; my $listsubs = undef; my $listaddons = undef; my $listparked = undef; my $listaliased = undef; my $domains = undef; my $reselleraccts = undef; my $resellerperms = undef; my $resellerprivs = undef; my $chk_cph_blocks = undef; my $helpME = undef; my $SearchFor = undef; my $cruft = undef; my $mail = undef; my $showHeader = undef; my $tomcat = undef; my $scan = undef; my $wpinfo = undef; my $showpass = undef; my $acct_over_quota = 0; our $isInNamedConf = 0; our $spincounter; our @HasCAA; GetOptions( 'listdbs' => \$listdbs, 'listssls' => \$listssls, 'domains' => \$domains, 'listsubs' => \$listsubs, 'listaddons' => \$listaddons, 'listaliased' => \$listaliased, 'listparked' => \$listaliased, 'reselleraccts' => \$reselleraccts, 'resellerperms' => \$resellerperms, 'resellerprivs' => \$resellerperms, 'all' => \$all, 'help' => \$helpME, 'cruft' => \$cruft, 'mail' => \$mail, 'header' => \$showHeader, 'cphulkblocks' => \$chk_cph_blocks, 'tomcat' => \$tomcat, 'scan' => \$scan, 'wpinfo' => \$wpinfo, 'showpass' => \$showpass, ); if ( $> != 0 ) { die "Sorry, acctinfo script can only be run as root\n"; } my $debugHookJSON = get_whmapi1( 'get_tweaksetting', 'key=debughooks' ); my $debugHookVal = $debugHookJSON->{data}->{tweaksetting}->{value}; my $debugHookChanged = 0; if ( $debugHookVal eq "logall" ) { # Temporarily set it to logdata to prevent interference in output get_whmapi1( 'set_tweaksetting', 'key=debughooks', 'value=logdata' ); $debugHookChanged = 1; } print BOLD BLUE "acctinfo - Version: " . YELLOW $VERSION . "\n"; if ($helpME) { Usage(); } getProfileNode(); my $conf = Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf(); #my $HOMEDIR = $conf->{'HOMEDIR'}; my $HOMEDIR; my $HOMEMATCH = $conf->{'HOMEMATCH'}; my $SERVER_IP = $conf->{'ADDR'}; my $NS1 = $conf->{'NS'}; my $NS2 = $conf->{'NS2'}; my $NS3 = $conf->{'NS3'}; my $NS4 = $conf->{'NS4'}; my $cpconf = Cpanel::Config::LoadCpConf::loadcpconf(); my $DBPrefix = $cpconf->{'database_prefix'}; my $ACLSEnabled = $cpconf->{'acls'}; my ( $os_release, $os_ises ) = get_release_version(); my $IS_USERNAME = 1; my $QUERY = @ARGV[0]; chomp($QUERY); if ( $QUERY eq "" ) { Usage(); } $QUERY = lc($QUERY); if ( index( $QUERY, '.' ) != -1 ) { $IS_USERNAME = 0; } my $MAINDOMAIN; my $HOSTNAME = Cpanel::Sys::Hostname::gethostname(); my $username = $QUERY; if ( $IS_USERNAME == 0 ) { # FULL DOMAIN NAME ENTERED my $xwww = substr( $QUERY, 0, 4 ); ## Strip www. if entered! if ( $xwww eq "www." ) { $QUERY = substr( $QUERY, 4 ); } my $DataJSON = get_whmapi1( 'getdomainowner', "domain=$QUERY" ); $username = $DataJSON->{data}->{user}; } my $ValUserJSON = get_whmapi1( 'validate_system_user', "user=$username" ); my $isReserved = $ValUserJSON->{data}->{reserved}; my $ASreason = $ValUserJSON->{metadata}->{reason}; my $UserExists = $ValUserJSON->{data}->{exists}; if ($isReserved) { print RED "[WARN] You entered a reserved Username [ $username ]\n"; print YELLOW "Some data may be missing from cPanel configuration files...\n"; print WHITE "See https://docs.cpanel.net/knowledge-base/accounts/reserved-invalid-and-misconfigured-username/\n"; } my $DataJSON = get_whmapi1( 'accountsummary', "user=$username" ); my $systemUser = $DataJSON->{metadata}->{result}; if ( $IS_USERNAME && !$systemUser ) { $MAINDOMAIN = ""; $username = $QUERY; } else { $username = $DataJSON->{data}->{acct}->[0]->{user}; $MAINDOMAIN = $DataJSON->{data}->{acct}->[0]->{domain}; $HOMEDIR = "/" . $DataJSON->{data}->{acct}->[0]->{partition}; my $quotaused = $DataJSON->{data}->{acct}->[0]->{diskused}; my $maxquota = $DataJSON->{data}->{acct}->[0]->{disklimit}; } my $UserSuspended = $DataJSON->{data}->{acct}->[0]->{suspended}; my $ASreason = $DataJSON->{metadata}->{reason}; my $UserExists = $ValUserJSON->{data}->{exists}; if ( !$UserExists or $ASreason ne "OK" ) { if ($cruft) { cruft_check(); } print RED "Error - $QUERY not found on $HOSTNAME\n"; print YELLOW "Try using the --cruft switch (acctinfo $QUERY --cruft)\n"; if ($debugHookChanged) { get_whmapi1( 'set_tweaksetting', 'key=debughooks', 'value=logall' ); } exit; } my $isReserved = $ValUserJSON->{data}->{reserved}; if ($isReserved) { if ( $username eq "" and $IS_USERNAME ) { $username = $QUERY; } print RED "\n[WARN] - " . YELLOW $QUERY . WHITE " is tied to a reserved username! " . BOLD CYAN "(some info may be missing from some files)\n"; if ($username) { my $RandomString = new String::Random; my $SuggestedUser = $RandomString->randpattern("cccccc"); print YELLOW "You can use the following API call to change $username to for example: $username$SuggestedUser\n"; print BOLD MAGENTA "/usr/sbin/whmapi1 modifyacct user=$username newuser=$username$SuggestedUser\n"; } } my $username = $DataJSON->{data}->{acct}->[0]->{user}; my $MAINDOMAIN = $DataJSON->{data}->{acct}->[0]->{domain}; my $quotaused = $DataJSON->{data}->{acct}->[0]->{diskused}; my $maxquota = $DataJSON->{data}->{acct}->[0]->{disklimit}; if ($scan) { scan(); if ($debugHookChanged) { get_whmapi1( 'set_tweaksetting', 'key=debughooks', 'value=logall' ); } exit; } # Load /var/cpanel/users/$username into (used for FEATURELIST only right now - see CPANEL-30573) my $user_conf = Cpanel::Config::LoadCpUserFile::load($username); my $DOMAIN = ""; my $IS_PARKED = ""; my $IS_ADDON = ""; my $IS_SUB = ""; my $maindocroot = ""; my ( $subdocroot, $addondocroot, $parkeddocroot ); my @OtherDomains = ""; my @SUBDOMAINS = ""; my @ADDONDOMAINS = ""; my @PARKEDDOMAINS = ""; my @users_domains = ( $user_conf->{'DOMAIN'}, $user_conf->{'DOMAINS'} ? @{ $user_conf->{'DOMAINS'} } : () ); push @OtherDomains, $MAINDOMAIN; my $DomainInfoJSON = get_whmapi1('get_domain_info'); my $DomainInfoLines; for $DomainInfoLines ( @{ $DomainInfoJSON->{data}->{domains} } ) { if ( $DomainInfoLines->{user} eq $username ) { if ( $DomainInfoLines->{domain_type} eq "sub" ) { push( @OtherDomains, $DomainInfoLines->{domain} ); push( @SUBDOMAINS, $DomainInfoLines->{domain} ); my $docroot_status = ""; $docroot_status = RED " [MISSING]" unless ( -d $DomainInfoLines->{docroot} ); $IS_SUB = expand( "$DomainInfoLines->{domain} is a sub domain of $MAINDOMAIN \n\t\\_ " ) . YELLOW . "(DocumentRoot: " . WHITE $DomainInfoLines->{docroot} . ") $docroot_status\n" unless ( $DomainInfoLines->{domain} ne $QUERY ); $subdocroot = $DomainInfoLines->{docroot} unless ( $DomainInfoLines->{domain} ne $QUERY ); } if ( $DomainInfoLines->{domain_type} eq "addon" ) { push( @OtherDomains, $DomainInfoLines->{domain} ); my $docroot_status = ""; $docroot_status = RED " [MISSING]" unless ( -d $DomainInfoLines->{docroot} ); push( @ADDONDOMAINS, $DomainInfoLines->{domain} . "\n\t\t \\_ [ Sub: " . $DomainInfoLines->{parent_domain} . " ]" . "\n\t\t \\_ [ DocumentRoot: " . $DomainInfoLines->{docroot} . " ] $docroot_status" ); $IS_ADDON = expand( "$DomainInfoLines->{domain} is an AddOn domain of $MAINDOMAIN \n\t\\_ " ) . YELLOW . "(DocumentRoot: " . WHITE $DomainInfoLines->{docroot} . ") $docroot_status\n" unless ( $DomainInfoLines->{domain} ne $QUERY ); $addondocroot = $DomainInfoLines->{docroot} unless ( $DomainInfoLines->{domain} ne $QUERY ); } if ( $DomainInfoLines->{domain_type} eq "parked" ) { push( @OtherDomains, $DomainInfoLines->{domain} ); push( @PARKEDDOMAINS, $DomainInfoLines->{domain} ); my $docroot_status = ""; $docroot_status = RED " [MISSING]" unless ( -d $DomainInfoLines->{docroot} ); $IS_PARKED = expand( "$DomainInfoLines->{domain} is an alias (parked) domain of $MAINDOMAIN\n\t\\_ " ) . YELLOW . "(DocumentRoot: " . WHITE $DomainInfoLines->{docroot} . ") $docroot_status\n" unless ( $DomainInfoLines->{domain} ne $QUERY ); $parkeddocroot = $DomainInfoLines->{docroot} unless ( $DomainInfoLines->{domain} ne $QUERY ); } if ( $DomainInfoLines->{domain_type} eq "main" ) { $maindocroot = $DomainInfoLines->{docroot}; } } } shift @SUBDOMAINS; shift @ADDONDOMAINS; shift @PARKEDDOMAINS; shift @OtherDomains; my $subcnt = @SUBDOMAINS; my $addoncnt = @ADDONDOMAINS; my $parkcnt = @PARKEDDOMAINS; my $othercnt = @OtherDomains; if ($cruft) { cruft_check(); } my $PACKAGE = $DataJSON->{data}->{acct}->[0]->{plan}; my $THEME = $DataJSON->{data}->{acct}->[0]->{theme}; my $IPADDR = $user_conf->{'IP'}; my $CHILD_WORKLOADS = $user_conf->{'CHILD_WORKLOADS'}; my $BACKUPENABLED = $DataJSON->{data}->{acct}->[0]->{backup}; my $MAXADDON = $DataJSON->{data}->{acct}->[0]->{maxaddons}; my $MAXPARK = $DataJSON->{data}->{acct}->[0]->{maxparked}; my $MAXSUB = $DataJSON->{data}->{acct}->[0]->{maxsub}; my $MAXFTP = $DataJSON->{data}->{acct}->[0]->{maxftp}; my $MAXSQL = $DataJSON->{data}->{acct}->[0]->{maxsql}; my $MAXLST = $DataJSON->{data}->{acct}->[0]->{maxlst}; my $SUSPEND_TIME = $DataJSON->{data}->{acct}->[0]->{suspendtime}; my $FEATURELIST = $user_conf->{'FEATURELIST'}; my $STARTDATE = $DataJSON->{data}->{acct}->[0]->{startdate}; my @ResolvedIP; my $ResolvedIP; my $HAS_AUTOSSL = "No"; my $HAS_AUTOSSLFEATURE = get_whmapi1( 'verify_user_has_feature', "user=$username", 'feature=autossl' ); if ( $HAS_AUTOSSLFEATURE->{data}->{has_feature} ) { $HAS_AUTOSSL = "Yes"; } my $HAS_AUTOSSL_TEXT; my $AutoSSL_Disabled = 0; my $AutoSSL_DisabledJSON = get_whmapi1( 'get_featurelist_data', 'featurelist=disabled' ); for my $AutoSSL_DisabledID ( @{ $AutoSSL_DisabledJSON->{data}->{features} } ) { if ( $AutoSSL_DisabledID->{id} eq "autossl" ) { $AutoSSL_Disabled = $AutoSSL_DisabledID->{value}; last; } } if ( !$AutoSSL_Disabled ) { $HAS_AUTOSSL_TEXT = "(AutoSSL disabled in the \"disabled\" feature list)"; } my $AutoSSL_Disabled; if ( !( -e ("/var/cpanel/features/$FEATURELIST") ) ) { $AutoSSL_Disabled = "$FEATURELIST not found in /var/cpanel/features!"; $HAS_AUTOSSL_TEXT = RED "[WARN] - Missing from the /var/cpanel/features/ directory"; } else { my $AutoSSL_DisabledJSON = get_whmapi1( 'get_featurelist_data', "featurelist=$FEATURELIST" ); for my $AutoSSL_DisabledID ( @{ $AutoSSL_DisabledJSON->{data}->{features} } ) { if ( $AutoSSL_DisabledID->{id} eq "autossl" ) { $AutoSSL_Disabled = $AutoSSL_DisabledID->{value}; last; } } if ( !$AutoSSL_Disabled ) { $HAS_AUTOSSL_TEXT = "(AutoSSL disabled in the \"$FEATURELIST\" feature list)"; } } if ( $HAS_AUTOSSL_TEXT eq "" ) { $HAS_AUTOSSL_TEXT = "(AutoSSL enabled in the \"$FEATURELIST\" feature list)"; } $BACKUPENABLED = ($BACKUPENABLED) ? "Yes" : "No"; if ($IS_USERNAME) { $ResolvedIP = timed_run( 4, 'dig', '@8.8.8.8', "$MAINDOMAIN", 'A', '+short' ); } else { $ResolvedIP = timed_run( 4, 'dig', '@8.8.4.4', "$QUERY", 'A', '+short' ); } @ResolvedIP = split "\n", $ResolvedIP; my $IPTYPE = ""; if ( $IPADDR eq $SERVER_IP ) { $IPTYPE = "shared"; } else { $IPTYPE = "dedicated"; } my $ip_on_localhost=0; if ( $IPADDR eq '127.0.0.1' ) { $IPTYPE = RED "[WARN] - $username\'s IP address is configured to localhost!"; $ip_on_localhost=1; } my $REAL_OWNER = $DataJSON->{data}->{acct}->[0]->{owner}; my $RO_TEXT = ""; if ( $REAL_OWNER ne $username and $REAL_OWNER ne "root" ) { $RO_TEXT = " (Which is under the reseller: $REAL_OWNER)"; } # Check if main domain (username) is a reseller. my @ACCTSOWNEDBYRESELLER = undef; my @SORTEDRESELLERACCTS = undef; my @LISTOFACCTS = undef; my $Is_Reseller = 0; my $ResellerAcctsCnt = 0; my $ResellerDomain = ""; my $vcu_account = ""; my $ResellersAcct = ""; my $RESELLER = ""; my $FOUND = ""; my $ResellerSharedIP = "None"; my @ResellerIPS = undef; my $ResellerIP = "None"; my $ResellerIPS = "None"; my @ALL_RESELLERS = Cpanel::ResellerFunctions::getresellerslist(); unshift @ALL_RESELLERS, 'root'; foreach $RESELLER (@ALL_RESELLERS) { chomp($RESELLER); if ( $RESELLER eq $username ) { $Is_Reseller = 1; # Grab resellers shared IP (if configured) from /var/cpanel/mainips/$RESELLER. if ( -e ("/var/cpanel/mainips/$RESELLER") ) { $ResellerSharedIP = timed_run( 2, 'cat', "/var/cpanel/mainips/$RESELLER" ); chomp($ResellerSharedIP); } if ( -e ("/var/cpanel/dips/$RESELLER") ) { open( RESELLERIPS, "/var/cpanel/dips/$RESELLER" ); @ResellerIPS = ; close(RESELLERIPS); } } } my $TOTAL_DOMAINS = Cpanel::Config::LoadUserDomains::counttrueuserdomains(); chomp($TOTAL_DOMAINS); print WHITE "There are " . YELLOW $TOTAL_DOMAINS . WHITE " total accounts on (" . GREEN ON_BLACK $HOSTNAME . WHITE ")\n"; if ($IS_USERNAME) { print "\n"; } else { print GREEN ON_BLACK "\nThe username for " . BRIGHT_BLUE $QUERY . GREEN ON_BLACK " is: " . YELLOW $username . "\n"; } if ( $CHILD_WORKLOADS ne "" ) { print "$username is a child node on this server. Please run acctinfo on the parent server\n"; exit; } my $user_child_nodesJSON = get_whmapi1('list_user_child_nodes'); my $linked_nodes; for my $child_nodes ( @{ $user_child_nodesJSON->{data}->{payload} } ) { if ( $child_nodes->{user} eq $username ) { $linked_nodes = YELLOW "The " . CYAN $child_nodes->{user} . YELLOW " account is linked to the: " . CYAN $child_nodes->{alias} . YELLOW " server for type: " . CYAN $child_nodes->{type}; } } # Get home directory from /etc/passwd our $SSLProvider = getSSLProvider(); my $RealHome = Cpanel::PwCache::gethomedir($username); my $OnCloud=($RealHome =~ m{remote-storage}) ? 1 : 0; my $RealShell = Cpanel::PwCache::Get::getshell($username); my $UID = Cpanel::PwCache::Get::getuid($username); my $GID = Cpanel::Config::CpUser::get_cpgid($username); my $UID_MIN = Cpanel::LoginDefs::get_uid_min(); my $GID_MIN = Cpanel::LoginDefs::get_gid_min(); if ( $UID < $UID_MIN or $GID < $GID_MIN ) { print RED "[WARN] - UID/GID for $username is less than $UID_MIN/$GID_MIN as set in /etc/login.defs\n"; } # Check for missing hash in /etc/shadow my $PWHashLine = timed_run( 2, 'grep', "^$username", '/etc/shadow' ); my ($PWHash) = ( split( /:/, $PWHashLine ) )[1]; if ( $PWHash eq "" ) { print RED "[WARN] * $username is missing password hash in /etc/shadow\n"; } print GREEN ON_BLACK "The main domain is " . YELLOW $MAINDOMAIN . GREEN ON_BLACK $RO_TEXT . "\n"; if ( $QUERY eq $HOSTNAME ) { print RED "[WARN] - $QUERY is the same as hostname $HOSTNAME! (Can cause email issues such as User not found)\n"; } my $LastLoginIP; if ( -e $RealHome && !$UserSuspended ) { my $LastLoginIPdata = get_uapi( 'LastLogin', 'get_last_or_current_logged_in_ip', "--user=$username" ); $LastLoginIP = $LastLoginIPdata->{result}->{data}; chomp($LastLoginIP); if ( $LastLoginIP eq "''" ) { $LastLoginIP = ""; } } print expand( "\t\\_ " . YELLOW . "(DocumentRoot: " . WHITE $maindocroot . ")\n" ); if ( $maindocroot eq "" ) { print expand( RED "\t \\_ [WARN] - DocumentRoot is blank!\n" ); } print WHITE "Real Home Directory (/etc/passwd): " . CYAN $RealHome . YELLOW " - Checking permissions..."; print "\n"; checkperms2() unless( $UserSuspended ); securitychk() unless( $UserSuspended ); # Check if user is in demo mode open( DEMO, "/etc/demousers" ); my @DEMOUSERS = ; close(DEMO); my $demouser; foreach $demouser (@DEMOUSERS) { chomp($demouser); if ( $demouser eq $username ) { print RED "[WARN] - $username is in demo mode!\n"; last; } } # Check if bandwidth limit exceeded if ( -e ("/var/cpanel/bwlimited/$username") or -e ("/var/cpanel/bwlimited/$MAINDOMAIN") or -e ("/var/cpanel/bwlimited/$QUERY") ) { print RED "[WARN] - $MAINDOMAIN ($username) may have exceeded their bandwidth limit!\n"; } if ($Is_Reseller) { print GREEN ON_BLACK "This account is also a reseller!\n"; if ($ResellerSharedIP) { print GREEN "Reseller's Shared IP: " . WHITE $ResellerSharedIP . "\n"; } if (@ResellerIPS) { print GREEN "Reseller's IP Delegation\n"; foreach $ResellerIP (@ResellerIPS) { chomp($ResellerIP); if ( $ResellerIP ne "" ) { print expand( YELLOW "\t \\_ $ResellerIP\n" ); } else { print expand( YELLOW "\t \\_ Open Delegation " ) . CYAN . "(any IP on this server can be used by " . WHITE $username . CYAN . ")\n"; } } } else { print GREEN "Reseller's IP Delegation: "; print YELLOW "Open Delegation " . CYAN . "(any IP on this server can be used by " . WHITE $username . CYAN . ")\n"; } } print GREEN "$IS_PARKED\n" unless ( $IS_PARKED eq "" ); print GREEN "$IS_ADDON\n" unless ( $IS_ADDON eq "" ); print GREEN "$IS_SUB\n" unless ( $IS_SUB eq "" ); if ( $showHeader || $all ) { print CYAN "Header information for " . GREEN $QUERY . "\n" unless ($IS_USERNAME); print CYAN "Header information for " . GREEN $MAINDOMAIN . "\n" unless ( !$IS_USERNAME ); display_header($QUERY) unless ($IS_USERNAME); display_header($MAINDOMAIN) unless ( !$IS_USERNAME ); } # domainfwdip and domainfwdmap checks if ( -e '/var/cpanel/domainfwdip' ) { if ( -s '/var/cpanel/domainmap' ) { open( my $fh, '<', '/var/cpanel/domainmap'); my @domainmaps=<$fh>; close( $fh ); foreach my $line(@domainmaps) { chomp($line); my ($fwddom,$fwdurl) = ( split( /\=/, $line )); #if ( $fwddom eq $MAINDOMAIN or $fwddom eq $QUERY ) { if ( $IS_USERNAME ) { if ( $fwddom eq $MAINDOMAIN ) { print RED "[WARN] - $fwddom is currently forwarding to $fwdurl\n"; } } else { if ( $fwddom eq $QUERY ) { print RED "[WARN] - $fwddom is currently forwarding to $fwdurl\n"; } } } } } print WHITE "Shell: " . CYAN $RealShell . "\n"; my $jailapacheTweakJSON = get_whmapi1( 'get_tweaksetting', 'key=jailapache' ); my $jaTweak = $jailapacheTweakJSON->{data}->{tweaksetting}->{value}; if ( $RealShell =~ m/jailshell/ && $RUID2Enabled && $jaTweak ) { print expand( YELLOW "\t\\_ Seeing 404's? ModRuid2 enabled w/Experimental Jail Apache Tweak.\n" ); print expand( YELLOW "\t\\_ Try disabling to see if that solves the error.\n" ); } ChkForIntegration(); # check if user is in /etc/ftpusers my $ftpblock = ""; if ( -e ("/etc/ftpusers") ) { open( FTPUSERS, "/etc/ftpusers" ); my @FTPUSERS = ; close(FTPUSERS); my $ftpblock; foreach $ftpblock (@FTPUSERS) { chomp($ftpblock); if ( $ftpblock eq $username ) { print RED "[WARN] - $username found in /etc/ftpusers file (FTP authentication will fail!\n"; last; } } } if ( -e ("/etc/proftpd/$username.suspended") and ( !$UserSuspended ) ) { print RED "[WARN] - $username.suspended file found /etc/proftpd directory. Can cause FTP password updates to fail!\n"; } if ( -e ("/etc/proftpd/$username.unsuspended_failed") ) { print RED "[WARN] - $username.unsuspended_failed file found /etc/proftpd directory. Can cause FTP password updates to fail!\n"; } # Check to make sure UID/GID is greater than what is defined in /etc/login.defs print WHITE "UID/GID: " . CYAN $UID . "/" . $GID . "\n"; my $quotaJSON = get_whmapi1( 'accountsummary', "user=$username" ); my $quotaused = $quotaJSON->{data}->{acct}->[0]->{diskused}; my $maxquota = $quotaJSON->{data}->{acct}->[0]->{disklimit}; print "Disk Quota: $quotaused used of $maxquota allowed "; if ( $quotaused > $maxquota ) { $acct_over_quota = 1 unless ( $maxquota eq "unlimited" ); print RED "[WARN] - $username appears to be over quota!" unless ( $maxquota eq "unlimited" ); } print "\n"; my $skip_bwcheck = 0; if ( !( -e ("/var/cpanel/features/$FEATURELIST") ) ) { print YELLOW "[INFO] - Skipping bandwidth check! Feature list \"$FEATURELIST\" missing from /var/cpanel/features/\n"; $skip_bwcheck = 1; } if ( $UserSuspended ) { print YELLOW "[INFO] - Skipping bandwidth check! Account is suspended\n"; $skip_bwcheck = 1; } if ( $skip_bwcheck == 0 ) { my $bwdataJSON = get_uapi( 'StatsBar', 'get_stats', 'display=bandwidthusage', "--user=$username" ); my $bwused = $bwdataJSON->{result}->{data}->[0]->{count}; my $bwmax = $bwdataJSON->{result}->{data}->[0]->{max}; print "Bandwidth: $bwused used of $bwmax allowed\n" unless ( $bwused eq "" or $bwmax eq "" ); } # Check for custom style (Paper Lantern Theme) my $custom_style_path = "$RealHome/var/cpanel/styled/current_style"; my $custom_style_link; my $custom_style; my @custom_style_array; my $custom_style_array; if ( -e ("$custom_style_path") ) { $custom_style_link = readlink($custom_style_path); @custom_style_array = split( "\/", $custom_style_link ); $custom_style = $custom_style_array[-1]; } my $undefined_package = ( $PACKAGE eq "undefined" ) ? RED " [WARNING: PACKAGE SHOULD NEVER BE UNDEFINED]" : ""; print WHITE "Hosting Package: " . CYAN $PACKAGE . $undefined_package . WHITE " (" . "Feature List: " . GREEN $FEATURELIST . WHITE ") " . $HAS_AUTOSSL_TEXT . "\n"; my $deprecated_theme_warning = ""; if ( $THEME eq "x3" ) { $deprecated_theme_warning = RED "\n\t\\_ WARNING - The x3 theme is deprecated. cPanel UI not loading? This is probably why!"; } if ( Cpanel::Version::compare( Cpanel::Version::getversionnumber(), '>=', '11.106.0.0') ) { if ( $THEME eq "paper_lantern" ) { $deprecated_theme_warning = RED "\n\t\\_ WARNING - The paper_lantern theme is deprecated as of version 106 and will be removed in a future version."; } } if( !$UserSuspended ) { my $wptkLiteJSON = get_uapi( "--user=$username", 'Features', 'has_feature', 'name=wp-toolkit' ); my $wptkDluxJSON = get_uapi( "--user=$username", 'Features', 'has_feature', 'name=wp-toolkit-deluxe' ); my $hasWPTKLite = $wptkLiteJSON->{result}->{data}; my $hasWPTKDeluxe = $wptkDluxJSON->{result}->{data}; my $hasWPTKFeature = "Neither Lite nor Deluxe"; if ($hasWPTKLite) { $hasWPTKFeature = "Lite"; } if ($hasWPTKDeluxe) { $hasWPTKFeature = "Deluxe"; } print WHITE "WP Toolkit Feature: " . CYAN $hasWPTKFeature . "\n"; } if ($custom_style) { print WHITE "Theme: " . CYAN $THEME . " (Style: $custom_style) " . $deprecated_theme_warning . "\n"; } else { print WHITE "Theme: " . CYAN $THEME . expand( $deprecated_theme_warning . "\n" ); } print WHITE "Max Addon/Alias/Sub Domains: " . CYAN $MAXADDON . WHITE " / " . CYAN $MAXPARK . WHITE " / " . CYAN $MAXSUB . "\n"; print WHITE "Max SQL Databases: " . CYAN $MAXSQL . "\n"; print WHITE "Max Mailman Lists: " . CYAN $MAXLST . "\n"; print WHITE "Max FTP Accounts: " . CYAN $MAXFTP . "\n"; print WHITE "Has AutoSSL Feature " . CYAN $HAS_AUTOSSL . " [" . YELLOW $SSLProvider . CYAN "]\n"; print WHITE "cPanel Backup Enabled: " . CYAN $BACKUPENABLED; if ( -e "$RealHome/.jbm" ) { my $jb4Path = '/usr/local/jetapps/usr/bin/jetbackup'; my $jb5Path = '/usr/local/jetapps/usr/bin/jetbackup5/jetbackup'; my $JB4Version; my $JB5Version; my $JBVersion; if ( -x $jb4Path ) { my ( $jb4_1, $jb4_2, $jb4_3 ) = ( split( /\s+/, Cpanel::SafeRun::Timed::timedsaferun( 4, "$jb4Path", '--version', "| head -1" ) ) )[ 1, 2, 3 ]; $JB4Version = $jb4_1 . " " . $jb4_2 . " " . $jb4_3; } if ( -x $jb5Path ) { my ( $jb5_1, $jb5_2, $jb5_3, $jb5_4 ) = ( split( /\s+/, Cpanel::SafeRun::Timed::timedsaferun( 4, "$jb5Path", '--version' ) ) )[ 7, 8, 9, 10 ]; $JB5Version = $jb5_1 . " " . $jb5_2 . " " . $jb5_3 . " " . $jb5_4; } chomp($JB4Version); chomp($JB5Version); if ( $JB4Version && $JB5Version ) { $JBVersion = $JB4Version . " / " . $JB5Version; } $JBVersion = ($JB4Version) ? $JB4Version : "" unless ($JBVersion); $JBVersion = ($JB5Version) ? $JB5Version : "" unless ($JBVersion); print " (" . BOLD GREEN "JetBackup Manager " . $JBVersion . " detected)"; } print "\n"; # List any linked server nodes if ($linked_nodes) { print WHITE "Linked Nodes:\n"; print expand( CYAN "\t\\_ $linked_nodes\n" ); } nginx_caching(); my $PHPDefaultVersion; if ($tomcat) { chk_tomcat(); } custom_UD(); my $cageFSStats = check_for_cagefs(); if ($cageFSStats) { print WHITE "CageFS: " . CYAN $cageFSStats . "\n"; } else { print WHITE "CageFS: " . CYAN . "Not installed/enabled!\n"; } # Check for php-selector (CloudLinux) my $clPHPVer = ""; $PHPDefaultVersion = get_system_php_version(); print "Default System PHP Version: " . CYAN $PHPDefaultVersion . "\n"; my $HasPrecedence = MAGENTA " cPanel EA4 PHP version has precedence"; my $found=timed_run( 0, 'grep', $QUERY, '/etc/userdomains' ); my $docroot; my $phpdomain; my $phpFPMStat; my $pmMaxChildren; my $pmMaxRequests; my $pmProcTimeout; my $PHPversion; my $result; if ( $found ) { ( $docroot, $phpdomain, $phpFPMStat, $pmMaxChildren, $pmMaxRequests, $pmProcTimeout, $PHPversion) = get_php_version($QUERY); $result=$QUERY; } else { ( $docroot, $phpdomain, $phpFPMStat, $pmMaxChildren, $pmMaxRequests, $pmProcTimeout, $PHPversion) = get_php_version($MAINDOMAIN); $result=$MAINDOMAIN; } my $PHPiniFile; my $PHPiniLoad; my $PHPiniScan; my $skipEA4 = 0; if ( $PHPversion eq "inherit" ) { if ($cageFSStats) { $HasPrecedence = MAGENTA " CloudLinux PHP Selector has precedence"; my $clPHP = timed_run( 2, 'grep', '^php', "$RealHome/.cl.selector/defaults.cfg" ); $clPHPVer =~ s/\s+//g; ($clPHPVer) = ( split( /=/, $clPHP ) )[1]; chomp($clPHPVer); $clPHPVer = alltrim($clPHPVer); my $clPHPVer1; my $clPHPVer2; if ( $clPHPVer eq "native" ) { my $clPHPVerLine = timed_run_noerr( 4, '/usr/bin/selectorctl', '--current', '--show-native-version'); ( $clPHPVer1, $clPHPVer2 ) = ( split( /\s+/, $clPHPVerLine ) )[ 0, 1 ]; $clPHPVer = $clPHPVer1 . " " . $clPHPVer2; } $PHPiniFile = timed_run( 0, 'su', '-s', '/bin/bash', "$username", '-c', 'php -i|grep "^Configuration File"' ); $PHPiniLoad = timed_run( 0, 'su', '-s', '/bin/bash', "$username", '-c', 'php -i| grep "^Loaded Configuration File"' ); $PHPiniScan = timed_run( 0, 'su', '-s', '/bin/bash', "$username", '-c', 'php -i|grep "^Scan this dir"' ); $skipEA4 = 1; } } print "EA4 PHP Version [ $result ]: " . CYAN $PHPversion; print WHITE "\nCloudLinux PHP Version: " . CYAN $clPHPVer unless ( !$clPHPVer ); print MAGENTA $HasPrecedence . "\n"; if ( !$skipEA4 ) { if ( $PHPversion eq "inherit" ) { $PHPiniFile = timed_run( 5, "$PHPDefaultVersion -i|grep '^Configuration File'" ); $PHPiniLoad = timed_run( 5, "$PHPDefaultVersion -i|grep '^Loaded Configuration File'" ); $PHPiniScan = timed_run( 5, "$PHPDefaultVersion -i|grep '^Scan this dir'" ); } else { $PHPiniFile = timed_run( 5, "$PHPversion -i|grep '^Configuration File'" ); $PHPiniLoad = timed_run( 5, "$PHPversion -i|grep '^Loaded Configuration File'" ); $PHPiniScan = timed_run( 5, "$PHPversion -i|grep '^Scan this dir'" ); } } chomp($PHPiniFile); chomp($PHPiniLoad); chomp($PHPiniScan); my $PHPHandlerJSON; my $PHPHandler; if ( ! $UserSuspended ) { $PHPHandlerJSON = get_uapi( 'LangPHP', 'php_get_domain_handler', 'type=vhost', "vhost=$MAINDOMAIN", "--user=$username"); $PHPHandler = $PHPHandlerJSON->{result}->{data}->{php_handler}; } my $suPHPConfPathFound; if ( -e ("$docroot/.htaccess") ) { $suPHPConfPathFound = timed_run( 2, "egrep -i ^suPHP_ConfigPath $docroot/.htaccess" ); } if ($suPHPConfPathFound) { chomp($suPHPConfPathFound); print YELLOW "[NOTE]" . WHITE . " - suPHP_ConfigPath found in $docroot/.htaccess file: " . CYAN . $suPHPConfPathFound . "\n"; my ($UsersuPHPConfPath) = ( split( /\s+/, $suPHPConfPathFound ) )[1]; $PHPiniFile = "Configuration File (php.ini) Path => $UsersuPHPConfPath"; $PHPiniLoad = "Loaded Configuration File => $UsersuPHPConfPath"; $PHPiniScan = "Scan this dir for additional .ini files => None"; } if ($phpFPMStat) { $PHPiniFile = "Configuration File Path => /opt/cpanel/$PHPversion/root/etc/php-fpm.d/$phpdomain.conf"; $PHPiniLoad = "Loaded Configuration File Path => /opt/cpanel/$PHPversion/root/etc/php-fpm.d/$phpdomain.conf"; $PHPHandler = "php-fpm"; } print expand( YELLOW "\t \\_ $PHPiniFile\n" ) unless ( $PHPiniFile eq "" ); print expand( YELLOW "\t \\_ $PHPiniLoad\n" ) unless ( $PHPiniLoad eq "" ); print expand( YELLOW "\t \\_ $PHPiniScan\n" ) unless ( $PHPiniScan eq "" ); print WHITE "PHP Handler: " . YELLOW $PHPHandler . "\n"; if ($phpFPMStat) { print CYAN "\nPHP-FPM pool detected\n"; print expand( GREEN "\t \\_ pm_max_children: " . WHITE $pmMaxChildren . "\n" ); print expand( GREEN "\t \\_ pm_max_requests: " . WHITE $pmMaxRequests . "\n" ); print expand( GREEN "\t \\_ pm_process_idle_timeout: " . WHITE $pmProcTimeout . "\n" ); } my $find_ini = timed_run( 5, 'find', "$RealHome/public_html", '-iname', '*.ini' ); my @find_ini = split /\n/, $find_ini; my $stopAfter = 0; if (@find_ini) { my $headPrinted = 0; my $file = ""; print CYAN "\nFound custom *.ini files (listing up to the first 5): \n" unless ($headPrinted); $headPrinted = 1; foreach $file (@find_ini) { chomp($file); print expand( YELLOW "\t \\_ $file\n" ); $stopAfter++; last if ( $stopAfter == 5 ); } } if ( $stopAfter >= 5 ) { print expand( CYAN "\t[ Hint: Run " . BOLD BLUE "find $RealHome/public_html -name '*.ini'" . CYAN " to list all ini files ]\n" ); } print "\n"; sub custom_UD { if ( -d ("/etc/apache2/conf.d/userdata") ) { my $CONFDATA = Cpanel::SafeRun::Timed::timedsaferun( 0, 'find', '/etc/apache2/conf.d/userdata', '-name', '*.conf' ); my @CONFDATA = split /\n/, $CONFDATA; my $confFile; my $NoShowHeader = 0; foreach $confFile (@CONFDATA) { chomp($confFile); next unless( $confFile =~ m{/$username/} ); if ( $confFile =~ m{/$username/} ) { print expand( YELLOW "Found custom Apache userdata directory for $username in:\n") unless ($NoShowHeader); $NoShowHeader = 1; print expand("\t \\_ $confFile\n"); } } } if ( -d ("/etc/nginx/conf.d/users/$username") ) { my $CONFDATA = Cpanel::SafeRun::Timed::timedsaferun( 0, 'find', "/etc/nginx/conf.d/users/$username", '-name', '*.conf' ); my @CONFDATA = split /\n/, $CONFDATA; my $confFile; my $NoShowHeader = 0; foreach $confFile (@CONFDATA) { chomp($confFile); next unless( $confFile =~ m{/$username/} ); if ( $confFile =~ m{/$username/} ) { print expand( YELLOW "Found custom NginX user directory for $username in:\n") unless ($NoShowHeader); $NoShowHeader = 1; print expand("\t \\_ $confFile\n"); } } } } my $ListIPsJSON = get_whmapi1('listips'); my $IS_IP_ON_SERVER = 0; my $NOTONSERVER = "[ Not configured on this server ]" . YELLOW "\n\t\\_ Need to run IP Migration Wizard? [ https://support.cpanel.net/hc/en-us/articles/360048023693 ]"; for my $ListOfIPs ( @{ $ListIPsJSON->{data}->{ip} } ) { if ( $ListOfIPs->{ip} eq $IPADDR ) { $IS_IP_ON_SERVER = 1; $NOTONSERVER = "[ Is configured on this server ] "; last; } } if ( $ip_on_localhost ) { $NOTONSERVER="INVALID" . YELLOW "\n\t\\_ Can be fixed by running IP Migration Wizard [ https://support.cpanel.net/hc/en-us/articles/360048023693 ]"; } print WHITE "Configured IP address: " . CYAN $IPADDR . WHITE " (" . CYAN $IPTYPE . WHITE ") - $NOTONSERVER\n"; my $defaultsite = 0; my $TotalARecords = @ResolvedIP; my $ResolvesToDetail = ""; my @multipleIPs = undef; foreach $ResolvedIP (@ResolvedIP) { chomp($ResolvedIP); next if !Cpanel::Validate::IP::is_valid_ip($ResolvedIP); my $hasCloud = hasCloud($ResolvedIP) if($OnCloud); if ( $hasCloud ) { my $ResolvedHost = Cpanel::SafeRun::Timed::timedsaferun( 4, 'dig', '-x', $ResolvedIP, '+short' ); chomp($ResolvedHost); $ResolvesToDetail = "A node on the cpCloud Server [ $ResolvedHost ]"; push @multipleIPs, CYAN "\t \\_ " . $ResolvedIP . " " . YELLOW $ResolvesToDetail . "\n"; } else { $ResolvesToDetail = check_resolved_ip($ResolvedIP); push @multipleIPs, CYAN "\t \\_ " . $ResolvedIP . " " . YELLOW $ResolvesToDetail . "\n"; } } print WHITE "Resolves to the following IPs:\n"; if ( $TotalARecords == 0 ) { print CYAN "\t \\_ DOES NOT RESOLVE " . RED "[NXDOMAIN]\n"; } else { my $multiIP = ""; foreach $multiIP (@multipleIPs) { chomp($multiIP); print $multiIP; } } # Check to see if domain name is in httpd.conf my $FoundInHTTPDconf; # NEED TO REVIEW THIS. SHOULD I JUST CHECK FOR $QUERY (regardless if it's a username or not?) if ($IS_USERNAME) { $FoundInHTTPDconf = timed_run( 3, "grep -w '$MAINDOMAIN' /etc/apache2/conf/httpd.conf" ); } else { $FoundInHTTPDconf = timed_run( 3, "grep -w '$QUERY' /etc/apache2/conf/httpd.conf" ); } if ( !($FoundInHTTPDconf) ) { if ($IS_USERNAME) { print RED "[WARN] " . YELLOW "- $MAINDOMAIN is missing from httpd.conf file!\n"; } else { print RED "[WARN] " . YELLOW "- $QUERY is missing from httpd.conf file!\n"; } $defaultsite = 1; } if ( -e "/var/cpanel/userdata/$username/main" ) { open( USER, "/var/cpanel/users/$username" ); my @USERFILE = ; close(USER); my $userline; my $MainDNSLine; foreach $userline (@USERFILE) { chomp($userline); if ( substr( $userline, 0, 4 ) eq "DNS=" ) { ($MainDNSLine) = ( split( /=/, $userline ) )[1]; last; } } chomp($MainDNSLine); open( USER, "/var/cpanel/userdata/$username/main" ); my @USERFILE = ; close(USER); my $userline; my $MainDomainLine; foreach $userline (@USERFILE) { chomp($userline); if ( $userline =~ "main_domain:" ) { ($MainDomainLine) = ( split( /\s+/, $userline ) )[1]; last; } } chomp($MainDomainLine); if ( $MainDNSLine ne $MainDomainLine ) { print MAGENTA "$MAINDOMAIN\n"; print expand ( CYAN "\t \\_ DNS=" . YELLOW $MainDNSLine . "\n" ); print expand ( CYAN "\t \\_ main_domain: " . YELLOW $MainDomainLine . "\n" ); print expand ( RED "\t \\_ [WARN]: DNS= line in users file does not match main_domain in userdata!\n\t\\_ Can cause IP Address to show up as MISSING in List Accounts! [CPANEL-20670]\n" ); } } if ($defaultsite) { print YELLOW "Not seeing the site you're expecting (or defaultwebpage.cgi)? - This may be why!\n"; } if ( $all or $mail ) { display_mail_info(); } # Last Login IP if ($LastLoginIP) { print WHITE "Last logged in to cPanel from IP: " . CYAN $LastLoginIP . "\n"; } print WHITE "Has been a customer since " . CYAN $STARTDATE . "\n"; # Check to see if the $username is in /var/cpanel/suspended directory my $SUSP = 0; my $REASON = ""; if ( -e ("/var/cpanel/suspended/$username") ) { $REASON = `cat /var/cpanel/suspended/$username`; chomp($REASON); $SUSP = 1; } print WHITE "Suspended: "; if ($SUSP) { print RED "YES! (Since: " . scalar localtime($SUSPEND_TIME) . ")"; print WHITE " - Reason: " . CYAN $REASON unless ( $REASON eq "" ); } else { print GREEN "No"; } if ( -d ("$RealHome/public_html/suspended.page") ) { print RED ", but a suspended.page template directory was found in $RealHome/public_html directory!"; } print "\n"; print WHITE "Count of other domains: [" . YELLOW "SUB: " . GREEN $subcnt . WHITE "] - [" . YELLOW "ALIASES " . GREEN $parkcnt . WHITE "] - [" . YELLOW "ADDONS: " . GREEN $addoncnt . WHITE "]\n"; my $TotalDomainCnt = $subcnt + $parkcnt + $addoncnt + 1; open( USERS, "/var/cpanel/users/$username" ); my @USERFILE = ; close(USERS); my $DNSLinesCnt = @users_domains; if ($IS_USERNAME) { $isInNamedConf = 1; } else { $isInNamedConf = Cpanel::SafeRun::Timed::timedsaferun( 0, 'grep', "$QUERY", '/etc/named.conf' ); } if ( $DNSLinesCnt != $TotalDomainCnt ) { print expand( RED "\t\\_ [WARN]: Total domains for $username doesn't match the DNS lines in " . BOLD CYAN "/var/cpanel/users/$username [DNS: $DNSLinesCnt / Cnt: $TotalDomainCnt]\n" ); print expand( YELLOW "\t\\_ Use --cruft to list missing domains\n" ); } border(); my $SUB = ""; my $PARK = ""; my $ADDON = ""; if ( $subcnt + $addoncnt + $parkcnt > 1 and ( $all or $domains or $listsubs or $listaddons or $listparked or $listaliased ) ) { print WHITE "The following are associated with " . CYAN $MAINDOMAIN . WHITE " (" . GREEN $username . WHITE ")\n"; smborder(); } if ( $all or $domains or $listsubs ) { print YELLOW "Sub Domains: "; if ( $subcnt > 0 and ( $all or $domains or $listsubs ) ) { print "\n"; foreach $SUB (@SUBDOMAINS) { chomp($SUB); print expand( YELLOW "\t \\_ $SUB\n" ); } } else { print MAGENTA "No Sub Domains found for " . CYAN $MAINDOMAIN . WHITE " (" . GREEN $username . WHITE ")\n"; } smborder(); } if ( $all or $domains or $listaddons ) { print YELLOW "Addon Domains: "; if ( $addoncnt > 0 and ( $all or $domains or $listaddons ) ) { print "\n"; foreach $ADDON (@ADDONDOMAINS) { chomp($ADDON); print expand( YELLOW "\t \\_ $ADDON\n" ); } } else { print MAGENTA "No Addon Domains found for " . CYAN $MAINDOMAIN . WHITE " (" . GREEN $username . WHITE ")\n"; } smborder(); } if ( $all or $domains or $listparked or $listaliased ) { print YELLOW "Aliased (Parked) Domains: "; if ( $parkcnt > 0 and ( $all or $domains or $listparked or $listaliased ) ) { print "\n"; foreach $PARK (@PARKEDDOMAINS) { chomp($PARK); print expand( YELLOW "\t \\_ $PARK\n" ); } } else { print MAGENTA "No Aliased (Parked) Domains found for " . CYAN $MAINDOMAIN . WHITE " (" . GREEN $username . WHITE ")\n"; } border(); } # RESELLER INFO if ( $reselleraccts or $all ) { my $tcOwnerDomains = Cpanel::SafeRun::Timed::timedsaferun( 3, 'grep', " $username", '/etc/trueuserowners' ); my @tcOwnerDomains = split /\n/, $tcOwnerDomains; foreach my $line(@tcOwnerDomains) { chomp($line); my ( $tcUser ) = (split( /\:/, $line ))[0]; chomp($tcUser); my $tcDomains = Cpanel::SafeRun::Timed::timedsaferun( 3, 'grep', "$tcUser", '/etc/trueuserdomains' ); my @tcDomains = split /\n/, $tcDomains; foreach my $line2(@tcDomains) { my( $tcDomain, $tcUser2 ) = (split( /\: /, $line2)); chomp($tcDomain); push @ACCTSOWNEDBYRESELLER, "$tcDomain ($tcUser2)" unless( $tcDomain eq $tcUser2 || $tcUser eq $username || $tcUser ne $tcUser2 ); } } splice( @ACCTSOWNEDBYRESELLER,0,1 ); $ResellerAcctsCnt = @ACCTSOWNEDBYRESELLER; my @SORTEDRESELLERACCTS = sort(@ACCTSOWNEDBYRESELLER); my $owned_by_reseller = ""; if ( $Is_Reseller and $ResellerAcctsCnt > 0 ) { print CYAN $MAINDOMAIN . WHITE " is a reseller and has the following ($ResellerAcctsCnt) accounts under it\n"; foreach $owned_by_reseller (@SORTEDRESELLERACCTS) { chomp($owned_by_reseller); print expand( BOLD YELLOW ON_BLACK "\t \\_ $owned_by_reseller\n" ); } } else { print WHITE "No Reseller accounts found for " . CYAN $MAINDOMAIN . WHITE " (" . GREEN $username . WHITE ")\n"; } border(); } if ( $resellerperms or $all ) { my $DefaultPerm = ""; my $defaultRPerm = ""; my @defaultperms = qw( acct-summary basic-system-info basic-whm-functions cors-proxy-get cpanel-api cpanel-integration create-user-session digest-auth generate-email-config list-pkgs manage-api-tokens manage-dns-records manage-oidc manage-styles mysql-info ns-config public-contact ssl-info track-email connected-applications ); if ($Is_Reseller) { open( RESELLERS, "/var/cpanel/resellers" ); my @RESELLERS = ; close(RESELLERS); my $resellerline = ""; my @rperms = undef; my $rperm = ""; print CYAN "The reseller " . $MAINDOMAIN . WHITE " has the following reseller permissions\n"; foreach $resellerline (@RESELLERS) { chomp($resellerline); my ( $reseller, $rperms ) = ( split( /:/, $resellerline ) ); if ( $reseller eq $username ) { my @rperms = split /,/, $rperms; foreach $rperm (@rperms) { chomp($rperm); foreach $defaultRPerm (@defaultperms) { chomp($defaultRPerm); if ( $rperm =~ $defaultRPerm ) { $DefaultPerm = BLUE ON_BLACK " - Initial [DEFAULT]"; last; } else { $DefaultPerm = ""; } } print expand( BOLD YELLOW ON_BLACK "\t \\_ $rperm " . $DefaultPerm ); if ( $rperm eq "all" ) { print RED "[DANGER] - HAS ROOT PRIVILEGES!!!"; } if ( $rperm =~ m/(edit-account|limit-bandwidth|quota|demo-setup|rearrange-accts|clustering|locale-edit)$/ ) { print MAGENTA "[WARNING] - SUPER PRIVILEGE! (can bypass certain limits)"; } if ( $rperm =~ m/(list-accts|show-bandwidth|create-acct|kill-acct|suspend-acct|upgrade-acct|ssl|ssl-buy|ssl-gencrt|edit-mx|passwd|file-restore|create-dns|kill-dns|park-dns|edit-dns|add-pkg|edit-pkg|thirdparty|mailcheck|news|assign-root-account-enhancements)$/ ) { print GREEN " - Standard Privilege"; } if ( $rperm =~ m/(status|stats|restart|resftp)$/ ) { print CYAN " - Global Privilege"; } print "\n"; } } } } } if ($wpinfo) { border(); if ($subdocroot) { get_wp_info($subdocroot); } elsif ($addondocroot) { get_wp_info($addondocroot); } elsif ($parkeddocroot) { get_wp_info($parkeddocroot); } else { get_wp_info($maindocroot); } } # MySQL INFO if ( $listdbs or $all ) { my $DBCnt = 0; my $UserDBCnt = 0; my $DBsJSON; print WHITE "The following MySQL databases can be found under: " . GREEN $username . "\n"; if ($UserSuspended) { print RED "Account $username is suspended - Currently cannot list databases!\n"; exit; } else { $DBsJSON = get_uapi( 'Mysql', 'list_databases', "--user=$username" ); if ( $DBsJSON->{result}->{errors}->[0] ) { print RED "$DBsJSON->{result}->{errors}->[0]\n"; } for my $UserDBs ( @{ $DBsJSON->{result}->{data} } ) { print BOLD GREEN "Database: " . $UserDBs->{database} . "\n"; $DBCnt++; for my $dbUsers ( @{ $UserDBs->{users} } ) { $UserDBCnt++; print expand( BOLD YELLOW "\t\\_User: " . $dbUsers . "\n" ); my $dbPrivsJSON = get_uapi( 'Mysql', 'get_privileges_on_database', "--user=$username", "user=$dbUsers", "database=$UserDBs->{database}" ); my $SQLPrivs; for my $dbPrivs ( @{ $dbPrivsJSON->{result}->{data} } ) { $SQLPrivs .= ' [' . $dbPrivs . ']'; } print expand( BOLD MAGENTA "\t\t\\_ GRANTS: " . WHITE $SQLPrivs . "\n" ); } } } print BOLD CYAN "\nTotal MySQL Databases: " . WHITE $DBCnt . CYAN " / Users: " . WHITE $UserDBCnt . "\n"; smborder(); # PostGreSQL INFO my $psql_runningJSON = get_whmapi1( 'servicestatus', "service=postgresql" ); my $psql_running = $psql_runningJSON->{data}->{service}->[0]->{enabled}; if ( -e ("/usr/bin/psql") and $psql_running ) { ## PostGreSQL is installed and running print WHITE "The following PostGreSQL databases can be found under: " . GREEN $username . "\n"; my $DBsJSON = get_uapi( 'Postgresql', 'list_databases', "--user=$username" ); my $DBCnt = 0; my $UserDBCnt = 0; for my $UserDBs ( @{ $DBsJSON->{result}->{data} } ) { print BOLD GREEN "Database: " . $UserDBs->{database} . "\n"; $DBCnt++; for my $dbUsers ( @{ $UserDBs->{users} } ) { $UserDBCnt++; print expand( BOLD YELLOW "\t\\_User: " . $dbUsers . "\n" ); } } print BOLD CYAN "\nTotal PostGreSQL Databases: " . WHITE $DBCnt . CYAN " / Users: " . WHITE $UserDBCnt . "\n"; } else { print BOLD MAGENTA "PostGreSQL is not enabled/running or installed on $HOSTNAME\n"; } border(); } if ( $listssls or $all ) { print WHITE "SSL Certificates installed under main domain: " . CYAN $MAINDOMAIN . WHITE " (" . GREEN $username . WHITE ")\n"; $sslsyscertdir = "/var/cpanel/ssl/apache_tls"; dispSSLdata($MAINDOMAIN); # Subdomains here print BOLD BLUE "\t========= Subdomains =========\n"; foreach $SUB (@SUBDOMAINS) { chomp($SUB); dispSSLdata($SUB); } # List any domains excluded from AutoSSL check my $isExcludedJSON = get_uapi( 'SSL', 'get_autossl_excluded_domains', "--user=$username" ); my $ExcludedDomain; my $ExcludedCnt = 0; for my $ExcludedDomains ( @{ $isExcludedJSON->{result}->{data} } ) { for $ExcludedDomain ( $ExcludedDomains->{excluded_domain} ) { if ( $ExcludedDomain and $ExcludedCnt == 0 ) { print BOLD MAGENTA "\n*** The following domains are excluded from AutoSSL ***\n"; $ExcludedCnt = 1; } print expand( YELLOW "\t \\_ " . BRIGHT_CYAN $ExcludedDomain . "\n" ); } } # Check for pending AutoSSL orders here (uses whmapi1 get_autossl_pending_queue (62.0.26+ only) print "\nChecking for pending AutoSSL orders: \n"; my $SSL_PENDINGJSON = get_whmapi1('get_autossl_pending_queue'); my $SSLPendingCnt = 0; for my $SSL_Pending ( @{ $SSL_PENDINGJSON->{data}->{pending_certificates} } ) { if ( $SSL_Pending->{user} eq $username ) { $SSLPendingCnt = 1; my $SSLPending_domain = $SSL_Pending->{domain}; my $SSLPending_ordernum = $SSL_Pending->{order_item_id}; my $SSLPending_URL = "https:\/\/manage2.cpanel.net\/certificate.cgi\?oii=$SSLPending_ordernum"; my $SSLPending_time = $SSL_Pending->{request_time}; print expand( GREEN "\t \\_ Domain: " . CYAN "$SSLPending_domain\n" ); print expand( GREEN "\t\t \\_ Request Time: " . CYAN "$SSLPending_time\n" ); print expand( GREEN "\t\t \\_ Order Number " . CYAN $SSLPending_ordernum . GREEN " [ " . BOLD MAGENTA $SSLPending_URL . GREEN " ]\n" ); } } if ( $SSLPendingCnt == 0 ) { print expand( GREEN "\t \\_ None\n" ); } # Check for purchased SSL's # print "Checking for pending SSL Orders (non-autossl): \n"; # if ( -e ("$RealHome/.cpanel/ssl/pending_queue.json") ) { # my ($PendingSSLOrder) = ( # split( /\s+/, qx[ python -mjson.tool $RealHome/.cpanel/ssl/pending_queue.json|grep -A1 cPStore ]))[3]; # $PendingSSLOrder =~ s/\"//g; # $PendingSSLOrder =~ s/,//g; # $PendingSSLOrder =~ s/://g; # if ($PendingSSLOrder) { # print expand( CYAN "\t \\_ Pending order number: " # . GREEN # . "https://manage2.cpanel.net/certificate.cgi?oii=$PendingSSLOrder\n" # ); # } # else { # print expand( GREEN "\t \\_ None\n" ); # } # } # else { # print expand( GREEN "\t \\_ None\n" ); # } # Check for CAA records here. print "Checking for CAA records: \n"; my $CAARecord; my $CAAFound = @HasCAA; if ( $CAAFound == 0 ) { print expand( GREEN "\t \\_ None\n" ); } else { print GREEN "The following domains have CAA records and SSL's can only be obtained from the following CA's:\n"; foreach $CAARecord (@HasCAA) { chomp($CAARecord); #print expand( CYAN "\t \\_ $CAARecord\n" ); print expand( CYAN "$CAARecord\n" ); } } border(); } if ($debugHookChanged) { get_whmapi1( 'set_tweaksetting', 'key=debughooks', 'value=logall' ); } exit; sub Usage { print WHITE "\nUsage: " . CYAN "acctinfo" . WHITE " {domainname.tld,cPUsername} ACTION [ADDITIONAL OPTIONS]\n\n"; print YELLOW "Examples: \n" . CYAN "acctinfo" . WHITE " --listdbs somedomain.net\n"; print expand( GREEN "\t Lists any MySQL and PostGreSQL databases for somedomain.net\n" ); print CYAN "acctinfo" . WHITE " --listsubs cptestdo\n"; print expand( GREEN "\t Lists all sub domains under the cptestdo user name.\n" ); print CYAN "acctinfo" . WHITE " --listaddons cptestdomain.net\n"; print expand( GREEN "\t Lists all addon domains under the cptestdomain.net domain name.\n" ); print CYAN "acctinfo" . WHITE " --listalias cptestdomain.net\n"; print expand( GREEN "\t Lists all alias (parked) domains under the cptestdomain.net domain name.\n" ); print CYAN "acctinfo" . WHITE " --domains cptestdomain.net\n"; print expand( GREEN "\t Lists all alias (parked), subdomains and addon domains under the cptestdomain.net domain name.\n" ); print CYAN "acctinfo" . WHITE " --reselleraccts cptestdo\n"; print expand( GREEN "\t Lists reseller information and domains under the cptestdo user name.\n" ); print CYAN "acctinfo" . WHITE " --resellerperms|--resellerprivs cptestdo\n"; print expand( GREEN "\t Lists reseller permissions under the cptestdo user name.\n" ); print CYAN "acctinfo" . WHITE " --listssls cptestdomain.net\n"; print expand( GREEN "\t Lists any SSL's under the cptestdomain.net domain name.\n" ); print CYAN "acctinfo" . WHITE " --cruft cptestdomain.net\n"; print expand( GREEN "\t Perform a cruft check on cptestdomain.net.\n" ); print CYAN "acctinfo" . WHITE " --mail cptestdomain.net [ --cphulkblocks]\n"; print expand( GREEN "\t Display mail information for cptestdomain.net.\n" ); print CYAN "acctinfo" . WHITE " --tomcat cptestdo\n"; print expand( GREEN "\t Display Tomcat 8.5/10.0 configuration information for the cptestdo user.\n" ); print CYAN "acctinfo" . WHITE " --wpinfo [--showpass] cptestdomain.net\n"; print expand( GREEN "\t Check for and display WordPress information for the cptestdomain.net domain.\n" ); print CYAN "acctinfo" . WHITE " --scan cptest\n"; print expand( GREEN "\t Scan users home directory for known infection strings.\n" ); print CYAN "acctinfo" . WHITE " --all cptestdomain.net\n"; print expand( GREEN "\t Lists everything for the cptestdomain.net domain name.\n" ); print CYAN "acctinfo" . WHITE " --header cptestdomain.net\n"; print expand( GREEN "\t Attempt to obtain certain Apache Header information and display if found [ requires patience ].\n" ); print CYAN "acctinfo" . WHITE " --help\n"; print expand( GREEN "\t Shows this usage information. (NOTE: ACTION can go before or after domain/username).\n\n" ); if ($debugHookChanged) { get_whmapi1( 'set_tweaksetting', 'key=debughooks', 'value=logall' ); } exit; } sub border { print MAGENTA ON_BLACK "===================================================================================\n"; return; } sub smborder { print MAGENTA "-----------------------------------------------------------------------------------\n"; return; } sub check_cloudflare_ips { my $chkIP = shift; chomp($chkIP); my $url = URI->new( 'https://www.cloudflare.com/ips-v4' ); my $ua = LWP::UserAgent->new( ssl_opts => { verify_hostname => 0 } ); $ua->agent(""); my $res = $ua->get($url); my $cf_subnets = $res->decoded_content; my @cf_subnets = split /\n/, $cf_subnets; foreach my $cf_subnet (@cf_subnets) { chomp($cf_subnet); my $network = NetAddr::IP->new($cf_subnet); my $ip = NetAddr::IP->new($chkIP); if ( $ip->within($network) ) { return 1; } } } sub check_for_nat { return if ( !( -e ("/var/cpanel/cpnat") ) ); my $chkIP = $_[0]; open( CPNAT, "/var/cpanel/cpnat" ); my @CPNAT = ; close(CPNAT); my $cpnat; foreach $cpnat (@CPNAT) { chomp($cpnat); my ( $outsideIP, $insideIP ) = ( split( /\s+/, $cpnat ) ); chomp($outsideIP); chomp($insideIP); if ( $outsideIP eq $chkIP ) { return $insideIP; } if ( $insideIP eq $chkIP ) { return $outsideIP; } } } sub hasCloud { my $tcIP=shift; return unless( -x '/usr/local/bin/kubectl' ); my $getnodes_raw = Cpanel::SafeRun::Timed::timedsaferun( 5, 'kubectl', 'get', 'nodes', '-o', 'wide', '-o', 'json' ); if ( grep { /$tcIP/ } $getnodes_raw ) { return 1; } else { return 0; } } sub check_resolved_ip { my $resolved_ip = shift; my $RetVal = ""; # RIGHT HERE my $is_cloudflare_ip = check_cloudflare_ips($resolved_ip); if ($is_cloudflare_ip) { $RetVal = $RetVal .= " <<--- CloudFlare IP Address"; $defaultsite = 1; return $RetVal; } my $ListIPsJSON = get_whmapi1('listips'); my ( $ip_on_server, $isNat, $publicIP, $privateIP ); for my $ListOfIPs ( @{ $ListIPsJSON->{data}->{ip} } ) { $publicIP = $ListOfIPs->{public_ip}; $privateIP = $ListOfIPs->{ip}; chomp($publicIP); chomp($privateIP); $ip_on_server = 1 if ( $publicIP eq $resolved_ip ); $isNat = 1 if ( $publicIP ne $privateIP ); } if ( !$ip_on_server ) { $RetVal = $RetVal .= "[Not on this server]"; $defaultsite = 1; return $RetVal; } if ($isNat) { $RetVal = " Routed via 1:1 NAT ( $publicIP => $privateIP )" . GREEN " [SAME]"; $defaultsite = 0; } else { ## NOT A NAT SYSTEM $RetVal = " Direct Route ($resolved_ip => $resolved_ip)" . GREEN " [SAME]"; $defaultsite = 0; } return $RetVal; } # OLD ROUTINE #sub check_resolved_ip { # my $IP2CHK = $shift; # my $RetVal = ""; # my $IS_CLOUDFLARE = check_cloudflare_ips($IP2CHK); # if ($IS_CLOUDFLARE) { # $RetVal = $RetVal .= " <<--- CloudFlare IP Address"; # $defaultsite = 1; # return $RetVal; # } # my $ListIPsJSON = get_whmapi1('listips'); # my $IS_IP_ON_SERVER = 0; # for my $ListOfIPs ( @{ $ListIPsJSON->{data}->{ip} } ) { # if ( $ListOfIPs->{public_ip} eq $IP2CHK ) { # $IS_IP_ON_SERVER = 1; # } # } # if ( !$IS_IP_ON_SERVER ) { # $RetVal = $RetVal .= "[Not on this server]"; # $defaultsite = 1; # return $RetVal; # } # # if ( -e ("/var/cpanel/cpnat") ) { # open( CPNAT, "/var/cpanel/cpnat" ); # my @CPNAT = ; # close(CPNAT); # my $cpnatline; # my $RetVal; # foreach $cpnatline (@CPNAT) { # chomp($cpnatline); # my ( $private_ip, $public_ip ) = ( split( /\s+/, $cpnatline ) ); # chomp($public_ip); # chomp($private_ip); # if ( $IP2CHK eq $public_ip ) { # $RetVal = " Routed via 1:1 NAT ($public_ip => $private_ip)" # . GREEN " [SAME]"; # $defaultsite = 0; # return $RetVal; # } # } # } # else { # $RetVal = " Direct Route ($IP2CHK => $IP2CHK)" . GREEN " [SAME]"; # $defaultsite = 0; # return $RetVal; # } #} sub cruft_check { border(); print CYAN "CRUFT CHECK\n"; border(); my $maxwidth = 25; my $file2search = ""; my $TheStatus = ""; my $spacer = 0; my $len = 0; my $filestatus = ""; my $isTerminated = 0; my $termdate = ""; my $createdate = ""; my @temp = undef; my $DNSLinesCnt = 0; my $TotalDomainCnt = 0; my $skipMySQLCruft = 0; if ( $systemUser == 0 ) { $skipMySQLCruft = 1; } my $isActive; my $is_acct; my $check_for; if ( substr( $QUERY, 0, 5 ) eq "cptkt" ) { print BOLD RED "[WARN] - cruft check for cPanel Support temporary reseller accounts are unpredictable!\n"; if ($debugHookChanged) { get_whmapi1( 'set_tweaksetting', 'key=debughooks', 'value=logall' ); } } # Check /var/cpanel/accounting.log file here (for CREATE and/or REMOVE lines) # ONLY MAIN ACCT / DOMAIN is checked print BRIGHT_BLUE "From your query of " . GREEN $QUERY . BRIGHT_BLUE " I have determined:\n"; open( my $fh, '<', '/var/cpanel/accounting.log' ); my @data=<$fh>; close( $fh); foreach my $line( @data ) { chomp($line); my ( $day, $mon, $date, $time, $year ) = ( split( /\s+/, $line ))[0,1,2,3,4]; my ( $onlyyear ) = ( split( /:/, $year ) )[0]; my $fulldate = $day . " " . $mon . " " . $date . " " . $time . " " . $onlyyear; my ( $cmd, $owner1, $owner2, $other1, $other2, $other3 ) = ( split( /\:/, $line ))[3,4,5,6,7,8]; next unless( $cmd =~ m{CREATE$|REMOVE$|CREATERESELLERWITHOUTDOMAIN$} ); if ( $line =~ m/$QUERY/ ) { $is_acct = 1; ## Main domain (sub/addon/parked don't get added to accounting.log file) if ( $cmd eq 'CREATE' ) { print GREEN "Domain: $other1 with username $other3 was created on $fulldate.\n"; $isActive = 1; $isTerminated = 0; } if ( $cmd eq 'REMOVE' ) { print RED "Domain: $other1 with username $other2 was terminated on $fulldate.\n"; $isActive = 0; $isTerminated = 1; } if ( $cmd eq 'CREATERESELLERWITHOUTDOMAIN' ) { print MAGENTA "A domainless reseller account ( $other1 ) was created on $fulldate.\n"; } } } my $NOGrep = 0; if ($isTerminated) { ## $is_acct is true if this is the main account/domain $NOGrep = 1; $skipMySQLCruft = 1; } if ($isActive) { if ( $MAINDOMAIN eq $HOSTNAME or $MAINDOMAIN eq "root" ) { print RED "[WARN] - $MAINDOMAIN is either root or is the same as hostname $HOSTNAME!\n"; } if ( $addoncnt > 0 ) { print "It has $addoncnt Addon domains "; } if ( $subcnt > 0 ) { print "It has $subcnt Sub domains "; } if ( $parkcnt > 0 ) { print "It has $parkcnt Aliased domains"; } print "\n"; } if ( $username and -e "/etc/passwd" ) { my $UID = Cpanel::PwCache::Get::getuid($username); my $GID = Cpanel::Config::CpUser::get_cpgid($username); my $UID_MIN = Cpanel::LoginDefs::get_uid_min(); my $GID_MIN = Cpanel::LoginDefs::get_gid_min(); if ( $UID < $UID_MIN or $GID < $GID_MIN ) { print RED "[WARN] - UID/GID for $username is less than $UID_MIN/$GID_MIN as set in /etc/login.defs\n" unless ( $UID == 0 or $GID == 0 ); } } # END OF ACCOUNTING LOG CHECK $TotalDomainCnt = $addoncnt + $subcnt + $parkcnt + 1; open( USERS, "/var/cpanel/users/$username" ); my @USERFILE = ; close(USERS); my $DNSLinesCnt = 0; my @DNSLinesOnly = ""; my $userline; foreach $userline (@USERFILE) { chomp($userline); next unless ( $userline =~ m/^DNS/ ); my ($dnsdomain) = ( split( /=/, $userline ) )[1]; push @DNSLinesOnly, $dnsdomain; $DNSLinesCnt++; } if ( !$isActive and !$is_acct and !$isTerminated and !$MAINDOMAIN and !$username ) { print "No data found for your query of: $QUERY in /var/cpanel/accounting.log\n"; print "Continuing search for $QUERY...\n"; } my $isAddon; my $isSub; my $isParked; my $SubDomain; my $TheAddonDomain; if ( !$isActive ) { $isAddon = qx[ grep '^$QUERY:' /etc/userdatadomains | grep '==addon==' ]; if ($isAddon) { $TheAddonDomain = qx[ grep '^$QUERY:' /etc/userdatadomains | grep '==addon==' | cut -d = -f7 ]; chomp($TheAddonDomain); ($username) = ( split( /\s+/, $isAddon ) )[1]; ($username) = ( split( /==/, $username ) ); ($isAddon) = ( split( /:/, $isAddon ) ); print YELLOW "$QUERY has an entry in /etc/userdatadomains as an Addon Domain under the " . CYAN $username . WHITE " user\n"; } $isSub = qx[ grep '^$QUERY:' /etc/userdatadomains | grep '==sub==' ]; if ($isSub) { ($username) = ( split( /\s+/, $isSub ) )[1]; ($username) = ( split( /==/, $username ) ); ($isSub) = ( split( /:/, $isSub ) ); print YELLOW "$QUERY has an entry in /etc/userdatadomains as a Sub Domain under the " . CYAN $username . WHITE " user\n" unless ($isAddon); } $isParked = qx[ grep '^$QUERY:' /etc/userdatadomains | grep '==parked==' ]; if ($isParked) { ($username) = ( split( /\s+/, $isParked ) )[1]; ($username) = ( split( /==/, $username ) ); ($isParked) = ( split( /:/, $isParked ) ); print YELLOW "$QUERY has an entry in /etc/userdatadomains as a Aliased Domain under the " . CYAN $username . WHITE " user\n"; } if ( !$MAINDOMAIN and $username ) { ($MAINDOMAIN) = ( split( /:/, qx[ grep '$username' /etc/trueuserdomains ] ) )[0]; chomp($MAINDOMAIN); } } smborder(); my @FILES2SEARCHUSER = qw( /etc/passwd /etc/group /etc/shadow /etc/gshadow /etc/quota.conf /etc/dbowners /etc/trueuserowners /var/cpanel/databases/users.db /etc/userdatadomains.json /var/cpanel/quotawarned /etc/nocgiusers /etc/userips /etc/userbwlimits /var/cpanel/resellers ); my @FILES2SEARCH = qw( /etc/userdomains /etc/trueuserdomains /etc/userdatadomains /etc/domainusers /etc/domainips /etc/localdomains /etc/remotedomains /etc/demousers /etc/email_send_limits /etc/demoids /etc/manualmx /etc/demodomains /etc/ssldomains /var/cpanel/moddirdomains /var/cpanel/domainmap ); my $file2searchu; if ($IS_USERNAME) { $username = $QUERY; } else { $MAINDOMAIN = $QUERY; } if ($username) { if ( $username ne "nobody" ) { print "Searching the following files for user: " . BOLD MAGENTA $username . "\n"; foreach $file2searchu (@FILES2SEARCHUSER) { chomp($file2searchu); if ( !( -s ($file2searchu) ) ) { my $filestat = $file2searchu . " is either empty or missing"; my $fileskip = CYAN "[SKIPPING]"; print_output( $filestat, $fileskip ); next; } $filestatus = check_file_existance( $file2searchu, $username ); if ($filestatus) { $filestatus = GREEN "[EXISTS]"; } else { $filestatus = RED "[MISSING]"; } print_output( $file2searchu, $filestatus ); } } } else { print "No Username detected for $QUERY - skipping some checks!\n\n"; $skipMySQLCruft = 1; } if ($IS_USERNAME) { $username = $QUERY; } else { $MAINDOMAIN = $QUERY; } if ($MAINDOMAIN) { print "Searching the following files for domain: " . BOLD MAGENTA $MAINDOMAIN . "\n"; foreach $file2search (@FILES2SEARCH) { chomp($file2search); if ( !( -s ($file2search) ) ) { my $filestat = $file2search . " is either empty or missing"; my $fileskip = CYAN "[SKIPPING]"; print_output( $filestat, $fileskip ); next; } $filestatus = check_file_existance( $file2search, $MAINDOMAIN ); if ($filestatus) { $filestatus = GREEN "[EXISTS]"; } else { $filestatus = RED "[MISSING]"; } print_output( $file2search, $filestatus ); } } if ($username) { # Check for home directory and others to see if they exist my $hmCnt; my $dirstatus = check_dir("$HOMEDIR/$username"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "$HOMEDIR/$username", $dirstatus ); if ( $dirstatus =~ m/EXISTS/ ) { my @FoundHere = qw( etc mail public_html ssl tmp ); my $FoundHere; foreach $FoundHere (@FoundHere) { chomp($FoundHere); my $dirstatus = check_dir("$HOMEDIR/$username/$FoundHere"); if ($dirstatus) { $dirstatus = GREEN "EXISTS"; } else { $dirstatus = RED "MISSING"; } print expand( "\t \\_ " . $FoundHere . " - " . $dirstatus . "\n" ); } } # Check /var/cpanel/userdata/$username my $dirstatus = check_dir("/var/cpanel/userdata/$username"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/userdata/$username", $dirstatus ); if ( $dirstatus =~ m/EXISTS/ ) { my @FoundHere = qx[ egrep -srliw '$QUERY|$MAINDOMAIN' /var/cpanel/userdata/$username/* | grep -v 'cache' ]; my $FoundHere; foreach $FoundHere (@FoundHere) { chomp($FoundHere); print expand( "\t \\_ " . $FoundHere . "\n" ); } } # Check /var/cpanel/users/$username my $dirstatus = check_dir("/var/cpanel/users/$username"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/users/$username", $dirstatus ); if ( -e ("/var/cpanel/users/$username") ) { if ( $DNSLinesCnt != $TotalDomainCnt ) { if ( $DNSLinesCnt > $TotalDomainCnt ) { print expand( RED "\t \\_ [WARN]: There may be extra DNS lines in this file! [DNS: $DNSLinesCnt / Cnt: $TotalDomainCnt]\n"); } if ( $DNSLinesCnt < $TotalDomainCnt ) { print expand( RED "\t \\_ [WARN]: One or more DNS lines may be missing from this file! [DNS: $DNSLinesCnt / Cnt: $TotalDomainCnt]\n"); } shift @DNSLinesOnly; my %y = map { $_ => 1 } @DNSLinesOnly; my @diff1 = grep( !defined $y{$_}, @OtherDomains ); my %x = map { $_ => 1 } @OtherDomains; my @diff2 = grep( !defined $x{$_}, @DNSLinesOnly ); my $d1cnt=@diff1; my $d2cnt=@diff2; my @diff; if ( $d1cnt > 0 ) { @diff=@diff1; } else { @diff=@diff2; } if ( $DNSLinesCnt > $TotalDomainCnt ) { print expand( "\t\\_ The following domains should probably not be listed in /var/cpanel/users/$username:\n"); } if ( $DNSLinesCnt < $TotalDomainCnt ) { print expand( "\t\\_ The following domains might be missing from /var/cpanel/users/$username:\n"); } foreach my $diff (@diff) { chomp($diff); print expand( CYAN "\t\t\\_ " . $diff . "\n" ); } } } # Check /var/cpanel/overquota/$username my $dirstatus = check_dir("/var/cpanel/overquota/$username"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/overquota/$username", $dirstatus ); # Check /var/cpanel/authn/api_tokens_v2/whostmgr/$username.json my $dirstatus = check_dir("/var/cpanel/authn/api_tokens_v2/whostmgr/$username.json"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/authn/api_tokens_v2/whostmgr/$username.json", $dirstatus ); # Check /var/cpanel/mainips/$username my $dirstatus = check_dir("/var/cpanel/mainips/$username"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/mainips/$username", $dirstatus ); # Check if /var/cpanel/databases/grants_$username.yaml exists! my $dirstatus = check_dir("/var/cpanel/databases/grants_$username.yaml"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/databases/grants_$username.yaml", $dirstatus ); my $yaml_json = ( Cpanel::Version::compare( Cpanel::Version::getversionnumber(), '<', '11.50' ) ) ? "yaml" : "json"; my $dirstatus = check_dir("/var/cpanel/databases/$username.$yaml_json"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/databases/$username.$yaml_json", $dirstatus ); my $dbindex = ( Cpanel::Version::compare( Cpanel::Version::getversionnumber(), '<', '11.50') ) ? "/var/cpanel/databases/dbindex.db" : "/var/cpanel/databases/dbindex.db.json"; my $dirstatus = check_file_existance( $dbindex, $username ); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( $dbindex, $dirstatus ); my $dirstatus = check_dir("/etc/proftpd/$username"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/proftpd/$username", $dirstatus ); # Check for /var/cpanel/bandwidth/username.sqlite file. my $dirstatus = check_dir("/var/cpanel/bandwidth/$username.sqlite"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/bandwidth/$username.sqlite", $dirstatus ); if ($username) { my $dirstatus = check_dir("/var/cpanel/bwlimited/$username"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/bwlimited/$username", $dirstatus ); } if ($MAINDOMAIN) { my $dirstatus = check_dir("/var/cpanel/bwlimited/$MAINDOMAIN"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/bwlimited/$MAINDOMAIN", $dirstatus ); } } if ($IS_USERNAME) { if ($MAINDOMAIN) { my $dirstatus = check_dir("/etc/valiases/$MAINDOMAIN"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/valiases/$MAINDOMAIN", $dirstatus ); } else { print_output( "/etc/valiases", CYAN "[SKIPPING]" ); } if ($MAINDOMAIN) { my $dirstatus = check_dir("/etc/vfilters/$MAINDOMAIN"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/vfilters/$MAINDOMAIN", $dirstatus ); } else { print_output( "/etc/vfilters", CYAN "[SKIPPING]" ); } my $dirstatus = check_dir("/etc/vdomainaliases/$QUERY"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/vdomainaliases/$QUERY", $dirstatus ); if ( $MAINDOMAIN ) { my $dirstatus = check_dir("/var/cpanel/ssl/apache_tls/$MAINDOMAIN"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/ssl/apache_tls/$MAINDOMAIN", $dirstatus ); } else { print_output( "/var/cpanel/ssl/apache_tls/$QUERY", CYAN "[SKIPPING]" ); } if ($MAINDOMAIN) { my $dirstatus = check_dir("/var/named/$MAINDOMAIN.db"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/named/$MAINDOMAIN.db", $dirstatus ); } else { print_output( "/var/named/", CYAN "[SKIPPING]" ); } if ($MAINDOMAIN) { my $dirstatus = check_file_existance( '/etc/named.conf', 'zone "' . $MAINDOMAIN . '"' ); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/named.conf", $dirstatus ); } else { print_output( "/etc/named.conf", CYAN "[SKIPPING]" ); } # RIGHT HERE # Check /etc/apache2/logs/domlogs/$QUERY if ($isAddon) { my $SubDomain = ( split( /\./, $QUERY ) )[0] . "." . $MAINDOMAIN; chomp($SubDomain); my $dirstatus = check_dir("/etc/apache2/logs/domlogs/$SubDomain"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$SubDomain", $dirstatus ); } else { if ($MAINDOMAIN) { my $dirstatus = check_dir("/etc/apache2/logs/domlogs/$MAINDOMAIN"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$MAINDOMAIN", $dirstatus ); my $dirstatus = check_dir( "/etc/apache2/logs/domlogs/$QUERY-ssl_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$QUERY-ssl_log", $dirstatus ); my $dirstatus = check_dir( "/etc/apache2/logs/domlogs/$QUERY-bytes_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$QUERY-bytes_log", $dirstatus ); } else { print_output( "/etc/apache2/logs/domlogs/", CYAN "[SKIPPING]" ); } } if ($MAINDOMAIN) { my $dirstatus = check_file_existance( "/etc/apache2/conf/httpd.conf", $MAINDOMAIN ); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/conf/httpd.conf", $dirstatus ); my $dirstatus = check_dir( "/etc/apache2/logs/domlogs/$MAINDOMAIN-ssl_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$MAINDOMAIN-ssl_log", $dirstatus ); my $dirstatus = check_dir( "/etc/apache2/logs/domlogs/$MAINDOMAIN-bytes_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$MAINDOMAIN-bytes_log", $dirstatus ); } else { print_output( "/etc/apache2/logs/domlogs/", CYAN "[SKIPPING]" ); } # Check /var/log/nginx/domains/$QUERY if ($isAddon) { my $SubDomain = ( split( /\./, $QUERY ) )[0] . "." . $MAINDOMAIN; chomp($SubDomain); my $dirstatus = check_dir("/var/log/nginx/domains/$SubDomain"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/log/nginx/domains/$SubDomain", $dirstatus ); } else { if ($MAINDOMAIN) { my $dirstatus = check_dir("/var/log/nginx/domains/$MAINDOMAIN"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/log/nginx/domains/$MAINDOMAIN", $dirstatus ); my $dirstatus = check_dir( "/var/log/nginx/domains/$QUERY-ssl_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/log/nginx/domains/$QUERY-ssl_log", $dirstatus ); my $dirstatus = check_dir( "/var/log/nginx/domains/$QUERY-bytes_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/log/nginx/domains/$QUERY-bytes_log", $dirstatus ); } else { print_output( "/var/log/nginx/domains/", CYAN "[SKIPPING]" ); } } } else { ## Domain name was entered (check $QUERY). my $dirstatus = check_dir("/etc/valiases/$QUERY"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/valiases/$QUERY", $dirstatus ); my $dirstatus = check_dir("/etc/vfilters/$QUERY"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/vfilters/$QUERY", $dirstatus ); my $dirstatus = check_dir("/etc/vdomainaliases/$QUERY"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/vdomainaliases/$QUERY", $dirstatus ); my $SubDomain = ( split( /\./, $QUERY ) )[0] . "." . $MAINDOMAIN; chomp($SubDomain); my $dirstatus=0; if ( $isAddon ) { $dirstatus = check_dir("/var/cpanel/ssl/apache_tls/$SubDomain"); } else { $dirstatus = check_dir("/var/cpanel/ssl/apache_tls/$QUERY"); } if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/cpanel/ssl/apache_tls/$QUERY", $dirstatus ); my $dirstatus = check_dir("/var/named/$QUERY.db"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/var/named/$QUERY.db", $dirstatus ); my $dirstatus = check_dir("/etc/apache2/logs/domlogs/$QUERY"); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$QUERY", $dirstatus ); my $dirstatus = check_dir( "/etc/apache2/logs/domlogs/$QUERY-ssl_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$QUERY-ssl_log", $dirstatus ); my $dirstatus = check_dir( "/etc/apache2/logs/domlogs/$QUERY-bytes_log" ); if ( $dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/logs/domlogs/$QUERY-bytes_log", $dirstatus ); # Check /etc/named.conf file my $dirstatus = check_file_existance( '/etc/named.conf', 'zone "' . $QUERY . '"' ); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/named.conf", $dirstatus ); my $dirstatus = check_file_existance( "/etc/apache2/conf/httpd.conf", $MAINDOMAIN ); if ($dirstatus) { $dirstatus = GREEN "[EXISTS]"; } else { $dirstatus = RED "[MISSING]"; } print_output( "/etc/apache2/conf/httpd.conf", $dirstatus ); } # Check for DNS Clustering if ( -e "/var/cpanel/useclusteringdns" ) { print "Found DNS Cluster - checking...\n"; opendir( CLUSTERS, "/var/cpanel/cluster/root/config" ); my @DNSCLUSTERS = readdir(CLUSTERS); closedir(CLUSTERS); my ( $dnscluster, $QueryCluster ); foreach $dnscluster (@DNSCLUSTERS) { chomp($dnscluster); if ( $dnscluster eq "." or $dnscluster eq ".." or $dnscluster =~ m/dnsrole/ or $dnscluster =~ m/json/ or $dnscluster =~ m/error_log/ or $dnscluster =~ m/.cache/ ) { next; } if ($IS_USERNAME) { $QueryCluster = qx[ dig +tries=2 +time=5 \@$dnscluster $MAINDOMAIN +short ]; if ($QueryCluster) { print expand( YELLOW "\t \\_ $MAINDOMAIN " . GREEN ON_BLACK . "was found in " . YELLOW $dnscluster . "\n" ); } else { print expand( YELLOW "\t \\_ $MAINDOMAIN " . RED . "NOT found in " . YELLOW $dnscluster . "\n" ); } } elsif ($TheAddonDomain) { $QueryCluster = qx[ dig +tries=2 +time=5 \@$dnscluster $TheAddonDomain +short ]; if ($QueryCluster) { print expand( YELLOW "\t \\_ $TheAddonDomain " . GREEN ON_BLACK . "was found in " . $dnscluster . "\n" ); } else { print expand( YELLOW "\t \\_ $TheAddonDomain " . RED . "NOT found in " . YELLOW $dnscluster . "\n" ); } } else { $QueryCluster = qx[ dig +tries=2 +time=5 \@$dnscluster $QUERY +short ]; if ($QueryCluster) { print expand( YELLOW "\t \\_ $QUERY " . GREEN ON_BLACK . "was found in " . $dnscluster . "\n" ); } else { print expand( YELLOW "\t \\_ $QUERY " . RED . "NOT found in " . YELLOW $dnscluster . "\n" ); } } } } # Check MySQL users table. if ( $skipMySQLCruft == 0 ) { my $UserDbsJSON = get_uapi( 'Mysql', 'list_databases', "--user=$username" ); print YELLOW "MySQL Databases:\n"; if ( $UserDbsJSON->{result}->{errors}->[0] ) { print expand( RED "$UserDbsJSON->{result}->{errors}->[0]\n" ); } my $DBCnt = 0; for my $UserDb ( @{ $UserDbsJSON->{result}->{data} } ) { $DBCnt++; print expand( WHITE "\t \\_ " . $UserDb->{database} . "\n" ); } if ( $DBCnt == 0 ) { print expand( WHITE "\t \\_ None Found\n" ); } } print YELLOW "Checking for any MySQL users in mysql.user table\n"; my $DBusername = timed_run( 3, 'mysql', '-BNe', "SELECT DISTINCT User FROM mysql.user WHERE User LIKE '$username\\_%'" ); $DBusername .= timed_run( 3, 'mysql', '-BNe', "SELECT DISTINCT User FROM mysql.user WHERE User = '$username'" ); my @DBusernames = split( '\n', $DBusername ); my $DBUserCnt = @DBusernames; if ( $DBUserCnt >= 1 ) { my $NewDBUser; foreach $NewDBUser (@DBusernames) { chomp($NewDBUser); print expand( WHITE "\t \\_ " . $NewDBUser . "\n" ); } } else { print expand( WHITE "\t \\_ None\n" ); } print YELLOW "Checking for any MySQL users and databases in mysql.db table\n"; my $DBusername = timed_run( 3, 'mysql', '-BNe', "SELECT DISTINCT User,Db FROM mysql.db WHERE User LIKE '$username\\_%'" ); $DBusername .= timed_run( 3, 'mysql', '-BNe', "SELECT DISTINCT User,Db FROM mysql.db WHERE User = '$username'" ); my @DBusernames = split( '\n', $DBusername ); my $DBUserCnt = @DBusernames; if ( $DBUserCnt >= 1 ) { my $NewDBUser; foreach $NewDBUser (@DBusernames) { chomp($NewDBUser); print expand( WHITE "\t \\_ " . $NewDBUser . "\n" ); } } else { print expand( WHITE "\t \\_ None\n" ); } # Check postgres my $psql_runningJSON = get_whmapi1( 'servicestatus', "service=postgresql" ); my $psql_running = $psql_runningJSON->{data}->{service}->[0]->{running}; if ( $skipMySQLCruft == 0 and $psql_running ) { my $UserDbsJSON = get_cpapi2( 'Postgres', 'listdbs', "--user=$username" ); my $UserDb; my $DBCnt = 0; print YELLOW "PostGreSQL Databases:\n"; for my $UserDb ( @{ $UserDbsJSON->{cpanelresult}->{data} } ) { for my $DbUser ( @{ $UserDb->{userlist} } ) { $DBCnt++; print expand( WHITE "\t \\_ " . $UserDb->{db} . "\n" ); } } if ( $DBCnt == 0 ) { print expand( WHITE "\t \\_ None Found\n" ); } } border(); if ($debugHookChanged) { get_whmapi1( 'set_tweaksetting', 'key=debughooks', 'value=logall' ); } exit; } sub check_file_existance { my $TheFile = $_[0]; my $TheSearchString = $_[1]; my $FoundLine = ""; if ( -e ($TheFile) ) { open my $fh, '<:encoding(UTF-8)', $TheFile; while ( my $line = <$fh> ) { if ( $line =~ /$TheSearchString/ ) { close($fh); return 1; } } close($fh); return 0; } } sub print_output { my $DisplayName = $_[0]; my $TheStatus = $_[1]; my $maxwidth = 30; my $spacer = 0; my $len = length($DisplayName); $spacer = ( $maxwidth - $len ) + 50; print YELLOW "$DisplayName"; printf "%" . $spacer . "s", $TheStatus; print "\n"; select( undef, undef, undef, 0.25 ); } sub check_dir() { my $Dir2Check = $_[0]; if ( -e ($Dir2Check) ) { return 1; } else { return 0; } } sub uniq { my %seen; grep !$seen{$_}++, @_; } # Taken from ssp - sub version_cmp { # should only be used by version_compare() no warnings 'uninitialized' ; # Prevent uninitialized value warnings when not using all 4 values my ( $a1, $b1, $c1, $d1 ) = split /[\._]/, $_[0]; my ( $a2, $b2, $c2, $d2 ) = split /[\._]/, $_[1]; return $a1 <=> $a2 || $b1 <=> $b2 || $c1 <=> $c2 || $d1 <=> $d2; } sub version_compare { # example: return if version_compare($ver_string, qw( >= 1.2.3.3 )); # Must be no more than four version numbers separated by periods and/or underscores. my ( $ver1, $mode, $ver2 ) = @_; return if ( $ver1 =~ /[^\._0-9]/ ); return if ( $ver2 =~ /[^\._0-9]/ ); # Shamelessly copied the comparison logic out of Cpanel::Version::Compare my %modes = ( '>' => sub { return if $_[0] eq $_[1]; return version_cmp(@_) > 0; }, '<' => sub { return if $_[0] eq $_[1]; return version_cmp(@_) < 0; }, '==' => sub { return $_[0] eq $_[1] || version_cmp(@_) == 0; }, '!=' => sub { return $_[0] ne $_[1] && version_cmp(@_) != 0; }, '>=' => sub { return 1 if $_[0] eq $_[1]; return version_cmp(@_) >= 0; }, '<=' => sub { return 1 if $_[0] eq $_[1]; return version_cmp(@_) <= 0; } ); return if ( !exists $modes{$mode} ); return $modes{$mode}->( $ver1, $ver2 ); } # ripped from /usr/local/cpanel/Cpanel/Sys/OS.pm sub get_release_version { my $ises = 0; my $ver; if ( open my $fh, '<', '/etc/redhat-release' ) { my $line = readline $fh; close $fh; chomp $line; if ( $line =~ m/(?:Corporate|Advanced\sServer|Enterprise)/i ) { $ises = 1; } elsif ( $line =~ /CloudLinux|CentOS/i ) { $ises = 2; } elsif ( $line =~ /WhiteBox/i ) { $ises = 3; } elsif ( $line =~ /caos/i ) { $ises = 4; } if ( $line =~ /(\d+\.\d+)/ ) { $ver = $1; } elsif ( $line =~ /(\d+)/ ) { $ver = $1; } } if ($ises) { return ( $ver, $ises ); } else { return ( $ver, 0 ); } } sub check_for_cagefs() { return unless ( -e ("/usr/sbin/cagefsctl") ); my $tcageFSStats = Cpanel::SafeRun::Timed::timedsaferun( 0, '/usr/sbin/cagefsctl', '--user-status', "$username" ); chomp($tcageFSStats); return $tcageFSStats; } sub get_php_version { my $tcDomain = shift; my $searchFor = ($IS_USERNAME) ? "account" : "vhost"; my $php_version_data = get_whmapi1('php_get_vhost_versions'); for my $phpVhosts ( @{ $php_version_data->{data}->{versions} } ) { if ( $searchFor eq 'account' ) { ## We only want the main domain if ( $phpVhosts->{$searchFor} eq $tcDomain && $phpVhosts->{main_domain} ) { if ( defined( $phpVhosts->{phpversion_source}->{system_default})) { return $phpVhosts->{documentroot}, $phpVhosts->{vhost}, 0, "", "", "", "inherit"; } else { if ( $phpVhosts->{php_fpm} ) { return $phpVhosts->{documentroot}, $phpVhosts->{phpversion_source}->{domain}, 1, $phpVhosts->{php_fpm_pool_parms}->{pm_max_children}, $phpVhosts->{php_fpm_pool_parms}->{pm_max_requests}, $phpVhosts->{php_fpm_pool_parms}->{pm_process_idle_timeout}, $phpVhosts->{version}; } else { return $phpVhosts->{documentroot}, $phpVhosts->{phpversion_source}->{domain}, 0, "", "", "", $phpVhosts->{version}; } } } } else { ## We are looking for specific vhost if ( $phpVhosts->{$searchFor} eq $tcDomain ) { if ( defined( $phpVhosts->{phpversion_source}->{system_default})) { return $phpVhosts->{documentroot}, $phpVhosts->{vhost}, 0, "", "", "", "inherit"; } else { if ( $phpVhosts->{php_fpm} ) { return $phpVhosts->{documentroot}, $phpVhosts->{phpversion_source}->{domain}, 1, $phpVhosts->{php_fpm_pool_parms}->{pm_max_children}, $phpVhosts->{php_fpm_pool_parms}->{pm_max_requests}, $phpVhosts->{php_fpm_pool_parms}->{pm_process_idle_timeout}, $phpVhosts->{version}; } else { return $phpVhosts->{documentroot}, $phpVhosts->{phpversion_source}->{domain}, 0, "", "", "", $phpVhosts->{version}; } } } } } } sub get_system_php_version() { my $phpDefaultJSON = get_whmapi1('php_get_system_default_version'); my $phpDefault = $phpDefaultJSON->{data}->{version}; return $phpDefault; } sub alltrim() { my $string2trim = $_[0]; $string2trim =~ s/^\s*(.*?)\s*$/$1/; return $string2trim; } sub display_mail_info { if ($acct_over_quota) { border(); print RED "Account $username is over quota - Cannot list email data!\n"; border(); return; } # CHECK $ProFileNode here # If DNSNODE, return # IF MAILNODE limit some of the output below (passwd/shadow files, smtpF0x/AnonymousF0x, shadow.roottn hacks, & any $RealHome path info). if ( $ProfileNode eq "DNSNODE" ) { print RED "$ProfileNode has no mail capabilities!\n"; return; } if ($IS_USERNAME) { $DOMAIN = $MAINDOMAIN; } else { $DOMAIN = $QUERY; } my @sortedPops; my $emailacctline; my $qused; my $qlimit; my $qpercent; my $localpart; my $emailPopCnt = 0; my $emailacctline; my @sortedPops; my $ListPopsJSON; $ListPopsJSON = get_uapi( 'Email', 'list_pops_with_disk', "--user=$username", "domain=$DOMAIN"); # Check for suspended/held outgoing email chk_mail_suspend( $username, 0 ); chk_mail_hold( $username, 0 ); smborder(); my @listPops; for my $EmailPop ( @{ $ListPopsJSON->{result}->{data} } ) { $emailacctline = $EmailPop->{email}; $qused = ( $EmailPop->{humandiskused} eq 'None' ) ? $EmailPop->{diskused} : $EmailPop->{humandiskused}; $qlimit = ( $EmailPop->{humandiskquota} eq 'None' ) ? "Unlimited/None" : $EmailPop->{humandiskquota}; $qpercent = $EmailPop->{diskusedpercent20}; $localpart = $EmailPop->{user}; push( @listPops, "$emailacctline||$qused||$qlimit||$qpercent||$localpart\n" ); } @sortedPops = sort(@listPops); $emailPopCnt = @sortedPops; if ( $ProfileNode =~ m/STANDARD|UNKNOWN/ ) { #Check for variants of the shadow.roottn.bak hack my $shadow_roottn_baks = qx[ find $RealHome/etc/$DOMAIN/ -name 'shadow\.*' -print ] unless ( !-e ("$RealHome/etc/$DOMAIN") ); if ($shadow_roottn_baks) { chomp($shadow_roottn_baks); print expand( RED "\t\\_ [WARN] - Possible variant of the shadow.roottn.bak hack found in $RealHome/etc/$DOMAIN/\n" ); print expand( YELLOW "\t\t\\_ $shadow_roottn_baks\n" ); print expand( RED "\t\t\\_ Account may have been compromised! [Use [PREDEFS > SECURITY] - shadow.roottn.bak exploit macro]\n" ); } # Check for AnonymousF0x/smtpF0x hacks my $hassmtpF0x = qx[ find $RealHome/etc/* -name 'shadow' -print | xargs egrep -li 'anonymousfox-|smtpf0x-|anonymousfox|smtpf' ]; chomp($hassmtpF0x); my @hassmtpF0x = split "\n", $hassmtpF0x; if (@hassmtpF0x) { print expand( RED "\t\\_ [WARN] - Found evidence of the AnonymousF0x/smtpF0x hack in the following:\n" ); my $foundsmtpF0x; foreach $foundsmtpF0x (@hassmtpF0x) { print expand( WHITE "\t\t\\_ $foundsmtpF0x\n" ); } } } # Check for /etc/manualmx file if ( -s '/etc/manualmx' ) { my $manual_mx_entry = Cpanel::SafeRun::Timed::timedsaferun( 5, 'egrep', "^$DOMAIN", '/etc/manualmx' ); print expand( YELLOW "[INFO] - " . $DOMAIN . BOLD CYAN " has an entry in /etc/manualmx." . YELLOW "\n\t\\_ $manual_mx_entry\n" ) unless ( !$manual_mx_entry ); } # Check for the domain being in /etc/vdomainaliases/ directory. if ( -s ("/etc/vdomainaliases/$DOMAIN") ) { print YELLOW "[INFO] - " . $DOMAIN . BOLD CYAN " is listed in the /etc/vdomainaliases/ directory. " . YELLOW "\n\t\\_ Existing accounts/autoresponders will NOT forward!\n"; } print "$DOMAIN has $emailPopCnt Email accounts: \n\n"; foreach my $EmailPop (@sortedPops) { chomp($EmailPop); ( $emailacctline, $qused, $qlimit, $qpercent, $localpart ) = ( split( /\|\|/, $EmailPop ) ); my $over_quota_warn = ""; $over_quota_warn = RED "[ OVER QUOTA ]" unless( $qpercent < 100 ); print expand( CYAN $emailacctline . "\n" ); print expand( "\t\\_ [Quota Used " . $qused . " MB of " . $qlimit . " (" . $qpercent . "%)] $over_quota_warn\n" ); if ( $ProfileNode =~ m/STANDARD|UNKNOWN/ ) { # Check for .boxtrapperenable touch file - enabled if it exists. if ( -e ("$RealHome/etc/$DOMAIN/$localpart/.boxtrapperenable") ) { print expand( YELLOW "\t \\_ Spam Boxtrapper Enabled\n" ); } # Check for default webmail app my $DefWebMailApp; if ( -e ( "$RealHome/.cpanel/nvdata/$emailacctline\_default_webmail_app" ) ) { $DefWebMailApp = qx[ cat "$RealHome/.cpanel/nvdata/$emailacctline\_default_webmail_app" ]; $DefWebMailApp = ucfirst($DefWebMailApp); print expand( YELLOW "\t \\_ Default Webmail Client: $DefWebMailApp\n" ); } # Check for mailbox_format.cpanel file. Display contents if it exists if ( -e ("$RealHome/mail/$DOMAIN/$localpart/mailbox_format.cpanel") ) { my $mbformat = qx [ cat "$RealHome/mail/$DOMAIN/$localpart/mailbox_format.cpanel" ]; chomp($mbformat); print expand( YELLOW "\t \\_ Account is using the $mbformat format.\n" ); } # check for mail hold/suspended/incoming email suspended, login disabled/suspended chk_mail_suspend( "$emailacctline", 1 ); chk_mail_hold( "$emailacctline", 1 ); chk_login_disabled( "$RealHome/etc/$DOMAIN", $localpart ); chk_suspended_mail( "$RealHome/etc/$DOMAIN/$localpart" ); if ( $ProfileNode =~ m/STANDARD|UNKNOWN/ ) { # Check rcube.db for corruption if ( -e ("$RealHome/etc/$DOMAIN/$localpart.rcube.db") ) { my $rcubechk = SQLiteDBChk("$RealHome/etc/$DOMAIN/$localpart.rcube.db"); print expand( RED "\t \\_ $RealHome/etc/$DOMAIN/$localpart.rcube.db might be corrupted.\n" ) unless ( $rcubechk =~ m/ok/ ); } # Check for dovecot-acl file my $dovecotACL = timed_run( 4, 'find', "$RealHome/mail/$DOMAIN/$localpart", '-name', 'dovecot-acl' ); chomp($dovecotACL); if ($dovecotACL) { print expand( RED "\t \\_ [WARN] - Found $dovecotACL - can cause odd permission issues.\n" ) unless ( !-s $dovecotACL ); } # Check for dovecot-uidlist.lock file if ( -e ( "$RealHome/mail/$DOMAIN/$emailacctline/dovecot-uidlist.lock" ) ) { print expand( RED "\t \\_ [WARN] - Found dovecot-uidlist.lock file in $RealHome/mail/$DOMAIN/$emailacctline/ \n\t\tWebmail acting strangely? - remove it and see if resolved.\n" ); } } } # Check for cphulkd blocks (if --cphulkblocks flag was passed) my $BlockCnt = 0; my $isCPHulkEnabledJSON = get_whmapi1('cphulk_status'); my $isCPHulkEnabled = $isCPHulkEnabledJSON->{data}->{is_enabled}; if ( $isCPHulkEnabled and $chk_cph_blocks ) { my $cPHBlock = timed_run( 3, 'whmapi1', 'get_cphulk_failed_logins' ); my @cPHBlocks = split( / /, $cPHBlock ); my $cPHBlockLine = ""; foreach $cPHBlockLine (@cPHBlocks) { if ( $cPHBlockLine =~ m/$emailacctline/ ) { $BlockCnt++; } } if ( $BlockCnt > 0 ) { print expand( RED "\t \\_ [WARN] - $emailacctline has at least $BlockCnt brute force blocks detected via cPHulkd\n" ); } } # user level mail filters my $UserFilterJSON = get_uapi( 'Email', 'list_filters', "--user=$username", "account=$localpart%40$DOMAIN" ); my $ShowHeader = 0; for my $UserFilter ( @{ $UserFilterJSON->{result}->{data} } ) { print expand( YELLOW "\t \\_ has the following user level filters\n" ) unless ($ShowHeader); $ShowHeader = 1; print expand( WHITE "\t\t \\_ $UserFilter->{filtername}\n" ); } } # Check for missing passwd and/or shadow files if ( $ProfileNode =~ m/STANDARD|UNKNOWN/ ) { if ( !-e "$RealHome/etc/$DOMAIN/passwd" && $emailPopCnt > 0 ) { print RED "[WARN] - $RealHome/etc/$DOMAIN/passwd file is missing!\n"; } if ( !-e "$RealHome/etc/$DOMAIN/shadow" && $emailPopCnt > 0 ) { print RED "[WARN] - $RealHome/etc/$DOMAIN/shadow file is missing!\n"; } my $passwdCnt = 0; my $shadowCnt = 0; if ( -e "$RealHome/etc/$DOMAIN/passwd" ) { ($passwdCnt) = ( split( /\s+/, qx[ wc -l $RealHome/etc/$DOMAIN/passwd ] ) )[0]; } if ( -e "$RealHome/etc/$DOMAIN/shadow" ) { ($shadowCnt) = ( split( /\s+/, qx[ wc -l $RealHome/etc/$DOMAIN/shadow ] ) )[0]; } if ( $passwdCnt ne $shadowCnt || $passwdCnt ne $emailPopCnt || $shadowCnt ne $emailPopCnt ) { print RED "[WARN] - passwd/shadow count does not equal total Email accounts\n"; print expand( WHITE "\t\\_ passwd: $passwdCnt / shadow: $shadowCnt / Email: $emailPopCnt\n" ); } smborder(); } # Now let's get the MX record and make sure the A record for it points to this server. print "Checking MX records for $DOMAIN...\n"; my @MXRecords = getMXrecord($DOMAIN); my $myline; my $skipMXchk = 0; foreach $myline (@MXRecords) { chomp($myline); if ( $myline eq "NONE" ) { $skipMXchk = 1; last; } } my $IsRemote = 0; my $MXRecord; my $Is_IP_OnServer; if ( !$skipMXchk ) { if (@MXRecords) { foreach $MXRecord (@MXRecords) { chomp($MXRecord); my $ARecordForMX; my @ARecordForMX = getArecords($MXRecord); foreach $ARecordForMX (@ARecordForMX) { chomp($ARecordForMX); my $IS_NAT = check_for_nat($ARecordForMX); if ($IS_NAT) { ## NAT IP ADDRESS RETURNED! $Is_IP_OnServer = qx[ ip addr | grep '$IS_NAT' ]; if ($Is_IP_OnServer) { print expand( YELLOW "\t \\_ $MXRecord resolves to $ARecordForMX => $IS_NAT (Configured on this server)\n" ); # Check reverse my $ReverseOfMX = getptr($MXRecord); if ( $ReverseOfMX eq $MXRecord ) { print expand( GREEN "\t\t \\_ [OK] - $ARecordForMX reverses back to the MX: $MXRecord\n" ); } elsif ( $ReverseOfMX eq $HOSTNAME ) { print expand( GREEN "\t\t \\_ [OK] - $ARecordForMX reverses back to the hostname: $HOSTNAME\n" ); } elsif ( $ReverseOfMX eq "mail.$MXRecord" ) { print expand( GREEN "\t\t \\_ [OK] - $ARecordForMX reverses back to: mail.$MXRecord\n" ); } else { if ( $ReverseOfMX eq "" ) { $ReverseOfMX = "[NXDOMAIN]"; } print expand( RED "\t\t \\_ [WARN] - $ARecordForMX reverses back to: $ReverseOfMX\n" ); } } else { print expand( YELLOW "\t \\_ $MXRecord resolves to $ARecordForMX (NOT configured on this server)\n" ); $IsRemote = 1; } } else { ## NO NAT FOUND! $Is_IP_OnServer = qx[ ip addr | grep '$ARecordForMX' ]; if ($Is_IP_OnServer) { print expand( YELLOW "\t \\_ $MXRecord resolves to $ARecordForMX (Configured on this server)\n" ); # Check reverse my $ReverseOfMX = getptr($MXRecord); if ( $ReverseOfMX eq $MXRecord ) { print expand( GREEN "\t\t \\_ [OK] - $ARecordForMX reverses back to the MX: $MXRecord\n" ); } elsif ( $ReverseOfMX eq $HOSTNAME ) { print expand( GREEN "\t\t \\_ [OK] - $ARecordForMX reverses back to the hostname $HOSTNAME\n" ); } elsif ( $ReverseOfMX eq "mail.$MXRecord" ) { print expand( GREEN "\t\t \\_ [OK] - $ARecordForMX reverses back to mail.$MXRecord\n" ); } else { if ( $ReverseOfMX eq "" ) { $ReverseOfMX = "[NXDOMAIN]"; } print expand( RED "\t\t \\_ [WARN] - $ARecordForMX reverses back to $ReverseOfMX\n" ); } } else { print expand( YELLOW "\t \\_ $MXRecord resolves to $ARecordForMX (NOT configured on this server)\n" ); $IsRemote = 1; } } print expand( RED "\t\t \\_ [WARN] - MX Record should not be an IP address (violates RFC-1035)\n" ) unless ( !Cpanel::Validate::IP::is_valid_ip($MXRecord) ); } } } } else { print expand( CYAN "\t \\_ None\n" ); } # Depending on whether $IsRemote is true or false, we check if the domain is listed in # /etc/localdomains or /etc/remotedomains smborder(); my $IsInRemoteDomains = qx[ egrep '^$DOMAIN' /etc/remotedomains ]; my $IsInLocalDomains = qx[ egrep '^$DOMAIN' /etc/localdomains ]; chomp($IsInRemoteDomains); chomp($IsInLocalDomains); print "Checking email routing (based on MX check above)...\n"; if ($IsRemote) { if ($IsInRemoteDomains) { print expand( GREEN "\t \\_ [OK] - $DOMAIN is listed in /etc/remotedomains\n" ); $IsInLocalDomains = qx[ egrep '^$DOMAIN' /etc/localdomains ]; if ($IsInLocalDomains) { print expand( RED "\t \\_ [WARN] - $DOMAIN was found in /etc/localdomains\n" ); } } else { print expand( RED "\t \\_ [WARN] - $DOMAIN is missing from /etc/remotedomains\n" ); if ($IsInLocalDomains) { print expand( RED "\t \\_ [WARN] - $DOMAIN was found in /etc/localdomains\n" ); print expand( YELLOW "\t \\_ [NOTE] - OK if MX record is pointing to an external anti-spam service/gateway\n" ); } } } else { ## is local if ($IsInLocalDomains) { print expand( GREEN "\t \\_ [OK] - $DOMAIN is listed in /etc/localdomains\n" ); $IsInRemoteDomains = qx[ egrep '^$DOMAIN' /etc/remotedomains ]; if ($IsInRemoteDomains) { print expand( RED "\t \\_ [WARN] - $DOMAIN was found in /etc/remotedomains\n" ); } } else { print expand( RED "\t \\_ [WARN] - $DOMAIN is missing from /etc/localdomains\n" ); if ($IsInRemoteDomains) { print expand( RED "\t \\_ [WARN] - $DOMAIN was found in /etc/remotedomains\n" ); } } } smborder(); # user/global forwarders my $UserForwarderJSON = get_uapi( 'Email', 'list_forwarders', "--user=$username", "account=$DOMAIN" ); print expand( YELLOW "Forwarders\n" ); for my $UserForwarder ( @{ $UserForwarderJSON->{result}->{data} } ) { print expand( WHITE "\t\\_ $UserForwarder->{dest} => $UserForwarder->{forward}\n" ); } my $DefaultAddressJSON = get_uapi( 'Email', 'list_default_address', "--user=$username", "domain=$DOMAIN" ); my $DefaultAddress = $DefaultAddressJSON->{result}->{data}->[0]->{defaultaddress}; my $DAtext = " (forward all unrouted email to $DefaultAddress)\n"; if ( $DefaultAddress eq $username ) { $DAtext = " (deliver to $username main account)\n"; } if ( $DefaultAddress =~ m/:fail:/ ) { $DAtext = " (fail - bounce with message)\n"; } if ( $DefaultAddress =~ m/\|/ ) { $DAtext = " (pipe to program)\n"; } if ( $DefaultAddress =~ m/:blackhole:/ ) { $DAtext = " (discard [not recommended])\n"; } print expand( WHITE "\t\\_ DEFAULT: " . YELLOW "*: $DefaultAddress $DAtext" ); smborder(); # List any Mailing Lists my $MailmanJSON = get_uapi( 'Email', 'list_lists', "--user=$username", "account=$DOMAIN" ); print expand( YELLOW "Mailing Lists (Mailman)\n" ); for my $mailmanlist ( @{ $MailmanJSON->{result}->{data} } ) { print expand( WHITE "\t\\_ List: " . CYAN $mailmanlist->{list} . WHITE " [ID: " . CYAN $mailmanlist->{listid} . WHITE "]\n\t\t\\_Type: " . CYAN $mailmanlist->{accesstype} . WHITE " Admin: " . CYAN $mailmanlist->{listadmin} . WHITE " Size: " . CYAN $mailmanlist->{humandiskused} . "\n" ); } smborder(); # Check for blocked country codes print expand( YELLOW "Blocked Country Codes\n" ); chk_for_blocked_country_codes(); # get any system level filters (from /etc/vfilters/$DOMAIN file) my $gfilter; smborder(); print "Global Level (system) Filters:\n"; open( GFILTFILE, "/etc/vfilters/$DOMAIN" ); my @GFILTER = ; my $gfiltercnt = 0; my @gfiltname; close(GFILTFILE); if (@GFILTER) { foreach $gfilter (@GFILTER) { if ( substr( $gfilter, 0, 2 ) =~ m/# / or substr( $gfilter, 0, 2 ) =~ m/#$/ ) { next; } if ( substr( $gfilter, 0, 1 ) eq "#" ) { push( @gfiltname, substr( $gfilter, 1 ) ); $gfiltercnt++; } } print expand( CYAN "\t\\_ " . $DOMAIN . " has " . $gfiltercnt . " global level filters\n" ); my $glistfilt; foreach $glistfilt (@gfiltname) { chomp($glistfilt); print expand( YELLOW "\t\t\\_ Name: " . $glistfilt . "\n" ); } } else { print expand( CYAN "\t \\_ None\n" ); } smborder(); my $valSPFJSON = get_whmapi1( 'validate_current_spfs', "domain=$DOMAIN" ); print "Checking SPF Record For $DOMAIN\n"; my $spfstate = $valSPFJSON->{data}->{payload}->[0]->{state}; my $spfreason = $valSPFJSON->{data}->{payload}->[0]->{records}->[0]->{reason}; my $spf = $valSPFJSON->{data}->{payload}->[0]->{records}->[0]->{current}; my $spfrstate = $valSPFJSON->{data}->{payload}->[0]->{records}->[0]->{state}; print expand( YELLOW "\t \\_ " . $spf . "\n" ) unless ( !$spf ); if ( $spfrstate eq "FAIL" ) { $spfrstate = "HARD FAIL"; } if ( $spfstate eq "MISSING" ) { print expand( RED "\t \\_ [ " . $spfstate . " ]\n" ); } if ( $spfstate eq "VALID" ) { print expand( GREEN "\t \\_ " . " [ " . $spfstate . " ]\n" ); } else { print expand( RED "\t \\_ " . $spfstate . ": " . $spfreason . "\n" ) unless ( !$spfreason ); } print "\n"; print "Checking DKIM Record For default._domainkey.$DOMAIN\n"; my $valDKIMJSON = get_whmapi1( 'validate_current_dkims', "domain=$DOMAIN" ); my $dkimrstate = $valDKIMJSON->{data}->{payload}->[0]->{state}; my $dkimcurrent = $valDKIMJSON->{data}->{payload}->[0]->{records}->[0]->{current}; my $dkimexpected; if ( $dkimrstate ne "VALID" ) { $dkimexpected = $valDKIMJSON->{data}->{payload}->[0]->{expected}; } print expand( YELLOW "\t \\_ CURRENT KEY: " . $dkimcurrent . "\n" ) unless ( !$dkimcurrent ); if ( $dkimrstate eq "VALID" ) { print expand( GREEN "\t \\_ [ " . $dkimrstate . " ]\n" ); } else { print expand( RED "\t \\_ [ " . $dkimrstate . " ]\n" ); print expand( RED "\t\t \\_ EXPECTED KEY: " . $dkimexpected . "\n" ); } print "\n"; print "Verifying PTR Record For $DOMAIN\n"; my $valPTRJSON = get_whmapi1( 'validate_current_ptrs', "domain=$DOMAIN" ); my $ptrstate = $valPTRJSON->{data}->{payload}->[0]->{state}; my $ptrdomain = $valPTRJSON->{data}->{payload}->[0]->{ptr_records}->[0]->{domain}; my $ptripAddr = $valPTRJSON->{data}->{payload}->[0]->{ip_address}; my $ptrHELO = $valPTRJSON->{data}->{payload}->[0]->{helo}; if ( $ptrstate eq "VALID" ) { print expand( GREEN "\t \\_ [ " . $ptrstate . " ]\n" ); } else { print expand( RED "\t \\_ [ " . $ptrstate . " ]\n" ); } print expand( YELLOW "\t\t \\_ Domain " . CYAN $ptrdomain . "\n" ); print expand( YELLOW "\t\t \\_ HELO: " . CYAN $ptrHELO . "\n" ); print expand( YELLOW "\t\t \\_ IP Address: " . CYAN $ptripAddr . "\n" ); print "\n"; # Check for DMARC record. my $dmarc = qx[ dig +tries=2 +time=5 \@208.67.222.222 _dmarc.$DOMAIN TXT +short ]; print "Checking DMARC Record For _dmarc.$DOMAIN\n"; if ($dmarc) { print expand( YELLOW "\t \\_ " . $dmarc . "\n" ); } else { print expand( YELLOW "\t \\_ None\n" ); } print "\n"; # Add MAX_EMAIL_PER_HOUR, MAX_DEFER_FAIL_PERCENTAGE, MAILBOX_FORMAT my $MAILBOX_FORMAT = $DataJSON->{data}->{acct}->[0]->{'mailbox_format'}; my $MAX_EMAIL_PER_HOUR = $DataJSON->{data}->{acct}->[0]->{'max_email_per_hour'}; my $MAX_DEFER_FAIL_PERCENTAGE = $DataJSON->{data}->{acct}->[0]->{'max_defer_fail_percentage'}; my $MAXPOP = $DataJSON->{data}->{acct}->[0]->{'maxpop'}; print WHITE "Max Mail Accounts " . CYAN $MAXPOP . "\n"; print WHITE "Mailbox Format: " . CYAN ucfirst($MAILBOX_FORMAT) . "\n"; print WHITE "Max Emails Per Hour: " . CYAN ucfirst($MAX_EMAIL_PER_HOUR) . "\n"; print WHITE "Max Defer Fail %: " . CYAN ucfirst($MAX_DEFER_FAIL_PERCENTAGE) . "\n"; if ( $MAX_DEFER_FAIL_PERCENTAGE > 100 ) { print expand( RED "\t \\_ [WARN] - Max Defer Fail % greater than 100 [CPANEL-20909]\n" ); } if ( -e ("/var/cpanel/email_send_limits/max_defer_$MAINDOMAIN") ) { print expand( RED "\t \\_ $MAINDOMAIN may have exceeded deferred mail limits. Listed in /var/cpanel/email_send_limits\n" ); } # Check here if send mail from dedicated IP is set and if /etc/mailips or /etc/mailhelo is referenced. my $SendFromDedicated = qx[ grep 'per_domain_mailips=1' /etc/exim.conf.localopts ]; $SendFromDedicated = ($SendFromDedicated) ? "Yes" : "No"; my $CustomHelo = qx[ grep 'custom_mailhelo=1' /etc/exim.conf.localopts ]; $CustomHelo = ($CustomHelo) ? "Yes" : "No"; my $CustomMailIP = qx[ grep 'custom_mailips=1' /etc/exim.conf.localopts ]; $CustomMailIP = ($CustomMailIP) ? "Yes" : "No"; my $CustomMailIPText = ""; if ( $CustomMailIP eq "Yes" ) { $CustomMailIPText = qx[ egrep '^$MAINDOMAIN' /etc/mailips ]; chomp($CustomMailIPText); } my $CustomHeloText = ""; if ( $CustomHelo eq "Yes" ) { $CustomHeloText = qx[ egrep '^$MAINDOMAIN' /etc/mailhelo ]; chomp($CustomHeloText); } print WHITE "Send from dedicated IP: " . CYAN $SendFromDedicated . "\n"; print WHITE "Using Custom HELO (/etc/mailhelo): " . CYAN $CustomHelo . " " . YELLOW $CustomHeloText . "\n"; print WHITE "Using Custom IP (/etc/mailips): " . CYAN $CustomMailIP . " " . YELLOW $CustomMailIPText; if ( -e ("$RealHome/.spamassassin/user_prefs") ) { my $SAscore; ($SAscore) = ( split( /\s+/, qx[ grep '^required_score' $RealHome/.spamassassin/user_prefs ] ) )[1]; print WHITE "\nSpam Threshold Score: " . CYAN $SAscore unless ( !($SAscore) ); } print "\n"; # Check status of email_outbound_spam_detection in Tweak Settings my $SpamDetectEnabledJSON = get_whmapi1( 'get_tweaksetting', 'key=email_outbound_spam_detect_enable' ); my $SpamDetectEnabled = $SpamDetectEnabledJSON->{data}->{tweaksetting}->{value}; my $SpamDetectActionJSON = get_whmapi1( 'get_tweaksetting', 'key=email_outbound_spam_detect_action' ); my $SpamDetectAction = $SpamDetectActionJSON->{data}->{tweaksetting}->{value}; my $SpamDetectThresholdJSON = get_whmapi1( 'get_tweaksetting', 'key=email_outbound_spam_detect_threshold' ); my $SpamDetectThreshold = $SpamDetectThresholdJSON->{data}->{tweaksetting}->{value}; if ($SpamDetectEnabled) { print WHITE "Detect Potential Spammers: " . GREEN "Enabled"; } else { print WHITE "Detect Potential Spammers: " . RED "Disabled - Not recommended. Please consider enabling this option in Tweak Settings."; } print "\n"; if ( $SpamDetectAction eq "noaction" && $SpamDetectEnabled ) { print expand( CYAN "\t\\_ Action: " . RED "No Action" . WHITE " - Not recommended. Please consider setting this to HOLD or REJECT\n" ); } if ( $SpamDetectAction eq "hold" && $SpamDetectEnabled ) { print expand( "\t\\_ Action: " . GREEN ucfirst($SpamDetectAction) . WHITE " - Messages will be held when more than " . MAGENTA $SpamDetectThreshold . WHITE " spam messages have been detected.\n" ); } if ( $SpamDetectAction eq "block" && $SpamDetectEnabled ) { print expand( "\t\\_ Action: " . GREEN ucfirst($SpamDetectAction) . WHITE " - Messages will be blocked when more than " . MAGENTA $SpamDetectThreshold . WHITE " spam messages have been detected.\n" ); } border(); } sub getMXrecord { my $tcDomain = $_[0]; my $rr; my @NEWMX; my $res = Net::DNS::Resolver->new; my @mx = mx( $res, $tcDomain ); if (@mx) { foreach $rr (@mx) { push( @NEWMX, $rr->exchange ); } return @NEWMX; } else { return "NONE"; } } sub getptr() { my $ip = $_[0]; chomp($ip); my $ipaddr = inet_aton($ip); my $ptrname = gethostbyaddr( $ipaddr, AF_INET ); return $ptrname; } sub getArecords { my $tcDomain = $_[0]; my @addresses = gethostbyname($tcDomain); @addresses = map { inet_ntoa($_) } @addresses[ 4 .. $#addresses ]; return @addresses; } sub scan { unlink("/root/ai_detections.txt") unless ( !-e "/root/ai_detections.txt" ); unlink("/root/suspicious_strings.yara") unless ( !-e "/root/suspicious_strings.yara" ); my $RealHome = Cpanel::PwCache::gethomedir($username); if ( -e "/usr/local/cpanel/3rdparty/bin/clamscan" ) { print YELLOW "ClamAV is installed - using YARA scan...\n"; my $URL = "https://raw.githubusercontent.com/cPanelPeter/infection_scanner/master/suspicious_strings.yara"; my @DEFINITIONS = qx[ curl -s $URL > "/root/suspicious_strings.yara" ]; print CYAN "Scanning " . WHITE $RealHome . "/public_html... \n"; open( RULES, "/root/suspicious_strings.yara" ); my @RULEDATA = ; close(RULES); my $resultLine; my @FOUND = undef; my @results = qx[ /usr/local/cpanel/3rdparty/bin/clamscan --no-summary --infected --suppress-ok-results --recursive --exclude="(.ttf|.pem|.js|.gz|.jpeg|.jpg|.psd|.dat|.bz2|.crt|.mp3|.mp4|.zip|.webm|.json|.pdf|.png|.css|.svg|.swf|Thumbs.db)" --include="(.php|.*htm*|.t*t|.pl|.cgi)" --database /root/suspicious_strings.yara "$RealHome/public_html" ]; foreach $resultLine (@results) { chomp($resultLine); my ( $scannedFile, $foundRule ) = ( split( /\s+/, $resultLine ) )[ 0, 1 ]; chomp($scannedFile); chomp($foundRule); $scannedFile =~ s/://g; $foundRule =~ s/YARA.//g; $foundRule =~ s/.UNOFFICIAL//g; my $resultCnt = 1; my $ruleData; foreach $ruleData (@RULEDATA) { chomp($ruleData); $resultCnt++; if ( $ruleData eq "rule $foundRule {" ) { $ruleData = $RULEDATA[$resultCnt]; my ($string) = ( split( /\"/, $ruleData ) )[1]; my $ChangeDate = timed_run( 3, "stat $scannedFile | grep -i change" ); ($ChangeDate) = ( split( /\./, $ChangeDate ) ); $ChangeDate =~ s/Change: //; push( @FOUND, expand( CYAN "\t \\_ File: " . WHITE "$scannedFile " . BOLD RED . "looks suspicious " . BOLD MAGENTA . " [ Modified: " . BOLD BLUE $ChangeDate . MAGENTA " ]" ) ); } } } splice( @FOUND, 0, 1 ); my $cntFOUND = @FOUND; my $foundLine; if ( $cntFOUND == 0 ) { print GREEN "Result: Nothing suspicious found!\n"; } else { foreach $foundLine (@FOUND) { chomp($foundLine); print "$foundLine\n"; } print "\n"; print RED "Result: " . WHITE $cntFOUND . RED " suspicious items found. "; print YELLOW "These should be investigated.\n"; } } else { print YELLOW "ClamAV is not installed - skipping YARA scan...\n"; my $URL = "https://raw.githubusercontent.com/cPanelPeter/infection_scanner/master/strings.txt"; my @DEFINITIONS = qx[ curl -s $URL > "/root/ai_detections.txt" ]; @DEFINITIONS = qx[ curl -s $URL ]; my $StringCnt = @DEFINITIONS; print "Scanning $RealHome/public_html for ($StringCnt) known phrases/strings\n"; my $retval = qx[ LC_ALL=C grep -srIwf /root/ai_detections.txt $RealHome/public_html/* ]; my @retval = split( /\n/, $retval ); my $TotalFound = @retval; my $ItemFound; my @FileNamesOnly; my $FileOnly; foreach $ItemFound (@retval) { chomp($ItemFound); ($FileOnly) = ( split( /:/, $ItemFound ) ); push( @FileNamesOnly, $FileOnly ); } my @newRetVal = uniq(@FileNamesOnly); my $TotalFilesFound = @newRetVal; foreach $FileOnly (@newRetVal) { my $ChangeDate = timed_run( 3, "stat $FileOnly | grep -i change" ); ($ChangeDate) = ( split( /\./, $ChangeDate ) ); $ChangeDate =~ s/Change: //; print expand( CYAN "\t \\_ File: " . WHITE "$FileOnly " . BOLD RED . "looks suspicious " . BOLD MAGENTA . " [ Modified: " . BOLD BLUE $ChangeDate . MAGENTA " ]\n" ); } print "\n"; if ( $TotalFound == 0 ) { print GREEN "Result: Nothing suspicious found!\n"; } else { print RED "Result: " . WHITE $TotalFound . RED " suspicious items found in " . WHITE $TotalFilesFound . RED " files. "; print YELLOW "These should be investigated.\n"; } } unlink("/root/ai_detections.txt") unless ( !-e "/root/ai_detections.txt" ); unlink("/root/suspicious_strings.yara") unless ( !-e "/root/suspicious_strings.yara" ); } sub spin { my %spinner = ( '|' => '/', '/' => '-', '-' => '\\', '\\' => '|' ); $spincounter = ( !defined $spincounter ) ? '|' : $spinner{$spincounter}; print STDERR "\b$spincounter"; print STDERR "\b"; } sub getSSLProvider { my $RetVal = ""; my $SSLmodule = ""; my $SSLmoduleJSON = get_whmapi1('get_autossl_providers'); for my $AutoSSL_Enabled ( @{ $SSLmoduleJSON->{data}->{payload} } ) { if ( $AutoSSL_Enabled->{enabled} ) { $SSLmodule = $AutoSSL_Enabled->{display_name}; last; } } my $RetVal = "Disabled Globally"; if ($SSLmodule) { $RetVal = $SSLmodule; } return $RetVal; } sub ChkForIntegration { my $IntegrationLinksJSON = get_whmapi1( 'list_integration_links', "user=$username" ); my $LinkCnt = 0; for my $AppLink ( @{ $IntegrationLinksJSON->{data}->{links} } ) { if ( $AppLink->{app} ) { $LinkCnt++; } } if ( $LinkCnt > 0 ) { print YELLOW "[NOTE] - $LinkCnt Integration links found under the " . GREEN . $username . YELLOW " account\n"; } } sub dispSSLdata { #my $tcDomain = $_[0]; my $tcDomain = shift; chomp($tcDomain); print YELLOW $tcDomain . "\n"; if ( -e ("$sslsyscertdir/$tcDomain/certificates") ) { my $sslsubject = timed_run( 3, 'openssl', 'x509', '-in', "$sslsyscertdir/$tcDomain/certificates", '-subject', '-noout' ); my $startdate = timed_run( 3, 'openssl', 'x509', '-in', "$sslsyscertdir/$tcDomain/certificates", '-startdate', '-noout' ); my $expiredate = timed_run( 3, 'openssl', 'x509', '-in', "$sslsyscertdir/$tcDomain/certificates", '-enddate', '-noout' ); chomp($startdate); chomp($expiredate); ($startdate) = ( split( /=/, $startdate ) )[1]; ($expiredate) = ( split( /=/, $expiredate ) )[1]; my $EpochExpire = str2time($expiredate); my $isExpired; my $time_now = time; my $timeDiff = $EpochExpire - $time_now; my $isExpired = GREEN "[VALID]"; if ( $timeDiff < 0 ) { $isExpired = RED "[EXPIRED]"; } print expand( WHITE "\t \\_ Not Before: " . GREEN . $startdate . "\n" ); print expand( WHITE "\t \\_ Not After : " . GREEN . $expiredate . " " . $isExpired . "\n" ); my ($SSLSubject) = ( split( /\= /, timed_run( 4, 'openssl', 'x509', '-in', "$sslsyscertdir/$tcDomain/certificates", '-subject', '-noout' ) ) )[1]; my ($SSLIssuer) = ( split( /\= /, timed_run( 4, 'openssl', 'x509', '-in', "$sslsyscertdir/$tcDomain/certificates", '-issuer', '-noout' ) ) )[1]; chomp($SSLSubject); chomp($SSLIssuer); my $isSelfSigned = ( $SSLSubject eq $SSLIssuer ) ? 1 : 0; if ( $isSelfSigned == 1 ) { print expand( RED "\t \\_ [WARN] " . WHITE "- Self-Signed Certificate!\n" ); } else { # Get Issuer and display it. my $SSLIssuer1 = timed_run( 4, 'openssl', 'x509', '-in', "$sslsyscertdir/$tcDomain/certificates", '-issuer', '-noout'); my ($SSLIssuer2) = ( split( /O=|O = /, $SSLIssuer1 ))[1]; my ($SSLIssuer) = ( split( /\//, $SSLIssuer2 ))[0]; $SSLIssuer =~ s/\"//g; print expand( GREEN "\t \\_ [CA SIGNED] " . WHITE "Issued by: $SSLIssuer" ); # Get OCSP and Certificate status my $OCSPstatus = qx[ openssl s_client -connect $tcDomain:443 -servername $tcDomain -status <<&1 | grep 'Cert Status:' ]; # It looks like if OCSPstatus is empty, the SSL may be revoked! if ( $OCSPstatus and $isExpired ne "[EXPIRED]" ) { print expand( BOLD CYAN "\t \\_ OCSP: " . BOLD MAGENTA substr( $OCSPstatus, 17 ) . "\n" ); } } print WHITE "Protecting the following Subject Alternative Names:\n"; my $SAN; my $getSANS = timed_run( 3, 'openssl', 'x509', '-in', "$sslsyscertdir/$tcDomain/certificates", '-noout', '-text' ); my @getSANS = split "\n", $getSANS; push @getSANS, "DNS: $tcDomain"; my $SSLHostMismatch = 0; foreach $SAN (@getSANS) { chomp($SAN); next unless( $SAN =~ m/DNS:/ ); $SAN =~ s/DNS://g; $SAN =~ s/\s+//; $SAN =~ s/, /\n/g; my @SANS = split( /\s+/, $SAN ); foreach $SAN (@SANS) { chomp($SAN); print expand( CYAN "\t \\_ " . CYAN $SAN . "\n" ); checkCAA($SAN); } } } else { print expand( WHITE "\t \\_ " . CYAN . "No SSL certificates found.\n" ); } } sub SQLiteDBChk { my $lcDB = $_[0]; $result = ""; my $dbh = DBI->connect( "dbi:SQLite:dbname=$lcDB", "", "", { RaiseError => 1, HandleError => \&handle_error }, ) or die $DBI::errstr; my $sth = $dbh->prepare("pragma quick_check"); if ($result) { $result = "Corrupted"; } else { $sth->execute() or die $DBI::errstr; my @row; while ( @row = $sth->fetchrow_array() ) { $result = "@row"; } $sth->finish(); $dbh->disconnect(); } return $result; } sub handle_error { my $error = shift; $result = 1; return $result; } sub securitychk { if ( -e ("$RealHome/.accesshash") ) { print RED "[SECURITY WARNING] - $RealHome/.accesshash file found - Consider using API Tokens instead!\n"; } if ( -e ("$RealHome/.my.cnf") ) { print RED "[SECURITY WARNING] - $RealHome/.my.cnf file found - No longer required nor recommended!\n"; } if ( -e ("$RealHome/public_html/.my.cnf") ) { print RED "[SECURITY WARNING] - $RealHome/public_html/.my.cnf file found - No longer required nor recommended!\n"; } if ( -e ("$RealHome/.env") ) { print RED "[SECURITY WARNING] - $RealHome/.env file found - Might store sensitive database information - Not recommended!\n"; } if ( -e ("$RealHome/public_html/.env") ) { print RED "[SECURITY WARNING] - $RealHome/public_html/.env file found - Might store sensitive database information - Not recommended!\n"; } my @dirs = qw( .well-known/pki-validation .well-known/acme-challenge ); my @files = qw( error_log ins.htm msg.jpg msges.jpg reso.zip rolf.zip stroi-invest.zip thn.htm ); for my $dir (@dirs) { for my $file (@files) { my $fullpath = "$RealHome/public_html/$dir/$file"; stat $fullpath; if ( -f _ and not -z _ ) { print RED "[SECURITY WARNING] - $RealHome/public_html/$dir/$file found - possible Troldesh ransomware?!\n"; } } } my $susp_filter = Cpanel::SafeRun::Timed::timedsaferun( 0, "grep -srl '\$header_from: contains \"@\"' $RealHome/etc/*/*/filter" ); if ( $susp_filter ) { print RED "[SECURITY WARNING] - Suspicious Mail Filter [ " . YELLOW "containing only '\@'" . RED " ] Found in " . CYAN $susp_filter; } my $susp_filter2 = Cpanel::SafeRun::Timed::timedsaferun( 0, "grep -srl '\$header_from: contains \"mailer-daemon\"' $RealHome/etc/*/*/filter" ); if ( $susp_filter2 ) { print RED "[SECURITY WARNING] - Suspicious Mail Filter [ " . YELLOW "containing only 'mailer-daemon'" . RED " ] Found in " . CYAN $susp_filter2; } } sub checkperms2 { if ($IS_USERNAME) { getPerms( "/etc/valiases/$MAINDOMAIN", "0640", "$username", "mail" ); getPerms( "/etc/vfilters/$MAINDOMAIN", "0640", "$username", "mail" ); } else { getPerms( "/etc/valiases/$QUERY", "0640", "$username", "mail" ); getPerms( "/etc/vfilters/$QUERY", "0640", "$username", "mail" ); } if ( !-e ("$RealHome") ) { print expand( RED "\t \\_ [WARN] - $RealHome directory is missing!\n" ); return; } else { my $FProtectEnabled = -e "/var/cpanel/fileprotect" ? 1 : 0; my $suExecEnabled = grep( /suexec/, @InstalledMods ) ? 1 : 0; $RUID2Enabled = grep( /ruid/, @InstalledMods ) ? 1 : 0; my $ITKEnabled = grep( /itk/, @InstalledMods ) ? 1 : 0; my $PHPHandlerJSON = get_uapi( "--user=$username", 'LangPHP', 'php_get_domain_handler', 'type=vhost', "vhost=$MAINDOMAIN" ); my $PHPHandler = $PHPHandlerJSON->{result}->{data}->{php_handler}; chomp($PHPHandler); if ($ACLSEnabled) { getPerms( "$RealHome", "0750", "$username", "$username" ); } else { getPerms( "$RealHome", "0711", "$username", "$username" ); } if ( !-e ("$RealHome/public_html") ) { print expand( RED "\t \\_ [WARN] - $RealHome/public_html directory is missing!\n" ); } else { if ( $FProtectEnabled and $PHPHandler =~ m/cgi|suphp/ and ( $RUID2Enabled or $ITKEnabled ) ) { getPerms( "$RealHome/public_html", "0750", "$username", "$username" ) unless ( !-e "$RealHome/public_html" ); } if ( $FProtectEnabled and ( !$RUID2Enabled and !$ITKEnabled ) ) { getPerms( "$RealHome/public_html", "0750", "$username", "nobody" ) unless ( !-e "$RealHome/public_html" ); } if ( !$FProtectEnabled ) { getPerms( "$RealHome/public_html", "0755", "$username", "$username" ) unless ( !-e "$RealHome/public_html" ); } if ( -e ("$RealHome/public_html/.well-known") ) { getPerms( "$RealHome/public_html/.well-known", "0755", "$username", "$username" ); } if ( -e ("$RealHome/public_html/.well-known/pki-validation") ) { getPerms( "$RealHome/public_html/.well-known/pki-validation", "0755", "$username", "$username" ); } if ( -e ("$RealHome/public_html/.well-known/acme-challenge") ) { getPerms( "$RealHome/public_html/.well-known/acme-challenge", "0755", "$username", "$username" ); } } if ( !-e ("$RealHome/etc") ) { print expand( RED "\t \\_ [WARN] - $RealHome/etc directory is missing!\n" ); } else { getPerms( "$RealHome/etc", "0750", "$username", "mail" ); } if ( !-e ("$RealHome/mail") ) { print expand( RED "\t \\_ [WARN] - $RealHome/mail directory is missing!\n" ); } else { getPerms( "$RealHome/mail", "0751", "$username", "$username" ); } my $which_getfacl = Cpanel::FindBin::findbin('getfacl'); if ( $which_getfacl ) { my $has_acl; $has_acl = Cpanel::SafeRun::Timed::timedsaferun( 4, "$which_getfacl", '-p', '-c', '-R', '/tmp' ); print expand( RED "\t \\_ [WARN] - Customer ACL set for $username [ getfacl -p -c -R /tmp | grep ':$username:' ]\n" ) unless( ! grep { /:$username:/ } $has_acl ); $has_acl = Cpanel::SafeRun::Timed::timedsaferun( 4, "$which_getfacl", '-p', '-c', '-R', "$RealHome" ); print expand( RED "\t \\_ [WARN] - Customer ACL set for $username [ getfacl -p -c -R $RealHome | grep ':$username:' ]\n" ) unless( ! grep { /:$username:/ } $has_acl ); $has_acl = Cpanel::SafeRun::Timed::timedsaferun( 4, "$which_getfacl", '-p', '-c', '-R', "$RealHome/public_html" ); print expand( RED "\t \\_ [WARN] - Customer ACL set for $username [ getfacl -p -c -R $RealHome/public_html | grep ':$username:' ]\n" ) unless( ! grep { /:$username:/ } $has_acl ); } } } sub getPerms { my $tcPermFile = $_[0]; my $DefPerm = $_[1]; my $DefPermUser = $_[2]; my $DefPermGrp = $_[3]; if ( !-e "$tcPermFile" ) { print RED "[WARN] - $tcPermFile is missing!\n"; return; } my $currentPerm = sprintf '%04o', ( stat $tcPermFile )[2] & 07777; my $curUserID = ( stat $tcPermFile )[4]; my $curGrpID = ( stat $tcPermFile )[5]; my $currentUser = ( getpwuid $curUserID )[0]; my $currentGrp = ( getgrgid $curGrpID )[0]; if ( $currentUser eq "" ) { $currentUser = "UNKNOWN"; } if ( $currentGrp eq "" ) { $currentGrp = "UNKNOWN"; } if ( $DefPerm ne $currentPerm ) { print RED "[WARN] - $tcPermFile has invalid permissions ($currentPerm) [Default: $DefPerm]\n"; } if ( $DefPermUser ne $currentUser ) { if ( $tcPermFile eq "/etc/valiases/$QUERY" or $tcPermFile eq "/etc/vfilters/$QUERY" ) { print RED "[WARN] - Check ownership on both /etc/valiases/$QUERY and /etc/vfilters/$QUERY as they maybe incorrect!\n"; print YELLOW "[NOTE] - Owner may be invalid - doublecheck those files!!!\n"; } else { print RED "[WARN] - $tcPermFile has invalid user ($currentUser) [Default: $DefPermUser]\n"; } } if ( $DefPermGrp ne $currentGrp ) { print RED "[WARN] - $tcPermFile has invalid group ($currentGrp) [Default: $DefPermGrp]\n"; } } sub chk_mail_suspend { my $tcAccount = shift; my $llTab = shift; my $SMTPUserSusp = qx[ grep '^$tcAccount' /etc/outgoing_mail_suspended_users ]; if ($SMTPUserSusp) { if ($llTab) { print expand( RED "\t \\_ $tcAccount is suspended from sending email\n" ); } else { print RED "The $tcAccount account is suspended from sending email.\n"; } } } sub chk_login_disabled { my $shadow_file = shift; my $tcLocalPart = shift; my $localpart = $tcLocalPart . ":!!"; if ( -s "$shadow_file/shadow" ) { open( my $fh, '<', "$shadow_file/shadow" ) or die( $! ); while( <$fh> ) { if ( $_ =~ m/$localpart/ ) { print expand( RED "\t \\_ Login for this account has been disabled/suspended\n" ); } } close( $fh ); } } sub chk_suspended_mail { my $filter_file = shift; if ( -s "$filter_file/filter" ) { open( my $fh, '<', "$filter_file/filter" ) or die( $! ); while( <$fh> ) { if ( $_ =~ m/SUSPEND RECEPTION OF NEW MESSAGES/ ) { print expand( RED "\t \\_ Receiving mail for this account has been suspended\n" ); } } close( $fh ); } } sub chk_mail_hold { my $tcAccount = $_[0]; my $llTab = $_[1]; my $SMTPUserHold = qx[ grep '^$tcAccount' /etc/outgoing_mail_hold_users ]; if ($SMTPUserHold) { if ($llTab) { print expand( RED "\t \\_ $tcAccount is on hold from sending email\n" ); } else { print RED "$tcAccount is on hold from sending email\n"; } } } sub chk_tomcat10 { return unless ( Cpanel::Version::compare( Cpanel::Version::getversionnumber(), '>=', '11.106') ); return unless ( -x '/opt/cpanel/ea-podman/bin/ea-podman' ); my $podmanPS = Cpanel::SafeRun::Timed::timedsaferun( 4, "su - $username -c 'podman ps | grep ea-tomcat'" ); if ( ! $podmanPS ) { print YELLOW "[INFO] - Tomcat not running or configured for $username\n"; return; } print YELLOW "[INFO] - Retrieving Tomcat information for $username\n"; my ( $httpport1 ) = ( split ( /:/, $podmanPS ))[2]; my ( $httpport ) = ( split ( /\//, $httpport1 )); my ( $ajpport1 ) = ( split ( /:/, $podmanPS ))[3]; my ( $ajpport ) = ( split ( /\//, $ajpport1 )); chomp($ajpport); my $data = Cpanel::SafeRun::Timed::timedsaferun( 0, "su - $username -c '/usr/local/cpanel/scripts/ea-podman running'" ); my $podman_running = decode_json $data; my $tomcat_ports; my $tomcat_container; my $TCEnabled = 0; opendir my $dh, "$RealHome/ea-podman.d/"; my @containers = readdir($dh); closedir( $dh ); foreach my $container(@containers) { chomp($container); next if ( $container =~ m/\.bak/ ); next unless( $container =~ m/tomcat/); if ( grep { /$container/ } $data ) { $tomcat_container = $container; $TCEnabled = 1; last; } } print YELLOW "Tomcat container [ " . BOLD BLUE $tomcat_container . YELLOW " ] installed using ports:\n"; print expand( CYAN "\t\\_ HTTP PORT: $httpport\n"); print expand( CYAN "\t\\_ AJP PORT: $ajpport\n"); my $container_status = Cpanel::SafeRun::Timed::timedsaferun( 3, "su - $username -c '/usr/local/cpanel/scripts/ea-podman status $tomcat_container' | grep 'Active:'" ); if ( $container_status =~ m/active \(running\)/) { print expand( GREEN "\t\\_ $tomcat_container is actively running.\n" ); } else { print expand( RED "\t\\_ $tomcat_container is NOT running!\n" ); return; } print YELLOW "\t \\_ Checking $RealHome/ea-podman.d/$tomcat_container/conf/server.xml:\n"; my $TCConnPort = timed_run( 2, "egrep 'Connector port.*HTTP' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml' | cut -d \= -f2"); ($TCConnPort) = ( split( /\s+/, $TCConnPort ) )[0]; $TCConnPort =~ s/\"//g; if ( $TCConnPort eq "8080" ) { print expand( CYAN "\t\t\t \\_ HTTP Connector Port: " . GREEN . $TCConnPort . "\n" ); } else { print expand( CYAN "\t\t\t \\_ HTTP Connector Port: " . RED . $TCConnPort . " [ CONFIG ERROR! Run " . YELLOW "su - $username -c 'podman ps' | grep ea-tomcat" . RED " ]\n" ); } my $TCAJPPort = timed_run( 2, "egrep 'Connector port.*AJP' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml' | cut -d \= -f2"); ($TCAJPPort) = ( split( /\s+/, $TCAJPPort ) )[0]; $TCAJPPort =~ s/\"//g; if ( $TCAJPPort eq "8009" ) { print expand( CYAN "\t\t\t \\_ AJP Connector Port: " . GREEN . $TCAJPPort . "\n" ); } else { print expand( CYAN "\t\t\t \\_ AJP Connector Port: " . RED . $TCAJPPort . " [ CONFIG ERROR! Run " . YELLOW "su - $username -c 'podman ps' | grep tomcat" . RED " ]\n" ); } my $TCdocBase = Cpanel::SafeRun::Timed::timedsaferun( 2, "egrep 'Context path.*docBase' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml' | cut -d \= -f3" ); chomp($TCdocBase); print expand( CYAN "\t\t\t \\_ docBase Defined: " . GREEN . $TCdocBase . "\n" ); my $TCHost = Cpanel::SafeRun::Timed::timedsaferun( 2, "grep 'Host name' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml' | cut -d \= -f2"); ($TCHost) = ( split( /\s+/, $TCHost ) )[0]; $TCHost =~ s/\"//g; print expand( CYAN "\t\t\t \\_ Host: " . GREEN . $TCHost . "\n" ); my $TCunpackWARs = Cpanel::SafeRun::Timed::timedsaferun( 2, "grep 'unpackWARs' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml'" ); ($TCunpackWARs) = ( split( /\s+/, $TCunpackWARs ) )[4]; $TCunpackWARs =~ s/\"//g; if ( $TCunpackWARs =~ m/false/ ) { print expand( CYAN "\t\t\t \\_ unpackWARs: " . GREEN . $TCunpackWARs . "\n" ); } else { print expand( CYAN "\t\t\t \\_ unpackWARs: " . RED . $TCunpackWARs . " [ SHOULD BE FALSE ]\n" ); } my $TCautoDeploy = Cpanel::SafeRun::Timed::timedsaferun( 2, "grep 'autoDeploy' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml'" ); ($TCautoDeploy) = ( split( /\s+/, $TCautoDeploy ) )[5]; $TCautoDeploy =~ s/\"//g; if ( $TCautoDeploy =~ m/false/ ) { print expand( CYAN "\t\t\t \\_ autoDeploy: " . GREEN . $TCautoDeploy . "\n" ); } else { print expand( CYAN "\t\t\t \\_ autoDeploy: " . RED . $TCautoDeploy . " [ SHOULD BE FALSE ]\n" ); } my $TCDeployStartup = Cpanel::SafeRun::Timed::timedsaferun( 2, "grep 'deployOnStartup' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml'" ); ($TCDeployStartup) = ( split( /\s+/, $TCDeployStartup ) )[6]; $TCDeployStartup =~ s/\"//g; if ( $TCDeployStartup =~ m/false/ ) { print expand( CYAN "\t\t\t \\_ deployOnStartup: " . GREEN . $TCDeployStartup . "\n" ); } else { print expand( CYAN "\t\t\t \\_ deployOnStartup: " . RED . $TCDeployStartup . " [ SHOULD BE FALSE ]\n" ); } my $TCDeployXML = Cpanel::SafeRun::Timed::timedsaferun( 2, "grep 'deployXML' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml'" ); ($TCDeployXML) = ( split( /\s+/, $TCDeployXML ) )[7]; $TCDeployXML =~ s/\"//g; $TCDeployXML =~ s/>//g; if ( $TCDeployXML =~ m/false/ ) { print expand( CYAN "\t\t\t \\_ deployXML " . GREEN . $TCDeployXML . "\n" ); } else { print expand( CYAN "\t\t\t \\_ deployXML " . RED . $TCDeployXML . " [ SHOULD BE FALSE ]\n" ); } my $TCsecretRequired = Cpanel::SafeRun::Timed::timedsaferun( 2, "grep 'secretRequired' '$RealHome/ea-podman.d/$tomcat_container/conf/server.xml'" ); ($TCsecretRequired) = ( split( /\s+/, $TCsecretRequired ) )[5]; $TCsecretRequired =~ s/\"//g; $TCsecretRequired =~ s/\/>//g; if ( $TCsecretRequired =~ m/false/ ) { print expand( CYAN "\t\t\t \\_ secretRequired " . GREEN . $TCsecretRequired . "\n" ); } else { print expand( CYAN "\t\t\t \\_ secretRequired " . RED . $TCsecretRequired . " [ SHOULD BE FALSE ]\n" ); } opendir my $dh, "$RealHome/ea-podman.d/$tomcat_container/webapps/ROOT"; my @webapp_data = readdir( $dh ); closedir( $dh ); my $showHeader=0; foreach my $jspfile (@webapp_data) { chomp($jspfile); next unless( $jspfile =~ m{\.jsp} ); print expand( "\t\t\t\\_ jsp files found in $RealHome/ea-podman.d/$tomcat_container/webapps/ROOT/\n" ) unless( $showHeader ); $showHeader=1; print expand( YELLOW "\t\t\t\t \\_ $jspfile\n"); } print "\n"; } sub chk_tomcat { if ( Cpanel::Version::compare( Cpanel::Version::getversionnumber(), '>=', '11.106') ) { chk_tomcat10; return; } my $TCEnabledUsers = timed_run( 2, '/usr/local/cpanel/scripts/ea-tomcat85', 'list' ); my @TCEnabledUsers = split "\n", $TCEnabledUsers; my $TCEnabled = 0; my $TCEnabledUser; foreach $TCEnabledUser (@TCEnabledUsers) { chomp($TCEnabledUser); if ( $TCEnabledUser eq $username ) { $TCEnabled = 1; last; } } if ( !$TCEnabled ) { print YELLOW "[INFO] - Tomcat not configured for $username\n"; return; } my $TCDispStatus = 1; if ( $RealShell ne "/bin/bash" ) { print RED "[WARN] - $username\'s shell is not Normal (/bin/bash) - unable to obtain a valid status for ea-tomcat85.\n"; $TCDispStatus = 0; print "\t \\_ Continuing with the remainder of the configuration.\n"; } if ( -e ("/etc/profile.d/limits.sh") ) { print RED "[WARN] - Shell fork bomb protection enabled - unable to obtain a valid status for ea-tomcat85.\n"; $TCDispStatus = 0; print "\t \\_ Continuing with the remainder of the configuration.\n"; } if ($TCDispStatus) { my $PerlPath = qx[ readlink /usr/local/cpanel/3rdparty/bin/perl ]; chomp($PerlPath); $PerlPath =~ s/\/bin\/perl/\/bin/; my $ubicPathFound = timed_run( 2, "grep $PerlPath '$RealHome/.bashrc'" ); chomp($ubicPathFound); if ($ubicPathFound) { my $TCStatus = timed_run( 2, "su - $username -s /bin/bash -c 'ubic status ea-tomcat85'" ); chomp($TCStatus); my ($TCStatus1) = ( split( /\s+/, $TCStatus ) )[1]; print YELLOW "ea-tomcat85 is: "; if ( $TCStatus1 eq "running" ) { print GREEN "running\n"; } else { print RED "not running\n"; } } else { print RED "[WARN] " . CYAN "- Check " . WHITE "$RealHome/.bashrc " . CYAN "file for PATH containing " . WHITE "$PerlPath\n" . YELLOW "\t[ubic command will not work without it].\n\n"; } } print YELLOW "Checking $RealHome/ea-tomcat85/conf/server.xml:\n"; my $TCConnPort = timed_run( 2, "egrep 'Connector port.*HTTP' '$RealHome/ea-tomcat85/conf/server.xml' | cut -d \= -f2" ); ($TCConnPort) = ( split( /\s+/, $TCConnPort ) )[0]; $TCConnPort =~ s/\"//g; print expand( CYAN "\t \\_ HTTP Connector Port: " . GREEN . $TCConnPort . "\n" ); my $TCAJPPort = timed_run( 2, "egrep 'Connector port.*AJP' '$RealHome/ea-tomcat85/conf/server.xml' | cut -d \= -f2" ); ($TCAJPPort) = ( split( /\s+/, $TCAJPPort ) )[0]; $TCAJPPort =~ s/\"//g; print expand( CYAN "\t \\_ AJP Connector Port: " . GREEN . $TCAJPPort . "\n" ); my $TCHost = timed_run( 2, "grep 'Host name' '$RealHome/ea-tomcat85/conf/server.xml' | cut -d \= -f2" ); ($TCHost) = ( split( /\s+/, $TCHost ) )[0]; $TCHost =~ s/\"//g; print expand( CYAN "\t \\_ Host: " . GREEN . $TCHost . "\n" ); my $TCunpackWARs = timed_run( 2, "grep 'unpackWARs' '$RealHome/ea-tomcat85/conf/server.xml'" ); ($TCunpackWARs) = ( split( /\s+/, $TCunpackWARs ) )[4]; $TCunpackWARs =~ s/\"//g; if ( $TCunpackWARs =~ m/true/ ) { print expand( CYAN "\t \\_ unpackWARs: " . GREEN . $TCunpackWARs . "\n" ); } else { print expand( CYAN "\t \\_ unpackWARs: " . RED . $TCunpackWARs . " [ SHOULD BE TRUE ]\n" ); } my $TCautoDeploy = timed_run( 2, "grep 'autoDeploy' '$RealHome/ea-tomcat85/conf/server.xml'" ); ($TCautoDeploy) = ( split( /\s+/, $TCautoDeploy ) )[5]; $TCautoDeploy =~ s/\"//g; if ( $TCautoDeploy =~ m/false/ ) { print expand( CYAN "\t \\_ autoDeploy: " . GREEN . $TCautoDeploy . "\n" ); } else { print expand( CYAN "\t \\_ autoDeploy: " . RED . $TCautoDeploy . " [ SHOULD BE FALSE ]\n" ); } my $TCDeployStartup = timed_run( 2, "grep 'deployOnStartup' '$RealHome/ea-tomcat85/conf/server.xml'" ); ($TCDeployStartup) = ( split( /\s+/, $TCDeployStartup ) )[6]; $TCDeployStartup =~ s/\"//g; if ( $TCDeployStartup =~ m/false/ ) { print expand( CYAN "\t \\_ deployOnStartup: " . GREEN . $TCDeployStartup . "\n" ); } else { print expand( CYAN "\t \\_ deployOnStartup: " . RED . $TCDeployStartup . " [ SHOULD BE FALSE ]\n" ); } my $TCDeployXML = timed_run( 2, "grep 'deployXML' '$RealHome/ea-tomcat85/conf/server.xml'" ); ($TCDeployXML) = ( split( /\s+/, $TCDeployXML ) )[7]; $TCDeployXML =~ s/\"//g; $TCDeployXML =~ s/>//g; if ( $TCDeployXML =~ m/false/ ) { print expand( CYAN "\t \\_ deployXML " . GREEN . $TCDeployXML . "\n" ); } else { print expand( CYAN "\t \\_ deployXML " . RED . $TCDeployXML . " [ SHOULD BE FALSE ]\n" ); } my $TCsecretRequired = timed_run( 2, "grep 'secretRequired' '$RealHome/ea-tomcat85/conf/server.xml'" ); ($TCsecretRequired) = ( split( /\s+/, $TCsecretRequired ) )[5]; $TCsecretRequired =~ s/\"//g; $TCsecretRequired =~ s/\/>//g; if ( $TCsecretRequired =~ m/false/ ) { print expand( CYAN "\t \\_ secretRequired " . GREEN . $TCsecretRequired . "\n" ); } else { print expand( CYAN "\t \\_ secretRequired " . RED . $TCsecretRequired . " [ SHOULD BE FALSE ]\n" ); } opendir my $dh, "$RealHome/ea-tomcat85/webapps/ROOT"; my @webapp_data = readdir( $dh ); closedir( $dh ); my $showHeader=0; foreach my $jspFile (@webapp_data) { chomp($jspFile); next unless( $jspFile =~ m{\.jsp} ); print expand( "jsp files found in $RealHome/ea-tomcat85/webapps/ROOT/\n" ) unless( $showHeader ); $showHeader=1; print expand( YELLOW "\t \\_ $jspFile\n"); if ( $jspFile eq "test.jsp" ) { my $TJP = timed_run( 2, "lynx --dump http://$MAINDOMAIN/test.jsp | head -1" ); if ( $TJP =~ m/Test JSP Page/ ) { print GREEN "\t \\_ Success: " . WHITE "http://$MAINDOMAIN/test.jsp" . CYAN " Works!\n"; } else { print RED "\t \\_ Failed: " . WHITE "http://$MAINDOMAIN/test.jsp" . CYAN " Didn't work!\n"; } } } } sub get_json_href { my ( $raw, $fail_warning ) = @_; return unless defined $raw; my $json = load_module_with_fallbacks( 'needed_subs' => [qw{new utf8 decode}], 'modules' => [qw{Cpanel::JSON::XS JSON::XS JSON::PP}], 'fail_warning' => $fail_warning, ); return {} unless $json; my $href; local $@; eval { $href = $json->new->utf8->decode($raw); }; return {} if !$href; return $href; } sub load_module_with_fallbacks { my %opts = @_; my $namespace_loaded; foreach my $module2try ( @{ $opts{'modules'} } ) { # Don't 'require' it if we already have it. my $inc_entry = join( "/", split( "::", $module2try ) ) . ".pm"; if ( !$INC{$module2try} ) { local $@; next if !eval "require $module2try; 1"; ## no critic (StringyEval) } # Check if the imported modules 'can' do the job next if ( scalar( grep { $module2try->can($_) } @{ $opts{'needed_subs'} } ) != scalar( @{ $opts{'needed_subs'} } ) ); # Ok, we're good to go! $namespace_loaded = $module2try; last; } # Fallback to coderef, but don't do sanity checking on this, as it is presumed the caller "knows what they are doing" if passing a coderef. if ( !$namespace_loaded ) { if ( !$opts{'fallback'} || ref $opts{'fallback'} != 'CODE' ) { print_warn( 'Missing Perl Module(s): ' . join( ', ', @{ $opts{'modules'} } ) . ' -- ' . $opts{'fail_warning'} . " -- Try using /usr/local/cpanel/3rdparty/bin/perl?\n" ) if $opts{'fail_warning'}; die "Stopping here." if $opts{'fail_fatal'}; } else { $opts{'fallback'}->(); # call like main::subroutine instead of Name::Space::subroutine $namespace_loaded = 'main'; } } return $namespace_loaded; } sub get_json_from_command { my @cmd = @_; return Cpanel::JSON::Load( Cpanel::SafeRun::Timed::timedsaferun( 30, @cmd ) ); } sub get_whmapi1 { return get_json_from_command( 'whmapi1', '--output=json', @_ ); } sub get_uapi { return get_json_from_command( 'uapi', '--output=json', @_ ); } sub get_cpapi2 { return get_json_from_command( 'cpapi2', '--output=json', @_ ); } sub timed_run { my ( $timer, @PROGA ) = @_; return _timedsaferun( $timer, 0, @PROGA ); } sub timed_run_noerr { my ( $timer, @PROGA ) = @_; return _timedsaferun( $timer, 1, @PROGA ); } sub _timedsaferun { my ( $timer, $stderr_to_stdout, @PROGA ) = @_; return '' if ( substr( $PROGA[0], 0, 1 ) eq '/' && !-x $PROGA[0] ); $timer = $timer ? $timer : 25; $timer = $OPT_TIMEOUT ? $OPT_TIMEOUT : $timer; my $output; my $complete = 0; my $pid; my $fh; eval { local $SIG{'__DIE__'} = 'DEFAULT'; local $SIG{'ALRM'} = sub { $output = ''; print RED ON_BLACK 'Timeout while executing: ' . join( ' ', @PROGA ) . "\n"; die; }; alarm($timer); if ( $pid = open( $fh, '-|' ) ) { local $/; $output = readline($fh); close($fh); } elsif ( defined $pid ) { open( STDIN, '<', '/dev/null' ); if ($stderr_to_stdout) { open( STDERR, '>&', 'STDOUT' ); } else { open( STDERR, '>', '/dev/null' ); } exec(@PROGA) or exit 1; } else { print RED ON_BLACK 'Error while executing: [ ' . join( ' ', @PROGA ) . ' ]: ' . $! . "\n"; alarm 0; die; } $complete = 1; alarm 0; }; alarm 0; if ( !$complete && $pid && $pid > 0 ) { kill( 15, $pid ); sleep(2); kill( 9, $pid ); } return defined $output ? $output : ''; } sub chk_for_blocked_country_codes { my $blockedCountriesJSON = get_whmapi1('list_blocked_incoming_email_countries'); my $bCountrycode; for my $blockedCountry ( @{ $blockedCountriesJSON->{data}->{countries} } ) { $bCountrycode .= $blockedCountry->{country_code} . ', '; } $bCountrycode =~ s/.{2}\z//; print CYAN "The following countries are blocked from sending emails to this server:\n" unless ( !$bCountrycode ); print expand("\t\\_ $bCountrycode\n") unless ( !$bCountrycode ); smborder() unless ( !$bCountrycode ); } sub checkCAA { my $tcDomain = $_[0]; my $CAAResult; my $CAAResults = timed_run( 10, 'dig', '+noall', '+answer', "$tcDomain", 'CAA' ); my @CAAResults = split "\n", $CAAResults; if (@CAAResults) { splice( @CAAResults, 0, 1 ); } else { return; } foreach $CAAResult (@CAAResults) { chomp($CAAResult); if ( $CAAResult =~ m/CNAME/ ) { next; } my ( $CAAFlag, $CAATag, $CAAValue ) = ( split( /\s+/, $CAAResult ) )[ 4, 5, 6 ]; push( @HasCAA, expand( YELLOW "\t\\_ " . $CAAFlag . " " . $CAATag . " " . $CAAValue ) ); } return; } sub display_header { my $tcDomain = shift; my $httpval = 0; $httpval = get_http_loc($tcDomain); return unless($httpval == 0); get_data( $tcDomain, 'Server', 4 ); get_data( $tcDomain, 'Powered-By:', 4 ); get_data( $tcDomain, 'Redirect-By:', 4 ); } sub get_data { my $tcDomain = shift; my $tcValue = shift; my $tnTimeout = shift; my $result = Cpanel::SafeRun::Timed::timedsaferun( $tnTimeout, 'curl', '-L', '-s', '--head', "http://$tcDomain" ); my @result = split /\n/, $result; my $lastline = ""; foreach my $line (@result) { chomp($line); if ( $line =~ m/$tcValue/i ) { print expand( CYAN "\t\\_ " . $line . "\n" ) unless ( $line eq $lastline ); $lastline = $line; } } } sub get_http_loc { my $tcDomain = shift; my $result = Cpanel::SafeRun::Timed::timedsaferun( 4, 'curl', '-L', '-s', '--head', "http://$tcDomain" ); if ( $result =~ m/Timeout while executing/ ) { return 1; } my @result = split /\n/, $result; my $lastline = ""; my $result2; my $line_printed=0; my $line2_printed=0; foreach my $line (@result) { chomp($line); if ( $line =~ m/301|302/ ) { if ( $line_printed==0 ) { print expand( YELLOW "\t\\_ $line\n" ) if ( $line =~ m/HTTP/ ); $result2 = qx[ curl -sL --head "http://$tcDomain" | grep -i 'Location:' ]; my @result2 = split /\n/, $result2; foreach my $line2 (@result2) { chomp($line2); if ( $line2_printed==0) { print expand( CYAN "\t\t\\_ $line2\n" ); } } } $line_printed=1; } elsif ( $line =~ m/200/ ) { print expand( GREEN "\t\\_ $line\n" ) if ( $line =~ m/HTTP/ ); $line_printed=1; } elsif ( $line =~ m/400|401|403|404|405|406|408|500|502|503|504|520|521|522|523|524|525|526/) { print expand( RED "\t\\_ $line\n" ) if ( $line =~ m/HTTP/ ); } else { print expand( MAGENTA "\t\\_ $line\n" ) if ( $line =~ m/HTTP/ ); } } } sub nginx_caching { return unless ( -e '/etc/nginx/conf.d/ea-nginx.conf' ); my ( $rawdata, $output ); my $user_nginx_caching_status = 0; my $global_nginx_caching_status = 0; if ( -e "/var/cpanel/userdata/$username/nginx-cache.json" ) { $rawdata = Cpanel::SafeRun::Timed::timedsaferun( 4, 'cat', "/var/cpanel/userdata/$username/nginx-cache.json" ); $output = decode_json $rawdata; $user_nginx_caching_status = ( $output->{enabled} ) ? 1 : 0; } if ( !-e '/etc/nginx/ea-nginx/enable.standalone' && -e '/etc/nginx/ea-nginx/cache.json' ) { $rawdata = Cpanel::SafeRun::Timed::timedsaferun( 4, 'cat', '/etc/nginx/ea-nginx/cache.json' ); $output = decode_json $rawdata; $global_nginx_caching_status = ( $output->{enabled} ) ? 1 : 0; } if ($user_nginx_caching_status) { print "NginX Caching (user): " . CYAN "Active\n"; return; } if ($global_nginx_caching_status) { print "NginX Caching (global): " . CYAN "Active\n"; return; } print "NginX Caching: " . CYAN "Inactive\n"; } sub getProfileNode { # Note that if the license is invalid (activated too many times, expired, etc...) this will fail. # Note that if running older than 74 (when profiles were introduced) this should just return true. my $ProfileNodeJSON = get_whmapi1('get_current_profile'); $ProfileNode = $ProfileNodeJSON->{data}->{code}; if ( $ProfileNode ne "STANDARD" ) { print RED "This server is not running on a standard node " . CYAN "[ " . WHITE $ProfileNode . CYAN " ]\n"; print YELLOW "Please run acctinfo only on a Standard Node!\n"; exit; } } sub get_wp_info { my $pubhtml = shift; my $allwpconfs = Cpanel::SafeRun::Timed::timedsaferun( 0, 'find', $pubhtml, '-type', 'f', '-name', 'wp-config.php' ); my @allwpconfs = split /\n/, $allwpconfs; my $dbfound=0; foreach my $wpconf( @allwpconfs ) { print "Found wp-config.php file at: $wpconf\n"; chomp($wpconf); my $wppath = dirname( $wpconf ); my $insecure = ($showpass) ? 1 : 0; my $inmaintmode = ( -e "$wppath/.maintenance" ) ? 1 : 0; print RED "[WARN] - WordPress is in maintenance mode!\n" unless ( !$inmaintmode ); open my $fh, '<:encoding(UTF-8)', $wpconf; my ( $value, $dbhost, $dbname, $dbuser, $dbpass, $tblprefix ); while ( my $line = <$fh> ) { if ( $line =~ m/DB_HOST/ ) { ($dbhost) = ( split( /'/, $line ) )[3]; print CYAN "Host: " . YELLOW $dbhost . "\n"; } if ( $line =~ m/DB_NAME/ ) { ($dbname) = ( split( /'/, $line ) )[3]; print CYAN "WordPress Database Name: " . YELLOW $dbname . "\n"; } if ( $line =~ m/DB_USER/ ) { ($dbuser) = ( split( /'/, $line ) )[3]; print CYAN "WordPress Database User: " . YELLOW $dbuser . "\n"; } if ( $line =~ m/DB_PASSWORD/ ) { ($dbpass) = ( split( /'/, $line ) )[3]; print CYAN "Database Password: " . YELLOW $dbpass . "\n" unless ( !$insecure ); print CYAN "Database Password: " . GREEN . "[REDACTED] " . MAGENTA "(use --showpass to reveal)\n" unless ($insecure); } if ( $line =~ m/table_prefix/ ) { ($tblprefix) = ( split( /'/, $line ) )[1]; print CYAN "Table Prefix: " . YELLOW $tblprefix . "\n"; } my $DB_Exists = Cpanel::SafeRun::Timed::timedsaferun( 4, 'mysql', '-BNe', "SHOW DATABASES" ); my @DB_Exists = split /\n/, $DB_Exists; foreach my $line(@DB_Exists) { chomp($line); if ( $line eq $dbname ) { $dbfound=1; last; } } next; } print RED "[WARN] - Database " . YELLOW $dbname . RED " seems to be missing!\n" unless ( $dbfound ); smborder(); close $fh; my $wp_settings = Cpanel::SafeRun::Timed::timedsaferun( 5, 'mysql', $dbname, '-BNe', "SELECT option_name,option_value FROM ${tblprefix}options WHERE option_name IN ('home','siteurl','permalink_structure', 'stylesheet', 'template' );" ); my @wp_settings = split /\n/, $wp_settings; if (@wp_settings) { print CYAN "WP Settings for home, siteurl, stylesheet, template and permalinks:\n"; foreach my $wp_setting (@wp_settings) { chomp($wp_setting); $wp_setting =~ s/\s+/: /g; print YELLOW "\t\\_ $wp_setting\n"; } } my $wp_activeplugins = qx[ mysql $dbname -BNe "SELECT option_value FROM ${tblprefix}options WHERE option_name='active_plugins';" | grep -oP '(?<=")[^"]+php' ]; my @wp_activeplugins = split /\n/, $wp_activeplugins; if (@wp_activeplugins) { print CYAN "WP Active Plugins:\n"; foreach my $wp_activeplugin (@wp_activeplugins) { chomp($wp_activeplugin); print YELLOW "\t\\_ $wp_activeplugin\n"; } } my $current_version; if ( -e "$wppath/wp-includes/version.php" ) { open my $fh, '<:encoding(UTF-8)', "$wppath/wp-includes/version.php"; while ( my $line = <$fh> ) { if ( $line =~ m/^\$wp_version/ ) { ($current_version) = ( split( /\s+/, $line ) )[2]; $current_version =~ s/\'//g; $current_version =~ s/\;//g; chomp($current_version); print CYAN "Current WordPress Version: " . YELLOW $current_version . "\n"; } } my $latest_version = Cpanel::SafeRun::Timed::timedsaferun( 4, 'curl', '-s', 'https://wordpress.org/download/' ); my @latest_version = split /\n/, $latest_version; my $release_version; foreach my $line(@latest_version) { chomp($line); next unless( $line =~ m{softwareVersion} ); $release_version = $line; last; } $release_version =~ s/^\s+|\s+$//g; ($release_version) = (split( /\s+/, $release_version ))[1]; $release_version =~ s/\"//g; $release_version =~ s/\,//g; print CYAN "Latest WordPress Version Available: " . YELLOW $release_version; if ( $release_version =~ m/$current_version/ ) { print GREEN " [OK]"; } else { print RED " [Update Recommended!]"; } print "\n"; } smborder(); } }