#!/usr/local/cpanel/3rdparty/bin/perl # Copyright 2006 - 2024, WebPros International, LLC. and Peter Elsner # All rights reserved. # # 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 copyright holder nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. # # THE SOFTWARE LICENSED HEREUNDER IS PROVIDED "AS IS" AND WEBPROS INTERNATIONAL, LLC D/B/A/ CPANEL (CPANEL) HEREBY DISCLAIMS ALL WARRANTIES OF ANY KIND, WHETHER EXPRESS OR IMPLIED, RELATING TO THE SOFTWARE, ITS THIRD PARTY COMPONENTS, AND ANY DATA ACCESSED THEREFROM, OR THE ACCURACY, TIMELINESS, COMPLETENESS, OR ADEQUACY OF THE SOFTWARE, ITS THIRD PARTY COMPONENTS, AND ANY DATA ACCESSED THEREFROM, INCLUDING THE IMPLIED WARRANTIES OF TITLE, MERCHANTABILITY, SATISFACTORY QUALITY, FITNESS FOR A PARTICULAR PURPOSE, AND NON-INFRINGEMENT. CPANEL DOES NOT WARRANT THAT THE SOFTWARE OR ITS THIRD PARTY COMPONENTS ARE ERROR-FREE OR WILL OPERATE WITHOUT INTERRUPTION. IF THE SOFTWARE, ITS THIRD PARTY COMPONENTS, OR ANY DATA ACCESSED THEREFROM IS DEFECTIVE, YOU ASSUME THE SOLE RESPONSIBILITY FOR THE ENTIRE COST OF ALL REPAIR OR INJURY OF ANY KIND, EVEN IF CPANEL HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DEFECTS OR DAMAGES. NO ORAL OR WRITTEN INFORMATION OR ADVICE GIVEN BY CPANEL, ITS AFFILIATES, LICENSEES, DEALERS, SUB-LICENSORS, AGENTS OR EMPLOYEES SHALL CREATE A WARRANTY OR IN ANY WAY INCREASE THE SCOPE OF ANY WARRANTY. # # SCRIPT: acctinfo # PURPOSE: Get as much information for a username or domain entered at command line as possible. # AUTHOR: Peter Elsner # CREATED: 06/17/2006 # CURRENT MAINTAINER: Peter Elsner use strict; my $VERSION = "2.5.53"; 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::Version::Compare (); use Cpanel::Config::Users (); use Cpanel::Config::LoadWwwAcctConf (); use Cpanel::Config::LoadCpConf (); use Cpanel::SafeRun::Timed (); use Cpanel::JSON (); use JSON::PP; 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 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 $scan = undef; my $wpinfo = undef; my $sitejetinfo = undef; my $showpass = undef; my $showtoken = undef; my $showteaminfo = undef; my $acct_over_quota = 0; my $coreEP="https://www.wpvulnerability.net/core"; my $pluginEP="https://www.wpvulnerability.net/plugin"; my $themeEP="https://www.wpvulnerability.net/theme"; 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, 'scan' => \$scan, 'wpinfo' => \$wpinfo, 'sitejetinfo' => \$sitejetinfo, 'showpass' => \$showpass, 'showtoken' => \$showtoken, 'teams' => \$showteaminfo, ); 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} ); my $sitejet_status = ( -e "$HOMEDIR/$username/.cpanel/sitejet/$DomainInfoLines->{domain}" ) ? MAGENTA "[Sitejet Enabled]" : ""; push( @ADDONDOMAINS, $DomainInfoLines->{domain} . " " . $sitejet_status . YELLOW "\n\t\t \\_ [ Sub: " . $DomainInfoLines->{parent_domain} . " ]" . "\n\t\t \\_ [ DocumentRoot: " . $DomainInfoLines->{docroot} . " ] $docroot_status" ); $IS_ADDON = expand( "$DomainInfoLines->{domain} $sitejet_status 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 $SitejetAPI = $user_conf->{SITEJET_API_TOKEN}; 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"; $ResolvedIP = ($IS_USERNAME) ? Cpanel::SafeRun::Timed::timedsaferun( 8, '/usr/local/cpanel/scripts/cpdig', $MAINDOMAIN, 'A') : Cpanel::SafeRun::Timed::timedsaferun( 8, '/usr/local/cpanel/scripts/cpdig', $QUERY, 'A'); #if ($IS_USERNAME) { # $ResolvedIP = Cpanel::SafeRun::Timed::timedsaferun( 8, '/usr/local/cpanel/scripts/cpdig', $MAINDOMAIN, 'A' ); #} #else { # $ResolvedIP = Cpanel::SafeRun::Timed::timedsaferun( 8, '/usr/local/cpanel/scripts/cpdig', $QUERY, 'A' ); #} @ResolvedIP=split /\n/, $ResolvedIP; my $IPTYPE = ( $IPADDR eq $SERVER_IP ) ? "shared" : "dedicated"; #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)"; } my $sitejet_status = ( -e "$HOMEDIR/$username/.cpanel/sitejet/$MAINDOMAIN" ) ? MAGENTA " [Sitejet Enabled]" : ""; # 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 . $sitejet_status . 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" ); } if ( $OnCloud ) { print GREEN expand( "\tcPCloud Instance Detected...\n"); my $namespaces=timed_run( 3, 'kubectl', 'get', 'namespaces' ); my @namespaces=split /\n/, $namespaces; foreach my $namespace(@namespaces) { chomp($namespace); next unless( $namespace =~ m/$username/ ); print expand( YELLOW "\t " . $namespace . "\n"); last; } my $serviceweb=timed_run( 3, 'kubectl', '-n', "cpsc-service-web-$username", 'get', 'all' ); my @servicewebs=split /\n/, $serviceweb; foreach my $line(@servicewebs) { chomp($line); print expand( CYAN "\t\t\\_ $line\n" ) if ( $line =~ m{pod/cpsc-service-web} ); } print "\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}; my $inodeslimit = $quotaJSON->{data}->{acct}->[0]->{inodeslimit}; my $inodesused = $quotaJSON->{data}->{acct}->[0]->{inodesused}; print "Disk Quota: $quotaused used of $maxquota allowed / Inodes Used: $inodesused used of $inodeslimit 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" ); } if ( $inodesused > $inodeslimit ) { $acct_over_quota = 1 unless ( $inodeslimit eq "unlimited" ); print RED "[WARN] - $username appears to have maxed out inodes!" unless ( $inodeslimit 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 ( $sitejetinfo ) { my $sitejet_domain = ( $IS_USERNAME ) ? $MAINDOMAIN : $QUERY; if ( -e "$RealHome/.cpanel/sitejet/$sitejet_domain" ) { my $insecure = ($showtoken) ? 1 : 0; print WHITE "Sitejet Builder Data [ " . GREEN "API Token: " . YELLOW $SitejetAPI . WHITE " ]\n" unless( !$insecure ); print WHITE "Sitejet Builder Data [ " . GREEN "API Token: " . YELLOW "REDACTED --showtoken to reveal" . WHITE " ]\n" unless( $insecure ); my $SjJSON = get_uapi( 'Sitejet', 'get_all_user_sitejet_info', "--user=$username", "domain=$sitejet_domain" ); print expand( YELLOW "\t\\_ Website ID: " . CYAN $SjJSON->{result}->{data}->[0]->{metadata}->{websiteId} . "\n" ); print expand( YELLOW "\t\\_ Template Name: " . CYAN $SjJSON->{result}->{data}->[0]->{metadata}->{templateName} . YELLOW " [ Template ID: " . CYAN $SjJSON->{result}->{data}->[0]->{metadata}->{templateId} . YELLOW " ]\n" ); my $isPublished = ( $SjJSON->{result}->{data}->[0]->{metadata}->{publish_status} ) ? "Yes" : "No"; if ( $isPublished eq "Yes" ) { print expand( YELLOW "\t\\_ Published: " . CYAN $isPublished . YELLOW " Latest Published Date: " . CYAN scalar(localtime($SjJSON->{result}->{data}->[0]->{metadata}->{latest_publish_date}))); } else { print expand( YELLOW "\t\\_ Published: " . CYAN "No" ); } print "\n\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; 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"; } # Added check to see if LiteSpeed is listening on port 80 and change $PHPHandler to lsphp if so... # See ZC-5996/ZC-6006 Even though it says that if Nginx is in use it's always PHP-FPM, that's not the case after testing. my $lsof = Cpanel::SafeRun::Timed::timedsaferun( 0, 'lsof', '-i', ':80' ); my @lsof = split /\n/, $lsof; if ( grep { /^litespeed/ } @lsof ) { $PHPHandler = 'lsphp/lsapi - (' . CYAN "LiteSpeed Webserver is listening on port 80 - all other handlers are ignored" . YELLOW ')'; } 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); $ResolvesToDetail = check_resolved_ip($ResolvedIP, $IPADDR); 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 " - Basic Privileges - 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-account|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 Privileges"; } if ( $rperm =~ m/(allow-shell|viewglobalpackages|allow-addoncreate|allow-parkedcreate|add-pkg-ip|add-pkg-shell|allow-unlimited-pkgs|allow-emaillimits-pkgs|allow-unlimited-disk-pkgs|allow-unlimited-bw-pkgs)$/ ) { print YELLOW " - Package Privileges"; } if ( $rperm =~ m/(status|stats|restart|resftp)$/ ) { print CYAN " - Global Privileges"; } 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 ( $showteaminfo or $all ) { my $teamdir = '/var/cpanel/team'; open( my $fh, '<', "$teamdir/$username" ); my $cnt=0; while ( <$fh> ) { $cnt++; } close( $fh ); $cnt--; print "Getting team info...\n"; my $max_team_accts=7; my $get_team_info = get_uapi( 'Team', 'list_team', "--user=$username" ); my $i=0; print expand( RED $get_team_info->{result}->{errors}->[0] . "\n" ) unless( $get_team_info->{result}->{errors}->[0] ); print "No team members configured.\n" unless( $get_team_info->{result}->{data}->[$i]->{created} ); while( $i < $cnt ) { print CYAN "Username: $get_team_info->{result}->{data}->[$i]->{username}\@$MAINDOMAIN\n" if ( defined $get_team_info->{result}->{data}->[$i]->{username} ); print expand( CYAN "\t\\_Created: " . YELLOW scalar localtime( $get_team_info->{result}->{data}->[$i]->{created} )) . "\n" if ( defined $get_team_info->{result}->{data}->[$i]->{created} ); print expand( CYAN "\t\\_Last Logged In: " . YELLOW scalar localtime( $get_team_info->{result}->{data}->[$i]->{lastlogin} )) . "\n" unless ( $get_team_info->{result}->{data}->[$i]->{lastlogin} eq "" ); print expand( "\t\\_Expires: " . YELLOW scalar localtime( $get_team_info->{result}->{data}->[$i]->{expire_date} )) . "\n" unless ( $get_team_info->{result}->{data}->[$i]->{expire_date} eq "" ); print expand( CYAN "\t\t\\_Expire Reason: " . MAGENTA $get_team_info->{result}->{data}->[$i]->{expire_reason} ) . "\n" unless ( $get_team_info->{result}->{data}->[$i]->{expire_reason} eq "" ); print expand( CYAN "\t\\_Suspend Date: " . YELLOW scalar localtime( $get_team_info->{result}->{data}->[$i]->{suspend_date} )) . "\n" unless ( $get_team_info->{result}->{data}->[$i]->{suspend_date} eq "" ); print expand( "\t\t\\_Suspend Reason: " . MAGENTA $get_team_info->{result}->{data}->[$i]->{suspend_reason} ) . "\n" unless ( $get_team_info->{result}->{data}->[$i]->{suspend_reason} eq "" ); my $x=0; print expand( CYAN "\t\\_Roles: " ); while( $x < 4 ) { print GREEN $get_team_info->{result}->{data}->[$i]->{roles}->[$x] . " "; $x++; } print "\n"; $i++; } print "\n"; } 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 CAA records 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 "$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 " --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 " --sitejetinfo [--showtoken] cptestdomain.net\n"; print expand( GREEN "\t Check for and display Sitejet 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 " --teams [domain.tld|username]\n"; print expand( GREEN "\t Show any users created via Team Manager and their info including roles.\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_rfc1918 { my $tcRFC1918chk = shift; my @rfc1918=qw( 10/8 172.16/12 192.168/16 ); my $nat=0; foreach my $rfc1918(@rfc1918) { my $network = NetAddr::IP->new($rfc1918); my $ip = NetAddr::IP->new($tcRFC1918chk); if ( $ip->within($network) ) { $nat=1; last; } } return $nat; } 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 $tcResolvedIP = shift; my $tcIPaddr = shift; my $RetVal = ""; # Does it resolve to a CloudFlare IP? my $is_cloudflare_ip = check_cloudflare_ips($tcResolvedIP); if ($is_cloudflare_ip) { $RetVal .= " <<--- CloudFlare IP Address"; $defaultsite = 1; return $RetVal; } # is $tcIPaddr an RFC1918 (private) IP? my $isRFC1918 = check_for_rfc1918($tcIPaddr); # Check if either IP is on the server. my $ListIPsJSON = get_whmapi1('listips'); my ( $ip_on_server, $publicIP, $privateIP ) = 0; for my $ListOfIPs ( @{ $ListIPsJSON->{data}->{ip} } ) { if ( $ListOfIPs->{public_ip} eq $tcResolvedIP ) { $publicIP = $ListOfIPs->{public_ip}; $privateIP = $ListOfIPs->{ip}; $ip_on_server = 1; last; } } #print "DEBUG: ip_on_server=$ip_on_server / isRFC1918=$isRFC1918 / tcResolvedIP=$tcResolvedIP / tcIPaddr=$tcIPaddr / publicIP=$publicIP / privateIP=$privateIP\n"; if ( !$ip_on_server ) { $RetVal .= " <<--- [Not on this server]"; $defaultsite = 1; return $RetVal; } if ($isRFC1918 ) { if ( $tcIPaddr eq $privateIP ) { $RetVal .= " <<--- Routed via 1:1 NAT ( $publicIP => $privateIP )" . GREEN " [SAME]"; $RetVal .= "\n\t\\_Missing /var/cpanel/cpnat file, Need to run /scripts/build_cpnat?\n" unless ( -e '/var/cpanel/cpnat' ); $defaultsite = 0; } } if ( !$isRFC1918 ) { if ( $publicIP eq $tcIPaddr ) { $RetVal = " <<--- Direct Route ($tcResolvedIP => $tcResolvedIP)" . GREEN " [SAME]"; $defaultsite = 0; } } if ( $tcResolvedIP ne $publicIP && $tcResolvedIP ne $tcIPaddr && $tcIPaddr ne $privateIP ) { $RetVal .= " <<--- Resolves to a different IP then configured for this domain" . RED " (IP address misconfiguration)"; $defaultsite = 1; } 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 = "/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]" ); } # 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 mail and etc directories for PHP files (there should never be any php files in these directories) - CX-1103 my @dirs=qw( etc mail ); foreach my $dir(@dirs) { if ( -d "$RealHome/$dir") { opendir my $dh, "$RealHome/$dir"; my @files = readdir( $dh ); closedir( $dh ); my $showHead=0; foreach my $file(@files) { next if ( $file eq "." or $file eq ".." ); next unless( $file =~ m{.php} ); print RED "[WARNING] - Suspicious PHP file found within the " . WHITE "$RealHome/$dir" . RED " directory!\n" unless( $showHead ); $showHead=1; print expand( YELLOW "\t\\_ $file\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 . " 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 autoresponders my $UserAutoRespJSON = get_uapi( 'Email', 'list_auto_responders', "--user=$username", "account=$DOMAIN" ); if ( @{ $UserAutoRespJSON->{result}->{data} } ) { print expand( YELLOW "Auto Responders\n" ); for my $UserAutoResp ( @{ $UserAutoRespJSON->{result}->{data} } ) { print GREEN "-=-" x 27; print "\n"; print expand( WHITE "\t\\_ $UserAutoResp->{email}" . CYAN " Subject: " . WHITE $UserAutoResp->{subject} . "\n" ); my $AutoRespDet = get_uapi( 'Email', 'get_auto_responder', "--user=$username", "email=$UserAutoResp->{email}" ); print expand( WHITE "\t\t\\_ Charset: $AutoRespDet->{result}->{data}->{charset} / Interval: $AutoRespDet->{result}->{data}->{interval} Hrs. / From: $AutoRespDet->{result}->{data}->{from}\n"); my $AutoRespStart = ( defined $AutoRespDet->{result}->{data}->{start} ) ? scalar localtime($AutoRespDet->{result}->{data}->{start}) : "Immediate"; my $AutoRespStop = ( defined $AutoRespDet->{result}->{data}->{stop} ) ? scalar localtime($AutoRespDet->{result}->{data}->{stop}) : "Never"; my $isHTML = ( defined $AutoRespDet->{result}->{data}->{is_html} ) ? "Yes" : "No"; print expand( WHITE "\t\t\\_ Start: $AutoRespStart / Stop: $AutoRespStop / HTML Body: $isHTML\n"); print expand( WHITE "\t\t\\_ Body:\n$AutoRespDet->{result}->{data}->{body}\n"); } } 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\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"; foreach $SAN (@getSANS) { chomp($SAN); next unless( $SAN =~ m/DNS:/ ); $SAN =~ s/\s+//; $SAN =~ s/DNS://g; $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" ); getPerms( "/var/cpanel/ssl/domain_tls/$MAINDOMAIN/combined", "0640", "root", "mail" ); } else { getPerms( "/etc/valiases/$QUERY", "0640", "$username", "mail" ); getPerms( "/etc/vfilters/$QUERY", "0640", "$username", "mail" ); getPerms( "/var/cpanel/ssl/domain_tls/$QUERY/combined", "0640", "root", "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]; return unless( -e $tcPermFile ); 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 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}; return if ( $ProfileNode eq 'WP2' ); 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 check_for_user_cdn { return if ( readlink '/usr/local/cpanel/server.type' ne 'wp2' ); ## User CDN's are only available on wp2 my $wp2_cdn_db="$RealHome/.cpanel/wp_squared/cdns.sqlite"; return unless ( -e $wp2_cdn_db ); ## If the SQLite db doesn't exist, return. my $hasCDN=Cpanel::SafeRun::Timed::timedsaferun( 2, 'sqlite3', $wp2_cdn_db, "SELECT * FROM zones;" ); # THIS NEXT SECTION IS STILL IN BETA! Unexpected results may occur. if ( $hasCDN ) { print "$username has a user CDN configured:\n"; my $CDNData=Cpanel::SafeRun::Timed::timedsaferun( 2, 'sqlite3', $wp2_cdn_db, "SELECT * FROM cdns;" ); my ( $CDN_id, $CDN_provider, $CDN_desc, $CDN_created, $CDN_expires, $CDN_checked ) = (split( /\|/, $CDNData ))[0,1,2,4,5,6]; print expand( CYAN "\t\\_ ID: " . YELLOW $CDN_id . CYAN " Provider: " . YELLOW ucfirst($CDN_provider) . CYAN " Description: " . YELLOW $CDN_desc ."\n"); $CDN_expires = ( $CDN_expires == 0 ) ? "N/A" : scalar(localtime($CDN_expires)); $CDN_checked = ( $CDN_checked == 0 ) ? "N/A" : scalar(localtime($CDN_checked)); print expand( CYAN "\t\\_ Created: " . YELLOW scalar(localtime($CDN_created)) . CYAN " / Expires: " . YELLOW $CDN_expires . CYAN " / Last Checked: " . YELLOW $CDN_checked . "\n"); } return; } sub get_wp_info { my $pubhtml = shift; my $allwpconfs = Cpanel::SafeRun::Timed::timedsaferun( 0, 'find', $pubhtml, '-maxdepth', '3', '-type', 'f', '-name', 'wp-config.php' ); my @allwpconfs = split /\n/, $allwpconfs; check_for_user_cdn(); 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 ); my @wpconfdata=<$fh>; close( $fh ); my $dbfound=0; foreach my $line( @wpconfdata ) { chomp($line); next unless( $line =~ m{DB_NAME|DB_HOST|DB_USER|DB_PASSWORD|table_prefix} ); if ( $line =~ m/DB_NAME/ ) { ($dbname) = ( split( /'/, $line ) )[3]; print CYAN "WordPress Database Name: " . YELLOW $dbname . "\n"; next; } if ( $line =~ m/DB_HOST/ ) { ($dbhost) = ( split( /'/, $line ) )[3]; print CYAN "Host: " . YELLOW $dbhost . "\n"; next; } if ( $line =~ m/DB_USER/ ) { ($dbuser) = ( split( /'/, $line ) )[3]; print CYAN "WordPress Database User: " . YELLOW $dbuser . "\n"; next; } 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); next; } if ( $line =~ m/table_prefix/ ) { ($tblprefix) = ( split( /'/, $line ) )[1]; print CYAN "Table Prefix: " . YELLOW $tblprefix . "\n"; } # Check for database to make sure it exists. 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; } } } if ( $dbfound ) { my %attr = ( PrintError=>0, RaiseError=>0 ); my $dbconnect = DBI->connect("dbi:mysql:$dbname",$dbuser,$dbpass, \%attr); print MAGENTA "Connection check using above credentials: "; print GREEN "Success\n" if( defined( $dbconnect )); print RED "Failed\n" unless( defined( $dbconnect )); my $AdminCnt = Cpanel::SafeRun::Timed::timedsaferun( 4, 'mysql', $dbname, '-BNe', "SELECT count(*) FROM " . $tblprefix . "users INNER JOIN " . $tblprefix . "usermeta ON " . $tblprefix . "users.ID=" . $tblprefix . "usermeta.user_id WHERE " . $tblprefix . "usermeta.meta_key='" . $tblprefix . "capabilities' AND " . $tblprefix . "usermeta.meta_value LIKE '%administrator%';"); chomp($AdminCnt); print RED "[WARN] * There is more than 1 'administrator' account within the users table [$AdminCnt] - Very Suspicious!\n" if ( $AdminCnt > 1 ); 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; my $theme_name = (split( /\s+/, $wp_setting))[1] if ( $wp_setting =~ m/template:/ ); print YELLOW "\t\\_ $wp_setting"; # RIGHT HERE we we'll add wpvuln theme check only if $wp_setting is template) if ( $wp_setting =~ m/template:/ ) { my ($num_unpatched,$numtotal)=get_theme_vuln( $theme_name ); print WHITE " ( $num_unpatched " . MAGENTA "of" . WHITE $numtotal . MAGENTA "vulnerabilities open" . WHITE " )" if (defined $num_unpatched && defined $num_unpatched); print MAGENTA " ( no open vulnerabilities )" unless (defined $num_unpatched && defined $num_unpatched); } print "\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"; my $dirname = dirname( $wpconf ); foreach my $wp_activeplugin (@wp_activeplugins) { chomp($wp_activeplugin); my $plugin_name=dirname($wp_activeplugin); next if ( $wp_activeplugin eq 'wp_squared.php' ); next if ( $wp_activeplugin eq 'hello.php' ); my $plugin_version = Cpanel::SafeRun::Timed::timedsaferun( 5, 'grep', 'Version: ', "$dirname/wp-content/plugins/$wp_activeplugin" ); chomp($plugin_version); $plugin_version =~ s/\s+\*\s+//g; $plugin_version =~ s/Version:\s+//g; $plugin_version =~ s/\*//; $plugin_version =~ s/^\s*(.*?)\s*$/$1/; print YELLOW "\t\\_ $plugin_name " . CYAN "[ Version: " . WHITE $plugin_version . CYAN "] "; my ($num_unpatched,$numtotal)=get_plugin_vuln( $plugin_name, $plugin_version ); my $goodbad=($num_unpatched == 0) ? GREEN " [GOOD]" : RED " [BAD] "; print WHITE " ( $num_unpatched " . MAGENTA "of " . WHITE $numtotal . MAGENTA " vulnerabilities open" . WHITE " ) $goodbad" if (defined $num_unpatched && defined $num_unpatched); print MAGENTA "( no open vulnerabilities ) $goodbad" unless (defined $num_unpatched && defined $num_unpatched); print "\n"; } } } else { print RED "[WARN] - Database " . YELLOW $dbname . RED " seems to be missing!\n" unless ( $dbfound ); } 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; my ($num_unpatched,$numtotal)=get_wpcore_vuln( $current_version ); print WHITE " ( $num_unpatched " . MAGENTA "of " . WHITE $numtotal . MAGENTA " vulnerabilities open" . WHITE " )" if (defined $num_unpatched && defined $num_unpatched); print MAGENTA " ( no open vulnerabilities )" unless (defined $num_unpatched && defined $num_unpatched); print "\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(); } } sub get_wpcore_vuln { my $tcCoreVer=shift; my $coreJSON = Cpanel::SafeRun::Timed::timedsaferun( 0, 'curl', '-s', "$coreEP/$tcCoreVer" ); my $json_output = get_json_href($coreJSON); return if ($json_output->{error} ); return unless( $json_output->{data}->{vulnerability} ); my $core_version=$json_output->{data}->{core}; my ( $cnt, $cnt_patched, $cnt_unpatched ) = get_data($json_output->{data}->{vulnerability}, $tcCoreVer); $cnt="no" unless( defined $cnt ); $cnt_patched="0" unless( defined $cnt_patched ); $cnt_unpatched="0" unless( defined $cnt_unpatched ); return $cnt_unpatched, $cnt; } sub get_plugin_vuln { my $tcPlugin_Slug=shift; my $tcVer=shift; my $pluginJSON = Cpanel::SafeRun::Timed::timedsaferun( 0, 'curl', '-s', "$pluginEP/$tcPlugin_Slug" ); my $json_output = get_json_href($pluginJSON); return if ($json_output->{error} ); return unless( $json_output->{data}->{plugin} ); return unless( $json_output->{data}->{vulnerability} ); my ( $cnt, $cnt_patched, $cnt_unpatched ) = get_data($json_output->{data}->{vulnerability}, $tcVer); $cnt="no" unless( defined $cnt ); $cnt_patched="0" unless( defined $cnt_patched ); $cnt_unpatched="0" unless( defined $cnt_unpatched ); return $cnt_unpatched, $cnt; } sub get_theme_vuln { my $tcTheme_Slug=shift; my $tcVer=shift; my $themeJSON = Cpanel::SafeRun::Timed::timedsaferun( 0, 'curl', '-s', "$themeEP/$tcTheme_Slug" ); my $json_output = get_json_href($themeJSON); return if ($json_output->{error} ); return unless( $json_output->{data}->{theme} ); return unless( $json_output->{data}->{vulnerability} ); my ( $cnt, $cnt_patched, $cnt_unpatched ) = get_data($json_output->{data}->{vulnerability}, $tcVer); $cnt="no" unless( defined $cnt ); $cnt_patched="0" unless( defined $cnt_patched ); $cnt_unpatched="0" unless( defined $cnt_unpatched ); return $cnt_unpatched, $cnt; } sub get_data { my $yy=shift; my $tcVer2=shift; my $cnt; my $cnt_patched; my $cnt_unpatched; for my $vuln ( @{ $yy // q{} } ) { my $cveid=$vuln->{source}->[0]->{name}; my $maxver=$vuln->{operator}->{max_version}; my $maxoperator=$vuln->{operator}->{max_operator}; my $op; $op="<" if ( $maxoperator eq "lt" ); $op="<=" if ( $maxoperator eq "le" ); $op=">" if ( $maxoperator eq "gt" ); $op=">=" if ( $maxoperator eq "ge" ); $op="=" if ( $maxoperator eq "eq" ); $op="!=" if ( $maxoperator eq "ne" ); if ( substr( $cveid, 0,3 ) eq 'CVE' ) { if ( $tcVer2 ) { if ( Cpanel::Version::Compare::compare( $maxver, $op, $tcVer2 ) ) { $cnt_patched++; } else { $cnt_unpatched++; } } $cnt++; } } return $cnt, $cnt_patched, $cnt_unpatched; }