#!/usr/bin/perl BEGIN { # Supress load of all of these at earliest point. $INC{'Cpanel/SafeDir/MK.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Logger.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Time/Local.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/SafeRun/Errors.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/SafeRun/Simple.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/AccessIds.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/PwCache.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/SafeFile.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/RcsRecord.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/FindBin.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/SafeRun/InOut.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Debug.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/IO.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/AccessIds/SetUids.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/AccessIds/ReducedPrivileges.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/MysqlUtils.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Config/LoadConfig.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Hash.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/MD5/Lite.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/FileUtils/Write/Storable/Lazy.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/FileUtils/Write.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Fcntl/Constants.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/DbUtils.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/MysqlUtils/MyCnf/Basic.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Config/LoadConfig/Tiny.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/CachedCommand.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/StatCache.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/LoadFile.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/SafeRun/Timed.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/SafeRun/Env.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Env.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/QuotaMtime.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/CachedCommand/Utils.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/CachedCommand/Save.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/SocketIP.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Alarm.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/DIp/MainIP.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/Config/LoadWwwAcctConf.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/GlobalCache.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/DIp/ValidIP.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; $INC{'Cpanel/IP/Loopback.pm'} = '/usr/local/cpanel/./scripts/dump_innodbs.static'; } { # --- BEGIN Cpanel/SafeDir/MK.pm package Cpanel::SafeDir::MK; # use Cpanel::Logger (); sub safemkdir { my $dir = shift; my $mode = shift; my $errors = shift; my $created = shift; # aref, where to push paths of dirs created 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/^\.\///; } $dir =~ s|/{2,}|/|g; if ( $dir =~ m/(?:^|\/)\.\.(?:\/|$)/ ) { 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) ) ) { push @{$created}, $newdir if $created; $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/Logger.pm package Cpanel::Logger; # use Cpanel::Time::Local (); my $tree; our $VERSION = 1.2; 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 $saved_errno = $!; 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::Local::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 $saved_errno = $!; my ( $self, $message, $conf_hr ) = @_; # not appropriate for debug() : __Logger_pushback(@_); $self = $self->new() if !ref $self; $conf_hr ||= { 'force' => 0, 'backtrace' => 0, 'output' => 1, # Logger's debug level should output to STDOUT }; 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 ); $! = $saved_errno; return \%log; } sub info { my $saved_errno = $!; my ( $self, @list ) = &__Logger_pushback(@_); my %log = ( 'message' => $list[0], 'level' => 'info', 'service' => $self->find_progname(), 'output' => 1, # FB#59177: info level should output to STDOUT 'backtrace' => 0 ); $self->store_message( $log{'level'}, $log{'service'}, $log{'message'} ); $self->logger( \%log ); $! = $saved_errno; } # end of info sub warn { my $saved_errno = $!; 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 ); $! = $saved_errno; } # 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 _get_configuration_for_logger { my ( $self, $cfg_or_msg ) = @_; my $hr = ref($cfg_or_msg) eq 'HASH' ? $cfg_or_msg : { 'message' => $cfg_or_msg }; $hr->{'message'} ||= 'Something is wrong'; $hr->{'level'} ||= ''; $hr->{'service'} ||= $self->find_progname(); $hr->{'output'} ||= 0; if ( !exists $hr->{'backtrace'} ) { $hr->{'backtrace'} = 1; } $hr->{'use_no_files'} ||= 0; $hr->{'use_fullmsg'} ||= 0; return $hr; } sub _write { syswrite( $_[0], $_[1] ); } sub logger { my $saved_errno = $!; my ( $self, @list ) = &__Logger_pushback(@_); my $hr = $self->_get_configuration_for_logger( $list[0] ); my $time = Cpanel::Time::Local::localtime2timestamp(); my ( $usingstderr, $logfile ) = ( 0, ( $self->{'alternate_logfile'} ? $self->{'alternate_logfile'} : $std_log_file ) ); my $log_fh; my $msg = '[' . $time . '] ' . $hr->{'level'} . ' [' . $hr->{'service'} . '] ' . ( $hr->{'backtrace'} ? $self->backtrace( $hr->{'message'} ) : $hr->{'message'} . "\n" ); unless ( $hr->{'use_no_files'} ) { unless ( ( !-e $logfile || -w _ ) && open $log_fh, '>>', $logfile ) { ( $usingstderr, $log_fh ) = ( 1, \*STDERR ); } _write( $log_fh, $msg ); if ( $hr->{'level'} eq 'panic' || $hr->{'level'} eq 'invalid' ) { if ( open my $panic_fh, '>>', '/usr/local/cpanel/logs/panic_log' ) { _write( $panic_fh, "$time $hr->{level} [$hr->{'service'}] " . ( $hr->{'backtrace'} ? $self->backtrace( $hr->{'message'} ) : $hr->{'message'} . "\n" ) ); close $panic_fh; } } } if ( $hr->{'output'} ) { my $out = "$hr->{level} [$hr->{'service'}] $hr->{'message'}\n"; $out = $msg if $hr->{'use_fullmsg'}; no strict 'subs'; if ( $hr->{'output'} == 3 ) { _write( STDOUT, $out ); _write( STDERR, $out ); } elsif ( $hr->{'output'} == 1 && -t STDOUT ) { _write( STDOUT, $out ); } elsif ( $hr->{'output'} == 2 ) { _write( STDERR, $out ); } } if ( !$usingstderr ) { close $log_fh; } if ( ( $hr->{'level'} eq 'die' || $hr->{'level'} eq 'panic' || $hr->{'die'} ) ) { CORE::die "exit level [$hr->{'level'}] [pid=$$] ($hr->{'message'})\n"; # make sure we die if die is overwritten } $! = $saved_errno; } # 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/Local.pm package Cpanel::Time::Local; my $server_offset_string; our ( $timecacheref, $localtimecacheref ) = ( [ -1, '', -1 ], [ -1, '', -1 ] ); sub localtime2timestamp { my $time_supplied = shift; my $delimiter = shift; $delimiter = ' ' unless defined $delimiter; my $time = $time_supplied || time(); return $localtimecacheref->[2] if $localtimecacheref->[0] == $time && $localtimecacheref->[1] eq $delimiter; my $tz_offset = get_server_offset_as_offset_string($time_supplied); my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime $time; @{$localtimecacheref}[ 0, 1 ] = ( $time, $delimiter ); return ( $localtimecacheref->[2] = sprintf( '%04d-%02d-%02d' . $delimiter . '%02d:%02d:%02d %s', $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $tz_offset ) ); } sub get_server_offset_as_offset_string { my $time_supplied = shift; if ( $time_supplied || !defined $server_offset_string ) { my $time = $time_supplied || 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 ); my $offset_string = sprintf( '%+03d%02d', int( $gmoffset / 60 ), $gmoffset % 60 ); if ($time_supplied) { return $offset_string; } else { $server_offset_string = $offset_string; } } return $server_offset_string; } 1; } # --- END Cpanel/Time/Local.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/AccessIds.pm package Cpanel::AccessIds; use strict; # use Cpanel::PwCache (); # use Cpanel::Logger (); # use Cpanel::AccessIds::SetUids (); # use Cpanel::AccessIds::ReducedPrivileges (); our $VERSION = '1.2'; 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, $RC ); pipe( $PR, $CW ); if ( defined &IO::Handle::autoflush ) { $CW->autoflush(); } if ( my $pid = fork() ) { close $CW; eval { $RC = Storable::fd_retrieve($PR); }; close $PR; waitpid( $pid, 0 ); } else { close $PR; Cpanel::AccessIds::SetUids::setuids( $user, $group ); Storable::nstore_fd( [ $code->(@_) ], $CW ); close $CW; exit 0; } return defined $RC ? @{$RC} : undef; } sub loadfile_as_user { my $user = shift; my $file = shift; my ( $uid, $gid ) = ( Cpanel::PwCache::getpwnam($user) )[ 2, 3 ]; $logger ||= Cpanel::Logger->new(); if ( !defined $uid || !defined $gid ) { $logger->warn('Attempting to use non-existent user.'); return; } my $data; if ( $< != 0 && $uid != $> ) { $logger->panic("Attempting to setuid as a normal user $>"); } Cpanel::AccessIds::ReducedPrivileges::call_as_user( sub { if ( open my $rr_fh, '<', $file ) { local $/; $data = readline($rr_fh); close $rr_fh; } else { $logger->warn("Could not open file '$file' for reading: $!"); } 1; }, $uid, $gid ) || $logger->warn("Cpanel::AccessIds::ReducedPrivileges::call_as_user did not return true"); return $data; } 1; } # --- END Cpanel/AccessIds.pm { # --- BEGIN Cpanel/PwCache.pm package Cpanel::PwCache; use Carp (); # use Cpanel::SafeFile (); # use Cpanel::Debug (); # use Cpanel::IO (); 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; $value = $PW[0]; if ( open my $shadow_fh, '<', '/etc/shadow' ) { my $match = qr/\n\Q$value\E:/s; my $block; while ( !eof($shadow_fh) ) { return @PW if ( !defined( $block = Cpanel::IO::read_bytes_to_end_of_line( $shadow_fh, 65535 ) ) ); $block = "\n" . $block; # need a \n for our regex to match -- we use /s since is much faster then /m if ( $block =~ $match && $block =~ m/\n(\Q$value\E:[^\r\n]+)/s ) { my $matched_line = $1; my @SH = split( m/:/, $matched_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 ); return @PW; } } } return @PW; } 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, $block ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat('/etc/passwd') )[9] ), $_[3] ); if ( open( my $passwd_fh, '<', '/etc/passwd' ) ) { my $match = qr/\n(?:[^:]+:){$field}\Q$value\E(?=[:\n]+)[^\n]*/s; my $capture_match = qr/\n((?:[^:]+:){$field}\Q$value\E(?=[:\n]+)[^\n]*)/s; while ( !eof($passwd_fh) ) { return if ( !defined( $block = Cpanel::IO::read_bytes_to_end_of_line( $passwd_fh, 65535 ) ) ); $block = "\n" . $block; # need a \n for our regex to match -- we use /s since is much faster then /m if ( $block =~ $match && $block =~ $capture_match ) { my $pwline = $1; my @PW = split( m/:/, $pwline ); 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 || $value =~ tr/\0// || $pwkey =~ tr/\0// ); 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"; my $num_keys = scalar keys %{$pwcache_ref}; if ($num_keys) { if ( $num_keys > 10 ) { %{$pwcache_ref} = ( %{$pwcache_ref}, %{$cache_ref} ); } else { my $original_pwcache_ref = $pwcache_ref; $pwcache_ref = $cache_ref; foreach my $key ( keys %{$original_pwcache_ref} ) { $pwcache_ref->{$key} = $original_pwcache_ref->{$key}; } } } else { $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 || $value =~ tr/\0// ); 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 ( defined( $_ = 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/SafeFile.pm package Cpanel::SafeFile; $Cpanel::SafeFile::VERSION = '2.2'; use Symbol (); use Fcntl (); # qw( O_WRONLY O_EXCL O_CREAT ); my $MAGIC_FCNTL_VALUE; my $verbose; # initialized in safelock my $MAX_FLOCK_WAIT = 60; my $logger; 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 || $file =~ tr/\0// ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->warn('Invalid arguments'); return; } elsif ( !defined $fh ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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::File' && $fh_type ne 'IO::Handle' && $fh_type ne 'FileHandle' ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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 { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->warn('could not get a lock'); return; } } sub safeclose { my ( $fh, $lockref ) = @_; my $success = 1; if ( defined fileno $fh ) { $success = close $fh; } return safeunlock($lockref) && $success; } 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 || $file =~ tr/\0// ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->warn('safelock: Invalid arguments'); return; } $verbose ||= ( -e '/var/cpanel/safefile_verbose' ? 1 : -1 ); local $0 = "$0 - waiting for lockfile"; $MAGIC_FCNTL_VALUE ||= ( &Fcntl::O_WRONLY | &Fcntl::O_EXCL | &Fcntl::O_CREAT ); $lock_fh ||= Symbol::gensym(); $lockfile ||= _calculate_lockfile($file); while ( !sysopen( $lock_fh, $lockfile, $MAGIC_FCNTL_VALUE, 0600 ) && ++$attempts < 60 ) { if ( !$lock_file_dir ) { if ( !-w _getdir($lockfile) ) { return 1; } } local $0 = "$0 - waiting for lockfile"; $lockfile = _lock_wait($file); return if !$lockfile; } if ($lock_fh) { syswrite( $lock_fh, $$ . "\n" . $0 . "\n" ); return [ $lockfile, $lock_fh, $] < 5.008008 ? ( ( stat($lockfile) )[ 1, 9 ] ) : ( ( stat($lock_fh) )[ 1, 9 ] ) ]; } require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->warn( 'safelock: waited for lock ' . $attempts . ' times' ); return; } sub safeunlock { my $lockref = shift; if ( !$lockref ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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 ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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]; require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->warn("Invalid zero length lock file $lockref->[0] detected."); #->[0] = file return; } else { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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 _calculate_lockfile { ( $_[0] =~ /^[><]*(.*)/ )[0] . '.lock'; } 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; require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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 ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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 ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->info("[$$] Waiting on invalid lock $lockfile for $waittime seconds") if $verbose == 1; } elsif ( $lock_pid == $$ ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->invalid("[$$] Double locking detected on $file by self ($lock_name)"); return; } else { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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 ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->info("[$$] Lock file $lockfile now gone, try to acquire") if $verbose == 1; return $lockfile; } if ( $mtime == $start_lockfile_mtime ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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 require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $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/RcsRecord.pm package Cpanel::RcsRecord; # use Cpanel::SafeFile (); # use Cpanel::FindBin (); # use Cpanel::SafeRun::InOut (); our $SKIP_IF_MTIME_MATCHES = 2; our $VERSION = '4.4'; sub rcsrecord { my ( $file, $description, $skip_lock, $flags ) = @_; $description ||= ''; $flags ||= 0; my ( $uid, $gid, $size, $mtime ) = ( stat($file) )[ 4, 5, 7, 9 ]; if ( !$mtime || $file =~ tr/\0// ) { return 0; } 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 0; } } my $rcslock; if ( !$skip_lock ) { $rcslock = Cpanel::SafeFile::safelock($file); if ( !$rcslock ) { return 0; } } 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' ); umask(0077); exec( $rcs_bin, '-q', '-i', $file ); exit 1; } else { if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } return 0; } } else { if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } return 0; } } elsif ( $flags & $SKIP_IF_MTIME_MATCHES ) { if ( $mtime && $mtime <= ( stat(_) )[9] ) { if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } return 0; } } 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' ); umask(0077); chmod( ( stat($file) )[2] & 00777, $file . ',v' ); exec( $ci_bin, '-l', qq{-m"$safe_msg"}, $file ); exit 1; } else { #fork failed if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } return 0; } } else { if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } return 0; } 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 0; } } else { if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } return 0; } 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 0; } if ( my $rcs_pid = fork() ) { waitpid( $rcs_pid, 0 ); } elsif ( defined $rcs_pid ) { open( STDOUT, '>&STDERR' ); open( STDIN, '<', '/dev/null' ); umask(0077); chmod( ( stat($file) )[2] & 00777, $file . ',v' ); exec( $rcs_bin, '-q', '-o:' . $rev, $file ); exit 1; } else { if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } return 0; } } } if ($rcslock) { Cpanel::SafeFile::safeunlock($rcslock); } chown $uid, $gid, $file; return 1; } 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/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/IO.pm package Cpanel::IO; sub read_bytes_to_end_of_line { my $buffer; if ( read( $_[0], $buffer, $_[1] || 32768 ) ) { return $buffer . readline( $_[0] ); } return undef; } 1; } # --- END Cpanel/IO.pm { # --- BEGIN Cpanel/AccessIds/SetUids.pm package Cpanel::AccessIds::SetUids; # use Cpanel::PwCache (); our $VERSION = '1.2'; sub setuids { my ( $user, $group, @additional_groups ) = @_; my ( $uid, $gid ); if ( $< != 0 ) { require Cpanel::Logger; Cpanel::Logger->new()->die("setuids failed: Attempting to setuid as a normal user with RUID $<"); } if ( $> != 0 ) { require Cpanel::Logger; Cpanel::Logger->new()->die("setuids failed: 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 ) { require Cpanel::Logger; Cpanel::Logger->new()->die("setuids failed: Could not resolve UID ($uid) or GID ($gid)"); } $gid = int($gid); $) = join( ' ', $gid, $gid, ( map { /^\d+$/ ? $_ : ( ( getgrgid($_) )[2] || () ) } @additional_groups ) ); if ( $gid != int($)) ) { require Cpanel::Logger; Cpanel::Logger->new()->die("setuids failed: Error setting EGID ($gid) [$user]"); } $( = $gid; if ( $gid != $( ) { require Cpanel::Logger; Cpanel::Logger->new()->die("setuids failed: Error setting RGID ($gid) [$user]"); } $> = $uid; if ( $> != $uid ) { require Cpanel::Logger; Cpanel::Logger->new()->die("setuids failed: Error setting EUID ($uid) [$user]"); } $< = $uid; if ( $< != $uid ) { require Cpanel::Logger; Cpanel::Logger->new()->die("setuids failed: Error setting RUID ($uid) [$user]"); } return $uid; } 1; } # --- END Cpanel/AccessIds/SetUids.pm { # --- BEGIN Cpanel/AccessIds/ReducedPrivileges.pm package Cpanel::AccessIds::ReducedPrivileges; # use Cpanel::PwCache (); # use Cpanel::Logger (); sub new { my ( $class, $user, $group ) = @_; if ( $class ne __PACKAGE__ ) { my $logger = Cpanel::Logger->new(); $logger->die("Attempting to drop privileges as '$class'."); } _allowed_to_reduce_privileges(); my ( $uid, $gid ) = _uid_and_gid( $user, $group ); my $self = { 'uid' => $>, 'gid' => $), 'user' => $user, 'new_uid' => $uid, 'new_gid' => "$gid $gid", }; _reduce_privileges( $uid, $gid ); return bless $self; } sub DESTROY { my ($self) = @_; _allowed_to_restore_privileges( $self->{'new_uid'}, $self->{'new_gid'} ); _restore_privileges( $self->{'uid'}, $self->{'gid'} ); } sub call_as_user { my ( $code, $user, $group ) = @_; if ( ref $code ne 'CODE' ) { my $logger = Cpanel::Logger->new(); $logger->die("First argument is not a code reference."); } _allowed_to_reduce_privileges(); my ( $saved_uid, $saved_gid ) = ( $>, $) ); my ( $uid, $gid ); if ( defined $user && $user =~ /\A[0-9]+\z/ && defined $group && $group =~ /\A[0-9]+\z/ ) { ( $uid, $gid ) = ( $user, $group ); } else { ( $uid, $gid ) = _uid_and_gid( $user, $group ); } _reduce_privileges( $uid, $gid ); my ( $scalar, @list ); if (wantarray) { #list context @list = eval { $code->(); }; } elsif ( defined wantarray ) { #scalar context $scalar = eval { $code->(); }; } else { #void context eval { $code->(); }; } my $ex = $@; _restore_privileges( $saved_uid, $saved_gid ); die $ex if $ex; return wantarray ? @list : $scalar; } sub _allowed_to_reduce_privileges { if ( $< != 0 ) { my $logger = Cpanel::Logger->new(); $logger->die("Attempting to drop privileges as a normal user with RUID $<"); } if ( $> != 0 ) { my $logger = Cpanel::Logger->new(); $logger->die("Attempting to drop privileges as a normal user with EUID $>"); } } sub _reduce_privileges { my ( $uid, $gid ) = @_; my $orig_gid = $); $) = "$gid $gid"; if ( int($)) != $gid ) { my $logger = Cpanel::Logger->new(); $logger->die("Failed to change EGID to $gid"); } $> = $uid; if ( $> != $uid ) { $) = $orig_gid; my $logger = Cpanel::Logger->new(); $logger->die("Failed to change EUID to $uid"); } } sub _uid_and_gid { my ( $user, $group ) = @_; if ( !$user ) { my $logger = Cpanel::Logger->new(); $logger->die("Attempting to drop privileges to root."); } my ( $uid, $gid ); if ( $user =~ /\A[0-9]+\z/ ) { ( $uid, $gid ) = ( Cpanel::PwCache::getpwuid($user) )[ 2, 3 ]; } else { ( $uid, $gid ) = ( Cpanel::PwCache::getpwnam($user) )[ 2, 3 ]; } if ( defined $group && length $group ) { if ( $group =~ /\A[0-9]+\z/ ) { $gid = $group; } else { $gid = ( getgrnam($group) )[2]; } } if ( !$uid || !$gid ) { my $logger = Cpanel::Logger->new(); $logger->die("Not a valid user '$user'"); } return ( $uid, $gid ); } sub _allowed_to_restore_privileges { my ( $uid, $gid ) = @_; if ( $< != 0 ) { my $logger = Cpanel::Logger->new(); $logger->die("Attempting to restore privileges as a normal user with RUID $<"); } if ( $> != $uid ) { my $logger = Cpanel::Logger->new(); $logger->warn("EUID ($>) does not match expected reduced user ($uid)"); } if ( int $) != int $gid ) { my $logger = Cpanel::Logger->new(); $logger->warn("EGID ($)) does not match expected reduced user ($gid)"); } } sub _restore_privileges { my ( $saved_uid, $saved_gid ) = @_; $> = $saved_uid; if ( $> != $saved_uid ) { my $logger = Cpanel::Logger->new(); $logger->die("Failed to restore EUID to '$saved_uid'."); } $) = int($saved_gid) . " $saved_gid"; if ( int $) != int $saved_gid ) { my $logger = Cpanel::Logger->new(); $logger->die("Failed to change EGID to '$saved_gid'."); } } 1; } # --- END Cpanel/AccessIds/ReducedPrivileges.pm { # --- BEGIN Cpanel/MysqlUtils.pm package Cpanel::MysqlUtils; use strict; use IPC::Open3 (); # use Cpanel::SafeRun::Simple (); # use Cpanel::Logger (); # use Cpanel::Config::LoadConfig (); # use Cpanel::DbUtils (); # use Cpanel::MysqlUtils::MyCnf::Basic (); # use Cpanel::CachedCommand (); # use Cpanel::SocketIP (); # use Cpanel::DIp::MainIP (); our $VERSION = '2.3'; my $logger = Cpanel::Logger->new(); my $mysql_bin; my $mycnf; my $mysqldatadir; # must stay undefined my $mysql_version; # must stay undefined *find_mysql = \&Cpanel::DbUtils::find_mysql; *find_mysql_config = \&Cpanel::DbUtils::find_mysql_config; *find_mysql_fix_privilege_tables = \&Cpanel::DbUtils::find_mysql_fix_privilege_tables; *find_mysqldump = \&Cpanel::DbUtils::find_mysqldump; *find_mysqladmin = \&Cpanel::DbUtils::find_mysqladmin; *find_mysqlcheck = \&Cpanel::DbUtils::find_mysqlcheck; *find_mysqld = \&Cpanel::DbUtils::find_mysqld; *_getmydb_param = \&Cpanel::MysqlUtils::MyCnf::Basic::_getmydb_param; *_getmydbparm = \&Cpanel::MysqlUtils::MyCnf::Basic::_getmydbparm; *getmydbpass = \&Cpanel::MysqlUtils::MyCnf::Basic::getmydbpass; *getmydbhost = \&Cpanel::MysqlUtils::MyCnf::Basic::getmydbhost; *find_mysql_upgrade = \&Cpanel::DbUtils::find_mysql_upgrade; sub db_exists { my $db = $_[0]; if ( !is_remote_mysql() ) { my $datadir = getmysqldir() || return 0; return 1 if -e $datadir . '/' . $db; } $db = safesqlstring($db); my $ms = sqlcmd("SHOW DATABASES LIKE '$db';"); $ms =~ s/\s//g; return ( $ms =~ /\A\Q$db\E\z/ ) ? 1 : 0; } sub user_exists { my ($user) = @_; my $mysql_user = sqlcmd( "SELECT DISTINCT(user) FROM mysql.user WHERE user = '" . safesqlstring($user) . "'" ); return ( $mysql_user =~ /^\s*\Q$user\E\s*$/ ) ? 1 : 0; } sub decode_mysql_output { return $_[0] if $_[0] !~ tr/\\//; my ($string) = @_; $string =~ s/(?quote( $_[0] ) : _quote( $_[0] ); } sub getmysqldir { if ( defined $mysqldatadir ) { return $mysqldatadir; } my $mysqlhost = getmydbhost(); if ( $mysqlhost && $mysqlhost ne 'localhost' ) { return; } if ( $> == 0 ) { my $mysqladmin = Cpanel::DbUtils::find_mysqladmin(); my $mysqladmin_output_ref = Cpanel::CachedCommand::_cached_cmd( 'binary' => $mysqladmin, 'args' => ['variables'], 'mtime' => ( stat('/etc/my.cnf') )[9], 'ttl' => 86400, ); if ( ref $mysqladmin_output_ref ) { $$mysqladmin_output_ref =~ s/\|//g; if ( $$mysqladmin_output_ref =~ m/\s+datadir\s+(\S+)/ ) { $mysqldatadir = $1; } } } elsif ( $ENV{'REMOTE_PASSWORD'} ) { my $user = shift; my $mysql = find_mysql(); my $variable_output = Cpanel::CachedCommand::cachedcommand( $mysql, '-u' . $user, '-p' . $ENV{'REMOTE_PASSWORD'}, '-N', '-e', 'SHOW VARIABLES LIKE "datadir";' ); if ( $variable_output =~ m/[\|\s]+(\/.*)/ ) { $mysqldatadir = $1; $mysqldatadir =~ s/[\s\|]+$//g; } } if ( !$mysqldatadir ) { $logger->warn('Unable to determine MySQL data directory from mysqladmin variables.'); if ( -d '/var/lib/mysql' ) { $mysqldatadir = '/var/lib/mysql'; # Linux default } elsif ( -d '/var/db/mysql' ) { $mysqldatadir = '/var/db/mysql'; # FreeBSD default } else { $logger->warn('Failed to determine MySQL data directory. Please check the MySQL installation.'); return; } } while ( -l $mysqldatadir ) { $mysqldatadir = readlink $mysqldatadir; } return $mysqldatadir; } sub sqlcmd { goto \&Cpanel::MysqlUtils::NetMySQL::sqlcmd if ( exists $INC{'Cpanel/MysqlUtils/NetMySQL.pm'} && $Cpanel::MysqlUtils::NetMySQL::obj ); goto \&Cpanel::MysqlUtils::Connect::sqlcmd if ( exists $INC{'Cpanel/MysqlUtils/Connect.pm'} && $Cpanel::MysqlUtils::Connect::dbh ); my ( $cmds, $opts ) = @_; my ( @preargs, @args ); if ( $opts->{'nodb'} ) { $mycnf ||= Cpanel::MysqlUtils::MyCnf::Basic::get_dot_my_dot_cnf('root'); @preargs = ( '--defaults-extra-file=' . $mycnf ); } else { @args = ( $opts->{'db'} || 'mysql' ); } push @preargs, '-N' unless $opts->{'column_names'}; if ( $opts->{'multi_fallback_ok'} && ref $cmds ) { $cmds = join( ";\n", @{$cmds} ); } my $retcode; my $result = ''; my $did_select; foreach my $cmd ( ref $cmds ? ( @{$cmds} ) : ($cmds) ) { my ( $wtrfh, $rdrfh ); $mysql_bin ||= find_mysql(); my $pid = IPC::Open3::open3( $wtrfh, $rdrfh, $rdrfh, $mysql_bin, @preargs, '-B', '-A', @args ); print {$wtrfh} "$cmd\n"; # make sure we already get something back so we know mysql is up close($wtrfh); local $/; $result .= readline($rdrfh); waitpid( $pid, 0 ); $retcode = $?; $did_select = $cmd =~ /^\s*(?:show|select)/i ? 1 : 0; } $result =~ s/\n$// if length $result; #match Connect and NetMySQL output return '0E0' if ( !$did_select && !length $result && $retcode == 0 ); return $result; } sub is_remote_mysql { my $self = shift; my $host = getmydbhost('root'); return ( $host && $host ne 'localhost' ) ? 1 : 0; } sub mysqlversion { if ( defined $mysql_version ) { return $mysql_version; } my $mysql_config = find_mysql_config(); if ( defined $mysql_config && -x $mysql_config ) { my $mysql_config_version = Cpanel::CachedCommand::cachedcommand( $mysql_config, '--version' ); if ( $mysql_config_version =~ m/^\s*(\d+\.\d+)\.\d+/ ) { $mysql_version = $1; return $mysql_version; } } if ( $> == 0 || $ENV{'REMOTE_PASSWORD'} ) { my $user = shift; $mysql_bin ||= find_mysql(); my $queried_version = Cpanel::CachedCommand::cachedcommand( $mysql_bin, ( $> != 0 ? ( '-u' . $user, '-p' . $ENV{'REMOTE_PASSWORD'} ) : ( '-u', 'root' ) ), '-N', '-e', 'SELECT VERSION()' ); if ( $queried_version =~ m/^\s*(\d+\.\d+)\.\d+/ ) { $mysql_version = $1; return $mysql_version; } return; } } sub mysqldump_version { my $mysqldump = find_mysqldump(); my $version_line = Cpanel::CachedCommand::cachedcommand( $mysqldump, '-V' ); if ( $version_line =~ /(\d+\.\d+)\.\d+/ ) { return $1; } else { return; } } sub get_server { my $server = getmydbhost('root'); if ( !$server || $server eq 'localhost' ) { return Cpanel::DIp::MainIP::getmainserverip(); } else { return Cpanel::SocketIP::_resolveIpAddress($server); } } sub process_sql_file { my %opts = @_; if ( 'SCALAR' ne ref $opts{'out_ref'} ) { return 0, 'Invalid output reference provided'; } elsif ( !-r $opts{'file'} ) { return 0, "Unable to read file $opts{'file'}"; } elsif ( !$opts{'db'} ) { return 0, 'Invalid database name provided'; } $mysql_bin ||= find_mysql(); my @command_line = ( '-B', '-A', '--force', $opts{'db'} ); unshift @command_line, "--defaults-file=$opts{'defaults-file'}" if $opts{'defaults-file'}; my ( $msg, $rh ); if ( open( my $wh, '<', $opts{'file'} ) ) { my $e; if ( my $pid = open( my $rh, '-|' ) ) { local $/; ${ $opts{'out_ref'} } = readline($rh); waitpid $pid, 0; $e = $?; # save value before close close $rh; } elsif ( defined $pid ) { open( STDIN, '<&', $wh ); exec( $mysql_bin, @command_line ); die "Failed to execute $mysql_bin: @command_line"; } else { return 0, "Failed to fork(): $!"; } return 1 if !( $e >> 8 ); if ( $e == -1 ) { $msg = "Execution of $mysql_bin @command_line failed: $!"; } elsif ( $e & 127 ) { $msg = "Execution of $mysql_bin @command_line died with signal: " . ( $e & 127 ); } else { $msg = "Execution of $mysql_bin @command_line exited with value: " . ( $e >> 8 ); } close($wh); } else { $msg = "Failed to open $opts{'file'}: $!"; } return 0, $msg; } sub _set_binding { my ( $stmt, @bind ) = @_; if ( ( scalar @bind > 0 ) and ( ( $stmt =~ tr/?// ) == scalar @bind ) ) { return join( '', map { $_, @bind ? quote( shift @bind ) : () } split( m/\?/, $stmt ) ); } return $stmt; } sub _quote { return qq{'$_[0]'} if $_[0] !~ tr/\\\0\n\r'"\?\x1a//; my ($string) = @_; $string =~ s/\\/\\\\/g; # Escape forward slashes $string =~ s/\0/\\0/g; # Escape nulls $string =~ s/\n/\\n/g; # Escape newlines $string =~ s/\r/\\r/g; # Escape carriage returns $string =~ s/'/\\'/g; # Escape single quotes $string =~ s/"/\\"/g; # Escape double quotes $string =~ s/\x1a/\\Z/g; # Escape Ctrl-Z which is EOF in windows text mode return qq{'$string'}; } sub fetch_hashref { goto \&Cpanel::MysqlUtils::NetMySQL::fetch_hashref if ( exists $INC{'Cpanel/MysqlUtils/NetMySQL.pm'} && $Cpanel::MysqlUtils::NetMySQL::obj ); goto \&Cpanel::MysqlUtils::Connect::fetch_hashref if ( exists $INC{'Cpanel/MysqlUtils/Connect.pm'} && $Cpanel::MysqlUtils::Connect::dbh ); my ( $stmt, @bind ) = @_; $stmt = _set_binding( $stmt, @bind ); my $data = _runsqlcmd($stmt); my @mysql_data = split /\n/, $data; my @field_names = map lc, split( /\t/, shift(@mysql_data) ); # single tab only my @result_set; foreach my $row (@mysql_data) { my @row = map { decode_mysql_output($_) } split /\t/, $row; # single tab only my $results = {}; my $count = 0; foreach my $field (@field_names) { $results->{$field} = $row[$count]; $count++; } push @result_set, $results; } return \@result_set; } sub do_sql { my ( $stmt, @bind ) = @_; $stmt = _set_binding( $stmt, @bind ); my $data = _runsqlcmd($stmt); return $data =~ m/^\s*ERROR/ ? 0 : 1; } sub _runsqlcmd { sqlcmd( $_[0], { 'nodb' => 1, 'column_names' => 1 } ); } sub stream_mysqldump_to_filehandle { my ($args) = @_; my $mysqldump = find_mysqldump(); local $ENV{'MYSQL_PWD'} = $args->{'dbpass'}; my $wfh; my $rfh; my $pid = IPC::Open3::open3( $wfh, $rfh, ">/dev/null", $mysqldump, '--no-defaults', '-u', $args->{'dbuser'}, '-h', $args->{'dbhost'}, @{ $args->{options} }, $args->{'db'}, ); close($wfh); while ( my $data = <$rfh> ) { print { $args->{'filehandle'} } $data; } close($rfh); waitpid( $pid, 0 ); } sub build_mysql_exec_env { my ($env) = @_; $mysql_bin ||= find_mysql(); return sub { my ($sql) = @_; if ( my $mysql_pid = open( my $mysql_input_fh, '|-' ) ) { print {$mysql_input_fh} $sql; close($mysql_input_fh); } else { local $ENV{'MYSQL_PWD'} = $env->{'dbpass'}; exec $mysql_bin, '--no-defaults', '-h', $env->{'dbhost'}, '-u', $env->{'dbuser'}, $env->{'db'}; die "Could not exec($mysql_bin)"; } } } sub stream_filehandle_to_mysql { my ($args) = @_; $mysql_bin ||= find_mysql(); my $wfh; my $rfh; my $efh; my $pid = IPC::Open3::open3( $wfh, $rfh, ">/dev/null", $mysql_bin, '--force', $args->{'db'}, ); close($rfh); my $fh = $args->{'filehandle'}; while ( my $line = <$fh> ) { print {$wfh} $line; } print {$wfh} "FLUSH PRIVILEGES;\n"; close($wfh); waitpid( $pid, 0 ); } 1; } # --- END Cpanel/MysqlUtils.pm { # --- BEGIN Cpanel/Config/LoadConfig.pm package Cpanel::Config::LoadConfig; use IO::Handle (); # use Cpanel::Hash (); #checked for bloat 7/11/10 - jnk # use Cpanel::SafeFile (); #checked for bloat 7/11/10 - jnk # use Cpanel::FileUtils::Write::Storable::Lazy (); my $logger; my %COMMON_CACHE_NAMES = ( ':__^\s*[#;]__0__0__' => 'default_colon', ':\s+__^\s*[#;]__0__0__' => 'default_colon_any_space', '=__^\s*[#;]__0__0__skip_readable_check_____1' => 'default_skip_readable', '=__^\s*[#;]__0__0__' => 'default', '\s*[:]\s*__^\s*[#;]__0__0__' => 'default_colon_before_after_space' ); 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 || $file =~ tr/\0// ) { require Cpanel::Logger; $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 _ ) { require Cpanel::Logger; $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 ) : 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'} && !$arg_ref->{'keep_locked_open'} ) ) { 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 . '___' . ( exists $COMMON_CACHE_NAMES{$stringified_args} ? $COMMON_CACHE_NAMES{$stringified_args} : 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 ) { if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) { print STDERR __PACKAGE__ . "::loadConfig using cache_file:$cache_file\n"; } 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; } } else { if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) { print STDERR __PACKAGE__ . "::loadConfig NOT using cache_file:$cache_file\n"; } } close($cache_fh); } } $conf_ref = {} if !$load_into_conf_ref; my $conf_fh = IO::Handle->new(); my $conflock = Cpanel::SafeFile::safeopen( $conf_fh, ( $arg_ref->{'rw'} ? '+<' : '<' ), $file ); if ($conflock) { if ( !$limit && !$pretreatline && !$use_hash_of_arr_refs ) { local $/; my $cfg_txt = readline($conf_fh); Cpanel::SafeFile::safeclose( $conf_fh, $conflock ) if !$arg_ref->{'keep_locked_open'}; 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 ($@) { Cpanel::SafeFile::safeclose( $conf_fh, $conflock ); require Cpanel::Logger; $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_fh>; Cpanel::SafeFile::safeclose( $conf_fh, $conflock ) if !$arg_ref->{'keep_locked_open'}; 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 ($@) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->panic("Failed to generate parser code :: $parser_code"); exit 1; } $parser_code_ref->(); } } else { require Cpanel::Logger; 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 ) ) { require Cpanel::Logger; $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 ) ) { require Cpanel::Logger; $logger ||= Cpanel::Logger->new(); $logger->warn( 'Could not create dir "' . $dir . '"' ); } } } } unless ( Cpanel::FileUtils::Write::Storable::Lazy::write_storable_file( $cache_file, $conf_ref ) ) { unlink $cache_file; #outdated } } return $conf_ref, $conf_fh, $conflock if $arg_ref->{'keep_locked_open'}; 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 ) { require Cpanel::Logger; Cpanel::Logger::cplog( 'hashifying non-HASH reference', 'warn', __PACKAGE__ ); ${$conf_ref} = {}; $conf_ref = ${$conf_ref}; } else { require Cpanel::Logger; Cpanel::Logger::cplog( 'defined value encountered where reference expected', 'die', __PACKAGE__ ); } } return $conf_ref; } sub _sorted_hashref_txt { my ($hashref) = @_; return ( ( scalar keys %$hashref ) ? join( '_____', map { $_, ( ref $hashref->{$_} eq 'HASH' ? _sorted_hashref_txt( $hashref->{$_} ) : ref $hashref->{$_} eq 'ARRAY' ? join( '_____', @{ $hashref->{$_} } ) : defined $hashref->{$_} ? $hashref->{$_} : '' ) } sort keys %$hashref ) : '' ); #sort is important for order; } 1; } # --- END Cpanel/Config/LoadConfig.pm { # --- BEGIN Cpanel/Hash.pm package Cpanel::Hash; # use Cpanel::MD5::Lite (); sub get_fastest_hash { my $data_to_hash = shift; my $hashed_data = Cpanel::MD5::Lite::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; } local @INC = ( '/usr/local/cpanel', '/usr/local/cpanel/scripts' ); 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/MD5/Lite.pm package Cpanel::MD5::Lite; sub md5_hex { my ($data_to_hash) = @_; my $hashed_data; if ( exists $INC{'Digest/MD5.pm'} ) { eval '$hashed_data = Digest::MD5::md5_hex($data_to_hash);'; no warnings 'redefine'; *md5_hex = \&Digest::MD5::md5_hex; 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; } 1; } # --- END Cpanel/MD5/Lite.pm { # --- BEGIN Cpanel/FileUtils/Write/Storable/Lazy.pm package Cpanel::FileUtils::Write::Storable::Lazy; # use Cpanel::FileUtils::Write (); sub write_storable_file { my ( $file, $data, @pass ) = @_; if ( exists $INC{'Storable.pm'} ) { return Cpanel::FileUtils::Write::writefile( $file, sub { eval 'Storable::nstore_fd( $data, $_[0] );'; #must be quoted or it ends up in the stash warn $@ if $@; 1; }, @pass ); } return 0; } 1; } # --- END Cpanel/FileUtils/Write/Storable/Lazy.pm { # --- BEGIN Cpanel/FileUtils/Write.pm package Cpanel::FileUtils::Write; # use Cpanel::Fcntl::Constants (); our $MAX_SYMLINK_DEPTH = 1024; our $MAX_TMPFILE_CREATE_ATTEMPTS = 1024; sub writefile { #now ensures files can never be partially written for use with LoadConfig::Tiny my ( $filename_or_fh, $contents, $perms, $no_overwrite ) = @_; if ( !defined $filename_or_fh ) { exists $INC{'Carp.pm'} ? Carp::confess("writefile called with undefined filename_or_fh") : die("writefile called with undefined filename_or_fh"); } my $fh_type = ref $filename_or_fh; if ( $fh_type eq 'GLOB' || $fh_type eq 'IO::File' || $fh_type eq 'IO::Handle' || $fh_type eq 'FileHandle' ) { seek( $filename_or_fh, 0, 0 ); if ( ref $contents eq 'CODE' ) { if ( !$contents->($filename_or_fh) ) { warn "Failed to execute coderef: $!"; return; } } elsif ( !print {$filename_or_fh} ( ref $contents eq 'SCALAR' ? $$contents : $contents ) ) { warn "Failed to write: $!"; return; } truncate( $filename_or_fh, tell($filename_or_fh) ); return 1; } elsif ($fh_type) { exists $INC{'Carp.pm'} ? Carp::confess("writefile requires a filename or filehandle") : die("writefile requires a filename or filehandle"); } else { my $depth = 0; while ( -l $filename_or_fh && ++$depth <= $MAX_SYMLINK_DEPTH ) { $filename_or_fh = readlink($filename_or_fh); } } my $orig_umask; if ( defined $perms ) { $orig_umask = umask(); umask( $perms ^ 07777 ); } my ( $tmpfile, $attempts, $fh ) = ( '', 0 ); while ( !$tmpfile || ( ++$attempts < $MAX_TMPFILE_CREATE_ATTEMPTS && !sysopen( $fh, $tmpfile, $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_EXCL, defined $perms ? 00777 : 00666 ) ) ) { $fh = undef; $tmpfile = $filename_or_fh . '.tmp.' . rand(99999999); ($tmpfile) = $tmpfile =~ /(.*)/; #untaint } umask($orig_umask) if defined $orig_umask; if ( defined $fh ) { if ( ref $contents eq 'CODE' ) { if ( !$contents->($fh) ) { warn "Failed to execute coderef: $!"; close $fh; return; } } elsif ( !print {$fh} ( ref $contents eq 'SCALAR' ? $$contents : $contents ) ) { exists $INC{'Carp.pm'} ? Carp::cluck("Failed to write: $!") : warn "Failed to write: $!"; close $fh; return; } close $fh; if ($no_overwrite) { if ( link( $tmpfile, $filename_or_fh ) ) { unlink($tmpfile); return 1; } else { unlink($tmpfile); return 0; } } return 1 if rename( $tmpfile, $filename_or_fh ); } return; } 1; } # --- END Cpanel/FileUtils/Write.pm { # --- BEGIN Cpanel/Fcntl/Constants.pm package Cpanel::Fcntl::Constants; BEGIN { our ( $F_GETFL, $F_SETFL, $O_NONBLOCK, $O_RDWR, $O_WRONLY, $O_CREAT, $O_EXCL ); if ( $^O eq 'linux' || $^O eq 'freebsd' ) { $O_WRONLY = 1; $O_RDWR = 2; $F_GETFL = 3; $F_SETFL = 4; } 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/DbUtils.pm package Cpanel::DbUtils; # use Cpanel::FindBin (); # use Cpanel::Logger (); our $VERSION = 1.2; sub _find_bin { return Cpanel::FindBin::findbin( $_[0], ( defined $_[1] ? $_[1] : () ) ); } sub find_mysqladmin { _find_bin('mysqladmin'); } sub find_mysqlcheck { _find_bin('mysqlcheck'); } sub find_mysql { _find_bin('mysql'); } sub find_mysql_config { _find_bin('mysql_config'); } sub find_psql { _find_bin('psql'); } sub find_pg_ctl { _find_bin('pg_ctl'); } sub find_pg_dump { _find_bin('pg_dump'); } sub find_pg_restore { _find_bin('pg_restore'); } sub find_createdb { _find_bin('createdb'); } sub find_dropdb { _find_bin('dropdb'); } sub find_createuser { _find_bin('createuser'); } sub find_dropuser { _find_bin('dropuser'); } sub find_mysql_fix_privilege_tables { _find_bin('mysql_fix_privilege_tables'); } sub find_mysqldump { _find_bin('mysqldump'); } sub find_mysqld { _find_bin( 'mysqld', [ '/usr/bin', '/usr/sbin', '/usr/local/bin', '/usr/local/sbin', '/usr/libexec', '/usr/local/libexec' ] ); } sub find_mysql_upgrade { _find_bin('mysql_upgrade') } sub find_postmaster { _find_bin('postmaster') } 1; } # --- END Cpanel/DbUtils.pm { # --- BEGIN Cpanel/MysqlUtils/MyCnf/Basic.pm package Cpanel::MysqlUtils::MyCnf::Basic; our $VERSION = '1.1'; my %MYDBCACHE; my %HOMECACHE; sub get_mycnf { my $loadConfig = _getloadConfig_symbol(); return $loadConfig->( '/etc/my.cnf', {}, '\s*=\s*', '^\s*[#;]', 0, 1 ); } sub _getloadConfig_symbol { if ( exists $INC{'Cpanel/Config/LoadConfig.pm'} ) { return 'Cpanel::Config::LoadConfig::loadConfig'; } elsif ( exists $INC{'Cpanel/Config/LoadConfig/Tiny.pm'} ) { return 'Cpanel::Config::LoadConfig::Tiny::loadConfig'; } else { require Cpanel::Config::LoadConfig::Tiny; return 'Cpanel::Config::LoadConfig::Tiny::loadConfig'; } } sub _getmydb_param { my $param = shift; my $file = shift; if ( !$param || !$file ) { require Cpanel::Logger; my $logger = Cpanel::Logger->new(); $logger->warn('Missing arguments'); return; } my $value; my $file_mtime = ( stat($file) )[9]; if ( !exists $MYDBCACHE{$file} || $MYDBCACHE{$file}->{'mtime'} != $file_mtime ) { my $loadConfig = _getloadConfig_symbol(); $MYDBCACHE{$file} = { 'data' => scalar $loadConfig->( $file, {}, '\s*=\s*', '^\s*[#;]', 0, 1 ), 'mtime' => $file_mtime }; } if ( exists $MYDBCACHE{$file}->{'data'}->{$param} ) { $value = $MYDBCACHE{$file}->{'data'}->{$param}; $value =~ s{ (?: \A \s* ["'] | ["']\s* \z ) }{}xmsg; } return $value; } sub get_dot_my_dot_cnf { my $user = shift || 'root'; my $homedir; if ( exists $HOMECACHE{$user} ) { $homedir = $HOMECACHE{$user}; } elsif ( $INC{'Cpanel/PwCache.pm'} ) { $HOMECACHE{$user} = $homedir = ( Cpanel::PwCache::getpwnam($user) )[7]; } else { $HOMECACHE{$user} = $homedir = ( getpwnam($user) )[7]; } return $homedir . '/.my.cnf'; } sub _getmydbparm { my $param = shift; my $user = shift || 'root'; my $mycnf = get_dot_my_dot_cnf($user); return if !-e $mycnf; return _getmydb_param( $param, $mycnf ); } sub getmydbpass { my $password = _getmydbparm( 'pass', @_ ); if ($password) { return $password; } $password = _getmydbparm( 'password', @_ ); return $password; } sub getmydbhost { return _getmydbparm( 'host', @_ ); } 1; } # --- END Cpanel/MysqlUtils/MyCnf/Basic.pm { # --- BEGIN Cpanel/Config/LoadConfig/Tiny.pm package Cpanel::Config::LoadConfig::Tiny; 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; die('loadConfig requires valid filename') if !$file || $file =~ tr/\0//; 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 ) : 0; $conf_ref = {} if !ref $conf_ref; my $key_value_text = $use_reverse ? '1,0' : '0,1'; if ( open( my $fh, '<', $file ) ) { local $/; my ( $parser_code, $name, $value, $k, $v, $count ); if ( $use_hash_of_arr_refs || $pretreatline || $allow_undef_values ) { $parser_code = ' LINELOOP: foreach my $line (split(/\n/, readline($fh) )) { 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; } ) . ' }'; } elsif ($comment) { $parser_code = ' %{$conf_ref}=( %$conf_ref, map { ' . 'if (m{' . $comment . '}) { (); } else { ' . '($k,$v) = (split(m/' . $delimiter . '/, $_, 2))[' . $key_value_text . ']; ' . ( $limit ? ' $count++ < $limit && ' : '' ) . 'defined($v) ? ($k,$v) : (); ' . '} } split(/\n/, readline($fh) ) )'; } else { $parser_code = ' %{$conf_ref}=( %$conf_ref, map { ' . '($k,$v) = (split(m/' . $delimiter . '/, $_, 2))[' . $key_value_text . ']; ' . ( $limit ? ' $count++ < $limit && ' : '' ) . 'defined($v) ? ($k,$v) : () ' . '} split(/\n/, readline($fh) ) )'; } eval $parser_code; if ($@) { die "Failed to parse $file: $@"; } } return wantarray ? %{$conf_ref} : $conf_ref; } 1; } # --- END Cpanel/Config/LoadConfig/Tiny.pm { # --- BEGIN Cpanel/CachedCommand.pm package Cpanel::CachedCommand; # use Cpanel::StatCache (); # use Cpanel::Logger (); # use Cpanel::LoadFile (); # use Cpanel::SafeRun::Timed (); # use Cpanel::SafeRun::Env (); # use Cpanel::SafeRun::Simple (); # use Cpanel::FindBin (); # use Cpanel::QuotaMtime (); # use Cpanel::CachedCommand::Utils (); # use Cpanel::CachedCommand::Save (); # use Cpanel::Debug (); require Exporter; our @ISA = qw( Exporter ); our @EXPORT = qw( cachedcommand cachedmcommand2 cachedmcommand cachedquotacommand noncachedcommand ); our $VERSION = '2.4'; my %MEMORY_CACHE; my $logger = Cpanel::Logger->new(); sub _get_datastore_filename { goto &Cpanel::CachedCommand::Utils::_get_datastore_filename; } sub destroy { goto &Cpanel::CachedCommand::Utils::destroy; } sub store { goto &Cpanel::CachedCommand::Save::store; } sub _savefile { goto &Cpanel::CachedCommand::Save::_savefile; } sub _getquota_mtime { if ( exists $Cpanel::CONF{'disablequotacache'} && $Cpanel::CONF{'disablequotacache'} eq '1' ) { return ( time() + 10000 ); } return Cpanel::QuotaMtime::get_quota_mtime(); } sub _is_memory_cache_valid { my %OPTS = @_; my $datastore_file = $OPTS{'datastore_file'}; if ( !exists $MEMORY_CACHE{$datastore_file} ) { print STDERR "_is_memory_cache_valid: rejecting $datastore_file because it does not exist in memory.\n" if $Cpanel::Debug::level; return 0; } my $ttl = $OPTS{'ttl'}; my $mtime = $OPTS{'mtime'}; if ( !$ttl && $mtime && $MEMORY_CACHE{$datastore_file}->{'mtime'} == $mtime ) { print STDERR "_is_memory_cache_valid: accepting $datastore_file because it passes the mtime test.\n" if $Cpanel::Debug::level; return 1; } else { my $now = time(); if ( $ttl && $MEMORY_CACHE{$datastore_file}->{'mtime'} > ( $now - $ttl ) ) { print STDERR "_is_memory_cache_valid: accepting $datastore_file because it passes the ttl test.\n" if $Cpanel::Debug::level; return 1; } } print STDERR "_is_memory_cache_valid: rejecting $datastore_file because it not pass the ttl or mtime test.\n" if $Cpanel::Debug::level; delete $MEMORY_CACHE{$datastore_file}; return 0; } sub _is_cache_valid { my %OPTS = @_; my ( $datastore_file, $datastore_file_mtime, $datastore_file_size, $binary, $ttl, $mtime, $min_expire_time, $now ) = ( ( $OPTS{'datastore_file'} || '' ), ( $OPTS{'datastore_file_mtime'} || 0 ), ( $OPTS{'datastore_file_size'} || 0 ), ( $OPTS{'binary'} || '' ), ( $OPTS{'ttl'} || 0 ), ( $OPTS{'mtime'} || 0 ), ( $OPTS{'min_expire_time'} || 0 ), ( $OPTS{'now'} || 0 ) ); if ( !$datastore_file_mtime && !-e $datastore_file ) { print STDERR "_is_cache_valid: rejecting $datastore_file because it does not exist.\n" if $Cpanel::Debug::level; return 0; } if ( !$datastore_file_size || !$datastore_file_mtime ) { ( $datastore_file_size, $datastore_file_mtime ) = ( stat(_) )[ 7, 9 ]; } if ( $datastore_file_size <= 0 || $datastore_file_mtime <= 0 ) { print STDERR "_is_cache_valid: rejecting $datastore_file as mtime or size is zero.\n" if $Cpanel::Debug::level; return 0; } if ($binary) { if ( substr( $binary, 0, 1 ) ne '/' ) { $binary = Cpanel::FindBin::findbin( $binary, split( /:/, $ENV{'PATH'} ) ); } my ( $binary_mtime, $binary_ctime ) = Cpanel::StatCache::cachedmtime_ctime($binary); if ( ( $binary_mtime && $binary_mtime > $datastore_file_mtime ) || ( $binary_ctime && $binary_ctime > $datastore_file_mtime ) ) { print STDERR "_is_cache_valid: rejecting $datastore_file as binary ($binary) ctime or mtime is newer.\n" if $Cpanel::Debug::level; return 0; } } $now ||= time(); if ( $datastore_file_mtime > $now ) { print STDERR "_is_cache_valid: rejecting $datastore_file as it is from the future (time warp safety).\n" if $Cpanel::Debug::level; return 0; } elsif ( $min_expire_time && $datastore_file_mtime > ( $now - $min_expire_time ) ) { print STDERR "_is_cache_valid: accept $datastore_file (mtime=$datastore_file_mtime) as min_expire_time ($now - $min_expire_time) is older.\n" if $Cpanel::Debug::level; return 1; } elsif ( $mtime > $datastore_file_mtime ) { print STDERR "_is_cache_valid: rejecting $datastore_file because mtime ($mtime) is newer then datastore mtime ($datastore_file_mtime).\n" if $Cpanel::Debug::level; return 0; } elsif ( $ttl && ( $datastore_file_mtime + $ttl ) < $now ) { print STDERR "_is_cache_valid: rejecting $datastore_file as it has reached its time to live.\n" if $Cpanel::Debug::level; return 0; } print STDERR "_is_cache_valid: accepting $datastore_file as it passes all tests.\n" if $Cpanel::Debug::level; return 1; } sub invalidate_cache { my $ds_file = Cpanel::CachedCommand::Utils::_get_datastore_filename(@_); unlink $ds_file; delete $MEMORY_CACHE{$ds_file}; return; } sub _cached_cmd { my %OPTS = @_; my ( $cleanenv, $cleanenv2, $binary, $ttl, $mtime, $timer, $exact, $regexcheck, $args_hr, $min_expire_time ) = ( ( $OPTS{'cleanenv'} || 0 ), ( $OPTS{'cleanenv2'} || 0 ), ( $OPTS{'binary'} || '' ), ( $OPTS{'ttl'} || 0 ), ( $OPTS{'mtime'} || 0 ), ( $OPTS{'timer'} || 0 ), ( $OPTS{'exact'} || 0 ), ( $OPTS{'regexcheck'} || '' ), ( $OPTS{'args_hr'} || {} ), ( $OPTS{'min_expire_time'} || 0 ), ); my @AG; if ( ref $OPTS{'args'} eq 'ARRAY' ) { @AG = @{ $OPTS{'args'} }; } if ( substr( $binary, 0, 1 ) eq '/' && !-x $binary ) { return "$binary is missing or not executable"; } my @SAFEAG = @AG; if ( !$exact && $#SAFEAG > 3 ) { splice( @SAFEAG, 3 ); } my $datastore_file = Cpanel::CachedCommand::Utils::_get_datastore_filename( $binary, @SAFEAG ); if ( _is_memory_cache_valid( 'binary' => $binary, 'datastore_file' => $datastore_file, 'ttl' => $ttl, 'mtime' => $mtime ) ) { return $MEMORY_CACHE{$datastore_file}->{'contents'}; } my ( $datastore_file_size, $datastore_file_mtime ) = ( stat($datastore_file) )[ 7, 9 ]; my $data_mtime; my $res; if ( _is_cache_valid( 'binary' => $binary, 'datastore_file' => $datastore_file, 'datastore_file_mtime' => $datastore_file_mtime, 'ttl' => $ttl, 'mtime' => $mtime, 'min_expire_time' => $min_expire_time, ) ) { $res = Cpanel::LoadFile::loadfile_r( $datastore_file, { 'skip_exists_check' => 1 } ); $data_mtime = $datastore_file_mtime; } else { $data_mtime = time(); if ( int($timer) > 0 ) { $res = Cpanel::SafeRun::Timed::timedsaferun( $timer, $binary, @AG ); } elsif ($cleanenv) { $res = Cpanel::SafeRun::Env::saferun_r_cleanenv( $binary, @AG ); } elsif ($cleanenv2) { $res = Cpanel::SafeRun::Env::saferun_cleanenv2($args_hr); } else { $res = Cpanel::SafeRun::Simple::saferun_r( $binary, @AG ); } if ( !$regexcheck || $res =~ m/$regexcheck/ ) { print STDERR "_cached_command: writing datastore file: $datastore_file " . ( $regexcheck ? "regex_check: $regexcheck" : '' ) . "\n" if $Cpanel::Debug::level; Cpanel::CachedCommand::Save::_savefile( $datastore_file, $res ); } else { print STDERR "_cached_command: failed regex check NOT writing datastore file: $datastore_file " . ( $regexcheck ? "regex_check: $regexcheck" : '' ) . "\n" if $Cpanel::Debug::level; } } if ( ref $res ) { if ( $ttl && length($$res) < 32768 ) { $MEMORY_CACHE{$datastore_file} = { 'mtime' => $data_mtime, 'contents' => $res }; } return $res; } else { if ( $ttl && length($res) < 32768 ) { $MEMORY_CACHE{$datastore_file} = { 'mtime' => $data_mtime, 'contents' => \$res }; } return \$res; } } sub has_cache { my ( $ttl, $bin, @AG ) = @_; my @SAFEAG = @AG; if ( $#SAFEAG > 3 ) { splice( @SAFEAG, 3 ); } my $datastore_file = Cpanel::CachedCommand::Utils::_get_datastore_filename( $bin, @SAFEAG ); return ( _is_cache_valid( 'datastore_file' => $datastore_file, 'binary' => $bin, 'ttl' => $ttl ) ) ? 1 : 0; } sub cachedcommand { my $binary = shift; my @ARGS = @_; my $cache_ref = _cached_cmd( 'binary' => $binary, 'args' => \@ARGS ); if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; } return $cache_ref; } sub cachedcommand_multifile { my $test_file_ar = shift; my $binary = shift; my @ARGS = @_; my ( $mtime, $ctime ) = Cpanel::StatCache::cachedmtime_ctime($binary); if ( $ctime > $mtime ) { $mtime = $ctime; } foreach my $file (@$test_file_ar) { my @test_times = Cpanel::StatCache::cachedmtime_ctime($file); foreach my $new_time (@test_times) { if ( $new_time > $mtime ) { $mtime = $new_time; } } } my $cache_ref = _cached_cmd( 'binary' => $binary, 'args' => \@ARGS, 'mtime' => $mtime ); if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; } return $cache_ref; } sub cachedmcommand { my $ttl = shift; my $binary = shift; my @ARGS = @_; my $cache_ref = _cached_cmd( 'ttl' => $ttl, 'binary' => $binary, 'args' => \@ARGS ); if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; } return $cache_ref; } sub cachedmcommand_r_cleanenv { my $ttl = shift; my $binary = shift; my @ARGS = @_; my $cache_ref = _cached_cmd( 'cleanenv' => 1, 'ttl' => $ttl, 'binary' => $binary, 'args' => \@ARGS ); if ( ref $cache_ref ne 'SCALAR' ) { return \$cache_ref; } return $cache_ref; } sub cachedmcommand_cleanenv2 { my $ttl = shift; my $args_hr = shift; my @cmd = @{ $args_hr->{'command'} }; my $binary = shift @cmd; my @ARGS = @cmd; my $cache_ref = _cached_cmd( 'cleanenv2' => 1, 'ttl' => $ttl, 'binary' => $binary, 'args' => \@ARGS, 'args_hr' => $args_hr, ); return $cache_ref; } sub cachedmcommand_r { my $ttl = shift; my $binary = shift; my @ARGS = @_; my $cache_ref = _cached_cmd( 'ttl' => $ttl, 'binary' => $binary, 'args' => \@ARGS ); if ( ref $cache_ref ne 'SCALAR' ) { return \$cache_ref; } return $cache_ref; } sub cachedquotacommand { my $binary = shift; my @ARGS = @_; my $mtime = _getquota_mtime(); my $cache_ref = _cached_cmd( 'ttl' => 900, 'min_expire_time' => ( ( exists $Cpanel::CONF{'disablequotacache'} && $Cpanel::CONF{'disablequotacache'} eq '1' ) ? 0 : 300 ), 'binary' => $binary, 'args' => \@ARGS, 'mtime' => $mtime ); if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; } return; } sub cachedmcommand2 { my $arg_ref = shift; my $bin = $arg_ref->{'bin'}; my $ttl = $arg_ref->{'age'}; my $timer = $arg_ref->{'timer'}; my $exact = $arg_ref->{'exact'}; my $regexcheck = $arg_ref->{'regexcheck'}; my @AG = @{ $arg_ref->{'ARGS'} }; my $cache_ref = _cached_cmd( 'binary' => $bin, 'ttl' => $ttl, 'timer' => $timer, 'exact' => $exact, 'regexcheck' => $regexcheck, 'args' => \@AG ); if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; } return $cache_ref; } sub noncachedcommand { my ( $bin, @AG ) = @_; if ( substr( $bin, 0, 1 ) eq '/' && !-x $bin ) { return "$bin is missing or not executable"; } my $datastore_file = Cpanel::CachedCommand::Utils::_get_datastore_filename( $bin, $AG[0] ); if ( -e $datastore_file ) { unlink $datastore_file; } return Cpanel::SafeRun::Simple::saferun( $bin, @AG ); } sub retrieve { my %OPTS = @_; return Cpanel::LoadFile::loadfile( Cpanel::CachedCommand::Utils::_get_datastore_filename( $OPTS{'name'} ) ); } sub clear_memory_cache { %MEMORY_CACHE = (); } 1; } # --- END Cpanel/CachedCommand.pm { # --- BEGIN Cpanel/StatCache.pm package Cpanel::StatCache; our $VERSION = 0.3; my %STATCACHE; sub StatCache_init { } sub cachedmtime { return ( exists $STATCACHE{ $_[0] } ? $STATCACHE{ $_[0] }->[0] : ($STATCACHE{ $_[0] } = ( -r $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ] : [ 0, 0, 0 ] ) )->[0] ); } sub cachedmtime_size { return ( exists $STATCACHE{ $_[0] } ? @{ $STATCACHE{ $_[0] } }[ 0, 1 ] : @{ ( $STATCACHE{ $_[0] } = ( -r $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ] : [ 0, 0, 0 ] ) ) }[ 0, 1 ] ); } sub cachedmtime_ctime { return ( exists $STATCACHE{ $_[0] } ? @{ $STATCACHE{ $_[0] } }[ 0, 2 ] : @{ ( $STATCACHE{ $_[0] } = ( -r $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ] : [ 0, 0, 0 ] ) ) }[ 0, 2 ] ); } sub clearcache { %STATCACHE = (); return 1; } sub api2 { my $func = shift; my $API = { 'clearcache' => { 'func' => 'clearcache', }, }; return $API->{$func}; } 1; } # --- END Cpanel/StatCache.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/SafeRun/Timed.pm package Cpanel::SafeRun::Timed; our $VERSION = '1.1'; sub timedsaferun { my ( $timer, @PROGA ) = @_; return if ( substr( $PROGA[0], 0, 1 ) eq '/' && !-x $PROGA[0] ); my $output; my $complete = 0; my $pid; eval { local $SIG{'__DIE__'} = 'DEFAULT'; local $SIG{'ALRM'} = sub { die "Timeout while execting: " . join( ' ', @PROGA ) . "\n"; }; alarm($timer); if ( $pid = open( my $fh, '-|' ) ) { local $/; $output = readline($fh); close($fh); } elsif ( defined $pid ) { open( STDIN, '<', '/dev/null' ); exec(@PROGA); exit 1; } else { warn 'Error while executing: [' . join( ' ', @PROGA ) . ']: ' . $!; alarm(0); return; } $complete = 1; alarm 0; }; alarm 0; if ( !$complete && $pid && $pid > 0 ) { kill( 15, $pid ); #TERM sleep(1); # Give the process a chance to die 'nicely' kill( 9, $pid ); #KILL } return $output; } 1; } # --- END Cpanel/SafeRun/Timed.pm { # --- BEGIN Cpanel/SafeRun/Env.pm package Cpanel::SafeRun::Env; # use Cpanel::Env (); # use Cpanel::Logger (); our $VERSION = '1.0'; sub saferun_r_cleanenv { return saferun_cleanenv2( { 'command' => \@_, 'return_ref' => 1, 'cleanenv' => { 'http_purge' => 1 } } ); } sub saferun_cleanenv2 { my $args_hr = shift; return unless ( defined $args_hr->{'command'} && ref $args_hr->{'command'} eq 'ARRAY' ); my @command = @{ $args_hr->{'command'} }; my $return_reference = $args_hr->{'return_ref'}; my $error_output = $args_hr->{'errors'}; my %cleanenv_args = defined $args_hr->{'cleanenv'} && ref $args_hr->{'cleanenv'} eq 'HASH' ? %{ $args_hr->{'cleanenv'} } : (); my $check_cpanel_homedir_user = defined $args_hr->{'check_cpanel_homedir_user'} ? $args_hr->{'check_cpanel_homedir_user'} : 1; return if ( substr( $command[0], 0, 1 ) eq '/' && !-x $command[0] ); my $output; if ( !@command ) { Cpanel::Logger::logger( { 'message' => 'Cannot execute a null program', 'level' => 'warn', 'service' => __PACKAGE__, 'output' => 2, 'backtrace' => 1 } ); return \$output if $return_reference; return $output; } require Cpanel::Env; local ( $/, *PROG, *RNULL ); open( RNULL, '<', '/dev/null' ); my $pid = open( PROG, "-|" ); if ( $pid > 0 ) { $output = ; } elsif ( $pid == 0 ) { open( STDIN, '<&RNULL' ); if ($error_output) { open STDERR, '>&STDOUT'; } Cpanel::Env::cleanenv(%cleanenv_args); if ( $check_cpanel_homedir_user && ( !$Cpanel::homedir || !$Cpanel::user ) ) { ( $ENV{'USER'}, $ENV{'HOME'} ) = ( getpwuid($>) )[ 0, 7 ]; #do not use PwCache here } exec(@command); exit(1); # Not reached } else { Cpanel::Logger::logger( { 'message' => 'Could not fork new process', 'level' => 'warn', 'service' => __PACKAGE__, 'output' => 2, 'backtrace' => 1 } ); return \$output if $return_reference; return $output; } close(PROG); close(RNULL); waitpid( $pid, 0 ); return \$output if $return_reference; return $output; } 1; } # --- END Cpanel/SafeRun/Env.pm { # --- BEGIN Cpanel/Env.pm package Cpanel::Env; our $VERSION = '1.5'; my %SAFE_ENV_VARS; sub cleanenv { my %OPTS = @_; if ( !%SAFE_ENV_VARS ) { %SAFE_ENV_VARS = map { $_ => undef } ( 'ALLUSERSPROFILE', 'APPDATA', 'CLIENTNAME', 'COMMONPROGRAMFILES', 'COMPUTERNAME', 'COMSPEC', 'CPANEL_IS_CRON', # Denotes that one of the parent procs is upcp 'CPANEL_BASE_INSTALL', # Used to indicate new install process 'DOCUMENT_ROOT', 'FP_NO_HOST_CHECK', 'HOMEDRIVE', 'HOMEPATH', 'LD_ASSUME_KERNEL', 'LOGONSERVER', 'LONGUSERS', 'NEWWHMUPDATE', 'NUMBER_OF_PROCESSORS', 'OS', 'PATH', 'PATHEXT', 'PROCESSOR_ARCHITECTURE', 'PROCESSOR_IDENTIFIER', 'PROCESSOR_LEVEL', 'PROCESSOR_REVISION', 'PROGRAMFILES', 'PROMPT', 'SERVER_SOFTWARE', 'SESSIONNAME', 'SSH_CLIENT', 'SYSTEMDRIVE', 'SYSTEMROOT', 'TEMP', 'TERM', 'TMP', 'USERDOMAIN', 'USERNAME', 'USERPROFILE', 'WINDIR', ); } if ( defined $OPTS{'keep'} && ref $OPTS{'keep'} eq 'ARRAY' ) { @SAFE_ENV_VARS{ @{ $OPTS{'keep'} } } = undef; } if ( defined $OPTS{'delete'} && ref $OPTS{'delete'} eq 'ARRAY' ) { delete @SAFE_ENV_VARS{ @{ $OPTS{'delete'} } }; } delete @ENV{ map { exists $SAFE_ENV_VARS{$_} ? '' : $_ } keys %ENV }; if ( $OPTS{'http_purge'} ) { delete @ENV{ 'SERVER_SOFTWARE', 'DOCUMENT_ROOT' }; } } sub set_safe_path { $ENV{'PATH'} = '/usr/local/jdk/bin:/usr/kerberos/sbin:/usr/kerberos/bin:/usr/lib/courier-imap/sbin:/usr/lib/courier-imap/bin:/usr/local/sbin:/usr/local/bin:/sbin:/bin:/usr/sbin:/usr/bin:/usr/X11R6/bin:/usr/local/bin:/usr/X11R6/bin:/root/bin:/opt/bin'; } 1; } # --- END Cpanel/Env.pm { # --- BEGIN Cpanel/QuotaMtime.pm package Cpanel::QuotaMtime; my %MOUNT_POINTS; my $mount_points_mtime; sub get_quota_mtime { my $mtime_to_beat = shift; my $maxmtime = 0; my $fstab_mtime = ( stat('/etc/fstab') )[9]; if ( !$mount_points_mtime || $mount_points_mtime != $fstab_mtime ) { if ( open my $fh_fstab, '<', '/etc/fstab' ) { %MOUNT_POINTS = (); $mount_points_mtime = $fstab_mtime; my ( $mntpoint, $fstype, $options ); while ( my $line = <$fh_fstab> ) { next if ( $line =~ /^\s*#/ ); # skip comments (freebsd) if ( ( ( $mntpoint, $fstype, $options ) = $line =~ /\S+[\t\s]+(\S+)\s+(\S+)\s*(\S*)/ )[0] ) { if ( ( $options && $options !~ /quota/i ) || # do not check fses without quota ( $mntpoint =~ m/\/virtfs\// || $mntpoint eq 'swap' || $mntpoint !~ /^\// || $mntpoint =~ m/^\/(?:mnt\/|swap|proc|dev|boot|sys)/ ) || #why waste a stat() ? ( $fstype eq 'nfs' || $fstype eq 'coda' || $fstype eq 'smb' ) #do not check network filesystems as they may be slow ) { next; } $mntpoint =~ s/\/+$//; $MOUNT_POINTS{$mntpoint} = undef; } } close($fh_fstab); } } foreach my $quota_file ( map { $_ . '/aquota.user', $_ . '/quota.user' } keys %MOUNT_POINTS ) { next if !-e $quota_file; my $qmtime = ( stat(_) )[9]; return $qmtime if ( $mtime_to_beat && $qmtime > $mtime_to_beat ); if ( $qmtime > $maxmtime ) { $maxmtime = $qmtime; } } return $maxmtime; } 1; } # --- END Cpanel/QuotaMtime.pm { # --- BEGIN Cpanel/CachedCommand/Utils.pm package Cpanel::CachedCommand::Utils; # use Cpanel::PwCache (); # use Cpanel::QuotaMtime (); # use Cpanel::StatCache (); my ( $cached_datastore_myuid, $cached_datastore_dir ); sub destroy { my %OPTS = @_; my $cache_file = _get_datastore_filename( $OPTS{'name'}, ( $OPTS{'args'} ? @{ $OPTS{'args'} } : () ) ); if ( -e $cache_file ) { return unlink $cache_file; } else { return 1; } return; } sub _get_datastore_filename { my ( $bin, @args ) = @_; my $file = join( '_', $bin, @args ); $file =~ s/\//_/g; $file =~ /(.*)/; $file = $1; my $datastore_dir = _get_datastore_dir(); $datastore_dir =~ /(.*)/; $datastore_dir = $1; #untaint return $datastore_dir . '/' . $file; } sub _get_datastore_dir { my $myuid = $>; if ( defined $cached_datastore_dir && length $cached_datastore_dir > 1 && $myuid == $cached_datastore_myuid ) { return $cached_datastore_dir; } if ( $> != 0 && defined $Cpanel::homedir && $Cpanel::homedir ) { #issafe $cached_datastore_dir = $Cpanel::homedir; $cached_datastore_dir =~ /(.*)/; #untaint $cached_datastore_dir = $1; } else { $cached_datastore_dir = ( Cpanel::PwCache::getpwuid($>) )[7]; $cached_datastore_dir =~ /(.*)/; #untaint $cached_datastore_dir = $1; } if ( !Cpanel::StatCache::cachedmtime( $cached_datastore_dir . '/.cpanel' ) ) { mkdir $cached_datastore_dir . '/.cpanel'; mkdir $cached_datastore_dir . '/.cpanel/datastore'; } elsif ( !Cpanel::StatCache::cachedmtime( $cached_datastore_dir . '/.cpanel/datastore' ) ) { mkdir $cached_datastore_dir . '/.cpanel/datastore'; } $cached_datastore_myuid = $myuid; $cached_datastore_dir .= '/.cpanel/datastore'; return $cached_datastore_dir; } 1; } # --- END Cpanel/CachedCommand/Utils.pm { # --- BEGIN Cpanel/CachedCommand/Save.pm package Cpanel::CachedCommand::Save; # use Cpanel::CachedCommand::Utils (); # use Cpanel::Logger (); my $logger = Cpanel::Logger->new(); sub _savefile { my ( $filename, $content ) = @_; return if !defined $content; #should be able to store 0 $filename =~ s{//+}{/}g; my @path = split( /\//, $filename ); my $file = pop(@path); my $dir = join( '/', @path ); if ( ( stat($dir) )[4] != $> ) { $logger->warn("Unable to write datastore file: $filename: target directory does not match uid $>"); return; } if ( open my $fh, '>', $filename ) { chmod( 0600, $filename ); if ( ref $content eq 'SCALAR' ) { print {$fh} $$content; } else { print {$fh} $content; } close $fh; return 1; } return; } sub store { my %OPTS = @_; _savefile( Cpanel::CachedCommand::Utils::_get_datastore_filename( $OPTS{'name'} ), $OPTS{'data'} ); } 1; } # --- END Cpanel/CachedCommand/Save.pm { # --- BEGIN Cpanel/SocketIP.pm package Cpanel::SocketIP; use strict; use Carp (); use Socket (); # use Cpanel::Alarm (); our $VERSION = '1.2'; sub _resolveIpAddress { my $host = shift; my $timeout = shift; my @trueaddresses; my $alarm; eval { $alarm = Cpanel::Alarm->new( $timeout, sub { local $SIG{'__DIE__'}; die; } ) if $timeout; # this is not a fatal event so we shouldn't log the die. my %seen; my @addresses = gethostbyname($host); foreach ( @addresses[ 4 .. $#addresses ] ) { my $address = Socket::inet_ntoa($_); next if exists $seen{$address}; push @trueaddresses, $address; $seen{$address} = 1; } }; undef $alarm; if ( $#trueaddresses == -1 ) { return wantarray ? @trueaddresses : 0; } else { return wantarray ? @trueaddresses : $trueaddresses[0]; } } 1; } # --- END Cpanel/SocketIP.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'} || 'DEFAULT'; 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/DIp/MainIP.pm package Cpanel::DIp::MainIP; use strict; use Carp (); # use Cpanel::Logger (); # use Cpanel::Config::LoadWwwAcctConf (); # use Cpanel::Config::LoadConfig (); # use Cpanel::CachedCommand (); # use Cpanel::GlobalCache (); # use Cpanel::SafeRun::Simple (); # use Cpanel::StatCache (); # use Cpanel::DIp::ValidIP (); # use Cpanel::IP::Loopback (); # use Cpanel::LoadFile (); eval { local $SIG{__DIE__} = sub { warn "Storable is not available (attempting to ignore)."; }; require Storable; }; our $VERSION = '1.3'; *validip = *Cpanel::DIp::ValidIP::validip; my $configuredips; my $cachedmainip = q{}; my $cachedserverip = q{}; sub getmainip { return $cachedmainip if ( $cachedmainip ne '' ); my $wwwaccthash_ref = Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf(); my $addr = q{}; if ( exists $wwwaccthash_ref->{'ADDR'} ) { if ( $wwwaccthash_ref->{'ADDR'} eq '' ) { return ( $cachedmainip = getmainserverip() ); } elsif ( !validip( $wwwaccthash_ref->{'ADDR'} ) && -x '/sbin/ifconfig' ) { return ( $cachedmainip = getmainserverip() ); } elsif ( !-x '/sbin/ifconfig' ) { return ( $cachedmainip = $wwwaccthash_ref->{'ADDR'} ); } $addr = $wwwaccthash_ref->{'ADDR'}; } if ( !-x '/sbin/ifconfig' ) { my $logger = Cpanel::Logger->new(); $logger->warn("Working ifconfig required to determine IP. Please check the permissions of /sbin/ifconfig"); return; } if ($addr) { my @IPS = getconfiguredips(); if ( grep( /^\Q$addr\E$/, @IPS ) ) { $cachedmainip = $addr; return $addr; } } my $mainserverip = getmainserverip(); $cachedmainip = $mainserverip; return $mainserverip; } sub getmainserverip { return $cachedserverip if ( $cachedserverip ne '' ); my $oldmainip = Cpanel::LoadFile::loadfile('/var/cpanel/mainip'); if ($oldmainip) { $cachedserverip = $oldmainip; return $oldmainip; } my $logger = Cpanel::Logger->new(); my $system = $^O; my $addr = q{}; my $ethdev = q{}; my $wwwaccthash_ref = Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf(); if ( exists $wwwaccthash_ref->{'ADDR'} && $wwwaccthash_ref->{'ADDR'} ne '' ) { $addr = $wwwaccthash_ref->{'ADDR'}; } if ( exists $wwwaccthash_ref->{'ETHDEV'} && $wwwaccthash_ref->{'ETHDEV'} ne '' ) { $ethdev = $wwwaccthash_ref->{'ETHDEV'}; } if ( !-x '/sbin/ifconfig' ) { if ($addr) { return $addr; } $logger->die("Fatal error: /sbin/ifconfig is not executable, determining main server IP impossible"); } my %alias_ips; if ( $system =~ /freebsd/i ) { my $rc_conf_ref = {}; Cpanel::Config::LoadConfig::loadConfig( '/etc/rc.conf', $rc_conf_ref, '=' ); %alias_ips = map { ( $rc_conf_ref->{$_} =~ m{ inet [\s]+ (\d+ [.] \d+ [.] \d+ [.] \d+) }xms )[0] => undef } grep ( /_alias/, keys %$rc_conf_ref ); } my @IPS; if ( $system =~ /freebsd/i || !$ethdev ) { @IPS = grep { !exists $alias_ips{$_} } getconfiguredips(); } else { my $wwwacct_conf_mtime = ( stat('/etc/wwwacct.conf') )[9]; my $ifconfig_mtime = 43200; #12 hours my $sec_since_wwwacct_conf_modified = ( time() - $wwwacct_conf_mtime ); if ( $sec_since_wwwacct_conf_modified < $ifconfig_mtime ) { $ifconfig_mtime = $sec_since_wwwacct_conf_modified - 60; } @IPS = split( /\n/, Cpanel::GlobalCache::cachedmcommand( 'cpanel', $ifconfig_mtime, '/sbin/ifconfig', $ethdev ) ); } foreach my $ip (@IPS) { if ( $ip =~ m{ [\s\:] (\d+ [.] \d+ [.] \d+ [.] \d+) }xms ) { my $thisip = $1; if ( !Cpanel::IP::Loopback::is_loopback($thisip) ) { $cachedserverip = $thisip; return $thisip; } } } my $retry_ok = 0; my $ips = ''; @IPS = (); if ( $ethdev eq '' ) { $ips = Cpanel::CachedCommand::noncachedcommand( '/sbin/ifconfig', '-a' ); } else { $retry_ok = 1; $ips = Cpanel::CachedCommand::noncachedcommand( '/sbin/ifconfig', $ethdev ); } @IPS = grep { !exists $alias_ips{$_} } split( /\n/, $ips ); foreach my $ip (@IPS) { if ( $ip =~ m{ [\s\:] (\d+ [.] \d+ [.] \d+ [.] \d+) }xms ) { my $thisip = $1; if ( !Cpanel::IP::Loopback::is_loopback($thisip) ) { $cachedserverip = $thisip; return $thisip; } } } if ($retry_ok) { $ips = ''; @IPS = (); $ips = Cpanel::CachedCommand::noncachedcommand( '/sbin/ifconfig', '-a' ); @IPS = split( /\n/, $ips ); foreach my $ip (@IPS) { if ( $ip =~ m{ [\s\:] (\d+ [.] \d+ [.] \d+ [.] \d+) }xms ) { my $thisip = $1; if ( !Cpanel::IP::Loopback::is_loopback($thisip) ) { $cachedserverip = $thisip; return $thisip; } } } } if ( $ethdev ne '' ) { $logger->warn("No IP address found on $ethdev, make sure device is correctly configured, returning 0.0.0.0"); } else { $logger->warn("No IP address found, returning 0.0.0.0"); } return '0.0.0.0'; } sub getconfiguredips { if ($configuredips) { return wantarray ? @$configuredips : $configuredips; } my $iplist_cachefile = Cpanel::CachedCommand::Utils::_get_datastore_filename('all_iplist.db'); my $now = time(); my $iplist_cache_age = $now - ( ( stat $iplist_cachefile )[9] || 0 ); my $use_cache = 1; if ( $iplist_cache_age < 0 || $iplist_cache_age > 300 ) { $use_cache = 0; } else { my $ips_age = $now - ( ( stat '/etc/ips' )[9] || 0 ); my $wwwacctconf_age = $now - ( ( stat '/etc/wwwacct.conf' )[9] || 0 ); if ( $iplist_cache_age > $ips_age || $iplist_cache_age > $wwwacctconf_age ) { $use_cache = 0; } } if ($use_cache) { if ( open( my $cache_fh, '<', $iplist_cachefile ) ) { eval { local $SIG{__DIE__}; local $SIG{__WARN__}; $configuredips = Storable::pretrieve($cache_fh); }; close($cache_fh); } } if ( !$configuredips || !@$configuredips ) { my $ips = Cpanel::SafeRun::Simple::saferun( '/sbin/ifconfig', '-a' ); my @IPS = split m{ \n }xms, $ips; foreach my $ip (@IPS) { if ( $ip =~ m{ [\s\:] (\d+ [.] \d+ [.] \d+ [.] \d+) }xms ) { my $thisip = $1; if ( !Cpanel::IP::Loopback::is_loopback($thisip) ) { push @$configuredips, $thisip; } } } Storable::nstore( $configuredips, $iplist_cachefile ); } $configuredips = [] unless ( defined $configuredips ); return wantarray ? @$configuredips : $configuredips; } 1; } # --- END Cpanel/DIp/MainIP.pm { # --- BEGIN Cpanel/Config/LoadWwwAcctConf.pm package Cpanel::Config::LoadWwwAcctConf; # use Cpanel::Logger (); # use Cpanel::Config::LoadConfig (); # use Cpanel::SafeFile (); # use Cpanel::Debug (); my $wwwconf_cache; my $wwwconf_mtime = 0; my $has_storable; our $wwwacctconf = '/etc/wwwacct.conf'; our $wwwacctconfshadow = "$wwwacctconf.shadow"; our $wwwacctconf_cache = "$wwwacctconf.cache"; our $wwwacctconfshadow_cache = "$wwwacctconf.shadow.cache"; sub import { my $this = shift; if ( !exists $INC{'Storable.pm'} ) { if ( !grep( /:nostorable/, @_ ) ) { eval { local $SIG{__DIE__} = sub { warn "Storable is not available (attempting to ignore)."; }; require Storable; }; } } if ( exists $INC{'Storable.pm'} ) { $has_storable = 1; } my @imports = grep( !/:nostorable/, @_ ); Exporter::import( $this, @imports ); } sub loadwwwacctconf { if ( $INC{'Storable.pm'} ) { $has_storable = 1; } #something else loaded it my $filesys_mtime = ( stat($wwwacctconf) )[9]; return if !$filesys_mtime; if ( $filesys_mtime == $wwwconf_mtime && $wwwconf_cache ) { return wantarray ? %{$wwwconf_cache} : $wwwconf_cache; } if ($has_storable) { my $cache_file; my $cache_filesys_mtime; my $have_valid_cache = 1; if ( $> == 0 && -r $wwwacctconfshadow_cache ) { $cache_filesys_mtime = ( stat(_) )[9]; #shadow cache's mtime my $shadow_file_mtime = ( stat $wwwacctconfshadow )[9] || 0; if ( $shadow_file_mtime < $cache_filesys_mtime ) { $cache_file = $wwwacctconfshadow_cache; } else { #don't use shadow cache if shadow file is newer $have_valid_cache = undef; } } elsif ( -r $wwwacctconf_cache && !( $> == 0 && -r $wwwacctconfshadow ) ) { $cache_filesys_mtime = ( stat $wwwacctconf_cache )[9]; #regular cache's mtime $cache_file = $wwwacctconf_cache; } else { $have_valid_cache = undef; } my $now = time(); if ( $Cpanel::Debug::level >= 5 ) { print STDERR __PACKAGE__ . "::loadwwwacctconf cache_filesys_mtime = $cache_filesys_mtime , filesys_mtime: $filesys_mtime , now : $now\n"; } if ( $have_valid_cache && $cache_filesys_mtime > $filesys_mtime && $cache_filesys_mtime < $now ) { my $wwwconf_ref; if ( open( my $conf_fh, '<', $cache_file ) ) { eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; $wwwconf_ref = Storable::pretrieve($conf_fh);'; #must be quoted or it ends up in the stash close($conf_fh); } if ( $wwwconf_ref && ( scalar keys %{$wwwconf_ref} ) > 0 ) { if ( $Cpanel::Debug::level >= 5 ) { print STDERR __PACKAGE__ . "::loadwwwconf file system cache hit\n"; } $wwwconf_cache = $wwwconf_ref; $wwwconf_mtime = $filesys_mtime; return wantarray ? %{$wwwconf_ref} : $wwwconf_ref; } } } my @configfiles; push @configfiles, $wwwacctconf; if ( -r $wwwacctconfshadow ) { push @configfiles, $wwwacctconfshadow; } #shadow file must be last as the cache gets written for each file with all the files before it in it my $can_write_cache; if ( $> == 0 && $has_storable ) { $can_write_cache = 1; } my %CONF = ( 'ADDR' => undef, 'AIMPASS' => undef, 'AIMUSER' => undef, 'CONTACTAIM' => undef, 'CONTACTEMAIL' => undef, 'CONTACTPAGER' => undef, 'CONTACTUIN' => undef, 'DEFMOD' => undef, 'ETHDEV' => undef, 'FTPTYPE' => undef, 'HOMEDIR' => undef, 'HOMEMATCH' => undef, 'HOST' => undef, 'ICQPASS' => undef, 'ICQUSER' => undef, 'LOGSTYLE' => undef, 'MINUID' => undef, 'NS' => undef, 'NS2' => undef, 'NS3' => undef, 'NS4' => undef, 'NSTTL' => undef, 'SCRIPTALIAS' => undef, 'TTL' => undef, ); foreach my $configfile (@configfiles) { Cpanel::Config::LoadConfig::loadConfig( $configfile, \%CONF, '\s+', undef, undef, undef, { 'nocache' => 1 } ); foreach ( keys %CONF ) { $CONF{$_} =~ s{\s+$}{} if defined $CONF{$_}; } $CONF{'HOMEDIR'} =~ s{/+$}{} if defined $CONF{'HOMEDIR'}; # Remove trailing slashes $CONF{'MINUID'} ||= 500; if ($can_write_cache) { my $cache_file = $configfile . '.cache'; my $wwwcachelock = Cpanel::SafeFile::safeopen( \*WWWCACHE, '>', $cache_file ); if ($wwwcachelock) { if ( $configfile eq $wwwacctconfshadow ) { chmod( 0600, $cache_file ); } else { chmod( 0644, $cache_file ); } eval 'Storable::nstore_fd( \%CONF, \*WWWCACHE );'; #must be quoted or it ends up in the stash Cpanel::SafeFile::safeclose( \*WWWCACHE, $wwwcachelock ); } } } $wwwconf_mtime = $filesys_mtime; $wwwconf_cache = \%CONF; return wantarray ? %CONF : \%CONF; } sub reset_mem_cache { ( $wwwconf_mtime, $wwwconf_cache ) = ( 0, undef ); } sub reset_has_storable { $has_storable = 0; } 1; } # --- END Cpanel/Config/LoadWwwAcctConf.pm { # --- BEGIN Cpanel/GlobalCache.pm package Cpanel::GlobalCache; eval { local $SIG{__DIE__} = sub { warn "Storable is not available (attempting to ignore)."; }; require Storable; }; # use Cpanel::CachedCommand (); # use Cpanel::StatCache (); my $GCACHEref = {}; sub load_cache { my $cachename = shift; if ( open( my $cache_fh, '/var/cpanel/globalcache/' . $cachename . '.cache' ) ) { $GCACHEref->{$cachename}->{'mtime'} = ( stat($cache_fh) )[9]; eval { local $SIG{__DIE__}; $GCACHEref->{$cachename}->{'data'} = Storable::pretrieve($cache_fh); }; close($cache_fh); } } sub cachedmcommand { my $cachename = shift; if ( !exists $GCACHEref->{$cachename} ) { load_cache($cachename); } my $cache_max_mtime = shift; my $key = join( '_', @_ ); return ( ( exists $GCACHEref->{$cachename}{'data'}{'command'}{$key} && ( $cache_max_mtime + $GCACHEref->{$cachename}{'mtime'} ) > time() ) ? $GCACHEref->{$cachename}{'data'}{'command'}{$key} : Cpanel::CachedCommand::cachedmcommand( $cache_max_mtime, @_ ) ); } sub cachedcommand { my $cachename = shift; if ( !exists $GCACHEref->{$cachename} ) { load_cache($cachename); } my ( $file_mtime, $file_ctime ) = Cpanel::StatCache::cachedmtime_ctime( $_[0] ); my $key = join( '_', @_ ); return ( ( exists $GCACHEref->{$cachename}{'data'}{'command'}{$key} && $GCACHEref->{$cachename}{'mtime'} > $file_mtime && $GCACHEref->{$cachename}{'mtime'} > $file_ctime ) ? $GCACHEref->{$cachename}{'data'}{'command'}{$key} : Cpanel::CachedCommand::cachedcommand(@_) ); } sub loadfile { my $cachename = shift; if ( !exists $GCACHEref->{$cachename} ) { load_cache($cachename); } my $file = shift; my $file_mtime = shift; unless ( defined $file_mtime ) { $file_mtime = ( stat($file) )[9] || 0; } return ( ( exists $GCACHEref->{$cachename}{'data'}{'file'}{$file} && $GCACHEref->{$cachename}{'mtime'} > $file_mtime ) ? $GCACHEref->{$cachename}{'data'}{'file'}{$file} : Cpanel::LoadFile::loadfile($file) ); } sub data { my $cachename = shift; if ( !exists $GCACHEref->{$cachename} ) { load_cache($cachename); } my $data = shift; my $test_mtime = shift || 0; return ( ( exists $GCACHEref->{$cachename}{'data'}{'data'}{$data} && $GCACHEref->{$cachename}{'mtime'} > $test_mtime ) ? $GCACHEref->{$cachename}{'data'}{'data'}{$data} : undef ); } 1; } # --- END Cpanel/GlobalCache.pm { # --- BEGIN Cpanel/DIp/ValidIP.pm package Cpanel::DIp::ValidIP; our $VERSION = '1.0'; sub validip { my $ip = shift || return; if ( $ip =~ m{ \A \s* (\d{1,3})[.] (\d{1,3})[.] (\d{1,3})[.] (\d{1,3}) \s* \z }xms && $1 <= 255 && $1 > 0 && $2 <= 255 && $3 <= 255 && $4 <= 255 ) { return 1; } return 0; } 1; } # --- END Cpanel/DIp/ValidIP.pm { # --- BEGIN Cpanel/IP/Loopback.pm package Cpanel::IP::Loopback; sub is_loopback { return ( $_[0] eq 'localhost' || $_[0] eq 'localhost.localdomain' || $_[0] eq '0000:0000:0000:0000:0000:0000:0000:0001' || $_[0] =~ m/^0000:0000:0000:0000:0000:ffff:7f/ # ipv4 inside of ipv6 match 127.* || $_[0] =~ m/^::ffff:127\./ # ipv4 inside of ipv6 match 127.* || $_[0] =~ m/^127\./ # ipv4 needs to match 127.* || $_[0] eq '0:0:0:0:0:0:0:1' || $_[0] eq ':1' || $_[0] eq '::1' || $_[0] eq '(null)' || $_[0] eq '(null):0000:0000:0000:0000:0000:0000:0000' || $_[0] eq '0000:0000:0000:0000:0000:0000:0000:0000' || $_[0] eq '0.0.0.0' ) ? 1 : 0; } 1; } # --- END Cpanel/IP/Loopback.pm package main; #!/usr/bin/perl BEGIN { ; } # use Cpanel::SafeDir::MK (); # use Cpanel::MysqlUtils (); use Cwd (); $| = 1; my $time = time(); my $save_dir = "/home/innodb_dumps/$time"; Cpanel::SafeDir::MK::safemkdir( $save_dir, '0700' ); chdir($save_dir) || die "Could not create $save_dir"; my @dbs = map { s/[\r\n\0]//g; s/^\s+|\s+$//g; $_; } grep { !m/^\s*$/ } split( m/\n/, Cpanel::MysqlUtils::sqlcmd("show databases;") ); foreach my $db (@dbs) { next if ( $db =~ m/^\.+$/ ); next if $db eq 'mysql' || $db eq 'information_schema' || $db eq 'horde'; # mysql db should never have InnoDB on # Horde session table is the only one that uses InnoDB, so this is not a problem my $ms = Cpanel::MysqlUtils::sqlcmd( "show table status from `" . Cpanel::MysqlUtils::safesqlstring($db) . "`;" ); next if !$ms; if ( $ms =~ m/\s+InnoDB\s+/s ) { print "Saving to $save_dir/$db.sql ..."; if ( my $pid = fork() ) { my $count = 0; while ( waitpid( $pid, 1 ) != -1 ) { print "." if $count++ % 10 == 0 || 1; select( undef, undef, undef, 0.1 ); } } else { open( STDOUT, '>', "$db.sql" ); exec 'mysqldump', '-q', $db; exit 1; } print "Done\n"; } else { print "$db has no InnoDB tables.\n"; } } 1;