#!/usr/bin/perl
# cpanel - scripts/fix-cpanel-perl Copyright 2019 cPanel, L.L.C.
# All rights Reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
# This script was written to provide a means to recover from a catostropic loss
# of cpanel-perl RPMs so that you cannot run check_cpanel_rpms and/or upcp to get
# all your lost cPanel files back. It is automatically invoked if need-be if
# check_cpanel_rpms cannot run.
package scripts::fixcpanelperl;
use strict;
use warnings;
use IPC::Open3 ();
use POSIX ();
my $COLOR_RED = 31;
my $COLOR_YELLOW = 33;
our $CPANEL_CONFIG_FILE = q[/var/cpanel/cpanel.config];
our $SIG_VALIDATION_CPCONF_KEY = 'signature_validation';
sub colorize_bold {
my ( $color, $msg ) = @_;
return $msg if !defined $color || -e q{/var/cpanel/disable_cpanel_terminal_colors};
$msg ||= '';
return chr(27) . '[1;' . $color . 'm' . $msg . chr(27) . '[0;m';
}
sub DEBUG($) { return _MSG( 'DEBUG', " " . shift ) } ## no critic(ProhibitSubroutinePrototypes)
sub ERROR($) { return _MSG( 'ERROR', colorize_bold( $COLOR_RED, shift ) ) } ## no critic(ProhibitSubroutinePrototypes)
sub WARN($) { return _MSG( 'WARN', colorize_bold( $COLOR_YELLOW, shift ) ) } ## no critic(ProhibitSubroutinePrototypes)
sub INFO($) { return _MSG( 'INFO', shift ) } ## no critic(ProhibitSubroutinePrototypes)
sub FATAL($) { _MSG( 'FATAL', colorize_bold( $COLOR_RED, shift ) ); die "\n"; } ## no critic(ProhibitSubroutinePrototypes)
# Cached and used all over the place.
my ( $wget_bin, $wget_args, $gpg_bin );
my ( $distro, $distro_version, $distro_arch );
my %sha;
exit script() unless caller();
sub script {
return 0 if cpanel_perl_is_stable();
ERROR("Core cpanel-perl modules have been found to be corrupt. Attempting to correct this.") unless $ENV{CPANEL_BASE_INSTALL};
( $wget_bin, $wget_args ) = get_download_tool_binary();
$gpg_bin = gpg_bin();
( $distro, $distro_version, $distro_arch ) = check_system_support();
fetch_and_install_gpg_keys() unless $ENV{CPANEL_BASE_INSTALL_GPG_KEYS_IMPORTED};
my ( $rpm_version_source, @rpms ) = rpms_to_download();
my $rpm_downloads_dir = '/usr/local/cpanel/tmp/rpm_downloads';
my $rpm_url_base = "/RPM/$rpm_version_source/centos/$distro_version/x86_64";
my $core_perl_rpm = shift @rpms;
get_rpm_sha512( $rpm_downloads_dir, $rpm_url_base, $rpm_version_source );
chdir $rpm_downloads_dir;
my $core_perl_pid;
if ( $core_perl_pid = fork() ) {
}
else {
wget_and_validate_file( $core_perl_rpm, $rpm_url_base, $rpm_downloads_dir );
system( qw{/bin/rpm -Uvh --force}, $core_perl_rpm );
exit(0);
}
foreach my $rpm_file (@rpms) {
wget_and_validate_file( $rpm_file, $rpm_url_base, $rpm_downloads_dir );
}
{
waitpid( $core_perl_pid, 0 );
FATAL "Core perl RPM transaction failed" unless $? == 0;
}
system( qw{/bin/rpm -Uvh --force}, @rpms );
FATAL "RPM transaction failed" unless $? == 0;
# updatenow.static will do the needful during an install.
return 0 if $ENV{CPANEL_BASE_INSTALL};
if ( !-x '/usr/local/cpanel/scripts/check_cpanel_rpms' ) {
FATAL "Unable to run scripts/check_cpanel_rpms. You will need to run updatenow.static and then re-run /usr/local/cpanel/scripts/check_cpanel_rpms --fix";
}
exec(qw{/usr/local/cpanel/scripts/check_cpanel_rpms --fix --long-list --no-digest})
or FATAL "Failed to exec /usr/local/cpanel/scripts/check_cpanel_rpms --fix";
return 255;
}
sub rpms_to_download {
return "11.86",
# The main perl rpm must always come first
qw {
cpanel-perl-530-5.30.0-4.cp1186.x86_64.rpm
cpanel-perl-530-common-sense-3.74-1.cp1186.noarch.rpm
cpanel-perl-530-CDB_File-0.99-1.cp1186.x86_64.rpm
cpanel-perl-530-IO-SigGuard-0.14-1.cp1186.noarch.rpm
cpanel-perl-530-JSON-XS-3.04-1.cp1186.x86_64.rpm
cpanel-perl-530-Compress-Raw-Lzma-2.087-1.cp1186.x86_64.rpm
cpanel-perl-530-Proc-FastSpawn-1.2-1.cp1186.x86_64.rpm
cpanel-perl-530-Try-Tiny-0.30-1.cp1186.noarch.rpm
cpanel-perl-530-Types-Serialiser-1.0-1.cp1186.noarch.rpm
cpanel-perl-530-YAML-Syck-1.31-1.cp1186.x86_64.rpm
cpanel-perl-530-Net-SSLeay-1.88-1.cp1186.x86_64.rpm
cpanel-perl-530-IO-Socket-SSL-2.066-2.cp1186.noarch.rpm
};
}
sub cpanel_perl_modules {
# Throw out 11.70 and the first RPM (perl)
my ( undef, undef, @rpms ) = rpms_to_download();
my @modules;
foreach my $rpm (@rpms) {
$rpm =~ s/^cpanel-perl-530-//;
$rpm =~ s/-\d.+$//;
$rpm =~ s/-/::/g;
push @modules, $rpm;
}
return @modules;
}
sub cpanel_perl_is_stable {
my $command = '/usr/local/cpanel/3rdparty/bin/perl';
$command .= " -M$_" foreach cpanel_perl_modules();
my $got = `$command -E'print q{ok}' 2>&1`;
return ( !$? && $got && $got eq 'ok' ) ? 1 : 0;
}
sub get_rpm_sha512 {
my ( $rpm_downloads_dir, $rpm_url_base, $rpm_version_source ) = @_;
# Setup the directory as best we can.
unlink( '/usr/local/cpanel/tmp', $rpm_downloads_dir );
mkdir '/usr/local/cpanel/tmp';
mkdir $rpm_downloads_dir;
-d $rpm_downloads_dir or FATAL("Can't make directory $rpm_downloads_dir ");
my $sha_file = "$rpm_downloads_dir/rpm.$rpm_version_source.sha512";
my $sig_file = "$rpm_downloads_dir/rpm.$rpm_version_source.sha512.asc";
#
wget_file( "$rpm_url_base/rpm.sha512.asc", $sig_file );
wget_file( "$rpm_url_base/rpm.sha512", $sha_file );
verify_file_signature( $sha_file, $sig_file, "$rpm_url_base/rpm.sha512" );
open( my $fh, '<', $sha_file ) or FATAL("Can't read $sha_file");
while ( my $line = <$fh> ) {
chomp $line;
my ( $sha, $file ) = split( qr{\s+}, $line );
$sha{$file} = $sha;
}
close $fh;
return;
}
sub get_download_tool_binary {
for my $bin (qw(/bin/wget /usr/bin/wget /usr/local/bin/wget)) {
next if ( !-e $bin );
next if ( !-x _ );
next if ( -z _ );
return ( $bin, ' -nv --no-dns-cache --tries=20 --timeout=60 --dns-timeout=60 --read-timeout=30 --waitretry=1 --retry-connrefused -O' ) if ( `$bin --version` =~ m/GNU\s+Wget\s+\d+\.\d+/ims );
}
FATAL "Can't bootstrap cpanel-perl without wget. Try: yum -y install wget";
return;
}
sub gpg_bin {
for my $bin (qw(/bin/gpg /usr/bin/gpg /usr/local/bin/gpg)) {
next if ( !-e $bin );
next if ( !-x _ );
next if ( -z _ );
return $bin;
}
FATAL "Can't bootstrap cpanel-perl without gpg. Try: yum -y install gnupg2";
return;
}
sub _MSG {
my $level = shift;
my $msg = shift || '';
chomp $msg;
my $message_caller_depth = 1;
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime;
my ( $package, $filename, $line ) = caller($message_caller_depth);
my $stamp_msg = sprintf( "%04d-%02d-%02d %02d:%02d:%02d %4s (%5s): %s\n", $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $line, $level, $msg );
print $stamp_msg;
return;
}
sub get_update_source {
my $update_source = 'httpupdate.cpanel.net';
my $source_file = '/etc/cpsources.conf';
if ( -r $source_file && -s $source_file ) { # pull in from cpsources.conf if it's set.
open( my $fh, "<", $source_file ) or return $update_source;
while (<$fh>) {
next if ( $_ !~ m/^\s*HTTPUPDATE\s*=\s*(\S+)/ );
$update_source = "$1";
FATAL("HTTPUPDATE is set to '$update_source' in the $source_file file.") if ( !$update_source );
last;
}
}
return $update_source;
}
sub wget_and_validate_file {
my ( $rpm_file, $rpm_url_base, $rpm_downloads_dir ) = @_;
my $rpm_file_path = wget_file( "$rpm_url_base/$rpm_file", "$rpm_downloads_dir/$rpm_file" );
my $result = `/usr/bin/sha512sum $rpm_file_path 2>&1`;
my ($sha) = $result =~ m/^([a-fA-F0-9]+)/;
if ( $sha{$rpm_file} ne $sha ) {
FATAL("Couldn't verify the expected sha ($sha{$rpm_file}) for $rpm_file. Got $sha");
}
return;
}
sub wget_file {
my ( $url, $dest_file ) = @_;
$url = 'http://' . get_update_source() . $url;
DEBUG "Retrieving $url";
my $output = `$wget_bin $wget_args '$dest_file' $url 2>&1`;
if ( !-e $dest_file || -z $dest_file ) {
unlink $dest_file;
FATAL "The system could not fetch the $dest_file file: $output";
}
return $dest_file;
}
sub signature_validation_enabled {
my $config = read_config();
return 1 unless defined $config->{$SIG_VALIDATION_CPCONF_KEY};
return 0 if $config->{$SIG_VALIDATION_CPCONF_KEY} eq '0' || lc( $config->{$SIG_VALIDATION_CPCONF_KEY} ) eq 'off';
return 1;
}
sub verify_file_signature {
my ( $file, $sig, $url ) = @_;
if ( !signature_validation_enabled() ) {
INFO "Skipping signature validation [currently disabled in cpanel.config]";
return;
}
INFO "FILE - $file";
INFO "SIG - $sig";
INFO "URL - $url";
my @gpg_args = (
'--logger-fd', '1',
'--status-fd', '1',
'--homedir', gpg_homedir(),
'--verify', $sig,
$file,
);
# Verify the validity of the GPG signature.
# Information on these return values can be found in 'doc/DETAILS' in the GnuPG source.
my ( %notes, $curnote );
my ( $gpg_out, $success, $status );
my $gpg_pid = IPC::Open3::open3( undef, $gpg_out, undef, $gpg_bin, @gpg_args );
while ( my $line = readline($gpg_out) ) {
if ( $line =~ /^\[GNUPG:\] VALIDSIG ([A-F0-9]+) (\d+-\d+-\d+) (\d+) ([A-F0-9]+) ([A-F0-9]+) ([A-F0-9]+) ([A-F0-9]+) ([A-F0-9]+) ([A-F0-9]+) ([A-F0-9]+)$/ ) {
$status = "Valid signature for $file";
$success = 1;
}
elsif ( $line =~ /^\[GNUPG:\] NOTATION_NAME (.+)$/ ) {
$curnote = $1;
$notes{$curnote} = '';
}
elsif ( $line =~ /^\[GNUPG:\] NOTATION_DATA (.+)$/ ) {
$notes{$curnote} .= $1;
}
elsif ( $line =~ /^\[GNUPG:\] BADSIG ([A-F0-9]+) (.+)$/ ) {
$status = "Invalid signature for $file.";
}
elsif ( $line =~ /^\[GNUPG:\] NO_PUBKEY ([A-F0-9]+)$/ ) {
$status = "Could not find public key in keychain.";
}
elsif ( $line =~ /^\[GNUPG:\] NODATA ([A-F0-9]+)$/ ) {
$status = "Could not find a GnuPG signature in the signature file.";
}
}
waitpid( $gpg_pid, 0 );
$status ||= "Unknown error from gpg.";
$status .= " ($file)";
if ($success) {
INFO $status;
}
else {
FATAL $status;
}
# At this point, the signature should be valid.
# We now need to check to see if the filename signature notation is correct.
$url =~ s/\.bz2$//;
if ( defined( $notes{'filename@gpg.notations.cpanel.net'} ) ) {
my $file_note = $notes{'filename@gpg.notations.cpanel.net'};
if ( $file_note ne $url ) {
FATAL "Filename notation ($file_note) does not match URL ($url).";
}
}
else {
FATAL "Signature does not contain a filename notation.";
}
return;
}
sub fetch_and_install_gpg_keys {
my $pub_keys = public_keys();
_create_gpg_homedir();
foreach my $key ( @{ keys_to_download() } ) {
INFO("Downloading GPG public key, $pub_keys->{$key}");
my $target = secure_downloads() . $pub_keys->{$key};
my $dest = gpg_homedir() . "/" . $pub_keys->{$key};
my $wget_cmd = $wget_args . " " . $dest . " " . $target;
my $wget_out = `$wget_bin $wget_cmd 2>&1`;
if ( !-e $dest ) {
WARN("Could not download GPG public key at $target : $wget_out");
return;
}
my $gpg_cmd = $gpg_bin . " -q --homedir " . gpg_homedir() . " --import " . $dest;
`$gpg_cmd`;
}
return;
}
sub _create_gpg_homedir {
mkdir( gpg_homedir(), 0700 ) if !-e gpg_homedir();
return;
}
sub invalid_system {
my $message = shift || '';
chomp $message;
ERROR "$message";
ERROR "The system detected an unsupported distribution. cPanel & WHM only supports CentOS 6 and 7, Red Hat Enterprise Linux® 6 and 7, and CloudLinux™ 6 and 7.";
FATAL "Please reinstall cPanel & WHM from a valid distribution.";
return; # Fatal will die.
}
sub get_distro_release_rpm {
# /etc/redhat-release or /etc/system-release must be present
my ( $rhel_release, $amazon_release ) = ( '/etc/redhat-release', '/etc/system-release' );
my $distro_release;
if ( -e $rhel_release ) {
$distro_release = $rhel_release;
}
elsif ( -e $amazon_release ) {
$distro_release = $amazon_release;
}
else {
invalid_system("The system could not detect a valid release file for this distribution");
}
chomp( my $release_rpm = `rpm -qf $distro_release` );
return $release_rpm;
}
sub _distro_name {
my ( $distro, $full ) = @_;
for my $names (
[ 'centos', 'CentOS', 'CentOS' ],
[ 'redhat', 'Red Hat', 'Red Hat Enterprise Linux®' ],
[ 'cloud', 'CloudLinux', 'CloudLinux™' ],
[ 'amazon', 'Amazon Linux', 'Amazon Linux' ],
) {
return $names->[ $full ? 2 : 1 ] if $distro eq $names->[0];
}
return $distro;
}
sub check_system_support {
# Some of these variables are unused *as of now*. However! some of these values may be useful for 'filling in the blanks' when the RPM check we do fails to provide all required info.
# For now, only the $system and $machine variables are used, $machine only when Amazon Linux is detected. See https://metacpan.org/pod/POSIX#uname for more info.
my ( $system, $nodename, $release, $version, $machine ) = POSIX::uname();
if ( $system !~ m/linux/i ) {
invalid_system("Could not detect version for operating system");
}
my $release_rpm = get_distro_release_rpm();
$release_rpm or invalid_system("RPMs do not manage release file.");
# an rpm we recognize must manage it.
# We require a non capturing group while still supporting CloudLinux 5, otherwise this will fail to install as it does not include the arch.
my ( $distro_type, $distro_version, $distro_arch ) = $release_rpm =~ m{^(\D+)-([\d\.]+).*?(?:\.([a-z0-9_]+))?$}ms;
$distro_version = $distro_version + 0;
$distro_version or invalid_system("The system found that the unexpected '$release_rpm' RPM manages the release file.");
# This is required for CloudLinux 5 as they do not set an arch for their rpm. So we want to ignore it.
$distro_arch ||= '';
# That RPM must have redhat or centos in the name.
$distro_type =~ m/centos|redhat|enterprise-release|system-release|cloud/i or invalid_system("The system found that the unexpected '$distro_type' RPM manages the release file.");
$distro_type =~ s/-release//imsg;
my $distro;
if ( $distro_type eq 'enterprise' ) {
$distro = 'redhat';
}
elsif ( $distro_type eq 'system' ) {
$distro = 'amazon';
$distro_arch = $machine if $distro_arch eq 'noarch'; # SEE CPANEL-8050
}
else {
$distro = $distro_type;
}
INFO _distro_name($distro) . " $distro_version (Linux) detected!";
# Handle redhat/centos versioning
if ( $distro ne 'amazon' ) {
$distro_version =~ s/\.\d+//; # Strip off the decimal on Cloud Linux 7
# The version number must be 6 or 7.
( int($distro_version) <= 7 && $distro_version >= 6 ) or invalid_system( "cPanel, L.L.C. does not support " . _distro_name($distro) . " version $distro_version." );
# Supported distros for installer: redhat/red hat enterprise/cloud/centos/amazon
$distro = ( $distro =~ m/redhat|hat enterprise/i ) ? 'redhat' : ( $distro =~ m/cloud/i ) ? 'cloud' : 'centos';
}
else {
$distro_version = '6'; # Amazon Linux needs to act like CentOS 6 for RPMs.
}
return ( $distro, $distro_version, $distro_arch );
}
our $CACHE_CONFIG;
sub read_config {
my $file = $CPANEL_CONFIG_FILE;
return $CACHE_CONFIG if $CACHE_CONFIG;
my $config = {};
open( my $fh, "<", $file ) or return $config;
while ( my $line = readline $fh ) {
chomp $line;
if ( $line =~ m/^\s*([^=]+?)\s*$/ ) {
my $key = $1 or next; # Skip loading the key if it's undef or 0
$config->{$key} = undef;
}
elsif ( $line =~ m/^\s*([^=]+?)\s*=\s*(.*?)\s*$/ ) {
my $key = $1 or next; # Skip loading the key if it's undef or 0
$config->{$key} = $2;
}
}
$CACHE_CONFIG = $config;
return $config;
}
sub keys_to_download {
my $config = read_config();
my $keyrings = gpg_keyrings();
if ( !defined $config->{'signature_validation'} ) {
my $mirror = get_update_source();
if ( $mirror =~ /^(?:.*\.dev|qa-build|next)\.cpanel\.net$/ ) {
return $keyrings->{'development'};
}
else {
return $keyrings->{'release'};
}
}
elsif ( $config->{'signature_validation'} =~ /^Release and (?:Development|Test) Keyrings$/ ) {
return $keyrings->{'development'};
}
else {
return $keyrings->{'release'};
}
}
# The installer may set $ENV{'CPANEL_BASE_INSTALL_GPG_KEYS_IMPORTED'}
# to true in which case the keys will be in /var/cpanel/.gpgtmpdir
sub gpg_homedir {
return '/var/cpanel/.gpgtmpdir';
}
sub public_keys {
return {
'release' => 'cPanelPublicKey.asc',
'development' => 'cPanelDevelopmentKey.asc',
};
}
sub secure_downloads {
return 'https://securedownloads.cpanel.net/';
}
sub gpg_keyrings {
return {
'release' => ['release'],
'development' => [ 'release', 'development' ],
};
}
1;
Copyright 2K16 - 2K18 Indonesian Hacker Rulez