File: //proc/self/root/proc/self/root/scripts.20110531.215904.25158/updatenow.1130.static
#!/usr/bin/perl
BEGIN { # Supress load of all of these at earliest point.
$INC{'Cpanel/Config/Sources.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Config/LoadConfig.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Hash.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SafeFile.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Logger.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Time.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SafeRun/Errors.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SafeRun/Simple.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/RcsRecord.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/FindBin.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SafeRun/InOut.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/OSSys.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/LoadFile.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/OSSys/Bits.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Sync/v2.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SafeDir.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SafeDir/MK.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/AccessIds.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/PwCache.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Debug.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/AccessIds/SetUids.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SafeDir/Read.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/LoadModule.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Rand.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Rand/Get.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Fcntl/Constants.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/FileUtils/TouchFile.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/FileUtils/Move.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/FileUtils/Link.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/HttpRequest.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Alarm.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/ArrayFunc.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/URL.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/PipeHandler.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/MirrorSearch.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/HttpTimer.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/UrlTools.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/SocketIP.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Encoder/URI.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/CPAN/URI/Escape.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Utils.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Tar.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Sync/Common.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Update/Logger.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Update.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Config/Services.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Services/Enabled.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Update/Config.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Config/FlushConfig.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Update/Blocker.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
$INC{'Cpanel/Usage.pm'} = '/usr/local/cpanel/scripts/updatenow.static';
}
{ # --- BEGIN Cpanel/Config/Sources.pm
package Cpanel::Config::Sources;
# use Cpanel::Config::LoadConfig ();
sub loadcpsources {
my $update_default = 'httpupdate.cpanel.net';
my %sources = (
'NEWS' => 'web.cpanel.net',
'RSYNC' => 'rsync.cpanel.net',
'HTTPUPDATE' => $update_default,
);
if ( -e '/etc/cpsources.conf' ) {
Cpanel::Config::LoadConfig::loadConfig( '/etc/cpsources.conf', \%sources );
}
$sources{'LAYER'} = $sources{'HTTPUPDATE'};
if ( $sources{'HTTPUPDATE'} eq 'layer2.cpanel.net' ) {
$sources{'LAYER'} = $update_default;
$sources{'HTTPUPDATE'} = $update_default;
}
return wantarray ? %sources : \%sources;
}
1;
} # --- END Cpanel/Config/Sources.pm
{ # --- BEGIN Cpanel/Config/LoadConfig.pm
package Cpanel::Config::LoadConfig;
# use Cpanel::Hash (); #checked for bloat 7/11/10 - jnk
# use Cpanel::SafeFile (); #checked for bloat 7/11/10 - jnk
# use Cpanel::Logger (); #checked for bloat 7/11/10 - jnk
my $logger;
sub loadConfig {
my ( $file, $conf_ref, $delimiter, $comment, $pretreatline, $allow_undef_values, $arg_ref ) = (
$_[0],
$_[1] || -1,
( $_[2] || '=' ),
( $_[3] || '^\s*[#;]' ),
( $_[4] || 0 ),
( $_[5] || 0 ),
$_[6]
);
my $use_reverse = 0;
my $use_hash_of_arr_refs = 0;
my $is_root = ( $> == 0 ? 1 : 0 );
if ( !$file ) {
$logger ||= Cpanel::Logger->new();
$logger->warn('loadConfig requires valid filename');
return;
}
my $filesys_mtime;
if ( !$arg_ref->{'skip_readable_check'} ) {
return if ( !-e $file );
if ( !-r _ ) {
$logger ||= Cpanel::Logger->new();
$logger->info("Unable to read $file, permission denied");
return;
}
$filesys_mtime = ( stat(_) )[9];
}
my $load_into_conf_ref = ( !ref $conf_ref && $conf_ref == -1 ) ? 0 : 1;
$conf_ref = _hashify_ref($conf_ref) if $load_into_conf_ref;
if ( defined($arg_ref) && exists( $arg_ref->{'use_reverse'} ) ) {
$use_reverse = $arg_ref->{'use_reverse'};
}
if ( $use_reverse == 0 ) {
delete $arg_ref->{'use_reverse'}; # should not have been sent -- delete to prevent dupe caching
}
if ( exists( $arg_ref->{'use_hash_of_arr_refs'} ) && defined( $arg_ref->{'use_hash_of_arr_refs'} ) ) {
$use_hash_of_arr_refs = $arg_ref->{'use_hash_of_arr_refs'};
}
if ( $use_hash_of_arr_refs == 0 ) {
delete $arg_ref->{'use_hash_of_arr_refs'}; #should not have been sent -- delete to prevent dupe caching
}
my $limit = exists $arg_ref->{'limit'} ? int $arg_ref->{'limit'} : 0;
my ( $homedir, $cache_dir );
if ($is_root) {
$cache_dir = '/var/cpanel/configs.cache';
}
else {
{
no warnings 'once';
$homedir = $Cpanel::homedir;
}
eval ' local $SIG{__DIE__}; local $SIG{__WARN__}; require Cpanel::PwCache; $homedir = (Cpanel::PwCache::getpwuid($>))[7]; ' if !$homedir;
$homedir = ( getpwuid($>) )[7] if !$homedir;
($homedir) = $homedir =~ /(.*)/; #untaint
$cache_dir = $homedir . '/.cpanel/caches/config';
}
my $cache_file;
if ( $INC{'Storable.pm'} && ( !defined $arg_ref || !ref $arg_ref || !exists $arg_ref->{'nocache'} ) ) {
my $safefile = $file;
$safefile =~ s/\//_/g;
my $stringified_args = join( '__', $delimiter, $comment, $pretreatline, $allow_undef_values, _sorted_hashref_txt($arg_ref) );
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) { print STDERR "::loadConfig stringified_args[$stringified_args]\n"; }
my $cache_key = $safefile . '___' . Cpanel::Hash::get_fastest_hash($stringified_args);
$cache_file = $cache_dir . '/' . $cache_key;
$filesys_mtime ||= ( stat($file) )[9];
if ( open( my $cache_fh, '<', $cache_file ) ) { # ok if the file is not there
my ( $cache_filesys_mtime, $now, $cache_conf_ref ) = ( ( stat($cache_fh) )[9], time() ); # stat the file after we have it open to avoid a race condition
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) { print STDERR __PACKAGE__ . "::loadConfig file:$file, cache_file:$cache_file, cache_filesys_mtime:$cache_filesys_mtime , filesys_mtime: $filesys_mtime , now : $now\n"; }
if ( $filesys_mtime && $cache_filesys_mtime > $filesys_mtime && $cache_filesys_mtime < $now ) {
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; $cache_conf_ref = Storable::pretrieve($cache_fh);'; # must be quoted or it ends up in the stash
if ( !$@ && $cache_conf_ref ) { #zero keys is a valid file still it may just be all comments or empty
close($cache_fh);
%{$conf_ref} = ( %{$conf_ref}, %{$cache_conf_ref} ) if $load_into_conf_ref;
return wantarray ? %{$cache_conf_ref} : $cache_conf_ref;
}
}
close($cache_fh);
}
}
$conf_ref = {} if !$load_into_conf_ref;
my $conflock = Cpanel::SafeFile::safeopen( \*CONF, '<', $file );
if ($conflock) {
if ( !$limit && !$pretreatline && !$use_hash_of_arr_refs ) {
local $/;
my $cfg_txt = readline( \*CONF );
Cpanel::SafeFile::safeclose( \*CONF, $conflock );
my $parser_code;
my $key_value_text = $use_reverse ? '1,0' : '0,1';
my ( $k, $v );
if ($allow_undef_values) {
$parser_code = ' %{$conf_ref}=( %$conf_ref, (map { ' . ( $comment ? 'm{' . $comment . '} ? () : ' : q{} ) . '(split(m/' . $delimiter . '/, $_, 2))[' . $key_value_text . '] } split(/\n/, $cfg_txt) ) ); ';
}
else {
if ($comment) {
$parser_code = ' %{$conf_ref}=( %$conf_ref, map { ' . 'if (m{' . $comment . '}) { (); } else { ' . '($k,$v) = (split(m/' . $delimiter . '/, $_, 2))[' . $key_value_text . ']; ' . 'defined($v) ? ($k,$v) : (); ' . '} } split(/\n/, $cfg_txt ) )';
}
else {
$parser_code = ' %{$conf_ref}=( %$conf_ref, map { ' . '($k,$v) = (split(m/' . $delimiter . '/, $_, 2))[' . $key_value_text . ']; ' . 'defined($v) ? ($k,$v) : () ' . '} split(/\n/, $cfg_txt ) )';
}
}
eval $parser_code;
if ($@) {
$logger ||= Cpanel::Logger->new();
$logger->panic("Failed to parse :: $parser_code");
exit 1;
}
delete $conf_ref->{''} if !defined( $conf_ref->{''} );
}
else {
if ( ( $Cpanel::Debug::level || 0 ) > 4 ) { print STDERR "[slow parser used for $file] LIMIT:[!$limit] PRETREATLINE[!$pretreatline] USE_HASH_OF_ARR_REFS[$use_hash_of_arr_refs)]\n"; }
my @dconf = <CONF>;
Cpanel::SafeFile::safeclose( \*CONF, $conflock );
my $keys = 0;
my ( $name, $value );
my $parser_code_ref;
my $parser_code = '$parser_code_ref = sub {
LINELOOP:
foreach my $line (@dconf) {
chomp $line; ' . "\n"
. q{next LINELOOP if $line eq '';} . "\n"
. ( $comment ? q{next LINELOOP if ( $line =~ m/$comment/o );} : '' ) . "\n"
. ( $limit ? q{last if $keys++ == } . $limit . ';' : '' ) . "\n"
. ( $pretreatline ? q{$line =~ s/$pretreatline//go;} : '' ) . "\n"
. ( $use_reverse ? q{( $value, $name ) = split( /$delimiter/, $line, 2 );} : q{( $name, $value ) = split( /$delimiter/, $line, 2 );} ) . "\n"
. ( !$allow_undef_values ? q{ next LINELOOP if !defined($value); } : '' ) . "\n"
. ( $use_hash_of_arr_refs ? q{ push @{ $conf_ref->{$name} }, $value; } : q{ $conf_ref->{$name} = $value; } ) . '
}
};';
eval $parser_code;
if ($@) {
$logger ||= Cpanel::Logger->new();
$logger->panic("Failed to generate parser code :: $parser_code");
exit 1;
}
$parser_code_ref->();
}
}
else {
Cpanel::Logger::cplog( "Unable to open $file: $!", 'warn', __PACKAGE__ );
return;
}
if ( defined($arg_ref) && exists( $arg_ref->{'skip_keys'} ) ) {
my %skip_key_list_ref;
if ( ref $arg_ref->{'skip_keys'} eq 'ARRAY' ) {
%skip_key_list_ref = map { $_ => 1 } @{ $arg_ref->{'skip_keys'} };
}
elsif ( ref $arg_ref->{'skip_keys'} eq 'HASH' ) {
%skip_key_list_ref = %{ $arg_ref->{'skip_keys'} };
}
delete @$conf_ref{ keys %skip_key_list_ref };
}
if ($cache_file) {
if ($is_root) {
if ( !-d $cache_dir ) {
($cache_dir) = $cache_dir =~ /(.*)/; #untaint
if ( !mkdir( $cache_dir, 0700 ) ) {
$logger ||= Cpanel::Logger->new();
$logger->warn( 'Could not create dir "' . $cache_dir . '"' );
}
}
}
elsif ( $homedir && !-e $cache_dir ) {
foreach my $dir ( $homedir . '/.cpanel', $homedir . '/.cpanel/caches', $cache_dir ) {
if ( !-d $dir ) {
($dir) = $dir =~ /(.*)/; #untaint
if ( !mkdir( $dir, 0700 ) ) {
$logger ||= Cpanel::Logger->new();
$logger->warn( 'Could not create dir "' . $dir . '"' );
}
}
}
}
eval 'Storable::lock_nstore( $conf_ref, $cache_file );'; #must be quoted or it ends up in the stash
}
return wantarray ? %{$conf_ref} : $conf_ref;
}
sub _hashify_ref {
my $conf_ref = shift;
if ( !defined($conf_ref) ) {
$conf_ref = {};
return $conf_ref;
}
unless ( ref $conf_ref eq 'HASH' ) {
if ( ref $conf_ref ) {
Cpanel::Logger::cplog( 'hashifying non-HASH reference', 'warn', __PACKAGE__ );
${$conf_ref} = {};
$conf_ref = ${$conf_ref};
}
else {
Cpanel::Logger::cplog( 'defined value encountered where reference expected', 'die', __PACKAGE__ );
}
}
return $conf_ref;
}
sub _sorted_hashref_txt {
my $hashref = shift;
return join(
'_____', map { $_, ( ref $hashref->{$_} eq 'HASH' ? _sorted_hashref_txt( $hashref->{$_} ) : ref $hashref->{$_} eq 'ARRAY' ? join( '_____', @{ $hashref->{$_} } ) : $hashref->{$_} ) }
sort keys %$hashref
); #sort is important for order;
}
1;
} # --- END Cpanel/Config/LoadConfig.pm
{ # --- BEGIN Cpanel/Hash.pm
package Cpanel::Hash;
sub get_fastest_hash {
my $data_to_hash = shift;
my $hashed_data;
if ( exists $INC{'Digest/MD5.pm'} ) {
eval '$hashed_data = Digest::MD5::md5_hex($data_to_hash);';
return $hashed_data if $hashed_data;
}
if ( exists $INC{'Cpanel/CPAN/Digest/Perl/MD5.pm'} ) { # nomunge
eval '$hashed_data = Cpanel::CPAN::Digest::Perl::MD5::md5_hex($data_to_hash);'; # nomunge
return $hashed_data if $hashed_data;
}
local @INC = ( '/usr/local/cpanel', '/usr/local/cpanel/scripts' );
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; require Cpanel::CPAN::Digest::Perl::MD5;';
if ( exists $INC{'Cpanel/CPAN/Digest/Perl/MD5.pm'} ) {
eval '$hashed_data = Cpanel::CPAN::Digest::Perl::MD5::md5_hex($data_to_hash);';
}
return $hashed_data if $hashed_data;
if ( exists $INC{'Crypt/Passwd/XS.pm'} ) {
my @predefsalt = ( 'ABCDEFGHI', 'JKLMNOPQR', 'STUVWXYZ1', '234567890', 'abcdefghi', 'jklmnopqr', 'rstuvwxyz', '!@#$%^&*(', ')-=+_;{}[' );
push @predefsalt, @predefsalt;
while ( @predefsalt && length($hashed_data) < 32 ) {
my ( $salt, $new_data ) = ( shift(@predefsalt), '' );
eval '($new_data) .= Crypt::Passwd::XS::unix_md5_crypt($data_to_hash,$salt);';
$new_data =~ s/^\$1\$[^\$]+\$//;
$new_data =~ tr/A-Zg-z.\//a-f0-9a-f0-9a-f0-9/;
$new_data =~ s/[^a-f0-9]//g;
$hashed_data .= $new_data;
}
$hashed_data = substr( $hashed_data, 0, 32 );
return $hashed_data if $hashed_data;
}
eval 'require Digest::MD5;';
eval '$hashed_data = Digest::MD5::md5_hex($data_to_hash);';
return $hashed_data if $hashed_data;
if ( exists $INC{'Carp.pm'} ) {
eval 'Carp::confess("Failed to generate hash");';
die "Failed to generate hash: $@";
}
die "Failed to generate hash";
}
1;
} # --- END Cpanel/Hash.pm
{ # --- BEGIN Cpanel/SafeFile.pm
package Cpanel::SafeFile;
$Cpanel::SafeFile::VERSION = '2.2';
use Symbol ();
use Fcntl (); # qw( O_WRONLY O_EXCL O_CREAT );
# use Cpanel::Logger ();
my $logger = Cpanel::Logger->new();
my $MAGIC_FCNTL_VALUE;
my $verbose; # initialized in safelock
my $MAX_FLOCK_WAIT = 60;
sub safeopen {
$_[0] = Symbol::gensym() if !ref $_[0]; # If we don't pass a filehandle then we generate a Symbol
my $fh = shift;
my ( $mode, $file ) = _get_open_args(@_);
if ( !$mode || !$file ) {
$logger->warn('Invalid arguments');
return;
}
elsif ( !defined $fh ) {
$logger->warn("Undefined file handle provided for safeopen of $file");
return;
}
else {
my $fh_type = ref $fh;
if ( $fh_type ne 'GLOB' && $fh_type ne 'IO::Handle' && $fh_type ne 'FileHandle' ) {
$logger->warn("Invalid file handle type $fh_type provided for safeopen of $file");
return;
}
}
if ( my $lockref = safelock($file) ) {
if ( open $fh, $mode, $file ) {
if ( !flock $fh, 6 ) { # flock 6 is LOCK_EX and LOCK_NB (non blocking exclusive lock)
eval {
local $SIG{'ALRM'} = sub { die "flock LOCK_EX timeout"; };
my $orig_alarm = alarm $MAX_FLOCK_WAIT;
flock $fh, 2; # This will hang
alarm $orig_alarm;
};
}
return $lockref;
}
else {
safeunlock($lockref);
return;
}
}
else {
$logger->warn('could not get a lock');
return;
}
}
sub safeclose {
my ( $fh, $lockref ) = @_;
if ( defined fileno $fh ) {
close $fh;
}
return safeunlock($lockref);
}
sub safe_replace_content {
my ( $fh, @content ) = @_;
@content = @{ $content[0] } if @_ == 2 && ref $content[0] eq 'ARRAY';
seek( $fh, 0, 0 );
print {$fh} @content;
truncate( $fh, tell($fh) );
}
sub safelock {
my ( $file, $lockfile, $lock_fh, $lock_file_dir, $attempts ) = ( $_[0] );
if ( !$file ) {
$logger->warn('safelock: Invalid arguments');
return;
}
$verbose ||= ( -e '/var/cpanel/safefile_verbose' ? 1 : -1 );
local $0 = "$0 - waiting for lockfile";
if ( !( $lockfile = _lock_wait($file) ) ) {
return;
}
$MAGIC_FCNTL_VALUE ||= ( &Fcntl::O_WRONLY | &Fcntl::O_EXCL | &Fcntl::O_CREAT );
while ( !sysopen( $lock_fh, $lockfile, $MAGIC_FCNTL_VALUE, 0600 ) && ++$attempts < 60 ) {
if ( !$lock_file_dir ) {
if ( !-w _getdir($lockfile) ) {
return 1;
}
}
$lockfile = _lock_wait($file);
return if !$lockfile;
}
if ($lock_fh) {
syswrite( $lock_fh, $$ . "\n" . $0 . "\n" );
return [ $lockfile, $lock_fh, ( stat($lock_fh) )[ 1, 9 ] ];
}
$logger->warn( 'safelock: waited for lock ' . $attempts . ' times' );
return;
}
sub safeunlock {
my $lockref = shift;
if ( !$lockref ) {
$logger->warn('safeunlock: Invalid arguments');
return;
}
elsif ( !ref $lockref && $lockref eq '1' ) {
return 1;
}
elsif ( !ref $lockref ) {
$lockref = [$lockref];
}
my ( $filesys_lock_ino, $filesys_lock_mtime ) = ( lstat( $lockref->[0] ) )[ 1, 9 ]; #->[0] = file
if ( !$filesys_lock_mtime ) {
$logger->warn("Lock $lockref->[0] lost!"); #->[0] = file
close( $lockref->[1] ) if ref $lockref->[1]; #->[1] = fh
return;
}
if ( $lockref->[2] == $filesys_lock_ino && $lockref->[3] == $filesys_lock_mtime ) { # if the fh we have open is the same inode we are good #->[2] = inode #->[3] = mtime
close( $lockref->[1] ) if ref $lockref->[1]; #->[0] = fh
unlink $lockref->[0] or return; #->[0] = file
return 1;
}
else {
close( $lockref->[1] ) if ref $lockref->[1]; #->[0] = fh
my ( $lock_pid, $lock_name ) = _fetch_lockfile_info( $lockref->[0] ); #->[0] = file
if ( !$lock_pid ) {
unlink $lockref->[0];
$logger->warn("Invalid zero length lock file $lockref->[0] detected."); #->[0] = file
return;
}
else {
$logger->warn("[$$] Attempt to unlock file that was locked by another process [LOCK] $lockref->[0] [LOCK PID] $lock_pid [LOCK PROCESS] $lock_name"); #->[0] = file
return;
}
}
}
sub _get_open_args {
my ( $mode, $file ) = @_;
if ( !$file ) {
( $mode, $file ) = $mode =~ m/^([<>+|]+|)(.*)/;
if ( $file && !$mode ) {
$mode = '<';
}
elsif ( !$file ) {
return;
}
}
$mode =
$mode eq '<' ? '<'
: $mode eq '>' ? '>'
: $mode eq '>>' ? '>>'
: $mode eq '+<' ? '+<'
: $mode eq '+>' ? '+>'
: return;
return ( $mode, $file );
}
sub _lock_wait {
my ( $file, $lockfile, $lockfile_inode, $lockfile_mtime ) = ( $_[0], ( $_[0] =~ /^[><]*(.*)/ )[0] . '.lock' );
return $lockfile if !-e $lockfile;
my ( $start_lockfile_inode, $fileuid, $locksize, $start_lockfile_mtime ) = ( stat(_) )[ 1, 4, 7, 9 ];
my $attempts = 0;
while ( !$locksize && ++$attempts < 15 ) { # wait up to 15 seconds for the lock file data to get written
sleep(1); # must sleep one because mtime is only granular to the second
( $lockfile_inode, $fileuid, $locksize, $lockfile_mtime ) = ( stat($lockfile) )[ 1, 4, 7, 9 ];
if ( ( $lockfile_inode && $lockfile_inode != $start_lockfile_inode ) || ( $lockfile_mtime && $lockfile_mtime != $start_lockfile_mtime ) ) {
$locksize = 0;
$start_lockfile_inode = $lockfile_inode;
$start_lockfile_mtime = $lockfile_mtime;
next;
}
if ( !defined $locksize ) {
return $lockfile;
}
elsif ( $locksize == 0 ) {
$start_lockfile_mtime = $lockfile_mtime;
$logger->invalid("Invalid lockfile $lockfile detected (zero size) [UID]: $fileuid [MTIME]: $start_lockfile_mtime [ORIGINAL INODE]: $start_lockfile_inode [TESTED INODE]: $lockfile_inode");
last;
}
}
my $waittime = 60;
if ( -e $file ) {
$waittime = int( ( stat(_) )[7] / 10000 );
$waittime = $waittime > 350 ? 350 : $waittime < 60 ? 60 : $waittime; # waittime is always between 60 and 350 seconds
}
my $lock_file_age = ( time() - $start_lockfile_mtime );
if ( $lock_file_age > $waittime ) {
$logger->info("Stale lock file: $lockfile. Age is $lock_file_age (mtime=$start_lockfile_mtime) which is longer then waittime ($waittime)") if $verbose == 1;
unlink $lockfile;
return $lockfile;
}
my $lock_is_our_uid = ( $fileuid == $> );
if ( $lock_is_our_uid || $> == 0 ) {
my $proc_is_usable = _proc_is_usable();
my $lock_pid = 0;
my $lock_name;
if ( $locksize && ( $lock_is_our_uid || $proc_is_usable ) ) { # PID is inside lock file
( $lock_pid, $lock_name ) = _fetch_lockfile_info($lockfile);
}
if ( !$lock_pid ) {
$logger->info("[$$] Waiting on invalid lock $lockfile for $waittime seconds") if $verbose == 1;
}
elsif ( $lock_pid == $$ ) {
$logger->invalid("[$$] Double locking detected on $file by self ($lock_name)");
return;
}
else {
$logger->info("[$$] Waiting for lock on $file held by $lock_name with pid $lock_pid") if $verbose == 1;
}
if ( _is_valid_pid($lock_pid) && ( $lock_is_our_uid || ( $proc_is_usable && -e '/proc/' . $lock_pid ) ) ) {
my $seconds_waiting = 0;
while (1) {
sleep 1;
$seconds_waiting++;
last if ( $seconds_waiting > $waittime );
last if !-e $lockfile;
if ($lock_is_our_uid) {
if ( !kill( 0, $lock_pid ) ) {
last;
}
}
elsif ( $proc_is_usable && !-e '/proc/' . $lock_pid ) {
last;
}
}
my $mtime = ( stat($lockfile) )[9];
if ( !$mtime ) {
$logger->info("[$$] Lock file $lockfile now gone, try to acquire") if $verbose == 1;
return $lockfile;
}
if ( $mtime == $start_lockfile_mtime ) {
$logger->info("[$$] Removing expired lock file $lockfile") if $verbose == 1;
unlink $lockfile;
return $lockfile;
}
else {
$start_lockfile_mtime = $mtime;
}
}
}
my $seconds_waiting = 0;
WAIT:
while ( $start_lockfile_mtime > 0 ) {
if ( -e $lockfile ) {
my $mtime = ( stat(_) )[9];
sleep 1;
if ( $mtime == $start_lockfile_mtime ) {
$seconds_waiting++; # lock file has aged
}
else { # there is a new lock file, reset
$start_lockfile_mtime = $mtime;
$seconds_waiting = 0;
}
if ( $seconds_waiting >= $waittime ) { # lock file aged waittime sec and we can stop waiting
$logger->info("Lock file $lockfile expired") if $verbose == 1;
unlink $lockfile;
last WAIT;
}
}
else {
last WAIT;
}
}
return $lockfile;
}
sub safe_readwrite {
my ( $file, $code_ref ) = @_;
return if !defined $file || $file eq '' || ref $code_ref ne 'CODE';
if ( my $lockref = safeopen( \*SAFEEDIT, '+<', $file ) ) {
my $rclog = $code_ref->( \*SAFEEDIT, \&safe_replace_content );
safeclose( \*SAFEEDIT, $lockref );
if ( $rclog && $rclog ne '0E0' ) {
eval q{
require Cpanel::RcsRecord;
Cpanel::RcsRecord::rcsrecord( $file, $rclog );
};
}
return $rclog;
}
else {
return;
}
}
sub _proc_is_usable {
return ( -e '/proc/1' && -r _ ) ? 1 : 0;
}
sub _fetch_lockfile_info {
my $lockfile = shift;
my ( $lock_pid, $lock_name );
if ( open my $lockfile_fh, '<', $lockfile ) {
local $/;
my ( $pid_line, $lock_name ) = split( /\n/, readline($lockfile_fh) );
chomp($lock_name);
($lock_pid) = $pid_line =~ m/(\d+)/;
close $lockfile_fh;
return ( $lock_pid, $lock_name || 'unknown' );
}
}
sub _is_valid_pid {
my $pid = shift;
return ( $pid =~ /^\d+$/ ? 1 : 0 );
}
sub _getdir {
my $file = shift;
my @path = split( /\/+/, $file );
pop(@path);
return join( '/', @path );
}
1;
} # --- END Cpanel/SafeFile.pm
{ # --- BEGIN Cpanel/Logger.pm
package Cpanel::Logger;
# use Cpanel::Time ();
my $tree;
our $VERSION = 1.0;
my $std_log_file = '/usr/local/cpanel/logs/error_log';
my ( $cached_progname, $cached_prog_pid, %singleton_stash );
sub new {
my ( $class, $hr_args ) = @_;
my $args_sig = 'no_args';
if ( $hr_args && ref($hr_args) eq 'HASH' ) {
$args_sig = join( ',', map { $_ . '=>' . $hr_args->{$_} } sort keys %{$hr_args} ); # Storable::freeze($hr_args);
}
if ( exists $singleton_stash{$class}{$args_sig} ) {
$singleton_stash{$class}{$args_sig}->{'cloned'}++;
}
else {
$singleton_stash{$class}{$args_sig} = bless( {}, $class );
if ( $hr_args && ref($hr_args) eq 'HASH' ) {
foreach my $k ( keys %$hr_args ) {
$singleton_stash{$class}{$args_sig}->{$k} = $hr_args->{$k};
}
}
}
if ( !$singleton_stash{$class}{$args_sig}->{'cloned'} ) {
$singleton_stash{$class}{$args_sig}->reset_accumulator();
$singleton_stash{$class}{$args_sig}->accumulate(1);
}
if ( exists $ENV{'CPANEL_LOGGER_FILE'} && $ENV{'CPANEL_LOGGER_FILE'} ) {
my $alt_log_file = $ENV{'CPANEL_LOGGER_FILE'};
$alt_log_file =~ s/\.\.//g; # Prevent directory traversal
if ( $> == 0 ) {
if ( $alt_log_file =~ m/([^\/]+)$/ ) {
$alt_log_file = $1;
}
$std_log_file = '/usr/local/cpanel/logs/' . $alt_log_file;
}
else {
$std_log_file = $alt_log_file;
}
}
return $singleton_stash{$class}{$args_sig};
}
sub __Logger_pushback {
my (@list) = @_;
my $p = __PACKAGE__;
if ( @list && ref( $list[0] ) =~ /^${p}/ ) {
return ( shift(@list), @list );
}
else {
my $self = $p->new();
return ( $self, @list );
}
}
sub accumulate {
my ( $self, $on ) = &__Logger_pushback(@_);
$self->{'accumulator_on'} = ( $on || !defined($on) ? 1 : 0 );
}
sub no_accumulate {
&accumulate( @_, 0 );
}
sub is_accumulated {
my ($self) = &__Logger_pushback(@_);
return ( @{ $self->{'container'} } ? 1 : 0 );
}
sub get_accumulate {
my ($self) = &__Logger_pushback(@_);
return wantarray ? @{ $self->{'container'} } : $self->{'container'};
}
sub get_messages {
&get_accumulate(@_);
}
sub get_last_msg {
my ($self) = &__Logger_pushback(@_);
return ${ $self->{'container'} }[ $#{ $self->{'container'} } ];
} # end of get_last_msg
sub get_msg_of_type {
my ( $self, $type ) = &__Logger_pushback(@_);
return if !$type;
my @msgs;
foreach my $m ( @{ $self->{'container'} } ) {
push( @msgs, $m ) if ( $m =~ m/^$type/ );
}
return wantarray ? @msgs : \@msgs;
} # end of get_msg_of_type
sub clear_messages {
&reset_accumulator(@_);
}
sub reset_accumulator {
my ($self) = &__Logger_pushback(@_);
$self->{'container'} = [];
}
sub store_message {
my ( $self, @list ) = &__Logger_pushback(@_);
if ( $self->{'accumulator_on'} ) {
while ( $#list > 2 ) {
pop(@list);
}
push( @{ $self->{'container'} }, sprintf( "%s [%s] %s", @list ) );
}
}
sub invalid {
my ( $self, @list ) = &__Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'invalid',
'service' => $self->find_progname(),
'output' => 0,
'backtrace' => 1,
'die' => 0,
);
if ( -e '/var/cpanel/dev_sandbox' ) {
if ( !-e '/var/cpanel/DEBUG' ) {
require Cpanel::SafeRun::Errors;
my $time = Cpanel::Time::localtime2timestamp();
Cpanel::SafeRun::Errors::saferunallerrors( '/usr/local/cpanel/scripts/icontact', '--define', qq[app=$log{'service'}], '--define', qq[subject=Cpanel::Logger::invalid called in $log{'service'}], '--define', "message=$time [$log{'service'}] " . $self->backtrace( $log{'message'} ), '--define', q{level=3}, );
}
$log{'output'} = -t STDIN ? 2 : 1;
}
$self->logger( \%log );
} # end of invalid
sub debug {
my ( $self, $message, $conf_hr ) = @_; # not appropriate for debug() : __Logger_pushback(@_);
$self = $self->new() if !ref $self;
$conf_hr ||= {
'force' => 0,
'backtrace' => 0,
'output' => 0,
};
return unless $conf_hr->{'force'} || ( defined $Cpanel::Debug::level && $Cpanel::Debug::level );
if ( !defined $message ) {
my @caller = caller();
$message = "debug() at $caller[1] line $caller[2].";
}
my %log = (
'message' => $message,
'level' => 'debug',
'service' => $self->find_progname(),
'output' => $conf_hr->{'output'},
'backtrace' => $conf_hr->{'backtrace'},
);
if ( ref $log{'message'} ) {
my $outmsg = $log{'message'};
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; require YAML::Syck; $outmsg = YAML::Syck::Dump($outmsg) ';
my @caller = caller();
$log{'message'} = "$log{'message'} at $caller[1] line $caller[2]:\n" . $outmsg;
}
elsif ( $log{'message'} =~ m/\A\d+(?:\.\d+)?\z/ ) {
$log{'message'} = "debug() number $log{'message'}";
}
$self->store_message( $log{'level'}, $log{'service'}, $log{'message'} );
$self->logger( \%log );
return \%log;
}
sub info {
my ( $self, @list ) = &__Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'info',
'service' => $self->find_progname(),
'output' => 0,
'backtrace' => 0
);
$self->store_message( $log{'level'}, $log{'service'}, $log{'message'} );
$self->logger( \%log );
} # end of info
sub warn {
my ( $self, @list ) = &__Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'warn',
'service' => $self->find_progname(),
'output' => -t STDIN ? 2 : 0,
'backtrace' => 1
);
$self->store_message( $log{'level'}, $log{'service'}, $log{'message'} );
$self->logger( \%log );
} # end of warn
sub die {
my ( $self, @list ) = &__Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'die',
'service' => $self->find_progname(),
'output' => -t STDIN ? 2 : 0,
'backtrace' => 1
);
$self->store_message( $log{'level'}, $log{'service'}, $log{'message'} );
$self->logger( \%log );
} # end of die
sub panic {
my ( $self, @list ) = &__Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'panic',
'service' => $self->find_progname(),
'output' => 2,
'backtrace' => 1
);
$self->store_message( $log{'level'}, $log{'service'}, $log{'message'} );
$self->logger( \%log );
} # end of panic
sub cplog {
my $msg = shift;
my $loglevel = shift;
my $service = shift;
my $nostdout = shift;
if ( !$nostdout ) {
$nostdout = 1;
}
else {
$nostdout = 0;
}
logger( { 'message' => $msg, 'level' => $loglevel, 'service' => $service, 'output' => $nostdout, 'backtrace' => 1 } );
} # end of cplog (deprecated)
sub event {
my ( $self, @list ) = &__Logger_pushback(@_);
my %args;
if ( @list && @list % 2 ) {
$args{'title'} = $list[0];
return if !$args{'title'};
}
else {
%args = @list;
}
if ( !$args{'summary'} ) {
$args{'summary'} = '';
}
$self->logger(
{
'message' => $args{'title'},
'service' => $args{'summary'},
'output' => 0,
'backtrace' => 1,
}
);
}
sub logger {
my ( $self, @list ) = &__Logger_pushback(@_);
my $hr = ref( $list[0] ) eq 'HASH' ? $list[0] : { 'message' => $list[0] };
$hr->{'message'} ||= 'Something is wrong';
$hr->{'level'} ||= '';
$hr->{'service'} ||= $self->find_progname();
$hr->{'output'} ||= 0;
if ( !exists $hr->{'backtrace'} ) {
$hr->{'backtrace'} = 1;
}
my $time = Cpanel::Time::localtime2timestamp();
my ( $usingstderr, $logfile ) = ( 0, ( $self->{'alternate_logfile'} ? $self->{'alternate_logfile'} : $std_log_file ) );
my $log_fh;
unless ( open $log_fh, '>>', $logfile ) {
( $usingstderr, $log_fh ) = ( 1, \*STDERR );
}
syswrite( $log_fh, '[' . $time . '] ' . $hr->{'level'} . ' [' . $hr->{'service'} . '] ' . ( $hr->{'backtrace'} ? $self->backtrace( $hr->{'message'} ) : $hr->{'message'} . "\n" ) );
if ( $hr->{'level'} eq 'panic' || $hr->{'level'} eq 'invalid' ) {
if ( open my $panic_fh, '>>', '/usr/local/cpanel/logs/panic_log' ) {
syswrite( $panic_fh, "$time $hr->{level} [$hr->{'service'}] " . ( $hr->{'backtrace'} ? $self->backtrace( $hr->{'message'} ) : $hr->{'message'} . "\n" ) );
close $panic_fh;
}
}
else {
$hr->{'output'} = 1;
}
if ( $hr->{'output'} ) {
my $out = "$hr->{level} [$hr->{'service'}] $hr->{'message'}\n";
if ( $hr->{'output'} == 3 ) {
print STDOUT $out;
print STDERR $out;
}
elsif ( $hr->{'output'} == 1 && -t STDOUT ) {
print STDOUT $out;
}
elsif ( $hr->{'output'} == 2 ) {
print STDERR $out;
}
}
if ( !$usingstderr ) {
close $log_fh;
}
if ( ( $hr->{'level'} eq 'die' || $hr->{'level'} eq 'panic' || $hr->{'die'} ) ) {
CORE::die "exit level [$hr->{'level'}] ($hr->{'message'})\n";
}
} # end of logger
sub find_progname {
if ( $cached_progname && $cached_prog_pid == $$ ) {
return $cached_progname;
}
my $s;
my $i = 1; # 0 is always find_progname
while ( my @service = caller( $i++ ) ) {
last if ( $service[3] =~ /::BEGIN$/ );
$s = $service[1] if ( $service[1] ne '' );
}
$s =~ s@.+/(.+)$@$1@;
$s =~ s@\..+$@@;
$cached_progname = $s;
$cached_prog_pid = $$;
$s;
}
sub backtrace {
my ( $self, @list ) = &__Logger_pushback(@_);
return (@list) if ( ref $list[0] );
local $_; # Protect surrounding program - just in case...
my ( $pack, $file, $line, $sub, $hargs, $eval, $require, @parms );
my $error = join( '', @list );
my $msg = '';
my $i = 0;
while (
do {
{
package DB;
( $pack, $file, $line, $sub, $hargs, undef, $eval, $require ) = caller( $i++ )
}
}
) {
next if ( $pack eq 'Cpanel::Logger' );
if ( $error eq '' ) {
if ( defined $eval ) {
$eval =~ s/([\\\'])/\\$1/g unless ($require); # Escape \ and '
$eval =~ s/([\x00-\x1F\x7F-\xFF])/sprintf("\\x%02X",ord($1))/eg;
if ($require) { $sub = "require $eval"; }
else { $sub = "eval '$eval'"; }
}
elsif ( $sub eq '(eval)' ) { $sub = 'eval {...}'; }
else {
@parms = ();
if ($hargs) {
@parms = @DB::args;
for (@parms) {
if ( defined $_ ) {
if ( ref $_ ) {
$_ = "$_";
}
else {
unless (/^-?\d+(?:\.\d+(?:[eE][+-]\d+)?)?$/) {
s/([\\\'])/\\$1/g;
s/([\x00-\x1F\x7F-\xFF])/sprintf("\\x%02X",ord($1))/eg;
$_ = "'$_'";
}
}
}
else { $_ = 'undef'; }
}
}
$sub .= '(' . join( ', ', @parms ) . ')';
}
if ( $msg eq '' ) { $msg = "\n\t$sub called"; }
else { $msg .= "\t$sub called"; }
}
else {
$msg = $error;
}
$msg .= " at $file line $line\n";
$error = '';
}
$msg ||= $error;
$msg =~ tr/\0//d; # Circumvent die's incorrect handling of NUL characters
$msg;
} # end of backtrace
1;
} # --- END Cpanel/Logger.pm
{ # --- BEGIN Cpanel/Time.pm
package Cpanel::Time;
my ( $timecacheref, $localtimecacheref ) = ( [ -1, '', -1 ], [ -1, '', -1 ] );
my @weekday = qw/Sun Mon Tue Wed Thu Fri Sat/;
my @longday = qw(Sunday Monday Tuesday Wednesday Thursday Friday Saturday);
my @month = qw/Jan Feb Mar Apr May Jun Jul Aug Sep Oct Nov Dec/;
sub time2http {
my $time = shift || time();
my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = gmtime $time;
return sprintf( '%s, %02d %s %04d %02d:%02d:%02d GMT', $weekday[$wday], $mday, $month[$mon], $year + 1900, $hour, $min, $sec );
}
sub time2clftime {
my $time = shift || time();
my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime $time;
return sprintf( '%02d/%02d/%04d:%02d:%02d:%02d -0000', $mon + 1, $mday, $year + 1900, $hour, $min, $sec );
}
sub time2dnstime {
my $time = shift || time();
my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime $time;
return sprintf( '%04d%02d%02d', $year + 1900, $mon + 1, $mday );
}
sub time2condensedtime {
my $time = shift || time();
my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime $time;
return sprintf( '%04d%02d%02d%02d%02d%02d', $year + 1900, $mon + 1, $mday, $hour, $min, $sec );
}
sub time2datetime {
my $time = shift || time();
my $delimiter = shift;
$delimiter = ' ' unless defined $delimiter;
return $timecacheref->[2] if $timecacheref->[0] == $time && $timecacheref->[1] eq $delimiter;
@{$timecacheref}[ 0, 1 ] = ( $time, $delimiter );
my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime $time;
return sprintf( '%04d-%02d-%02d' . $delimiter . '%02d:%02d:%02d', $year + 1900, $mon + 1, $mday, $hour, $min, $sec );
}
sub localtime2timestamp {
my $time = shift || time();
my $delimiter = shift;
$delimiter = ' ' unless defined $delimiter;
return $localtimecacheref->[2] if $localtimecacheref->[0] == $time && $localtimecacheref->[1] eq $delimiter;
my $tz_offset = get_server_offset_as_offset_string();
@{$localtimecacheref}[ 0, 1 ] = ( $time, $delimiter );
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime $time;
return ( $localtimecacheref->[1] = sprintf( '%04d-%02d-%02d' . $delimiter . '%02d:%02d:%02d %s', $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $tz_offset ) );
}
sub time2cookie {
my $time = shift || time();
my ( $sec, $min, $hour, $mday, $mon, $year, $wday ) = gmtime $time;
return sprintf( '%s, %02d-%s-%02d %02d:%02d:%02d GMT', $longday[$wday], $mday, $month[$mon], $year % 100, $hour, $min, $sec );
}
my $server_offset;
sub get_server_offset_in_seconds {
if ( !defined $server_offset ) {
if ( get_server_offset_as_offset_string() =~ m/([-+]?[0-9]{2})([0-9]{2})/ ) {
my ( $hours, $minutes ) = ( $1, $2 );
my $seconds = ( ( abs($hours) * 60 * 60 ) + ( $minutes * 60 ) );
$server_offset = $hours < 0 ? "-$seconds" : $seconds;
}
else {
$server_offset = 0;
}
}
return $server_offset;
}
my $server_offset_string;
sub get_server_offset_as_offset_string {
if ( !defined $server_offset_string ) {
my $time = time;
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime $time;
my ( $gmmin, $gmhour, $gmday ) = ( gmtime($time) )[ 1, 2, 3 ];
my $gmoffset = ( $hour * 60 + $min ) - ( $gmhour * 60 + $gmmin ) + 1440 * ( $mday <=> $gmday );
$server_offset_string = sprintf( '%+03d%02d', int( $gmoffset / 60 ), $gmoffset % 60 );
}
return $server_offset_string;
}
1;
} # --- END Cpanel/Time.pm
{ # --- BEGIN Cpanel/SafeRun/Errors.pm
package Cpanel::SafeRun::Errors;
# use Cpanel::SafeRun::Simple ();
sub saferunallerrors {
my $output_ref = Cpanel::SafeRun::Simple::_saferun_r( \@_, 1 ); #1 = errors to stdout
return wantarray ? split( /\n/, $$output_ref ) : $$output_ref;
}
sub saferunnoerror {
my $output_ref = Cpanel::SafeRun::Simple::_saferun_r( \@_, 2 ); # 2 = errors to devnull
return wantarray ? split( /\n/, $$output_ref ) : $$output_ref;
}
1;
} # --- END Cpanel/SafeRun/Errors.pm
{ # --- BEGIN Cpanel/SafeRun/Simple.pm
package Cpanel::SafeRun::Simple;
use strict;
sub saferun_r {
return _saferun_r( \@_ );
}
sub _saferun_r {
my ( $cmdline, $error_flag ) = @_;
my $output;
if ( substr( $cmdline->[0], 0, 1 ) eq '/' ) {
my ($check) = !-e $cmdline->[0] && $cmdline->[0] =~ /[\s<>&\|\;]/ ? split( /[\s<>&\|\;]/, $cmdline->[0], 2 ) : $cmdline->[0];
if ( !-x $check ) {
$? = -1;
return \$output;
}
}
local ($/);
my ( $pid, $prog_fh );
if ( $pid = open( $prog_fh, '-|' ) ) {
}
else {
( $ENV{'PATH'} ) = $ENV{'PATH'} =~ m/(.*)/; # untaint, case 6622
if ( $error_flag && $error_flag == 1 ) { open( STDERR, '>&STDOUT' ); }
elsif ( $error_flag && $error_flag == 2 ) { open( STDERR, '>', '/dev/null' ); }
exec(@$cmdline);
exit(127);
}
if ( !$prog_fh || !$pid ) {
$? = -1;
return \$output;
}
$output = readline($prog_fh);
close($prog_fh);
return \$output;
}
sub saferun {
my $ref = _saferun_r( \@_, 0 );
if ($ref) {
return $$ref;
}
else {
return;
}
}
1;
} # --- END Cpanel/SafeRun/Simple.pm
{ # --- BEGIN Cpanel/RcsRecord.pm
package Cpanel::RcsRecord;
# use Cpanel::SafeFile ();
# use Cpanel::FindBin ();
# use Cpanel::SafeRun::InOut ();
our $SKIP_IF_MTIME_MATCHES = 2;
our $VERSION = '4.3';
sub rcsrecord {
my ( $file, $description, $skip_lock, $flags ) = @_;
if ( !$description ) {
$description = '';
}
my ( $uid, $gid, $size, $mtime ) = ( stat($file) )[ 4, 5, 7, 9 ];
if ( !$mtime ) { return; }
my $maxsize = ( $size * 25 );
if ( $maxsize < 32768 ) { $maxsize = 32768; }
my @DIR = split( /\//, $file );
my $truefile = pop(@DIR);
my $dir = '';
if ( $#DIR > -1 ) {
$dir = join( '/', @DIR );
}
if ( !$dir ) {
$dir = '.';
}
if ( $uid != $> ) {
my $dir_uid = ( stat($dir) )[4];
if ( $dir_uid != $> ) {
warn "rcsrecord skipped on $file because we do not own it or the directory it is in";
return;
}
}
my $rcslock;
if ( !$skip_lock ) {
$rcslock = Cpanel::SafeFile::safelock($file);
if ( !$rcslock ) {
return;
}
}
if ( -e "$dir/,$truefile," ) {
my $tmp_mtime = ( stat("$dir/,$truefile,") )[9];
if ( ( time() - $tmp_mtime ) > 360 ) { #remove temp files after 5 minutes
unlink("$dir/,$truefile,");
}
}
my $rcs_bin;
if ( !-e "$dir/$truefile,v" ) {
$rcs_bin = Cpanel::FindBin::findbin('rcs');
if ($rcs_bin) {
if ( my $rcs_pid = fork() ) {
waitpid( $rcs_pid, 0 );
}
elsif ( defined $rcs_pid ) {
open( STDOUT, '>&STDERR' );
open( STDIN, '<', '/dev/null' );
exec( $rcs_bin, '-q', '-i', $file );
exit 1;
}
else {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
}
else {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
}
elsif ( $flags & $SKIP_IF_MTIME_MATCHES ) {
if ( $mtime && $mtime <= ( stat(_) )[9] ) {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
}
my $ci_bin = Cpanel::FindBin::findbin('ci');
if ($ci_bin) {
my $safe_msg = "Modified by $0 $description";
$safe_msg =~ s/\"//g;
if ( my $ci_pid = fork() ) {
waitpid( $ci_pid, 0 );
}
elsif ( defined $ci_pid ) {
open( STDIN, '<', '/dev/null' );
open( STDOUT, '>', '/dev/null' );
open( STDERR, '>', '/dev/null' );
exec( $ci_bin, '-l', qq{-m"$safe_msg"}, $file );
exit 1;
}
else { #fork failed
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
}
else {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
if ( ( stat("$dir/$truefile,v") )[7] > $maxsize ) {
my $rlog_bin = Cpanel::FindBin::findbin('rlog');
my @REVS;
if ($rlog_bin) {
my $head;
my $rlog_pid = Cpanel::SafeRun::InOut::inout( my $wtr_rlog, my $rdr_rlog, $rlog_bin, $file );
if ($rlog_pid) {
close($wtr_rlog);
while ( readline($rdr_rlog) ) {
if (/revision[\s\t]+([\d\.]+)/) {
push @REVS, $1;
}
}
close($rdr_rlog); # no waitpid needed because inout will use an open call which will waitpid automaticlly when this is closed
}
else {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
}
else {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
if (@REVS) {
my $rev = $REVS[ int( $#REVS / 2 ) ]; #remove 50% of revisions
if ( !$rcs_bin ) { $rcs_bin = Cpanel::FindBin::findbin('rcs'); }
if ( !$rcs_bin ) {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
if ( my $rcs_pid = fork() ) {
waitpid( $rcs_pid, 0 );
}
elsif ( defined $rcs_pid ) {
open( STDOUT, '>&STDERR' );
open( STDIN, '<', '/dev/null' );
exec( $rcs_bin, '-q', '-o:' . $rev, $file );
exit 1;
}
else {
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
return;
}
}
}
if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); }
chown $uid, $gid, $file;
}
1;
} # --- END Cpanel/RcsRecord.pm
{ # --- BEGIN Cpanel/FindBin.pm
package Cpanel::FindBin;
use strict;
our $VERSION = 1.2;
my %bin_cache;
my @default_path = qw( /usr/bin /usr/local/bin /bin /sbin /usr/sbin /usr/local/sbin );
sub findbin {
my $binname = shift;
return if !$binname;
my @lookup_path = get_path(@_);
my $nocache = grep( /nocache/, @_ );
if ( !$nocache && exists $bin_cache{$binname} && $bin_cache{$binname} ne '' ) {
return $bin_cache{$binname};
}
foreach my $path (@lookup_path) {
if ( -x $path . '/' . $binname ) {
$bin_cache{$binname} = $path . '/' . $binname unless $nocache;
return $path . '/' . $binname;
}
}
return;
}
sub get_path {
if ( !$_[0] ) {
return @default_path;
}
elsif ( scalar @_ > 1 ) {
my %opts = @_;
if ( exists $opts{'path'} && ref $opts{'path'} eq 'ARRAY' ) {
return @{ $opts{'path'} };
}
else {
return @_;
}
}
elsif ( ref $_[0] eq 'ARRAY' ) {
return @{ $_[0] };
}
return @default_path;
}
1;
} # --- END Cpanel/FindBin.pm
{ # --- BEGIN Cpanel/SafeRun/InOut.pm
package Cpanel::SafeRun::InOut;
use Symbol ();
sub inout {
my $wtr = $_[0] ||= Symbol::gensym();
my $rdr = $_[1] ||= Symbol::gensym();
my $child_read;
pipe( $child_read, $wtr );
select( ( select($wtr), $| = 1 )[0] ); #aka $wtr->autoflush(1);
select( ( select($child_read), $| = 1 )[0] ); #aka $child_read->autoflush(1);
if ( my $pid = open( $rdr, '-|' ) ) {
return $pid;
}
elsif ( defined $pid ) {
open( STDIN, '<&=' . fileno($child_read) );
if ( ref $_[2] eq 'CODE' ) {
$_[2]->();
exec( @_[ 3 .. $#_ ] );
}
else {
exec( @_[ 2 .. $#_ ] );
}
exit 1;
}
else {
return;
}
}
1;
} # --- END Cpanel/SafeRun/InOut.pm
{ # --- BEGIN Cpanel/OSSys.pm
package Cpanel::OSSys;
# use Cpanel::LoadFile (); # used by Cpanel::Logger and Cpanel::OSSys::get_envtype
# use Cpanel::Logger ();
# use Cpanel::OSSys::Bits ();
our $VERSION = 1.5;
my $system;
my $POSIX = 'POSIX';
if ( !exists $INC{'POSIX.pm'} && !exists $INC{'Cpanel/POSIX/Tiny.pm'} ) {
eval '
local $SIG{__DIE__} = "DEFAULT";
use Cpanel::POSIX::Tiny (); #issafe
$system = lc( ( Cpanel::POSIX::Tiny::uname() )[0] ); #issafe
$POSIX = "Cpanel::POSIX::Tiny"; #issafe
';
}
elsif ( exists $INC{'Cpanel/POSIX/Tiny.pm'} ) {
$POSIX = "Cpanel::POSIX::Tiny"; #issafe
$system = lc( ( uname() )[0] ); #issafe
}
if ( !$system ) {
eval 'use POSIX ();
$system = lc( ( POSIX::uname() )[0] );';
}
sub get_system {
return $system;
}
sub getos { goto &get_system; }
sub uname {
my @UNAME;
eval '@UNAME = ' . $POSIX . '::uname();';
my $bits = Cpanel::OSSys::Bits::getbits();
if ( $bits == 32 && $UNAME[4] =~ /64/ ) {
$UNAME[4] = 'i686'; #virtualized in the wrong basesystem
}
wantarray ? return @UNAME : return $UNAME[0];
}
sub write {
eval $POSIX . '::write(@_);';
}
sub nice {
eval $POSIX . "::nice($_[0]);";
}
sub setsid {
eval $POSIX . "::setsid()";
eval '*Cpanel::OSSys::setsid = *' . $POSIX . '::setsid;';
}
sub pipe {
my @fds;
eval '@fds = ' . $POSIX . "::pipe()";
return @fds;
}
sub close {
eval $POSIX . '::close(@_)';
eval '*Cpanel::OSSys::close = *' . $POSIX . '::close;';
}
sub times {
eval '*times = \&' . $POSIX . "::times;";
goto \× if !$@;
}
sub sysconf {
my $const = shift;
if ( ref $const ) {
my $logger = Cpanel::Logger->new();
$logger->die( "sysconf cannot accept a " . scalar ref $const . " it must be passed a scalar which will be converted into a constant" );
}
my $res;
my $val = ( $POSIX ne 'POSIX' ) ? ( eval $POSIX . '::constant($const,0);' ) : ( eval '&' . $POSIX . "::$const" );
if ( !$val ) {
my $logger = Cpanel::Logger->new();
$logger->die("Unknown constant $const for $POSIX");
}
eval '$res = ' . $POSIX . '::sysconf(' . $val . ');';
return $res;
}
sub get_envtype {
my $envtype = Cpanel::LoadFile::loadfile( '/var/cpanel/envtype', { 'skips_exists_check' => 1 } );
if ( !$envtype ) {
my $logger = Cpanel::Logger->new();
$logger->warn('Failed to load envtype. Defaulting to "standard".');
return 'standard';
}
return $envtype;
}
1;
} # --- END Cpanel/OSSys.pm
{ # --- BEGIN Cpanel/LoadFile.pm
package Cpanel::LoadFile;
use Fcntl ();
sub loadfileasarrayref {
my $fileref = _load_file( shift, { 'array_ref' => 1 } );
if ( ref $fileref eq 'ARRAY' ) { return $fileref; }
return;
}
sub loadbinfile {
my $fileref = _load_file( shift, { 'binmode' => 1 } );
if ( ref $fileref eq 'SCALAR' ) { return $$fileref; }
return;
}
sub loadfile_to_fh {
my $fh = shift;
my $fileref = _load_file(shift);
if ( ref $fileref eq 'SCALAR' ) {
print {$fh} $$fileref;
}
}
sub slurpfile { goto &loadfile_to_fh; }
sub loadfile_r {
return _load_file(@_);
}
sub loadfile {
my $fileref = _load_file(@_);
if ( ref $fileref eq 'SCALAR' ) { return $$fileref; }
return;
}
sub _load_file {
my ( $file, $arg_ref ) = @_;
if ( !$arg_ref->{'skip_exists_check'} ) { return if !-f $file }
if ( open my $lf_fh, '<', $file ) {
if ( $arg_ref->{'binmode'} ) { binmode $lf_fh; }
my $data;
if ( $arg_ref->{'array_ref'} ) {
@{$data} = readline $lf_fh;
close $lf_fh;
return $data;
}
else {
local $/;
$data = readline $lf_fh;
close $lf_fh;
return \$data;
}
}
else {
return;
}
}
sub load_userfile_as_root {
my $file = shift;
my $max_length = shift;
sysopen( my $ff_fh, $file, &Fcntl::O_RDONLY | &Fcntl::O_NOFOLLOW );
my $data;
if ($max_length) {
read( $ff_fh, $data, 1024 );
}
else {
local $/;
$data = readline($ff_fh);
}
close($ff_fh);
return $data;
}
1;
} # --- END Cpanel/LoadFile.pm
{ # --- BEGIN Cpanel/OSSys/Bits.pm
package Cpanel::OSSys::Bits;
sub getbits {
return length( pack( 'l!', 1000 ) ) * 8;
}
1;
} # --- END Cpanel/OSSys/Bits.pm
{ # --- BEGIN Cpanel/Sync/v2.pm
package Cpanel::Sync::v2;
use strict;
use warnings;
use Cwd ();
# use Cpanel::SafeDir ();
# use Cpanel::Rand ();
# use Cpanel::FileUtils::TouchFile ();
# use Cpanel::FileUtils::Move ();
# use Cpanel::HttpRequest ();
# use Cpanel::Tar ();
# use Cpanel::Sync::Common ();
# use Cpanel::Update::Logger ();
use File::Basename ();
$Cpanel::Sync::v2::VERSION = 0.1;
sub new {
my $class = shift;
my $args = shift;
ref($args) eq 'HASH' or die("Cannot recognize arguments passed to script. $args is now a HASH");
$args = bless $args, $class;
foreach my $param (qw( syncto url source logger)) {
$args->{$param} or die( "Cannot create " . __PACKAGE__ . " without $param parameter" );
}
ref( $args->{'source'} ) eq 'ARRAY' or die( "Required array ref not passed to new as 'source' in " . __PACKAGE__ );
$args->{'http_client'} = Cpanel::HttpRequest->new(
'die_on_404' => 1,
'retry_dns' => 0,
'hideOutput' => $args->{'http_verbose'} ? 0 : 1,
);
$args->{'sync_basename'} = '.cpanelsync';
unlink "$args->{'syncto'}/$args->{'sync_basename'}.new";
if ( -e "$args->{'syncto'}/.cpanelsync.new" ) {
$args->{'logger'}->warning("Could not remove '$args->{'syncto'}/$args->{'sync_basename'}.new': $!");
}
return $args;
}
sub get_source_list { @{ $_[0]->{'source'} } }
sub get_fs_safe_source { my $copy = $_[1]; $copy =~ s{/+}{__forward_slash__}g; return $copy }
sub unbzip2 { Cpanel::Sync::Common::unbzip2( $Cpanel::Sync::Common::hasbzip2, $_[1] ); }
sub md5 {
my $self = shift;
my $path = shift;
$path =~ s/\Q$self->{'syncto'}\E\///;
Cpanel::Sync::Common::getmd5sum( $self->{'md5_lookup'}, $self->{'syncto'}, $path, $Cpanel::Sync::Common::hasmd5, @_ );
}
sub init_source_data {
my ( $self, $source_struct ) = @_;
ref($source_struct) eq 'HASH' or die;
$self->{'source_data'} = $source_struct;
( $self->{'source_data'}{'sync_file_data'}, $self->{'source_data'}{'sync_file_data_order'} ) = $self->get_hashref_of_syncfile( $self->{'source_data'}{'tmp_file'}, $self->{'syncto'} );
$self->{'source_data'}{'sync_file_data'} or die;
$self->{'found_in_any_source'}{$_} = 1 foreach @{ $self->{'source_data'}{'sync_file_data_order'} };
$self->{'found_in_any_source'}{ $self->{'syncto'} } = 1;
$self->{'source_data'}{'excludes'} = { map { ( $self->normalize_syncfile_path( $_, $self->{'syncto'} ) => undef ) } @{ $self->{'source_data'}{'file_excludes'} } };
$self->{'source_data'}{'chmod_excludes'} = { map { ( $self->normalize_syncfile_path( $_, $self->{'syncto'} ) => undef ) } @{ $self->{'source_data'}{'chmod_excludes'} } };
}
sub is_excluded {
my ( $self, $path ) = @_;
return $self->syncfile_path_is_excluded( $path, $self->{'syncto'}, $self->{'source_data'}{'excludes'} );
}
sub is_excluded_chmod {
my ( $self, $path ) = @_;
return $self->syncfile_path_is_excluded( $path, $self->{'syncto'}, $self->{'source_data'}{'chmod_excludes'} );
}
sub file_move_into_syncto_path {
my ( $self, $source, $target_in_syncto ) = @_;
my $target_dir = $target_in_syncto =~ m{(.*)/[^/]*$} ? $1 : $self->{'syncto'};
unless ( -d $target_dir || Cpanel::SafeDir::MK::safemkdir( $target_dir, '0755', 2 ) ) {
$self->{'logger'}->fatal("Could not make '$target_dir' directory: $!");
die("Cannot continue without dir $target_dir");
}
unless ( Cpanel::FileUtils::Move::safemv( '-f', $source, $target_in_syncto ) ) {
$self->{'logger'}->fatal("Could not move '$source' to ' $target_in_syncto' : $!");
die("Cannot continue without $target_in_syncto");
}
}
sub sync_new_and_modified_files {
my ($self) = @_;
my $path; # buffer
for $path ( @{ $self->{'source_data'}{'sync_file_data_order'} } ) {
next if $self->is_excluded($path); # already normalized, no need for $base_path
$self->handle_entry($path);
}
}
sub handle_entry {
my ( $self, $entry ) = @_;
my $target_info = $self->get_target_info($entry);
if ( $self->{'source_data'}{'sync_file_data'}{$entry}{'type'} eq 'f' ) {
$self->handle_file( $entry, $target_info );
}
elsif ( $self->{'source_data'}{'sync_file_data'}{$entry}{'type'} eq 'd' ) {
$self->handle_directory( $entry, $target_info );
}
elsif ( $self->{'source_data'}{'sync_file_data'}{$entry}{'type'} eq 'l' ) {
$self->handle_symlink( $entry, $target_info );
}
}
sub handle_file {
my ( $self, $path, $target_info ) = @_;
if ( $target_info->{'isdir'} ) {
Cpanel::SafeDir::safermdir($path);
}
elsif ( $target_info->{'islnk'} ) {
unlink $path;
}
if ( ( $target_info->{'isdir'} || $target_info->{'islnk'} || !$target_info->{'exists'} )
|| $self->md5($path) ne $self->{'source_data'}{'sync_file_data'}{$path}{'extra'} ) {
my $working_file = $path . '-cpanelsync';
my $full_url = join(
'/',
$self->{'url'},
map {
my $copy = $_;
$copy =~ s/^\.\///;
$copy =~ s/^\/+//;
$copy =~ s/\/+$//;
$copy
} ( $self->{'source_data'}{'current_source'}, $self->{'source_data'}{'sync_file_data'}{$path}{'file'} )
);
$self->download_file_into( $full_url, $working_file );
my $check_md5sum = $self->md5( $working_file, 'skipcache' => 1 );
if ( $check_md5sum eq $self->{'source_data'}{'sync_file_data'}{$path}{'extra'} ) {
$self->{'logger'}->debug("Got file $path ok (md5 matches)");
if ( $self->is_excluded_chmod($path) ) {
my $original_mode = sprintf( '%04o', ( $target_info->{'perm'} & 07777 ) );
$self->{'logger'}->debug("$path is excluded from chmod so the local mode $original_mode preserved.");
chmod( oct($original_mode), $working_file );
}
else {
$self->{'logger'}->debug("$path set to remote mode $self->{'source_data'}{'sync_file_data'}{$path}{'perm'}");
chmod( oct( $self->{'source_data'}{'sync_file_data'}{$path}{'perm'} ), $working_file );
}
unlink $path;
if ( -e $path ) {
if ( rename( $path, $path . '.unlink' ) ) {
unlink $path . '.unlink';
}
else {
unlink $path;
}
}
if ( !rename( $working_file, $path ) ) {
my $message = $!;
if ( -e $path && !-w $path && $> == 0 ) {
$message .= ' (Has the file been made immutable with chattr +i?)';
}
unlink $working_file;
die "Could not put new '$path' into place: $message";
}
push @{ $self->{'new_files'} }, $path;
}
else {
unlink $working_file;
my $size = ( stat( $path . '-cpanelsync' ) )[7] || 0;
$self->{'logger'}->error("md5sum mismatch (actual: $check_md5sum) (expected: $self->{'source_data'}{'sync_file_data'}{$path}{'extra'}) (size: $size)");
}
}
else {
if ( !$self->is_excluded_chmod($path)
&& $target_info->{'exists'}
&& sprintf( '%04o', ( $target_info->{'perm'} & 07777 ) ) ne $self->{'source_data'}{'sync_file_data'}{$path}{'perm'} ) {
chmod( oct( $self->{'source_data'}{'sync_file_data'}{$path}{'perm'} ), $target_info->{'path'} );
}
}
}
sub handle_symlink {
my ( $self, $path, $target_info ) = @_;
my $dolink = 0;
if ( !$target_info->{'exists'} ) {
$dolink = 1;
}
elsif ( $target_info->{'islnk'} ) {
if ( readlink $target_info->{'path'} ne $self->{'source_data'}{'sync_file_data'}{$path}{'extra'} ) {
unlink $target_info->{'path'};
$dolink = 1;
}
}
elsif ( $target_info->{'isnormfile'} ) {
unlink $target_info->{'path'};
$dolink = 1;
}
elsif ( $target_info->{'isdir'} ) {
Cpanel::SafeDir::safermdir($path);
$dolink = 1;
}
if ($dolink) {
if ( symlink( $self->{'source_data'}{'sync_file_data'}{$path}{'extra'}, $target_info->{'path'} ) ) {
$self->{'logger'}->info("Created symlink $target_info->{'path'} -> $self->{'source_data'}{'sync_file_data'}{$path}{'extra'} successfully");
}
else {
$self->{'logger'}->error("Failed to create symlink $target_info->{'path'} -> $self->{'source_data'}{'sync_file_data'}{$path}{'extra'}: $!");
}
}
}
sub handle_directory {
my ( $self, $path, $target_info ) = @_;
if ( $target_info->{'islnk'} || $target_info->{'isnormfile'} ) {
unlink $target_info->{'path'};
$target_info->{'exists'} = 0;
}
if ( !$target_info->{'exists'} ) {
if ( Cpanel::SafeDir::MK::safemkdir( $target_info->{'path'}, $self->{'source_data'}{'sync_file_data'}{$path}{'perm'}, 2 ) ) {
$self->{'logger'}->info("Created directory $target_info->{'path'} successfully");
}
}
elsif ( $self->is_excluded_chmod($path)
&& sprintf( "%04o", ( $target_info->{'perm'} & 07777 ) ) ne $self->{'source_data'}{'sync_file_data'}{$path}{'perm'} ) {
if ( chmod( oct( $self->{'source_data'}{'sync_file_data'}{$path}{'perm'} ), $target_info->{'path'} ) ) {
$self->{'logger'}->info("Directory $target_info->{'path'} verified");
}
else {
$self->{'logger'}->error("Failed to update permissions on directory $target_info->{'path'}: $!");
}
}
}
sub get_target_info {
my ( $self, $normalized_path ) = @_;
my %target = ( 'path' => $normalized_path );
my @_lstat = lstat( $target{'path'} );
@target{ 'perm', 'size', 'mtime' } = @_lstat[ 2, 7, 9 ];
$target{'isdir'} = -d _;
$target{'exists'} = -e _;
$target{'isnormfile'} = -f _;
$target{'islnk'} = 0;
if ( -l _ ) {
$target{'islnk'} = 1;
$target{'isdir'} = 0;
$target{'isnormfile'} = 0;
}
return \%target;
}
sub create_dot_new_file {
my ($self) = @_;
my $new_file = "$self->{'syncto'}/.cpanelsync.new";
unless ( exists $self->{'new_files'} && ref( $self->{'new_files'} ) eq 'ARRAY' ) {
$self->{'logger'}->debug("No new files to manifest");
return 1;
}
my $new_fh;
unless ( open( $new_fh, '>', $new_file ) ) {
$self->{'logger'}->fatal("Could not open '$new_file' for writing: $!");
die("Cannot continue without $new_file");
}
for ( sort @{ $self->{'new_files'} } ) {
print {$new_fh} "$_\n";
}
close($new_fh);
}
sub init_current_md5_cache {
my ($self) = @_;
$self->{'md5_lookup'} = {};
my $md5s_file = "$self->{'syncto'}/.cpanelsync.md5s";
return if ( !-e $md5s_file ); # No MD5 is fine.
my $md5_fh;
unless ( open( $md5_fh, '<', $md5s_file ) ) {
$self->{'logger'}->fatal("Could not open '$md5s_file' for reading: $!");
die("Cannot continue without reading $md5s_file");
}
while (<$md5_fh>) {
chomp();
my ( $filename, $size, $mtime, $md5 ) = split( /:::/, $_, 4 );
$self->{'md5_lookup'}{ $self->normalize_syncfile_path( $filename, $self->{'syncto'} ) } = {
'file' => $filename,
'size' => $size,
'mtime' => $mtime,
'md5' => $md5,
};
}
close($md5_fh);
}
sub save_updated_md5_data {
my ($self) = @_;
my $md5s_file = "$self->{'syncto'}/.cpanelsync.md5s";
my $md5_fh;
unless ( open( $md5_fh, '>', $md5s_file ) ) {
$self->{'logger'}->fatal("Could not open '$md5s_file' for writing: $!");
die("Cannot continue without writing $md5s_file");
}
foreach my $normalized_path ( sort keys %{ $self->{'md5_lookup'} } ) {
my $file = exists $self->{'md5_lookup'}{$normalized_path}{'file'} ? $self->{'md5_lookup'}{$normalized_path}{'file'} : $normalized_path;
next if ( !$self->{'md5_lookup'}{$normalized_path}{'used'} || substr( $file, 0, 1 ) eq '/' );
print {$md5_fh} join(
':::',
$file,
$self->{'md5_lookup'}{$normalized_path}{'size'},
$self->{'md5_lookup'}{$normalized_path}{'mtime'},
$self->{'md5_lookup'}{$normalized_path}{'md5'},
) . "\n";
}
close($md5_fh);
}
sub get_hashref_of_syncfile {
my ( $self, $syncfile, $base_path ) = @_;
my %sync;
my @order;
my $wa = wantarray();
open( my $sync_fh, '<', $syncfile ) or die("Cannot open $syncfile for read");
my $line; # buffer
while ( $line = <$sync_fh> ) {
chomp($line);
my ( $rtype, $rfile, $rperm, $rextra ) = split( /===/, $line );
next if $rtype =~ m/^\s*\.\s*$/ || !$rtype;
$rperm = sprintf( '%04d', $rperm );
my $key = $self->normalize_syncfile_path( $rfile, $base_path );
$sync{$key} = {
'file' => $rfile,
'type' => $rtype,
'perm' => $rperm,
'extra' => $rextra,
};
if ($wa) {
push @order, $key;
}
}
close $sync_fh;
if ( !%sync ) {
$self->{'logger'}->fatal("$syncfile unexpectedly had no data.");
die("Cannot continue without valid data from $syncfile");
}
return $wa ? ( \%sync, \@order ) : \%sync;
}
sub normalize_syncfile_path {
my ( $self, $path_from_syncfile, $base_path ) = @_;
$path_from_syncfile =~ s{/+$}{};
if ($base_path) {
if ( $path_from_syncfile =~ m/^\./ ) {
$base_path = Cwd::abs_path($base_path);
$path_from_syncfile =~ s/^\./$base_path/eo;
}
}
else {
if ( $path_from_syncfile =~ m/^\./ ) {
$path_from_syncfile =~ s/^\./Cwd::cwd()/eo;
}
}
return $path_from_syncfile;
}
sub syncfile_path_is_excluded {
my ( $self, $path, $base_path, $normalized_exclude_lookup_hr ) = @_;
my $check = $self->normalize_syncfile_path( $path, $base_path );
return if exists $normalized_exclude_lookup_hr->{$check};
while (1) {
my $copy = $check;
$check =~ s{/[^/]+$}{};
last if !$check || $check eq '/' || $copy eq $check;
return if ( exists $normalized_exclude_lookup_hr->{$check} );
}
}
sub seek_removed_files {
my ( $self, $cur_files_file ) = @_;
return if !keys %{ $self->{'source_data'}{'sync_file_data'} };
return if ( !-e $cur_files_file );
my $fh;
if ( !open( $fh, '<', $cur_files_file ) ) {
$self->{'logger'}->fatal("Could not open '$cur_files_file' for reading: $!");
die("Cannot proceed.");
}
my $line;
while ( $line = <$fh> ) {
my ( $type, $path ) = split( /===/, $line, 3 ); # We only want the first 2 fields without including the fields after 2 as part of 2.
next unless ( $type && $path ); # Skip lines which aren't populated.
$path = $self->normalize_syncfile_path( $path, $self->{'syncto'} );
next if $self->is_excluded($path);
next if ( $self->{'found_in_any_source'}{$path} );
if ( $type eq 'd' ) {
$self->{'existing_dirs'}{$path} = undef;
}
else {
$self->{'existing_files_and_links'}{$path} = undef;
}
}
}
sub sync_removed_files {
my ($self) = @_;
foreach my $path ( keys %{ $self->{'existing_files_and_links'} } ) {
next if ( $self->{'found_in_any_source'}{$path} );
$self->{'logger'}->info("Removing file/link $path");
unlink $path;
if ( -f $path || -l $path ) {
$self->{'logger'}->warning("Could not unlink '$path': $!");
}
}
my @dirs = grep { !$self->{'found_in_any_source'}{$_} }
map { $_->[0] }
sort { $b->[1] <=> $a->[1] }
map { [ $_, length $_ ] }
keys %{ $self->{'existing_dirs'} };
foreach my $path (@dirs) {
next if ( $self->{'found_in_any_source'}{$path} );
$self->{'logger'}->info("Removing dir $path");
rmdir($path);
if ( -d $path ) {
if ( ( stat($path) )[3] > 2 ) { # ? or if (3rd readdir()) { ?
$self->{'logger'}->warning("'$path' is not empty, not removing.");
}
else {
$self->{'logger'}->warning("Could not remove directory '$path': $!");
}
}
}
}
sub download_file_into {
my ( $self, $full_url, $download_into, $protocol ) = @_;
( defined $download_into and length $download_into ) or die("I can't download a file without knowing where to put it!");
defined $protocol or $protocol = 1;
$full_url =~ m{https?://([^/]+)(.*)};
my $host = $1;
my $url = $2;
my $uncompress = ( $url !~ m/\.(bz2|gz)$/ );
my $dest_file = $download_into . ( $uncompress ? '.bz2' : '' );
my $download_dir = File::Basename::dirname($download_into);
if ( !-d $download_dir ) {
$self->{'logger'}->info("Creating $download_dir");
Cpanel::SafeDir::MK::safemkdir( $download_dir, '0755', 2 );
}
$self->{'logger'}->info("Retrieving $full_url");
local $| = 1;
my @return = $self->{'http_client'}->request(
'host' => $host,
'url' => $url . ( $uncompress ? '.bz2' : '' ), # Tack on bz2 if we're going to need to uncompress.
'protocol' => $protocol,
'destfile' => $dest_file,
);
-e "$dest_file" or die("Failed to download $dest_file");
$return[-1] or die( "Error downloading $url:\n" . join( "\n", @return ) );
if ($uncompress) {
$self->unbzip2($dest_file);
-e $download_into or die("Failed to uncompress $dest_file");
}
return wantarray ? ( @return > 1 ? ( $return[1], $return[0] ) : ( $return[-1] ) ) : $return[-1]; # -1 is always the status
}
sub script {
my $self = shift;
$self->{'logger'}->debug( 'Starting at ' . time() );
$self->{'logger'}->debug('Initializing md5 cache');
$self->init_current_md5_cache();
for my $source ( $self->get_source_list() ) {
my $sync_base_name_safe_source = $self->get_fs_safe_source($source);
$self->{'logger'}->info("Starting sync of $source ...");
my $local_cpanelsync_file = "$self->{'syncto'}/$self->{'sync_basename'}_$sync_base_name_safe_source";
my $old_log_level;
if ( !-e $local_cpanelsync_file ) {
$self->{'logger'}->debug("Creating blank sync file to force a fresh download.");
$old_log_level = $self->{'logger'}->get_logging_level;
Cpanel::SafeDir::MK::safemkdir( File::Basename::dirname($local_cpanelsync_file), '0755', 2 );
open( my $fh, '>', $local_cpanelsync_file ) or die("Cannot open $local_cpanelsync_file for write");
print {$fh} ".\n";
close $fh;
}
$self->{'logger'}->debug("1. grab $self->{'url'}/$source/$self->{'sync_basename'} into tmp file");
my $tmp_file = $local_cpanelsync_file . '.tmp';
$self->download_file_into( "$self->{'url'}/$source/$self->{'sync_basename'}", $tmp_file );
$self->{'logger'}->debug("2. create data structures");
$self->init_source_data(
{
'current_source' => $source,
'tmp_file' => $tmp_file,
'source_cpanelsync_file' => $local_cpanelsync_file,
'file_excludes' => [ Cpanel::Sync::Common::get_excludes($Cpanel::Sync::Common::cpanelsync_excludes) ],
'chmod_excludes' => [ Cpanel::Sync::Common::get_excludes($Cpanel::Sync::Common::cpanelsync_chmod_excludes) ],
}
);
$self->{'logger'}->debug("3. looking for files that are in $self->{'source_data'}{'source_cpanelsync_file'} but not in the updated temp file");
$self->seek_removed_files( $self->{'source_data'}{'source_cpanelsync_file'} );
$self->{'logger'}->debug('4. put new data in place');
$self->file_move_into_syncto_path( $tmp_file, $self->{'source_data'}{'source_cpanelsync_file'} );
$self->{'logger'}->debug('5. sync all files in the new local files');
if ( defined $old_log_level ) { # This is a full sync. Suppress output by raising logging level to warnings.
$self->{'logger'}->warning("Assuming this is a new install because I could not find $local_cpanelsync_file. This may take several minutes.");
$self->{'logger'}->warning("Downloaded files will not be displayed to prevent console flood.");
$self->{'logger'}->set_logging_level('warn');
}
$self->sync_new_and_modified_files();
$self->{'logger'}->set_logging_level($old_log_level) if ( defined $old_log_level );
$self->{'logger'}->debug("... $source is complete.");
}
$self->{'logger'}->debug('Remove pre-existing files that are not found in any data source');
$self->sync_removed_files();
$self->{'logger'}->debug('Saving updated md5 cache');
$self->save_updated_md5_data();
$self->{'logger'}->debug('Making .cpanelsync.new file list');
$self->create_dot_new_file();
$self->{'logger'}->debug( "Ending at " . time() );
}
sub sync_updatenow_static {
my $self = shift;
my $target = '/usr/local/cpanel/scripts/updatenow.static';
$self->download_file_into( "$self->{'url'}/cpanel/scripts/updatenow.static", $target );
-e $target or die("Failed to download updatenow.static from server");
chmod 0700, $target;
return $target;
}
1;
} # --- END Cpanel/Sync/v2.pm
{ # --- BEGIN Cpanel/SafeDir.pm
package Cpanel::SafeDir;
use Cwd ();
# use Cpanel::SafeDir::MK ();
# use Cpanel::SafeDir::Read ();
# use Cpanel::Logger ();
# use Cpanel::LoadModule ();
my %SAFEDIRCACHE;
our $VERSION = 1.0;
sub safedir {
my $odir = shift || '';
my $homedir = shift;
my $abshomedir = shift;
if ( !$homedir ) { $homedir = $Cpanel::homedir; }
if ( !$abshomedir ) { $abshomedir = $Cpanel::abshomedir; }
my $dir = $odir;
if ( $SAFEDIRCACHE{$odir} ne '' ) { return $SAFEDIRCACHE{$odir}; }
if ( $dir eq $homedir || $dir eq $abshomedir ) { return $abshomedir; }
$dir =~ s/[\r\n]//g;
$dir = homedirfixup( $dir, $homedir, $abshomedir );
my $testdir = safe_abs_path($dir);
return $dir
if !$testdir; #if the dir doesn't exist we just want to fix the prefix
$dir = homedirfixup($testdir);
$SAFEDIRCACHE{$odir} = $dir;
return $dir;
}
sub maildirfixup {
my $dir = shift;
my $homedir = shift;
my $abshomedir = shift;
my $acct = shift;
my $basedir = 'mail';
if ( defined $Cpanel::appname && $Cpanel::appname eq 'webmail' && $Cpanel::authuser =~ /\@/ ) {
$acct = $Cpanel::authuser;
}
if ( $acct && $acct =~ /\@/ ) {
my ( $user, $domain ) = split( /\@/, $acct );
$user =~ s/\///g;
$domain =~ s/\///g;
$domain =~ tr/\.//s;
$basedir = 'mail/' . $domain . '/' . $user;
}
if ( !$homedir ) { $homedir = $Cpanel::homedir; }
if ( !$abshomedir ) { $abshomedir = $Cpanel::abshomedir; }
$dir =~ s/^$basedir//;
my $did_strip_abshomedir = ( $dir =~ s/^$abshomedir(\/$basedir)?// );
if ( !$did_strip_abshomedir ) {
$dir =~ s/^$homedir(\/$basedir)?//;
}
$dir =~ s/\.\.//g;
$dir = "$abshomedir/$basedir/$dir";
$dir =~ s{//+}{/}g;
$dir =~ s/\/$//g;
return $dir;
}
sub publichtmldirfixup {
my $dir = shift;
my $homedir = shift;
my $abshomedir = shift;
if ( !$homedir ) { $homedir = $Cpanel::homedir; }
if ( !$abshomedir ) { $abshomedir = $Cpanel::abshomedir; }
$dir =~ s/\n//g;
$dir =~ s/^public_html//;
my $did_strip_abshomedir = ( $dir =~ s/^$abshomedir(\/public_html)?// );
if ( !$did_strip_abshomedir ) {
$dir =~ s/^$homedir(\/public_html)?//;
}
$dir =~ s/\.\.//g;
$dir = "$abshomedir/public_html/$dir";
$dir =~ s{//+}{/}g;
$dir =~ s/\/$//g;
return $dir;
}
sub homedirfixup {
my $dir = shift;
my $homedir = shift;
my $abshomedir = shift;
if ( !$homedir ) { $homedir = $Cpanel::homedir; }
if ( !$abshomedir ) { $abshomedir = $Cpanel::abshomedir; }
$dir =~ s/\n//g;
my $did_strip_abshomedir = ( $dir =~ s/^$abshomedir// );
if ( !$did_strip_abshomedir ) {
$dir =~ s/^$homedir//;
}
$dir =~ s/\.\.//g;
$dir = "$abshomedir/$dir";
$dir =~ s{//+}{/}g;
$dir =~ s/\/$//g;
return $dir;
}
sub safe_abs_path {
my $path = shift;
my $tainted_path = Cwd::abs_path($path);
($tainted_path) = $tainted_path =~ m{^(.*)$};
return $tainted_path; # the regex-capture-as-last-item would return it but lets be explicit for sanity's sake
}
sub safemkdir {
goto &Cpanel::SafeDir::MK::safemkdir;
}
sub safermdir {
Cpanel::LoadModule::lazy_load_module('File::Copy::Recursive');
if ( !exists $INC{'File/Copy/Recursive.pm'} ) {
return if !-d $_[0];
system( 'rm', '-rf', $_[0] );
return 1 if !-d $_[0];
return;
}
else {
goto &File::Copy::Recursive::pathrmdir;
}
}
sub read_dir {
goto &Cpanel::SafeDir::Read::read_dir;
}
1;
} # --- END Cpanel/SafeDir.pm
{ # --- BEGIN Cpanel/SafeDir/MK.pm
package Cpanel::SafeDir::MK;
# use Cpanel::Logger ();
sub safemkdir {
my $dir = shift;
my $mode = shift;
my $errors = shift;
if ( defined $mode && $mode ne '' ) {
if ( $mode !~ /^[0-7]{3,4}$/ ) {
$mode = '0755';
}
elsif ( $mode =~ /^[0-7]{3}$/ ) {
$mode = '0' . $mode;
}
}
my $default = '';
if ( $dir =~ m/^\// ) {
$default = '/';
}
elsif ( $dir eq '.' || $dir eq './' ) {
if ( !-l $dir && $mode ) {
return chmod oct($mode), $dir;
}
return 1;
}
else {
$dir =~ s/^\.\///;
}
if ( $dir =~ m/(?:^|\/)\.\.(?:\/|$)/ || $dir =~ m/\/{2}/ ) {
Cpanel::Logger::logger(
{
'message' => "Possible improper directory $dir specified",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors,
'backtrace' => 0,
}
);
my @dir_parts = split /\//, $dir;
my @good_parts;
my $first;
foreach my $part (@dir_parts) {
next if ( !defined $part || $part eq '' );
next if $part eq '.';
if ( $part eq '..' ) {
if ( !$first || !@good_parts ) {
Cpanel::Logger::logger(
{
'message' => "Will not proceed above first directory part $first",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors,
'backtrace' => 0,
}
);
return 0;
}
if ( $first eq $good_parts[$#good_parts] ) {
undef $first;
}
pop @good_parts;
next;
}
elsif ( $part =~ m/^\.+$/ ) {
Cpanel::Logger::logger(
{
'message' => "Total stupidity found in directory $dir",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors,
'backtrace' => 0,
}
);
return 0;
}
push @good_parts, $part;
if ( !$first ) { $first = $part }
}
$dir = $default . join '/', @good_parts;
if ( !$dir ) {
Cpanel::Logger::logger(
{
'message' => "Could not validate given directory",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors,
'backtrace' => 0,
}
);
return;
}
Cpanel::Logger::logger(
{
'message' => "Improper directory updated to $dir",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors
}
);
}
if ( -d $dir ) {
if ( !-l $dir && $mode ) {
return chmod oct($mode), $dir;
}
return 1;
}
elsif ( -e _ ) {
Cpanel::Logger::logger(
{
'message' => "$dir was expected to be a directory!",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors
}
);
return 0;
}
my @dir_parts = split /\//, $dir;
if ( scalar @dir_parts > 100 ) {
Cpanel::Logger::logger(
{
'message' => "Encountered excessive directory length. This should never happen.",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors
}
);
return 0;
}
my $returnvalue;
foreach my $i ( 0 .. $#dir_parts ) {
my $newdir = join( '/', @dir_parts[ 0 .. $i ] );
next if $newdir eq '';
my $is_dir = -d $newdir;
my $exists = -e _;
if ( !$exists ) {
my $local_mode = $mode ? $mode : '0755';
if ( mkdir( $newdir, oct($local_mode) ) ) {
$returnvalue++;
}
else {
Cpanel::Logger::logger(
{
'message' => "mkdir $newdir failed: $!",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors
}
);
return;
}
}
elsif ( !$is_dir ) {
Cpanel::Logger::logger(
{
'message' => "Encountered non-directory $newdir in path of $dir: $!",
'level' => 'warn',
'service' => __PACKAGE__,
'output' => $errors
}
);
last;
}
}
return $returnvalue;
}
sub safemkdir_as_user {
my ( $user, $path, $mode ) = @_;
if ( $> == 0 ) {
require Cpanel::AccessIds;
return Cpanel::AccessIds::do_as_user(
$user,
sub {
$path =~ /^(.*)$/;
$path = $1;
return Cpanel::SafeDir::MK::safemkdir( $path, $mode );
}
);
}
else {
return Cpanel::SafeDir::MK::safemkdir( $path, $mode );
}
}
1;
} # --- END Cpanel/SafeDir/MK.pm
{ # --- BEGIN Cpanel/AccessIds.pm
package Cpanel::AccessIds;
use strict;
# use Cpanel::PwCache ();
# use Cpanel::Logger ();
# use Cpanel::AccessIds::SetUids ();
our $VERSION = '1.1';
my $logger;
{
no warnings 'once';
*setuid = \&Cpanel::AccessIds::SetUids::setuids;
*setuids = \&Cpanel::AccessIds::SetUids::setuids;
*run_as_user_group = \&runasusergroup;
*run_as_user = \&runasuser;
}
sub runasuser {
my $user = shift;
my $gid = ( Cpanel::PwCache::getpwnam($user) )[3]; # gets passed to setuids() which works w/ numeric id or name
my @CMDS = @_;
return runasusergroup( $user, $gid, @CMDS );
}
sub runasusergroup {
my ( $user, $group, @CMDS ) = @_;
my $homedir = '';
if ( $user !~ m/^\d+$/ ) {
$homedir = ( Cpanel::PwCache::getpwnam($user) )[7];
}
else {
$homedir = ( Cpanel::PwCache::getpwuid($user) )[7];
}
if ( my $pid = fork() ) {
waitpid( $pid, 0 );
}
else {
Cpanel::AccessIds::SetUids::setuids( $user, $group );
$ENV{'HOME'} = $homedir;
exec @CMDS;
exit 1;
}
return;
}
sub do_as_user {
my $user = shift;
my $code = shift;
my $gid = ( Cpanel::PwCache::getpwnam($user) )[3];
return do_as_user_group( $user, $gid, $code, @_ );
}
sub do_as_user_group {
my $user = shift;
return if !$user;
my $group = shift;
return if !$group;
my $code = shift;
if ( !$code || ref $code ne 'CODE' ) {
$logger ||= Cpanel::Logger->new();
$logger->warn("Failed to provide CODE");
return;
}
require Storable;
my ( $PR, $CW, $CR, $PW, $RC );
pipe( $PR, $CW );
pipe( $CR, $PW );
if ( defined &IO::Handle::autoflush ) {
$CW->autoflush();
$PW->autoflush();
}
if ( my $pid = fork() ) {
$RC = Storable::fd_retrieve($CR);
close $CR;
close $CW;
waitpid( $pid, 0 );
}
else {
Cpanel::AccessIds::SetUids::setuids( $user, $group );
Storable::nstore_fd( [ $code->(@_) ], $PW );
close $PR;
close $PW;
exit 0;
}
return @{$RC};
}
sub loadfile_as_user {
my $user = shift;
my $file = shift;
my ( $uid, $gid ) = ( Cpanel::PwCache::getpwnam($user) )[ 2, 3 ];
if ( !defined $uid || !defined $gid ) {
$logger ||= Cpanel::Logger->new();
$logger->warn('Attempting to use non-existent user.');
return;
}
my $data;
if ( $< != 0 && $uid != $> ) {
$logger ||= Cpanel::Logger->new();
$logger->panic("Attempting to setuid as a normal user $>");
}
my $orig_euid = $>;
my $orig_egid = $);
my $change_effectives = $< == 0 && $uid != $> ? 1 : 0;
if ($change_effectives) {
$) = "$gid $gid"; # set EGID before EUID,
$> = $uid;
}
if ( open my $rr_fh, '<', $file ) {
local $/;
$data = readline($rr_fh);
close $rr_fh;
}
else {
$logger ||= Cpanel::Logger->new();
$logger->warn("Could not open file '$file' for reading: $!");
}
if ($change_effectives) {
$> = $orig_euid; # reset EUID before EGID
local $! = 0;
$) = $orig_egid
; # on some BSD's (others?) setgroups() behaves oddly (see case 33632). The result is you might lose one group. There is really no way to get it set to what it was originally, even some/most/all Linux distros do a reverse sort on the values so the order will change. These are limitations we can live with (vs. having all sorts of logic to try and address each possible nuance without gaurantee that it will have any effect) in this context since only root can get to this point and root can already read anything
if ($!) {
$logger ||= Cpanel::Logger->new();
$logger->warn("Resetting the EGID ($)) to '$orig_egid' had some errors: $!");
}
}
return $data;
}
1;
} # --- END Cpanel/AccessIds.pm
{ # --- BEGIN Cpanel/PwCache.pm
package Cpanel::PwCache;
use Carp ();
# use Cpanel::SafeFile ();
# use Cpanel::Debug ();
use IO::Handle ();
$Cpanel::PwCache::VERSION = '3.1';
my ( $system, $isopen, $pwcacheistied, $pwcache_inited, $MIN_FIELDS_FOR_VALID_ENTRY, $pwcache_has_uid_cache, $pwcache_ref, $getpwent_open_time, $pwdatafuncuid, $pwdatafunc, $pwcachetie, $skip_uid_cache, $getpwent_passwd_fh, @SHAREDPW ) = ( $^O, 0, 0, 0, 6 );
my %HOMEDIR_CACHE;
our ( $CASE_SENSITIVE, $CASE_INSENSITIVE ) = ( 0, 1 );
sub istied { $pwcacheistied }
sub deinit { $pwcacheistied = 0; }
sub no_uid_cache { $skip_uid_cache = 1; }
sub uid_cache { $skip_uid_cache = 0; }
sub pwcheckos { return $system; }
*_getos = *pwcheckos;
sub init { ( $pwcachetie, $skip_uid_cache, $pwcacheistied ) = ( $_[0], $_[1], 1 ); }
sub validate {
my ( $pwkey, $record ) = @_;
if ( exists $record->{'contents'} && ref $record->{'contents'} ne 'ARRAY' && scalar @{ $record->{'contents'} } ) { return 0; }
my ( $stored_mtime, $hstored_mtime ) = ( $record->{'cachetime'}, $record->{'hcachetime'} );
my ( $passwdmtime, $hpasswdmtime ) = (
( stat('/etc/passwd') )[9], ( $system eq 'freebsd' && -r '/etc/master.passwd' )
? ( stat(_) )[9]
: ( -r '/etc/shadow' ? ( stat(_) )[9] : 0 )
);
if ( $Cpanel::Debug::level > 3 ) {
print STDERR __PACKAGE__ . "::validate called for record " . dump_rec($record) . "\n";
}
if ( $hstored_mtime
&& $stored_mtime
&& $hpasswdmtime == $hstored_mtime
&& $passwdmtime == $stored_mtime ) {
if ( !$pwcache_ref->{$pwkey}->{'cachetime'}
|| $pwcache_ref->{$pwkey}->{'cachetime'} != $stored_mtime
|| $pwcache_ref->{$pwkey}->{'hcachetime'} != $hstored_mtime ) {
@{ $pwcache_ref->{$pwkey} }{ 'hcachetime', 'cachetime', 'contents' } = ( $hstored_mtime, $stored_mtime, $record->{'contents'} );
}
if ( $Cpanel::Debug::level > 3 ) {
print STDERR __PACKAGE__ . "::validate returned 1 " . dump_rec($record) . "\n";
}
return 1;
}
if ( $Cpanel::Debug::level > 3 ) {
print STDERR __PACKAGE__ . "::validate returned 0 " . dump_rec($record) . "\n";
print STDERR __PACKAGE__ . "::validate returned 0 [ hstored_mtime = $hstored_mtime, hpasswdmtime = $hpasswdmtime ] [ stored_mtime = $stored_mtime, passwdmtime = $stored_mtime ]\n";
}
return 0;
}
sub load {
my $pwkey = shift;
my ( $field, $value ) = split( /:/, $pwkey );
my ( $passwdmtime, $hpasswdmtime ) = (
( stat('/etc/passwd') )[9], ( $system eq 'freebsd' && -r '/etc/master.passwd' )
? ( stat(_) )[9]
: ( -r '/etc/shadow' ? ( stat(_) )[9] : 0 )
);
if ( $Cpanel::Debug::level > 3 ) {
print STDERR __PACKAGE__ . "::load called for pwkey $pwkey\n";
}
my $pwdata = _getpwdata( $value, $field, $passwdmtime, $hpasswdmtime );
@{ $pwcache_ref->{$pwkey} }{ 'hcachetime', 'cachetime', 'contents' } = ( $hpasswdmtime, $passwdmtime, $pwdata );
return $pwcache_ref->{$pwkey};
}
sub pwmksafecache {
foreach my $pwkey ( keys %{$pwcache_ref} ) {
${ $pwcache_ref->{$pwkey}->{'contents'} }[1] = 'x';
}
}
sub getpwent {
my ( $passwdmtime, $shadowmtime, $cryptpw ) = @_;
if ( !$isopen ) {
if ($getpwent_passwd_fh) { close($getpwent_passwd_fh); }
$getpwent_passwd_fh = IO::Handle->new();
open( $getpwent_passwd_fh, '<', '/etc/passwd' );
if ( !$getpwent_passwd_fh ) {
Carp::cluck "Unable to open /etc/passwd: $!";
return;
}
$getpwent_open_time = ( stat('/etc/passwd') )[9];
$shadowmtime = $passwdmtime = $getpwent_open_time;
$isopen = 1;
}
elsif ( !$passwdmtime || !$shadowmtime ) {
$shadowmtime = $passwdmtime = ( $getpwent_open_time ? $getpwent_open_time : ( stat('/etc/passwd') )[9] );
}
while ( my $passwd_line = readline($getpwent_passwd_fh) ) {
chomp $passwd_line;
@SHAREDPW = ( split( /:/, $passwd_line ), undef );
if ( scalar @SHAREDPW > 7 && $SHAREDPW[0] =~ m/^[A-Za-z0-9_]/ ) { # Check for valid lines and skip commented/invalid lines
return wantarray ? ( $SHAREDPW[0], ( $cryptpw ? $cryptpw : $SHAREDPW[1] ), $SHAREDPW[2], $SHAREDPW[3], '', '', $SHAREDPW[4], $SHAREDPW[5], $SHAREDPW[6], -1, -1, $passwdmtime, $shadowmtime ) : $SHAREDPW[0];
}
}
return;
}
sub getpwuid {
my $pwref = Cpanel::PwCache::_pwfunc( $_[0], 2 );
if ($pwref) {
return wantarray ? @$pwref : $pwref->[0];
}
return; #important not to return 0
}
sub getpwnam {
my $pwref = Cpanel::PwCache::_pwfunc( $_[0], 0 );
if ($pwref) {
return wantarray ? @$pwref : $pwref->[2];
}
return; #important not to return 0
}
sub pwclearcache {
$pwcache_inited = undef;
$pwcache_has_uid_cache = undef;
$pwcache_ref = {};
%HOMEDIR_CACHE = ();
}
sub setpwent {
if ($getpwent_passwd_fh) { seek( $getpwent_passwd_fh, 0, 0 ); }
if ( $getpwent_passwd_fh && eof($getpwent_passwd_fh) ) {
$isopen = 0;
if ($getpwent_passwd_fh) { close($getpwent_passwd_fh); }
}
}
sub endpwent {
if ($getpwent_passwd_fh) { close($getpwent_passwd_fh); }
$isopen = 0;
}
sub _readshadow {
my ( $value, $field, $passwdmtime, $shadowmtime ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat('/etc/passwd') )[9] ), ( $_[3] || ( stat('/etc/shadow') )[9] ) );
my @PW = Cpanel::PwCache::_readpasswd( $value, $field, $passwdmtime, $shadowmtime );
return if !@PW;
my $match = quotemeta( $PW[0] ) . ':';
if ( open my $shadow_fh, '<', '/etc/shadow' ) {
while ( my $line = <$shadow_fh> ) {
if ( $line =~ m/^$match/ ) {
chomp $line;
my @SH = split( /:/, $line );
if ( @SH && defined( $SH[0] ) ) {
( $PW[1], $PW[9], $PW[10], $PW[11], $PW[12] ) = (
$SH[1], #encrypted pass
$SH[5], #expire time
$SH[2], #change time
$passwdmtime,
$shadowmtime
);
close $shadow_fh;
return @PW;
}
}
}
close $shadow_fh;
}
else {
Carp::cluck("Unable to open /etc/shadow: $!");
}
Carp::cluck("Entry for $value missing in /etc/shadow");
return @PW;
}
sub _readpasswd {
my ( $value, $field, $passwdmtime, $shadowmtime, $pwline, @PW ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat('/etc/passwd') )[9] ), $_[3] );
if ( open( my $passwd_fh, '<', '/etc/passwd' ) ) {
if ( $field == 0 ) {
my $match = quotemeta($value) . ':';
while ( !eof($passwd_fh) ) {
if ( !defined( $pwline = readline($passwd_fh) ) ) {
return;
}
if ( $pwline =~ m/^$match/ ) {
chomp $pwline;
@PW = split( /:/, $pwline );
close($passwd_fh);
return ( $PW[0], $PW[1], $PW[2], $PW[3], '', '', $PW[4], $PW[5], $PW[6], -1, -1, $passwdmtime, ( $shadowmtime || $passwdmtime ) );
}
}
}
else {
while ( !eof($passwd_fh) ) {
if ( !defined( $pwline = readline($passwd_fh) ) ) {
return;
}
@PW = split( /:/, $pwline );
if ( scalar @PW >= $MIN_FIELDS_FOR_VALID_ENTRY && $PW[0] =~ m/^[A-Za-z0-9_.]/ && $PW[$field] eq $value ) { # Check for valid lines and skip commented/invalid lines
chomp $PW[$#PW];
close($passwd_fh);
return ( $PW[0], $PW[1], $PW[2], $PW[3], '', '', $PW[4], $PW[5], $PW[6], -1, -1, $passwdmtime, ( $shadowmtime || $passwdmtime ) );
}
}
}
close($passwd_fh);
}
return;
}
sub _readmasterpasswd {
my ( $value, $field, $passwdmtime, $shadowmtime, @PW ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat('/etc/passwd') )[9] ), ( $_[3] || ( stat('/etc/master.passwd') )[9] ) );
if ( open my $master_fh, '<', '/etc/master.passwd' ) {
while ( my $line = <$master_fh> ) {
chomp $line;
my @PW = split( /:/, $line );
if ( @PW && defined( $PW[$field] ) && $PW[$field] eq $value ) {
close $master_fh;
return ( $PW[0], $PW[1], $PW[2], $PW[3], $PW[5], $PW[4], $PW[7], $PW[8], $PW[9], $PW[6], $PW[5], $passwdmtime, $shadowmtime );
}
}
close $master_fh;
}
else {
Carp::cluck("Unable to open /etc/master.passwd: $!");
}
return;
}
sub _pwfunc {
my ( $value, $field, $useipc, $pwkey ) = ( $_[0], ( $_[1] || 0 ), $_[2], $_[1] . ':' . ( $_[0] || 0 ) );
return if ( !defined $value );
if ($pwcacheistied) {
$Cpanel::Debug::level > 3 && print STDERR __PACKAGE__ . "::_pwfunc cache tie (tied) value[$value] field[$field]\n";
return $pwcachetie->{$pwkey}->{'contents'};
}
my ( $passwdmtime, $hpasswdmtime ) = (
( stat('/etc/passwd') )[9], ( $system eq 'freebsd' && -r '/etc/master.passwd' )
? ( stat(_) )[9]
: ( -r '/etc/shadow' ? ( stat(_) )[9] : 0 )
);
if ( exists $pwcache_ref->{$pwkey} ) {
$Cpanel::Debug::level > 3 && print STDERR __PACKAGE__ . "::_pwfunc exists in cache value[$value] field[$field]\n";
if ( $hpasswdmtime && ( $hpasswdmtime != $pwcache_ref->{$pwkey}->{'hcachetime'} )
|| ( $passwdmtime && $passwdmtime != $pwcache_ref->{$pwkey}->{'cachetime'} ) ) { #timewarp safe
$Cpanel::Debug::level > 3 && print STDERR __PACKAGE__ . "::_pwfunc cache miss value[$value] field[$field] pwkey[$pwkey] " . qq{hpasswdmtime: $hpasswdmtime != $pwcache_ref->{$pwkey}->{'hcachetime'} } . qq{passwdmtime: $passwdmtime != $pwcache_ref->{$pwkey}->{'cachetime'} } . "\n";
if ( defined $pwcache_ref->{$pwkey} && defined $pwcache_ref->{$pwkey}->{'contents'} ) {
delete @$pwcache_ref{ keys %$pwcache_ref }; #nuke it as its no longer valid
}
}
elsif ( exists( $pwcache_ref->{$pwkey}->{'contents'} ) ) {
$Cpanel::Debug::level > 3 && print STDERR __PACKAGE__ . "::_pwfunc cache hit value[$value] field[$field]\n";
return $pwcache_ref->{$pwkey}->{'contents'};
}
}
$Cpanel::Debug::level > 3 && print STDERR __PACKAGE__ . "::_pwfunc cache miss pwkey[$pwkey] value[$value] field[$field] passwdmtime[$passwdmtime] pwcacheistied[$pwcacheistied] hpasswdmtime[$hpasswdmtime]\n";
my $pwdata = _getpwdata( $value, $field, $passwdmtime, $hpasswdmtime );
if ( $pwdata && @$pwdata ) {
@{ $pwcache_ref->{$pwkey} }{ 'cachetime', 'hcachetime', 'contents' } = ( $passwdmtime, $hpasswdmtime, $pwdata );
if ( $field eq '0' ) { #if we getpwnam, then cache the getpwuid info as well
@{ $pwcache_ref->{ '2' . ':' . $pwdata->[2] } }{ 'cachetime', 'hcachetime', 'contents' } = ( $passwdmtime, $hpasswdmtime, $pwdata );
}
elsif ( !$skip_uid_cache && $field eq '2' ) {
@{ $pwcache_ref->{ '0' . ':' . $pwdata->[0] } }{ 'cachetime', 'hcachetime', 'contents' } = ( $passwdmtime, $hpasswdmtime, $pwdata );
}
}
return $pwdata;
}
sub init_pwcache {
return _build_pwcache();
}
sub init_passwdless_pwcache {
return _build_pwcache( 'nopasswd' => 1 );
}
sub fetch_pwcache {
init_passwdless_pwcache() if !$pwcache_inited;
return [ map { $pwcache_ref->{$_}->{'contents'} } ( $pwcache_has_uid_cache ? grep( !/^0:/, keys %{$pwcache_ref} ) : ( keys %{$pwcache_ref} ) ) ];
}
sub _build_pwcache {
my %OPTS = @_;
my ( $cache_file, $passwdmtime, $cache_file_mtime, $epasswd_ref, $epasswd_file, $hpasswdmtime ) = ( '/etc/passwd.cache', ( stat('/etc/passwd') )[9] );
if ( $OPTS{'nopasswd'} ) {
$hpasswdmtime = ( $system eq 'freebsd' && -r '/etc/master.passwd' ) ? ( stat(_) )[9] : ( stat('/etc/shadow') )[9];
$cache_file = '/etc/passwd' . ( $skip_uid_cache ? '.nouids' : '' ) . '.cache';
}
elsif ( $system eq 'freebsd' && -r '/etc/master.passwd' ) {
$hpasswdmtime = ( stat(_) )[9];
$epasswd_file = '/etc/master.passwd';
$cache_file = '/etc/master.passwd' . ( $skip_uid_cache ? '.nouids' : '' ) . '.cache';
}
elsif ( -r '/etc/shadow' ) {
$hpasswdmtime = ( stat(_) )[9];
$epasswd_file = '/etc/shadow';
$cache_file = '/etc/shadow' . ( $skip_uid_cache ? '.nouids' : '' ) . '.cache';
}
else {
$hpasswdmtime = 0;
}
if ( !$pwcacheistied && exists $INC{'Storable.pm'} ) {
$cache_file_mtime = ( stat($cache_file) )[9] || 0;
if ( $cache_file_mtime > $hpasswdmtime && $cache_file_mtime > $passwdmtime ) {
if ( open( my $cache_fh, '<', $cache_file ) ) {
my $cache_ref;
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; $cache_ref = Storable::pretrieve($cache_fh);'; #must be quoted or it ends up in the stash
close($cache_fh);
$Cpanel::Debug::level > 3 && print STDERR "[read pwcache from $cache_file]\n";
if ( $cache_ref
&& ( scalar keys %{$cache_ref} ) > 0
&& $cache_ref->{'0:root'}->{'hcachetime'} == $hpasswdmtime
&& $cache_ref->{'0:root'}->{'cachetime'} == $passwdmtime ) {
$Cpanel::Debug::level > 3 && print STDERR "[validated pwcache from $cache_file]\n";
%{$pwcache_ref} = ( %{$pwcache_ref}, %{$cache_ref} );
$pwcache_inited = ( $OPTS{'nopasswd'} ? 1 : 2 );
return;
}
}
}
}
if ($epasswd_file) { $epasswd_ref = _load_pws($epasswd_file); }
$pwcache_inited = ( $OPTS{'nopasswd'} ? 1 : 2 );
$pwcache_has_uid_cache = ( $skip_uid_cache ? 0 : 1 );
if ( open( my $pwcache_passwd_fh, '<', '/etc/passwd' ) ) {
local $/;
$pwcache_ref = {
map {
$_ = [ split( /:/, $_ ) ];
scalar @$_ >= $MIN_FIELDS_FOR_VALID_ENTRY && $_->[0] =~ m/^[A-Za-z0-9_]/
? (
'0:' . $_->[0] => { 'cachetime' => $passwdmtime, 'hcachetime' => $hpasswdmtime, 'contents' => [ $_->[0], ( exists $epasswd_ref->{ $_->[0] } ? $epasswd_ref->{ $_->[0] } : $_->[1] ), $_->[2], $_->[3], '', '', $_->[4], $_->[5], $_->[6], -1, -1, $passwdmtime, $hpasswdmtime ] },
)
: ()
}
split( /\n/, readline($pwcache_passwd_fh) )
};
if ( !$skip_uid_cache ) {
%$pwcache_ref = (
%$pwcache_ref,
map { '2:' . $pwcache_ref->{$_}{'contents'}->[2] => $pwcache_ref->{$_} } keys %$pwcache_ref
);
}
close($pwcache_passwd_fh);
}
if ( !$pwcacheistied && exists $INC{'Storable.pm'} && ref $pwcache_ref ) {
if ( !$cache_file_mtime
&& open( my $cache_fh, '>', $cache_file ) ) {
chmod( 0600, $cache_file );
close($cache_fh);
}
$Cpanel::Debug::level > 3 && print STDERR "[wrote pwcache to $cache_file]\n";
my $pwlock = Cpanel::SafeFile::safeopen( \*PWCACHEFILE, '>', $cache_file );
if ($pwlock) {
eval 'Storable::nstore_fd( $pwcache_ref, \*PWCACHEFILE );'; #must be quoted or it ends up in the stash
Cpanel::SafeFile::safeclose( \*PWCACHEFILE, $pwlock );
}
}
return;
}
sub pwcache_is_initted {
return ( $pwcache_inited ? $pwcache_inited : 0 );
}
sub _getpwdata {
my ( $value, $field, $passwdmtime, $shadowmtime ) = @_;
return if ( !defined $value || !defined $field );
my $myuid = $>;
$Cpanel::Debug::level > 3 && print STDERR __PACKAGE__ . "::_getpwdata called for myuid[$myuid] value[$value] field[$field]\n";
if ( !$pwdatafunc || $myuid != $pwdatafuncuid ) {
$pwdatafuncuid = $myuid;
if ( $system eq 'freebsd' ) {
$pwdatafunc = ( -r '/etc/master.passwd' ? \&_readmasterpasswd : \&_readpasswd );
}
else {
$pwdatafunc = ( -r '/etc/shadow' ? \&_readshadow : \&_readpasswd );
}
}
return [ $pwdatafunc->( $value, $field, $passwdmtime, $shadowmtime ) ];
}
sub _load_pws {
my $lookup_file = shift;
my %PW;
if ( open my $lookup_fh, '<', $lookup_file ) {
seek( $lookup_fh, 0, 0 );
local $/;
%PW = map { ( split( /:/, $_ ) )[ 0, 1 ] } split( /\n/, readline($lookup_fh) );
delete @PW{ '', grep( m/^#/, keys %PW ) };
close $lookup_fh;
}
return \%PW;
}
{
no warnings 'once';
*get_line_from_pwfile = \&_get_line_from_pwfile;
}
sub _get_line_from_pwfile {
my ( $lookup_file, $lookup_key, $is_case_insensitive ) = @_;
my @PW;
if ( open my $lookup_fh, '<', $lookup_file ) {
seek( $lookup_fh, 0, 0 );
my $search_regex = $is_case_insensitive ? qr/^\Q$lookup_key\E:/i : qr/^\Q$lookup_key\E:/;
while ( readline($lookup_fh) ) {
if ( $_ =~ $search_regex ) {
chomp();
@PW = split( /:/, $_ );
last;
}
}
close $lookup_fh;
}
return \@PW;
}
sub _get_keyvalue_from_pwfile {
my ( $lookup_file, $key_position, $lookup_key, $is_case_insensitive ) = @_;
my $pwref = _get_line_from_pwfile( $lookup_file, $lookup_key, $is_case_insensitive );
if ( defined $pwref && @{$pwref} ) {
return $pwref->[$key_position];
}
}
sub beginmatch {
return ( substr( $_[0], 0, length( $_[1] ) ) eq $_[1] ) ? 1 : 0;
}
sub dump_rec {
my $rec = shift;
if ( ref $rec eq 'HASH' ) {
my $buf;
foreach my $key ( keys %{$rec} ) {
if ( ref $rec->{$key} eq 'ARRAY' ) {
$buf .= "$key=" . join( ',', @{ $rec->{$key} } ) . "\t";
}
else {
$buf .= "$key=$rec->{$key}\t";
}
}
return $buf;
}
else {
return $rec;
}
}
sub get_gid_cacheref {
my $grplock = Cpanel::SafeFile::safeopen( \*GRPFILE, '<', '/etc/group' );
return if !$grplock;
local $/;
my %GIDCACHE = map { defined $_->[2] ? ( $_->[2] => $_ ) : () } map { [ split( /:/, $_ ) ] } split( /\n/, readline( \*GRPFILE ) );
Cpanel::SafeFile::safeclose( \*GRPFILE, $grplock );
return \%GIDCACHE;
}
sub load_pw_cache_file {
my ( $file, $passwduid, $passwdmtime, $no_uidcheck, $keepforever ) = @_;
if ( !defined $passwduid || !defined $passwdmtime ) {
( $passwduid, $passwdmtime ) = ( stat($file) )[ 4, 9 ];
}
if ( !$INC{'Storable.pm'} || ( !$no_uidcheck && $passwduid != 0 ) || !$passwdmtime ) { return; }
my $pwdata_ref;
if ( open( my $pw_file_fh, '<', $file ) ) {
eval ' local $SIG{__DIE__}; local $SIG{__WARN__}; $pwdata_ref = Storable::pretrieve($pw_file_fh); ';
close($pw_file_fh);
}
if ( !$pwdata_ref || !ref $pwdata_ref ) { return; }
my $pwdata = ( ref $pwdata_ref eq 'ARRAY' ? $pwdata_ref : $pwdata_ref->{'contents'} );
if ( !$pwdata || ref $pwdata ne 'ARRAY' ) { return; }
if ($keepforever) {
$pwdata->[11] = 2147483647;
$pwdata->[12] = 2147483647;
}
@{ $pwcache_ref->{ '2' . ':' . $pwdata->[2] } }{ 'cachetime', 'hcachetime', 'contents' } = ( $pwdata->[11], $pwdata->[12], $pwdata );
@{ $pwcache_ref->{ '0' . ':' . $pwdata->[0] } }{ 'cachetime', 'hcachetime', 'contents' } = ( $pwdata->[11], $pwdata->[12], $pwdata );
return $pwdata->[0];
}
sub invalidate {
my $keyname = shift;
if ( $keyname eq 'user' ) { $keyname = 0; }
elsif ( $keyname eq 'uid' ) { $keyname = 2; }
my $key = shift;
$keyname = int $keyname;
$key =~ s/\///g;
my $pwkey = $keyname . ':' . $key;
if ( exists $pwcache_ref->{$pwkey} ) {
delete $pwcache_ref->{$pwkey};
}
if ( $keyname == 0 ) {
unlink( '/var/cpanel/@pwcache/' . $key );
}
unlink( '/var/cpanel/pw.cache/' . $pwkey );
unlink(
'/etc/master.passwd.cache',
'/etc/master.passwd.nouids.cache',
'/etc/passwd.cache',
'/etc/passwd.nouids.cache',
'/etc/shadow.cache',
'/etc/shadow.nouids.cache',
);
}
sub getusername {
my $user = defined $_[0] ? $_[0] : $>;
return ( Cpanel::PwCache::getpwuid($user) )[0];
}
sub gethomedir {
if ( defined $_[0] ) {
return $HOMEDIR_CACHE{ $_[0] } if exists $HOMEDIR_CACHE{ $_[0] };
if ( $_[0] =~ /^[0-9]+$/ ) {
return ( $HOMEDIR_CACHE{ $_[0] } = ( Cpanel::PwCache::getpwuid( $_[0] ) )[7] );
}
else {
if ( $Cpanel::user && $Cpanel::homedir && $_[0] eq $Cpanel::user ) { return $Cpanel::homedir; }
return ( $HOMEDIR_CACHE{ $_[0] } = ( Cpanel::PwCache::getpwnam( $_[0] ) )[7] );
}
}
else {
my $uid = $>;
return $HOMEDIR_CACHE{$uid} if exists $HOMEDIR_CACHE{$uid};
return ( $HOMEDIR_CACHE{$uid} = ( Cpanel::PwCache::getpwuid($uid) )[7] );
}
}
1;
} # --- END Cpanel/PwCache.pm
{ # --- BEGIN Cpanel/Debug.pm
package Cpanel::Debug;
our $level = ( exists $ENV{'CPANEL_DEBUG_LEVEL'} && $ENV{'CPANEL_DEBUG_LEVEL'} ? int $ENV{'CPANEL_DEBUG_LEVEL'} : 0 );
1;
} # --- END Cpanel/Debug.pm
{ # --- BEGIN Cpanel/AccessIds/SetUids.pm
package Cpanel::AccessIds::SetUids;
# use Cpanel::PwCache ();
# use Cpanel::Logger ();
our $VERSION = '1.2';
sub setuids {
my ( $user, $group, @additional_groups ) = @_;
my ( $uid, $gid );
if ( $< != 0 ) {
my $cplog = Cpanel::Logger->new();
$cplog->die("Attempting to setuid as a normal user with RUID $<");
}
if ( $> != 0 ) {
my $cplog = Cpanel::Logger->new();
$cplog->die("Attempting to setuid as a normal user with EUID $>");
}
if ( $user !~ m/^\d+$/ ) {
( $uid, $gid ) = ( Cpanel::PwCache::getpwnam($user) )[ 2, 3 ];
}
else {
$uid = $user;
}
if ( $group && $group !~ /^\d+$/ ) {
$gid = ( Cpanel::PwCache::getpwnam($user) )[3];
}
elsif ( $group && $group =~ m/^\d+$/ ) {
$gid = $group;
}
elsif ( !defined $gid ) {
$gid = ( Cpanel::PwCache::getpwuid($uid) )[3];
}
Cpanel::PwCache::pwmksafecache();
if ( !defined $uid || !defined $gid ) {
my $cplog = Cpanel::Logger->new();
$cplog->die("Could not resolve UID ($uid) or GID ($gid)");
}
$gid = int($gid);
$( = $gid;
if ( $gid != $( ) {
my $cplog = Cpanel::Logger->new();
$cplog->die("Error setting RGID ($gid) [$user]");
}
$) = join( ' ', $gid, $gid, ( map { /^\d+$/ ? $_ : ( ( getgrgid($_) )[2] || () ) } @additional_groups ) );
if ( $gid != int($)) ) {
my $cplog = Cpanel::Logger->new();
$cplog->die("Error setting EGID ($gid) [$user]");
}
$< = $uid;
if ( $< != $uid ) {
my $cplog = Cpanel::Logger->new();
$cplog->die("Error setting RUID ($uid) [$user]");
}
$> = $uid;
if ( $> != $uid ) {
my $cplog = Cpanel::Logger->new();
$cplog->die("Error setting EUID ($uid) [$user]");
}
return $uid;
}
1;
} # --- END Cpanel/AccessIds/SetUids.pm
{ # --- BEGIN Cpanel/SafeDir/Read.pm
package Cpanel::SafeDir::Read;
sub read_dir {
my ( $dir, $coderef ) = @_;
if ( !-d $dir ) {
$! = 20;
return;
}
if ( !defined $coderef || ref $coderef ne 'CODE' ) {
$coderef = sub { return 1; };
}
my @contents;
if ( opendir my $dir_dh, $dir ) {
while ( my $file = readdir($dir_dh) ) {
next if $file =~ m{ \A [.]+ \z }xms;
push @contents, $file if $coderef->($file);
}
closedir $dir_dh;
return wantarray ? @contents : \@contents;
}
else {
return;
}
}
1;
} # --- END Cpanel/SafeDir/Read.pm
{ # --- BEGIN Cpanel/LoadModule.pm
package Cpanel::LoadModule;
# use Cpanel::Logger ();
my $logger;
my $has_perl_dir = 0;
sub _modloader {
my $module = shift;
if ( !$module ) {
$logger ||= Cpanel::Logger->new();
$logger->warn("Empty module name passed to modloader");
return;
}
eval qq{ require Cpanel::${module}; Cpanel::${module}::${module}_init(); }; #issafe #nomunge
}
sub loadmodule {
if ( !module_is_loaded( $_[0] ) ) {
_modloader( $_[0] );
}
}
sub module_is_loaded {
return exists $INC{ 'Cpanel/' . $_[0] . '.pm' } ? 1 : 0;
}
sub lazy_load_module {
my $mod = shift;
if ( !$has_perl_dir ) {
$has_perl_dir = 1;
if ( !grep( /\Q\/usr\/local\/cpanel\/perl\E/, @INC ) ) {
push @INC, '/usr/local/cpanel/perl';
}
}
my $mod_path = $mod;
$mod_path =~ s/::/\//g;
if ( exists $INC{ $mod_path . '.pm' } ) {
return;
}
eval "use $mod ();";
if ($@) {
delete $INC{ $mod_path . '.pm' };
$logger ||= Cpanel::Logger->new();
$logger->warn("Cpanel::LoadModule:: Failed to load module $mod - $@");
return;
}
return 1;
}
1;
} # --- END Cpanel/LoadModule.pm
{ # --- BEGIN Cpanel/Rand.pm
package Cpanel::Rand;
use strict;
no strict 'subs';
# use Cpanel::Rand::Get ();
# use Cpanel::Fcntl::Constants ();
require Exporter;
our @ISA = qw(Exporter);
our @EXPORT = qw(gettmpfile getranddata);
our $VERSION = '2.1';
my $logger;
my $MAX_TMPFILE_CREATE_ATTEMPTS = 50;
my $entropy_owner = $$;
my @entropy;
our ( $DO_OPEN, $SKIP_OPEN, $TYPE_FILE, $TYPE_DIR ) = ( 0, 1, 0, 1 );
{
no warnings 'once';
*getranddata = *Cpanel::Rand::Get::getranddata;
}
sub gettmpfile {
return get_tmp_file_by_name('/tmp/cpanel.TMP');
}
sub gettmpdir {
return get_tmp_dir_by_name('/tmp/cpanel.TMP');
}
sub api2_getranddata {
my %CFG = @_;
return { 'random' => Cpanel::Rand::Get::getranddata( $CFG{'length'} || 16 ) };
}
sub api2 {
my $func = shift;
my (%API);
$API{'getranddata'} = {};
return $API{$func};
}
sub get_tmp_dir_by_name {
my $templatefile = shift;
my $suffix = shift || '.work';
return get_tmp_file_by_name( $templatefile, $suffix, $TYPE_DIR, $DO_OPEN );
}
sub get_tmp_file_by_name {
my $templatefile = shift;
my $suffix = shift || '.work';
my $type = shift || $TYPE_FILE;
my $open = shift || $DO_OPEN;
if ( $suffix !~ m/^[.]/ ) {
$suffix = '.' . $suffix;
}
my $fh;
my $tmpfile = $templatefile . $suffix . '.' . Cpanel::Rand::Get::getranddata( 16, 0, 0, 10 );
($tmpfile) = $tmpfile =~ /(.*)/; #untaint
my $attempts = 0;
while (
++$attempts < $MAX_TMPFILE_CREATE_ATTEMPTS
&& (
-e $tmpfile
|| (
$open == $DO_OPEN
? (
$type == $TYPE_DIR
? !mkdir( $tmpfile, 0700 )
: !sysopen( $fh, $tmpfile, $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_EXCL, 0600 )
)
: 0
)
)
) {
$tmpfile = $templatefile . $suffix . '.' . Cpanel::Rand::Get::getranddata( 16, 0, 0, 10 );
($tmpfile) = $tmpfile =~ /(.*)/; #untaint
}
chmod( 0700, $tmpfile ) if $type == $TYPE_DIR; #in case of odd umask
if ( $attempts == $MAX_TMPFILE_CREATE_ATTEMPTS ) {
$logger ||= Cpanel::Logger->new();
$logger->warn("Failed to create a temp file using get_tmp_file_by_name using templatefile: $templatefile");
return ( '/dev/null', {} ) if wantarray && $type == $TYPE_FILE;
return '/dev/null';
}
return ( $tmpfile, $fh ) if wantarray && $type == $TYPE_FILE;
return $tmpfile; # file will close when $fh leaves context
}
1;
} # --- END Cpanel/Rand.pm
{ # --- BEGIN Cpanel/Rand/Get.pm
package Cpanel::Rand::Get;
# use Cpanel::Fcntl::Constants ();
our $random_source = '/dev/urandom';
my $entropy_owner = $$;
my $entropy;
my %TR_CODE_CACHE;
sub getranddata {
my ( $size, $sendheader, $my_chars_ar, $preloadcount ) = @_;
use bytes; #will go away when we leave this context
$preloadcount = 0 if ( !defined $preloadcount );
my $pid = $$;
if ( $entropy_owner != $pid ) {
$entropy = '';
$entropy_owner = $pid;
}
my $preloadcountsize = ( $preloadcount ? $preloadcount : 100 ) * 16;
$size = 10 if !defined $size || $size eq '0' || $size !~ m{ \A \d+ \z }xms;
my @chars;
my $rnddata = '';
my $rand_fh;
my $want_chars = ref $my_chars_ar eq 'ARRAY' ? join( '', @{$my_chars_ar} ) : join( '', ( 0 .. 9, 'A' .. 'Z', 'a' .. 'z', '_' ) );
my $tr_code_ref = $TR_CODE_CACHE{$want_chars} ||= eval 'sub { return ${$_[0]} =~ tr/' . quotemeta($want_chars) . '//cd; }';
while ( length($rnddata) < $size ) {
if ( length $entropy < $preloadcountsize ) {
if ( ref $rand_fh || open( $rand_fh, '<', $random_source ) ) {
if ( -c $rand_fh ) {
set_nonblock($rand_fh); # /dev/urandom should be non-blocking, however under some systems it may stall and we used to have to have
my ( $rin, $rout, $attempts, $bytes_needed );
while ( length $entropy < $preloadcountsize ) {
$bytes_needed = int( $preloadcountsize * 5.00 ); #we expect a large part of the data to be unusable
while ( $bytes_needed && ++$attempts < 50 ) {
if ( $bytes_needed -= sysread( $rand_fh, $entropy, $bytes_needed, length $entropy ) ) {
vec( $rin, fileno($rand_fh), 1 ) = 1 if !$rin;
select( $rout = $rin, undef, undef, 0.0125 ) if $bytes_needed;
}
}
last if $attempts == 100;
$tr_code_ref->( \$entropy );
}
}
}
}
if ( length $entropy ) {
$rnddata .= substr( $entropy, 0, $size, '' ); # just take what we need
}
else {
if ( !@chars ) {
srand( time ^ $$ ^ unpack '%L*', `ps axww | gzip` );
@chars = split( //, $want_chars );
}
for ( 1 .. $size ) {
$rnddata .= $chars[ rand($#chars) ];
}
}
}
close($rand_fh) if ref $rand_fh;
return substr( $rnddata, 0, $size );
}
sub set_nonblock {
my $fh = shift;
my $flags = fcntl( $fh, $Cpanel::Fcntl::Constants::F_GETFL, 0 )
or warn "Can't get the fcntl flags on $fh: $!\n";
fcntl( $fh, $Cpanel::Fcntl::Constants::F_SETFL, $flags | $Cpanel::Fcntl::Constants::O_NONBLOCK )
or warn "Can't fcntl $fh to non-blocking: $!\n";
}
1;
} # --- END Cpanel/Rand/Get.pm
{ # --- BEGIN Cpanel/Fcntl/Constants.pm
package Cpanel::Fcntl::Constants;
BEGIN {
our ( $F_GETFL, $F_SETFL, $O_NONBLOCK, $O_WRONLY, $O_CREAT, $O_EXCL );
if ( $^O eq 'linux' || $^O eq 'freebsd' ) {
$F_GETFL = 3;
$F_SETFL = 4;
$O_WRONLY = 1;
}
if ( $^O eq 'linux' ) {
$O_NONBLOCK = 2048;
$O_CREAT = 64;
$O_EXCL = 128;
}
elsif ( $^O eq 'freebsd' ) {
$O_NONBLOCK = 4;
$O_CREAT = 512;
$O_EXCL = 2048;
}
else {
require Fcntl;
$F_GETFL = &Fcntl::F_GETFL;
$F_SETFL = &Fcntl::F_SETFL;
$O_NONBLOCK = &Fcntl::O_NONBLOCK;
$O_WRONLY = &Fcntl::O_WRONLY;
$O_CREAT = &Fcntl::O_CREAT;
$O_EXCL = &Fcntl::O_EXCL;
( $F_GETFL && $F_SETFL && $O_NONBLOCK ) or die;
}
}
1;
} # --- END Cpanel/Fcntl/Constants.pm
{ # --- BEGIN Cpanel/FileUtils/TouchFile.pm
package Cpanel::FileUtils::TouchFile;
use strict;
# use Cpanel::Logger ();
my $logger = Cpanel::Logger->new();
our $VERSION = '1.1';
sub touchfile {
my ( $file, $verbose, $fail_ok ) = @_;
if ( !defined $file ) {
$logger->warn("touchfile called with undefined file");
return;
}
my $mtime = -e $file ? ( stat _ )[9] : 0; # for warnings safe numeric comparison
my $now = time();
if ( utime $now, $now, $file ) {
return 1;
}
my $mtime_after_utime = -e $file ? ( stat _ )[9] : 0; # for warnings safe numeric comparison
if ( $mtime == $mtime_after_utime ) {
if ( open my $fh, '>>', $file ) { # append so we don't wipe out contents
close $fh;
my $mtime_after_open = -e $file ? ( stat _ )[9] : 0; # for warnings safe numeric comparison
return 1 if $mtime != $mtime_after_open; # in case open does not change it, see comment below
}
}
if ($fail_ok) { return; }
my $at_this_point = -e $file ? ( stat _ )[9] : 0; # for warnings safe numeric comparison
if ( $mtime == $at_this_point ) {
my $new_at_this_point = -e $file ? ( stat _ )[9] : 0; # for warnings safe numeric comparison
if ( $mtime == $new_at_this_point ) {
$logger->info('Trying to do system touch') if $verbose;
if ( system( 'touch', $file ) != 0 ) {
$logger->info('system method 1 failed.') if $verbose;
if ( system("touch $file") != 0 ) {
$logger->info('system method 2 failed') if $verbose;
}
}
}
}
if ( !-e $file ) { # obvisouly it didn't touch it if it doesn't exist...
$logger->warn("Failed to create $file: $!");
return;
}
else {
my $after_all_that = -e $file ? ( stat _ )[9] : 0; # for warnings safe numeric comparison
if ( $mtime && $mtime == $after_all_that ) {
$logger->warn("mtime of $file not changed");
return;
}
return 1;
}
}
1;
} # --- END Cpanel/FileUtils/TouchFile.pm
{ # --- BEGIN Cpanel/FileUtils/Move.pm
package Cpanel::FileUtils::Move;
use strict;
# use Cpanel::Rand ();
# use Cpanel::Logger ();
# use Cpanel::FileUtils::Link ();
sub safemv {
my $srcfile = shift;
my $option;
if ( $srcfile =~ m/^\-/ ) {
$option = $srcfile;
$srcfile = shift;
}
my $destfile = shift;
my $returnval = 0;
my $clobber = 0;
my $verbose = 0;
my $archive = 0;
if ( !-e $srcfile ) {
Cpanel::Logger::cplog( "$srcfile does not exist.", 'warn', __PACKAGE__ );
return 0;
}
return 0 if !$destfile;
if ( $option && $option ne '--' ) {
if ( $option =~ m/f/i ) { $clobber = 1; }
if ( $option =~ m/v/i ) { $verbose = 1; }
if ( $option =~ m/a/i ) { $archive = 1; }
}
if ( !$clobber && -e $destfile ) {
print "Unable to move, $destfile exists!" if $verbose;
Cpanel::Logger::cplog( "Unable to move, $destfile, needs force option.", 'warn', __PACKAGE__ );
return 0;
}
elsif ( $clobber && $archive && -e $destfile ) {
my $tmpdestfile = Cpanel::Rand::get_tmp_file_by_name( $destfile, 'cpbak', $Cpanel::Rand::TYPE_FILE, $Cpanel::Rand::SKIP_OPEN ); # audit case 46806 ok
if ( !rename( $destfile, $tmpdestfile ) ) {
Cpanel::Logger::cplog( "Unable to rename $destfile: $!", 'warn', __PACKAGE__ );
return 0;
}
else {
print "Renamed destination file to $tmpdestfile\n" if $verbose;
}
}
elsif ( -e $destfile ) {
unlink $destfile;
}
if ( !rename( $srcfile, $destfile ) ) {
if ( !Cpanel::FileUtils::Link::_replicate_file( $srcfile, $destfile ) ) {
return 0;
}
}
print "Moved: $srcfile -> $destfile\n" if $verbose;
return 1;
}
1;
} # --- END Cpanel/FileUtils/Move.pm
{ # --- BEGIN Cpanel/FileUtils/Link.pm
package Cpanel::FileUtils::Link;
# use Cpanel::Logger ();
# use Cpanel::SafeFile ();
use strict;
sub safeunlink {
my $file = shift;
return 1 if !-e $file;
if ( unlink $file ) {
return 1;
}
else {
Cpanel::Logger::cplog( "Unable to unlink $file: $!", 'warn', __PACKAGE__, 1 );
return;
}
}
sub _replicate_file {
my $orig = shift;
my $dest = shift;
my ( $mode, $uid, $gid, $atime, $mtime ) = ( stat($orig) )[ 2, 4, 5, 8, 9 ];
$mode = $mode & 07777;
return 0 if ( !-e _ );
return 0 unless ( -r _ || -w _ || -x _ );
open( ORI, '<', $orig ) || return 0;
my $destlock = Cpanel::SafeFile::safeopen( \*DEST, '>', $dest ) || do {
Cpanel::Logger::cplog( "Unable to open $dest for write!", 'warn', __PACKAGE__ );
close(ORI);
return 0;
};
while (<ORI>) {
print DEST;
}
close(ORI);
Cpanel::SafeFile::safeclose( \*DEST, $destlock );
if ( -z $orig != -z $dest ) {
unlink($dest);
Cpanel::Logger::cplog( "Unable to properly write $dest", 'warn', __PACKAGE__ );
return 0;
}
unless ( chown( $uid, $gid, $dest ) ) {
Cpanel::Logger::cplog( "Unable to chown $dest to UID $uid GID $gid", 'warn', __PACKAGE__ );
}
unless ( chmod( $mode, $uid, $dest ) ) {
Cpanel::Logger::cplog( "Unable to chmod $dest for UID $uid", 'warn', __PACKAGE__ );
}
unless ( utime( $atime, $mtime, $dest ) ) {
Cpanel::Logger::cplog( "Unable to set utime on $dest for UID $uid", 'warn', __PACKAGE__ );
}
return 1;
}
sub safelink {
my $orig = shift;
my $dest = shift;
return 0 if ( $orig eq '' || $dest eq '' || !-e $orig );
return 0 unless ( -r $orig || -w $orig || -x $orig );
if ( !link( $orig, $dest ) ) {
return _replicate_file( $orig, $dest );
}
return 1;
}
1;
} # --- END Cpanel/FileUtils/Link.pm
{ # --- BEGIN Cpanel/HttpRequest.pm
package Cpanel::HttpRequest;
use strict;
use Socket ();
# use Cpanel::Alarm ();
# use Cpanel::ArrayFunc ();
# use Cpanel::URL ();
# use Cpanel::PipeHandler ();
# use Cpanel::MirrorSearch ();
# use Cpanel::UrlTools ();
# use Cpanel::SocketIP ();
# use Cpanel::Encoder::URI ();
# use Cpanel::Logger ();
our $SIMULATE_FAILURE = 0;
our $DEBUG = 0;
our $VERSION = '2.0';
our $MAX_CONTENT_SIZE = ( 1024 * 1024 * 1024 * 1024 );
our $HTTP_RETRY_COUNT = 12;
my $logger;
my $buffer_size = 131070;
my $has_cpanel_dnsRoots = 0;
sub new {
my ( $obj, %OPTS ) = @_;
my $self = {};
bless $self;
$self->{'connectedHostAddress'} = ''; #ip of connected host
$self->{'connectedHostname'} = ''; #hostname of connected host
$self->{'connectedHostFailCount'} = 0;
$self->{'connected'} = 0;
$self->{'http_retry_count'} = $OPTS{'http_retry_count'} || $HTTP_RETRY_COUNT;
$self->{'use_dns_cache'} = exists $OPTS{'use_dns_cache'} ? int $OPTS{'use_dns_cache'} : 1; #on by default
$self->{'retry_dns'} = exists $OPTS{'retry_dns'} ? $OPTS{'retry_dns'} : 1;
$self->{'use_mirror_addr_list'} = $OPTS{'use_mirror_addr_list'} || 0;
$self->{'hideOutput'} = $OPTS{'hideOutput'} && $OPTS{'hideOutput'} eq '1' ? 1 : 0;
$self->{'dns_cache_ttl'} = exists $OPTS{'dns_cache_ttl'} ? int $OPTS{'dns_cache_ttl'} : 7200;
$self->{'mirror_search_attempts'} = {};
$self->{'last_status'} = undef;
$self->{'die_on_404'} = $OPTS{'die_on_404'} || 0;
$self->{'return_on_404'} = $OPTS{'return_on_404'} || 0;
$self->{'protocol'} = $OPTS{'protocol'} || 0;
$self->{'level'} = 0;
return $self;
}
sub request {
my ( $self, %OPTS ) = @_;
my $host = $OPTS{'host'};
my $url = $OPTS{'url'};
my $destfile = $OPTS{'destfile'} || '';
my $http_retry_count = $OPTS{'http_retry_count'} || $self->{'http_retry_count'};
my $complete = 0;
my $keepalive = 1;
my $page;
my $quiet = $self->{'hideOutput'} || 0;
$url =~ tr/\///s; # squash duplicate "/"
print STDERR "[request] $host/$url\n" if $DEBUG;
my $http_protocol;
if ( $OPTS{'protocol'} || $self->{'protocol'} ) { $http_protocol = 'HTTP/1.1'; }
else { $http_protocol = 'HTTP/1.0'; $keepalive = 0; }
print( ( "\t" x $self->{'level'}++ ) . "Fetching http://${host}${url} (connected:" . $self->{'connected'} . ')....' ) if !$self->{'hideOutput'};
my $alarm = Cpanel::Alarm->new(60);
{
my $httptrycount = 0;
HTTP_TRY:
while ( !$complete && $httptrycount < $http_retry_count ) { # should be < then because we increment in the loop
++$httptrycount;
print "...(request attempt $httptrycount/$http_retry_count)..." if !$self->{'hideOutput'};
$self->_initrequest(
'host' => ( $host || '' ),
'url' => ( $url || '' ),
'http_protocol' => ( $http_protocol || '' ),
'destfile' => $destfile,
'headers' => $OPTS{'headers'},
'port' => $OPTS{'port'},
'addresslist' => $OPTS{'addresslist'},
'attempt' => $httptrycount,
'max_attempts' => $http_retry_count,
) || next HTTP_TRY;
if ( !defined $self->{'httpsocket'} || !ref $self->{'httpsocket'} ) {
$self->{'connected'} = 0;
next HTTP_TRY;
}
my $clength = 0;
my $buffer;
my $percent;
my $lastpercent = 0;
my $inheaders = 1;
my $togo;
print '...receiving...' if !$self->{'hideOutput'};
eval {
local $SIG{'__DIE__'} = 'DEFAULT';
local $SIG{'ALRM'} = sub {
print '..Timeout on receive..' if !$quiet;
die;
};
local $SIG{'PIPE'} = $SIG{'ALRM'};
my $httpline = readline $self->{'httpsocket'};
if ( $httpline =~ m/^HTTP\/(\S+)\s+(\d+)/ ) {
my ( $protocol, $error_code ) = ( $1, $2 );
$keepalive = 0 if $protocol eq 'HTTP/1.0';
$self->{'last_status'} = $error_code;
if ( $error_code eq '404' || $error_code eq '500' || $error_code eq '301' ) {
if ( $error_code eq '404' && $OPTS{'exitOn404'} ) {
print "...Distribution not required.\n" if !$self->{'hideOutput'};
exit;
}
if ( $error_code eq '404' && ( $OPTS{'die_on_404'} or $self->{'die_on_404'} ) ) {
die "404 - $httpline";
}
print "Error $error_code while fetching URL http://$host/$url\n" if !$self->{'hideOutput'};
if ( $destfile ne '' ) { close( $self->{'destfile_fh'} ); unlink $destfile; }
$self->_skip_failed_mirror();
die;
}
}
$alarm->set(60);
my %HEADERS;
{
local $/ = ( $httpline =~ tr/\r// ) ? "\r\n\r\n" : "\n\n";
%HEADERS = map { ( lc $_->[0], substr( $_->[1], 0, 8190 ) ) } # lc the header and truncate the value to 8190 bytes
map { [ ( split( /:\s*/, $_, 2 ) )[ 0, 1 ] ] } # split header into name, value - and place into an arrayref for the next map to alter
split( /\r?\n/, readline( $self->{'httpsocket'} ) ); # split each header
}
$keepalive = 0 if exists $HEADERS{'connection'} && $HEADERS{'connection'} =~ /close/i;
if ( exists $HEADERS{'transfer-encoding'} && $HEADERS{'transfer-encoding'} =~ /chunked/i ) {
local $/ = "\r\n";
my ( $size_to_read, $read_size, $read_buffer, $bytes_read );
my $clength = ( defined $HEADERS{'content-length'} ? int $HEADERS{'content-length'} : $MAX_CONTENT_SIZE );
CHUNKED_READ:
while (1) {
chomp( $read_size = readline( $self->{'httpsocket'} ) );
$size_to_read = hex($read_size) + 2;
while ($size_to_read) {
$alarm->set(60);
if ( !defined($read_size) || !( $bytes_read = read( $self->{'httpsocket'}, $read_buffer, $size_to_read ) ) ) {
last CHUNKED_READ;
}
$size_to_read -= $bytes_read;
$page .=
$size_to_read == 0 ? substr( $read_buffer, 0, -2 )
: $size_to_read == 1 ? substr( $read_buffer, 0, -1 )
: $read_buffer;
$togo -= length $page;
if ( ( $percent = int( ( ( $clength - $togo ) / $clength ) * 100 ) ) != $lastpercent ) {
print $percent . '%' . '...' if !$self->{'hideOutput'};
$lastpercent = $percent;
}
if ( $destfile ne '' ) {
print { $self->{'destfile_fh'} } $page;
$page = '';
}
}
last if hex($read_size) == 0;
}
}
else {
if ( $togo = $clength = ( defined $HEADERS{'content-length'} ? int $HEADERS{'content-length'} : $MAX_CONTENT_SIZE ) ) {
my $bytes_read;
if ( $destfile ne '' ) {
while ($togo) {
$alarm->set(60);
$togo -= ( $bytes_read = read $self->{'httpsocket'}, $buffer, ( $togo > $buffer_size ? $buffer_size : $togo ) );
if ( ( $percent = int( ( ( $clength - $togo ) / $clength ) * 100 ) ) != $lastpercent ) {
print $percent . '%' . '...' if !$self->{'hideOutput'};
$lastpercent = $percent;
}
last if !$bytes_read;
print { $self->{'destfile_fh'} } $buffer;
}
}
else {
$page = '';
while ($togo) {
$alarm->set(60);
$togo -= ( $bytes_read = read $self->{'httpsocket'}, $page, ( $togo > $buffer_size ? $buffer_size : $togo ), length $page );
if ( ( $percent = int( ( ( $clength - $togo ) / $clength ) * 100 ) ) != $lastpercent ) {
print $percent . '%' . '...' if !$self->{'hideOutput'};
$lastpercent = $percent;
}
last if !$bytes_read;
}
}
die "Incomplete read.\n" if defined $HEADERS{'content-length'} && $togo != 0;
}
}
$complete = 1;
};
if ( !$complete ) {
if ( defined( $self->{'httpsocket'} ) ) { close( $self->{'httpsocket'} ); }
$self->{'connected'} = 0;
if ( $inheaders && ++$self->{'connectedHostFailCount'} < 3 ) { #if we failed inside the http headers we can just reconnect to the same host as the server may have just disconnected us per the http spec
print "...server closed connection..." if !$self->{'hideOutput'};
}
else {
print "...failover..." if !$self->{'hideOutput'};
$self->_remove_failed_mirror();
}
if ( $self->{'return_on_404'} && $self->{'last_status'} eq '404' ) {
$self->{'level'}--;
return wantarray ? ( '', 0 ) : 0;
}
}
else {
$self->{'connectedHostFailCount'} = 0;
}
$alarm->set(60);
}
}
undef $alarm;
print "...request success..." if $complete && !$self->{'hideOutput'};
print "...Done\n" if !$self->{'hideOutput'};
if ( $destfile ne '' ) {
close( $self->{'destfile_fh'} );
}
if ( $complete == 0 || !$keepalive ) {
$self->{'connected'} = 0;
if ( defined $self->{'httpsocket'} ) {
close $self->{'httpsocket'};
}
}
else {
$self->{'connected'} = 1;
}
$self->{'level'}--;
if ( $destfile ne '' ) {
return wantarray ? ($complete) : $complete;
}
return wantarray ? ( $page, $complete ) : $page;
}
sub disconnect {
my ($self) = @_;
if ( defined( $self->{'httpsocket'} ) ) { close( $self->{'httpsocket'} ); }
$self->{'connected'} = 0;
}
sub http_post_req {
my ( $self, $args_hr ) = @_;
my $query = $args_hr->{'query'};
if ( ref $args_hr->{'query'} eq 'HASH' ) {
$query = '';
foreach my $key ( keys %{ $args_hr->{'query'} } ) {
if ( ref $args_hr->{'query'}{$key} eq 'ARRAY' ) {
for my $val ( @{ $args_hr->{'query'}{$key} } ) {
$query .=
$query
? "&$key=" . Cpanel::Encoder::URI::uri_encode_str($val)
: "$key=" . Cpanel::Encoder::URI::uri_encode_str($val);
}
}
else {
$query .=
$query
? "&$key=" . Cpanel::Encoder::URI::uri_encode_str( $args_hr->{'query'}{$key} )
: "$key=" . Cpanel::Encoder::URI::uri_encode_str( $args_hr->{'query'}{$key} );
}
}
}
my $postdata_len = length($query);
my $proto = getprotobyname('tcp');
return unless defined $proto;
socket( my $socket_fh, &Socket::AF_INET, &Socket::SOCK_STREAM, $proto );
return unless $socket_fh;
my $iaddr = gethostbyname( $args_hr->{'host'} );
my $port = getservbyname( 'http', 'tcp' );
return unless ( defined $iaddr && defined $port );
my $sin = Socket::sockaddr_in( $port, $iaddr );
return unless defined $sin;
if ( connect( $socket_fh, $sin ) ) {
send $socket_fh, "POST /$args_hr->{'uri'} HTTP/1.1\nContent-Length: $postdata_len\nHost: $args_hr->{'host'}\n\n$query", 0;
if ( ref $args_hr->{'output_handler'} eq 'CODE' ) {
my $in_header = 1;
while (<$socket_fh>) {
if ( /^\n$/ || /^\r\n$/ || /^$/ ) {
$in_header = 0;
next;
}
$args_hr->{'output_handler'}->( $_, $in_header );
}
}
}
close $socket_fh;
}
sub download {
my ( $self, $url, $file ) = @_;
unlink $file;
my $parsed_url = Cpanel::URL::parse($url);
my $res = $self->request(
'host' => $parsed_url->{'host'},
'url' => $parsed_url->{'uri'},
'destfile' => $file,
'protocol' => 0,
);
return $res;
}
sub httpreq {
my ( $self, $host, $url, $file ) = @_;
unlink $file;
my $res;
my %OPTS = (
'host' => $host,
'url' => $url,
'protocol' => 0
);
if ($file) {
$OPTS{'destfile'} = $file;
}
return $self->request(%OPTS);
}
sub skiphost {
my $self = shift;
$self->{'connected'} = 0;
if ( defined( $self->{'httpsocket'} ) ) { close( $self->{'httpsocket'} ); }
$self->_remove_failed_mirror();
}
sub _getAddressList {
my ( $self, $host ) = @_;
print STDERR "[_getAddressList][$host]\n" if $DEBUG;
my $quiet = $self->{'hideOutput'} || 0;
my $resolve_attempt_count = 1;
my ( $cache_key, $homedir, @trueaddresses, $loaded_cache );
if ( $self->{'use_dns_cache'} ) {
$homedir = $Cpanel::homedir || ( getpwuid($>) )[7];
$cache_key = $host;
$cache_key =~ s/\///g;
if ( !-e $homedir . '/.HttpRequest' ) {
mkdir $homedir . '/.HttpRequest', 0700;
}
if ( -e $homedir . '/.HttpRequest/' . $cache_key && ( ( stat(_) )[9] + $self->{'dns_cache_ttl'} ) > time() ) {
if ( open( my $address_cache_fh, '<', $homedir . '/.HttpRequest/' . $cache_key ) ) {
local $/;
@trueaddresses = split( /\n/, readline($address_cache_fh) );
$loaded_cache = 1;
close($address_cache_fh);
print "Using dns cache file $homedir/.HttpRequest/$cache_key..." if !$quiet;
}
else {
print "Loading dns cache file $homedir/.HttpRequest/$cache_key failed ($!)..." if !$quiet;
}
}
}
while ( !@trueaddresses ) {
$loaded_cache = 0;
print "Resolving $host...(resolve attempt $resolve_attempt_count/65)..." if !$quiet;
@trueaddresses = Cpanel::SocketIP::_resolveIpAddress($host);
last if (@trueaddresses);
print "Resolving $host using backup method..." if !$quiet;
eval 'require Cpanel::DnsRoots; $has_cpanel_dnsRoots=1' if !$has_cpanel_dnsRoots;
@trueaddresses = Cpanel::DnsRoots::resolveIpAddressBAMP($host) if $has_cpanel_dnsRoots;
last if ( @trueaddresses || !$self->{'retry_dns'} || $resolve_attempt_count++ >= 65 );
print "Waiting for dns resolution to become available...." if !$quiet;
sleep 1;
}
print STDERR "[finished address lookups]\n" if $DEBUG;
if ($SIMULATE_FAILURE) {
@trueaddresses = ( '1.1.1.1', '2.2.2.2' );
}
if ( scalar @trueaddresses == 0 ) {
$logger ||= Cpanel::Logger->new();
$logger->warn("$host could not be resolved to an ip adddress, please check your /etc/resolv.conf");
return wantarray ? () : [];
}
elsif ( scalar @trueaddresses > 1 ) {
Cpanel::ArrayFunc::shuffle( \@trueaddresses );
print STDERR "[has resolved, testing]\n" if $DEBUG;
if ( !$loaded_cache ) {
if ( $self->{'use_mirror_addr_list'} || $host eq 'httpupdate.cpanel.net' ) {
print "\n" if !$quiet;
my $mirror_addr_list = $self->request( 'level' => 1, 'addresslist' => \@trueaddresses, 'host' => $host, 'url' => '/mirror_addr_list', 'protocol' => 1, 'http_retry_count' => 3 );
my @mirror_addr_list_ADDR_LIST;
if ($mirror_addr_list) {
foreach my $line ( split( /\n/, $mirror_addr_list ) ) {
chomp($line);
my ($ipaddr) = split( /=/, $line );
push @mirror_addr_list_ADDR_LIST, $ipaddr if ( $ipaddr =~ /^[\d\.\:]+$/ );
}
}
if (@mirror_addr_list_ADDR_LIST) {
print "...found " . scalar @mirror_addr_list_ADDR_LIST . " host(s) from mirror_addr_list..." if !$quiet;
@trueaddresses = @mirror_addr_list_ADDR_LIST;
}
}
if ( $self->{'use_dns_cache'} && @trueaddresses ) {
if ( open( my $address_cache_fh, '>', $homedir . '/.HttpRequest/' . $cache_key ) ) {
print {$address_cache_fh} join( "\n", @trueaddresses );
close($address_cache_fh);
}
}
}
if ( ++$self->{'mirror_search_attempts'}{$host} == 4 ) { # first time = loaded from cache
die "$host did not have any working mirrors. Please check your internet connection or dns server.";
}
print "...searching for mirrors (mirror search attempt " . $self->{'mirror_search_attempts'}{$host} . "/3)..." if !$quiet;
my @goodurls = Cpanel::MirrorSearch::findfastest(
'days' => 10,
'key' => $host,
'count' => 10,
'urls' => [ Cpanel::UrlTools::buildurlfromuri( \@trueaddresses, '/speedcheck' ) ],
'quiet' => $quiet,
);
if ( @trueaddresses = Cpanel::UrlTools::extracthosts( \@goodurls ) ) {
print "...mirror search success..." if !$self->{'hideOutput'};
$self->{'mirror_search_attempts'}{$host} = 0;
}
else {
print "...mirror search failed..." if !$self->{'hideOutput'};
}
if ( !@trueaddresses && $self->{'use_dns_cache'} ) {
$self->_destroy_ip_cache($host);
}
}
if ($SIMULATE_FAILURE) {
return wantarray ? () : [];
}
return wantarray ? @trueaddresses : \@trueaddresses;
}
sub _remove_failed_mirror {
my $self = shift;
my $addr = $self->{'connectedHostAddress'}; #ip of connected host
print STDERR "[_remove_failed_mirror] $addr\n" if $DEBUG;
print "...removing $addr..." if !$self->{'hideOutput'};
@{ $self->{'hostIps'}{ $self->{'host'} } } = grep( !m/^\Q$addr\E$/, @{ $self->{'hostIps'}{ $self->{'host'} } } );
Cpanel::MirrorSearch::remove_mirror( 'key' => $self->{'connectedHostname'}, 'addr' => $addr );
if ( !@{ $self->{'hostIps'}{ $self->{'host'} } } && $self->{'use_dns_cache'} ) {
$self->_destroy_ip_cache();
}
}
sub _skip_failed_mirror {
my $self = shift;
my $addr = $self->{'connectedHostAddress'}; #ip of connected host
print STDERR "[_skip_failed_mirror] $addr\n" if $DEBUG;
print "...skipping $addr..." if !$self->{'hideOutput'};
@{ $self->{'hostIps'}{ $self->{'host'} } } = grep( !m/^\Q$addr\E$/, @{ $self->{'hostIps'}{ $self->{'host'} } } );
push @{ $self->{'skipped_hostIps'}{ $self->{'host'} } }, $addr; # if ! grep( !m/^\Q$addr\E$/, @{ $self->{'skipped_hostIps'}{ $self->{'host'} } } );;
if ( !@{ $self->{'hostIps'}{ $self->{'host'} } } && @{ $self->{'skipped_hostIps'}{ $self->{'host'} } } ) {
@{ $self->{'hostIps'}{ $self->{'host'} } } = @{ $self->{'skipped_hostIps'}{ $self->{'host'} } };
$self->{'skipped_hostIps'}{ $self->{'host'} } = []; # important to clear out this since we moved them to hostIps
}
}
sub _destroy_ip_cache {
my $self = shift;
my $cache_key = shift || $self->{'connectedHostname'};
my $homedir = $Cpanel::homedir || ( getpwuid($>) )[7];
$cache_key =~ s/\///g;
unlink("$homedir/.HttpRequest/$cache_key"); # Kill the cache if we run out of working hosts so it will rebuild
}
sub _setupsocket {
my $self = shift;
my $host = shift;
my $addr = shift;
my $port = shift || 80;
if ( exists $self->{'httpsocket'}
&& $self->{'httpsocket'} ) {
close $self->{'httpsocket'};
}
my $quiet = $self->{'hideOutput'} || 0;
print "...connecting to $addr..." if !$quiet;
my $alarm = Cpanel::Alarm->new(30);
my $connected = 0;
eval {
local $SIG{'__DIE__'} = 'DEFAULT';
local $SIG{'ALRM'} = sub {
print '..Timeout on connect..' if !$quiet;
die;
};
local $SIG{'PIPE'} = $SIG{'ALRM'};
my $proto = getprotobyname('tcp') || do {
print "..Cannot resolve protocol tcp.." if !$quiet;
die;
};
socket( $self->{'httpsocket'}, &Socket::AF_INET, &Socket::SOCK_STREAM, $proto ) || do {
print "..Cannot create socket.." if !$quiet;
die;
};
my $iaddr = Socket::inet_aton("$addr") || do {
print "...Unable to translate IP address for host: ${host}..." if !$quiet;
die;
};
my $sin = Socket::sockaddr_in( $port, $iaddr ) || do {
print "..ERROR: $! .." if !$quiet;
die;
};
connect( $self->{'httpsocket'}, $sin ) || do {
die "Unable to connect: $!";
};
$connected = 1;
};
undef $alarm;
return $connected;
}
sub _initrequest {
my $self = shift;
$self->{'last_status'} = undef;
my %OPTS = @_;
print STDERR "\n\n\n\n[_initrequest]\n" if $DEBUG;
my $host = $OPTS{'host'};
$self->{'host'} = $host;
$self->{'hostIps'}{$host} ||= [];
$self->{'skipped_hostIps'}{$host} ||= [];
my $url = $OPTS{'url'};
my $http_protocol = $OPTS{'http_protocol'};
my $destfile = $OPTS{'destfile'};
my $headers = $OPTS{'headers'};
my $port = $OPTS{'port'};
$self->{'connected'} = 0 if ( $self->{'connectedHostname'} ne $host );
my $has_an_addresslist = ( exists $OPTS{'addresslist'} && ref $OPTS{'addresslist'} ) ? 1 : 0;
if ( ( $self->{'using_addresslist'}{$host} && !$has_an_addresslist ) || ( !$self->{'using_addresslist'}{$host} && $has_an_addresslist ) || $SIMULATE_FAILURE ) {
delete $self->{'using_addresslist'}{$host};
$self->{'hostIps'}{$host} = $self->{'skipped_hostIps'}{$host} = [];
$self->{'connected'} = 0; # We must also disconnect because we may not be connected
}
if ($DEBUG) {
print STDERR "[about to get ips has_an_addresslist=($has_an_addresslist)]\n";
my $hostips_defined = @{ $self->{'hostIps'}{$host} } ? 1 : 0;
my $skipped_hostips_defined = @{ $self->{'skipped_hostIps'}{$host} } ? 1 : 0;
print "[HOSTIPS_DEFINED] $hostips_defined [SKIPPEDHOSTIPS_DEFINED] $skipped_hostips_defined\n";
}
if ( !@{ $self->{'hostIps'}{$host} } && !@{ $self->{'skipped_hostIps'}{$host} } ) {
print STDERR "[...doing address lookup block...]\n" if $DEBUG;
if ( @{ $self->{'hostIps'}{$host} } = $has_an_addresslist ? @{ $OPTS{'addresslist'} } : $self->_getAddressList($host) ) {
if ($has_an_addresslist) { $self->{'using_addresslist'}{$host} = 1; }
if ($DEBUG) {
my $hostips_defined = @{ $self->{'hostIps'}{$host} } ? 1 : 0;
print STDERR "[....addresslist load or getAddressList ok...] ($hostips_defined)\n";
}
}
else {
print STDERR "[...failed to resolve $host...]\n" if $DEBUG;
return;
}
}
print STDERR "[init_req finished populating hostIps]\n" if $DEBUG;
open( $self->{'destfile_fh'}, '>', $destfile ) if $destfile ne '';
if ( $self->{'connected'} == 0 || !defined $self->{'httpsocket'} ) {
foreach my $addr ( @{ $self->{'hostIps'}{$host} } ) {
$self->{'connectedHostAddress'} = $addr; #must be set before we actually connect() in setupsocket
$self->{'connectedHostname'} = $host;
$self->_setupsocket( $host, $addr, $port ) || do {
print "...Unable to connect to host: ${addr}..." if !$self->{'hideOutput'};
$self->_remove_failed_mirror();
next;
};
$self->{'connectedHostFailCount'} = 0;
$self->{'connected'} = 1;
last;
}
if ( $self->{'connected'} == 0 ) {
print '...Failed...' if !$self->{'hideOutput'};
if ( $destfile ne '' ) {
close( $self->{'destfile_fh'} );
}
if ( defined $self->{'httpsocket'} ) {
close $self->{'httpsocket'};
}
return;
}
}
print '@' . $self->{'connectedHostAddress'} . '...' . '...connected...' if !$self->{'hideOutput'};
$url =~ s/\s/\%20/g;
my $req = "GET $url $http_protocol\r\n" . ( $http_protocol eq 'HTTP/1.0' ? "Connection: close\r\n" : '' ) . "Host: $host\r\n" . ( $headers ? $headers : '' ) . "Accept: text/html, text/plain, audio/mod, image/*, application/msword, application/pdf, application/postscript, text/sgml, */*;q=0.01\r\n\r\n";
my $quiet = $self->{'hideOutput'} || 0;
my $alarm = Cpanel::Alarm->new(60);
my $sent = 0;
eval {
local $SIG{'__DIE__'} = 'DEFAULT';
local $SIG{'ALRM'} = sub {
print "..Timeout on send.." if !$quiet;
die;
};
local $SIG{'PIPE'} = $SIG{'ALRM'};
send $self->{'httpsocket'}, $req, 0;
$sent = 1;
};
undef $alarm;
if ( !$sent ) {
$self->{'connected'} = 0;
$self->_remove_failed_mirror();
}
return $sent;
}
1;
} # --- END Cpanel/HttpRequest.pm
{ # --- BEGIN Cpanel/Alarm.pm
package Cpanel::Alarm;
sub new {
my $class = shift or die("Cpanel::Alarm::new is a method call.");
my $local_alarm_length = shift || 0;
my $action = shift;
my $self = bless( {}, $class );
$self->{'previous_alarm_time_left'} = $self->set($local_alarm_length);
$self->{'creation_time'} = $self->{'local_alarm_start'};
$self->{'previous_action'} = $SIG{'ALRM'};
if ( defined $action ) {
$SIG{'ALRM'} = $action;
}
return $self;
}
sub get_length { shift->{'local_alarm_length'} }
sub set {
my $self = shift or die;
$self->{'local_alarm_length'} = $_[0];
$self->{'local_alarm_start'} = time();
alarm( $_[0] ); # This is the total length of the alarm since new()
}
sub get_remaining {
my $self = shift or die;
return ( $self->{'local_alarm_length'} - ( time - $self->{'local_alarm_start'} ) );
}
sub DESTROY {
my $self = shift or return;
$SIG{'ALRM'} = $self->{'previous_action'};
alarm(0);
return if ( !$self->{'previous_alarm_time_left'} );
my $new_alarm = int( $self->{'previous_alarm_time_left'} - ( time() - $self->{'creation_time'} ) );
( $new_alarm <= 0 ) ? alarm(1) : alarm($new_alarm);
}
1;
} # --- END Cpanel/Alarm.pm
{ # --- BEGIN Cpanel/ArrayFunc.pm
package Cpanel::ArrayFunc;
use strict;
our $VERSION = '1.2';
sub reorder {
my $element = shift;
my $rARRY = shift;
@{$rARRY} = grep { $_ ne $element } @{$rARRY};
push( @{$rARRY}, $element );
}
sub uniq_from_arrayrefs {
my %seen;
my @final_values = ();
my @temp = map { @{$_} } grep { ref $_ eq 'ARRAY' } @_;
foreach my $value (@temp) {
unless ( defined( $seen{$value} ) ) {
push @final_values, $value;
$seen{$value} = 1;
}
}
return wantarray ? @final_values : [@final_values];
}
sub uniq_from_aoh {
my @uniq;
my %seen_refs_lookup;
HASH:
foreach my $hashref ( map { @{$_} } grep { ref $_ eq 'ARRAY' } @_ ) {
if ( ref $hashref ne 'HASH' ) {
push @uniq, $hashref;
}
else {
next HASH if exists $seen_refs_lookup{"$hashref"}; # double quotes for explicity not necessity
$seen_refs_lookup{"$hashref"}++; # double quotes for explicity not necessity
SEEN:
for my $seen_item (@uniq) {
next SEEN if ref $seen_item ne 'HASH'; # from first if(), this check saves us from having another array @seen
next SEEN if ( keys %{$seen_item} != keys %{$hashref} );
my $diff = 0;
SEEN_CHECK:
for my $seen_item_key ( keys %{$seen_item} ) {
if ( exists $hashref->{$seen_item_key} ) {
if ( $seen_item->{$seen_item_key} ne $hashref->{$seen_item_key} ) {
$diff++;
last SEEN_CHECK;
}
}
else {
$diff++;
last SEEN_CHECK;
}
}
if ( !$diff ) {
HASH_CHECK:
for my $hashref_key ( keys %{$hashref} ) {
if ( exists $seen_item->{$hashref_key} ) {
if ( $hashref->{$hashref_key} ne $seen_item->{$hashref_key} ) {
$diff++;
last HASH_CHECK;
}
}
else {
$diff++;
last HASH_CHECK;
}
}
}
next HASH if !$diff;
}
push @uniq, $hashref;
}
}
return wantarray ? @uniq : \@uniq;
}
sub any { $_ && return $_ for @_; 0 } #FG: RETURN THE FIRST TRUE VALUE, NOT 1
sub all { $_ || return $_ for @_; 1 } #FG: RETURN THE FALSE VALUE, IF ANY
sub none { $_ && return 0 for @_; 1 }
sub notall { $_ || return 1 for @_; 0 }
sub true {
scalar grep { $_ } @_;
}
sub false {
scalar grep { !$_ } @_;
}
sub first (&@) {
my $code = shift;
foreach (@_) {
return $_ if &{$code}(); # see comment at mapfirst() below
}
undef;
}
sub mapfirst (&@) {
my $code = shift;
foreach (@_) {
my $val = &{$code}($_); # see comment above
return $val if $val;
}
undef;
}
sub uniq (@) {
my %seen;
return grep { !$seen{$_}++ } @_;
}
sub shuffle {
my $element_to_shuffle = scalar @{ $_[0] };
my $element_to_exchange;
while ( $element_to_shuffle-- ) {
$element_to_exchange = int rand( $element_to_shuffle + 1 );
@{ $_[0] }[ $element_to_shuffle, $element_to_exchange ] = @{ $_[0] }[ $element_to_exchange, $element_to_shuffle ];
}
$_[0];
}
1;
} # --- END Cpanel/ArrayFunc.pm
{ # --- BEGIN Cpanel/URL.pm
package Cpanel::URL;
sub parse {
my $url = shift;
return {} if !$url;
my ( $host, $uri ) = ( split m{/+}, $url, 3 )[ 1, 2 ];
$uri = '/' . $uri;
$uri =~ s{[^/]+/+\.\./}{}g;
$uri =~ s/\.\.//g;
my ($filename) = $uri =~ m{/([^/]+)$};
return { 'file' => $filename, 'host' => $host, 'uri' => $uri };
}
1;
} # --- END Cpanel/URL.pm
{ # --- BEGIN Cpanel/PipeHandler.pm
package Cpanel::PipeHandler;
use Carp ();
my @FDLIST;
my $hasPIPE = 0;
my $lastpipe = 0;
my $pipetime = 0;
sub register_fds {
push( @FDLIST, @_ );
}
sub pipeBGMgr {
$hasPIPE++;
$pipetime = time();
if ( $hasPIPE == 1 ) {
$0 .= " - running in background (disconnected)";
foreach my $fd (@FDLIST) {
if ( fileno($fd) != 1 ) {
close($fd);
}
}
print STDERR "$0 [$$]: " . Carp::longmess('SIGPIPE received, process going into background');
open( STDOUT, ">", "/dev/null" );
$lastpipe = time();
}
elsif ( $pipetime > $lastpipe + 1 ) {
print STDERR "$0 [$$]: " . Carp::longmess('Fatal SIGPIPE received');
die "$$";
}
return;
}
sub pipeMgr {
$hasPIPE++;
if ( $hasPIPE > 2 ) { die "$$"; }
print STDERR "$0 [$$]: " . Carp::longmess('SIGPIPE received');
if ( $hasPIPE > 1 ) { die "$$"; }
return;
}
1;
} # --- END Cpanel/PipeHandler.pm
{ # --- BEGIN Cpanel/MirrorSearch.pm
package Cpanel::MirrorSearch;
use strict;
# use Cpanel::HttpTimer ();
# use Cpanel::OSSys ();
use Carp ();
our $VERSION = '2.1';
sub remove_mirror {
my %REQ = @_;
my $key = $REQ{'key'};
my $addr = $REQ{'addr'};
$key =~ s/\///g;
$addr =~ s/\///g;
my $homedir = ( getpwuid($>) )[7];
unlink("$homedir/.MirrorSearch/$key/pingtimes/$addr");
if ( open( my $mspeeds_fh, '+<', "$homedir/.MirrorSearch/$key/mirrors.speeds" ) ) {
my @mspeeds = grep( !/^\Q$addr\E=/, <$mspeeds_fh> );
seek( $mspeeds_fh, 0, 0 );
print {$mspeeds_fh} join( '', @mspeeds );
truncate( $mspeeds_fh, tell($mspeeds_fh) );
close($mspeeds_fh);
}
}
sub findfastest {
my (%REQ) = @_;
my $now = time();
my $days = $REQ{'days'};
my $key = $REQ{'key'};
my $count = $REQ{'count'};
my @urls = @{ $REQ{'urls'} };
my $urllist = join( "\n", @urls );
die 'You must specify the number of days to cache a host speed (days)' if ( $days eq '' );
die 'You must specify a key to use for this host (key)' if ( $key eq '' );
die 'You must specify a number of hosts to return (count)' if ( $count eq '' );
die 'You must specify an array of urls to try (urls)' if ( $urllist eq '' );
$key =~ s/\///g;
my %URLS;
foreach my $url (@urls) {
if ( $url =~ /(?:ftp|http)\:\/\/([^\/]+)\// ) {
my $host = $1;
$URLS{$host} = $url;
}
else {
Carp::confess 'Invalid Url';
}
}
my @GOODURLS = _fetchgoodhosts( $key, $days, $count, {}, \%URLS, 1 );
if ( $#GOODURLS >= ( $count - 1 ) ) {
if ( !$REQ{'quiet'} ) { print "...loaded mirror speeds from cache..."; }
return @GOODURLS;
}
my %PINGTIMES;
my $homedir = ( getpwuid($>) )[7];
mkdir $homedir . '/.MirrorSearch', 0700 if !-e $homedir . '/.MirrorSearch';
mkdir $homedir . '/.MirrorSearch/' . $key, 0700 if !-e $homedir . '/.MirrorSearch/' . $key;
my $need_pings = 0;
foreach my $host ( keys %URLS ) {
next if ( ( ( stat("$homedir/.MirrorSearch/${key}/pingtimes/${host}") )[9] + ( 86400 * $days ) ) > $now );
$need_pings++;
$PINGTIMES{$host} = 1000; #if ping is broken
}
if ($need_pings) {
my ( $read_fd, $write_fd ) = Cpanel::OSSys::pipe();
Cpanel::OSSys::write( $write_fd, $urllist, length($urllist) );
Cpanel::OSSys::close($write_fd);
if ( $REQ{'quiet'} ) {
require Cpanel::SafeRun::Errors;
Cpanel::SafeRun::Errors::saferunallerrors( '/usr/local/cpanel/scripts/MirrorSearch_pingtest', $key, $days, $read_fd );
}
else {
print "Testing connection speed for $key ($need_pings servers)...(using fast method)...";
system '/usr/local/cpanel/scripts/MirrorSearch_pingtest', $key, $days, $read_fd;
print ".Done\n";
}
Cpanel::OSSys::close($read_fd);
}
opendir( PT, "$homedir/.MirrorSearch/${key}/pingtimes" );
while ( my $pt = readdir(PT) ) {
next if ( $pt =~ /^\./ );
open( PINGTIMES, '<', "$homedir/.MirrorSearch/$key/pingtimes/$pt" );
$PINGTIMES{$pt} = <PINGTIMES>;
chomp $PINGTIMES{$pt};
close(PINGTIMES);
}
closedir(PT);
return _fetchgoodhosts( $key, $days, $count, \%PINGTIMES, \%URLS, 0, $REQ{'quiet'} );
}
sub _fetchgoodhosts {
my $key = shift;
my $days = shift;
my $count = shift;
my $PINGTIMES = shift;
my $URLS = shift;
my $cacheonly = shift;
my $quiet = shift || 0;
$key =~ s/\///g;
my $now = time();
my %MIRRORSPEED;
my %MIRRORTIME;
my @GOODURLS;
my $homedir = ( getpwuid($>) )[7];
if ( open my $mirrorspeeds_fh, '<', "$homedir/.MirrorSearch/${key}/mirrors.speeds" ) {
while (<$mirrorspeeds_fh>) {
chomp;
my ( $mirror, $speed, $lastcheck ) = split( /=/, $_ );
next if ( $lastcheck < ( $now - ( 86400 * $days ) ) );
$MIRRORTIME{$mirror} = $lastcheck;
$MIRRORSPEED{$mirror} = $speed;
}
close $mirrorspeeds_fh;
}
if ($cacheonly) {
foreach my $host ( sort { $MIRRORSPEED{$b} <=> $MIRRORSPEED{$a} } keys %MIRRORSPEED ) {
push( @GOODURLS, $$URLS{$host} ) if $$URLS{$host};
}
}
else {
my $usable_mirror_count = 0;
my %URLS_BY_SPEED;
foreach my $host ( sort { $$PINGTIMES{$a} <=> $$PINGTIMES{$b} } keys %{$PINGTIMES} ) {
if ( $$URLS{$host} ) {
if ( $MIRRORSPEED{$host} eq '' ) {
print "Ping:$$PINGTIMES{$host} (ticks) " if !$quiet;
( $MIRRORSPEED{$host} ) = _testmirrorspeed( $$URLS{$host}, $quiet );
$MIRRORTIME{$host} = $now;
}
if ( $MIRRORSPEED{$host} > 1 ) {
$URLS_BY_SPEED{ $$URLS{$host} } = $MIRRORSPEED{$host};
if ( ++$usable_mirror_count >= $count ) {
print "$count usable mirrors located\n" if !$quiet;
last;
}
}
}
}
if ( $usable_mirror_count < $count ) { print "...$usable_mirror_count usable mirrors located. (less then expected)..." if !$quiet; }
@GOODURLS = sort { $URLS_BY_SPEED{$b} <=> $URLS_BY_SPEED{$a} } keys %URLS_BY_SPEED;
if ($usable_mirror_count) { # dump the cache so we retry next time around
if ( open my $mirrorspeeds_fh, '>', "$homedir/.MirrorSearch/${key}/mirrors.speeds" ) {
foreach my $mirror ( keys %MIRRORSPEED ) {
print {$mirrorspeeds_fh} "${mirror}=$MIRRORSPEED{$mirror}=$MIRRORTIME{$mirror}\n";
}
close $mirrorspeeds_fh;
}
else {
warn "Unable to write $homedir/.MirrorSearch/${key}/mirrors.speeds: $!";
}
}
else {
unlink("$homedir/.MirrorSearch/$key/mirrors.speeds");
}
}
return @GOODURLS;
}
sub _testmirrorspeed {
my ( $url, $quiet ) = @_;
my ($host) = $url =~ /(?:ftp|http)\:\/\/([^\/]+)\//;
Carp::confess( 'Invalid Url : ' . $url ) if !$host;
my $file = ( split( /\//, $url, -1 ) )[-1] || Carp::confess( 'Invalid Url : ' . $url );
print "Testing connection speed to $host using pureperl..." if !$quiet;
my $RES = Cpanel::HttpTimer::timedrequest( 'url' => $url, 'nolocal' => 1, 'quiet' => 1 ); # fix hang on mirrors pointing to localhost
if ( !$RES->{'speed'} ) {
print "test failed...Done\n" if !$quiet;
return 0;
}
else {
print "($RES->{'speed'} bytes/s)...Done\n" if !$quiet;
return $RES->{'speed'};
}
}
1;
} # --- END Cpanel/MirrorSearch.pm
{ # --- BEGIN Cpanel/HttpTimer.pm
package Cpanel::HttpTimer;
use strict;
use Socket ();
# use Cpanel::OSSys ();
# use Cpanel::UrlTools ();
# use Cpanel::SocketIP ();
sub _setupsocket {
my $self = shift;
my $host = shift;
my $addr = shift;
if ( exists $self->{'httpsocket'}
&& $self->{'httpsocket'} ) {
close $self->{'httpsocket'};
}
my $connected = 0;
eval {
local $SIG{'__DIE__'} = 'DEFAULT';
local $SIG{'PIPE'} = $SIG{'ALRM'} = sub {
print STDERR "..Timeout on connect..";
die;
};
alarm 30;
my $proto = getprotobyname('tcp') || do {
print "..Cannot resolve protocol tcp..";
die;
};
socket( $self->{'httpsocket'}, &Socket::AF_INET, &Socket::SOCK_STREAM, $proto ) || do {
print "..Cannot create socket..";
die;
};
my $iaddr = Socket::inet_aton("$addr") || do {
print "...Unable to translate IP address for host: ${host}...";
die;
};
my $port = getservbyname( 'http', 'tcp' ) || do {
print "..Cannot lookup port for http..";
die;
};
my $sin = Socket::sockaddr_in( $port, $iaddr ) || do {
print "..ERROR: $! ..";
die;
};
connect( $self->{'httpsocket'}, $sin ) || die "Unable to connect: $!";
alarm 0;
$connected = 1;
};
alarm 0;
return $connected;
}
sub timedrequest {
my (%REQ) = @_;
my $url = $REQ{'url'};
my ( $host, $uri ) = Cpanel::UrlTools::extract_host_uri($url);
my $self = {};
bless $self;
my $addr;
if ( $host =~ m/(\d+\.\d+\.d\+\d+)/ ) {
$addr = $host;
}
else {
$addr = Cpanel::SocketIP::_resolveIpAddress($host);
if ( !$addr ) {
print "Unable to resolve $host. Check /etc/resolv.conf\n" if !$REQ{'quiet'};
return { 'status' => 0, 'speed' => 0 };
}
}
if ( exists $REQ{'nolocal'} && $addr eq '127.0.0.1' ) {
return { 'status' => 0, 'speed' => 0 };
}
if ( !$self->_setupsocket( $host, $addr ) ) {
return { 'status' => 0, 'speed' => 0 };
}
my $filename;
my $storef_fh;
if ( $REQ{'store'} ) {
if ( $REQ{'filename'} eq '' ) { $filename = Cpanel::UrlTools::urltofile($uri); }
else { $filename = $REQ{'filename'}; }
open( $storef_fh, '>', $filename );
}
my $clock_ticks = Cpanel::OSSys::sysconf('_SC_CLK_TCK');
my $bytes = 0;
my $start_time = ( Cpanel::OSSys::times() )[0];
eval {
local $SIG{'__DIE__'} = 'DEFAULT';
local $SIG{'PIPE'} = $SIG{'ALRM'} = sub {
print STDERR '..Timeout on recieve..' if !$REQ{'quiet'};
die;
};
alarm(20); #set alarm to prevent death
send $self->{'httpsocket'}, "GET $uri HTTP/1.0\r\nHost: $host\r\n\r\n", 0;
my $inheader = 1;
while ( readline $self->{'httpsocket'} ) {
alarm(20);
$bytes += length($_);
if ( !$inheader && $REQ{'store'} ) {
print {$storef_fh} $_;
}
if ( $inheader && (/^HTTP\/\d+\.\d+ (\d+)/) ) {
my $status = $1;
if ( $status =~ /^([345]\d+)/ ) {
print "Error $1 while fetching url ${url}\n" if !$REQ{'quiet'};
return { 'status' => 0, 'speed' => 0 };
}
}
if ( $inheader && ( /^\n$/ || /^\r\n$/ || /^$/ ) ) { $inheader = 0; }
}
alarm(0);
};
my $end_time = ( Cpanel::OSSys::times() )[0];
my $telap = ( ( $end_time - $start_time ) / $clock_ticks ) + 0.01; #clock drift
my $bps = sprintf( "%.2f", ( $bytes / $telap ) );
if ( $REQ{'store'} ) {
close($storef_fh);
}
return { 'status' => 1, 'speed' => $bps };
}
1;
} # --- END Cpanel/HttpTimer.pm
{ # --- BEGIN Cpanel/UrlTools.pm
package Cpanel::UrlTools;
use strict;
# use Cpanel::Logger ();
sub urltofile {
my ($url) = @_;
my (@URL) = split( /\//, $url );
return ( $URL[$#URL] );
}
sub buildurlfromuri {
my ( $rHOSTS, $uri ) = @_;
if ( substr( $uri, 0, 1 ) ne '/' ) { $uri = '/' . $uri; }
my (@URLS);
foreach my $host ( @{$rHOSTS} ) {
push @URLS, 'http://' . $host . $uri;
}
return wantarray ? @URLS : \@URLS;
}
sub extracthosts {
my ($rURLS) = @_;
my @HOSTS;
foreach my $url ( @{$rURLS} ) {
if ( $url =~ m/https?\:\/\/([^\/]+)\// ) {
push @HOSTS, $1;
}
else {
my $logger = Cpanel::Logger->new();
$logger->warn( 'Invalid URL: ' . $url );
}
}
return wantarray ? @HOSTS : \@HOSTS;
}
sub extract_host_uri {
my ($url) = @_;
if ( $url =~ m/https?\:\/\/([^\/]+)(\/.*)/ ) {
return ( $1, $2 );
}
else {
my $logger = Cpanel::Logger->new();
$logger->warn( 'Invalid URL: ' . $url );
}
}
sub build_http_get {
my ( $destination, %query ) = @_;
my @query_parts;
for my $query_key ( keys %query ) {
my $query_value = $query{$query_key};
$query_key = Cpanel::CPAN::URI::Escape::fast_uri_escape($query_key);
my $new_query_part;
if ( ref $query_value eq 'ARRAY' ) {
$new_query_part = join( '&', map { $query_key . '=' . Cpanel::CPAN::URI::Escape::fast_uri_escape($_) } @{$query_value} );
}
else {
$new_query_part = $query_key . '=' . Cpanel::CPAN::URI::Escape::fast_uri_escape($query_value);
}
push @query_parts, $new_query_part;
}
my $security_token_prefix =
( $destination =~ m{\A/} )
? $ENV{'cp_security_token'}
: q{};
return $security_token_prefix . $destination . '?' . join( '&', @query_parts );
}
1;
} # --- END Cpanel/UrlTools.pm
{ # --- BEGIN Cpanel/SocketIP.pm
package Cpanel::SocketIP;
use strict;
use Carp ();
use Socket ();
our $VERSION = '1.0';
sub _resolveIpAddress {
my ($host) = @_;
my @trueaddresses;
eval {
my @addresses = gethostbyname($host);
foreach ( @addresses[ 4 .. $#addresses ] ) {
push @trueaddresses, Socket::inet_ntoa($_);
}
};
if ( $#trueaddresses == -1 ) {
return wantarray ? @trueaddresses : 0;
}
else {
return wantarray ? @trueaddresses : $trueaddresses[0];
}
}
1;
} # --- END Cpanel/SocketIP.pm
{ # --- BEGIN Cpanel/Encoder/URI.pm
package Cpanel::Encoder::URI;
# use Cpanel::CPAN::URI::Escape ();
our $VERSION = '1.0';
*uri_encode_str = *Cpanel::CPAN::URI::Escape::fast_uri_escape;
sub uri_decode_str {
my $str = shift;
if ( defined $str ) {
$str =~ tr/+/ /;
$str =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
}
$str;
}
sub uri_encode_dirstr {
my @DI = split( /\//, shift );
for ( my $i = 0; $i <= $#DI; $i++ ) {
$DI[$i] = uri_encode_str( $DI[$i] );
}
return join( '/', @DI );
}
1;
} # --- END Cpanel/Encoder/URI.pm
{ # --- BEGIN Cpanel/CPAN/URI/Escape.pm
package Cpanel::CPAN::URI::Escape;
use strict;
our ( @ISA, @EXPORT, @EXPORT_OK, $VERSION );
our %escapes;
# use Cpanel::Utils ();
require Exporter;
@ISA = qw(Exporter);
@EXPORT = qw(uri_escape uri_unescape);
@EXPORT_OK = qw(%escapes uri_escape_utf8);
$VERSION = sprintf( "%d.%02d", q$Revision: 3.28 $ =~ /(\d+)\.(\d+)/ );
my %subst; # compiled patternes
sub _init {
return if (%escapes);
%escapes = map { chr($_) => sprintf( '%%%02x', $_ ) } ( 0 .. 255 );
}
sub fast_uri_escape {
_init();
*fast_uri_escape = \&real_fast_uri_escape;
goto \&real_fast_uri_escape;
}
sub real_fast_uri_escape {
my $text = shift;
defined $text && $text =~ s/([^A-Za-z0-9\-_\.~])/$escapes{$1}/g ? $text : $text; #Its 2% faster with a value and 9% fast with undef on a single line
}
sub uri_escape {
_init();
Cpanel::Utils::destroy_this_sub();
*uri_escape = \&real_uri_escape;
goto \&real_uri_escape;
}
sub real_uri_escape {
my ( $text, $patn ) = @_;
return undef unless defined $text;
if ( defined $patn ) {
unless ( exists $subst{$patn} ) {
( my $tmp = $patn ) =~ s,/,\\/,g;
eval "\$subst{\$patn} = sub {\$_[0] =~ s/([$tmp])/\$escapes{\$1} || _fail_hi(\$1)/ge; }";
Carp::croak("uri_escape: $@") if $@;
}
&{ $subst{$patn} }($text);
}
else {
$text =~ s/([^A-Za-z0-9\-_\.~])/$escapes{$1} || _fail_hi($1)/ge;
}
$text;
}
sub _fail_hi {
my $chr = shift;
require Carp;
Carp::croak( sprintf "Can't escape \\x{%04X}, try uri_escape_utf8() instead", ord($chr) );
}
sub uri_escape_utf8 {
my $text = shift;
if ( $] < 5.008 ) {
$text =~ s/([^\0-\x7F])/do {my $o = ord($1); sprintf("%c%c", 0xc0 | ($o >> 6), 0x80 | ($o & 0x3f)) }/ge;
}
else {
utf8::encode($text);
}
return uri_escape( $text, @_ );
}
sub fast_uri_unescape {
my $str = shift;
$str =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg if defined $str;
$str;
}
sub uri_unescape {
my $str = shift;
if ( @_ && wantarray ) {
my @str = ( $str, @_ ); # need to copy
foreach (@str) {
s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg;
}
return @str;
}
$str =~ s/%([0-9A-Fa-f]{2})/chr(hex($1))/eg if defined $str;
$str;
}
1;
} # --- END Cpanel/CPAN/URI/Escape.pm
{ # --- BEGIN Cpanel/Utils.pm
package Cpanel::Utils;
sub destroy_this_sub {
no strict 'refs';
my $calling_func = ( caller(1) )[3];
my ( $root, $function ) = $calling_func =~ m/^(.*::)([^:]+)$/;
undef *{$calling_func};
delete ${"$root"}{$function};
}
1;
} # --- END Cpanel/Utils.pm
{ # --- BEGIN Cpanel/Tar.pm
package Cpanel::Tar;
use Cwd ();
# use Cpanel::FindBin ();
# use Cpanel::SafeRun::Errors ();
# use Cpanel::Logger ();
my $logger = Cpanel::Logger->new();
my $tarcfg = {};
sub load_tarcfg {
return $tarcfg if exists $tarcfg->{'bin'};
$tarcfg->{'bin'} = ( Cpanel::FindBin::findbin('gtar') || Cpanel::FindBin::findbin('tar') ); # prefer gnutar
my ( $tar_version, $tar_help );
if ( exists $INC{'Cpanel/CachedCommand.pm'} ) {
eval q{
$tar_version = Cpanel::CachedCommand::cachedcommand( $tarcfg->{'bin'}, '--version' );
$tar_help = Cpanel::CachedCommand::cachedcommand( $tarcfg->{'bin'}, '--help' );
};
}
if ( !$tar_version ) { $tar_version = Cpanel::SafeRun::Errors::saferunallerrors( $tarcfg->{'bin'}, '--version' ); }
if ( !$tar_help ) { $tar_help = Cpanel::SafeRun::Errors::saferunallerrors( $tarcfg->{'bin'}, '--help' ); }
$tarcfg->{'working_env'} = $tar_version =~ m/\s1\.13\.25\s/m ? 0 : 1; # tar 1.13.25 will segfault with TAR_OPTIONS set
$tarcfg->{'no_same_owner'} = $tar_help =~ m/no-same-owner/m ? '--no-same-owner' : '-o';
$tarcfg->{'no_same_permissions'} = $tar_help =~ m/no-same-permissions/m ? '--no-same-permissions' : '';
$tarcfg->{'no_wildcards_match_slash'} = $tar_help =~ m/no-wildcards-match-slash/m ? 1 : 0;
$tarcfg->{'dash_T'} = $tar_help =~ m/\-T/ ? 1 : 0;
$tarcfg->{'dash_S'} = $tar_help =~ m/\-S/ ? 1 : 0;
$tarcfg->{'dash_j'} = $tar_help =~ m/\-j\W/ ? 1 : 0;
$tarcfg->{'dash_b'} = $tar_help =~ m/\-b/ ? 1 : 0;
$tarcfg->{'dashdash_utc'} = $tar_help =~ m/\-\-utc/ ? 1 : 0;
$tarcfg->{'type'} = 'gnu';
if ( $tar_help =~ m/bsdtar/ ) { $tarcfg->{'no_same_owner'} = '-o'; $tarcfg->{'dash_T'} = 1; $tarcfg->{'type'} = 'bsd'; }
my $system = $^O;
if ( $system =~ m/freebsd/i ) {
$tarcfg->{'dash_b'} = 0;
}
return $tarcfg;
}
sub checkperm {
my ($args) = @_;
my $tar_cfg = load_tarcfg();
my $tar_bin = $tarcfg->{'bin'};
my $caller = $args->{'caller'};
if ( !$tar_bin ) {
$logger->warn('Unable to locate suitable tar binary');
return ( 0, 'Unable to locate suitable tar binary' );
}
else {
my $cwd = '';
my $orig_tar_bin = $tar_bin;
if ( -l $tar_bin ) {
my $path = $tar_bin;
$path =~ s/[^\/]+$//;
$tar_bin = readlink $tar_bin;
$cwd = Cwd::fastcwd();
chdir $path or $logger->warn("Failed to chdir: $!"); # Failure here is OK as one of the following conditions will verify it.
}
if ( -e $tar_bin ) {
my $tar_perms = sprintf( '%04o', ( stat(_) )[2] & 01111 );
if ( $tar_perms ne '0111' ) {
if ( -o $tar_bin ) {
$logger->info("Setting permissions of $tar_bin to 0755");
if ( chmod( 0755, $tar_bin ) ) {
return ( 1, "Permissions of $tar_bin set to 0755" );
}
else {
$logger->warn("Failed to set 0755 permissions on $tar_bin: $!");
return ( 0, "Failed to set 0755 permissions on $tar_bin" );
}
}
else {
$logger->warn("Invalid permissions on $orig_tar_bin, tar should be executable by all users. Skipping permision change due to ownership.");
return ( 0, "Invalid permissions on $orig_tar_bin, tar should be executable by all users. Skipping permision change due to ownership." );
}
}
}
else {
$logger->warn("Unable to locate suitable tar binary! $orig_tar_bin does not exist.");
return ( 0, "Unable to locate suitable tar binary! $orig_tar_bin does not exist." );
}
}
return ( 1, 'Tar check successful' );
}
1;
} # --- END Cpanel/Tar.pm
{ # --- BEGIN Cpanel/Sync/Common.pm
package Cpanel::Sync::Common;
our $hasmd5 = 0;
eval { require Digest::MD5; $hasmd5 = 1; };
our $hasbzip2 = 0;
eval { require Compress::Bzip2; $hasbzip2 = 1; };
our $cpanelsync_excludes = '/etc/cpanelsync.exclude';
our $cpanelsync_chmod_excludes = '/etc/cpanelsync.no_chmod';
sub unbzip2 {
my ( $hasbzip2, $file ) = @_;
if ($hasbzip2) {
my $outfile = $file;
$outfile =~ s/\.bz2$//g;
if ( $outfile eq $file ) { return; }
my $out_fh;
open( $out_fh, '>', $outfile ) or die("cpanelsync: unbzip2: error opening $outfile");
my $bzip2 = Compress::Bzip2->new( '-verbosity' => 0 );
$bzip2->bzopen( $file, 'r' );
my $buf;
my $read;
while ( $read = $bzip2->bzread( $buf, 65535 ) ) { #65535 is about 35% faster then 512
if ( $read < 0 ) {
print "error: $Compress::Bzip2::bzerrno\n";
print STDERR "error: $Compress::Bzip2::bzerrno\n";
last;
}
syswrite( $out_fh, $buf, $read );
}
close($out_fh);
unlink($file);
}
else {
system( 'bzip2', '-df', $file );
}
}
sub get_excludes {
my ($file) = @_;
return if ( !-e $file || -z _ );
my @excludes;
open( EX, '<', $file ) or return;
while (<EX>) {
next if m/^\s*$/;
chomp;
s!/$!!;
push @excludes, $_;
}
close(EX);
return @excludes;
}
sub getmd5sum {
my ( $hr_MD5LIST, $root, $file, $hasmd5, %OPTS ) = @_;
return if ( !defined $file || $file eq '' );
my ( $skipcache, $mtime, $size ) = @OPTS{ 'skipcache', 'mtime', 'size' };
my $fullfile = $root . '/' . $file;
$fullfile =~ s{/\./}{/}g;
$fullfile =~ s{//+}{/}g;
if ( defined $OPTS{'is_normal_file'} ) {
if ( !$OPTS{'is_normal_file'} ) {
return '';
}
}
else {
return '' if ( !-f $fullfile );
}
( $size, $mtime ) = ( lstat($fullfile) )[ 7, 9 ] if ( !defined $size || !$mtime );
if ( !$skipcache
&& defined $hr_MD5LIST->{$file}{'size'}
&& $hr_MD5LIST->{$file}{'size'} == $size
&& defined $hr_MD5LIST->{$file}{'mtime'}
&& $hr_MD5LIST->{$file}{'mtime'} == $mtime
&& $hr_MD5LIST->{$file}{'md5'} ne '' ) {
$hr_MD5LIST->{$file}{'used'} = 1;
return ( $hr_MD5LIST->{$file}{'md5'} );
}
my $md5;
if ($hasmd5) {
my $fsize = ( stat($fullfile) )[7];
if ( open my $md5_fh, '<', $fullfile ) {
my $ctx = Digest::MD5->new;
my $buf = '';
my $len = 0;
while ( my $n = read( $md5_fh, $buf, 65535 ) ) {
$len += bytes::length($buf);
$ctx->add($buf);
}
$md5 = $ctx->hexdigest() if $len == $fsize;
}
else {
warn "Unable to open $fullfile: $!";
system 'stat', $fullfile; #give more debug info
return '';
}
}
else {
if ( $ENV{'PATH'} !~ m/\/sbin/ ) {
$ENV{'PATH'} .= ':/sbin';
}
if ( -e '/bin/md5sum'
|| -e '/usr/bin/md5sum'
|| -e '/usr/local/bin/md5sum' ) {
open( MD5, '-|' ) || exec 'md5sum', $fullfile;
}
else {
open( MD5, '-|' ) || exec 'md5', '-r', $fullfile;
}
while (<MD5>) {
chomp();
( $md5, undef ) = split( /\s+/, $_ );
last();
}
close(MD5);
}
@{ $hr_MD5LIST->{$file} }{ 'size', 'mtime', 'md5', 'used' } = ( $size, $mtime, $md5, 1 );
return $md5;
}
1;
} # --- END Cpanel/Sync/Common.pm
{ # --- BEGIN Cpanel/Update/Logger.pm
package Cpanel::Update::Logger;
use strict;
use warnings;
use Cpanel::SafeDir::MK;
use File::Basename;
use constant {
DEBUG => int 0,
INFO => int 25,
WARN => int 50,
ERROR => int 75,
FATAL => int 100,
};
our $VERSION = '1.0';
sub new {
my $class = shift;
my $self = shift || {};
ref($self) eq 'HASH' or die("hashref not passed to new");
bless( $self, $class );
$self->{'stdout'} = 1 if ( !defined $self->{'stdout'} );
eval { $self->set_logging_level( $self->{'log_level'} ); 1 }
or die("An invalid logging level was passed to new: $self->{'log_level'}");
$self->open_log if ( $self->{'logfile'} );
if ( exists $self->{'pbar'} and defined $self->{'pbar'} ) {
$self->{'pbar'} += 0;
$self->update_pbar( $self->{'pbar'} );
}
return $self;
}
sub open_log {
my $self = shift or die;
my $log_file = $self->{'logfile'};
my $logfile_dir = File::Basename::dirname($log_file);
my $created_dir = 0;
if ( !-d $logfile_dir ) {
Cpanel::SafeDir::MK::safemkdir( $logfile_dir, '0755', 2 );
$created_dir = 1;
}
open( my $fh, '>>', $log_file ) or die("Cannot open '$self->{logfile}' for append.");
my $orig_fh = select($fh);
$| = 1;
select($orig_fh);
$self->{'fh'} = $fh;
print {$fh} '-' x 100 . "\n";
print {$fh} "=> Log opened from $0 at " . localtime(time) . "\n";
$self->warn("Had to create directory $logfile_dir before opening log") if ($created_dir);
}
sub close_log {
my $self = shift or die;
my $fh = $self->{'fh'};
print {$fh} "=> Log closed " . localtime(time) . "\n";
warn("Failed to close file handle for $self->{'logfile'}") if ( !close $fh );
delete $self->{'fh'};
return;
}
sub DESTROY {
my $self = shift or die("DESTROY called without an object");
$self->close_log if ( $self->{'fh'} );
}
sub log {
my $self = shift or die("log called as a class");
ref $self eq __PACKAGE__ or die("log called as a class");
my $msg = shift or return;
my $stdout = shift;
$stdout = $self->{'stdout'} if ( !defined $stdout );
my $fh = $self->{'fh'};
foreach my $line ( split /[\r\n]+/, $msg ) {
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday, $isdst ) = localtime;
my $time_stamp = sprintf( "%04d%02d%02d.%02d%02d%02d", $year + 1900, $mon + 1, $mday, $hour, $min, $sec );
chomp $line;
print STDOUT "[$time_stamp] $line\n" if $stdout;
print {$fh} "[$time_stamp] $line\n" if $fh;
}
}
sub fatal {
my $self = shift or die;
return if ( $self->{'log_level_numeric'} > FATAL );
$self->log("***** FATAL: $_[0]");
}
sub error {
my $self = shift or die;
return if ( $self->{'log_level_numeric'} > ERROR );
$self->log("E $_[0]");
}
sub warning {
my $self = shift or die;
return if ( $self->{'log_level_numeric'} > WARN );
$self->log("W $_[0]");
}
sub info {
my $self = shift or die;
return if ( $self->{'log_level_numeric'} > INFO );
$self->log(" $_[0]");
}
sub debug {
my $self = shift or die;
return if ( $self->{'log_level_numeric'} > DEBUG );
$self->log("D $_[0]");
}
sub get_logging_level { shift->{'log_level'} }
sub set_logging_level {
my $self = shift or die;
my $log_level = shift;
$log_level = 'info' if ( !defined $log_level );
my $old_log_level = $self->get_logging_level;
if ( $log_level =~ m/^fatal/i ) {
$self->{'log_level'} = 'fatal';
$self->{'log_level_numeric'} = FATAL;
}
elsif ( $log_level =~ m/^error/i ) {
$self->{'log_level'} = 'error';
$self->{'log_level_numeric'} = ERROR;
}
elsif ( $log_level =~ m/^warn/i ) {
$self->{'log_level'} = 'warning';
$self->{'log_level_numeric'} = WARN;
}
elsif ( $log_level =~ m/^info/i ) {
$self->{'log_level'} = 'info';
$self->{'log_level_numeric'} = INFO;
}
elsif ( $log_level =~ m/^debug/i ) {
$self->{'log_level'} = 'debug';
$self->{'log_level_numeric'} = DEBUG;
}
else {
die("Unknown logging level '$log_level' passed to set_logging_level");
}
return $old_log_level;
}
sub get_pbar { shift->{'pbar'} }
sub increment_pbar {
my $self = shift or die;
return if ( !exists $self->{'pbar'} );
my $amount = shift || 1;
my $new_value = $self->{'pbar'} + $amount;
$self->update_pbar($new_value);
}
sub update_pbar {
my $self = shift or die;
return if ( !exists $self->{'pbar'} );
my $new_value = shift || 0;
if ( $new_value > 100 ) {
$self->debug("Pbar set to > 100 ($new_value)");
$new_value = 100;
}
$self->{'pbar'} = $new_value;
$self->info( $new_value . '% complete' );
}
1;
} # --- END Cpanel/Update/Logger.pm
{ # --- BEGIN Cpanel/Update.pm
package Cpanel::Update;
# use Cpanel::Logger ();
# use Cpanel::Config::Services ();
# use Cpanel::Config::Sources ();
# use Cpanel::HttpRequest ();
# use Cpanel::Update::Config ();
our $VERSION = '2.1';
our $cpanel_update_conf = $Cpanel::Update::Config::cpanel_update_conf;
my $logger = Cpanel::Logger->new();
sub _deprecate {
my ( $old, $new ) = @_;
$logger->invalid("Cpanel::Update::$old has been renamed to Cpanel::Update::$new. Please call it instead.");
}
sub get_transfers_sync_tree { _deprecate( 'get_transfers_sync_tree', 'get_transfers_sync_tier' ); goto &get_transfers_sync_tier; }
sub is_manual_update { _deprecate( 'is_manual_update', 'is_cpanel_update_enabled' ); goto &Cpanel::Update::is_cpanel_update_enabled; }
sub loadupdateconfig { _deprecate( 'loadupdateconfig', 'Config::load' ); goto &Cpanel::Update::Config::load; }
sub sanitize_config { _deprecate( 'sanitize_config', 'Config::sanitize' ); goto &Cpanel::Update::Config::sanitize; }
sub saveupdateconfig { _deprecate( 'saveupdateconfig', 'Config::save' ); goto &Cpanel::Update::Config::save; }
sub get_update_tier { _deprecate( 'get_update_tier', 'Config::get_tier' ); goto &Cpanel::Update::Config::get_tier; }
sub get_update_tree { _deprecate( 'get_update_tree', 'Config::get_tier' ); goto &Cpanel::Update::Config::get_tier; }
sub pkg_update_list { _deprecate( 'pkg_update_list', 'Config::pkg_update_list' ); goto &Cpanel::Update::Config::pkg_update_list; }
sub init_UP_update {
my ($update_name) = @_;
my ( $status, $disabled_name ) = Cpanel::Config::Services::service_enabled($update_name);
if ( !$status ) {
print "Updates for \xE2\x80\x9C$update_name\xE2\x80\x9D are disabled because \xE2\x80\x9C$disabled_name\xE2\x80\x9D exists.\n";
exit;
}
( $status, $disabled_name ) = Cpanel::Config::Services::service_enabled( $update_name . 'up' );
if ( !$status ) {
print "Updates for \xE2\x80\x9C$update_name\xE2\x80\x9D are disabled because \xE2\x80\x9C$disabled_name\xE2\x80\x9D exists.\n";
exit;
}
my $rUPCONF = Cpanel::Update::Config::load();
if ( $rUPCONF->{'SYSUP'} =~ m/never/i ) {
print "Updates for \xE2\x80\x9C$update_name\xE2\x80\x9D are disabled because \xE2\x80\x9CSYSUP\xE2\x80\x9D is set to \xE2\x80\x9Cnever\xE2\x80\x9D in $cpanel_update_conf.\n";
exit;
}
my $upper = uc($update_name) . 'UP';
if ( exists $rUPCONF->{ uc($update_name) . 'UP' } && $rUPCONF->{ uc($update_name) . 'UP' } =~ m/never/i ) {
print "Updates for \xE2\x80\x9C$update_name\xE2\x80\x9D are disabled because \xE2\x80\x9C$upper\xE2\x80\x9D is set to \xE2\x80\x9Cnever\xE2\x80\x9D in $cpanel_update_conf.\n";
exit;
}
}
sub safe_update_environment {
my $update_name = shift;
$SIG{'PIPE'} = 'DEFAULT';
if ( ( !$ENV{'LANG'} || $ENV{'LANG'} ne 'C' ) && -x '/usr/local/cpanel/scripts/' . $update_name . 'up' ) {
$ENV{'LANG'} = 'C';
$ENV{'LC_ALL'} = 'C';
exec '/usr/local/cpanel/scripts/' . $update_name . 'up', @ARGV;
exit;
}
if ( !-t STDERR ) {
open( STDERR, '>&STDOUT' );
}
$| = 1;
}
sub set_tier {
my $tier = shift || '';
$tier =~ tr/A-Z/a-z/;
my $rUPCONF = Cpanel::Update::Config::load();
my $old_tier = $rUPCONF->{'CPANEL'};
return $old_tier if ( !$tier or $old_tier eq $tier );
return $old_tier if ( !validate_tier($tier) );
$rUPCONF->{'CPANEL'} = $tier;
if ( Cpanel::Update::Config::save($rUPCONF) ) {
return $tier;
}
else {
$logger->warn( 'Unable to Cpanel::Update::Config::save() with ' . $tier );
return $old_tier;
}
}
sub validate_tier {
my ($tier) = @_;
my $update_config_ref = { 'CPANEL' => $tier };
my $CPSRC = Cpanel::Config::Sources::loadcpsources();
my $remote_tiers = eval { get_remote_tiers_info($CPSRC) };
return if ( !$remote_tiers );
my $version = get_remote_version_from_tier( $update_config_ref, $remote_tiers );
return if ( !$version );
return $version;
}
sub set_update_type {
my $rUPCONF = Cpanel::Update::Config::load();
my $default_rUPCONF = Cpanel::Update::Config::_default_preferences();
my $update_type = shift or return $rUPCONF->{'UPDATES'};
$update_type =~ tr/A-Z/a-z/;
$update_type =~ m/^(daily|manual|never)$/ or return $rUPCONF->{'UPDATES'};
$rUPCONF->{'UPDATES'} = $update_type;
Cpanel::Update::Config::save($rUPCONF);
return $update_type;
}
sub get_remote_version_from_tier {
my ( $update_config_ref, $remote_tiers ) = @_;
my $cpconf_tier = Cpanel::Update::Config::get_tier($update_config_ref);
return $cpconf_tier if ( $cpconf_tier =~ m{^\d+\.\d+\.\d+\.\d+$} );
if ( exists $remote_tiers->{$cpconf_tier} ) {
return $remote_tiers->{$cpconf_tier};
}
return;
}
sub get_remote_tiers_info {
my ($CPSRC) = @_;
my $HttpRequest_obj = Cpanel::HttpRequest->new( 'hideOutput' => 1 );
my $remote_tiers_info;
eval {
local $SIG{'ALRM'} = sub { die 'Timeout while fetching version information'; };
alarm 45;
$remote_tiers_info = $HttpRequest_obj->request(
'host' => $CPSRC->{'HTTPUPDATE'},
'url' => '/cpanelsync/TIERS',
);
};
alarm 0;
if ( $@ && $@ =~ m{ Timeout }xms ) { # Retry
sleep 2;
eval {
local $SIG{'ALRM'} = sub { die 'Timeout while fetching version information'; };
alarm 45;
$remote_tiers_info = $HttpRequest_obj->request(
'host' => $CPSRC->{'HTTPUPDATE'},
'url' => '/cpanelsync/TIERS',
);
};
alarm 0;
}
if ( !$remote_tiers_info ) {
$logger->die("Unable to retrieve tier version info: $!");
}
my %tiers;
foreach my $tier_definition ( split( /\n/, $remote_tiers_info ) ) {
next if ( $tier_definition =~ m/^\s*#/ ); # Skip commented lines.
next if ( $tier_definition !~ m/^\s*([^:\s]+)\s*:\s*(\S+)/ );
my ( $remote_tier, $remote_version ) = ( $1, $2 );
$tiers{$remote_tier} = $remote_version;
}
return \%tiers;
}
sub get_transfers_sync_tier {
return -f '/var/cpanel/transfers_devel' ? 'DEVEL' : 'PUBLIC';
}
1;
} # --- END Cpanel/Update.pm
{ # --- BEGIN Cpanel/Config/Services.pm
package Cpanel::Config::Services;
# use Cpanel::Services::Enabled ();
my %disable_files = (
'dns' => '/etc/nameddisable',
'ftp' => '/etc/ftpddisable',
'mail' => '/etc/imapdisable',
);
sub is_enabled { goto &service_enabled; }
sub service_enabled {
my $service = shift;
if ( !$service ) {
require Cpanel::Logger;
my $logger = Cpanel::Logger->new();
$logger->warn('No service specified to determine if enabled');
return wantarray ? ( 0, 'No service specified to determine if enabled' ) : 0;
}
my $disabled_service_name;
my $srv_en = 1; # default to enabled
my @srvcs = split( /\,/, $service );
my $msg;
foreach my $srvc (@srvcs) {
if ( !Cpanel::Services::Enabled::is_enabled($srvc) ) {
$disabled_service_name = $srvc;
$msg = defined( $disable_files{$srvc} ) ? $disable_files{$srvc} : '/etc/' . $disabled_service_name . 'disable';
$srv_en = 0;
last;
}
}
if ( $srv_en == 0 ) {
return wantarray ? ( 0, $msg ) : 0;
}
else {
return wantarray ? ( 1, $service . ' is enabled' ) : 1;
}
}
1;
} # --- END Cpanel/Config/Services.pm
{ # --- BEGIN Cpanel/Services/Enabled.pm
package Cpanel::Services::Enabled;
my %disable_files = (
'dns' => '/etc/nameddisable',
'ftp' => '/etc/ftpddisable',
'mail' => '/etc/imapdisable',
);
sub is_enabled {
my $service = shift;
if ( defined $disable_files{$service} ) {
return -e $disable_files{$service} ? 0 : 1;
}
elsif ( -e '/etc/' . $service . 'disable' || -e '/etc/' . $service . 'isevil' ) {
return 0;
}
return -1;
}
1;
} # --- END Cpanel/Services/Enabled.pm
{ # --- BEGIN Cpanel/Update/Config.pm
package Cpanel::Update::Config;
# use Cpanel::Config::LoadConfig ();
# use Cpanel::Config::FlushConfig ();
our $VERSION = '2.1';
our $cpanel_update_conf = '/etc/cpupdate.conf';
sub _default_preferences {
return {
'BANDMINUP' => 'inherit',
'BSDPORTS' => 'yes',
'COURIERUP' => 'inherit',
'CPANEL' => -e '/var/cpanel/dnsonly' ? 'stable' : 'release',
'DOVECOTUP' => 'inherit',
'EXIMUP' => 'inherit',
'FTPUP' => 'inherit',
'MYSQLUP' => 'inherit',
'NSDUP' => 'inherit',
'PYTHONUP' => 'inherit',
'RPMUP' => 'daily',
'SARULESUP' => 'daily',
'SYSUP' => 'daily',
'UPDATES' => 'daily',
};
}
sub upgrade_to_1130 {
my $rUPCONF = shift or die;
if ( !defined $rUPCONF->{'CPANEL'} ) {
$rUPCONF->{'CPANEL'} = _default_preferences()->{'CPANEL'};
}
my $changed = 0;
if ( $rUPCONF->{'CPANEL'} =~ m/daily/i ) {
$rUPCONF->{'CPANEL'} = 'release';
$rUPCONF->{'UPDATES'} = 'daily';
}
if ( $rUPCONF->{'CPANEL'} =~ s/^\s*manual-?//i or $rUPCONF->{'CPANEL'} =~ s/-manual\s*$//i ) {
$rUPCONF->{'CPANEL'} = 'stable' if ( !$rUPCONF->{'CPANEL'} ); # Stand alone manual implied stable
$rUPCONF->{'UPDATES'} = 'manual';
$changed = 1;
}
if ( $rUPCONF->{'CPANEL'} =~ m/dnsonly/i ) {
$changed = 1;
if ( $rUPCONF->{'CPANEL'} =~ m/beta/i ) {
$rUPCONF->{'CPANEL'} = 'current'; # DNSONLY-beta -> current
}
else {
$rUPCONF->{'CPANEL'} = 'stable' # DNSONLY -> stable
}
if ( !-e '/var/cpanel/dnsonly' ) { # Touch file.
open( my $fhtmp, '>', '/var/cpanel/dnsonly' );
close $fhtmp;
}
}
if ( $rUPCONF->{'CPANEL'} =~ m/^\s*never\s*$/i ) {
$rUPCONF->{'CPANEL'} = 'stable';
$rUPCONF->{'UPDATES'} = 'never';
$changed = 1;
}
if ( $rUPCONF->{'CPANEL'} =~ m/^edge|beta|nightly$/i ) {
$rUPCONF->{'CPANEL'} = 'edge';
$rUPCONF->{'UPDATES'} = 'manual';
$changed = 1;
}
if ( !defined $rUPCONF->{'UPDATES'} ) {
$rUPCONF->{'UPDATES'} = _default_preferences()->{'UPDATES'};
}
if ($changed) {
my $msg = 'cPanel detected a cpupdate.conf file which has not been updated to the 11.30 format. ';
$msg .= 'Your settings have been changed. Your new settings are as follows:' . "\n", $msg .= "CPANEL = $rUPCONF->{'CPANEL'}\n";
$msg .= "UPDATES = $rUPCONF->{'UPDATES'}\n";
$msg .= "Please review and update your settings in WHM (Update Preferences) if you have any concerns\n";
eval { require Cpanel::iContact; };
if ( exists $INC{'Cpanel/iContact.pm'} ) {
Cpanel::iContact::icontact(
'application' => '/etc/cpupdate.conf',
'subject' => '/etc/cpupdate.conf has changed',
'message' => $msg,
'level' => 3,
);
}
}
return $changed;
}
sub load {
my $default_rUPCONF = _default_preferences();
if ( !-e $cpanel_update_conf ) {
save($default_rUPCONF);
return wantarray ? %{$default_rUPCONF} : $default_rUPCONF;
}
my $rUPCONF = Cpanel::Config::LoadConfig::loadConfig($cpanel_update_conf);
my $changed = sanitize($rUPCONF);
$changed += upgrade_to_1130($rUPCONF) if ( !exists $rUPCONF->{'UPDATES'} );
foreach my $key ( keys %{$default_rUPCONF} ) {
if ( !exists $rUPCONF->{$key} ) {
$changed++;
$rUPCONF->{$key} = $default_rUPCONF->{$key};
}
}
save($rUPCONF) if $changed;
return wantarray ? %{$rUPCONF} : $rUPCONF;
}
sub sanitize {
my $conf_ref = shift;
return if ref $conf_ref ne 'HASH';
my $changed = 0;
foreach my $key ( keys %{$conf_ref} ) {
my $value = $conf_ref->{$key};
if ( !defined $value || $value =~ m/^\s+$/ ) {
$value = '';
$changed++;
}
else {
$changed++ if ( $value =~ s/[\n\r]//g );
$changed++ if ( $value =~ tr/A-Z/a-z/ );
}
$conf_ref->{$key} = $value;
}
return $changed;
}
sub save {
my $conf_ref = shift;
return if ref $conf_ref ne 'HASH';
sanitize($conf_ref);
return Cpanel::Config::FlushConfig::flushConfig( $cpanel_update_conf, $conf_ref, '=', undef, { 'sort' => 1 } );
}
sub get_tier {
my ($update_config_ref) = @_;
if ( !defined $update_config_ref ) {
$update_config_ref = load();
}
elsif ( ref $update_config_ref ne 'HASH' ) {
$update_config_ref = { 'CPANEL' => $update_config_ref };
}
return $update_config_ref->{'CPANEL'};
}
sub pkg_update_list {
return qw(bandmin courier dovecot exim ftp mysql nsd python);
}
sub is_permitted {
my $value = shift or return;
return if ( $value eq 'never' );
return if ( $ENV{'CPANEL_IS_CRON'} && $value eq 'manual' );
return 1;
}
sub get_update_type {
my $rUPCONF = load();
return $rUPCONF->{'UPDATES'};
}
1;
} # --- END Cpanel/Update/Config.pm
{ # --- BEGIN Cpanel/Config/FlushConfig.pm
package Cpanel::Config::FlushConfig;
# use Cpanel::SafeFile ();
# use Cpanel::Logger ();
our $VERSION = '1.2';
my $logger;
sub flushConfig {
my $filename = shift;
my $conf = shift;
my $delimiter = shift || '=';
my $header = shift;
my $opts = shift; #'sort'
if ( !$filename ) {
$logger ||= Cpanel::Logger->new();
$logger->warn('flushConfig requires valid filename as first argument');
return;
}
if ( !$conf || ref $conf ne 'HASH' ) {
$logger ||= Cpanel::Logger->new();
$logger->warn('flushConfig requires HASH reference as second argument');
return;
}
my $conflock = Cpanel::SafeFile::safeopen( \*CONF, '>', $filename );
if ( !$conflock ) {
$logger ||= Cpanel::Logger->new();
$logger->warn("Unable to write $filename: $!");
return;
}
print CONF ( $header ? ( $header . "\n" ) : '' ) .
join( "\n",
map {
$_ . ( defined $conf->{$_} ? ( $delimiter . $conf->{$_} ) : '' )
} ( $opts && $opts->{'sort'} ? (sort keys %{$conf}) : (keys %{$conf}) )
) .
"\n";
Cpanel::SafeFile::safeclose( \*CONF, $conflock );
return 1;
}
1;
} # --- END Cpanel/Config/FlushConfig.pm
{ # --- BEGIN Cpanel/Update/Blocker.pm
package Cpanel::Update::Blocker;
use strict;
use warnings;
# use Cpanel::Logger ();
our $update_blocks_fname = '/var/cpanel/update_blocks.config';
my $logger = Cpanel::Logger->new();
sub is_update_blocked {
my ( $installed_version, $target_version, $upconf_ref, $remote_tiers, $logger ) = @_;
my @messages;
my $is_fatal_block = 0;
if ( $upconf_ref->{'CPANEL'} eq '11.30' ) {
if ( exists $remote_tiers->{'11.32'} ) {
push(
@messages,
{
'message' => 'The 11.32 version of cPanel is available.',
'severity' => 'info'
}
);
}
}
my $license_expired = Cpanel::Update::Blocker::is_license_expired( $upconf_ref, $logger );
if ($license_expired) {
push(
@messages,
{
'message' => 'License problem detected. Unable to continue upgrade',
'severity' => 'fatal',
}
);
$is_fatal_block = 1;
}
my $version_blocked_message = is_version_change_blocked( $installed_version, $target_version );
if ($version_blocked_message) {
push(
@messages,
{
'message' => "You are not allowed to go from version $installed_version to $target_version: $version_blocked_message",
'severity' => 'fatal',
}
);
$is_fatal_block = 1;
}
if ( scalar @messages ) {
open( my $fh, '>', $update_blocks_fname )
or die("Unable to open $update_blocks_fname");
for my $msg_ref (@messages) {
print {$fh} "$msg_ref->{'severity'},$msg_ref->{'message'}\n";
}
close($fh);
}
else {
if ( -e $update_blocks_fname ) {
unlink($update_blocks_fname) or $logger->error("Unable to unlink $update_blocks_fname");
}
}
return $is_fatal_block;
}
sub is_license_expired {
my ( $upconf_ref, $logger ) = @_;
system( '/usr/bin/rdate', '-s', 'rdate.cpanel.net' );
my $now = time();
$logger->info("Checking license\n");
if ( $ENV{'CPANEL_BASE_INSTALL'} ) {
$logger->info("Installation detected in progress. Skipping license check.\n");
return;
}
if ( !-e '/usr/local/cpanel/version' && !-e '/usr/local/cpanel/cpanel' && !-e '/usr/local/cpanel/cpkeyctl' && !-e '/usr/local/cpanel/cpanel.lisc' ) {
$logger->warning("Your system appears unstable, bypassing license check.");
return;
}
if ( -e '/var/cpanel/dnsonly' ) {
return;
}
if ( !-e '/usr/local/cpanel/cpanel.lisc' ) {
$logger->error("No license file found. Your cPanel software will not function properly.\nPlease execute /usr/local/cpanel/cpkeyclt via the command line to rectify this issue.\n");
return 1;
}
if ( $upconf_ref->{'UPDATE_EXP_TIME'} ) {
$logger->warning("UPDATE_EXP_TIME=$upconf_ref->{'UPDATE_EXP_TIME'} in cpupdate.conf is blocking a proper license check.\n");
return if ( $now <= $upconf_ref->{'UPDATE_EXP_TIME'} );
$logger->warning("License is detected as expired based on this value");
}
else {
my $cplisc_fh;
if ( !open( $cplisc_fh, '<', '/usr/local/cpanel/cpanel.lisc' ) ) {
$logger->error("Unable to read license file: $!\nYou may need to execute /usr/local/cpanel/cpkeyclt via the command line to rectify this issue.\n");
return 1;
}
my $updates_expire_time = 0;
while ( my $line = readline($cplisc_fh) ) {
if ( $line =~ m/^updates_expire_time:\s+(\d+)/ ) {
$updates_expire_time = $1;
last;
}
}
close($cplisc_fh);
if ( !$updates_expire_time or $now <= ( $updates_expire_time - 86400 * 7 ) ) {
$logger->info("License file check complete\n");
return;
}
}
$logger->warning("\n");
$logger->warning("\n");
$logger->warning("\n");
$logger->warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
$logger->warning("\n");
$logger->warning("cPanel software update unable to continue. This server's license is\n");
$logger->warning("no longer eligible for software updates. In order to update this\n");
$logger->warning("server's cPanel software, you will need to purchase an update\n");
$logger->warning("extension for this server. Please contact customer service for more\n");
$logger->warning("information on software updates and update extensions.\n");
$logger->warning("\n");
$logger->warning("https://tickets.cpanel.net/submit/?reqtype=cs\n");
$logger->warning("\n");
$logger->warning("!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!!\n");
$logger->warning("\n");
$logger->warning("\n");
$logger->warning("\n");
$logger->warning("Server's license is no longer eligble for updates\nLicense check found regular cPanel & WHM license with expired updates\n");
return 1;
}
my $update_blocks_cache;
sub parse_update_blocks_file {
return $update_blocks_cache if defined $update_blocks_cache;
my @messages;
if ( -e $update_blocks_fname ) {
open( my $fh, '<', $update_blocks_fname ) or $logger->warning("Unable to read $update_blocks_fname");
while ( my $line = scalar <$fh> ) {
chomp($line);
my ( $severity, $message ) = split( ',', $line, 2 );
push(
@messages,
{
'message' => $message,
'severity' => $severity
}
);
}
close($fh);
$update_blocks_cache = \@messages;
return $update_blocks_cache;
}
return;
}
sub is_version_change_blocked {
my ( $current_version, $new_version ) = @_;
return if ( !$current_version );
return if ( $current_version eq $new_version );
my @current = split /\./, $current_version;
my @new = split /\./, $new_version;
return if ( $current[0] == $new[0] && $current[1] == $new[1] );
if ( $current[0] > $new[0] || ( $current[0] == $new[0] && $current[1] > $new[1] ) ) {
return "upcp/updatenow cannot perform a downgrade; try /usr/local/cpanel/scripts/downgrade_cpanel";
}
return;
}
1;
} # --- END Cpanel/Update/Blocker.pm
{ # --- BEGIN Cpanel/Usage.pm
package Cpanel::Usage;
my $g_prefs; # Ref to hash containing up to three boolean preferences, as follows:
$Cpanel::Usage::VERSION = '1.07';
sub version { # Reports our current revision number.
$Cpanel::Usage::VERSION;
}
sub wrap_options {
my $arg1 = $_[0];
$g_prefs = {};
if ( defined $arg1 && ( ref $arg1 ) =~ /\bHASH\b/ ) { # hash of preferences
$g_prefs = $arg1;
shift;
}
my ( $ar_argv, $cr_usage, $hr_opts ) = @_;
getoptions( usage( $ar_argv, $cr_usage ), $hr_opts );
}
sub usage {
my ( $ar_argv, $cr_usage ) = @_;
foreach my $arg (@$ar_argv) {
if ( $arg =~ /^-+(h|help|usage)$/ ) {
if ( defined($cr_usage) ) {
&$cr_usage();
}
return 1;
}
}
$ar_argv;
}
sub getoptions {
my ( $ar_cmdline, $hr ) = @_;
my $non_opt_arg_seen = "";
return $ar_cmdline if ( ref $ar_cmdline || "" ) !~ /\bARRAY\b/;
if ( !$#$ar_cmdline && $ar_cmdline->[0] eq "1" ) {
return 1;
}
unless ( defined $hr && ( ref $hr ) =~ /\bHASH\b/ ) {
print "Error: opts must be a hash reference\n";
return 2;
}
my $predefined = keys %{$hr};
my @cmdline_out = @$ar_cmdline; # save a copy of the arg array
if ( !$predefined ) {
if ( no_switches($ar_cmdline) ) {
my $i = 0;
foreach my $arg (@$ar_cmdline) {
$hr->{ $i++ } = $arg;
}
return "";
}
}
if ($predefined) {
foreach my $k ( keys %$hr ) {
if ( ref( $hr->{$k} ) =~ /^HASH/ ) {
foreach my $kk ( keys %{ $hr->{$k} } ) {
${ $hr->{$k}->{$kk} } = 0 unless ( ${ $hr->{$k}->{$kk} } );
}
}
else {
${ $hr->{$k} } = 0 unless ( ${ $hr->{$k} } );
}
}
}
for ( my $i = 0; $i <= $#$ar_cmdline; $i++ ) {
if ( $ar_cmdline->[$i] =~ /^-+(.+)$/ ) {
my $o = $1;
if ( "" ne $non_opt_arg_seen and $g_prefs->{'require_left'} ) {
print qq{Error: Preference require_left was specified, all opt args must therefore appear first on the command line; option "-$o" found after "$non_opt_arg_seen" violates this rule\n};
return 3;
}
my $eq_value = '';
if ( $o =~ /(.+?)=(.+)/ ) {
$o = $1;
$eq_value = $2;
$eq_value =~ s@^\s+@@;
$eq_value =~ s@\s+$@@;
}
if ( $g_prefs->{'strict'} && $predefined && !exists $hr->{$o} ) {
print qq{Error: While "strict" is in effect, we have encountered option --$o on the command line, an option that was not specified in the opts hash.\n};
return 4;
}
if ( # It is a "lone switch", that is, an
$eq_value eq '' && ( $i == $#$ar_cmdline
|| $ar_cmdline->[ $i + 1 ] =~ /^-+.+$/ )
) {
if ( ref( $hr->{$o} ) =~ /^HASH/ ) {
foreach my $kk ( keys %{ $hr->{$o} } ) {
if ($predefined) {
${ $hr->{$o}->{$kk} }++ if ( exists( $hr->{$o} ) );
}
}
}
else {
if ($predefined) {
${ $hr->{$o} }++ if ( exists( $hr->{$o} ) );
}
else {
$hr->{ _multihelp($o) }++;
}
}
}
else { # not a "lone switch"; the next arg might be the value
if ( ref( $hr->{$o} ) =~ /^HASH/ ) {
print "Error: A multi-level option can only be used when implicitly boolean (true), but you have attempted to use a multi-level option with an explicitly specified option argument.\n";
return 5;
}
if ( $eq_value ne '' ) { # Sorry, we already have a value for the switch
if ($predefined) {
${ $hr->{$o} } = $eq_value if ( exists( $hr->{$o} ) );
}
else {
$hr->{$o} = $eq_value;
}
}
else { # We have no value yet for the switch, so use next arg as the value
$cmdline_out[$i] = undef if $g_prefs->{'remove'};
++$i;
if ($predefined) {
${ $hr->{$o} } = $ar_cmdline->[$i]
if ( exists( $hr->{$o} ) );
}
else {
$hr->{$o} = $ar_cmdline->[$i];
}
}
}
$cmdline_out[$i] = undef if $g_prefs->{'remove'};
}
else { # It's a regular (non-hyphen-prefixed) arg, not an option arg
if ( "" eq $non_opt_arg_seen ) {
$non_opt_arg_seen = $ar_cmdline->[$i];
}
}
}
if ( $g_prefs->{'remove'} ) {
@cmdline_out = grep { defined } @cmdline_out;
@{$ar_cmdline} = @cmdline_out;
}
return ""; # aka 0, successful completion
}
sub _multihelp { # For internal use only
my $name = shift;
return $name =~ /^(h|help|usage)$/ ? 'help' : $name;
}
sub no_switches {
my $ar = shift;
return !grep { /^-+.+/ } @{$ar};
}
sub dump_args {
my $hr_opts = shift;
require Data::Dumper;
print Data::Dumper::Dumper($hr_opts);
}
1;
} # --- END Cpanel/Usage.pm
#!/usr/bin/perl
# cpanel - updatenow Copyright(c) 2011 cPanel, Inc.
# All rights Reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
#
# This is the authorative code used to sync /usr/local/cpanel
# Accept no imitators.
#
=head1 NAME
updatenow - updates /usr/local/cpanel from update servers
=head1 USAGE
# Called from upcp like this:
# --force tells updatenow to sync even if the version has not changed (similar to --sync). This setting will also bypass cron detection.
updatenow --upcp
# upcp will call this script like this if upcp is called with --force
updatenow --upcp --force
# If files which this script manages somehow get deleted, this will re-sync them but not upgrade.
# This is used by other cpanel scrypts as a safety measure.
updatenow.static --sync
# Used by cpanel installer prior to vps optimization
updatenow --no-check-perl
=head1 DESCRIPTION
NOTE: Under most circumstances, you should never call this script directly. B</usr/local/cpanel/scripts/upcp>, called with an optional [--force]
should be all you need. --sync is only intended to be called by other cpanel scripts when it appears that cpanel managed files
have been deleted.
The normal usage of this script is to update cpanel to the latest version of your TIER I<(see /etc/cpupdate.conf)>.
By default, no update is done by default if you are already on that version. The UPDATES setting in cpupdate.conf
is also honored when the environment variable CPANEL_IS_CRON is set (usually from upcp).
If an HTTPUPDATE= setting is present in /etc/cpsources.conf with a hostname, this will be the preferred source to sync from.
=cut
use strict;
use warnings;
BEGIN { ; }
# use Cpanel::Config::Sources ();
# use Cpanel::OSSys ();
# use Cpanel::SafeRun::Errors ();
# use Cpanel::Sync::v2 ();
# use Cpanel::Update ();
# use Cpanel::Update::Config ();
# use Cpanel::Update::Blocker ();
# use Cpanel::Update::Logger ();
# use Cpanel::Usage ();
use File::Basename ();
# Command line parameters passed:
my $is_sync_only = 0;
my $is_forced = 0;
my $is_upcp = 0;
my $downloaded = 0;
my $logfile_path = undef;
my $is_man = 0;
my $verbose = 0;
my $ulc = '/usr/local/cpanel';
my $is_dns_only = ( -e '/var/cpanel/dnsonly' );
Cpanel::Usage::wrap_options(
\@ARGV,
\&usage,
{
'sync' => \$is_sync_only,
'force' => \$is_forced,
'upcp' => \$is_upcp,
'downloaded' => \$downloaded,
'log' => \$logfile_path,
'man' => \$is_man,
'verbose' => \$verbose,
}
);
if ($is_man) {
exec( 'perldoc', $0 );
exit;
}
my $logger = Cpanel::Update::Logger->new( { 'logfile' => $logfile_path, 'stdout' => 1, 'log_level' => ( $verbose ? 'debug' : 'info' ) } );
my $upconf_ref = Cpanel::Update::Config::load();
unless ( $is_forced or Cpanel::Update::Config::is_permitted( $upconf_ref->{'UPDATES'} ) ) {
if ( $upconf_ref->{'UPDATES'} eq 'never' ) {
$logger->info('Cpanel updates are disabled.');
}
elsif ( $upconf_ref->{'UPDATES'} eq 'manual' ) {
$logger->info('Cpanel updates are disabled via cron.');
}
$logger->info("No sync will occur.");
exit;
}
#Case 47901 - updatenow needs to block anything but --sync if updates=never
if ( $upconf_ref->{'UPDATES'} =~ m/never/i ) {
$logger->info("Cpanel UPDATES is set to NEVER. Exiting.");
exit;
}
$logger->info("--sync passed on command line. No upgrade will be allowed\n")
if ($is_sync_only);
$logger->info("--force passed on command line. Upgrade will disgregard update config settings.\n")
if ($is_forced);
# Must call the program with at least --upcp, --force, or --sync
unless ( $is_forced or $is_sync_only or $is_upcp ) {
die "This script is not designed to be called directly. Please use /usr/local/cpanel/scripts/upcp\n";
}
my $CPSRC = Cpanel::Config::Sources::loadcpsources();
my $source_url = 'http://' . $CPSRC->{'HTTPUPDATE'} . '/cpanelsync/';
# Determine our current version if possible.
my $current_version = '';
if ( -e "$ulc/version" ) {
open( my $fh, "<", "$ulc/version" );
$current_version = <$fh> || '';
chomp $current_version;
close $fh;
# Remove all white space from the line.
$current_version =~ s/\s+//msg;
# We only care if /ULC/version is wrong if --sync was passed on command line.
die("/usr/local/cpanel/version has been altered! ($current_version) should be in the format ##.##.## or ##.##.##.##")
if ( $is_sync_only && $current_version !~ m/ ^ \d+ (?:[.]\d+){2,3} $ /x );
}
elsif ($is_sync_only) {
die "Cannot determine current cpanel version in order to sync. Please re-run with --force.";
}
# Get the remote tiers entries
my $remote_tiers = Cpanel::Update::get_remote_tiers_info($CPSRC);
# Determine our sync source base url.
my $new_version;
if ($is_sync_only) {
$new_version = $current_version;
}
else {
## invoked without --sync option (meaning this could be an upgrade), so must do license check
## and other "pinning" blocks
$new_version = Cpanel::Update::get_remote_version_from_tier( $upconf_ref, $remote_tiers );
if ( !$new_version ) {
$logger->error( "\$new_version is not defined! The tier '" . $upconf_ref->{'CPANEL'} . "' appears invalid!" );
die;
}
if ( !$is_forced && $new_version eq $current_version ) {
$logger->info("Version is the same on the server and locally ($current_version)\n");
exit;
}
}
$source_url .= $new_version;
# Base options we always use to sync with.
my %base_settings = ( 'url' => $source_url, 'logger' => $logger );
# Download and the target version's updatenow.static,
# unless we already *are* running that version
# (as indicated by the --downloaded command line option)
if ( !$downloaded ) {
# Download the new version of updatenow.static
my $static_script = Cpanel::Sync::v2->new( { %base_settings, 'source' => ['cpanel'], 'syncto' => $ulc } )->sync_updatenow_static;
if ( !$static_script ) {
die("Failed to download updatenow.static from server");
}
chmod( 0700, $static_script )
or die "Could not set downloaded updatenow.static to executable";
# Exec into the downloaded script with --downloaded
exec( '/usr/bin/perl', $static_script, '--downloaded', @ARGV )
or die "Failed to run downloaded updatenow.static";
}
###########################################################################################################################
#
# At this point, we know the running updatenow (static) is the same version as what we're upgrading to.
#
###########################################################################################################################
if ( Cpanel::Update::Blocker::is_update_blocked( $current_version, $new_version, $upconf_ref, $remote_tiers, $logger ) ) {
$logger->error("Cpanel updates are currently blocked. See $Cpanel::Update::Blocker::update_blocks_fname.\n");
exit 0;
}
# Determine binary tree source
my ( $kernel_type, $machine_arch ) = ( Cpanel::OSSys::uname() )[ 0, 4 ];
## Validate $machine_arch
if ( $kernel_type !~ m/^(freebsd|linux)$/i ) {
die("cPanel does not support the $kernel_type kernel");
}
$kernel_type = lc($kernel_type);
## Set arch stub
if ( $machine_arch =~ m/64/ ) {
$machine_arch = 'x86_64';
}
else {
$machine_arch = 'i386';
}
my $binary_sync_source = 'binaries/' . $kernel_type . '-' . $machine_arch;
# Sync everything.
$logger->info("Syncing $ulc\n");
Cpanel::Sync::v2->new( { %base_settings, 'source' => [ 'cpanel', $binary_sync_source ], 'syncto' => $ulc } )->script;
## case 46926: ensure remove themes from dnsonly machines; otherwise download them
if ($is_dns_only) {
require Cpanel::SafeDir;
for my $_dir (qw(x3 x3mail)) {
my $dir_path = "$ulc/base/frontend/$_dir";
if ( -e $dir_path ) {
my $rv = Cpanel::SafeDir::safermdir($dir_path);
unless ($rv) {
$logger->warning("Removal of theme directory $dir_path failed!\n");
}
}
}
}
else {
$logger->info("Syncing themes\n");
Cpanel::Sync::v2->new( { %base_settings, 'source' => ['x3'], 'syncto' => "$ulc/base/frontend/x3" } )->script;
Cpanel::Sync::v2->new( { %base_settings, 'source' => ['x3mail'], 'syncto' => "$ulc/base/frontend/x3mail" } )->script;
}
$logger->info("All files synced\n");
# Make sure /ULC/version is touched so upcp knows for sure we made some sort of attempt to sync
my $now = time;
utime $now, $now, "$ulc/version";
exit;
sub usage {
print qq{Usage: $0 [options]};
print qq{
Options:
--help Brief help message
--man Detailed help
Note: This script is designed to be run by cPanel programs directly ONLY. Please see upcp instead. It's probably what you want.
};
}