Current File : //etc/exim.pl.local |
=encoding utf-8
=head1 NAME
/etc/exim.pl.local - Perl functions for exim that are loaded by /etc/exim.pl
=cut
my $VALIASES_DIR = '/etc/valiases';
my $VDOMAINALIASES_DIR = '/etc/vdomainaliases';
my $outgoing_mail_suspended_message;
my $outgoing_sender;
my $outgoing_sender_domain;
my $outgoing_sender_counted_domain;
my $outgoing_sender_sysuser;
my $outgoing_sender_is_mailman;
my $outgoing_sender_archive_directory = 'outgoing';
my $mail_gid;
my $nobody_uid;
my $nobody_gid;
my $mailtrap_gid;
my $check_mail_permissions_domain = '';
my $check_mail_permissions_sender = '';
my $check_mail_permissions_msgid = '';
my $check_mail_permissions_data = '';
my $check_mail_permissions_is_mailman = 0;
my $enforce_mail_permissions_data = '';
my $primary_hostname;
my %uid_cache = ( 0 => 'root', 47 => 'mailnull', 99 => 'nobody' );
my %user_cache = ( 'root' => 0, 'mailnull' => 47, 'nobody' => 99 );
my $reattempt_message = 'Message will be reattempted later';
my $sender_lookup;
my $sender_lookup_method;
# TEST VARIABLES
my $check_mail_permissions_result;
my %file_exists_cache;
sub file_exists {
return $file_exists_cache{ $_[0] } if exists $file_exists_cache{ $_[0] };
$file_exists_cache{ $_[0] } = -e $_[0] ? 1 : 0;
return $file_exists_cache{ $_[0] };
}
sub checkbx_autowhitelist {
my $address = shift;
my $phost = Exim::expand_string('$primary_hostname');
my $rp = Exim::expand_string('$received_protocol');
if ( $rp eq 'local' || $rp !~ /^e?smtps?a$/i || !$address || $address eq '' ) { return 'no'; }
my ( $localpart, $domain ) = split( /\@/, $address );
if ( ( !$domain || $domain eq '' || $domain eq $phost ) ) {
my $homedir = gethomedir($localpart);
unless ( $homedir ne '' ) {
return 'no';
}
if ( -e $homedir . '/etc/.boxtrapperenable' && !-e $homedir . '/etc/.boxtrapperautowhitelistdisable' ) {
return 'yes';
}
else {
return 'no';
}
}
else {
my $owner = getdomainowner($domain);
my $homedir = gethomedir($owner);
unless ( $homedir ne '' ) {
return 'no';
}
my $passwd = "${homedir}/etc/${domain}/passwd";
my $addressexists = user_exists_in_db( $localpart, $passwd );
if ( $addressexists && ( -e $homedir . "/etc/${domain}/${localpart}/.boxtrapperenable" && !-e $homedir . "/etc/${domain}/${localpart}/.boxtrapperautowhitelistdisable" ) ) {
return 'yes';
}
else {
return 'no';
}
}
}
sub getemailuser {
my ( $address, $received_protocol, $sender_ident ) = @_;
my $primary_hostname = Exim::expand_string('$primary_hostname');
my ( $local_part, $domain ) = split( m/[\@\+\%\:]/, ( $address || ( $received_protocol && $received_protocol eq 'local' ? $sender_ident : '' ) ) );
if ( !$domain || $domain eq '' || $domain eq $primary_hostname ) {
return $local_part;
}
else {
my $user = getdomainowner($domain);
if ($user) { return $user; }
}
return 'nobody';
}
#DO NOT REMOVE THIS COMMENT AS IT TELLS CPANEL TO ENABLE SERVICE AUTH CHECKING
#exim:serviceauth=1
#
# Checkpass not used since auth is passed to dovecot SASL
{
no warnings 'redefine';
sub checkuserpass { 0; }
sub checkpass { 0; }
}
sub checkspam {
# This is an old code block that should never be reached unless there is a serious
# problem installing their exim configuration
Exim::log_write("Something went very wrong during the exim configuration update. Please try reinstalling your exim configuration.");
1;
}
sub convert_address_directory_to_dovecot_lda_destination_username {
my $local_part = Exim::expand_string('$local_part');
my $domain = Exim::expand_string('$domain');
$primary_hostname ||= Exim::expand_string('$primary_hostname');
my $address_file = Exim::expand_string('$address_file');
if ( $address_file !~ m{mail/\Q$domain\E} ) {
return ( getpwuid($>) )[0];
}
else {
return $local_part . '@' . $domain;
}
}
sub convert_address_directory_to_dovecot_lda_mailbox {
my $address_file = Exim::expand_string('$address_file');
my ($mailbox) = $address_file =~ m{/\.([^\/]+)};
if ($mailbox) {
return "INBOX.$mailbox";
}
return 'INBOX';
}
sub call_cpwrap {
my ( $function, @ARGS ) = @_;
my @JSON_ENCODED_ARGS = map { aggressive_json_safe_encode($_) } @ARGS;
my $data = join( ' ', @JSON_ENCODED_ARGS );
my $json_template = qq[{"function":"$function","namespace":"Cpanel","version":2,"action":"run","data":"$data","send_data_only":1,"module":"exim"}\r\n\r\n];
require Cpanel::Encoder::Exim;
return eval { Exim::expand_string( '${readsocket{/usr/local/cpanel/var/cpwrapd.sock}{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($json_template) . '}{10s}}' ); };
}
sub aggressive_json_safe_encode {
my ($arg) = @_;
$arg =~ tr/^a-zA-Z0-9!#\$\-=?^_{}~:.//cd;
return $arg;
}
my $archived_at_domain_level = 0;
my $archived_outgoing = 0;
my $archived_mailman = 0;
sub should_archive_incoming_domain_message {
return ( $archived_at_domain_level = !_message_has_been_seen() );
}
sub _message_has_been_seen {
#ARCHIVE ONLY IF
#
#$parent_domain = ""
#
#OR
#
#$parent_domain != $domain
# Delivery was not a result of an expansion
my $parent_domain = Exim::expand_string('$parent_domain');
if ( !length $parent_domain ) {
return 0;
}
# Delivery was the result of an expansion / alias. Since its a diffrent domain we don't
# know if it was archived so we need to archive if enabled
my $domain = Exim::expand_string('$domain');
if ( $domain ne $parent_domain ) {
return 0;
}
my $parent_local_part = Exim::expand_string('$parent_local_part');
my $local_part = Exim::expand_string('$local_part');
# case 60975: If any deliveries happened, parent_domain and parent_local_part
# will get set to match domain and local_part. Since we need to
# still archive outgoing if it to our same domain or a local
# user we need to accept when they all match
if ( $parent_domain eq $domain && $local_part && $parent_local_part ) {
return 0;
}
# parent_local_part ne local_part and
# parent_domain == domain so it already got archived if we have it on
return 1;
}
sub archive_headers {
my ($router) = @_;
if ( $router eq 'archive_incoming_email_domain_method' ) {
return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part') . '@' . Exim::expand_string('$domain');
}
elsif ( $router eq 'archive_incoming_email_local_user_method' ) {
return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part');
}
elsif ( $router eq 'archive_outgoing_email' ) {
return "X-Archive-Type: " . $outgoing_sender_archive_directory . "\nX-Archive-Sender: $outgoing_sender";
}
}
sub should_archive_incoming_localuser_message {
# case 60999: Do not archive a message at the localuser level
# if we have already archived it at the domain level (avoid two copies)
return 0 if $archived_at_domain_level;
my $local_part = Exim::expand_string('$local_part');
my $incoming_domain = getusersdomain($local_part);
if ($incoming_domain) {
my $home = gethomedir($local_part);
if ( file_exists("$home/etc/$incoming_domain/archive/incoming") ) {
return 1;
}
}
return 0;
}
sub get_incoming_domain {
return getusersdomain( Exim::expand_string('$local_part') );
}
sub should_archive_outgoing_message {
return 0 if _message_has_been_seen();
return determine_sender_and_check_if_archive_needed();
}
sub determine_sender_and_check_if_archive_needed {
my $uid = int( Exim::expand_string('$originator_uid') );
my $gid = int( Exim::expand_string('$originator_gid') );
# outgoing_sender_domain is the domain of the actual sender
# outgoing_sender_counted_domain is the domain we actually count the message against
# Currently these are always the same except domain may be
# rewritten if we are coming from a mailman list in order
# to count against the owner of the list instead of the mailman
# user assuming /var/cpanel/email_send_limits/count_mailman exists
( $outgoing_sender, $outgoing_sender_domain, $outgoing_sender_counted_domain, $outgoing_sender_is_mailman ) = get_message_sender( $uid, $gid );
if ( $outgoing_sender_domain && $outgoing_sender_domain ne '-system-' ) {
$outgoing_sender_sysuser = getdomainowner($outgoing_sender_domain);
my $home = gethomedir($outgoing_sender_sysuser);
if ( $outgoing_sender_is_mailman && file_exists("$home/etc/$outgoing_sender_domain/archive/mailman") ) {
$outgoing_sender_archive_directory = 'mailman';
return 0 if $archived_mailman; # already archived
return ( $archived_mailman = 1 );
}
elsif ( file_exists("$home/etc/$outgoing_sender_domain/archive/outgoing") ) {
$outgoing_sender_archive_directory = 'outgoing';
return 0 if $archived_outgoing; # already archived
return ( $archived_outgoing = 1 );
}
}
return 0;
}
sub pack_archive_address_data {
my ($router) = @_;
return join( ' ',
'router=' . Cpanel::Encoder::Exim::encode_string_literal($router),
'sender=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender),
'sender_domain=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_domain),
'sender_sysuser=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_sysuser),
'sender_archive_directory=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_archive_directory)
);
}
sub get_outgoing_sender {
return ( $outgoing_sender // Exim::expand_string('${extract{sender}{$address_data}}'));
}
sub get_outgoing_sender_domain {
return ( $outgoing_sender_domain // Exim::expand_string('${extract{sender_domain}{$address_data}}'));
}
sub get_outgoing_sender_sysuser {
return ( $outgoing_sender_sysuser // Exim::expand_string('${extract{sender_sysuser}{$address_data}}'));
}
sub get_outgoing_archive_directory {
return ( $outgoing_sender_archive_directory // Exim::expand_string('${extract{sender_archive_directory}{$address_data}}'));
}
sub YYYYMMDDGMT {
my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime( $_[0] || time() );
return sprintf( '%04d-%02d-%02d', $year + 1900, $mon + 1, $mday );
}
our $DEFAULT_EMAIL_SEND_LIMITS_DEFER_CUTOFF_PERCENTAGE = 125;
sub getmaxemailsperhour {
my $domain = shift;
return 0 if $domain eq '-system-';
$domain =~ s/\///g; #jic
my $maxemails = 0; # Defaults to "unlimited"
my $master_email_send_limits_mtime = ( stat('/etc/email_send_limits') )[9];
my $max_fh;
if ( open( $max_fh, '<', '/var/cpanel/email_send_limits/cache/' . $domain ) && ( stat($max_fh) )[9] > $master_email_send_limits_mtime ) { # This is the user's main domain. All user's domains are aggregated here
$maxemails = readline $max_fh;
close $max_fh;
return 0 if !$maxemails || $maxemails eq 'unlimited';
return ( $maxemails ? int($maxemails) : 0 );
}
my $search_regex = qr/^\Q$domain\E:/;
my $search_wildcard_regex = qr/^\Q*\E:/;
_check_cache_dir();
my $old_umask = umask();
umask(0027);
#format DOMAIN: MAX_EMAIL_PER_HOUR,MAX_DEFER_FAIL_PERCENTAGE,MIN_DEFER_FAIL_TO_TRIGGER_PROTECTION
if ( open( my $max_fh, '>', '/var/cpanel/email_send_limits/cache/.' . $domain ) ) {
umask($old_umask);
if ( open( my $email_limits_fh, '<', '/etc/email_send_limits' ) ) {
while ( readline($email_limits_fh) ) {
if ( $_ =~ $search_regex ) {
$maxemails = ( split( /\,/, ( split( /:\s+/, $_ ) )[1] ) )[0];
last if $maxemails || $maxemails eq '0'; # case 51568: if there is no value we use the wildcard
}
elsif ( $_ =~ $search_wildcard_regex ) {
$maxemails = ( split( /\,/, ( split( /:\s+/, $_ ) )[1] ) )[0];
last;
}
}
}
chomp $maxemails;
print {$max_fh} $maxemails;
close($max_fh);
rename( '/var/cpanel/email_send_limits/cache/.' . $domain, '/var/cpanel/email_send_limits/cache/' . $domain ); #rename is atomic and will overwrite the file
return int $maxemails; # case 51568: must transform 'unlimited' to 0
}
else {
umask($old_umask);
}
return 0;
}
sub increment_max_emails_per_hour {
my ( $domain, $time, $msgid ) = @_;
$domain =~ s/\///g; #jic
_check_tracker_dir($domain);
$time ||= time();
Exim::log_write( "SMTP connection outbound $time $msgid $domain " . Exim::expand_string('$local_part') . '@' . Exim::expand_string('$domain') );
if ( open( my $emailt_fh, '>>', "/var/cpanel/email_send_limits/track/$domain/" . join( '.', ( gmtime($time) )[ 2, 3, 4, 5 ] ) ) ) {
print {$emailt_fh} '1';
close($emailt_fh);
}
# !DEBUG!
# if ( open( my $emailt_fh, '>>', "/var/cpanel/email_send_limits/track/$domain/msgids_" . join( '.', ( gmtime( $time ) )[ 2, 3, 4, 5 ] ) ) ) {
#
# print {$emailt_fh} $msgid . "\n";
# close($emailt_fh);
# }
}
sub _check_cache_dir {
mkdir( '/var/cpanel/email_send_limits/cache', 0750 ) if !-e '/var/cpanel/email_send_limits/cache';
}
sub _check_tracker_dir {
my $domain = shift;
$domain =~ s/\///g; #jic
if ( !-e '/var/cpanel/email_send_limits/track/' . $domain ) {
mkdir( '/var/cpanel/email_send_limits', 0751 );
mkdir( '/var/cpanel/email_send_limits/track', 0750 );
mkdir( '/var/cpanel/email_send_limits/track/' . $domain, 0750 );
}
}
sub get_current_emails_per_hour {
( ( stat( "/var/cpanel/email_send_limits/track/$_[0]/" . join( '.', ( gmtime( $_[1] || time() ) )[ 2, 3, 4, 5 ] ) ) )[7] || 0 );
}
sub get_current_emails_per_day {
my $domain = shift;
$domain =~ s/\///g; #jic
return 0 if ( !-e '/var/cpanel/email_send_limits/track/' . $domain );
my $total_size = 0;
if ( opendir( my $domain_track_fh, '/var/cpanel/email_send_limits/track/' . $domain ) ) {
while ( my $domaintime = readdir($domain_track_fh) ) {
next if ( $domaintime =~ /^\.\.?$/ );
my $tracker_file_size = ( stat("/var/cpanel/email_send_limits/track/$domain/$domaintime") )[7];
$total_size += $tracker_file_size;
}
}
return $total_size;
}
sub reached_max_emails_per_hour {
my $domain = shift;
$domain =~ s/\///g; #jic
my $max_allowed = int( shift || 0 );
my $time = shift || time();
if ($max_allowed) {
# AKA number_of_emails_sent >= $max_allowed
if ( get_current_emails_per_hour( $domain, $time ) >= $max_allowed ) {
return 1;
}
else {
return 0;
}
}
return 0;
}
#
# This converse function for reference only
#
#sub set_email_send_limits_defer_cutoff {
# my $percentage = int shift ;
#
# # The value is the size of the file so we can avoid the open/close overhead (just a stat)
# if ( open(my $cut_off_percentage_fh,'>','/var/cpanel/email_send_limits/defer_cutoff') ) {
# print {$cut_off_percentage_fh} 'x' x $percentage;
# return 1;
# }
#
# return 0;
# }
sub get_email_send_limits_defer_cutoff {
# The value is the size of the file so we can avoid the open/close overhead (just a stat)
my $cut_off_percentage = ( stat('/var/cpanel/email_send_limits/defer_cutoff') )[7];
if ( !defined $cut_off_percentage ) { $cut_off_percentage = $DEFAULT_EMAIL_SEND_LIMITS_DEFER_CUTOFF_PERCENTAGE; }
return $cut_off_percentage;
}
#
# This converse function for reference only
#
# sub set_email_daily_limit_notify {
# my $limit = int shift ;
# if ( $limit == 0 ) {
# unlink '/var/cpanel/email_send_limits/daily_limit_notify';
# return 1;
# }
# # The value is the size of the file so we can avoid the open/close overhead (just a stat)
# if ( open(my $daily_limit_fh,'>','/var/cpanel/email_send_limits/daily_limit_notify') ) {
# print {$daily_limit_fh} 'x' x $limit;
# return 1;
# }
# return 0;
# }
sub get_email_daily_limit_notify {
# The value is the size of the file so we can avoid the open/close overhead (just a stat)
my $limit = ( stat('/var/cpanel/email_send_limits/daily_limit_notify') )[7];
if ( !defined $limit ) { $limit = 0; }
return $limit;
}
sub create_daily_notify_touchfile {
my $domain = shift;
$domain =~ s/\///g; #jic
mkdir( '/var/cpanel/email_send_limits/daily_notify', 0750 ) if !-e '/var/cpanel/email_send_limits/daily_notify';
if ( open( my $daily_limit_fh, '>', '/var/cpanel/email_send_limits/daily_notify/' . $domain ) ) {
close $daily_limit_fh;
}
return undef;
}
BEGIN {
unshift @INC, '/usr/local/cpanel';
}
#DO NOT USE lib here
# use Cpanel::Encoder::Exim (); -- no loaded with require or preload
sub gethomedir {
my $user = shift;
require Cpanel::Encoder::Exim;
return Exim::expand_string( '${extract{5}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}{$value}}}}' ) || '';
}
sub getuid {
my $user = shift;
require Cpanel::Encoder::Exim;
my $uid = Exim::expand_string( '${extract{2}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}{$value}}}}' );
return defined $uid ? $uid : '';
}
sub getdomainowner {
my $domain = shift;
require Cpanel::Encoder::Exim;
substr($domain,0,4,'') if index($domain,'www.') == 0;
return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($domain) . '}lsearch{/etc/userdomains}{$value}}' ) || '';
}
my %domain_to_user_cache;
# This must be cached because we call getusersdomain as root in the archive_incoming_email_local_user_method router
# and then we need to read the user out of the memory cache in archiver_incoming_local_user_method since
# we no longer have access to read /etc/domainusers at that point. Note, we need to be able to cache multiple
# users in case they send a message to multiple system users
sub getusersdomain {
return '' if !$_[0] || $_[0] eq 'root' || $_[0] =~ tr{/}{} || !-e "/var/cpanel/users/$_[0]";
return ( $domain_to_user_cache{ $_[0] } || ( $domain_to_user_cache{ $_[0] } = lookup_key_in_file( '/etc/domainusers', $_[0] ) ) );
}
sub lookup_key_in_file {
my ( $file, $key ) = @_;
require Cpanel::Encoder::Exim;
return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($key) . '}lsearch{' . $file . '}{$value}}' ) || '';
}
sub isdemo {
my $user = shift;
return if ( !$user );
return 0 if $user eq '0' || $user eq '8' || $user eq 'mail' || $user eq 'mailnull' || $user eq 'root';
if ( $user =~ /^\d+$/ ) {
return user_exists_in_db( $user, '/etc/demouids' );
}
return user_exists_in_db( $user, '/etc/demousers' );
}
sub user_exists_in_db {
my ( $user, $db ) = @_;
# If the user is empty, '0' or only whitespace
# we should return 0 as $lookup will always return
# 1 even if it does not exist
return 0 if !$user || $user !~ tr{ \t}{}c;
require Cpanel::Encoder::Exim;
return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}lsearch{' . $db . '}{1}{0}}' ) || '0';
}
my %sender_recent_authed_mail_ips_address_cache;
my $get_recent_authed_mail_ips_lookup_method;
sub get_recent_authed_mail_ips_text_entry {
my ( $sender, $domain ) = get_recent_authed_mail_ips_entry(@_);
return join( '|', ( $sender || '' ), $domain );
}
sub popbeforesmtpwarn {
if ( my @possible_users = _get_possible_users_from_recent_authed_mail_ips_users() ) {
return ( "X-PopBeforeSMTPSenders: " . join( ",", @possible_users ) );
}
return '';
}
sub get_recent_authed_mail_ips_entry {
my $log = shift;
# SENDING OVER POP B4 SMTP or NOAUTH
# case 43151, case 43150
$get_recent_authed_mail_ips_lookup_method = '';
my $sender_host_address = Exim::expand_string('$sender_host_address');
# Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] log=[$log]");
my ( $sender, $domain );
if ( exists $sender_recent_authed_mail_ips_address_cache{$sender_host_address} ) {
# Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] USING CACHE");
( $sender, $domain, $get_recent_authed_mail_ips_lookup_method ) = @{ $sender_recent_authed_mail_ips_address_cache{$sender_host_address} };
$get_recent_authed_mail_ips_lookup_method = "cached: " . $get_recent_authed_mail_ips_lookup_method;
$log = 0;
}
else {
my $recent_authed_mail_ips_users_is_up_to_date = ( stat('/etc/recent_authed_mail_ips_users') )[9] + 7200 > time() ? 1 : 0;
my $sender_address_domain;
# Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] recent_authed_mail_ips_users_is_up_to_date= $recent_authed_mail_ips_users_is_up_to_date");
# If we have a recent_authed_mail_ips_users file that is up to date, we can verify the ip matches
if ($recent_authed_mail_ips_users_is_up_to_date) {
# This is what the user has claimed as the sender
my $sender_address = Exim::expand_string('$sender_address');
my $from_h_domain = Exim::expand_string('${domain:$h_from:}');
my $from_h_localpart = Exim::expand_string('${local_part:$h_from:}');
my $from_h = "$from_h_localpart\@$from_h_domain";
# First we try to find the address in the recent_authed_mail_ips_users file (with a cached exim lookup)
if ( my @possible_users = _get_possible_users_from_recent_authed_mail_ips_users() ) {
if ( grep { tr/@// ? $from_h eq $_ : $from_h eq $_ . '@' . $primary_hostname } @possible_users ) {
$sender = $from_h;
$domain = getdomainfromaddress($from_h);
$get_recent_authed_mail_ips_lookup_method = "full match of from_h in recent_authed_mail_ips_users";
}
elsif ( grep { tr/@// ? $sender_address eq $_ : $sender_address eq $_ . '@' . $primary_hostname } @possible_users ) {
$sender = $sender_address;
$domain = getdomainfromaddress($sender_address);
$get_recent_authed_mail_ips_lookup_method = "full match of sender_address in recent_authed_mail_ips_users";
}
elsif ( ( $sender_address_domain = ( split( m/\@/, $sender_address ) )[1] ) && grep( m/\@\Q$sender_address_domain\E$/, @possible_users ) ) {
$domain = $sender_address_domain;
$sender = '-unknown-@' . $domain;
$get_recent_authed_mail_ips_lookup_method = "match of sender_address_domain in recent_authed_mail_ips_users";
}
elsif ( grep { tr/@// ? ( $from_h eq $_ ) : ( $from_h_localpart eq $_ && ( !length $from_h_domain || $from_h_domain eq $primary_hostname ) ) } @possible_users ) {
$sender = $from_h;
$domain = $from_h_domain;
$get_recent_authed_mail_ips_lookup_method = "full match of from_h in recent_authed_mail_ips_users";
}
elsif ( grep( m/\@\Q$from_h_domain\E$/, @possible_users ) ) {
$domain = $from_h_domain;
$sender = '-unknown-@' . $from_h_domain;
$get_recent_authed_mail_ips_lookup_method = "match of from_h_domain in recent_authed_mail_ips_users";
}
elsif ( $possible_users[0] && $possible_users[0] eq '-alwaysrelay-' ) {
if ($from_h_domain) {
Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting from_h_domain of: $from_h_domain and from_h_localpart: $from_h_localpart");
$domain = $from_h_domain;
$sender = $from_h;
$get_recent_authed_mail_ips_lookup_method = "in alwaysrelay trusted from_h";
}
else {
Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting sender_address_domain of: $sender_address_domain");
$domain = $sender_address_domain;
$sender = $sender_address;
$get_recent_authed_mail_ips_lookup_method = "in alwaysrelay trusted sender_address";
}
}
else {
# If none of them matched, we have to assume they authenticated in some we so we go with the first one
$domain = getdomainfromaddress( $possible_users[0] );
$sender = $possible_users[0];
$get_recent_authed_mail_ips_lookup_method = "in recent_authed_mail_ips_users using first address";
}
if ( $sender =~ m/^\*/ ) {
$sender =~ s/^\*/-unknown-/;
}
$sender_recent_authed_mail_ips_address_cache{$sender_host_address} = [ $sender, $domain, $get_recent_authed_mail_ips_lookup_method ];
}
}
# we need to check alwaysrelay since we don't require recentauthedmailiptracker to be enabled
if ( !$domain && -e '/etc/alwaysrelay' ) {
my $alwaysrelay_result = Exim::expand_string('${lookup{$sender_host_address}iplsearch{/etc/alwaysrelay}{$sender_host_address $value}}');
if ($alwaysrelay_result) {
my ( $alwaysrelay_ip, $alwaysrelay_user ) = split( /\s+/, $alwaysrelay_result );
if ($alwaysrelay_user) {
$domain = getdomainfromaddress($alwaysrelay_user);
$sender = $alwaysrelay_user;
$get_recent_authed_mail_ips_lookup_method = "full match in alwaysrelay with recentauthedmailiptracker disabled";
Exim::log_write("$sender_host_address in /etc/alwaysrelay using domain $domain from lookup of $alwaysrelay_user");
}
if ( !$domain ) {
$domain = $sender_address_domain = ( split( /\@/, Exim::expand_string('$sender_address') ) )[1];
$sender = "-unknown-\@$domain";
$get_recent_authed_mail_ips_lookup_method = "in alwaysrelay with recentauthedmailiptracker disabled";
Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting sender_address_domain of: $sender_address_domain");
}
}
# no need to check /etc/alwaysrelay as they are automaticlly built into recent_authed_mail_ips_users
}
}
if ($domain) {
if ($log) {
my $message_exim_id = Exim::expand_string('$message_exim_id');
my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}');
my $sender_host_port = Exim::expand_string('$sender_host_port');
my $recent_authed_mail_ips_local_user = getdomainowner($domain);
my $recent_authed_mail_ips_local_uid = user2uid($recent_authed_mail_ips_local_user);
Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port U=$recent_authed_mail_ips_local_user ID=$recent_authed_mail_ips_local_uid S=$sender B=get_recent_authed_mail_ips_entry");
}
return ( $sender, $domain, $get_recent_authed_mail_ips_lookup_method );
}
return ( '', '', '' );
}
sub _get_possible_users_from_recent_authed_mail_ips_users {
my $recent_authed_mail_ips_users_result = Exim::expand_string('${lookup{$sender_host_address}lsearch{/etc/recent_authed_mail_ips_users}{$value}}');
return map {
s/\/.*$//g if tr/\///;
tr/+%:/@/;
$_;
} split( m/\s*\,\s*/, $recent_authed_mail_ips_users_result );
}
my $local_connection_uid;
my $local_connection_user;
my %sender_host_address_cache;
sub get_identified_local_connection_uid {
$local_connection_uid;
}
sub get_identified_local_connection_user {
$local_connection_user;
}
sub identify_local_connection {
# passes but not for production
# use strict;
# On Linux we can identify users by reading /proc/net/tcp*
# Since this requires access kernel memory on bsd and we don't have a way
# do that under exim users MUST authenticate to send messages from localhost
my ( $sender_host_address, $sender_host_port, $received_ip_address, $received_port, $log ) = @_;
undef $local_connection_uid;
undef $local_connection_user;
my $uid;
if ( exists $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } ) {
$uid = $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port };
$log = 0;
}
else {
local @INC = ( '/usr/local/cpanel', @INC ) if !grep { '/usr/local/cpanel' } @INC;
require Cpanel::Ident;
$uid = Cpanel::Ident::identify_local_connection( $sender_host_address, $sender_host_port, $received_ip_address, $received_port );
if ( !defined $uid ) {
$uid = identify_local_connection_wrapped( $sender_host_address, $sender_host_port, $received_ip_address, $received_port );
}
}
if ( defined $uid ) {
$local_connection_uid = $uid;
$sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = $local_connection_uid;
if ( $uid == -1 ) {
Exim::log_write("Could not identify the local connection from $sender_host_address on port $sender_host_port. Please authenticate") if $log;
return 0;
}
$local_connection_user = uid2user($uid);
# Log this for tailwatchd
Exim::log_write("SMTP connection identification H=localhost A=$sender_host_address P=$sender_host_port U=$local_connection_user ID=$local_connection_uid S=$local_connection_user B=identify_local_connection") if $log;
return 1;
}
else {
$sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = undef;
Exim::log_write("could not identify the local connection from $sender_host_address on port $sender_host_port. Please authenticate") if $log;
return 0;
}
}
sub identify_local_connection_wrapped {
my ( $address, $port, $localaddress, $localport ) = @_;
my $uidline = call_cpwrap( 'IDENTIFYLOCALCONNECTION', $address, $port, $localaddress, $localport );
chomp($uidline) if defined $uidline;
my ( $uidkey, $uid ) = split( /:/, $uidline, 2 );
$uid = undef if $uid eq '';
Exim::log_write("/usr/local/cpanel/bin/eximwrap IDENTIFYLOCALCONNECTION $address $port $localaddress $localport failed to return the uid key.") if ( !defined $uidkey || $uidkey ne 'uid' );
return $uid;
}
my $headers_rewrite_notice = '';
my $new_from_header;
use constant {
_ENOENT => 2,
_EEXIST => 17,
_SENDER_SYSTEM => '-system-',
};
sub spamd_is_available {
require Cpanel::Services::Enabled::Spamd;
return eval { Cpanel::Services::Enabled::Spamd::is_enabled() } // do {
warn;
1; # this defaults to on for historical reasons
};
}
sub get_dkim_domain {
my $msg_sender_domain = get_message_sender_domain();
if ($msg_sender_domain eq _SENDER_SYSTEM) {
$msg_sender_domain = Exim::expand_string('$sender_address_domain');
}
return $msg_sender_domain =~ tr<A-Z><a-z>r;
}
sub sender_domain_can_dkim_sign {
require Cpanel::DKIM::ValidityCache;
my $sender_domain = get_dkim_domain();
local $@;
return eval { Cpanel::DKIM::ValidityCache->get($sender_domain) } // do {
warn;
q<>;
};
}
sub discover_sender_information {
# If $sender_lookup_method and $check_mail_permissions_sender is already set
# we have already discovered the sender
if ( !$sender_lookup_method || !$check_mail_permissions_sender ) {
my $uid = int( Exim::expand_string('$originator_uid') );
my $gid = int( Exim::expand_string('$originator_gid') );
#Exim::log_write("discover_sender_information calling get_message_sender");
my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 1 );
$check_mail_permissions_sender = $sender if $sender;
$check_mail_permissions_is_mailman = $is_mailman;
}
#Exim::log_write("discover_sender_information calling discover_sender_information");
$new_from_header = get_from_header_rewrite_target();
return 0;
}
sub get_headers_rewrite {
return $new_from_header if $new_from_header;
my ($from_h_sender) = _get_from_h_sender();
Exim::log_write("discover_sender_information failed to set the from header rewrite for $from_h_sender");
return $from_h_sender;
}
sub get_from_header_rewrite_target {
$headers_rewrite_notice = '';
my ( $from_h_sender, $from_h_localpart, $from_h_domain ) = _get_from_h_sender();
if ( $sender_lookup_method && $check_mail_permissions_sender ) {
my $actual_sender = _get_login_from_check_mail_permissions_sender($check_mail_permissions_sender);
#Exim::log_write("!DEBUG! get_from_header_rewrite_target() actual_sender=[$actual_sender] from_h_sender=[$from_h_sender]");
my $qualified_actual_sender = _qualify_as_email_address($actual_sender);
my ( $status, $statusmsg );
if ( $sender_lookup_method =~ m{^redirect/forwarder} ) {
$headers_rewrite_notice = 'unmodified, forwarded message';
return $from_h_sender;
}
elsif ($check_mail_permissions_is_mailman) {
$headers_rewrite_notice = 'unmodified, sender is mailman';
return $from_h_sender;
}
elsif ( $from_h_sender eq $actual_sender ) {
$headers_rewrite_notice = 'unmodified, already matched';
return $from_h_sender;
}
else {
if ( $actual_sender eq 'mailnull' ) { # handle Mailer-Daemon messages
$headers_rewrite_notice = 'unmodified, actual sender is mailnull';
return $from_h_sender;
}
my $from_h_sender_domainowner = getdomainowner($from_h_domain);
# Actual Sender is a system user.
if ( $from_h_sender_domainowner && $from_h_sender_domainowner eq $actual_sender ) {
$headers_rewrite_notice = 'unmodified, actual sender is system user that owns from domain in the from header';
return $from_h_sender;
}
elsif ( $from_h_sender eq $qualified_actual_sender ) {
$headers_rewrite_notice = 'unmodified, actual sender is the system user';
return $from_h_sender;
}
elsif ( $actual_sender eq 'root' ) {
$headers_rewrite_notice = 'unmodified, actual sender is root';
return $from_h_sender;
}
elsif ( $actual_sender eq 'mailman' ) {
$headers_rewrite_notice = 'unmodified, actual sender is mailman';
return $from_h_sender;
}
elsif ( $actual_sender !~ tr/\@// && _is_trusted_user($actual_sender) ) {
$headers_rewrite_notice = 'unmodified, actual sender is a trusted user';
return $from_h_sender;
}
elsif ( ( ( $status, $statusmsg ) = _has_valias_pointing_to_actual_sender( $from_h_sender, $actual_sender ) )[0] ) {
if ( $statusmsg eq 'valias_exact_match' ) {
$headers_rewrite_notice = 'unmodified, there is a forwarder that points to the actual sender.';
}
elsif ( $statusmsg eq 'valias_domainowner_match' ) {
$headers_rewrite_notice = 'unmodified, there is a forwarder that points to a user owned by actual sender.';
}
elsif ( $statusmsg eq 'vdomainaliases_match' ) {
$headers_rewrite_notice = 'unmodified, there is a domain forwarder that maps to the actual sender.';
}
return $from_h_sender;
}
else {
if ( $actual_sender !~ tr/\@// ) {
$headers_rewrite_notice = 'rewritten was: [' . $from_h_sender . '], actual sender is not the same system user';
}
else {
$headers_rewrite_notice = 'rewritten was: [' . $from_h_sender . '], actual sender does not match';
}
Exim::log_write("From: header ($headers_rewrite_notice) original=[$from_h_sender] actual_sender=[$qualified_actual_sender]");
return $qualified_actual_sender;
}
}
}
# We have no sender set so we leave it unmodified
# AKA unable to determine sender would get here
$headers_rewrite_notice = 'unmodified, no actual sender determined from check mail permissions';
return $from_h_sender;
}
sub get_headers_rewritten_notice {
if ($headers_rewrite_notice) {
return "X-From-Rewrite: $headers_rewrite_notice";
}
return '';
}
#
# This converts an unqualified address which is just a system
# account IE local_part. Into local_part@primary_hostname.
#
# If the address is already qualified ie has @, it returns returns the
# address.
#
sub _qualify_as_email_address {
my ($address) = @_;
return $address if $address =~ tr/@//;
$primary_hostname ||= Exim::expand_string('$primary_hostname');
return $address . '@' . $primary_hostname;
}
#
# Convert the $check_mail_permissions_sender variable
# into the real login that the user has authenticated as
# in most cases this is already their email address, however it may
# be USER@PRIMARY_HOSTNAME, in which case we want to strip PRIMARY_HOSTNAME
#
sub _get_login_from_check_mail_permissions_sender {
my ($sender) = @_;
$primary_hostname ||= Exim::expand_string('$primary_hostname');
$sender =~ s/\@\Q$primary_hostname\E$//;
return $sender;
}
# _has_valias_pointing_to_target lets us know if there
# if a forwarder for the address pointing at the target.
#
# For example ORIGIN bob@cpanel.net
# might point to a user account DEST 'bob'
#
sub _has_valias_pointing_to_actual_sender {
my ( $origin, $actual_sender ) = @_;
#Exim::log_write("!DEBUG! _has_valias_pointing_to_actual_sender() actual_sender=[$actual_sender] origin=[$origin]");
my $qualified_origin = _qualify_as_email_address($origin);
my $qualified_actual_sender = _qualify_as_email_address($actual_sender);
my ( $origin_local_part, $origin_domain ) = split( m{@}, $qualified_origin, 2 );
my ( $actual_sender_local_part, $actual_sender_domain ) = split( m{@}, $qualified_actual_sender, 2 );
my $actual_sender_domainowner;
require Cpanel::Encoder::Exim;
return ( 0, 'invalid_origin_domain' ) if $origin_domain =~ m{/};
if ( file_exists("$VALIASES_DIR/$origin_domain") ) {
if ( my $valiases_alias_line = Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($origin) . '}lsearch*{' . $VALIASES_DIR . '/' . $origin_domain . '}{$value}}' ) ) {
if ( my @forwarders = _get_forwarders_from_string($valiases_alias_line) ) {
foreach my $forwarder_destination (@forwarders) {
#
# Handle exact matches
# IE bob@cpanel.net is forwarded to the actual sender
#
if ( _qualify_as_email_address($forwarder_destination) eq $qualified_actual_sender ) {
return ( 1, 'valias_exact_match' );
}
# $VALIASES_DIR/dog.com: nick@dog.org: me@samsdomain.org
# I send email From: nick@dog.org and I am authenticated as 'sam' it should likely be allowed
if ( $actual_sender !~ tr/\@// && $forwarder_destination =~ tr/\@// ) {
my ( $forwarder_destination_local_part, $forwarder_destination_domain ) = split( m{@}, $forwarder_destination, 2 );
my $forwarder_destination_domainowner = getdomainowner($forwarder_destination_domain);
if ( $actual_sender eq $forwarder_destination_domainowner ) {
return ( 1, 'valias_domainowner_match' );
}
}
}
}
}
}
if ( file_exists("$VDOMAINALIASES_DIR/$origin_domain") ) {
if ( my $vdomainaliases_alias_line = Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($origin_domain) . '}lsearch{' . $VDOMAINALIASES_DIR . '/' . $origin_domain . '}{$value}}' ) ) {
my $vdomainaliases_domain = _ws_trim($vdomainaliases_alias_line);
if ( ( $origin_local_part . '@' . $vdomainaliases_domain ) eq $qualified_actual_sender ) {
return ( 1, 'vdomainaliases_match' );
}
}
}
return ( 0, 'no_match' );
}
sub _is_trusted_user {
my ($user) = @_;
return 0 if !file_exists('/etc/trusted_mail_users');
local $/;
open my $trusted_mail_users_fh, '<', '/etc/trusted_mail_users' or return 0;
my @trusted_mail_users = split( qq{\n}, <$trusted_mail_users_fh> );
close $trusted_mail_users_fh;
return scalar grep { $_ eq $user } @trusted_mail_users;
}
#
# From Cpanel::StringFunc::Trim
#
sub _ws_trim {
my ($this) = @_;
my $fix = ref $this eq 'SCALAR' ? $this : \$this;
${$fix} =~ s/^\s+//;
${$fix} =~ s/\s+$//;
return ${$fix};
}
#
# From Cpanel::API::Email
#
sub _get_forwarders_from_string {
my ($forwarder_csv) = @_;
# to leave \, as \, uncomment this:
# $forwarder_csv =~ s{\\,}{\\\\,}g;
my @forwarders =
$forwarder_csv =~ /^[\s"]*\:(fail|defer|blackhole|include)\:/
? ($forwarder_csv)
: split( /(?<![\\]),/, $forwarder_csv );
my @parsed_forwarders;
for my $forward (@forwarders) {
$forward = _ws_trim($forward);
next if ( $forward =~ m{^"} );
push @parsed_forwarders, $forward;
}
return wantarray ? @parsed_forwarders : \@parsed_forwarders;
}
sub check_mail_permissions_results {
return $check_mail_permissions_data;
}
sub enforce_mail_permissions_results {
$enforce_mail_permissions_data;
}
sub uid2user {
my $uid = shift;
return exists $uid_cache{$uid} ? $uid_cache{$uid} : ( $uid_cache{$uid} = ( getpwuid($uid) )[0] );
}
sub user2uid {
my $user = shift;
return exists $user_cache{$user} ? $user_cache{$user} : ( $user_cache{$user} = getuid($user) );
}
sub get_sender_from_uid {
my $uid = int( Exim::expand_string('$originator_uid') );
my $user = uid2user($uid);
return getdomainfromaddress($user);
}
sub mailtrapheaders {
$primary_hostname ||= Exim::expand_string('$primary_hostname');
my $original_domain = Exim::expand_string('$original_domain');
my $sender_address_domain = Exim::expand_string('$sender_address_domain');
my $originator_uid = Exim::expand_string('$originator_uid');
my $originator_gid = Exim::expand_string('$originator_gid');
my $caller_uid = Exim::expand_string('$caller_uid');
my $caller_gid = Exim::expand_string('$caller_gid');
my $headers =
"X-AntiAbuse: This header was added to track abuse, please include it with any abuse report\n"
. "X-AntiAbuse: Primary Hostname - $primary_hostname\n"
. "X-AntiAbuse: Original Domain - $original_domain\n"
. "X-AntiAbuse: Originator/Caller UID/GID - [$originator_uid $originator_gid] / [$caller_uid $caller_gid]\n"
. "X-AntiAbuse: Sender Address Domain - $sender_address_domain\n"
. check_mail_permissions_headers() . "\n";
if ( file_exists('/etc/eximmailtrap') ) {
my $xsource = $ENV{'X-SOURCE'};
my $xsourceargs = $ENV{'X-SOURCE-ARGS'};
my $xsourcedir = maskdir( $ENV{'X-SOURCE-DIR'} );
$headers .= "X-Source: ${xsource}\n" . "X-Source-Args: ${xsourceargs}\n" . "X-Source-Dir: ${xsourcedir}";
}
return ($headers);
}
sub getdomainfromaddress {
my $address = shift;
$address =~ s/\/.*$//g if $address =~ tr/\///; # remove /spam
if ( $address =~ tr/@+%:// ) {
unless ( $address =~ tr/@// ) {
# This matches exactly how authentication occurs
$address =~ s/[+:%]/@/;
}
$primary_hostname ||= Exim::expand_string('$primary_hostname');
if ( $address =~ m/[@]\Q$primary_hostname\E$/ ) {
return getusersdomain( ( split( m/[@]/, $address, 2 ) )[0] ) || _SENDER_SYSTEM; #from MailAuth.pm
}
else {
return ( split( m/[@]/, $address, 2 ) )[1]; #from MailAuth.pm
}
}
else {
return getusersdomain($address) || _SENDER_SYSTEM;
}
}
sub get_message_sender_domain {
my ( $uid, $gid, $log ) = @_;
$uid = int( Exim::expand_string('$originator_uid') ) if !defined $uid;
$gid = int( Exim::expand_string('$originator_gid') ) if !defined $gid;
return ( ( get_message_sender( $uid, $gid, $log ) )[1] ) || '';
}
sub get_sender_lookup_method {
return $sender_lookup_method || 'none';
}
sub get_sender_lookup {
return $sender_lookup || '';
}
sub check_mail_permissions_headers {
return "X-Get-Message-Sender-Via: " . ( $primary_hostname ||= Exim::expand_string('$primary_hostname') ) . ": " . get_sender_lookup_method() . "\n" . "X-Authenticated-Sender: " . ( $primary_hostname ||= Exim::expand_string('$primary_hostname') ) . ": " . get_sender_lookup();
}
# This must match the logic extactly for Cpanel::TailWatch::EximStats ($direction eq '<=')
sub get_message_sender {
#passes but not for production
#use strict;
my ( $uid, $gid, $log ) = @_;
my ( $authenticated_local_user, $authenticated_id, $recent_authed_mail_ips_text_entry, $domain, $counted_domain, $sender, $is_mailman, $username );
$sender_lookup_method = '';
my ( $acl_c_vhost_owner, $acl_c_vhost_owner_url ) = split( m{:}, Exim::expand_string('$acl_c_vhost_owner') || '', 2 );
my $message_exim_id = Exim::expand_string('$message_exim_id');
# SMTP AUTH
if ( $authenticated_id = Exim::expand_string('$authenticated_id') ) {
$authenticated_id =~ s/[\r\n\f]//g;
if ( $authenticated_id eq 'nobody' ) {
if ($acl_c_vhost_owner) {
$authenticated_id = uid2user($acl_c_vhost_owner);
}
$sender_lookup_method = 'uid via acl_c_vhost_owner from authenticated_id: ' . $authenticated_id . ' from ' . $acl_c_vhost_owner_url;
}
else {
$sender_lookup_method = 'authenticated_id: ' . $authenticated_id;
}
$sender = $authenticated_id;
$domain = getdomainfromaddress($authenticated_id);
# If the sender owns the domain they are sending
# from we can trust it
if ( length $sender && $sender !~ tr/\@// ) {
( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method );
}
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from authenticated_id ($authenticated_id)");
}
# FROM A CONNECTION TO LOCALHOST (linux only)
elsif ( $authenticated_local_user = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{$acl_c_authenticated_local_user}{}}') ) {
my $authenticated_local_uid = user2uid($authenticated_local_user);
my $sender_host_address = Exim::expand_string('$sender_host_address');
my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}');
my $sender_host_port = Exim::expand_string('$sender_host_port');
$domain = getusersdomain($authenticated_local_user) || _SENDER_SYSTEM;
$sender = $authenticated_local_user;
$sender_lookup_method = 'acl_c_authenticated_local_user: ' . $authenticated_local_user;
if ($log) { Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port M=$message_exim_id U=$authenticated_local_user ID=$authenticated_local_uid S=$sender B=authenticated_local_user"); } #replay for tailwatchd
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from acl_c_authenticated_local_user");
}
# RELAY HOSTS
elsif ( $recent_authed_mail_ips_text_entry = Exim::expand_string('$acl_c_recent_authed_mail_ips_text_entry') ) {
#FIXME: need to get sender
( $sender, $domain ) = split( /\|/, $recent_authed_mail_ips_text_entry );
my $sender_host_address = Exim::expand_string('$sender_host_address');
my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}');
my $sender_host_port = Exim::expand_string('$sender_host_port');
my $recent_authed_mail_ips_local_user = getdomainowner($domain);
my $recent_authed_mail_ips_local_uid = user2uid($recent_authed_mail_ips_local_user);
$sender_lookup_method = 'acl_c_recent_authed_mail_ips_text_entry: ' . $recent_authed_mail_ips_text_entry;
if ($log) { Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port M=$message_exim_id U=$recent_authed_mail_ips_local_user ID=$recent_authed_mail_ips_local_uid S=$sender B=recent_authed_mail_ips_domain") }
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from acl_c_recent_authed_mail_ips_text_entry");
}
elsif ( Exim::expand_string('$received_protocol') eq 'local' ) {
my $sender_ident = Exim::expand_string('$sender_ident');
$sender_ident =~ s/[\r\n\f]//g;
my $used_vhost_owner_lookup = 0;
if ( $sender_ident eq 'nobody' ) {
if ($acl_c_vhost_owner) {
$used_vhost_owner_lookup = 1;
$sender_ident = uid2user($acl_c_vhost_owner);
}
}
$sender = $sender_ident;
$domain = getusersdomain($sender_ident) || _SENDER_SYSTEM;
$sender_lookup_method = 'sender_ident via received_protocol == local: ' . $sender_ident . ( $used_vhost_owner_lookup ? ' : used vhost owner lookup from: ' . $acl_c_vhost_owner_url : '' );
# If the sender owns the domain they are sending
# from we can trust it
if ( length $sender && $sender !~ tr/\@// ) {
( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method );
}
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from local user ($sender_ident)");
}
else {
$mail_gid ||= int( ( getgrnam('mail') )[2] );
#Exim::log_write("!DEBUG! mailgid=$mail_gid == gid=$gid (uid=$uid)");
if ( $gid == $mail_gid ) {
my ( $recent_authed_mail_ips_sender, $recent_authed_mail_ips_domain, $recent_authed_mail_ips_lookup_method ) = get_recent_authed_mail_ips_entry();
if ($recent_authed_mail_ips_domain) {
$sender = $recent_authed_mail_ips_sender;
$sender =~ s/[\r\n\f]//g;
$domain = $recent_authed_mail_ips_domain;
$sender_lookup_method = 'mailgid via get_recent_authed_mail_ips_entry: ' . $sender . "/$recent_authed_mail_ips_lookup_method";
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from get_recent_authed_mail_ips_entry() or sender_address_domain");
}
$primary_hostname ||= Exim::expand_string('$primary_hostname');
if ( $domain && $domain eq $primary_hostname ) {
$username = Exim::expand_string('$sender_address_local_part');
$sender = $username;
$domain = getusersdomain($username) || _SENDER_SYSTEM;
$sender_lookup_method = 'mailgid via primary_hostname' . "/$recent_authed_mail_ips_lookup_method";
}
if ( !$domain ) {
# If we cannot find the sender and it is not _SENDER_SYSTEM it is a redirected/forwarded message
my $parent_domain = Exim::expand_string('$parent_domain');
my $parent_local_part = Exim::expand_string('$parent_local_part');
my $local_part = Exim::expand_string('$local_part');
my $delivery_domain = Exim::expand_string('$domain');
$parent_domain =~ s/[^\w\.\-\/]//g;
$parent_local_part =~ s/[^\w\.\-\/]//g;
$local_part =~ s/[^\w\.\-\/]//g;
$delivery_domain =~ s/[^\w\.\-\/]//g;
# If we have a parent_domain its probably a redirect
if ( $parent_domain && ( $parent_domain ne $delivery_domain || $parent_local_part ne $local_part ) ) {
# If the parent_domain is the primary_hostname its a localuser redirect
if ( my $local_user = $parent_domain eq $primary_hostname ? $parent_local_part : getdomainowner($parent_domain) ) {
my $local_uid = user2uid($local_user);
my $redirected_domain = $parent_domain eq $primary_hostname ? getusersdomain($parent_local_part) : $parent_domain;
if ($log) { Exim::log_write("SMTP connection identification D=$redirected_domain O=$parent_local_part\@$parent_domain E=$local_part\@$delivery_domain M=$message_exim_id U=$local_user ID=$local_uid B=redirect_resolver") }
; #replay for tailwatchd
$domain = $redirected_domain;
$sender = $parent_domain eq $primary_hostname ? $local_user : "$parent_local_part\@$parent_domain";
$sender_lookup_method = "redirect/forwarder owner $parent_local_part\@$parent_domain -> $local_part\@$delivery_domain";
}
}
}
if ( !$domain ) {
$sender_lookup_method = 'mailgid no entry from get_recent_authed_mail_ips_entry';
#Exim::log_write("!DEBUG! get_message_sender() failed to get the domain. However the sender domain claims to be $sender_address_domain");
}
}
else {
# FROM A SHELL OR CGI
$username = uid2user($uid);
if ($username) {
if ( $username eq 'nobody' ) {
if ($acl_c_vhost_owner) {
$username = uid2user($acl_c_vhost_owner);
}
$sender_lookup_method = 'uid via acl_c_vhost_owner from shell cgi: ' . $username . ' from: ' . $acl_c_vhost_owner_url;
}
else {
$sender_lookup_method = 'uid via shell cgi: ' . $username;
}
$domain = getusersdomain($username) || _SENDER_SYSTEM;
$sender = $username;
}
# If the sender owns the domain they are sending
# from we can trust it
if ( length $sender && $sender !~ tr/\@// ) {
( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method );
}
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from UID");
}
}
if ($domain) {
$domain =~ s/[^\w\.\-\/]//g;
$domain = lc $domain;
$counted_domain = $domain;
if ($sender) {
$sender =~ tr/+%:/@/;
$sender =~ s/[^\w\.\-\/\@]//g;
if ( $sender eq 'mailman' ) {
$is_mailman = 1;
$domain = lc Exim::expand_string('$sender_address_domain');
$sender_lookup_method .= '/mailman';
$sender = 'mailman@' . $domain;
$counted_domain = $domain if ( file_exists('/var/cpanel/email_send_limits/count_mailman') );
}
}
}
$sender_lookup = $sender;
if ( $log && $message_exim_id ) {
$username ||= ( ( $sender =~ tr{@}{} ) ? getdomainowner( ( split( m{@}, $sender ) )[1] ) : $sender );
if ($username) {
# Will log as 2017-05-26 13:42:22 1dEKBq-0007HB-6R Sender identification S=nick
Exim::log_write("Sender identification U=$username D=$domain S=$sender"); #replay for tailwatchd
}
}
return ( $sender, $domain, $counted_domain, $is_mailman );
}
sub get_message_sender_address {
return ( get_message_sender(@_) )[0];
}
sub enforce_mail_permissions {
$enforce_mail_permissions_data ? 1 : 0;
}
sub check_mail_permissions {
$check_mail_permissions_domain = undef;
#Exim::log_write("!DEBUG! running check_mail_permissions");
my $uid = int( Exim::expand_string('$originator_uid') );
$enforce_mail_permissions_data = ':fail: check_mail_permissions failed to complete or set a status';
$check_mail_permissions_result = '';
$check_mail_permissions_data = ':unknown:';
$check_mail_permissions_domain = '';
$check_mail_permissions_sender = '';
$check_mail_permissions_is_mailman = 0;
$nobody_uid ||= user2uid('nobody');
my $acl_c_vhost_owner = ( split( m{:}, Exim::expand_string('$acl_c_vhost_owner') || '' ) )[0];
my $acl_c_vhost_owner_known_user = ( $acl_c_vhost_owner && $acl_c_vhost_owner != $nobody_uid ) ? 1 : 0;
if ( $uid == $nobody_uid && !$acl_c_vhost_owner_known_user && file_exists('/etc/webspam') ) {
$enforce_mail_permissions_data = ':fail: Mail sent by user nobody being discarded due to sender restrictions in WHM->Tweak Settings';
$check_mail_permissions_result = "uid ($uid) is the nobody_uid ($nobody_uid) and /etc/webspam exists"; # for tests (only set when enforce_mail_permissions_data is empty)
return 'no';
}
my $gid = int( Exim::expand_string('$originator_gid') );
#MAILTRAP
if ( file_exists('/etc/eximmailtrap') ) {
$mailtrap_gid ||= int( ( getgrnam('mailtrap') )[2] );
$nobody_gid ||= int( ( getgrnam('nobody') )[2] );
if ( $uid >= $nobody_uid && $gid >= $nobody_gid && $gid != $mailtrap_gid ) {
$enforce_mail_permissions_data = ":fail: Gid $gid is not permitted to relay mail, or has directly called /usr/sbin/exim instead of /usr/sbin/sendmail.";
return 'no';
}
}
#MAILTRAP
if ( Exim::expand_string('$received_protocol') eq 'local' && isdemo($uid) ) {
$enforce_mail_permissions_data = ":fail: User with uid $uid is a demo user. You cannot send mail if your account is in demo mode.";
return 'no';
}
my $message_exim_id = Exim::expand_string('$message_exim_id');
if ( !$message_exim_id && !Exim::expand_string('$sender_address') ) {
$enforce_mail_permissions_data = ''; # permit normal acction
#Exim::log_write("!DEBUG! check_mail_permissions called without sender_address set from $sender_host_address (rcount: $recipients_count)");
$check_mail_permissions_result = "webspam check, mailtrap check, demo check passed and no sender_address"; # for tests (only set when enforce_mail_permissions_data is empty)
return 'no';
}
# real_domain is the domain of the actual sender
# domain is the domain we actually count the message against
# Currently these are always the same except domain may be
# rewritten if we are coming from a mailman list in order
# to count against the owner of the list instead of the mailman
# user assuming /var/cpanel/email_send_limits/count_mailman exists
my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 1 );
if ( $sender =~ m/^_archive\@/ ) {
$enforce_mail_permissions_data = ":fail: Archive Users are not permitted to send email. Message discarded.";
$check_mail_permissions_result = "get_message_sender returned an archive user";
return 'no';
}
if ( !Cpanel::Server::Type::Role::MailRelay->is_enabled() ) {
$enforce_mail_permissions_data = ":fail: This server does not relay mail.";
$check_mail_permissions_result = "This server does not relay mail.";
return 'no';
}
if ( !$domain || $domain eq '' ) {
my $sender_host_address = Exim::expand_string('$received_protocol') eq 'local' ? 'localhost' : Exim::expand_string('$sender_host_address');
my $recipients_count = Exim::expand_string('$recipients_count');
my $routed_domain = Exim::expand_string('$domain');
if ( $sender eq 'nobody' && file_exists('/etc/webspam') ) {
Exim::log_write("check_mail_permissions could not determine the sender domain for a nobody message [routed_domain=$routed_domain message_exim_id=$message_exim_id sender_host_address=$sender_host_address recipients_count=$recipients_count]") if $recipients_count && !getdomainowner($routed_domain);
$enforce_mail_permissions_data = ':fail: Mail sent by user nobody that cannot be linked to a user is being discarded due to sender restrictions in WHM->Tweak Settings';
$check_mail_permissions_result = "The sender of the message nobody and /etc/webspam exists"; # for tests (only set when enforce_mail_permissions_data is empty)
}
else {
Exim::log_write("check_mail_permissions could not determine the sender domain [routed_domain=$routed_domain message_exim_id=$message_exim_id sender_host_address=$sender_host_address recipients_count=$recipients_count]") if $recipients_count && !getdomainowner($routed_domain);
# If delivery is to a userdomain that its expected that we cannot get the sender domain
$enforce_mail_permissions_data = ''; # permit normal acction
$check_mail_permissions_result = "get_message_sender returned no domain"; # for tests (only set when enforce_mail_permissions_data is empty)
}
return 'no';
}
else {
if ( !$message_exim_id ) {
#Exim::log_write("check_mail_permissions !DEBUG! got the domain ($domain) of a message before the message id!");
}
}
#Exim::log_write("check_mail_permissions !DEBUG! found sender domain of message: $message_exim_id to be $domain with sender [$sender]");
$check_mail_permissions_msgid = $message_exim_id if $message_exim_id;
$check_mail_permissions_domain = $domain if $domain;
$check_mail_permissions_sender = $sender if $sender;
$check_mail_permissions_is_mailman = $is_mailman;
if ( $domain && $domain ne _SENDER_SYSTEM ) {
my $now;
# Just before we check to see if we've exceeded the allowable mail counts for this domain,
# check to see if we need to notify the admin about someone exceeding the warning level
my $mail_count = get_current_emails_per_day($domain) + 1; # +1 for the one we're *about* to send, but haven't yet!
my $emails_to_notify = get_email_daily_limit_notify();
if ( ( $emails_to_notify > 0 ) && ( $mail_count > $emails_to_notify ) ) {
if ( !file_exists( '/var/cpanel/email_send_limits/daily_notify/' . $domain ) ) {
create_daily_notify_touchfile($domain);
Exim::log_write("check_mail_permissions Hit daily email notify limit for domain $domain");
}
}
if ( file_exists( '/var/cpanel/email_send_limits/max_deferfail_' . $domain ) ) {
local $/;
my $limit_data;
if ( open( my $email_fh, '<', '/var/cpanel/email_send_limits/max_deferfail_' . $domain ) ) {
$limit_data = readline($email_fh);
close($email_fh);
}
my ( $currentmail, $maxmails, $percentage ) = $limit_data =~ /([0-9]+)\/([0-9]+)\s+\(([0-9]+)/;
$currentmail ||= 'unknown';
$maxmails ||= 'unknown';
$percentage ||= 100;
$enforce_mail_permissions_data = ":fail: Domain $domain has exceeded the max defers and failures per hour ($currentmail/$maxmails ($percentage\%)) allowed. Message discarded.";
return 'no';
}
elsif ( my $maxmails = getmaxemailsperhour($domain) ) {
my $currentmail = get_current_emails_per_hour( $domain, ( $now ||= time() ) );
if ( $currentmail >= $maxmails ) {
my $cutoff_percentage = get_email_send_limits_defer_cutoff();
my $percentage = int( ( $currentmail / $maxmails ) * 100 );
if ( $percentage >= $cutoff_percentage ) {
$enforce_mail_permissions_data = ":fail: Domain $domain has exceeded the max emails per hour ($currentmail/$maxmails ($percentage\%)) allowed. Message discarded.";
return 'no';
}
else {
increment_max_emails_per_hour( $domain, ( $now ||= time() ), $message_exim_id ); # need to count it because we will try it later
# this will result in percentages above 100% which may be confusing however correct
# this is how we decide to defer or fail the message
return _check_mail_permission_defer_with_message("Domain $domain has exceeded the max emails per hour ($currentmail/$maxmails ($percentage\%)) allowed. $reattempt_message");
}
}
}
if ( domain_has_outgoing_mail_suspended($domain) ) {
# We already check this in the ACL, however if the sender domain
# is forged we have to check it again here to ensure that
# we are checking against the actual sender and not the
# domain in the from: field
$enforce_mail_permissions_data = ":fail: Domain $domain has an outgoing mail suspension. Message discarded.";
return 'no';
}
elsif ( domain_has_outgoing_mail_hold($domain) ) {
track_held_message($domain);
return _check_mail_permission_defer_with_message("Domain $domain has an outgoing mail hold. $reattempt_message");
}
elsif ($sender) {
if ( user_has_outgoing_mail_suspended($sender) ) {
# We already check this in the ACL, however if the sender domain
# is forged we have to check it again here to ensure that
# we are checking against the actual sender and not the
# domain in the from: field
$enforce_mail_permissions_data = ":fail: Sender $sender has an outgoing mail suspension. Message discarded.";
return 'no';
}
elsif ( user_has_outgoing_mail_hold($sender) ) {
track_held_message($sender);
return _check_mail_permission_defer_with_message("Sender $sender has an outgoing mail hold. $reattempt_message");
}
}
}
$enforce_mail_permissions_data = ''; # permit normal action
$check_mail_permissions_result = "reached end of check_mail_permissions"; # for tests (only set when enforce_mail_permissions_data is empty)
return 'no';
}
sub _check_mail_permission_defer_with_message {
my ($message) = @_;
my $message_body = Exim::expand_string('$message_body');
my $message_body_size = Exim::expand_string('$message_body_size');
my $message_body_length = length($message_body);
$check_mail_permissions_data =
qq{# Exim filter\n\nunseen mail }
. ( $check_mail_permissions_sender ? qq{to } . Cpanel::Encoder::Exim::unquoted_encode_string_literal($check_mail_permissions_sender) . qq{\n} : '' )
. q{subject "Mail delivery deferred: returning message to sender" }
. q{from "Mail Delivery System <Mailer-Daemon@$primary_hostname>" }
. q{text "This message was created automatically by mail delivery software.\n} . q{\n}
. q{A message that you sent could not be delivered to one or more of its\n}
. q{recipients. This is a temporary error. The following address(es) deferred:\n} . q{\n}
. q{ $local_part@$domain\n}
. qq{ $message} . q{\n\n}
. q{------- This is a copy of the message, including all the headers. ------\n}
. ( ( $message_body_length < $message_body_size ) ? ( q{------ The body of the message is $message_body_size characters long; only the first\n} . q{------ } . $message_body_length . q{ or so are included here.\n} ) : () )
. q{$message_headers\n\n}
. q{$message_body"}
. qq{\nfinish};
$enforce_mail_permissions_data = ":defer: \"$message\"";
return 'yes';
}
sub domain_has_outgoing_mail_hold {
my ($domain) = @_;
my $user = getdomainowner($domain);
if ( $user && user_has_outgoing_mail_hold($user) ) {
return 1;
}
return 0;
}
sub domain_has_outgoing_mail_suspended {
my ($domain) = @_;
my $user = getdomainowner($domain);
if ( $user && user_has_outgoing_mail_suspended($user) ) {
return 1;
}
return 0;
}
sub user_has_outgoing_mail_suspended {
my ($user) = @_;
if ( -e '/etc/outgoing_mail_suspended_users' ) {
return user_exists_in_db( $user, '/etc/outgoing_mail_suspended_users' );
}
return 0;
}
sub user_has_outgoing_mail_hold {
my ($user) = @_;
if ( -e '/etc/outgoing_mail_hold_users' ) {
return user_exists_in_db( $user, '/etc/outgoing_mail_hold_users' );
}
return 0;
}
sub check_outgoing_mail_suspended {
if ( !Cpanel::Server::Type::Role::MailSend->is_enabled() && Exim::expand_string('$sender_host_address') ) {
$outgoing_mail_suspended_message = "This server does not relay mail.";
return 1;
}
my $uid = int( Exim::expand_string('$originator_uid') );
my $gid = int( Exim::expand_string('$originator_gid') );
my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 0 );
if ( $real_domain && $real_domain ne _SENDER_SYSTEM && domain_has_outgoing_mail_suspended($real_domain) ) {
$outgoing_mail_suspended_message = "Outgoing mail from \"$real_domain\" has been suspended.";
return 1;
}
elsif ( $sender && user_has_outgoing_mail_suspended($sender) ) {
$outgoing_mail_suspended_message = "Outgoing mail from \"$sender\" has been suspended.";
return 1;
}
return 0;
}
sub get_outgoing_mail_suspended_message {
return $outgoing_mail_suspended_message;
}
sub increment_max_emails_per_hour_if_needed {
# Exim::log_write("!DEBUG! increment_max_emails_per_hour_if_needed entered");
if ( $check_mail_permissions_domain && $check_mail_permissions_domain ne _SENDER_SYSTEM ) {
if ( Exim::expand_string('${if first_delivery{1}{0}}') || ( $check_mail_permissions_msgid && _get_last_delivery_message($check_mail_permissions_msgid) =~ m/$reattempt_message/o ) ) {
# if FIRST_DELIVERY or last line of msglog is our $reattempt_message
# example == f@kos.net R=check_mail_permissions defer (-1): Domain pigdog.org has exceeded the max emails per hour (12/10 (120%)) allowed. Message will be reattempted later
# we need to tell the next function to charge us for the message since it was deferred before and we did not get here
# Exim::log_write("!DEBUG! increment_max_emails_per_hour=$check_mail_permissions_domain msgid=$check_mail_permissions_msgid");
increment_max_emails_per_hour( $check_mail_permissions_domain, time(), $check_mail_permissions_msgid );
}
}
return 'no';
}
sub store_spam {
my $sender_host_address = shift;
my $spam_score = shift;
my $now = time();
open( my $spam_fh, '>>', '/var/cpanel/spamstore' );
#uncomment to deploy
# syswrite($spam_fh, $now . ':' . $sender_host_address . ':' . $spam_score . ":.\n");
close($spam_fh);
}
sub _get_last_delivery_message {
my $message_exim_id = shift;
my ( $last_message, $msglog_file, $msglog_size );
my $spool_directory = Exim::expand_string('$spool_directory');
my $spool_split_directory = substr( ( split( /-/, $message_exim_id ) )[0], -1, 1 );
if ( file_exists("$spool_directory/msglog/$spool_split_directory/$message_exim_id") ) { #split spool
$msglog_size = ( stat(_) )[7];
$msglog_file = "$spool_directory/msglog/$spool_split_directory/$message_exim_id";
}
elsif ( file_exists("$spool_directory/msglog/$message_exim_id") ) { #not split
$msglog_size = ( stat(_) )[7];
$msglog_file = "$spool_directory/msglog/$message_exim_id";
}
if ( $msglog_file && open( my $msg_log_fh, '<', $msglog_file ) ) {
seek( $msg_log_fh, $msglog_size - 4096, 0 ) if $msglog_size > 8192;
local $/;
$last_message = ( split( /\n/, readline($msg_log_fh) ) )[-1];
}
# Exim::log_write("!DEBUG! _get_last_delivery_message for [$message_exim_id] is $last_message");
return $last_message || '';
}
sub resolve_authenticated_sender {
my ( $sender, $domain, $sender_lookup_method ) = @_;
my $sender_address = Exim::expand_string('$sender_address');
my $sender_address_domain = Exim::expand_string('$sender_address_domain');
# We only want to use the sender in the from header if they have already
# authenticated with at least the permissions of the account
my ( $from_h_sender, $from_h_localpart, $from_h_domain ) = _get_from_h_sender();
$primary_hostname ||= Exim::expand_string('$primary_hostname');
# The user expects to be able to just set the From: headers
# we try to accomodate that first if they have permissions on the account
if ( $from_h_domain eq $primary_hostname ) {
$sender_lookup_method .= "/primary_hostname/system user";
}
elsif ( $sender eq getdomainowner($from_h_domain) ) {
$sender = $from_h_localpart . '@' . $from_h_domain;
$domain = $from_h_domain;
$sender_lookup_method .= "/from_h";
}
# otherwise we fallback to the sender_address_domain
elsif ( $sender eq getdomainowner($sender_address_domain) ) {
$sender = $sender_address;
$domain = $sender_address_domain;
$sender_lookup_method .= "/sender_address_domain";
}
else {
# finally we accept that we don't know who sent it besdies the
# authenticated user
$sender_lookup_method .= "/only user confirmed/virtual account not confirmed";
}
return ( $sender, $domain, $sender_lookup_method );
}
sub resolve_vhost_owner {
if ( file_exists('/var/cpanel/config/email/trust_x_php_script') ) {
if ( my $x_php_script = Exim::expand_string('$h_x-php-script:') ) {
#X-PHP-Script: <servername><php-self> for <remote-addr>
#X-PHP-Script: www.example.com/~user/testapp/send-mail.php for 10.0.0.1
my ( $servername, $uri ) = split( m{/}, $x_php_script, 2 );
if ( $uri =~ m/^\/?\~([^\/\s]+)/ ) {
my $http_user = $1;
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=trust_x_php_script");
return $uid . ':' . '//' . $servername . '/' . $uri . ' ';
}
elsif ( my $http_user = getdomainowner($servername) ) {
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=trust_x_php_script");
return $uid . ':' . '//' . $servername . '/' . $uri . ' ';
}
}
}
if ( file_exists('/var/cpanel/config/email/query_apache_for_nobody_senders') ) {
# Lets lookup the real uid by querying apache
require Cpanel::ProcessInfo;
require Cpanel::ApacheServerStatus;
my $server_status = Cpanel::ApacheServerStatus->new();
my $httpd_pid;
my $http_status_data;
my $current_pid = $$;
while ( ( $current_pid = Cpanel::ProcessInfo::get_parent_pid($current_pid) ) && $current_pid != 1 ) {
if ( my $status_data = $server_status->get_status_by_pid($current_pid) ) {
$httpd_pid = $current_pid;
$http_status_data = $status_data;
last;
}
}
if ($http_status_data) {
my $uri = ( split( /\s+/, $http_status_data->{'request'} ) )[1];
if ( $uri =~ m/^\/?\~([^\/\s]+)/ ) {
my $http_user = $1;
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=query_apache_for_nobody_senders");
return $uid . ':' . '//' . $http_status_data->{'vhost'} . $uri . ' ';
}
elsif ( my $http_user = getdomainowner( $http_status_data->{'vhost'} ) ) {
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=query_apache_for_nobody_senders");
return $uid . ':' . '//' . $http_status_data->{'vhost'} . $uri . ' ';
}
}
}
return;
}
# Obtain the from header from the message
# We fallback to the envelope sender if there
# is no from header set (ie sendmail -bt or missing From header)
sub _get_from_h_sender {
my $from_h_domain = Exim::expand_string('${domain:$h_from:}');
my $from_h_local_part = Exim::expand_string('${local_part:$h_from:}');
if ( length $from_h_local_part ) {
if ( length $from_h_domain ) {
return ( $from_h_local_part . '@' . $from_h_domain, $from_h_local_part, $from_h_domain );
}
else {
$primary_hostname ||= Exim::expand_string('$primary_hostname');
return ( $from_h_local_part . '@' . $primary_hostname, $from_h_local_part, $primary_hostname );
}
}
else {
# Handle fallback to sender_address when message is missing a from header
my $sender_address_domain = Exim::expand_string('$sender_address_domain');
my $sender_address_local_part = Exim::expand_string('$sender_address_local_part');
return ( $sender_address_local_part . '@' . $sender_address_domain, $sender_address_local_part, $sender_address_domain );
}
}
my $email_holds_dir = '/var/cpanel/email_holds';
sub track_held_message {
my ($holder) = @_;
if ( -1 != index( $holder, '/' ) ) {
warn "Holder “$holder” should not have “/” in it!";
$holder =~ s/\///g; #jic
}
my $message_exim_id = Exim::expand_string('$message_exim_id');
_check_hold_dir($holder);
my $path = "$email_holds_dir/track/$holder/$message_exim_id";
if ( !-e $path ) {
if ( $! == _ENOENT() ) {
open( my $fh, '>>', $path ) or do {
warn "open(>>, $path): $!";
};
}
else {
warn "stat($path): $!";
}
}
return 1;
}
sub _mkdir_if_not_exists_or_warn {
my ( $path, $mode ) = @_;
mkdir( $path, $mode ) or do {
if ( $! != _EEXIST() ) {
warn "mkdir($path, $mode): $!";
}
return undef;
};
return 1;
}
sub _check_hold_dir {
my ($holder) = @_;
if ( !-e "$email_holds_dir/track/$holder" ) {
if ( $! == _ENOENT() ) {
_mkdir_if_not_exists_or_warn( $email_holds_dir, 0751 );
_mkdir_if_not_exists_or_warn( "$email_holds_dir/track", 0750 );
_mkdir_if_not_exists_or_warn( "$email_holds_dir/track/$holder", 0750 );
}
else {
warn "stat($email_holds_dir/track/$holder): $!";
}
}
return;
}
=head2 maskdir($dir)
This function converts a path on the system to a path relative to the users home directory that it contains. The relative path is prefixed with the user's primary domain in the below format:
domain.tld:/public_html/cgi-bin/xyz.cgi
If the path is not contained within a user's home directory, the path is returned without modification.
=cut
sub maskdir {
my ($dir) = @_;
# Try the user first
my $maskeddir = $dir;
my ($likely_user) = ( split( m{/}, $dir ) )[2];
if ( my $likely_homedir = gethomedir($likely_user) ) {
chop $likely_homedir if substr( $likely_homedir, -1 ) eq '/';
if ( rindex( $dir, "$likely_homedir/", 0 ) == 0 ) {
substr( $maskeddir, 0, length($likely_homedir), getusersdomain($likely_user) . ":" );
return $maskeddir;
}
}
# Next try all users in /etc/passwd
if ( open my $passwd_fh, '<', "/etc/passwd" ) {
while ( readline($passwd_fh) ) {
my ( $homedir, $uid, $user ) = ( split( /:/, $_ ) )[ 0, 2, 5 ];
next if $uid < 100 || length $homedir < 3;
chop $homedir if substr( $homedir, -1 ) eq '/';
if ( rindex( $dir, "$homedir/", 0 ) == 0 ) {
substr( $maskeddir, 0, length($homedir), getusersdomain($user) . ":" );
return $maskeddir;
}
}
}
else {
warn "open(/etc/passwd): $!";
}
return $dir;
}
sub extract_hosts_from_route_list_item {
my $item = shift;
my (undef, $hosts, undef) = Exim::parse_route_item($item);
return $hosts;
}
sub convert_to_hostlist_item {
my ($item, $separator) = @_;
$separator //= '\n';
$item =~ s/^\s+//;
$item =~ s/\s+$//;
# Ignore group separator:
if ($item eq '+') {
$item = '';
}
# Extract bracketed IP address:
elsif ( $item !~ s/^\[(\S*)\]:\d+$/$1/ ) {
# If nothing subbed, what's left is an unbracketed IPv4 or a hostname.
# Remove port if present:
$item =~ s/:\d+$//;
# Finally, if the hostname specified /mx, do a lookup of its MX records and sub in the entire list:
if ($item =~ s{^(\S+)/mx$}{$1}i) {
$item = Exim::expand_string('${lookup dnsdb{>' . $separator . ' mxh=' . $item . '}{$value}}');
}
}
return $item;
}
sub get_suspended_shell {
my ($user) = @_;
my $passwd_file_shell = Exim::expand_string( '${extract{6}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}}}}' );
if ( !length($passwd_file_shell) ) {
return '';
}
if ( $passwd_file_shell ne '/bin/false' ) {
return $passwd_file_shell;
}
if ( open my $fh, '<', "/var/cpanel/suspendinfo/${user}" ) {
while ( my $ln = readline($fh) ) {
if ( $ln =~ m{\Ashell=\s*(\S+)} ) {
close $fh;
return $1;
}
}
close $fh;
}
return '/usr/local/cpanel/bin/noshell';
}
sub is_temp_domain {
my ($domain) = @_;
if ( length $domain ) {
require Cpanel::IP::AutoDomain::TemporaryDomain::Check;
return 1 if Cpanel::IP::AutoDomain::TemporaryDomain::Check::domain_is_temporary_subdomain($domain);
}
return 0;
}
# Untaint a string for exim. This is not a perl untaint
sub untaint {
return $_[0];
}
require Cpanel::Encoder::Exim;
require Cpanel::Server::Type::Role::MailRelay;
require Cpanel::Server::Type::Role::MailSend;
1;
BEGIN { # Suppress load of all of these at earliest point.
$INC{'cPstrict.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Encoder/Exim.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ExceptionMessage.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Locale/Utils/Fallback.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ExceptionMessage/Raw.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/LoadModule/Utils.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ScalarUtil.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Exception/CORE.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Pack.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Pack/Template.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/IP/v4.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/IP.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/IP/Expand.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/Expand.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Linux/Netlink.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Linux/Proc/Net/Tcp.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Ident.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie/CORE/exists.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie/CORE/exists_nofollow.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie/More/Lite.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Services/Enabled/Spamd.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FileUtils/Dir.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/DKIM/ValidityCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Context.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ProcessInfo.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Fcntl/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Socket/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Hulk/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ApacheServerStatus.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Time/Local.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Fcntl.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FileUtils/Open.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Parser/Vars.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Encoder/Tiny/Rare.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Encoder/Tiny.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Regex.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Carp.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Set.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/TimeHiRes.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SafeFileLock.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FHUtils/Tiny.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Debug.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Hash.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SafeFile/LockInfoCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SafeFile/LockWatcher.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Syscall.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Inotify.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SafeFile.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/LoadModule.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Linux/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/FilesystemNodeName.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Notify.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Utils.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Logger.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Sys/Uname.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Sys/Hostname/Fallback.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/LoadFile/ReadFast.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/LoadFile.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Sys/Hostname.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Hostname.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/NAT/Object.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/NAT.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Struct/Common/Time.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Struct/timespec.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/NanoStat.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/NanoUtime.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/HiRes.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Path/Normalize.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/JSON/Unicode.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Encoder/ASCII.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/UTF8/Strict.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/JSON.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/JSON/FailOK.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Hash/Stringify.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Destruct.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Finally.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Readlink.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FileUtils/Write.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FileUtils/Write/JSON/Lazy.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/AdminBin/Serializer.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/AdminBin/Serializer/FailOK.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SV.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Umask.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Config/LoadConfig.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Config/LoadWwwAcctConf.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/StatCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/NSCD/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Socket/UNIX/Micro.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/NSCD/Check.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/PwCache/Helpers.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/PwCache/Cache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/PwCache/Find.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/PwCache/Build.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/PwCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SafeDir/MK.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/CachedCommand/Utils.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FindBin.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/CachedCommand/Valid.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/CachedCommand/Save.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/LocaleString.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Errno.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Config/Constants/Perl.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ChildErrorStringifier.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Env.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FHUtils/Autoflush.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FHUtils/OS.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FHUtils/Blocking.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IO/Flush.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ReadMultipleFH.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ForkAsync.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SafeRun/Object.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/SafeRun/Env.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/CachedCommand.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/GlobalCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/NonlocalBind/Cache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FileUtils/TouchFile.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Linux/NetlinkConstants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Linux/RtNetlink.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/Loopback.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/Configured.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/Bound.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/DIp/MainIP.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/StringFunc/Trim.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Encoder/Punycode.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/Domain/Tiny.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/Domain/Normalize.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/AutoDomain/Base.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Version/Tiny.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Version/Full.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Version/Compare.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Version.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/AutoDomain.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/AutoDomain/TemporaryDomain/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/YAML/Syck.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/YAML.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Transaction/File/Read/YAML.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Transaction/File/BaseReader.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Transaction/File/YAMLReader.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Transaction/File/Read/JSON.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Transaction/File/JSONReader.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/AutoDomain/TemporaryDomain/Check.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Profile/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/AnyAllMatcher.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Profile.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role/EnabledCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role/TouchFileRole.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role/MailRelay.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role/MailSend.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
}
{ # --- BEGIN cPstrict
package cPstrict;
# Copyright 2024 WebPros International, LLC
# All rights reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited.
use strict;
use warnings;
=pod
This is importing the following to your namespace
use strict;
use warnings;
use v5.30;
use feature 'signatures';
no warnings 'experimental::signatures';
=cut
sub import {
if ( $] < 5.030 ) {
require Carp;
Carp::confess("cPstrict is being loaded from an unsupported perl ($^X)");
}
# auto import strict and warnings to our caller
warnings->import();
strict->import();
require feature;
feature->import( ':5.30', 'signatures' );
warnings->unimport('experimental::signatures');
return;
}
1;
} # --- END cPstrict
{ # --- BEGIN Cpanel/Encoder/Exim.pm
package Cpanel::Encoder::Exim;
my %encodes = (
q{\\} => q{\\\\\\\\}, #\ -> \\\\
q{"} => q{\\"}, #" -> \"
q{$} => q{\\\\$}, #$ -> \\$
"\x0a" => q{\\n}, #newline -> \n
"\x0d" => q{\\r}, #carriage return -> \r
"\x09" => q{\\t}, #tab => \t
);
sub encode_string_literal {
return if !defined $_[0];
return q{"} . join( q{}, map { $encodes{$_} || $_ } split( m{}, $_[0] ) ) . q{"};
}
sub unquoted_encode_string_literal {
my $string = shift;
return if !defined $string;
$string =~ s/\\N/\\N\\\\N\\N/g; # Only use / here for perl compat
return "\\N$string\\N";
}
1;
} # --- END Cpanel/Encoder/Exim.pm
{ # --- BEGIN Cpanel/ExceptionMessage.pm
package Cpanel::ExceptionMessage;
use strict;
# use Cpanel::Exception (); # perlpkg line 211
*load_perl_module = \&Cpanel::Exception::load_perl_module;
1;
} # --- END Cpanel/ExceptionMessage.pm
{ # --- BEGIN Cpanel/Locale/Utils/Fallback.pm
package Cpanel::Locale::Utils::Fallback;
use strict;
use warnings;
no warnings 'once';
sub interpolate_variables {
my ( $str, @maketext_opts ) = @_;
my $c = 1;
my %h = map { $c++, $_ } @maketext_opts;
$str =~ s{(\[(?:[^_]+,)?_([0-9])+\])}{$h{$2}}g;
return $str;
}
1;
} # --- END Cpanel/Locale/Utils/Fallback.pm
{ # --- BEGIN Cpanel/ExceptionMessage/Raw.pm
package Cpanel::ExceptionMessage::Raw;
use strict;
use warnings;
no warnings 'once';
# use base Cpanel::ExceptionMessage (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::ExceptionMessage); }
# use Cpanel::Locale::Utils::Fallback (); # perlpkg line 211
sub new {
my ( $class, $str ) = @_;
my $str_copy = $str;
return bless( \$str_copy, $class );
}
sub to_string {
my ($self) = @_;
return $$self;
}
sub get_language_tag {
return 'en';
}
BEGIN {
*Cpanel::ExceptionMessage::Raw::convert_localized_to_raw = *Cpanel::Locale::Utils::Fallback::interpolate_variables;
*Cpanel::ExceptionMessage::Raw::to_locale_string = *Cpanel::ExceptionMessage::Raw::to_string;
*Cpanel::ExceptionMessage::Raw::to_en_string = *Cpanel::ExceptionMessage::Raw::to_string;
}
1;
} # --- END Cpanel/ExceptionMessage/Raw.pm
{ # --- BEGIN Cpanel/LoadModule/Utils.pm
package Cpanel::LoadModule::Utils;
use strict;
use warnings;
no warnings 'once';
sub module_is_loaded {
my $p = module_path( $_[0] );
return 0 unless defined $p;
return defined $INC{$p} ? 1 : 0;
}
sub module_path {
my ($module_name) = @_;
if ( defined $module_name && length($module_name) ) {
substr( $module_name, index( $module_name, '::' ), 2, '/' ) while index( $module_name, '::' ) > -1;
$module_name .= '.pm' unless substr( $module_name, -3 ) eq '.pm';
}
return $module_name;
}
sub is_valid_module_name {
return $_[0] =~ m/\A[A-Za-z_]\w*(?:(?:'|::)\w+)*\z/ ? 1 : 0;
}
1;
} # --- END Cpanel/LoadModule/Utils.pm
{ # --- BEGIN Cpanel/ScalarUtil.pm
package Cpanel::ScalarUtil;
use strict;
use warnings;
no warnings 'once';
sub blessed {
return ref( $_[0] ) && UNIVERSAL::isa( $_[0], 'UNIVERSAL' ) || undef;
}
1;
} # --- END Cpanel/ScalarUtil.pm
{ # --- BEGIN Cpanel/Exception/CORE.pm
package Cpanel::Exception::CORE;
1;
package Cpanel::Exception;
use strict;
BEGIN {
$INC{'Cpanel/Exception.pm'} = '__BYPASSED__';
}
our $_SUPPRESS_STACK_TRACES = 0;
our $_EXCEPTION_MODULE_PREFIX = 'Cpanel::Exception';
our $IN_EXCEPTION_CREATION = 0;
our $_suppressed_msg = '__STACK_TRACE_SUPPRESSED__YOU_SHOULD_NEVER_SEE_THIS_MESSAGE__';
my $PACKAGE = 'Cpanel::Exception';
my $locale;
my @ID_CHARS = qw( a b c d e f g h j k m n p q r s t u v w x y z 2 3 4 5 6 7 8 9 );
my $ID_LENGTH = 6;
# use Cpanel::ExceptionMessage::Raw (); # perlpkg line 211
# use Cpanel::LoadModule::Utils (); # perlpkg line 211
use constant _TRUE => 1;
use overload (
'""' => \&__spew,
bool => \&_TRUE,
fallback => 1,
);
BEGIN {
die "Cannot compile Cpanel::Exception::CORE" if $INC{'B/C.pm'} && $0 !~ m{cpkeyclt|cpsrvd\.so|t/large};
}
sub _init { return 1 } # legacy
sub create {
my ( $exception_type, @args ) = @_;
_init();
if ($IN_EXCEPTION_CREATION) {
_load_cpanel_carp();
die 'Cpanel::Carp'->can('safe_longmess')->("Attempted to create a “$exception_type” exception with arguments “@args” while creating exception “$IN_EXCEPTION_CREATION->[0]” with arguments “@{$IN_EXCEPTION_CREATION->[1]}”.");
}
local $IN_EXCEPTION_CREATION = [ $exception_type, \@args ];
if ( $exception_type !~ m/\A[A-Za-z0-9_]+(?:\:\:[A-Za-z0-9_]+)*\z/ ) {
die "Invalid exception type: $exception_type";
}
my $perl_class;
if ( $exception_type eq __PACKAGE__ ) {
$perl_class = $exception_type;
}
else {
$perl_class = "${_EXCEPTION_MODULE_PREFIX}::$exception_type";
}
_load_perl_module($perl_class) unless $perl_class->can('new');
if ( $args[0] && ref $args[0] eq 'ARRAY' && scalar @{ $args[0] } > 1 ) {
$args[0] = { @{ $args[0] } };
}
return $perl_class->new(@args);
}
sub create_raw {
my ( $class, $msg, @extra_args ) = @_;
_init();
my $msg_obj = 'Cpanel::ExceptionMessage::Raw'->new($msg);
if ( $class =~ m<\A(?:\Q${_EXCEPTION_MODULE_PREFIX}::\E)?Collection\z> ) {
die "Use create('Collection', ..) to create a Cpanel::Exception::Collection object.";
}
return create( $class, $msg_obj, @extra_args );
}
sub _load_perl_module {
my ($module) = @_;
local ( $!, $@ );
if ( !defined $module ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a module name.") );
}
return 1 if Cpanel::LoadModule::Utils::module_is_loaded($module);
my $module_name = $module;
$module_name =~ s{\.pm$}{};
if ( !Cpanel::LoadModule::Utils::is_valid_module_name($module_name) ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a valid module name: '$module_name'.") );
}
{
eval qq{use $module (); 1 }
or die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module cannot load '$module_name': $@") )
}
return 1;
}
sub new {
my ( $class, @args ) = @_;
@args = grep { defined } @args;
my $self = {};
bless $self, $class;
if ( ref $args[-1] eq 'HASH' ) {
$self->{'_metadata'} = pop @args;
}
if ( defined $self->{'_metadata'}->{'longmess'} ) {
$self->{'_longmess'} = &{ $self->{'_metadata'}->{'longmess'} }($self)
if $self->{'_metadata'}->{'longmess'};
}
elsif ($_SUPPRESS_STACK_TRACES) {
$self->{'_longmess'} = $_suppressed_msg;
}
else {
if ( !$INC{'Carp.pm'} ) { _load_carp(); }
$self->{'_longmess'} = scalar do {
local $Carp::CarpInternal{'Cpanel::Exception'} = 1;
local $Carp::CarpInternal{$class} = 1;
'Carp'->can('longmess')->();
};
}
_init();
$self->{'_auxiliaries'} = [];
if ( UNIVERSAL::isa( $args[0], 'Cpanel::ExceptionMessage' ) ) {
$self->{'_message'} = shift @args;
}
else {
my @mt_args;
if ( @args && !ref $args[0] ) {
@mt_args = ( shift @args );
if ( ref $args[0] eq 'ARRAY' ) {
push @mt_args, @{ $args[0] };
}
}
else {
$self->{'_orig_mt_args'} = $args[0];
my $phrase = $self->_default_phrase( $args[0] );
if ($phrase) {
if ( ref $phrase ) {
@mt_args = $phrase->to_list();
}
else {
$self->{'_message'} = Cpanel::ExceptionMessage::Raw->new($phrase);
return $self;
}
}
}
if ( my @extras = grep { !ref } @args ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("Extra scalar(s) passed to $PACKAGE! (@extras)") );
}
if ( !length $mt_args[0] ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("No args passed to $PACKAGE constructor!") );
}
$self->{'_mt_args'} = \@mt_args;
}
return $self;
}
sub get_string {
my ( $exc, $no_id_yn ) = @_;
return get_string_no_id($exc) if $no_id_yn;
return _get_string( $exc, 'to_string' );
}
sub get_string_no_id {
my ($exc) = @_;
return _get_string( $exc, 'to_string_no_id' );
}
sub _get_string {
my ( $exc, $cp_exc_stringifier_name ) = @_;
return $exc if !ref $exc;
{
local $@;
my $ret = eval { $exc->$cp_exc_stringifier_name() };
return $ret if defined $ret && !$@ && !ref $ret;
}
if ( ref $exc eq 'HASH' && $exc->{'message'} ) {
return $exc->{'message'};
}
if ( $INC{'Cpanel/YAML.pm'} ) {
local $@;
my $ret = eval { 'Cpanel::YAML'->can('Dump')->($exc); };
return $ret if defined $ret && !$@;
}
if ( $INC{'Cpanel/JSON.pm'} ) {
local $@;
my $ret = eval { 'Cpanel::JSON'->can('Dump')->($exc); };
return $ret if defined $ret && !$@;
}
return $exc;
}
sub _create_id {
srand();
return join(
q<>,
map { $ID_CHARS[ int rand( 0 + @ID_CHARS ) ]; } ( 1 .. $ID_LENGTH ),
);
}
sub get_stack_trace_suppressor {
return Cpanel::Exception::_StackTraceSuppression->new();
}
sub set_id {
my ( $self, $new_id ) = @_;
$self->{'_id'} = $new_id;
return $self;
}
sub id {
my ($self) = @_;
return $self->{'_id'} ||= _create_id();
}
sub set {
my ( $self, $key ) = @_;
$self->{'_metadata'}{$key} = $_[2];
if ( exists $self->{'_orig_mt_args'} ) {
my $phrase = $self->_default_phrase( $self->{'_orig_mt_args'} );
if ($phrase) {
if ( ref $phrase ) {
$self->{'_mt_args'} = [ $phrase->to_list() ];
undef $self->{'_message'};
}
else {
$self->{'_message'} = Cpanel::ExceptionMessage::Raw->new($phrase);
}
}
}
return $self;
}
sub get {
my ( $self, $key ) = @_;
my $v = $self->{'_metadata'}{$key};
if ( my $reftype = ref $v ) {
local $@;
if ( $reftype eq 'HASH' ) {
$v = { %{$v} }; # shallow copy
}
elsif ( $reftype eq 'ARRAY' ) {
$v = [ @{$v} ]; # shallow copy
}
elsif ( $reftype eq 'SCALAR' ) {
$v = \${$v}; # shallow copy
}
else {
local ( $@, $! );
require Cpanel::ScalarUtil;
if ( $reftype ne 'GLOB' && !Cpanel::ScalarUtil::blessed($v) ) {
warn if !eval {
_load_perl_module('Clone') if !$INC{'Clone.pm'};
$v = 'Clone'->can('clone')->($v);
};
}
}
}
return $v;
}
sub get_all_metadata {
my $self = shift;
my %metadata_copy;
for my $key ( keys %{ $self->{'_metadata'} } ) {
$metadata_copy{$key} = $self->get($key);
}
return \%metadata_copy;
}
my $loaded_LocaleString;
sub _require_LocaleString {
return $loaded_LocaleString ||= do {
local $@;
eval 'require Cpanel::LocaleString; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand
1;
};
}
my $loaded_ExceptionMessage_Locale;
sub _require_ExceptionMessage_Locale {
return $loaded_ExceptionMessage_Locale ||= do {
local $@;
eval 'require Cpanel::ExceptionMessage::Locale; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand
1;
};
}
sub _default_phrase {
_require_LocaleString();
return 'Cpanel::LocaleString'->new( 'An unknown error in the “[_1]” package has occurred.', scalar ref $_[0] ); # PPI NO PARSE - loaded above
}
sub longmess {
my ($self) = @_;
return '' if $self->{'_longmess'} eq $_suppressed_msg;
_load_cpanel_carp() if !$INC{'Cpanel/Carp.pm'};
return Cpanel::Carp::sanitize_longmess( $self->{'_longmess'} );
}
sub to_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_string_no_id() );
}
sub to_string_no_id {
my ($self) = @_;
my $string = $self->to_locale_string_no_id();
if ( $self->_message()->get_language_tag() ne 'en' ) {
my $en_string = $self->to_en_string_no_id();
$string .= "\n$en_string" if ( $en_string ne $string );
}
return $string;
}
sub _apply_id_prefix {
my ( $id, $msg ) = @_;
return sprintf "(XID %s) %s", $id, $msg;
}
sub to_en_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_en_string_no_id() );
}
sub to_en_string_no_id {
my ($self) = @_;
return $self->_message()->to_en_string() . $self->_stringify_auxiliaries('to_en_string');
}
sub to_locale_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_locale_string_no_id() );
}
sub to_locale_string_no_id {
my ($self) = @_;
return $self->_message()->to_locale_string() . $self->_stringify_auxiliaries('to_locale_string');
}
sub add_auxiliary_exception {
my ( $self, $aux ) = @_;
return push @{ $self->{'_auxiliaries'} }, $aux;
}
sub get_auxiliary_exceptions {
my ($self) = @_;
die 'List context only!' if !wantarray; #Can’t use Cpanel::Context
return @{ $self->{'_auxiliaries'} };
}
sub __spew {
my ($self) = @_;
return $self->_spew();
}
sub _spew {
my ($self) = @_;
return ref($self) . '/' . join "\n", $self->to_string() || '<no message>', $self->longmess() || ();
}
sub _stringify_auxiliaries {
my ( $self, $method ) = @_;
my @lines;
if ( @{ $self->{'_auxiliaries'} } ) {
local $@;
_require_LocaleString();
my $intro = 'Cpanel::LocaleString'->new( 'The following additional [numerate,_1,error,errors] occurred:', 0 + @{ $self->{'_auxiliaries'} } ); # PPI NO PARSE - required above
if ( $method eq 'to_locale_string' ) {
push @lines, _locale()->makevar( $intro->to_list() );
}
elsif ( $method eq 'to_en_string' ) {
push @lines, _locale()->makethis_base( $intro->to_list() );
}
else {
die "Invalid method: $method";
}
push @lines, map { UNIVERSAL::isa( $_, __PACKAGE__ ) ? $_->$method() : $_ } @{ $self->{'_auxiliaries'} };
}
return join q<>, map { "\n$_" } @lines;
}
*TO_JSON = \&to_string;
sub _locale {
return $locale ||= do {
local $@;
eval 'require Cpanel::Locale; 1;' or die $@;
'Cpanel::Locale'->get_handle(); # hide from perlcc
};
}
sub _reset_locale {
return undef $locale;
}
sub _load_carp {
if ( !$INC{'Carp.pm'} ) {
local $@;
eval 'require Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc
}
return;
}
sub _load_cpanel_carp {
if ( !$INC{'Cpanel/Carp.pm'} ) {
local $@;
eval 'require Cpanel::Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc
}
return;
}
sub _message {
my ($self) = @_;
return $self->{'_message'} if $self->{'_message'};
local $!;
if ($Cpanel::Exception::LOCALIZE_STRINGS) { # the default
_require_ExceptionMessage_Locale();
return ( $self->{'_message'} ||= 'Cpanel::ExceptionMessage::Locale'->new( @{ $self->{'_mt_args'} } ) ); # PPI NO PARSE - required above
}
return ( $self->{'_message'} ||= Cpanel::ExceptionMessage::Raw->new( Cpanel::ExceptionMessage::Raw::convert_localized_to_raw( @{ $self->{'_mt_args'} } ) ) );
}
package Cpanel::Exception::_StackTraceSuppression;
sub new {
my ($class) = @_;
$Cpanel::Exception::_SUPPRESS_STACK_TRACES++;
return bless [], $class;
}
sub DESTROY {
$Cpanel::Exception::_SUPPRESS_STACK_TRACES--;
return;
}
1;
} # --- END Cpanel/Exception/CORE.pm
{ # --- BEGIN Cpanel/Pack.pm
package Cpanel::Pack;
use strict;
sub new {
my ( $class, $template_ar ) = @_;
if ( @$template_ar % 2 ) {
die "Cpanel::Pack::new detected an odd number of elements in hash assignment!";
}
my $self = bless {
'template_str' => '',
'keys' => [],
}, $class;
my $ti = 0;
while ( $ti < $#$template_ar ) {
push @{ $self->{'keys'} }, $template_ar->[$ti];
$self->{'template_str'} .= $template_ar->[ 1 + $ti ];
$ti += 2;
}
return $self;
}
sub unpack_to_hashref { ## no critic (RequireArgUnpacking)
my %result;
@result{ @{ $_[0]->{'keys'} } } = unpack( $_[0]->{'template_str'}, $_[1] );
return \%result;
}
sub pack_from_hashref {
my ( $self, $opts_ref ) = @_;
no warnings 'uninitialized';
return pack( $self->{'template_str'}, @{$opts_ref}{ @{ $self->{'keys'} } } );
}
sub sizeof {
my ($self) = @_;
return ( $self->{'sizeof'} ||= length pack( $self->{'template_str'}, () ) );
}
sub malloc {
my ($self) = @_;
return pack( $self->{'template_str'} );
}
1;
} # --- END Cpanel/Pack.pm
{ # --- BEGIN Cpanel/Pack/Template.pm
package Cpanel::Pack::Template;
use strict;
use warnings;
no warnings 'once';
use constant PACK_TEMPLATE_INT => 'i';
use constant PACK_TEMPLATE_UNSIGNED_INT => 'i!';
use constant PACK_TEMPLATE_UNSIGNED_LONG => 'L!';
use constant PACK_TEMPLATE_U32 => 'L';
use constant U32_BYTES_LENGTH => 4;
use constant PACK_TEMPLATE_U16 => 'S';
use constant U16_BYTES_LENGTH => 2;
use constant PACK_TEMPLATE_U8 => 'C';
use constant U8_BYTES_LENGTH => 1;
use constant PACK_TEMPLATE_BE16 => 'n';
use constant PACK_TEMPLATE_BE32 => 'N';
1;
} # --- END Cpanel/Pack/Template.pm
{ # --- BEGIN Cpanel/Validate/IP/v4.pm
package Cpanel::Validate::IP::v4;
use strict;
use warnings;
no warnings 'once';
sub is_valid_ipv4 {
my ($ip) = @_;
return unless $ip; # False scalars are never an _[0].
my @segments = split /\./, $ip, -1;
return unless scalar @segments == 4;
my $octet_index;
for my $octet_value (@segments) {
return if !_valid_octet( $octet_value, ++$octet_index );
}
return 1;
}
sub is_valid_cidr4 {
my ($ip) = @_;
return unless defined $ip && $ip;
my ( $ip4, $mask ) = split /\//, $ip;
return if !defined $mask || !length $mask || $mask =~ tr/0-9//c;
return is_valid_ipv4($ip4) && 0 < $mask && $mask <= 32;
}
sub _valid_octet {
my ( $octet_value, $octet_index ) = @_;
return (
!length $octet_value || #
$octet_value =~ tr/0-9//c || #
$octet_value > 255 || #
( substr( $octet_value, 0, 1 ) == 0 && length($octet_value) > 1 ) || # Only dec values are permitted
$octet_index == 1 && length($octet_value) && !$octet_value # First oct can't be zero.
) ? 0 : 1;
}
1;
} # --- END Cpanel/Validate/IP/v4.pm
{ # --- BEGIN Cpanel/Validate/IP.pm
package Cpanel::Validate::IP;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Validate::IP::v4 (); # perlpkg line 211
sub is_valid_ipv6 {
my ($ip) = @_;
return unless defined $ip && $ip;
if ( ( substr( $ip, 0, 1 ) eq ':' && substr( $ip, 1, 1 ) ne ':' )
|| ( substr( $ip, -1, 1 ) eq ':' && substr( $ip, -2, 1 ) ne ':' ) ) {
return; # Can't have single : on front or back
}
my @seg = split /:/, $ip, -1; # -1 to keep trailing empty fields
shift @seg if $seg[0] eq '';
pop @seg if $seg[-1] eq '';
my $max = 8;
if ( index( $seg[-1], '.' ) > -1 ) {
return unless Cpanel::Validate::IP::v4::is_valid_ipv4( pop @seg );
$max -= 2;
}
my $cmp;
for my $seg (@seg) {
if ( !defined $seg || $seg eq '' ) {
return if $cmp;
++$cmp;
next;
}
return if $seg =~ tr/0-9a-fA-F//c || length $seg == 0 || length $seg > 4;
}
if ($cmp) {
return ( @seg && @seg <= $max ) && 1; # true returned as 1
}
return $max == @seg;
}
sub is_valid_ipv6_prefix {
my ($ip) = @_;
return unless $ip;
my ( $ip6, $mask ) = split /\//, $ip;
return unless defined $mask;
return if !length $mask || $mask =~ tr/0-9//c;
return is_valid_ipv6($ip6) && 0 < $mask && $mask <= 128;
}
sub is_valid_ip {
return !defined $_[0] ? undef : index( $_[0], ':' ) > -1 ? is_valid_ipv6(@_) : Cpanel::Validate::IP::v4::is_valid_ipv4(@_);
}
sub ip_version {
return 4 if Cpanel::Validate::IP::v4::is_valid_ipv4(@_);
return 6 if is_valid_ipv6(@_);
return;
}
sub is_valid_ip_cidr_or_prefix {
return unless defined $_[0];
if ( $_[0] =~ tr/:// ) {
return $_[0] =~ tr{/}{} ? is_valid_ipv6_prefix(@_) : is_valid_ipv6(@_);
}
return $_[0] =~ tr{/}{} ? Cpanel::Validate::IP::v4::is_valid_cidr4(@_) : Cpanel::Validate::IP::v4::is_valid_ipv4(@_);
}
sub is_valid_ip_range_cidr_or_prefix {
my $str = shift;
return 0 if !$str;
return 1 if is_valid_ip_cidr_or_prefix($str);
my @pieces = split /-/, $str, 2;
return 1 if 2 == grep { defined($_) } map { Cpanel::Validate::IP::v4::is_valid_ipv4($_) } @pieces;
return 1 if 2 == grep { defined($_) } map { is_valid_ipv6($_) } @pieces;
return 0;
}
1;
} # --- END Cpanel/Validate/IP.pm
{ # --- BEGIN Cpanel/Validate/IP/Expand.pm
package Cpanel::Validate::IP::Expand;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Validate::IP (); # perlpkg line 211
# use Cpanel::Validate::IP::v4 (); # perlpkg line 211
sub normalize_ipv4 {
return unless Cpanel::Validate::IP::v4::is_valid_ipv4( $_[0] );
return join '.', map { $_ + 0 } split /\./, $_[0];
}
sub expand_ipv6 {
my $ip = shift;
return unless Cpanel::Validate::IP::is_valid_ipv6($ip);
return $ip if length $ip == 39; # already expanded
my @seg = split /:/, $ip, -1;
$seg[0] = '0000' if !length $seg[0];
$seg[-1] = '0000' if !length $seg[-1];
if ( $seg[-1] =~ tr{.}{} && Cpanel::Validate::IP::v4::is_valid_ipv4( $seg[-1] ) ) {
my @ipv4 = split /\./, normalize_ipv4( pop @seg );
push @seg, sprintf( '%04x', ( $ipv4[0] << 8 ) + $ipv4[1] ), sprintf( '%04x', ( $ipv4[2] << 8 ) + $ipv4[3] );
}
my @exp;
for my $seg (@seg) {
if ( !length $seg ) {
my $count = scalar(@seg) - scalar(@exp);
while ( $count + scalar(@exp) <= 8 ) {
push @exp, '0000';
}
}
else {
push @exp, sprintf( '%04x', hex $seg );
}
}
return join ':', @exp;
}
sub normalize_ipv6 {
my $ip = shift;
return unless $ip = expand_ipv6($ip);
$ip = lc($ip);
$ip =~ s/:(0+:){2,}/::/; # flatten multiple groups of 0's to :: #
$ip =~ s/(:0+){2,}$/::/; # flatten multiple groups of 0's to :: #
$ip =~ s/^0+([1-9a-f])/$1/; # flatten the first segment's leading 0's to a single 0 #
$ip =~ s/:0+([1-9a-f])/:$1/g; # flatten each segment, after the first, leading 0's to a single 0 #
$ip =~ s/:0+(:)/:0$1/g; # flatten any segments that are just 0's to a single 0 #
$ip =~ s/:0+$/:0/g; # flatten the end segment if it's just 0's to a single 0 #
$ip =~ s/^0+::/::/; # remove single 0 at the beginning #
$ip =~ s/::0+$/::/; # remote single 0 at the end #
return $ip;
}
sub normalize_ip {
return !defined $_[0] ? undef : index( $_[0], ':' ) > -1 ? normalize_ipv6( $_[0] ) : normalize_ipv4( $_[0] );
}
sub expand_ip {
return !defined $_[0] ? undef : index( $_[0], ':' ) > -1 ? expand_ipv6( $_[0] ) : normalize_ipv4( $_[0] );
}
1;
} # --- END Cpanel/Validate/IP/Expand.pm
{ # --- BEGIN Cpanel/IP/Expand.pm
package Cpanel::IP::Expand;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Validate::IP::v4 (); # perlpkg line 211
# use Cpanel::Validate::IP::Expand (); # perlpkg line 211
sub expand_ip {
my ( $ip, $version ) = @_;
$ip =~ tr{ \r\n\t}{}d if defined $ip;
if ( defined $version && $version eq 6 && Cpanel::Validate::IP::v4::is_valid_ipv4($ip) ) {
my @ipv4 = map { $_ + 0 } split /\./, $ip;
return "0000:0000:0000:0000:0000:ffff:" . sprintf( '%04x', ( $ipv4[0] << 8 ) + $ipv4[1] ) . ':' . sprintf( '%04x', ( $ipv4[2] << 8 ) + $ipv4[3] );
}
my $expanded = Cpanel::Validate::IP::Expand::expand_ip($ip);
return $expanded if $expanded;
if ( defined $version && $version eq 6 || $ip =~ m/:/ ) {
return '0000:0000:0000:0000:0000:0000:0000:0000';
}
return '0.0.0.0';
}
sub ip2binary_string {
my $ip = shift || '';
if ( $ip =~ tr/:// ) {
$ip = expand_ip( $ip, 6 );
$ip =~ tr<:><>d;
return unpack( 'B128', pack( 'H32', $ip ) );
}
return unpack( 'B32', pack( 'C4C4C4C4', split( /\./, $ip ) ) );
}
sub first_last_ip_in_range {
my ($range) = @_;
my ( $range_firstip, $mask ) = split( m{/}, $range );
if ( !length $mask ) {
die "Invalid input ($range) -- must be CIDR!";
}
my $mask_offset = 0;
if ( $range_firstip !~ tr/:// ) { # match as if it were an embedded ipv4 in ipv6
$range_firstip = expand_ip( $range_firstip, 6 );
$mask_offset = ( 128 - 32 ); # If we convert the range from ipv4 to ipv6 we need to move the mask
}
my $size = 128;
my $range_firstip_binary_string = ip2binary_string($range_firstip);
my $range_lastip_binary_string = substr( $range_firstip_binary_string, 0, $mask + $mask_offset ) . '1' x ( $size - $mask - $mask_offset );
return ( $range_firstip_binary_string, $range_lastip_binary_string );
}
1;
} # --- END Cpanel/IP/Expand.pm
{ # --- BEGIN Cpanel/Linux/Netlink.pm
package Cpanel::Linux::Netlink;
use strict;
use warnings;
no warnings 'once';
use constant DEBUG => 0;
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::Pack (); # perlpkg line 211
# use Cpanel::Pack::Template (); # perlpkg line 211
my $NETLINK_READ_SIZE = 262144; # Maximum size of netlink message
use constant PAGE_SIZE => 0x400;
use constant READ_SIZE => 8 * PAGE_SIZE;
our $PF_NETLINK = 16;
our $AF_INET = 2;
our $AF_INET6 = 10;
our $NLMSG_NOOP = 0x1;
our $NLMSG_ERROR = 0x2;
our $NLMSG_DONE = 0x3;
our $NLMSG_OVERRUN = 0x4;
our $NETLINK_INET_DIAG_26_KERNEL = 0;
our $NETLINK_INET_DIAG = 4;
our $NLM_F_REQUEST = 1;
our $NLM_F_MULTI = 2; # /* Multipart message, terminated by NLMSG_DONE */
our $NLM_F_ROOT = 0x100;
our $NLM_F_MATCH = 0x200; # in queries, return all matches
our $NLM_F_EXCL = 0x200; # in commands, don't alter if it exists
our $NLM_F_CREATE = 0x400; # in commands, create if it does not exist
our $NLM_F_ACK = 4;
our $SOCK_DGRAM = 2;
our $TCPDIAG_GETSOCK = 18;
our $INET_DIAG_NOCOOKIE = 0xFFFFFFFF;
use constant {
PACK_TEMPLATE_U16 => Cpanel::Pack::Template::PACK_TEMPLATE_U16,
U16_BYTES_LENGTH => Cpanel::Pack::Template::U16_BYTES_LENGTH,
PACK_TEMPLATE_U32 => Cpanel::Pack::Template::PACK_TEMPLATE_U32,
U32_BYTES_LENGTH => Cpanel::Pack::Template::U32_BYTES_LENGTH,
};
my $NLMSG_HEADER_PACK_OBJ;
my $NLMSG_HEADER_PACK_OBJ_SIZE;
our @NLMSG_HEADER_TEMPLATE;
BEGIN {
@NLMSG_HEADER_TEMPLATE = (
'nlmsg_length' => PACK_TEMPLATE_U32(), #__u32 nlmsg_len; /* Length of message including header. */
'nlmsg_type' => PACK_TEMPLATE_U16(), #__u16 nlmsg_type; /* Type of message content. */
'nlmsg_flags' => PACK_TEMPLATE_U16(), #__u16 nlmsg_flags; /* Additional flags. */
'nlmsg_seq' => PACK_TEMPLATE_U32(), #__u32 nlmsg_seq; /* Sequence number. */
'nlmsg_pid' => PACK_TEMPLATE_U32(), #__u32 nlmsg_pid; /* Sender port ID. */
);
}
my @NETLINK_XACTION_REQUIRED = (
'message', #hashref, to be sent via “send_pack_obj”
'send_pack_obj', #Cpanel::Pack instance
'recv_pack_obj', #Cpanel::Pack instance
'sock', #Perl socket
);
my %_u16_cache;
my %_u32_cache;
sub netlink_transaction {
my (%OPTS) = @_;
foreach (@NETLINK_XACTION_REQUIRED) {
die "$_ is required for netlink_transaction" if !$OPTS{$_};
}
my ( $message_ref, $send_pack_obj, $recv_pack_obj, $sock, $parser, $payload_parser, $header_parms_ar ) = @OPTS{ @NETLINK_XACTION_REQUIRED, 'parser', 'payload_parser', 'header' };
my $packed_nlmsg = _pack_nlmsg_with_header( $send_pack_obj, $message_ref, $header_parms_ar );
if (DEBUG) {
require Data::Dumper;
print STDERR "[request]:" . Data::Dumper::Dumper($message_ref);
}
printf STDERR "Send %v02x\n", $packed_nlmsg if DEBUG;
send( $sock, $packed_nlmsg, 0 ) or die "send: $!";
my $message_hr;
my $packed_response = '';
my $header_pack_size = $NLMSG_HEADER_PACK_OBJ->sizeof();
my $recv_pack_size = $recv_pack_obj->sizeof();
my $msgcount = 0;
my ( $msg, $u32, $u16, $nlmsg_length, $nlmsg_type, $nlmsg_flags );
READ_LOOP:
while ( !_nlmsg_type_indicates_finished_reading($message_hr) ) {
sysread( $sock, $packed_response, $NETLINK_READ_SIZE, length $packed_response ) or die "sysread: $!";
PARSE_LOOP:
while (1) {
$msg = substr( $packed_response, 0, $header_pack_size, q<> );
$u32 = substr( $msg, 0, U32_BYTES_LENGTH, '' );
$nlmsg_length = $_u32_cache{$u32} //= unpack( PACK_TEMPLATE_U32, $u32 );
$u16 = substr( $msg, 0, U16_BYTES_LENGTH, '' );
$nlmsg_type = $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 );
$u16 = substr( $msg, 0, U16_BYTES_LENGTH );
$nlmsg_flags = $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 );
last PARSE_LOOP if !$nlmsg_length || length $packed_response < $nlmsg_length - $NLMSG_HEADER_PACK_OBJ_SIZE;
print STDERR "Received message, total size: [$nlmsg_length]\n" if DEBUG;
if ( $nlmsg_type == $NLMSG_ERROR ) {
require Data::Dumper;
my ( $errno, $msg ) = unpack 'i a*', $packed_response;
die Cpanel::Exception::create( 'Netlink', [ error => do { local $! = -$errno }, message => $msg ] );
}
if ( $recv_pack_size <= length $packed_response ) {
my $main_msg = substr( $packed_response, 0, $recv_pack_size, '' );
$message_hr = $recv_pack_obj->unpack_to_hashref($main_msg);
if (DEBUG) {
require Data::Dumper;
printf STDERR "Received %v02x\n", $main_msg;
print STDERR "[response]:" . Data::Dumper::Dumper($message_hr);
}
my $payload = substr(
$packed_response,
0,
$nlmsg_length - $NLMSG_HEADER_PACK_OBJ_SIZE - $recv_pack_size,
q<>,
);
if ( $payload_parser && length $payload ) {
printf STDERR "payload: Received [%v02x]\n", $payload if DEBUG;
$payload_parser->( $msgcount, $message_hr, $payload );
}
}
last READ_LOOP if _nlmsg_type_flags_indicates_finished_reading( $nlmsg_type, $nlmsg_flags );
$msgcount++;
}
}
$parser->( $msgcount, $message_hr ) if $parser && $nlmsg_type;
return 1;
}
our @INET_DIAG_SOCKID_TEMPLATE = (
'idiag_sport' => Cpanel::Pack::Template::PACK_TEMPLATE_BE16, #__be16 idiag_sport;
'idiag_dport' => Cpanel::Pack::Template::PACK_TEMPLATE_BE16, #__be16 idiag_dport;
'idiag_src_0' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[0];
'idiag_src_1' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[1];
'idiag_src_2' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[2];
'idiag_src_3' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[3];
'idiag_dst_0' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[0];
'idiag_dst_1' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[1];
'idiag_dst_2' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[2];
'idiag_dst_3' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[3];
'idiag_if' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_if;
'idiag_cookie_0' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_cookie[0];
'idiag_cookie_1' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_cookie[1];
);
my $INET_DIAG_MSG_PACK_OBJ;
our @INET_DIAG_MSG_TEMPLATE = (
'idiag_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_family; /* Family of addresses. */
'idiag_state' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_state;
'idiag_timer' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_timer;
'idiag_retrans' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_retrans;
@INET_DIAG_SOCKID_TEMPLATE, # inet_diag_sockid
'idiag_expires' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_expires;
'idiag_rqueue' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_rqueue;
'idiag_wqueue' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_wqueue;
'idiag_uid' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_uid;
'idiag_inode' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 #__u32 idiag_inode;
);
my $INET_DIAG_REQ_PACK_OBJ;
our @INET_DIAG_REQ_TEMPLATE = (
'idiag_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_family; /* Family of addresses. */
'idiag_src_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_src_len;
'idiag_dst_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_dst_len;
'idiag_ext' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_ext; /* Query extended information */
@INET_DIAG_SOCKID_TEMPLATE, #inet_diag_sockid
'idiag_states' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_states; /* States to dump */
'idiag_dbs' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 #__u32 idiag_dbs; /* Tables to dump (NI) */
);
sub connection_lookup {
my ( $source_address, $source_port, $dest_address, $dest_port ) = @_;
die "A source port is required." if !defined $source_port;
die "A destination port is required." if !defined $dest_port;
my ( $idiag_dst_0, $idiag_dst_1, $idiag_dst_2, $idiag_dst_3 );
my ( $idiag_src_0, $idiag_src_1, $idiag_src_2, $idiag_src_3 );
my ($idiag_family);
if ( $dest_address =~ tr/:// ) {
require Cpanel::IP::Expand; # hide from exim but not perlcc - not eval quoted
( $idiag_dst_0, $idiag_dst_1, $idiag_dst_2, $idiag_dst_3 ) = unpack( 'N4', pack( 'n8', split /:/, Cpanel::IP::Expand::expand_ip($dest_address) ) );
( $idiag_src_0, $idiag_src_1, $idiag_src_2, $idiag_src_3 ) = unpack( 'N4', pack( 'n8', split /:/, Cpanel::IP::Expand::expand_ip($source_address) ) );
$idiag_family = $AF_INET6;
}
else {
my $u32_dest_address = unpack( 'N', pack( 'C4', split( /\D/, $dest_address, 4 ) ) );
my $u32_source_address = unpack( 'N', pack( 'C4', split( /\D/, $source_address, 4 ) ) );
$idiag_src_0 = $u32_source_address;
$idiag_dst_0 = $u32_dest_address;
$idiag_family = $AF_INET;
}
my $sock;
socket( $sock, $PF_NETLINK, $SOCK_DGRAM, $NETLINK_INET_DIAG ) or die "socket: $!";
$INET_DIAG_REQ_PACK_OBJ ||= Cpanel::Pack->new( \@INET_DIAG_REQ_TEMPLATE );
$INET_DIAG_MSG_PACK_OBJ ||= Cpanel::Pack->new( \@INET_DIAG_MSG_TEMPLATE );
my %RESPONSE;
netlink_transaction(
'message' => {
'idiag_family' => $idiag_family,
'idiag_dst_0' => $idiag_dst_0,
'idiag_dst_1' => $idiag_dst_1,
'idiag_dst_2' => $idiag_dst_2,
'idiag_dst_3' => $idiag_dst_3,
'idiag_dport' => $dest_port,
'idiag_src_0' => $idiag_src_0,
'idiag_src_1' => $idiag_src_1,
'idiag_src_2' => $idiag_src_2,
'idiag_src_3' => $idiag_src_3,
'idiag_sport' => $source_port,
'idiag_cookie_0' => $INET_DIAG_NOCOOKIE,
'idiag_cookie_1' => $INET_DIAG_NOCOOKIE,
},
'sock' => $sock,
'send_pack_obj' => $INET_DIAG_REQ_PACK_OBJ,
'recv_pack_obj' => $INET_DIAG_MSG_PACK_OBJ,
'parser' => sub {
my ( undef, $response_ref ) = @_;
%RESPONSE = %$response_ref if ( $response_ref && 'HASH' eq ref $response_ref );
}
);
return \%RESPONSE;
}
my @NETLINK_SEND_HEADER = (
'nlmsg_length' => undef, #gets put in place
'nlmsg_type' => $TCPDIAG_GETSOCK,
'nlmsg_flags' => 0, #gets |=’d with $NLM_F_REQUEST
'nlmsg_pid' => undef, #gets put in place
'nlmsg_seq' => 2, #default
);
sub _pack_nlmsg_with_header {
my ( $send_pack_obj, $message_ref, $header_parms_ar ) = @_;
my $nlmsg = $send_pack_obj->pack_from_hashref($message_ref);
if ( !$NLMSG_HEADER_PACK_OBJ ) {
$NLMSG_HEADER_PACK_OBJ = Cpanel::Pack->new( \@NLMSG_HEADER_TEMPLATE );
$NLMSG_HEADER_PACK_OBJ_SIZE = $NLMSG_HEADER_PACK_OBJ->sizeof();
}
my %header_data = (
@NETLINK_SEND_HEADER,
( $header_parms_ar ? @$header_parms_ar : () ),
nlmsg_length => $NLMSG_HEADER_PACK_OBJ_SIZE + length $nlmsg,
nlmsg_pid => $$,
);
$header_data{'nlmsg_flags'} |= $NLM_F_REQUEST;
my $hdr_str = $NLMSG_HEADER_PACK_OBJ->pack_from_hashref( \%header_data );
return $hdr_str . $nlmsg;
}
sub _nlmsg_type_indicates_finished_reading {
return _nlmsg_type_flags_indicates_finished_reading( $_[0]->{'nlmsg_type'}, $_[0]->{'nlmsg_flags'} );
}
sub _nlmsg_type_flags_indicates_finished_reading {
return 0 if !length $_[0];
return ( $_[0] == $NLMSG_ERROR || ( $_[1] & $NLM_F_MULTI && $_[0] == $NLMSG_DONE ) || !( $_[1] & $NLM_F_MULTI ) ) ? 1 : 0;
}
sub expect_acknowledgment {
my ( $my_sysread, $socket, $sequence ) = @_;
my $NETLINK_HEADER = Cpanel::Pack->new( \@NLMSG_HEADER_TEMPLATE );
my $response_buffer = '';
my $header_hr;
my $error_code;
do {
while ( length $response_buffer < $NETLINK_HEADER->sizeof() ) {
$my_sysread->( $socket, \$response_buffer, READ_SIZE(), length $response_buffer ) or return "sysread, message header: $!";
}
$header_hr = $NETLINK_HEADER->unpack_to_hashref( substr( $response_buffer, 0, $NETLINK_HEADER->sizeof() ) );
while ( length $response_buffer < $header_hr->{nlmsg_length} ) {
$my_sysread->( $socket, \$response_buffer, READ_SIZE(), length $response_buffer ) or return "sysread, message body: $!";
}
my $message = substr( $response_buffer, 0, $header_hr->{nlmsg_length}, '' );
$error_code = 0;
if ( $header_hr->{nlmsg_type} == $NLMSG_ERROR ) {
$error_code = unpack( Cpanel::Pack::Template::PACK_TEMPLATE_U32, substr( $message, $NETLINK_HEADER->sizeof(), Cpanel::Pack::Template::U32_BYTES_LENGTH ) );
}
if ( $header_hr->{nlmsg_seq} eq $sequence ) {
if ( $header_hr->{nlmsg_type} == $NLMSG_ERROR && $error_code != 0 ) {
local $! = -$error_code;
return "Received error code when expecting acknowledgement: $!\n";
}
if ( $header_hr->{nlmsg_type} == $NLMSG_OVERRUN ) {
return "Data lost due to message overrun";
}
if ( $header_hr->{nlmsg_type} == $NLMSG_DONE ) {
return "Received multipart data when expecting ACK";
}
}
} while ( $header_hr->{nlmsg_seq} ne $sequence || $header_hr->{nlmsg_type} != $NLMSG_ERROR || $error_code != 0 );
return undef;
}
1;
} # --- END Cpanel/Linux/Netlink.pm
{ # --- BEGIN Cpanel/Linux/Proc/Net/Tcp.pm
package Cpanel::Linux::Proc::Net::Tcp;
use strict;
our $PROC_NET_TCP = '/proc/net/tcp';
our $PROC_NET_TCP6 = '/proc/net/tcp6';
sub connection_lookup {
my ( $remote_address, $remote_port, $local_address, $local_port ) = @_;
my ( $tcp_file, $remote_ltl_endian_hex_address, $remote_hex_port, $local_ltl_endian_hex_address, $local_hex_port );
$remote_hex_port = _dec_port_to_hex_port($remote_port);
$local_hex_port = _dec_port_to_hex_port($local_port);
if ( $remote_address =~ tr/:// ) { #ipv6
$tcp_file = $PROC_NET_TCP6;
$remote_ltl_endian_hex_address = _ipv6_text_to_little_endian_hex_address($remote_address);
$local_ltl_endian_hex_address = _ipv6_text_to_little_endian_hex_address($local_address);
}
else {
$tcp_file = $PROC_NET_TCP;
$remote_ltl_endian_hex_address = _ipv4_txt_to_little_endian_hex_address($remote_address);
$local_ltl_endian_hex_address = _ipv4_txt_to_little_endian_hex_address($local_address);
}
if ( open( my $tcp_fh, '<', $tcp_file ) ) {
my $uid;
while ( readline($tcp_fh) ) {
if ( m/^\s*\d+:\s+([\dA-F]{8}(?:[\dA-F]{24})?):([\dA-F]{4})\s+([\dA-F]{8}(?:[\dA-F]{24})?):([\dA-F]{4})\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\d+)/
&& $remote_ltl_endian_hex_address eq $1
&& $remote_hex_port eq $2
&& $local_ltl_endian_hex_address eq $3
&& $local_hex_port eq $4 ) {
$uid = $6;
last;
}
}
return $uid;
}
return;
}
sub _dec_port_to_hex_port {
my ($dec_port) = @_;
return sprintf( '%04X', $dec_port );
}
sub _ipv4_txt_to_little_endian_hex_address {
my ($ipv4_txt) = @_;
return sprintf( "%08X", unpack( 'V', pack( 'C4', split( /\D/, $ipv4_txt, 4 ) ) ) );
}
sub _ipv6_text_to_little_endian_hex_address {
my ($ipv6_txt) = @_;
require Cpanel::IP::Expand; # hide from exim but not perlcc - not eval quoted
my $hexip = '';
my @ip = split /:/, Cpanel::IP::Expand::expand_ip( $ipv6_txt, 6 );
while (@ip) {
my $block1 = shift @ip;
my $block2 = shift @ip;
$hexip .= uc substr( $block2, 2, 2 ) . uc substr( $block2, 0, 2 ) . uc substr( $block1, 2, 2 ) . uc substr( $block1, 0, 2 );
}
return $hexip;
}
1;
} # --- END Cpanel/Linux/Proc/Net/Tcp.pm
{ # --- BEGIN Cpanel/Ident.pm
package Cpanel::Ident;
use strict;
our $TESTING_FLAGS = 0; # FOR TESTING
our $USE_NETLINK = 1; # FOR TESTING
our $USE_PROC = 2; # FOR TESTING
use constant NOTFOUND => 0xff_ff_ff_ff;
sub identify_local_connection {
my ( $source_address, $source_port, $dest_address, $dest_port ) = @_;
if ( !defined($source_port) || !defined($dest_port) ) {
die 'Need source and destination ports!';
}
my $netlink_failed;
if ( !$TESTING_FLAGS || $TESTING_FLAGS == $USE_NETLINK ) {
require Cpanel::Linux::Netlink; # hide from exim but not perlcc - not eval quoted
my $response;
local $@;
eval {
$response = Cpanel::Linux::Netlink::connection_lookup(
$source_address, $source_port,
$dest_address, $dest_port,
);
};
if ($@) {
$netlink_failed = 1;
warn;
}
elsif ( $response
&& defined $response->{'idiag_state'}
&& ( $response->{'idiag_state'} != 1 && $response->{'idiag_state'} != 8 && $response->{'idiag_state'} != 10 ) ) {
return -1;
}
elsif ( $response
&& ref $response
&& $response->{'idiag_dport'}
&& defined( $response->{'idiag_uid'} )
&& $response->{'idiag_uid'} != NOTFOUND() ) {
return $response->{'idiag_uid'};
}
}
if ( $netlink_failed || $TESTING_FLAGS == $USE_PROC ) {
require Cpanel::Linux::Proc::Net::Tcp; # hide from exim but not perlcc - not eval quoted
my $uid = Cpanel::Linux::Proc::Net::Tcp::connection_lookup( $source_address, $source_port, $dest_address, $dest_port );
return $uid if defined $uid;
}
return;
}
1;
} # --- END Cpanel/Ident.pm
{ # --- BEGIN Cpanel/Autodie.pm
package Cpanel::Autodie;
use strict;
use warnings;
no warnings 'once';
sub _ENOENT { return 2; }
sub _EEXIST { return 17; }
sub _EINTR { return 4; }
sub import {
shift;
_load_function($_) for @_;
return;
}
our $AUTOLOAD;
sub AUTOLOAD {
substr( $AUTOLOAD, 0, 1 + rindex( $AUTOLOAD, ':' ) ) = q<>;
_load_function($AUTOLOAD);
goto &{ Cpanel::Autodie->can($AUTOLOAD) };
}
sub _load_function {
_require("Cpanel/Autodie/CORE/$_[0].pm");
return;
}
sub _require {
local ( $!, $^E, $@ );
require $_[0];
return;
}
1;
} # --- END Cpanel/Autodie.pm
{ # --- BEGIN Cpanel/Autodie/CORE/exists.pm
package Cpanel::Autodie;
use strict;
use warnings;
no warnings 'once';
sub exists { ## no critic qw( RequireArgUnpacking )
local ( $!, $^E );
if ( ${^GLOBAL_PHASE} eq 'START' ) {
_die_err( $_[0], "do not access the filesystem at compile time" );
}
return 1 if -e $_[0];
return 0 if $! == _ENOENT();
return _die_err( $_[0], $! );
}
sub exists_nofollow {
my ($path) = @_;
local ( $!, $^E );
return 1 if CORE::lstat $path;
return 0 if $! == _ENOENT();
return _die_err( $path, $! );
}
sub _die_err {
my ( $path, $err ) = @_;
local $@; # $! is already local()ed.
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::StatError', [ error => $err, path => $path ] );
}
1;
} # --- END Cpanel/Autodie/CORE/exists.pm
{ # --- BEGIN Cpanel/Autodie/CORE/exists_nofollow.pm
package Cpanel::Autodie;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Autodie::CORE::exists(); # PPI NO PARSE # perlpkg line 211
1;
} # --- END Cpanel/Autodie/CORE/exists_nofollow.pm
{ # --- BEGIN Cpanel/Autodie/More/Lite.pm
package Cpanel::Autodie::More::Lite;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Autodie (); # perlpkg line 211
# use Cpanel::Autodie::CORE::exists (); # PPI USE OK - reload so we can map the symbol below # perlpkg line 211
# use Cpanel::Autodie::CORE::exists_nofollow (); # PPI USE OK - reload so we can map the symbol below # perlpkg line 211
BEGIN {
*exists = *Cpanel::Autodie::exists;
*exists_nofollow = *Cpanel::Autodie::exists_nofollow;
}
1;
} # --- END Cpanel/Autodie/More/Lite.pm
{ # --- BEGIN Cpanel/Services/Enabled/Spamd.pm
package Cpanel::Services::Enabled::Spamd;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Autodie::More::Lite (); # perlpkg line 211
our $_TOUCHFILE_PATH = '/etc/spamddisable';
sub is_enabled {
return !Cpanel::Autodie::More::Lite::exists($_TOUCHFILE_PATH);
}
1;
} # --- END Cpanel/Services/Enabled/Spamd.pm
{ # --- BEGIN Cpanel/FileUtils/Dir.pm
package Cpanel::FileUtils::Dir;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
use constant _ENOENT => 2;
sub directory_has_nodes {
return directory_has_nodes_if_exists( $_[0] ) // do {
local $! = _ENOENT();
die _opendir_err( $_[0] );
};
}
sub directory_has_nodes_if_exists {
my ($dir) = @_;
local $!;
opendir my $dh, $dir or do {
if ( $! == _ENOENT() ) {
return undef;
}
die _opendir_err($dir);
};
local $!;
my $has_nodes = 0;
while ( my $node = readdir $dh ) {
next if $node eq '.' || $node eq '..';
$has_nodes = 1;
last;
}
_check_for_readdir_error($dir) if !$has_nodes;
_closedir( $dh, $dir );
return $has_nodes;
}
sub get_directory_nodes_if_exists {
my ($dir) = @_;
local $!;
if ( opendir my $dh, $dir ) {
return _read_directory_nodes( $dh, $dir );
}
elsif ( $! != _ENOENT() ) {
die _opendir_err($dir);
}
return undef;
}
sub get_directory_nodes {
return _read_directory_nodes( _opendir( $_[0] ), $_[0] );
}
sub _read_directory_nodes { ## no critic qw(Subroutines::RequireArgUnpacking) -- used in loops
local $!;
my @nodes = grep { $_ ne '.' && $_ ne '..' } readdir( $_[0] );
_check_for_readdir_error( $_[0] );
_closedir( $_[0], $_[1] );
return \@nodes;
}
sub _check_for_readdir_error {
if ( $! && ( $^V >= v5.20.0 ) ) {
die Cpanel::Exception::create( 'IO::DirectoryReadError', [ path => $_[0], error => $! ] );
}
return;
}
sub _opendir {
local $!;
opendir my $dh, $_[0] or do {
die _opendir_err( $_[0] );
};
return $dh;
}
sub _closedir {
local $!;
closedir $_[0] or do {
die Cpanel::Exception::create( 'IO::DirectoryCloseError', [ path => $_[1], error => $! ] );
};
return;
}
sub _opendir_err {
return Cpanel::Exception::create( 'IO::DirectoryOpenError', [ path => $_[0], error => $! ] );
}
1;
} # --- END Cpanel/FileUtils/Dir.pm
{ # --- BEGIN Cpanel/DKIM/ValidityCache.pm
package Cpanel::DKIM::ValidityCache;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Autodie (); # perlpkg line 211
our $BASE_DIRECTORY = '/var/cpanel/domain_keys/validity_cache';
sub _BASE { return $BASE_DIRECTORY; }
sub get {
my ( undef, $entry ) = @_;
return Cpanel::Autodie::exists("$BASE_DIRECTORY/$entry");
}
sub get_all {
require Cpanel::FileUtils::Dir;
return Cpanel::FileUtils::Dir::get_directory_nodes_if_exists($BASE_DIRECTORY);
}
1;
} # --- END Cpanel/DKIM/ValidityCache.pm
{ # --- BEGIN Cpanel/Context.pm
package Cpanel::Context;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
sub must_be_list {
return 1 if ( caller(1) )[5]; # 5 = wantarray
my $msg = ( caller(1) )[3]; # 3 = subroutine
$msg .= $_[0] if defined $_[0];
return _die_context( 'list', $msg );
}
sub must_not_be_scalar {
my ($message) = @_;
my $wa = ( caller(1) )[5]; # 5 = wantarray
if ( !$wa && defined $wa ) {
_die_context( 'list or void', $message );
}
return 1;
}
sub must_not_be_void {
return if defined( ( caller 1 )[5] );
return _die_context('scalar or list');
}
sub _die_context {
my ( $context, $message ) = @_;
local $Carp::CarpInternal{__PACKAGE__} if $INC{'Carp.pm'};
my $to_throw = length $message ? "Must be $context context ($message)!" : "Must be $context context!";
die Cpanel::Exception::create_raw( 'ContextError', $to_throw );
}
1;
} # --- END Cpanel/Context.pm
{ # --- BEGIN Cpanel/ProcessInfo.pm
package Cpanel::ProcessInfo;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Context (); # perlpkg line 211
# use Cpanel::Autodie (); # perlpkg line 211
our $VERSION = '1.0';
sub get_pid_lineage {
Cpanel::Context::must_be_list();
my @lineage;
my $ppid = getppid();
while ( $ppid > 1 ) {
push @lineage, $ppid;
$ppid = get_parent_pid($ppid);
}
return @lineage;
}
sub get_parent_pid {
_die_if_pid_invalid( $_[0] );
return getppid() if $_[0] == $$;
if ( open( my $proc_status_fh, '<', "/proc/$_[0]/status" ) ) {
local $/;
my %status = map { lc $_->[0] => $_->[1] }
map { [ ( split( /\s*:\s*/, $_ ) )[ 0, 1 ] ] }
grep { index( $_, ':' ) > -1 }
split( /\n/, readline($proc_status_fh) );
return $status{'ppid'};
}
return undef;
}
sub get_pid_exe {
_die_if_pid_invalid( $_[0] );
return Cpanel::Autodie::readlink_if_exists( '/proc/' . $_[0] . '/exe' );
}
sub get_pid_cmdline {
_die_if_pid_invalid( $_[0] );
if ( open( my $cmdline, '<', "/proc/$_[0]/cmdline" ) ) {
local $/;
my $cmdline = readline($cmdline);
$cmdline =~ tr{\0}{ };
$cmdline =~ tr{\r\n}{}d;
substr( $cmdline, -1, 1, '' ) if substr( $cmdline, -1 ) eq ' ';
return $cmdline;
}
return '';
}
sub get_pid_cwd {
_die_if_pid_invalid( $_[0] );
return readlink( '/proc/' . $_[0] . '/cwd' ) || '/';
}
sub _die_if_pid_invalid {
die "Invalid PID: $_[0]" if !length $_[0] || $_[0] =~ tr{0-9}{}c;
return;
}
1;
} # --- END Cpanel/ProcessInfo.pm
{ # --- BEGIN Cpanel/Fcntl/Constants.pm
package Cpanel::Fcntl::Constants;
use strict;
use warnings;
no warnings 'once';
BEGIN {
our $O_RDONLY = 0;
our $O_WRONLY = 1;
our $O_RDWR = 2;
our $O_ACCMODE = 3;
our $F_GETFD = 1;
our $F_SETFD = 2;
our $F_GETFL = 3;
our $F_SETFL = 4;
our $SEEK_SET = 0;
our $SEEK_CUR = 1;
our $SEEK_END = 2;
our $S_IWOTH = 2;
our $S_ISUID = 2048;
our $S_ISGID = 1024;
our $O_CREAT = 64;
our $O_EXCL = 128;
our $O_TRUNC = 512;
our $O_APPEND = 1024;
our $O_NONBLOCK = 2048;
our $O_DIRECTORY = 65536;
our $O_NOFOLLOW = 131072;
our $O_CLOEXEC = 524288;
our $S_IFREG = 32768;
our $S_IFDIR = 16384;
our $S_IFCHR = 8192;
our $S_IFBLK = 24576;
our $S_IFIFO = 4096;
our $S_IFLNK = 40960;
our $S_IFSOCK = 49152;
our $S_IFMT = 61440;
our $LOCK_SH = 1;
our $LOCK_EX = 2;
our $LOCK_NB = 4;
our $LOCK_UN = 8;
our $FD_CLOEXEC = 1;
}
1;
} # --- END Cpanel/Fcntl/Constants.pm
{ # --- BEGIN Cpanel/Socket/Constants.pm
package Cpanel::Socket::Constants;
use strict;
use warnings;
no warnings 'once';
our $SO_REUSEADDR = 2;
our $AF_UNIX = 1;
our $AF_INET = 2;
our $PF_INET = 2;
our $AF_INET6 = 10;
our $PF_INET6 = 10;
our $PROTO_IP = 0;
our $PROTO_ICMP = 1;
our $PROTO_TCP = 6;
our $PROTO_UDP = 17;
our $IPPROTO_TCP;
*IPPROTO_TCP = \$PROTO_TCP;
our $SO_PEERCRED = 17;
our $SOL_SOCKET = 1;
our $SOCK_STREAM = 1;
our $SOCK_NONBLOCK = 2048;
our $SHUT_RD = 0;
our $SHUT_WR = 1;
our $SHUT_RDWR = 2;
our $MSG_PEEK = 2;
our $MSG_NOSIGNAL = 16384;
1;
} # --- END Cpanel/Socket/Constants.pm
{ # --- BEGIN Cpanel/Hulk/Constants.pm
package Cpanel::Hulk::Constants;
use strict;
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::Socket::Constants (); # perlpkg line 211
*F_GETFL = \$Cpanel::Fcntl::Constants::F_GETFL;
*F_SETFL = \$Cpanel::Fcntl::Constants::F_SETFL;
*O_NONBLOCK = \$Cpanel::Fcntl::Constants::O_NONBLOCK;
our $EINTR = 4;
our $EPIPE = 32;
our $EINPROGRESS = 115;
our $ETIMEDOUT = 110;
our $EISCONN = 106;
our $ECONNRESET = 104;
our $EAGAIN = 11;
*PROTO_IP = \$Cpanel::Socket::Constants::PROTO_IP;
*PROTO_ICMP = \$Cpanel::Socket::Constants::PROTO_ICMP;
*PROTO_TCP = \$Cpanel::Socket::Constants::PROTO_TCP;
*SO_PEERCRED = \$Cpanel::Socket::Constants::SO_PEERCRED;
*SOL_SOCKET = \$Cpanel::Socket::Constants::SOL_SOCKET;
*SOCK_STREAM = \$Cpanel::Socket::Constants::SOCK_STREAM;
*AF_INET6 = \$Cpanel::Socket::Constants::AF_INET6;
*AF_INET = \$Cpanel::Socket::Constants::AF_INET;
*AF_UNIX = \$Cpanel::Socket::Constants::AF_UNIX;
our $TOKEN_SALT_BASE = '$6$';
our $SALT_LENGTH = 16;
our $TIME_BASE = 1410000000;
our $SIX_HOURS_IN_SECONDS = 21600;
1;
} # --- END Cpanel/Hulk/Constants.pm
{ # --- BEGIN Cpanel/ApacheServerStatus.pm
package Cpanel::ApacheServerStatus;
# use Cpanel::Hulk::Constants (); # perlpkg line 211
sub new {
my ($class) = @_;
my $obj = {};
bless $obj, $class;
my $html = $obj->fetch_server_status_html();
$html =~ m/<table[^\>]*>(.*?)<\/table[^\>]*>/is;
my $inner_table = $1;
$inner_table =~ s/[\r\n\0]//g;
my $line_count = 0;
my ( @index, @data, %server_status );
while ( $inner_table =~ m/<tr[^\>]*>(.*?)<\/tr[^\>]*>/isg ) {
my $contents = $1;
@data = map { s/^\s+//; s/\s+$//; lc $_; } ( $contents =~ m/(?:<[^\>]+>)+([^\<]+)/isg );
if ( $line_count == 0 ) {
@index = @data;
}
else {
my $count = 0;
my %named_data = map { $index[ $count++ ] => $_; } @data;
$server_status{ $named_data{'pid'} } = \%named_data;
}
$line_count++;
}
$obj->{'server_status'} = \%server_status;
return $obj;
}
sub get_status_by_pid {
my ( $self, $pid ) = @_;
return $self->{'server_status'}->{$pid};
}
sub get_apache_port {
if ( open( my $ap_port_fh, '<', '/var/cpanel/config/apache/port' ) ) {
my $port_txt = readline($ap_port_fh);
chomp($port_txt);
if ( $port_txt =~ m/:/ ) {
return ( split( m/:/, $port_txt ) )[1];
}
elsif ( $port_txt =~ /^[0-9]+$/ ) {
return $port_txt;
}
}
}
sub fetch_server_status_html {
my ($self) = @_;
my $port = 80;
my $html;
eval {
my $socket_scc;
if ( !socket( $socket_scc, $Cpanel::Hulk::Constants::AF_INET, $Cpanel::Hulk::Constants::SOCK_STREAM, $Cpanel::Hulk::Constants::PROTO_TCP ) || !$socket_scc ) {
die "Could not setup tcp socket for connection to $port: $!";
}
if ( !connect( $socket_scc, pack( 'S n a4 x8', $Cpanel::Hulk::Constants::AF_INET, $port, ( pack 'C4', ( split /\./, "127.0.0.1" ) ) ) ) ) {
my $non_default_port = $self->get_apache_port();
if ( $non_default_port && $non_default_port != $port ) {
if ( !connect( $socket_scc, pack( 'S n a4 x8', $Cpanel::Hulk::Constants::AF_INET, $non_default_port, ( pack 'C4', ( split /\./, "127.0.0.1" ) ) ) ) ) {
die "Unable to connect to port $non_default_port on 127.0.0.1: $!";
}
}
}
syswrite( $socket_scc, "GET /whm-server-status HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" );
local $/;
$html = readline($socket_scc);
close($socket_scc);
};
$html;
}
1;
} # --- END Cpanel/ApacheServerStatus.pm
{ # --- BEGIN Cpanel/Time/Local.pm
package Cpanel::Time::Local;
use strict;
our $server_offset_string;
our ( $timecacheref, $localtimecacheref ) = ( [ -1, '', -1 ], [ -1, '', -1 ] );
my $server_offset;
my $localtime_link_or_mtime;
our $ETC_LOCALTIME = q{/etc/localtime};
sub _clear_caches {
undef $_
for (
$server_offset,
$server_offset_string,
$timecacheref,
$localtimecacheref,
$localtime_link_or_mtime,
);
return;
}
sub localtime2timestamp {
my ( $time, $delimiter ) = @_;
$delimiter ||= ' ';
$time ||= time();
return $localtimecacheref->[2] if $localtimecacheref->[0] == $time && $localtimecacheref->[1] eq $delimiter;
my $tz_offset = get_server_offset_as_offset_string($time);
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) = @_;
if ( !$time_supplied ) {
my $link_or_mtime;
if ( -l $ETC_LOCALTIME ) {
$link_or_mtime = readlink($ETC_LOCALTIME);
}
else {
$link_or_mtime = ( stat($ETC_LOCALTIME) )[9];
}
if ( defined $link_or_mtime ) {
$localtime_link_or_mtime ||= $link_or_mtime;
if ( $localtime_link_or_mtime ne $link_or_mtime ) {
_clear_caches();
$localtime_link_or_mtime = $link_or_mtime;
}
}
}
if ( $time_supplied || !defined $server_offset_string ) {
UNTIL_SAME_SECOND: {
my $starttime = time();
my $time = $time_supplied || $starttime;
my ( $sec, $min, $hour, $mday, $mon, $year, $wday, $yday ) = localtime $time;
my ( $gmmin, $gmhour, $gmyear, $gmyday ) = ( gmtime($time) )[ 1, 2, 5, 7 ];
redo UNTIL_SAME_SECOND if time != $starttime;
my $yday_offset;
if ( $year == $gmyear ) {
$yday_offset = ( $yday <=> $gmyday );
}
elsif ( $year < $gmyear ) {
$yday_offset = -1;
}
elsif ( $year > $gmyear ) {
$yday_offset = 1;
}
my $gmoffset = ( $hour * 60 + $min ) - ( $gmhour * 60 + $gmmin ) + 1440 * $yday_offset;
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;
}
sub get_server_offset_in_seconds {
if ( !defined $server_offset ) {
if ( get_server_offset_as_offset_string() =~ m/([-+]?[0-9]{2})([0-9]{2})/ ) {
my ( $hours, $minutes ) = ( $1, $2 );
my $seconds = ( ( abs($hours) * 60 * 60 ) + ( $minutes * 60 ) );
$server_offset = $hours < 0 ? "-$seconds" : $seconds;
}
else {
$server_offset = 0;
}
}
return $server_offset;
}
1;
} # --- END Cpanel/Time/Local.pm
{ # --- BEGIN Cpanel/Fcntl.pm
package Cpanel::Fcntl;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
my %CONSTANTS;
my %CACHE;
sub or_flags {
my (@flags) = @_;
my $flag_cache_key = join( '|', @flags );
return $CACHE{$flag_cache_key} if defined $CACHE{$flag_cache_key};
my $numeric = 0;
foreach my $o_const (@flags) {
$numeric |= (
$CONSTANTS{$o_const} ||= do {
my $glob = $Cpanel::Fcntl::Constants::{$o_const};
my $number_r = $glob && *{$glob}{'SCALAR'};
die "Missing \$Cpanel::Fcntl::Constants::$o_const! (does it need to be added?)" if !$number_r;
$$number_r;
}
);
}
return ( $CACHE{$flag_cache_key} = $numeric );
}
1;
} # --- END Cpanel/Fcntl.pm
{ # --- BEGIN Cpanel/FileUtils/Open.pm
package Cpanel::FileUtils::Open;
use strict;
# use Cpanel::Fcntl (); # perlpkg line 211
sub sysopen_with_real_perms { ##no critic qw(RequireArgUnpacking)
my ( $file, $mode, $custom_perms ) = ( @_[ 1 .. 3 ] );
if ( $mode && substr( $mode, 0, 1 ) eq 'O' ) {
$mode = Cpanel::Fcntl::or_flags( split m<\|>, $mode );
}
my ( $sysopen_perms, $original_umask );
if ( defined $custom_perms ) {
$custom_perms &= 0777;
$original_umask = umask( $custom_perms ^ 07777 );
$sysopen_perms = $custom_perms;
}
else {
$sysopen_perms = 0666;
}
my $ret = sysopen( $_[0], $file, $mode, $sysopen_perms );
if ( defined $custom_perms ) {
() = umask($original_umask);
}
return $ret;
}
1;
} # --- END Cpanel/FileUtils/Open.pm
{ # --- BEGIN Cpanel/Parser/Vars.pm
package Cpanel::Parser::Vars;
use strict;
our $current_tag = '';
our $can_leave_cpanelaction = 1;
our $buffer = '';
our $loaded_api = 0;
our $trial_mode = 0;
our $sent_headers = 0;
our $live_socket_file;
our $incpanelaction = 0;
our $altmode = 0;
our $jsonmode = 0;
our $javascript = 0;
our $title = 0;
our $input = 0;
our $style = 0;
our $embtag = 0;
our $textarea = 0;
our $file = '[stdin]';
our $firstfile = '[stdin]';
our $trap_defaultfh = undef; # Known to be boolean.
our %BACKCOMPAT;
our $cptag;
our $sent_content_type;
1;
} # --- END Cpanel/Parser/Vars.pm
{ # --- BEGIN Cpanel/Encoder/Tiny/Rare.pm
package Cpanel::Encoder::Tiny::Rare;
use strict;
use warnings;
no warnings 'once';
sub angle_bracket_decode {
my ($string) = @_;
$string =~ s{ < }{<}xmsg;
$string =~ s{ > }{>}xmsg;
return $string;
}
sub decode_utf8_html_entities {
my $str = shift;
$str =~ s/&\#(\d{4})\;/chr($1);/eg;
return $str;
}
my %uri_encoding_cache = (
'"' => '%22',
q{'} => '%27',
'(' => '%28',
')' => '%29',
q{ } => '%20',
"\t" => '%09',
);
sub css_encode_str {
my $str = shift;
$str =~ s{([\(\)\s"'])}{
$uri_encoding_cache{$1}
|| require Cpanel::Encoder::URI && Cpanel::Encoder::URI::uri_encode_str($1)
}ge;
return $str;
}
1;
} # --- END Cpanel/Encoder/Tiny/Rare.pm
{ # --- BEGIN Cpanel/Encoder/Tiny.pm
package Cpanel::Encoder::Tiny;
use strict;
my %XML_ENCODE_MAP = ( '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' );
my %HTML_ENCODE_MAP = ( '&' => '&', '<' => '<', '>' => '>', '"' => '"', "'" => ''' );
my %HTML_DECODE_MAP = ( 'amp' => '&', 'lt' => '<', 'gt' => '>', 'quot' => '"', 'apos' => q{'}, '#39' => q{'} );
my $decode_regex = do { my $tmp = join( '|', keys %HTML_DECODE_MAP ); "&($tmp);"; };
sub angle_bracket_encode {
my ($string) = @_;
$string =~ s{<}{<}xmsg;
$string =~ s{>}{>}xmsg;
return $string;
}
sub safe_xml_encode_str {
my $data = join( '', @_ );
return $data if $data !~ tr/&<>"'//;
$data =~ s/([&<>"'])/$XML_ENCODE_MAP{$1}/sg;
return $data;
}
sub safe_html_encode_str {
return $_[0] if !defined $_[0] || ( !defined $_[1] && $_[0] !~ tr/&<>"'// );
my $data = defined $_[1] ? join( '', @_ ) : $_[0];
return $data if $data !~ tr/&<>"'//;
$data =~ s/([&<>"'])/$HTML_ENCODE_MAP{$1}/sg;
return $data;
}
sub safe_html_decode_str {
return undef if !defined $_[0];
my $data = join( '', @_ );
$data =~ s/$decode_regex/$HTML_DECODE_MAP{$1}/g;
return $data;
}
sub css_encode_str {
require Cpanel::Encoder::Tiny::Rare;
*css_encode_str = *Cpanel::Encoder::Tiny::Rare::css_encode_str;
goto \&Cpanel::Encoder::Tiny::Rare::css_encode_str;
}
1;
} # --- END Cpanel/Encoder/Tiny.pm
{ # --- BEGIN Cpanel/Regex.pm
package Cpanel::Regex;
use strict;
our $VERSION = '0.2.5';
my $dblquotedstr = q{"([^\\\\"]*(?:\\\\.[^\\\\"]*)*)"};
my $sglquotedstr = $dblquotedstr;
$sglquotedstr =~ tr{"}{'};
my $zero_through_255 = '(?:25[0-5]|2[0-4][0-9]|1[0-9]{2}|[1-9][0-9]?|0)';
our %regex = (
'emailaddr' => '[a-zA-Z0-9!#\$\-=?^_{}~]+(?:\.[a-zA-Z0-9!#\$\-=?^_{}~]+)*(?:\+[a-zA-Z0-9 \.=\-\_]+)*\@[\da-zA-Z](?:[-\da-zA-Z]*[\da-zA-Z])?(?:\.[\da-zA-Z](?:[-\da-zA-Z]*[\da-zA-Z])?)*',
'oneplusdot' => '\.+',
'oneplusspacetab' => '[\s\t]+',
'multipledot' => '\.{2,}',
'commercialat' => '\@',
'plussign' => '\+',
'singledot' => '\.',
'newline' => '\n',
'doubledot' => '\.\.',
'lineofdigits' => '^\d+$',
'lineofnonprintingchars' => '^[\s\t]*$',
'getemailtransport' => '^from\s+.*\s+by\s+\S+\s+with\s+(\S+)',
'getreceivedfrom' => '^from\s+(.*)\s+by\s+',
'emailheaderterminator' => '^[\r\n]*$',
'forwardslash' => '\/',
'backslash' => chr(92) x 4,
'singlequote' => q('),
'doublequote' => '"',
'allspacetabchars' => '[\s\t]*',
'beginswithspaceortabs' => '^[\s\t]',
doublequotedstring => $dblquotedstr,
singlequotedstring => $sglquotedstr,
DUNS => '[0-9]{2}(?:-[0-9]{3}-[0-9]{4}|[0-9]{7})',
YYYY_MM_DD => '[0-9]{4}-(?:1[012]|0[1-9])-(?:3[01]|[12][0-9]|0[1-9])',
ipv4 => "(?:$zero_through_255\.){3}$zero_through_255",
iso_z_time => '[0-9]{4}-[0-9]{2}-[0-9]{2}T[0-9]{2}:[0-9]{2}:[0-9]{2}Z',
);
1;
} # --- END Cpanel/Regex.pm
{ # --- BEGIN Cpanel/Carp.pm
package Cpanel::Carp;
use strict;
# use Cpanel::Parser::Vars (); # perlpkg line 211
our ( $SHOW_TRACE, $OUTPUT_FORMAT, $VERBOSE ) = ( 1, 'text', 0 );
my $__CALLBACK_AFTER_DIE_SPEW; # Set when we need to run a code ref after spewing on die
my $error_count = 0;
sub import { return enable(); }
sub enable {
my (
$callback_before_warn_or_die_spew, # Runs before the spew on warn or die, currently used in cpanel to ensure we emit headers before body in the event of a warn or die spew
$callback_before_die_spew, # Runs before the spew on die, not currently used
$callback_after_die_spew, # Runs after the spew on die, currently used in whostmgr to ensure we emit the javascript footer when we die to avoid the UI breaking
) = @_;
$SIG{'__WARN__'} = sub { ## no critic qw(Variables::RequireLocalizedPunctuationVars)
my @caller = caller(1);
return if defined $caller[3] && index( $caller[3], 'eval' ) > -1; # Case 35335: Quiet spurious warn errors from evals
++$error_count;
my $time = time();
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time);
my ( $gmmin, $gmhour, $gmday ) = ( gmtime($time) )[ 1, 2, 3 ];
my $gmoffset = ( $hour * 60 + $min ) - ( $gmhour * 60 + $gmmin ) + 1440 * ( $mday <=> $gmday );
my $tz = sprintf( '%+03d%02d', int( $gmoffset / 60 ), $gmoffset % 60 );
my $error_timestamp = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $tz );
my $longmess;
my $ignorable;
if ( UNIVERSAL::isa( $_[0], 'Cpanel::Exception' ) ) {
$longmess = Cpanel::Carp::safe_longmess( $_[0]->to_locale_string() );
}
elsif ( ref $_[0] eq 'Template::Exception' ) {
$longmess = Cpanel::Carp::safe_longmess( "Template::Exception:\n\t[TYPE]=[" . $_[0]->[0] . "]\n\t[INFO]=[" . $_[0]->[1] . "]\n\t[TEXT]=[" . ( ref $_[0]->[2] eq 'SCALAR' ? ${ $_[0]->[2] } : $_[0]->[2] ) . "]\n" );
}
else {
$longmess = Cpanel::Carp::safe_longmess(@_);
$ignorable = 1 if index( $_[0], 'Use of uninitialized value' ) == 0;
}
my $error_container_text = 'A warning occurred while processing this directive.';
my $current_file = $Cpanel::Parser::Vars::file || 'unknown';
print STDERR "[$error_timestamp] warn [Internal Warning while parsing $current_file $$] $longmess\n\n";
return if ( $OUTPUT_FORMAT eq 'suppress' || $OUTPUT_FORMAT eq 'supress' || $ENV{'CPANEL_PHPENGINE'} );
return if $ignorable && !$VERBOSE;
_run_callback_without_die_handler($callback_before_warn_or_die_spew) if $callback_before_warn_or_die_spew;
if ( $OUTPUT_FORMAT eq 'html' ) {
if ($SHOW_TRACE) {
_print_without_die_handler( _generate_html_error_message( 'warn', $error_container_text, $longmess ) );
}
else {
_print_without_die_handler(qq{<span class="error" style="cursor:hand;cursor:pointer;">[$error_container_text]</span>});
}
}
elsif ( $OUTPUT_FORMAT eq 'xml' ) {
_print_without_die_handler("<error>$error_container_text</error>");
}
else {
_print_without_die_handler("[$error_container_text]\n");
}
};
$SIG{'__DIE__'} = sub { ## no critic qw(Variables::RequireLocalizedPunctuationVars)
return if $^S;
die $_[0] unless defined $^S;
delete $SIG{'__DIE__'};
_run_callback_without_die_handler($callback_before_warn_or_die_spew) if $callback_before_warn_or_die_spew;
_run_callback_without_die_handler($callback_before_die_spew) if $callback_before_die_spew;
$__CALLBACK_AFTER_DIE_SPEW = $callback_after_die_spew;
goto \&spew_on_die;
};
return 1;
}
sub spew_on_die { ## no critic qw(Subroutines::RequireArgUnpacking)
my ($err) = @_;
++$error_count;
my $time = time();
my ( $sec, $min, $hour, $mday, $mon, $year ) = localtime($time);
my ( $gmmin, $gmhour, $gmday ) = ( gmtime($time) )[ 1, 2, 3 ];
my $gmoffset = ( $hour * 60 + $min ) - ( $gmhour * 60 + $gmmin ) + 1440 * ( $mday <=> $gmday );
my $tz = sprintf( '%+03d%02d', int( $gmoffset / 60 ), $gmoffset % 60 );
my $error_timestamp = sprintf( '%04d-%02d-%02d %02d:%02d:%02d %s', $year + 1900, $mon + 1, $mday, $hour, $min, $sec, $tz );
my $error_text;
if ( UNIVERSAL::isa( $err, 'Cpanel::Exception' ) ) {
$error_text = Cpanel::Carp::safe_longmess( $err->to_locale_string() );
}
elsif ( UNIVERSAL::isa( $err, 'Template::Exception' ) ) {
$error_text = Cpanel::Carp::safe_longmess( "Template::Exception:\n\t[TYPE]=[" . $err->type() . "]\n\t[INFO]=[" . $err->info() . "]\n\t[TEXT]=[" . $err->text() . "]\n" );
}
else {
$error_text = Cpanel::Carp::safe_longmess(@_);
}
my $current_file = $Cpanel::Parser::Vars::file || 'unknown';
print STDERR "[$error_timestamp] die [Internal Death while parsing $current_file $$] $error_text\n\n";
return if ( $OUTPUT_FORMAT eq 'suppress' || $OUTPUT_FORMAT eq 'supress' || $ENV{'CPANEL_PHPENGINE'} );
my $error_container_text = 'A fatal error or timeout occurred while processing this directive.';
if ( $OUTPUT_FORMAT eq 'html' ) {
if ($SHOW_TRACE) {
_print_without_die_handler( _generate_html_error_message( 'error', $error_container_text, $error_text ) );
}
else {
_print_without_die_handler(qq{<span class="error" style="cursor:hand;cursor:pointer;">[$error_container_text]</span>});
}
}
elsif ( $OUTPUT_FORMAT eq 'xml' ) {
_print_without_die_handler("<error>[$error_container_text]</error>");
}
else {
_print_without_die_handler("[$error_container_text]\n");
}
_run_callback_without_die_handler($__CALLBACK_AFTER_DIE_SPEW) if $__CALLBACK_AFTER_DIE_SPEW;
return;
}
my @SAFE_LONGMESS_KEY_REGEXP_ITEMS = (
'(?<![a-zA-Z0-9_])pw(?![a-zA-Z0-9_])',
qw(
hash
pass
auth
root
key
fullbackup
),
);
my @SAFE_LONGMESS_FUNCTION_REGEXP_ITEMS = (
@SAFE_LONGMESS_KEY_REGEXP_ITEMS,
'__ANON__',
);
sub _print_without_die_handler {
my ($text) = @_;
local $SIG{'__WARN__'} = sub { };
local $SIG{'__DIE__'} = 'DEFAULT';
return print $text;
}
sub _run_callback_without_die_handler {
my ($callback) = @_;
local $SIG{'__WARN__'} = sub { };
local $SIG{'__DIE__'} = 'DEFAULT';
return $callback->();
}
sub _generate_html_error_message {
my ( $type, $error_container_message, $error_message ) = @_;
require Cpanel::Encoder::Tiny;
my $safe_error_message = Cpanel::Encoder::Tiny::safe_html_encode_str($error_message);
return qq[
<style type="text/css">.cpanel_internal_message_container {display: inline-block; margin: 10px; width: auto;} .cpanel_internal_message { border: 1px solid #fff; outline-style: solid; outline-width: 1px; outline-color: #aaa; padding: 5px; } .cpanel_internal_error_warn { background-color: #FFF6CF; } .cpanel_internal_error_error { background-color: #F8E7E6; }</style>
<div id="cpanel_notice_item_$error_count" class="cjt-pagenotice-container cjt-notice-container cpanel_internal_message_container internal-error-container">
<div class="yui-module cjt-notice cjt-pagenotice cjt-notice-$type">
<div class="cpanel_internal_message cpanel_internal_error_$type bd">
<div class="cjt-notice-content" style="width: 420px;">
<span>
$error_container_message
<a
class="error"
style="cursor:hand;cursor:pointer;"
onClick="document.getElementById('cpanel_internal_error_$error_count').style.display='';this.style.display='none'; return false;">
[show]
</a>
<a
class="error"
style="cursor:hand;cursor:pointer;"
onClick="document.getElementById('cpanel_notice_item_$error_count').style.display='none'; return false;">
[close]
</a>
</span>
<div id="cpanel_internal_error_$error_count" style="display:none;">
<textarea class="cpanel_internal_error_$type" style="font-weight:900; height:200px; width:410px; color: black;">$safe_error_message</textarea>
</div>
</div>
</div>
</div>
</div>
];
}
sub safe_longmess {
require Carp;
$Carp::Internal{'Cpanel::Carp'} = 1;
return sanitize_longmess( scalar Carp::longmess(@_) );
}
my ( $key_regexp, $key_regexp_double, $function_regexp );
sub sanitize_longmess {
_build_regexes() if !$key_regexp;
return join(
"\n",
map {
( tr{'"}{} && ( m{$key_regexp}o || m{$key_regexp_double}o || ( ( $_ =~ m{^[ \t]*([^\(]+)\(} )[0] || '' ) =~ m{$function_regexp}o ) ) # matches a line that needs to be sanitized
&& _sanitize_line($_); # sanitize
$_
} split( m{\n}, $_[0] )
) . "\n";
}
sub error_count {
return $error_count;
}
sub _sanitize_line { # Operates directly on $_[0] for speed
if ( !$INC{'Cpanel/Regex.pm'} ) { # PPI NO PARSE - inc check
local $@;
eval {
local $SIG{__DIE__};
local $SIG{__WARN__};
require Cpanel::Regex; # PPI NO PARSE - inc check
};
}
$_[0] =~ s/$Cpanel::Regex::regex{'singlequotedstring'}/__CPANEL_HIDDEN__/go if index( $_[0], q{'} ) != -1;
$_[0] =~ s/$Cpanel::Regex::regex{'doublequotedstring'}/__CPANEL_HIDDEN__/go if index( $_[0], q{"} ) != -1;
return 1;
}
sub _build_regexes {
my $key_regexp_items = join '|', @SAFE_LONGMESS_KEY_REGEXP_ITEMS;
$key_regexp = qr<
'
.*?
(?:
$key_regexp_items
)
.*?
'
\s*
,
>x;
$key_regexp_double = $key_regexp;
$key_regexp_double =~ tr{'}{"}; # "' fix for poor editors
my $function_regexp_items = join '|', @SAFE_LONGMESS_FUNCTION_REGEXP_ITEMS;
$function_regexp = qr<
::
.*?
(?:
$function_regexp_items
)
.*?
$
>x;
return 1;
}
1;
} # --- END Cpanel/Carp.pm
{ # --- BEGIN Cpanel/Set.pm
package Cpanel::Set;
use strict;
use warnings;
no warnings 'once';
sub difference {
my ($super_ar) = @_;
my %lookup;
@lookup{ map { @$_ } @_[ 1 .. $#_ ] } = ();
return grep { !exists $lookup{$_} } @$super_ar;
}
sub intersection {
my ( $super_ar, $sub_ar ) = @_;
my %lookup;
@lookup{@$sub_ar} = ();
return grep { exists $lookup{$_} } @$super_ar;
}
1;
} # --- END Cpanel/Set.pm
{ # --- BEGIN Cpanel/TimeHiRes.pm
package Cpanel::TimeHiRes;
use strict;
use warnings;
no warnings 'once';
use constant {
_gettimeofday => 96,
_clock_gettime => 228,
_CLOCK_REALTIME => 0,
_EINTR => 4,
_PACK_TEMPLATE => 'L!L!',
};
sub clock_gettime {
my $timeval = pack( _PACK_TEMPLATE, () );
_get_time_from_syscall(
_clock_gettime,
_CLOCK_REALTIME,
$timeval,
);
return unpack( _PACK_TEMPLATE, $timeval );
}
sub time {
my ( $secs, $nsecs ) = clock_gettime();
return $secs + ( $nsecs / 1_000_000_000 );
}
sub sleep {
my ($secs) = @_;
local $!;
my $retval = select( undef, undef, undef, $secs );
if ( $retval == -1 && $! != _EINTR ) {
require Cpanel::Exception;
die 'Cpanel::Exception'->can('create')->( 'SystemCall', 'The system failed to suspend command execution for [quant,_1,second,seconds] because of an error: [_2]', [ $secs, $! ] );
}
return $secs;
}
sub gettimeofday {
my $timeval = pack( _PACK_TEMPLATE, () );
_get_time_from_syscall(
_gettimeofday,
$timeval,
undef,
);
return unpack( _PACK_TEMPLATE, $timeval );
}
sub _get_time_from_syscall { ##no critic qw(RequireArgUnpacking)
my $syscall_num = shift;
local $!;
my $retval = syscall( $syscall_num, @_ );
if ( $retval == -1 ) {
require Cpanel::Exception;
die 'Cpanel::Exception'->can('create')->( 'SystemCall', 'The system failed to retrieve the time because of an error: [_1]', [$!] );
}
return;
}
1;
} # --- END Cpanel/TimeHiRes.pm
{ # --- BEGIN Cpanel/SafeFileLock.pm
package Cpanel::SafeFileLock;
use strict;
use warnings;
no warnings 'once';
use constant {
_ENOENT => 2,
_EDQUOT => 122,
DEBUG => 0,
MAX_LOCKFILE_SIZE => 8192,
};
sub new {
my ( $class, $path_to_lockfile, $fh, $path_to_file_being_locked ) = @_;
if ( scalar @_ != 4 ) {
die 'Usage: Cpanel::SafeFileLock->new($path_to_lockfile, $fh, $path_to_file_being_locked)';
}
if ($fh) {
write_lock_contents( $fh, $path_to_lockfile ) or return;
}
my $self = bless [
$path_to_lockfile,
$fh,
$path_to_file_being_locked,
], $class;
push @$self, @{ $self->stat_ar() }[ 1, 9 ];
return $self;
}
sub new_before_lock {
my ( $class, $path_to_lockfile, $path_to_file_being_locked ) = @_;
if ( scalar @_ != 3 ) {
die 'Usage: Cpanel::SafeFileLock->new_before_lock($path_to_lockfile, $path_to_file_being_locked)';
}
return bless [
$path_to_lockfile,
undef,
$path_to_file_being_locked,
], $class;
}
sub set_filehandle_and_unlinker_after_lock {
$_[0][1] = $_[1];
push @{ $_[0] }, @{ $_[0]->stat_ar() }[ 1, 9 ];
$_[0][5] = $_[2];
return $_[0];
}
sub get_path {
return $_[0]->[0];
}
sub get_path_to_file_being_locked {
return $_[0]->[2] // die "get_path_to_file_being_locked requires the object to be instantiated with the path_to_file_being_locked";
}
sub set_filehandle {
$_[0][1] = $_[1];
return $_[0];
}
sub get_filehandle {
return $_[0]->[1];
}
sub get_inode {
return $_[0]->[3];
}
sub get_mtime {
return $_[0]->[4];
}
sub get_path_fh_inode_mtime {
return @{ $_[0] }[ 0, 1, 3, 4 ];
}
sub stat_ar {
return [ stat( ( $_[0]->[1] && fileno( $_[0]->[1] ) ) ? $_[0]->[1] : $_[0]->[0] ) ];
}
sub lstat_ar {
return [ $_[0]->[1] && fileno( $_[0]->[1] ) ? stat( $_[0]->[1] ) : lstat( $_[0]->[0] ) ];
}
sub close {
return close $_[0]->[1] if ref $_[0]->[1];
$_[0]->[5] = undef;
return;
}
sub write_lock_contents { ## no critic qw(Subroutines::RequireArgUnpacking) -- only unpack on the failure case
local $!;
if (DEBUG) {
require Cpanel::Carp;
return 1 if syswrite( $_[0], "$$\n$0\n" . Cpanel::Carp::safe_longmess() . "\n" );
}
return 1 if syswrite( $_[0], "$$\n$0\n" );
my ( $fh, $path_to_lockfile ) = @_;
my $write_error = $!;
CORE::close($fh);
unlink $path_to_lockfile;
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::FileWriteError', [ 'path' => $path_to_lockfile, 'error' => $write_error ] );
}
sub fetch_lock_contents_if_exists {
my ($lockfile) = @_;
die 'Need lock file!' if !$lockfile;
open my $lockfile_fh, '<:stdio', $lockfile or do {
return if $! == _ENOENT();
die "open($lockfile): $!";
};
my $buffer;
my $read_result = read( $lockfile_fh, $buffer, MAX_LOCKFILE_SIZE );
if ( !defined $read_result ) {
die "read($lockfile): $!";
}
my ( $pid_line, $lock_name, $lock_obj ) = split( /\n/, $buffer, 3 );
chomp($lock_name) if length $lock_name;
my ($lock_pid) = $pid_line && ( $pid_line =~ m/(\d+)/ );
return ( $lock_pid, $lock_name || 'unknown', $lock_obj || 'unknown', $lockfile_fh );
}
1;
} # --- END Cpanel/SafeFileLock.pm
{ # --- BEGIN Cpanel/FHUtils/Tiny.pm
package Cpanel::FHUtils::Tiny;
use strict;
use warnings;
no warnings 'once';
sub is_a {
return !ref $_[0] ? 0 : ( ref $_[0] eq 'IO::Handle' || ref $_[0] eq 'GLOB' || UNIVERSAL::isa( $_[0], 'GLOB' ) ) ? 1 : 0;
}
sub are_same {
my ( $fh1, $fh2 ) = @_;
return 1 if $fh1 eq $fh2;
if ( fileno($fh1) && ( fileno($fh1) != -1 ) && fileno($fh2) && ( fileno($fh2) != -1 ) ) {
return 1 if fileno($fh1) == fileno($fh2);
}
return 0;
}
sub to_bitmask {
my @fhs = @_;
my $mask = q<>;
for my $fh (@fhs) {
vec( $mask, ref($fh) ? fileno($fh) : $fh, 1 ) = 1;
}
return $mask;
}
1;
} # --- END Cpanel/FHUtils/Tiny.pm
{ # --- BEGIN Cpanel/Debug.pm
package Cpanel::Debug;
use strict;
use warnings;
no warnings 'once';
our $HOOKS_DEBUG_FILE = '/var/cpanel/debughooks';
our $level = ( exists $ENV{'CPANEL_DEBUG_LEVEL'} && $ENV{'CPANEL_DEBUG_LEVEL'} ? int $ENV{'CPANEL_DEBUG_LEVEL'} : 0 );
my $debug_hooks_value;
my $logger;
sub debug_level {
my ($level) = @_;
$Cpanel::Debug::level = $level if defined $level;
return $Cpanel::Debug::level;
}
sub logger {
$logger = shift if (@_); # Set method for $logger if something is passed in.
return $logger ||= do {
local ( $@, $! );
require Cpanel::Logger;
Cpanel::Logger->new();
};
}
sub log_error {
local $!; #prevent logger from overwriting $!
return logger()->error( $_[0] );
}
sub log_warn {
local $!; #prevent logger from overwriting $!
return logger()->warn( $_[0] );
}
sub log_warn_no_backtrace {
local $!; #prevent logger from overwriting $!
my $logger = logger();
no warnings 'once';
local $Cpanel::Logger::ENABLE_BACKTRACE = 0;
return $logger->warn( $_[0] );
}
sub log_invalid {
local $!; #prevent logger from overwriting $!
return logger()->invalid( $_[0] );
}
sub log_deprecated {
local $!; #prevent logger from overwriting $!
return logger()->deprecated( $_[0] );
}
sub log_panic {
local $!; #prevent logger from overwriting $!
return logger()->panic( $_[0] );
}
sub log_die {
local $!; #prevent logger from overwriting $!
return logger()->die( $_[0] );
}
sub log_info {
local $!; #prevent logger from overwriting $!
return logger()->info( $_[0] );
}
sub log_debug {
local $!; #prevent logger from overwriting $!
return logger()->debug( $_[0] );
}
sub log_dump {
require Data::Dumper;
no warnings 'once';
local $Data::Dumper::Sortkeys = 1;
return log_info( Data::Dumper::Dumper( $_[0] ) );
}
sub debug_hooks_value {
return $debug_hooks_value if defined $debug_hooks_value;
return ( $debug_hooks_value = ( stat($HOOKS_DEBUG_FILE) )[7] || 0 );
}
1;
} # --- END Cpanel/Debug.pm
{ # --- BEGIN Cpanel/Hash.pm
package Cpanel::Hash;
use strict;
*get_fastest_hash = \&fnv1a_32;
use constant FNV1_32A_INIT => 0x811c9dc5;
use constant FNV_32_PRIME => 0x01000193;
use constant FNV_32_MOD => 2**32; # AKA 0x100000000 but that it non-portable;
sub fnv1a_32 {
my $fnv32 = FNV1_32A_INIT();
( $fnv32 = ( ( $fnv32 ^ $_ ) * FNV_32_PRIME() ) % FNV_32_MOD ) for unpack( 'C*', $_[0] );
return $fnv32;
}
1;
} # --- END Cpanel/Hash.pm
{ # --- BEGIN Cpanel/SafeFile/LockInfoCache.pm
package Cpanel::SafeFile::LockInfoCache;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::SafeFileLock (); # perlpkg line 211
sub new {
my ( $class, $pathname ) = @_;
die 'need path!' if !$pathname;
return bless { _path => $pathname }, $class;
}
sub get {
my ( $self, $inode, $mtime ) = @_;
die 'Need an inode & an mtime!' if !defined $inode || !defined $mtime;
if ( !exists $self->{"_inode_${inode}_$mtime"} ) {
my ( $pid, $name, $obj, $fh ) = Cpanel::SafeFileLock::fetch_lock_contents_if_exists( $self->{'_path'} );
if ($pid) {
my ( $real_inode, $real_mtime ) = ( stat $fh )[ 1, 9 ];
$self->{"_inode_${real_inode}_$real_mtime"} = [ $pid, $name, $obj ];
}
}
return $self->{"_inode_${inode}_$mtime"} ||= undef;
}
1;
} # --- END Cpanel/SafeFile/LockInfoCache.pm
{ # --- BEGIN Cpanel/SafeFile/LockWatcher.pm
package Cpanel::SafeFile::LockWatcher;
use strict;
use warnings;
no warnings 'once';
use constant _ENOENT => 2;
use constant _FILEHANDLE_TTL => 2;
sub new {
my ( $class, $lockfile ) = @_;
my $self = bless { _path => $lockfile, _new => 1 }, $class;
return $self->reload_from_disk();
}
sub reload_from_disk {
my ($self) = @_;
my $old_inode = $self->{'inode'};
@{$self}{qw( inode uid size mtime)} = $self->_get_inode_uid_size_mtime();
if ( delete $self->{'_new'} ) {
$self->{'changed'} = 0;
}
else {
$self->{'changed'} = ( $self->{'inode'} || 0 ) != ( $old_inode || 0 ) ? 1 : 0;
}
return $self;
}
sub _get_inode_uid_size_mtime {
my ($self) = @_;
my ( $inode, $uid, $size, $mtime );
local $!;
if ( open my $fh, '<', $self->{'_path'} ) {
( $inode, $uid, $size, $mtime ) = ( stat $fh )[ 1, 4, 7, 9 ];
$self->_add_fh_if_needed( $fh, $inode );
}
elsif ( $! != _ENOENT ) {
die "open(<, $self->{'_path'}): $!";
}
return ( $inode, $uid, $size, $mtime );
}
sub _add_fh_if_needed {
my ( $self, $fh, $inode ) = @_;
my $now = time;
my $fhs_hr = $self->{'_time_fhs'} //= {};
my $seen_inode = 0;
for my $time ( keys %$fhs_hr ) {
if ( ( $now - $time ) > _FILEHANDLE_TTL() ) {
delete $fhs_hr->{$time};
next;
}
if ( !$seen_inode ) {
foreach my $entry ( @{ $fhs_hr->{$time} } ) {
if ( $entry->[1] == $inode ) {
$seen_inode = 1;
last;
}
}
}
}
return if $seen_inode;
push @{ $fhs_hr->{ time() } }, [ $fh, $inode ];
return;
}
1;
} # --- END Cpanel/SafeFile/LockWatcher.pm
{ # --- BEGIN Cpanel/Syscall.pm
package Cpanel::Syscall;
use strict;
my %NAME_TO_NUMBER = qw(
close 3
fcntl 72
lchown 94
getrlimit 97
getsid 124
gettimeofday 96
sendfile 40
setrlimit 160
splice 275
write 1
setsid 112
getsid 124
inotify_init1 294
inotify_add_watch 254
inotify_rm_watch 255
setresuid 117
setresgid 119
setgroups 116
umount2 166
);
sub name_to_number {
my ($name) = @_;
return $NAME_TO_NUMBER{$name} || _die_unknown_syscall($name);
}
sub _die_unknown_syscall {
my ($name) = @_;
die "Unknown system call: “$name”";
}
sub syscall { ##no critic qw(RequireArgUnpacking)
local $!;
_die_unknown_syscall( $_[0] ) unless defined $_[0] && $NAME_TO_NUMBER{ $_[0] };
my $ret = CORE::syscall( $NAME_TO_NUMBER{ $_[0] }, scalar @_ > 1 ? @_[ 1 .. $#_ ] : () );
if ( ( $ret == -1 ) && $! ) {
if ( $INC{'Cpanel/Exception.pm'} ) {
die Cpanel::Exception::create( 'SystemCall', [ name => $_[0], error => $!, arguments => [ @_[ 1 .. $#_ ] ] ] );
}
else {
die "Failed system call “$_[0]”: $!";
}
}
return $ret;
}
1;
} # --- END Cpanel/Syscall.pm
{ # --- BEGIN Cpanel/Inotify.pm
package Cpanel::Inotify;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Autodie (); # perlpkg line 211
# use Cpanel::Context (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::Pack (); # perlpkg line 211
# use Cpanel::Syscall (); # perlpkg line 211
use constant POLL_SIZE => 65536;
use constant READ_TEMPLATE => (
wd => 'i', #int Watch descriptor
mask => 'I', #uint32_t Mask of events
cookie => 'I', #uint32_t Unique cookie associating related events
len => 'I', #uint32_t Size of “name” field
);
my %add_flags;
my %read_flags;
my %init1_flag;
my $UNPACK_OBJ;
my $UNPACK_SIZE;
sub new {
my ( $class, %opts ) = @_;
if ( !$UNPACK_OBJ ) {
$UNPACK_OBJ = Cpanel::Pack->new( [ READ_TEMPLATE() ] );
$UNPACK_SIZE = $UNPACK_OBJ->sizeof();
_setup_flags();
}
my @given_flags = $opts{'flags'} ? @{ $opts{'flags'} } : ();
my $mask = 0;
for my $f (@given_flags) {
$mask |= $init1_flag{$f} || do {
die Cpanel::Exception->create_raw("Invalid inotify_init1 flag: “$f”");
};
}
my $fd = Cpanel::Syscall::syscall( 'inotify_init1', $mask );
my %self = (
_fd => $fd,
);
Cpanel::Autodie::open( $self{'_fh'}, '<&=', $fd );
return bless \%self, $class;
}
sub add {
my ( $self, $path, %opts ) = @_;
my @flags = @{ $opts{'flags'} };
my $mask = 0;
for my $f (@flags) {
$mask |= $add_flags{$f} || do {
die Cpanel::Exception->create_raw("Invalid inotify_add_watch flag: “$f”");
};
}
my $wd = Cpanel::Syscall::syscall(
'inotify_add_watch',
$self->{'_fd'},
$path,
$mask,
);
if ( $wd < 1 ) {
die Cpanel::Exception->create_raw("inotify watch descriptor “$wd” means something is wrong?");
}
$self->{'_watches'}{$wd} = $path;
return $wd;
}
sub remove {
my ( $self, $wd ) = @_;
Cpanel::Syscall::syscall( 'inotify_rm_watch', $self->{'_fd'}, $wd );
return;
}
sub poll {
my ($self) = @_;
Cpanel::Context::must_be_list();
my $buf = q<>;
Cpanel::Autodie::sysread_sigguard( $self->{'_fh'}, $buf, POLL_SIZE() );
my @events;
while ( length $buf ) {
my $evt = $UNPACK_OBJ->unpack_to_hashref( substr( $buf, 0, $UNPACK_SIZE, q<> ) );
$evt->{'name'} = substr( $buf, 0, delete( $evt->{'len'} ), q<> );
$evt->{'name'} =~ s<\0+\z><>; #trailing NULs
$evt->{'flags'} = _mask_to_flags_ar( delete $evt->{'mask'} );
push @events, $evt;
}
return @events;
}
sub fileno {
my ($self) = @_;
return fileno( $self->{'_fh'} );
}
sub _mask_to_flags_ar {
my ($mask) = @_;
my @flags;
for my $k ( keys %read_flags ) {
push @flags, $k if $mask & $read_flags{$k};
}
@flags = sort @flags;
return \@flags;
}
sub _setup_flags {
my %flag_num = (
ACCESS => 0x1, # File was accessed
MODIFY => 0x2, # File was modified
ATTRIB => 0x4, # Metadata changed
CLOSE_WRITE => 0x8, # File opened for writing was closed
CLOSE_NOWRITE => 0x10, # File not opened for writing was closed
OPEN => 0x20, # File was opened
MOVED_FROM => 0x40, # File was moved from X
MOVED_TO => 0x80, # File was moved to Y
CREATE => 0x100, # Subfile was created
DELETE => 0x200, # Subfile was deleted
DELETE_SELF => 0x400, # Self was deleted
MOVE_SELF => 0x800, # Self was moved
);
%read_flags = (
%flag_num,
UNMOUNT => 0x00002000, # Backing fs was unmounted
Q_OVERFLOW => 0x00004000, # Event queued overflowed ('wd' is -1)
IGNORED => 0x00008000, # Watch was removed
ISDIR => 0x40000000, # event occurred against dir
);
%add_flags = (
%flag_num,
ONLYDIR => 0x01000000, # only watch the path if it is a directory
DONT_FOLLOW => 0x02000000, # don't follow a sym link
EXCL_UNLINK => 0x04000000, # exclude events on unlinked objects
MASK_ADD => 0x20000000, # add to the mask of an already existing watch
ONESHOT => 0x80000000, # only send event once
CLOSE => $read_flags{'CLOSE_WRITE'} | $read_flags{'CLOSE_NOWRITE'},
MOVE => $read_flags{'MOVED_FROM'} | $read_flags{'MOVED_TO'},
);
my $mask = 0;
$mask |= $_ for values %flag_num;
$add_flags{'ALL_EVENTS'} = $mask;
%init1_flag = (
CLOEXEC => $Cpanel::Fcntl::Constants::O_CLOEXEC,
NONBLOCK => $Cpanel::Fcntl::Constants::O_NONBLOCK,
);
return;
}
1;
} # --- END Cpanel/Inotify.pm
{ # --- BEGIN Cpanel/SafeFile.pm
package Cpanel::SafeFile;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::TimeHiRes (); # perlpkg line 211
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::SafeFileLock (); # perlpkg line 211
# use Cpanel::FHUtils::Tiny (); # perlpkg line 211
use constant {
_EWOULDBLOCK => 11,
_EACCES => 13,
_EDQUOT => 122,
_ENOENT => 2,
_EINTR => 4,
_EEXIST => 17,
_ENOSPC => 28,
_EPERM => 1,
MAX_LOCK_CREATE_ATTEMPTS => 90,
NO_PERM_TO_WRITE_TO_DOTLOCK_DIR => -1,
INOTIFY_FILE_DISAPPEARED => 2,
CREATE_FCNTL_VALUE => ( $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_EXCL | $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_NONBLOCK ),
UNLOCK_FCNTL_VALUE => $Cpanel::Fcntl::Constants::LOCK_UN,
LOCK_FILE_PERMS => 0644,
DEFAULT_LOCK_WAIT_TIME => 196,
MAX_LOCK_WAIT_TIME => 400,
MAX_LOCK_FILE_LENGTH => 225,
};
$Cpanel::SafeFile::VERSION = '5.0';
my $OVERWRITE_FCNTL_VALUE;
my $verbose = 0; # initialized in safelock
our $LOCK_WAIT_TIME; #allow lock wait time to be overwritten
my $OPEN_LOCKS = 0;
our $TIME_BETWEEN_DOTLOCK_CHECKS = 0.3;
our $TIME_BETWEEN_FLOCK_CHECKS = 0.05;
our $MAX_FLOCK_WAIT = 60; # allowed to be overwritten in tests
our $_SKIP_DOTLOCK_WHEN_NO_PERMS = 0;
our $_SKIP_WARN_ON_OPEN_FAIL = 0;
my $DOUBLE_LOCK_DETECTED = 4096;
sub safeopen { #fh, open()-style mode, path
my ( $mode, $file ) = _get_open_args( @_[ 1 .. $#_ ] );
my $open_method_coderef = sub {
my $ret = open( $_[0], $_[1], $_[2] ) || do {
_log_warn("open($_[1], $_[2]): $!");
return undef;
};
return $ret;
};
return _safe_open( $_[0], $mode, $file, $open_method_coderef, 'safeopen' );
}
sub safesysopen_no_warn_on_fail {
local $_SKIP_WARN_ON_OPEN_FAIL = 1;
return safesysopen(@_);
}
sub safesysopen_skip_dotlock_if_not_root {
local $_SKIP_DOTLOCK_WHEN_NO_PERMS = $> == 0 ? 0 : 1;
return safesysopen(@_);
}
sub safeopen_skip_dotlock_if_not_root {
local $_SKIP_DOTLOCK_WHEN_NO_PERMS = $> == 0 ? 0 : 1;
return safeopen(@_);
}
sub safelock_skip_dotlock_if_not_root {
local $_SKIP_DOTLOCK_WHEN_NO_PERMS = $> == 0 ? 0 : 1;
return safelock(@_);
}
sub safereopen { ##no critic qw(RequireArgUnpacking)
my $fh = shift;
if ( !$fh ) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Undefined filehandle not allowed!");
}
elsif ( !fileno $fh ) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Closed filehandle ($fh) not allowed!");
}
my ( $mode, $file ) = _get_open_args(@_);
my $open_method_coderef = sub {
return open( $_[0], $_[1], $_[2] ) || do {
_log_warn("open($_[1], $_[2]): $!");
return undef;
};
};
return _safe_re_open( $fh, $mode, $file, $open_method_coderef, 'safereopen' );
}
sub safesysopen { ##no critic qw(RequireArgUnpacking)
my ( $file, $open_mode, $custom_perms ) = ( @_[ 1 .. 3 ] );
my ( $sysopen_perms, $original_umask );
$open_mode = _sanitize_open_mode($open_mode);
my $open_method_coderef = sub {
return sysopen( $_[0], $_[2], $_[1], $sysopen_perms ) || do {
_log_warn("open($_[2], $_[1], $sysopen_perms): $!") unless $_SKIP_WARN_ON_OPEN_FAIL;
return undef;
};
};
if ( defined $custom_perms ) {
$custom_perms &= 0777;
$original_umask = umask( $custom_perms ^ 07777 );
$sysopen_perms = $custom_perms;
}
else {
$sysopen_perms = 0666;
}
my $lock_ref;
local $@;
my $ok = eval {
$lock_ref = _safe_open( $_[0], $open_mode, $file, $open_method_coderef, 'safesysopen' );
1;
};
if ( defined $custom_perms ) {
umask($original_umask);
}
die if !$ok;
return $lock_ref;
}
sub safeclose {
my ( $fh, $lockref, $do_something_before_releasing_lock ) = @_;
if ( $do_something_before_releasing_lock && ref $do_something_before_releasing_lock eq 'CODE' ) {
$do_something_before_releasing_lock->();
}
my $success = 1;
if ( $fh && defined fileno $fh ) {
flock( $fh, UNLOCK_FCNTL_VALUE ) or _log_warn( "flock(LOCK_UN) on “" . $lockref->get_path() . "” failed with error: $!" ); # LOCK_UN
$success = close $fh;
}
my $safe_unlock = safeunlock($lockref);
$OPEN_LOCKS-- if ( $safe_unlock && $success );
return ( $safe_unlock && $success );
}
sub safelock {
my ($file) = @_;
my $lock_obj = _safelock($file);
return if !ref $lock_obj;
return $lock_obj;
}
sub _safelock {
my ($file) = @_;
if ( !$file || $file =~ tr/\0// ) {
_log_warn('safelock: Invalid arguments');
return;
}
$verbose ||= ( _verbose_flag_file_exists() ? 1 : -1 );
my $lockfile = _calculate_lockfile($file);
my $safefile_lock = Cpanel::SafeFileLock->new_before_lock( $lockfile, $file );
my ( $lock_status, $lock_fh, $attempts, $last_err );
{
local $@;
while ( ++$attempts < MAX_LOCK_CREATE_ATTEMPTS ) {
( $lock_status, $lock_fh ) = _lock_wait( $file, $safefile_lock, $lockfile );
last if $lock_status;
$last_err = $!;
if ( $lock_fh && $lock_fh == $DOUBLE_LOCK_DETECTED ) {
return 0;
}
}
}
if ( $lock_fh == 1 ) {
return 1;
}
elsif ( $lock_status && $lock_fh ) {
return $safefile_lock;
}
_log_warn( 'safelock: waited for lock (' . $lockfile . ') ' . $attempts . ' times' );
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::FileCreateError', [ 'path' => $lockfile, 'error' => $last_err ] );
}
sub _write_temp_lock_file {
my ($lockfile) = @_;
my $temp_file = sprintf(
'%s-%x-%x-%x',
$lockfile,
substr( rand, 2 ),
scalar( reverse time ),
scalar( reverse $$ ),
);
my ( $ok, $fh_or_err ) = _create_lockfile($temp_file);
if ( !$ok ) {
if ( $fh_or_err == _EPERM() || $fh_or_err == _EACCES() ) {
local $!;
my $lock_dir = _getdir($lockfile);
if ( !-w $lock_dir ) {
if ($_SKIP_DOTLOCK_WHEN_NO_PERMS) { # A hack to allow /etc/valiases to still be flock()ed until we can refactor
return ( NO_PERM_TO_WRITE_TO_DOTLOCK_DIR, $fh_or_err );
}
else {
_log_warn("safelock: Failed to create a lockfile '$temp_file' in the directory '$lock_dir' that isn't writable: $fh_or_err");
}
}
}
return ( 0, $fh_or_err );
}
Cpanel::SafeFileLock::write_lock_contents( $fh_or_err, $temp_file );
return ( $temp_file, $fh_or_err );
}
sub _try_to_install_lockfile {
my ( $temp_file, $lockfile ) = @_;
link( $temp_file => $lockfile ) or do {
return 0 if $! == _EEXIST;
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::LinkError', [ oldpath => $temp_file, newpath => $lockfile, error => $! ] );
};
return 1;
}
sub safeunlock {
my $lockref = shift;
if ( !$lockref ) {
_log_warn('safeunlock: Invalid arguments');
return;
}
elsif ( !ref $lockref ) {
return 1 if $lockref eq '1'; # No lock file created so just succeed
$lockref = Cpanel::SafeFileLock->new( $lockref, undef, undef );
if ( !$lockref ) {
_log_warn("safeunlock: failed to generate a Cpanel::SafeFileLock object from a path");
return;
}
}
my ( $lock_path, $fh, $lock_inode, $lock_mtime ) = $lockref->get_path_fh_inode_mtime();
my ( $filesys_lock_ino, $filesys_lock_mtime ) = ( lstat $lock_path )[ 1, 9 ];
if ( $fh && !defined fileno($fh) ) {
return 1;
}
elsif ( !$filesys_lock_mtime ) {
_log_warn( 'Lock on ' . $lockref->get_path_to_file_being_locked() . ' lost!' );
$lockref->close();
return; # return false on false
}
elsif ( $lock_inode && ( $lock_inode == $filesys_lock_ino ) && $lock_path && ( $lock_mtime == $filesys_lock_mtime ) ) {
unlink $lock_path or do {
_log_warn("Could not unlink lock file “$lock_path” as ($>/$)): $!\n");
$lockref->close();
return; # return false on false
};
return $lockref->close();
}
$lockref->close();
my ( $lock_pid, $lock_name, $lock_obj ) = Cpanel::SafeFileLock::fetch_lock_contents_if_exists($lock_path);
if ($lock_pid) {
if ( $lock_pid == $$ ) {
unlink $lock_path; # best-effort; ignore failure
return 1;
}
$lock_inode ||= 0;
$lock_mtime ||= 0;
_log_warn("[$$] Attempt to unlock file that was locked by another process [LOCK_PATH]=[$lock_path] [LOCK_PID]=[$lock_pid] [LOCK_PROCESS]=[$lock_name] [LOCK_INODE]=[$filesys_lock_ino] [LOCK_MTIME]=[$filesys_lock_mtime] -- [NON_LOCK_PID]=[$$] [NON_LOCK_PROCESS]=[$0] [NON_LOCK_INODE]=[$lock_inode] [NON_LOCK_MTIME]=[$lock_mtime]");
}
return;
}
sub _safe_open {
my ( undef, $open_mode, $file, $open_method_coderef, $open_method ) = @_;
if ( !defined $open_mode || !$open_method_coderef || !$file || $file =~ tr/\0// ) {
_log_warn('_safe_open: Invalid arguments');
return;
}
elsif ( defined $_[0] ) {
my $fh_type = ref $_[0];
if ( !Cpanel::FHUtils::Tiny::is_a( $_[0] ) ) {
_log_warn("Invalid file handle type '$fh_type' provided for $open_method of '$file'");
return;
}
}
if ( my $lockref = _safelock($file) ) {
if ( $open_method_coderef->( $_[0], $open_mode, $file ) ) {
if ( my $err = _do_flock_or_return_exception( $_[0], $open_mode, $file ) ) {
safeunlock($lockref);
local $@ = $err;
die;
}
$OPEN_LOCKS++;
return $lockref;
}
else {
local $!;
safeunlock($lockref);
return;
}
}
else {
_log_warn("safeopen: could not acquire a lock for '$file': $!");
return;
}
}
my $_lock_ex_nb;
my $_lock_sh_nb;
sub _do_flock_or_return_exception {
my ( $fh, $open_mode, $path ) = @_;
my $flock_start_time;
my $lock_op =
_is_write_open_mode($open_mode)
? ( $_lock_ex_nb //= $Cpanel::Fcntl::Constants::LOCK_EX | $Cpanel::Fcntl::Constants::LOCK_NB )
: ( $_lock_sh_nb //= $Cpanel::Fcntl::Constants::LOCK_SH | $Cpanel::Fcntl::Constants::LOCK_NB );
local $!;
my $flock_err;
my $flock_max_wait_time_is_whole_number = int($MAX_FLOCK_WAIT) == $MAX_FLOCK_WAIT;
while ( !flock $fh, $lock_op ) {
$flock_err = $!;
if ( $flock_err == _EINTR || $flock_err == _EWOULDBLOCK ) {
if ( !$flock_start_time ) {
$flock_start_time = $flock_max_wait_time_is_whole_number ? time() : Cpanel::TimeHiRes::time();
next;
}
if ( ( ( $flock_max_wait_time_is_whole_number ? time() : Cpanel::TimeHiRes::time() ) - $flock_start_time ) > $MAX_FLOCK_WAIT ) {
require Cpanel::Exception;
return _timeout_exception( $path, $MAX_FLOCK_WAIT );
}
else {
Cpanel::TimeHiRes::sleep($TIME_BETWEEN_FLOCK_CHECKS);
}
next;
}
require Cpanel::Exception;
return Cpanel::Exception::create( 'IO::FlockError', [ path => $path, error => $flock_err, operation => $lock_op ] );
}
return undef;
}
sub _safe_re_open {
my ( $fh, $open_mode, $file, $open_method_coderef, $open_method ) = @_;
if ( !defined $open_mode || !$open_method_coderef || !$file || $file =~ tr/\0// ) {
_log_warn('_safe_re_open: Invalid arguments');
return;
}
else {
my $fh_type = ref $fh;
if ( !Cpanel::FHUtils::Tiny::is_a($fh) ) {
_log_warn("Invalid file handle type '$fh_type' provided for $open_method of '$file'");
return;
}
}
close $fh;
if ( $open_method_coderef->( $fh, $open_mode, $file ) ) {
if ( my $err = _do_flock_or_return_exception( $fh, $open_mode, $file ) ) {
die $err;
}
return $fh;
}
return;
}
sub _log_warn {
require Cpanel::Debug;
goto &Cpanel::Debug::log_warn;
}
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 '+>' ? '+>'
: $mode eq '+>>' ? '+>>'
: return;
return ( $mode, $file );
}
sub _sanitize_open_mode {
my ($mode) = @_;
return if $mode =~ m/[^0-9]/;
my $safe_mode = ( $mode & $Cpanel::Fcntl::Constants::O_RDONLY );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_WRONLY );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_RDWR );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_CREAT );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_EXCL );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_APPEND );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_TRUNC );
$safe_mode |= ( $mode & $Cpanel::Fcntl::Constants::O_NONBLOCK );
return $safe_mode;
}
sub _calculate_lockfile { ## no critic qw(Subroutines::RequireArgUnpacking)
my $lockfile = $_[0] =~ tr{<>}{} ? ( ( $_[0] =~ /^[><]*(.*)/ )[0] . '.lock' ) : $_[0] . '.lock';
return $lockfile if ( length $lockfile <= MAX_LOCK_FILE_LENGTH );
require File::Basename;
my $lock_basename = File::Basename::basename($lockfile);
return $lockfile if ( length $lock_basename <= MAX_LOCK_FILE_LENGTH );
require Cpanel::Hash;
my $hashed_lock_basename = Cpanel::Hash::get_fastest_hash($lock_basename) . ".lock";
if ( $lockfile eq $lock_basename ) {
return $hashed_lock_basename;
}
else {
return File::Basename::dirname($lockfile) . '/' . $hashed_lock_basename;
}
}
sub is_locked {
my ($file) = @_;
my $lockfile = _calculate_lockfile($file);
my ( $lock_pid, $lock_name, $lock_obj ) = Cpanel::SafeFileLock::fetch_lock_contents_if_exists($lockfile);
if ( _is_valid_pid($lock_pid) && _pid_is_alive($lock_pid) ) {
return 1;
}
return 0;
}
sub _timeout_exception {
my ( $path, $waited ) = @_;
require Cpanel::Exception;
return Cpanel::Exception::create( 'Timeout', 'The system failed to lock the file “[_1]” after [quant,_2,second,seconds].', [ $path, $waited ] );
}
sub _die_if_file_is_flocked_cuz_already_waited_a_while {
my ( $file, $waited ) = @_;
if ( _open_to_write( my $fh, $file ) ) {
$_lock_ex_nb //= $Cpanel::Fcntl::Constants::LOCK_EX | $Cpanel::Fcntl::Constants::LOCK_NB;
if ( flock( $fh, $_lock_ex_nb ) == 1 ) {
flock $fh, UNLOCK_FCNTL_VALUE or die "Failed to unlock “$file” after having just locked it: $!";
}
else {
require Cpanel::Exception;
if ( $! == _EWOULDBLOCK ) {
die _timeout_exception( $file, $waited );
}
else {
die Cpanel::Exception::create( 'IO::FlockError', [ path => $file, error => $!, operation => $_lock_ex_nb ] );
}
}
}
return;
}
sub _lock_wait { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $file, $safefile_lock, $lockfile ) = @_;
my ( $temp_file, $fh ) = _write_temp_lock_file( $lockfile, $file );
if ( $temp_file eq NO_PERM_TO_WRITE_TO_DOTLOCK_DIR ) {
return ( 1, 1 );
}
if ( !$temp_file ) {
return ( 0, $fh );
}
$safefile_lock->set_filehandle_and_unlinker_after_lock( $fh, Cpanel::SafeFile::_temp->new($temp_file) );
return ( 1, $fh ) if _try_to_install_lockfile( $temp_file, $lockfile );
local $0 = ( $verbose == 1 ) ? "$0 - waiting for lock on $file" : "$0 - waiting for lock";
require Cpanel::SafeFile::LockInfoCache;
require Cpanel::SafeFile::LockWatcher;
my $watcher = Cpanel::SafeFile::LockWatcher->new($lockfile);
my $waittime = _calculate_waittime_for_file($file);
my ( $inotify_obj, $inotify_mask, $inotify_file_disappeared );
my $start_time = time;
my $waited = 0;
my $lockfile_cache = Cpanel::SafeFile::LockInfoCache->new($lockfile);
my ( $inotify_inode, $inotify_mtime );
LOCK_WAIT:
while (1) {
$waited = ( time() - $start_time );
if ( $waited > $waittime ) {
_die_if_file_is_flocked_cuz_already_waited_a_while( $file, $waited );
if ( defined $watcher->{'inode'} ) {
require Cpanel::Debug;
Cpanel::Debug::log_warn( sprintf "Replacing stale lock file: $lockfile. The kernel’s lock is gone, last modified %s seconds ago (mtime=$watcher->{'mtime'}), and waited over $waittime seconds.", time - $watcher->{'mtime'} );
}
return ( 1, $fh ) if _overwrite_lockfile_if_inode_mtime_matches( $temp_file, $lockfile, $watcher->{'inode'}, $watcher->{'mtime'} );
die _timeout_exception( $file, $waittime );
}
if ( $watcher->{'inode'} ) {
my $lock_get = $lockfile_cache->get( @{$watcher}{ 'inode', 'mtime' } );
if ( !$lock_get ) {
my $size_before_reload = $watcher->{'size'};
$watcher->reload_from_disk();
if ( $size_before_reload == 0 && $watcher->{'size'} == 0 ) {
_log_warn("[$$] UID $> clobbering empty lock file “$lockfile” (UID $watcher->{'uid'}) written by “unknown” at $watcher->{'mtime'}");
return ( 1, $fh ) if _overwrite_lockfile_if_inode_mtime_matches( $temp_file, $lockfile, $watcher->{'inode'}, $watcher->{'mtime'} );
}
next LOCK_WAIT;
}
my ( $lock_pid, $lock_name, $lock_obj ) = @$lock_get;
if ( $lock_pid == $$ ) {
$watcher->reload_from_disk();
_log_warn("[$$] Double locking detected by self [LOCK_PATH]=[$lockfile] [LOCK_PID]=[$lock_pid] [LOCK_OBJ]=[$lock_obj] [LOCK_PROCESS]=[$lock_name] [ACTUAL_INODE]=[$watcher->{'inode'}] [ACTUAL_MTIME]=[$watcher->{'mtime'}]");
return ( 0, $DOUBLE_LOCK_DETECTED );
}
elsif ( !_pid_is_alive($lock_pid) ) {
my $time = time();
if ( _overwrite_lockfile_if_inode_mtime_matches( $temp_file, $lockfile, $watcher->{'inode'}, $watcher->{'mtime'} ) ) {
_log_warn("[$$] TIME $time UID $> clobbered stale lock file “$lockfile” (NAME “$lock_name”, UID $watcher->{'uid'}) written by PID $lock_pid at $watcher->{'mtime'}");
return ( 1, $fh );
}
$watcher->reload_from_disk();
next LOCK_WAIT;
}
else {
require Cpanel::Debug;
Cpanel::Debug::log_info("[$$] Waiting for lock on $file held by $lock_name with pid $lock_pid") if $verbose == 1;
}
}
return ( 1, $fh ) if _try_to_install_lockfile( $temp_file, $lockfile );
$watcher->reload_from_disk();
if ( !$inotify_obj || !$inotify_inode || !$watcher->{'inode'} || $inotify_inode != $watcher->{'inode'} || $inotify_mtime != $watcher->{'mtime'} ) {
INOTIFY: {
( $inotify_obj, $inotify_mask, $inotify_file_disappeared ) = _generate_inotify_for_lock_file($lockfile);
$watcher->reload_from_disk();
if ( $inotify_file_disappeared || !$watcher->{'inode'} ) {
undef $inotify_obj;
next LOCK_WAIT;
}
redo INOTIFY if $watcher->{'changed'};
( $inotify_inode, $inotify_mtime ) = @{$watcher}{ 'inode', 'mtime' };
}
}
my $selected = _select( my $m = $inotify_mask, undef, undef, $TIME_BETWEEN_DOTLOCK_CHECKS );
if ( $selected == -1 ) {
die "select() error: $!" if $! != _EINTR();
}
elsif ($selected) {
return ( 1, $fh ) if _try_to_install_lockfile( $temp_file, $lockfile );
$watcher->reload_from_disk();
() = $inotify_obj->poll();
}
}
return;
}
sub _select {
return select( $_[0], $_[1], $_[2], $_[3] );
}
sub _generate_inotify_for_lock_file {
my ($file) = @_;
require Cpanel::Inotify;
my $inotify_obj;
my $rin = '';
local $@;
eval {
$inotify_obj = Cpanel::Inotify->new( flags => ['NONBLOCK'] );
$inotify_obj->add( $file, flags => [ 'ATTRIB', 'DELETE_SELF' ] );
vec( $rin, $inotify_obj->fileno(), 1 ) = 1;
};
if ($@) {
my $err = $@;
if ( eval { $err->isa('Cpanel::Exception::SystemCall') } ) {
my $err = $err->get('error');
if ( $err == _ENOENT ) {
return ( undef, undef, INOTIFY_FILE_DISAPPEARED );
}
elsif ( $err != _EACCES ) { # Don’t warn if EACCES
local $@ = $err;
warn;
}
}
else {
local $@ = $err;
warn;
}
return;
}
return ( $inotify_obj, $rin, 0 );
}
sub _pid_is_alive {
my ($pid) = @_;
local $!;
if ( kill( 0, $pid ) ) {
return 1;
}
elsif ( $! == _EPERM ) {
return !!( stat "/proc/$pid" )[0];
}
return 0;
}
sub _calculate_waittime_for_file {
my ($file) = @_;
return $LOCK_WAIT_TIME if $LOCK_WAIT_TIME;
my $waittime = DEFAULT_LOCK_WAIT_TIME;
if ( -e $file ) {
$waittime = int( ( stat _ )[7] / 10000 );
$waittime = $waittime > MAX_LOCK_WAIT_TIME ? MAX_LOCK_WAIT_TIME : $waittime < DEFAULT_LOCK_WAIT_TIME ? DEFAULT_LOCK_WAIT_TIME : $waittime;
}
return $waittime;
}
sub _is_valid_pid {
my $pid = shift;
return 0 unless defined $pid;
return $pid =~ tr{0-9}{}c ? 0 : 1;
}
sub _getdir {
my @path = split( /\/+/, $_[0] );
return join( '/', (@path)[ 0 .. ( $#path - 1 ) ] ) || '.';
}
sub _create_lockfile {
my $lock_fh;
return sysopen( $lock_fh, $_[0], CREATE_FCNTL_VALUE, LOCK_FILE_PERMS ) ? ( 1, $lock_fh ) : ( 0, $! );
}
sub _open_to_write {
my $path = $_[1];
$OVERWRITE_FCNTL_VALUE ||= ( $Cpanel::Fcntl::Constants::O_WRONLY | $Cpanel::Fcntl::Constants::O_NONBLOCK | $Cpanel::Fcntl::Constants::O_APPEND | $Cpanel::Fcntl::Constants::O_NOFOLLOW );
return sysopen( $_[0], $path, $OVERWRITE_FCNTL_VALUE, LOCK_FILE_PERMS );
}
sub _overwrite_lockfile_if_inode_mtime_matches {
my ( $temp_file, $lockfile, $lockfile_inode, $lockfile_mtime ) = @_;
my ( $inode, $mtime ) = ( stat $lockfile )[ 1, 9 ];
if ( !$inode ) {
die "stat($lockfile): $!" if $! != _ENOENT();
}
if ( !$inode || ( $inode == $lockfile_inode && $mtime == $lockfile_mtime ) ) {
rename( $temp_file, $lockfile ) or do {
require Cpanel::Exception;
die Cpanel::Exception::create( 'IO::RenameError', [ oldpath => $temp_file, newpath => $lockfile, error => $! ] );
};
return 1;
}
return 0;
}
sub _is_write_open_mode {
my ($mode) = @_;
if ( $mode =~ tr{0-9}{}c ) {
if ( $mode && ( -1 != index( $mode, '>' ) || -1 != index( $mode, '+' ) ) ) {
return 1;
}
}
else {
if ( $mode && ( ( $mode & $Cpanel::Fcntl::Constants::O_WRONLY ) || ( $mode & $Cpanel::Fcntl::Constants::O_RDWR ) ) ) {
return 1;
}
}
return 0;
}
sub _verbose_flag_file_exists {
return -e '/var/cpanel/safefile_verbose';
}
package Cpanel::SafeFile::_temp;
use constant _ENOENT => 2;
sub new { return bless [ $_[1], $_SKIP_DOTLOCK_WHEN_NO_PERMS, $$ ], $_[0]; }
sub DESTROY {
local $!;
unlink $_[0]->[0] or do {
if ( !$_[0]->[1] && $! != _ENOENT && $_[0]->[2] == $$ ) {
warn "unlink($_[0]->[0]): $!";
}
};
return;
}
1;
} # --- END Cpanel/SafeFile.pm
{ # --- BEGIN Cpanel/LoadModule.pm
package Cpanel::LoadModule;
use strict;
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::LoadModule::Utils (); # perlpkg line 211
my $logger;
my $has_perl_dir = 0;
sub _logger_warn {
my ( $msg, $fail_ok ) = @_;
return if $fail_ok && $ENV{'CPANEL_BASE_INSTALL'} && index( $^X, '/usr/local/cpanel' ) == -1;
if ( $INC{'Cpanel/Logger.pm'} ) {
$logger ||= 'Cpanel::Logger'->new();
$logger->warn($msg);
}
return warn $msg;
}
sub _reset_has_perl_dir {
$has_perl_dir = 0;
return;
}
sub load_perl_module { ## no critic qw(Subroutines::RequireArgUnpacking)
if ( -1 != index( $_[0], q<'> ) ) {
die Cpanel::Exception::create_raw( 'InvalidParameter', "Module names with single-quotes are prohibited. ($_[0])" );
}
return $_[0] if Cpanel::LoadModule::Utils::module_is_loaded( $_[0] );
my ( $mod, @LIST ) = @_;
local ( $!, $@ );
if ( !is_valid_module_name($mod) ) {
die Cpanel::Exception::create( 'InvalidParameter', '“[_1]” is not a valid name for a Perl module.', [$mod] );
}
my $args_str;
if (@LIST) {
$args_str = join ',', map {
die "Only scalar arguments allowed in LIST! (@LIST)" if ref;
_single_quote($_);
} @LIST;
}
else {
$args_str = q<>;
}
eval "use $mod ($args_str);"; ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
if ($@) {
die Cpanel::Exception::create( 'ModuleLoadError', [ module => $mod, error => $@ ] );
}
return $mod;
}
*module_is_loaded = *Cpanel::LoadModule::Utils::module_is_loaded;
*is_valid_module_name = *Cpanel::LoadModule::Utils::is_valid_module_name;
sub loadmodule {
return 1 if cpanel_namespace_module_is_loaded( $_[0] );
return _modloader( $_[0] );
}
sub lazy_load_module {
my $mod = shift;
my $mod_path = $mod;
$mod_path =~ s{::}{/}g;
if ( exists $INC{ $mod_path . '.pm' } ) {
return;
}
if ( !is_valid_module_name($mod) ) {
_logger_warn("Cpanel::LoadModule: Invalid module name ($mod)");
return;
}
eval "use $mod ();";
if ($@) {
delete $INC{ $mod_path . '.pm' };
_logger_warn( "Cpanel::LoadModule:: Failed to load module $mod - $@", 1 );
return;
}
return 1;
}
sub cpanel_namespace_module_is_loaded {
my ($modpart) = @_;
$modpart =~ s{::}{/}g;
return exists $INC{"Cpanel/$modpart.pm"} ? 1 : 0;
}
sub _modloader {
my $module = shift;
if ( !$module ) {
_logger_warn("Empty module name passed to modloader");
return;
}
if ( !is_valid_module_name($module) ) {
_logger_warn("Invalid module name ($module) passed to modloader");
return;
}
eval qq[ use Cpanel::${module}; Cpanel::${module}::${module}_init() if "Cpanel::${module}"->can("${module}_init"); ]; # PPI USE OK - This looks like usage of the Cpanel module and it's not.
if ($@) {
_logger_warn("Error loading module $module - $@");
return;
}
return 1;
}
sub _single_quote {
local ($_) = $_[0];
s/([\\'])/\\$1/g;
return qq('$_');
}
1;
} # --- END Cpanel/LoadModule.pm
{ # --- BEGIN Cpanel/Linux/Constants.pm
package Cpanel::Linux::Constants;
use strict;
use warnings;
no warnings 'once';
use constant {
NAME_MAX => 255,
PATH_MAX => 4096,
};
1;
} # --- END Cpanel/Linux/Constants.pm
{ # --- BEGIN Cpanel/Validate/FilesystemNodeName.pm
package Cpanel::Validate::FilesystemNodeName;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::Linux::Constants (); # perlpkg line 211
sub is_valid {
my ($node) = @_;
local $@;
eval { validate_or_die($node); };
return $@ ? 0 : 1;
}
sub validate_or_die {
my ($name) = @_;
if ( !length $name ) {
die Cpanel::Exception::create('Empty');
}
elsif ( $name eq '.' || $name eq '..' ) {
die Cpanel::Exception::create( 'Reserved', [ value => $name ] );
}
elsif ( length $name > Cpanel::Linux::Constants::NAME_MAX() ) {
die Cpanel::Exception::create( 'TooManyBytes', [ value => $name, maxlength => Cpanel::Linux::Constants::NAME_MAX() ] );
}
elsif ( index( $name, '/' ) != -1 ) {
die Cpanel::Exception::create( 'InvalidCharacters', [ value => $name, invalid_characters => ['/'] ] );
}
elsif ( index( $name, "\0" ) != -1 ) {
die Cpanel::Exception::create( 'InvalidCharacters', 'This value may not contain a [asis,NUL] byte.', [ value => $name, invalid_characters => ["\0"] ] );
}
return 1;
}
1;
} # --- END Cpanel/Validate/FilesystemNodeName.pm
{ # --- BEGIN Cpanel/Notify.pm
package Cpanel::Notify;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Set (); # perlpkg line 211
# use Cpanel::Fcntl (); # perlpkg line 211
# use Cpanel::SafeFile (); # perlpkg line 211
# use Cpanel::LoadModule (); # perlpkg line 211
# use Cpanel::Validate::FilesystemNodeName (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
our $VERSION = '1.8';
my $DEFAULT_CONTENT_TYPE = 'text/plain; charset=utf-8';
our $NOTIFY_INTERVAL_STORAGE_DIR = '/var/cpanel/notifications';
sub notification_class {
my (%args) = @_;
if ( !defined $args{'interval'} ) {
$args{'interval'} = 1;
}
if ( !defined $args{'status'} ) {
$args{'status'} = 'No status set';
}
foreach my $param (qw(application status class constructor_args)) {
die Cpanel::Exception::create( 'MissingParameter', [ 'name' => $param ] ) if !defined $args{$param};
}
if ( my @unwelcome_params = Cpanel::Set::difference( [ keys %args ], [qw(application status class constructor_args interval)] ) ) {
die Cpanel::Exception::create_raw(
'InvalidParameters',
"The following parameters don't belong as an argument to notification_class(); you may have meant to pass these in constructor_args instead: " . join( ' ', @unwelcome_params )
);
}
my $constructor_args = { @{ $args{'constructor_args'} } };
if ( $constructor_args->{'skip_send'} ) {
my $class = "Cpanel::iContact::Class::$args{'class'}";
Cpanel::LoadModule::load_perl_module($class);
return $class->new(%$constructor_args);
}
return _notification_backend(
$args{'application'},
$args{'status'},
$args{'interval'},
sub {
my $class = "Cpanel::iContact::Class::$args{'class'}";
Cpanel::LoadModule::load_perl_module($class);
return $class->new(%$constructor_args);
},
);
}
sub notification {
my %AGS = @_;
my $app = $AGS{'app'} || $AGS{'application'} || 'Notice';
return _notification_backend(
$app,
$AGS{'status'},
$AGS{'interval'} || 0,
sub {
my $module = "Cpanel::iContact";
Cpanel::LoadModule::load_perl_module($module);
my $from = $AGS{'from'};
my $to = $AGS{'to'};
my $msgheader = $AGS{'msgheader'} || $AGS{'subject'};
my $message = $AGS{'message'};
my $plaintext_message = $AGS{'plaintext_message'};
my $priority = $AGS{'priority'} || 3;
my $attach_files = $AGS{'attach_files'} || [];
my $content_type = $AGS{'content-type'} || $DEFAULT_CONTENT_TYPE;
"$module"->can('icontact')->(
'attach_files' => $attach_files,
'application' => $app,
'level' => $priority,
'from' => $from,
'to' => $to,
'subject' => $msgheader,
'message' => $message,
'plaintext_message' => $plaintext_message,
'content-type' => $content_type,
);
}
);
}
sub _notification_backend {
my ( $app, $status, $interval, $todo_cr ) = @_;
my $is_ready = _checkstatusinterval(
'app' => $app,
'status' => $status,
'interval' => $interval,
);
if ($is_ready) {
return $todo_cr->();
}
elsif ( $Cpanel::Debug::level > 3 ) {
Cpanel::Debug::log_warn("not sending notify app=[$app] status=[$status] interval=[$interval]");
}
return $is_ready ? 1 : 0;
}
sub notify_blocked {
my %AGS = @_;
my $app = $AGS{'app'};
my $status = $AGS{'status'};
my $interval = $AGS{'interval'};
return 0 if $interval <= 1; # Special Case (ignore interval check);
$app =~ s{/}{_}g; # Its possible to have slashes in the app name
$status =~ s{:}{_}g; # Its possible to have colons in the status
my $db_file = "$NOTIFY_INTERVAL_STORAGE_DIR/$app";
return 0 if !-e $db_file;
my %notifications;
my $notify_db_fh;
if (
my $nlock = Cpanel::SafeFile::safesysopen(
$notify_db_fh, $db_file, Cpanel::Fcntl::or_flags('O_RDONLY'),
0600
)
) {
local $/;
%notifications = map { ( split( /:/, $_, 2 ) )[ 0, 1 ] } split( m{\n}, readline($notify_db_fh) );
Cpanel::SafeFile::safeclose( $notify_db_fh, $nlock );
}
else {
Cpanel::Debug::log_warn("Could not open $db_file: $!");
return;
}
if ( $notifications{$status} && ( ( $notifications{$status} + $interval ) > time() ) ) {
return 1;
}
return 0;
}
{
no warnings 'once';
*update_notification_time_if_interval_reached = \&_checkstatusinterval;
}
sub _checkstatusinterval {
my %AGS = @_;
my $app = $AGS{'app'};
my $status = $AGS{'status'};
my $interval = $AGS{'interval'};
return 1 if $interval <= 1; # Special Case (ignore interval check);
$app =~ s{/}{_}g; # Its possible to have slashes in the app name
$status =~ s{:}{_}g; # Its possible to have colons in the status
Cpanel::Validate::FilesystemNodeName::validate_or_die($app);
my $notify = 0;
if ( !-e $NOTIFY_INTERVAL_STORAGE_DIR ) {
Cpanel::LoadModule::load_perl_module('Cpanel::SafeDir::MK');
Cpanel::SafeDir::MK::safemkdir( $NOTIFY_INTERVAL_STORAGE_DIR, '0700' );
if ( !-d $NOTIFY_INTERVAL_STORAGE_DIR ) {
Cpanel::Debug::log_warn("Failed to setup notifications directory: $NOTIFY_INTERVAL_STORAGE_DIR: $!");
return;
}
}
my %notifications;
my $notify_db_fh;
my $db_file = "$NOTIFY_INTERVAL_STORAGE_DIR/$app";
if ( my $nlock = Cpanel::SafeFile::safesysopen( $notify_db_fh, $db_file, Cpanel::Fcntl::or_flags(qw( O_RDWR O_CREAT )), 0600 ) ) {
local $/;
%notifications = map { ( split( /:/, $_, 2 ) )[ 0, 1 ] } split( m{\n}, readline($notify_db_fh) );
if ( !exists $notifications{$status} || ( int( $notifications{$status} ) + int($interval) ) < time() ) {
$notifications{$status} = time;
$notify = 1;
}
seek( $notify_db_fh, 0, 0 );
print {$notify_db_fh} join( "\n", map { $_ . ':' . $notifications{$_} } sort keys %notifications );
truncate( $notify_db_fh, tell($notify_db_fh) );
Cpanel::SafeFile::safeclose( $notify_db_fh, $nlock );
}
else {
Cpanel::Debug::log_warn("Could not open $db_file: $!");
return;
}
return $notify;
}
1;
} # --- END Cpanel/Notify.pm
{ # --- BEGIN Cpanel/Server/Utils.pm
package Cpanel::Server::Utils;
use strict;
sub is_subprocess_of_cpsrvd {
return 0 if $INC{'cpanel/cpsrvd.pm'}; # If we ARE cpsrvd we do not want this behavior
return $ENV{'CPANEL'} ? 1 : 0;
}
1;
} # --- END Cpanel/Server/Utils.pm
{ # --- BEGIN Cpanel/Logger.pm
package Cpanel::Logger;
use strict;
# use Cpanel::Time::Local (); # perlpkg line 211
my $is_sandbox;
my $is_smoker;
our $VERSION = 1.3;
use constant TRACE_TOUCH_FILE => '/var/cpanel/log_stack_traces';
our $ENABLE_BACKTRACE;
our $DISABLE_OUTPUT; # used by cpanminus
our $ALWAYS_OUTPUT_TO_STDERR;
our $STD_LOG_FILE = '/usr/local/cpanel/logs/error_log';
our $PANIC_LOG_FILE = '/usr/local/cpanel/logs/panic_log';
my ( $cached_progname, $cached_prog_pid, %singleton_stash );
sub new {
my ( $class, $hr_args ) = @_;
if ( $hr_args->{'open_now'} && $hr_args->{'use_no_files'} ) {
die "“open_now” and “use_no_files” mutually exclude!";
}
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);
}
my $no_load_from_cache = $hr_args->{'no_load_from_cache'} ? 1 : 0;
if ( exists $singleton_stash{$class}{$args_sig} and !$no_load_from_cache ) {
$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};
}
}
}
my $self = $singleton_stash{$class}{$args_sig};
if ( !$self->{'cloned'} ) {
if ( $self->{'open_now'} && !$self->{'use_no_files'} ) {
$self->_open_logfile();
}
}
$self->_set_backtrace( $ENABLE_BACKTRACE // $self->{'backtrace'} // _get_backtrace_touchfile() );
return $self;
}
sub __Logger_pushback {
if ( @_ && index( ref( $_[0] ), __PACKAGE__ ) == 0 ) {
return @_;
}
return ( __PACKAGE__->new(), @_ );
}
sub invalid {
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'invalid',
'output' => 0,
'service' => $self->find_progname(),
'backtrace' => $self->get_backtrace(),
'die' => 0,
);
if ( is_sandbox() ) {
if ( !-e '/var/cpanel/DEBUG' ) {
$self->notify( 'invalid', \%log );
}
$log{'output'} = _stdin_is_tty() ? 2 : 1;
}
return $self->logger( \%log );
} # end of invalid
sub is_sandbox {
return 0 if $INC{'B/C.pm'}; # avoid cache during compile
return $is_sandbox if defined $is_sandbox;
return ( $is_sandbox = -e '/var/cpanel/dev_sandbox' ? 1 : 0 );
}
sub is_smoker {
return 0 if $INC{'B/C.pm'}; # avoid cache during compile
return $is_smoker if defined $is_smoker;
return ( $is_smoker = -e '/var/cpanel/smoker' ? 1 : 0 );
}
sub deprecated { ## no critic qw(Subroutines::RequireArgUnpacking)
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'deprecated',
'output' => 0,
'service' => $self->find_progname(),
'backtrace' => $self->get_backtrace(),
'die' => 0,
);
unless ( is_sandbox() ) {
$self->logger( \%log );
return;
}
$self->notify( 'deprecated', \%log );
$log{'output'} = _stdin_is_tty() ? 2 : 1;
$log{'die'} = 1;
return $self->logger( \%log );
}
sub debug {
my ( $self, $message, $conf_hr ) = @_; # not appropriate for debug() : __Logger_pushback(@_);
$self = $self->new() if !ref $self;
$conf_hr ||= {
'force' => 0,
'backtrace' => 0,
'output' => 1, # Logger's debug level should output to STDOUT
};
return unless $conf_hr->{'force'} || ( defined $Cpanel::Debug::level && $Cpanel::Debug::level ); ## PPI NO PARSE - avoid recursive use statements
if ( !defined $message ) {
my @caller = caller();
$message = "debug() at $caller[1] line $caller[2].";
}
my %log = (
'message' => $message,
'level' => 'debug',
'output' => $conf_hr->{'output'},
'backtrace' => $conf_hr->{'backtrace'},
);
if ( ref $log{'message'} ) {
my $outmsg = $log{'message'};
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; require Cpanel::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->logger( \%log );
return \%log;
}
sub info {
my ( $self, @list ) = __Logger_pushback(@_);
return $self->logger(
{
'message' => $list[0],
'level' => 'info',
'output' => $self->{'open_now'} ? 0 : 1, # FB#59177: info level should output to STDOUT
'backtrace' => 0
}
);
} # end of info
sub warn {
my ( $self, @list ) = __Logger_pushback(@_);
return $self->logger(
{
'message' => $list[0],
'level' => 'warn',
'output' => _stdin_is_tty() ? 2 : 0,
'backtrace' => $self->get_backtrace()
}
);
} # end of warn
sub error {
my ( $self, @list ) = __Logger_pushback(@_);
return $self->logger(
{
'message' => $list[0],
'level' => 'error',
'output' => -t STDIN ? 2 : 0,
'backtrace' => $self->get_backtrace()
}
);
} # end of error
sub die {
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'die',
'output' => _stdin_is_tty() ? 2 : 0,
'backtrace' => $self->get_backtrace()
);
return $self->logger( \%log );
} # end of die
sub panic {
my ( $self, @list ) = __Logger_pushback(@_);
my %log = (
'message' => $list[0],
'level' => 'panic',
'output' => 2,
'backtrace' => $self->get_backtrace()
);
return $self->logger( \%log );
} # end of panic
sub raw {
return $_[0]->logger(
{
'message' => $_[1],
'level' => 'raw',
'output' => 0,
'backtrace' => 0
}
);
}
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' => $ENABLE_BACKTRACE // _get_backtrace_touchfile() } );
} # end of cplog (deprecated)
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->{'output'} ||= 0;
$hr->{'output'} = 0 if $DISABLE_OUTPUT;
if ( !exists $hr->{'backtrace'} ) {
$hr->{'backtrace'} = $self->get_backtrace();
}
$hr->{'use_no_files'} ||= 0;
$hr->{'use_fullmsg'} ||= 0;
return $hr;
}
sub _write {
return print { $_[0] } $_[1];
}
sub get_backtrace {
my ($self) = __Logger_pushback(@_);
return $ENABLE_BACKTRACE // $self->{'backtrace'};
}
sub _set_backtrace {
my ( $self, @args ) = __Logger_pushback(@_);
$self->{'backtrace'} = $args[0] ? 1 : 0;
return;
}
sub _get_backtrace_touchfile {
return -e TRACE_TOUCH_FILE ? 1 : 0;
}
sub get_fh {
my ($self) = @_;
return $self->{'log_fh'};
}
sub set_fh {
my ( $self, $fh ) = @_;
$self->{'log_fh'} = $fh;
return 1;
}
sub logger { ## no critic(RequireArgUnpacking)
my ( $self, @list ) = __Logger_pushback(@_);
my $hr = $self->_get_configuration_for_logger( $list[0] );
my ( $msg, $time, $status );
$status = 1;
my ($msg_maybe_bt) = $hr->{'backtrace'} ? $self->backtrace( $hr->{'message'} ) : $hr->{'message'} . "\n";
if ( $hr->{'level'} eq 'raw' ) {
$msg = $hr->{'message'};
}
else {
$time ||= Cpanel::Time::Local::localtime2timestamp();
$hr->{'service'} ||= $self->find_progname(); # only compute the service name if we HAVE to do so as it can be expensive
if ( $self->{'log_pid'} ) {
$msg = "[$time] $hr->{'level'} [$hr->{'service'}] [$$] $msg_maybe_bt";
}
else {
$msg = "[$time] $hr->{'level'} [$hr->{'service'}] $msg_maybe_bt";
}
}
unless ( $hr->{'use_no_files'} ) {
local $self->{'log_fh'} = \*STDERR if $ALWAYS_OUTPUT_TO_STDERR;
$self->_open_logfile() if !$self->{'log_fh'} || ( !eval { fileno( $self->{'log_fh'} ) } && !UNIVERSAL::isa( $self->{'log_fh'}, 'IO::Scalar' ) );
_write( $self->{'log_fh'}, $msg ) or $status = 0;
if ( $hr->{'level'} eq 'panic' || $hr->{'level'} eq 'invalid' || $hr->{'level'} eq 'deprecated' ) {
my $panic_fh;
require Cpanel::FileUtils::Open;
if ( Cpanel::FileUtils::Open::sysopen_with_real_perms( $panic_fh, $PANIC_LOG_FILE, 'O_WRONLY|O_APPEND|O_CREAT', 0600 ) ) {
$time ||= Cpanel::Time::Local::localtime2timestamp();
$hr->{'service'} ||= $self->find_progname(); # only compute the service name if we HAVE to do so as it can be expensive
_write( $panic_fh, "$time $hr->{level} [$hr->{'service'}] $msg_maybe_bt" );
close $panic_fh;
}
}
}
if ( $hr->{'output'} ) {
$hr->{'service'} ||= $self->find_progname(); # only compute the service name if we HAVE to do so as it can be expensive
my $out = "$hr->{level} [$hr->{'service'}] $hr->{'message'}\n";
if ( $self->{'timestamp_prefix'} ) {
$out = "[$time] $out";
}
$out = $msg if $hr->{'use_fullmsg'};
$status &&= $self->_write_message( $hr, $out );
}
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
}
return $status;
} # end of logger
sub _write_message {
my ( $self, $hr, $out ) = @_;
my $status = 1;
if ( $hr->{'output'} == 3 ) {
_write( \*STDOUT, $out ) or $status = 0;
_write( \*STDERR, $out ) or $status = 0;
}
elsif ( $hr->{'output'} == 1 && ( $self->{'use_stdout'} || _stdout_is_tty() ) ) {
_write( \*STDOUT, $out ) or $status = 0;
}
elsif ( $hr->{'output'} == 2 ) {
_write( \*STDERR, $out ) or $status = 0;
}
return $status;
}
sub find_progname {
if ( $cached_progname && $cached_prog_pid == $$ ) {
return $cached_progname;
}
my $s = $0;
if ( !length $s ) { # Someone _could_ set $0 = '';
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@ if $s =~ tr{/}{};
$s =~ s@\..+$@@ if $s =~ tr{\.}{};
$s =~ s@ .*$@@ if $s =~ tr{ }{};
$cached_progname = $s;
$cached_prog_pid = $$;
return $s;
}
sub backtrace { ## no critic qw(Subroutines::RequireArgUnpacking)
my ( $self, @list ) = __Logger_pushback(@_);
if ( ref $list[0] ) {
return $list[0] if scalar @list == 1;
return (@list);
}
require Cpanel::Carp;
local $_; # Protect surrounding program - just in case...
local $Carp::Internal{ (__PACKAGE__) } = 1;
local $Carp::Internal{'Cpanel::Debug'} = 1;
return Cpanel::Carp::safe_longmess(@list);
}
sub redirect_stderr_to_error_log {
return open( STDERR, '>>', $STD_LOG_FILE );
}
sub notify {
my ( $self, $call, $log_ref ) = @_;
my $time = Cpanel::Time::Local::localtime2timestamp();
my ($bt) = $self->backtrace( $log_ref->{'message'} );
$log_ref->{'service'} //= '';
my $logfile = qq{$time [$log_ref->{'service'}] } . ( $bt // '' );
if ( eval { require Cpanel::LoadModule; Cpanel::LoadModule::load_perl_module('Cpanel::iContact::Class::Logger::Notify'); 1; } ) {
eval {
require Cpanel::Notify;
Cpanel::Notify::notification_class(
'class' => 'Logger::Notify',
'application' => 'Logger::Notify',
'constructor_args' => [
'origin' => $log_ref->{'service'},
'logger_call' => $call,
'attach_files' => [ { name => 'cpanel-logger-log.txt', content => \$logfile } ],
'subject' => $log_ref->{'subject'},
]
);
};
}
elsif ( eval { require Cpanel::LoadModule; Cpanel::LoadModule::load_perl_module('Cpanel::iContact'); 1; } ) {
Cpanel::iContact::icontact(
'application' => $log_ref->{'service'},
'subject' => $log_ref->{'subject'} ? $log_ref->{'subject'} : qq{Cpanel::Logger::$call called in $log_ref->{'service'}},
'message' => $logfile,
);
}
else {
CORE::warn( $log_ref->{'subject'} ? $log_ref->{'subject'} : qq{Cpanel::Logger::$call called in $log_ref->{'service'}} . ": $logfile" );
}
return;
}
*fatal = *die;
*out = *info;
*success = *info;
*throw = *die;
*warning = *warn;
sub _is_subprocess_of_cpsrvd {
require Cpanel::Server::Utils;
goto \&Cpanel::Server::Utils::is_subprocess_of_cpsrvd;
}
sub _open_logfile {
my ($self) = @_;
my $usingstderr = 0;
my $log_fh;
$self->{'alternate_logfile'} ||= $STD_LOG_FILE;
if ( $STD_LOG_FILE eq $self->{'alternate_logfile'} && _is_subprocess_of_cpsrvd() ) {
$log_fh = \*STDERR;
$usingstderr = 1;
}
else {
require Cpanel::FileUtils::Open;
if ( !Cpanel::FileUtils::Open::sysopen_with_real_perms( $log_fh, $self->{'alternate_logfile'}, 'O_WRONLY|O_APPEND|O_CREAT', 0600 ) ) {
( $usingstderr, $log_fh ) = ( 1, \*STDERR );
}
select( ( select($log_fh), $| = 1 )[0] ); ## no critic qw(Variables::RequireLocalizedPunctuationVars InputOutput::ProhibitOneArgSelect) -- Cpanel::FHUtils::Autoflush would be expensive to load every time
}
$self->{'log_fh'} = $log_fh;
$self->{'usingstderr'} = $usingstderr;
return 1;
}
sub _stdin_is_tty {
local $@;
return eval { -t STDIN };
}
sub _stdout_is_tty {
local $@;
return eval { -t STDOUT };
}
sub clear_singleton_stash {
%singleton_stash = ();
return;
}
1;
} # --- END Cpanel/Logger.pm
{ # --- BEGIN Cpanel/Sys/Uname.pm
package Cpanel::Sys::Uname;
use strict;
our $SYS_UNAME = 63;
our $UNAME_ELEMENTS = 6;
our $_UTSNAME_LENGTH = 65;
my $UNAME_PACK_TEMPLATE = ( 'c' . $_UTSNAME_LENGTH ) x $UNAME_ELEMENTS;
my $UNAME_UNPACK_TEMPLATE = ( 'Z' . $_UTSNAME_LENGTH ) x $UNAME_ELEMENTS;
my @uname_cache;
sub get_uname_cached {
return ( @uname_cache ? @uname_cache : ( @uname_cache = syscall_uname() ) );
}
sub clearcache {
@uname_cache = ();
return;
}
sub syscall_uname {
my $uname;
if ( syscall( $SYS_UNAME, $uname = pack( $UNAME_PACK_TEMPLATE, () ) ) == 0 ) {
return unpack( $UNAME_UNPACK_TEMPLATE, $uname );
}
else {
die "The uname() system call failed because of an error: $!";
}
return;
}
1;
} # --- END Cpanel/Sys/Uname.pm
{ # --- BEGIN Cpanel/Sys/Hostname/Fallback.pm
package Cpanel::Sys::Hostname::Fallback;
use strict;
use warnings;
no warnings 'once';
use Socket ();
# use Cpanel::Sys::Uname (); # perlpkg line 211
sub get_canonical_hostname {
my @uname = Cpanel::Sys::Uname::get_uname_cached();
my ( $err, @results ) = Socket::getaddrinfo( $uname[1], 0, { flags => Socket::AI_CANONNAME() } );
if ( @results && $results[0]->{'canonname'} ) {
return $results[0]->{'canonname'};
}
return undef;
}
1;
} # --- END Cpanel/Sys/Hostname/Fallback.pm
{ # --- BEGIN Cpanel/LoadFile/ReadFast.pm
package Cpanel::LoadFile::ReadFast;
use strict;
use warnings;
no warnings 'once';
use constant READ_CHUNK => 1 << 18; # 262144
use constant _EINTR => 4;
sub read_fast {
$_[1] //= q<>;
return ( @_ > 3 ? sysread( $_[0], $_[1], $_[2], $_[3] ) : sysread( $_[0], $_[1], $_[2] ) ) // do {
goto \&read_fast if $! == _EINTR;
die "Failed to read data: $!";
};
}
my $_ret;
sub read_all_fast {
$_[1] //= q<>;
$_ret = 1;
while ($_ret) {
$_ret = sysread( $_[0], $_[1], READ_CHUNK, length $_[1] ) // do {
redo if $! == _EINTR;
die "Failed to read data: $!";
}
}
return;
}
1;
} # --- END Cpanel/LoadFile/ReadFast.pm
{ # --- BEGIN Cpanel/LoadFile.pm
package Cpanel::LoadFile;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
sub loadfileasarrayref {
my $fileref = _load_file( shift, { 'array_ref' => 1 } );
return ref $fileref eq 'ARRAY' ? $fileref : undef;
}
sub loadbinfile {
my $fileref = _load_file( shift, { 'binmode' => 1 } );
return ref $fileref eq 'SCALAR' ? $$fileref : undef;
}
sub slurpfile {
my $fh = shift;
my $fileref = _load_file(shift);
if ( ref $fileref eq 'SCALAR' ) {
print {$fh} $$fileref;
}
return;
}
sub loadfile {
my $fileref = _load_file(@_);
return ref $fileref eq 'SCALAR' ? $$fileref : undef;
}
sub loadfile_r {
my ( $file, $arg_ref ) = @_;
if ( open my $lf_fh, '<:stdio', $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 {
$data = '';
local $@;
eval { Cpanel::LoadFile::ReadFast::read_all_fast( $lf_fh, $data ); };
return $@ ? undef : \$data;
}
}
return;
}
*_load_file = *loadfile_r;
sub _open {
return _open_if_exists( $_[0] ) || die Cpanel::Exception::create( 'IO::FileNotFound', [ path => $_[0], error => _ENOENT() ] );
}
sub _open_if_exists {
local $!;
open my $fh, '<:stdio', $_[0] or do {
if ( $! == _ENOENT() ) {
return undef;
}
die Cpanel::Exception::create( 'IO::FileOpenError', [ path => $_[0], error => $!, mode => '<' ] );
};
return $fh;
}
sub load_if_exists {
my $ref = _load_r( \&_open_if_exists, @_ );
return $ref ? $$ref : undef;
}
sub load_r_if_exists {
return _load_r( \&_open_if_exists, @_ );
}
sub load {
return ${ _load_r( \&_open, @_ ) };
}
sub load_r {
return _load_r( \&_open, @_ );
}
sub _load_r {
my ( $open_coderef, $path, $offset, $length ) = @_;
my $fh = $open_coderef->($path) or return undef;
local $!;
if ($offset) {
sysseek( $fh, $offset, $Cpanel::Fcntl::Constants::SEEK_SET );
if ($!) {
die Cpanel::Exception::create(
'IO::FileSeekError',
[
path => $path,
position => $offset,
whence => $Cpanel::Fcntl::Constants::SEEK_SET,
error => $!,
]
);
}
}
my $data = q<>;
if ( !defined $length ) {
my $bytes_read = Cpanel::LoadFile::ReadFast::read_fast( $fh, $data, Cpanel::LoadFile::ReadFast::READ_CHUNK );
if ( $bytes_read == Cpanel::LoadFile::ReadFast::READ_CHUNK ) {
my $file_size = -f $fh && -s _;
if ($file_size) {
Cpanel::LoadFile::ReadFast::read_fast( $fh, $data, $file_size, length $data ) // die _read_err($path);
}
}
Cpanel::LoadFile::ReadFast::read_all_fast( $fh, $data );
}
else {
my $togo = $length;
my $bytes_read;
while ( $bytes_read = Cpanel::LoadFile::ReadFast::read_fast( $fh, $data, $togo, length $data ) && length $data < $length ) {
$togo -= $bytes_read;
}
}
if ($!) {
die Cpanel::Exception::create( 'IO::FileReadError', [ path => $path, error => $! ] );
}
close $fh or warn "The system failed to close the file “$path” because of an error: $!";
return \$data;
}
sub _ENOENT { return 2; }
1;
} # --- END Cpanel/LoadFile.pm
{ # --- BEGIN Cpanel/Sys/Hostname.pm
package Cpanel::Sys::Hostname;
use strict;
use warnings;
no warnings 'once';
our $VERSION = 2.0;
# use Cpanel::Sys::Uname (); # perlpkg line 211
our $cachedhostname = '';
sub gethostname {
my $nocache = shift || 0;
if ( !$nocache && length $cachedhostname ) { return $cachedhostname }
my $hostname = _gethostname($nocache);
if ( length $hostname ) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
$cachedhostname = $hostname;
}
return $hostname;
}
sub _gethostname {
my $nocache = shift || 0;
my $hostname;
Cpanel::Sys::Uname::clearcache() if $nocache;
my @uname = Cpanel::Sys::Uname::get_uname_cached();
if ( $uname[1] && index( $uname[1], '.' ) > -1 ) {
$hostname = $uname[1];
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
return $hostname;
}
eval {
require Cpanel::Sys::Hostname::Fallback;
$hostname = Cpanel::Sys::Hostname::Fallback::get_canonical_hostname();
};
if ($hostname) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
return $hostname;
}
require Cpanel::LoadFile;
chomp( $hostname = Cpanel::LoadFile::loadfile( '/proc/sys/kernel/hostname', { 'skip_exists_check' => 1 } ) );
if ($hostname) {
$hostname =~ tr{A-Z}{a-z}; # hostnames must be lowercase (see Cpanel::Sys::Hostname::Modify::make_hostname_lowercase_fqdn)
$hostname =~ tr{\r\n}{}d; # chomp is not enough (not sure if this is required, however we cannot test all kernels so its safer to leave it in)
return $hostname;
}
require Cpanel::Debug;
Cpanel::Debug::log_warn('Unable to determine correct hostname');
return;
}
sub shorthostname {
my $hostname = gethostname();
return $hostname if index( $hostname, '.' ) == -1; # Hostname is not a FQDN (this should never happen)
return substr( $hostname, 0, index( $hostname, '.' ) );
}
1;
} # --- END Cpanel/Sys/Hostname.pm
{ # --- BEGIN Cpanel/Hostname.pm
package Cpanel::Hostname;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Sys::Hostname (); # perlpkg line 211
our $VERSION = 2.0;
{
no warnings 'once';
*gethostname = *Cpanel::Sys::Hostname::gethostname;
*shorthostname = *Cpanel::Sys::Hostname::shorthostname;
}
1;
} # --- END Cpanel/Hostname.pm
{ # --- BEGIN Cpanel/NAT/Object.pm
package Cpanel::NAT::Object;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::Validate::IP::v4 (); # perlpkg line 211
our $NAT_FILE = '/var/cpanel/cpnat';
sub new {
my ( $class, $file ) = @_;
my $self = {
'cpnat_file' => $file || $NAT_FILE,
'cpnat_data' => {},
'file_read' => 0,
'only_local_ip' => [],
'dups' => {},
};
bless $self, $class;
$self->load_file();
return $self;
}
sub load_file {
my ($self) = @_;
$self->{'file_read'} = 0;
$self->{'cpnat_data'} = {};
if ( !-e $self->{'cpnat_file'} || !-r _ || -z _ ) {
return;
}
my $nat_data;
{
local $/;
open my $fh, '<', $self->{'cpnat_file'} or die "Failed to open “$self->{'cpnat_file'}”: $!";
$nat_data = <$fh>;
close $fh;
}
$self->{'nat_data'} = $nat_data;
$self->{'cpnat_data'} = $self->_parse_nat_file($nat_data);
$self->{'file_read'} = 1;
return 1 if %{ $self->{'cpnat_data'} };
return;
}
sub enabled {
my ($self) = @_;
return $self->{'file_read'} ? 1 : 0;
}
sub ordered_list {
my ($self) = @_;
return ( $self->{'cpnat_ordered'} ||= $self->_create_ordered_list( $self->{'nat_data'} ) );
}
sub get_public_ip {
return $_[1] if !$_[1] || !$_[0]->{'file_read'} || !$_[0]->{'cpnat_data'}->{ $_[1] };
return $_[0]->_get_public_ip( $_[1] );
}
sub get_all_public_ips {
my ($self) = @_;
return [ sort values %{ $self->{cpnat_data} } ];
}
sub get_public_ip_raw {
my ( $self, $local_ip ) = @_;
return 'FILE NOT READ' if !$self->{'file_read'};
return 'INVALID LOCAL IP' if !$self->{'cpnat_data'}->{$local_ip} && !$self->_find_ip($local_ip) && !exists $self->{'dups'}->{$local_ip};
return $self->_get_public_ip($local_ip) || $self->{'dups'}->{$local_ip} || '';
}
sub _find_ip {
my ( $self, $ip ) = @_;
my $found = grep { $_ eq $ip } @{ $self->{'only_local_ip'} };
return $ip if $found;
return;
}
sub _get_public_ip {
my ( $self, $local_ip ) = @_;
my $public_ip = $self->{'cpnat_data'}->{$local_ip};
return $public_ip;
}
sub get_local_ip {
my ( $self, $public_ip ) = @_;
return $public_ip unless $public_ip;
return $public_ip if !$self->{'file_read'};
$self->{'_public_to_local'} ||= { reverse %{ $self->{'cpnat_data'} } };
return $self->{'_public_to_local'}{$public_ip} || $public_ip;
}
sub _parse_nat_file {
my ( $self, $nat_data ) = @_;
return if !$nat_data;
my $cpnat_hash = {};
my @file = split /\n/, $nat_data;
my $only_local_ip = $self->{'only_local_ip'};
foreach my $line (@file) {
my ( $local, $public ) = split /\s+/, $line;
if ( !$public ) {
push @$only_local_ip, $local;
next;
}
if ( !Cpanel::Validate::IP::v4::is_valid_ipv4($local)
&& !Cpanel::Validate::IP::v4::is_valid_ipv4($public) ) {
Cpanel::Debug::log_warn( 'Invalid line in cpnat file: ' . $line );
next;
}
if ( !grep { $public && $public eq $_ } values %{$cpnat_hash} ) {
$cpnat_hash->{$local} = $public;
}
else {
$self->{'dups'}->{$local} = $public;
}
}
return $cpnat_hash;
}
sub _create_ordered_list {
my ( $self, $nat_data ) = @_;
return if !$nat_data;
my $cpnat_array = [];
my $group_hash = {};
my $order = [];
my @file = split /\n/, $nat_data;
foreach my $line (@file) {
my ( $local, $public ) = split /\s+/, $line;
my $key = $public || $local;
$public ||= '';
push @$order, $key if !$group_hash->{$key};
push @{ $group_hash->{$key} }, [ $local, $public ];
}
foreach my $key (@$order) {
if ( scalar @{ $group_hash->{$key} } == 1 ) {
push @$cpnat_array, pop @{ $group_hash->{$key} };
}
else {
push @$cpnat_array, $group_hash->{$key};
}
}
return $cpnat_array;
}
1;
} # --- END Cpanel/NAT/Object.pm
{ # --- BEGIN Cpanel/NAT.pm
package Cpanel::NAT;
use strict;
# use Cpanel::NAT::Object (); # perlpkg line 211
my $nat;
sub set_cpnat {
$nat = shift;
return;
}
sub cpnat {
return $nat ||= Cpanel::NAT::Object->new();
}
sub reload {
return cpnat()->load_file();
}
sub get_public_ip {
return ( $nat ||= cpnat() )->get_public_ip( $_[0] );
}
sub get_local_ip {
return ( $nat ||= cpnat() )->get_local_ip( $_[0] );
}
sub get_public_ip_raw {
return ( $nat ||= cpnat() )->get_public_ip_raw( $_[0] );
}
sub ordered_list {
return cpnat()->ordered_list();
}
sub get_all_public_ips {
return cpnat()->get_all_public_ips();
}
sub is_nat {
return cpnat()->enabled();
}
1;
} # --- END Cpanel/NAT.pm
{ # --- BEGIN Cpanel/Struct/Common/Time.pm
package Cpanel::Struct::Common::Time;
use strict;
use warnings;
no warnings 'once';
use constant PACK_TEMPLATE => 'L!L!';
my %CLASS_PRECISION;
sub float_to_binary {
return pack(
PACK_TEMPLATE(),
int( $_[1] ),
int( 0.5 + ( $_[0]->_PRECISION() * $_[1] ) - ( $_[0]->_PRECISION() * int( $_[1] ) ) ),
);
}
sub binary_to_float {
return $_[0]->_binary_to_float( PACK_TEMPLATE(), $_[1] )->[0];
}
sub binaries_to_floats_at {
return $_[0]->_binary_to_float(
"\@$_[3] " . ( PACK_TEMPLATE() x $_[2] ),
$_[1],
);
}
my ( $i, $precision, @sec_psec_pairs );
sub _binary_to_float { ## no critic qw(RequireArgUnpacking)
@sec_psec_pairs = unpack( $_[1], $_[2] );
$i = 0;
my @floats;
$precision = $CLASS_PRECISION{ $_[0] } ||= $_[0]->_PRECISION();
while ( $i < @sec_psec_pairs ) {
push @floats, 0 + ( q<> . ( $sec_psec_pairs[$i] + ( $sec_psec_pairs[ $i + 1 ] / $precision ) ) );
$i += 2;
}
return \@floats;
}
1;
} # --- END Cpanel/Struct/Common/Time.pm
{ # --- BEGIN Cpanel/Struct/timespec.pm
package Cpanel::Struct::timespec;
use strict;
use warnings;
no warnings 'once';
# use parent Cpanel::Struct::Common::Time (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Struct::Common::Time); }
use constant {
_PRECISION => 1_000_000_000, # nanoseconds
};
1;
} # --- END Cpanel/Struct/timespec.pm
{ # --- BEGIN Cpanel/NanoStat.pm
package Cpanel::NanoStat;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Struct::timespec (); # perlpkg line 211
use constant {
_NR_stat => 4,
_NR_fstat => 5,
_NR_lstat => 6,
};
use constant _PACK_TEMPLATE => q<
Q # st_dev
Q # st_ino
@24 L # st_mode
@16 Q # st_nlink
@28
L # st_uid
L # st_gid
x![Q]
Q # st_rdev
Q # st_size
Q # st_blksize
Q # st_blocks
>;
my $pre_times_pack_len = length pack _PACK_TEMPLATE();
my $buf = ( "\0" x 144 );
sub stat {
return _syscall( _NR_stat(), $_[0] );
}
sub lstat {
return _syscall( _NR_lstat(), $_[0] );
}
sub fstat {
return _syscall( _NR_fstat(), 0 + ( ref( $_[0] ) ? fileno( $_[0] ) : $_[0] ) );
}
sub _syscall { ## no critic qw(RequireArgUnpacking)
my $arg_dupe = $_[1];
return undef if -1 == syscall( $_[0], $arg_dupe, $buf );
my @vals = unpack _PACK_TEMPLATE(), $buf;
splice(
@vals, 8, 0,
@{ Cpanel::Struct::timespec->binaries_to_floats_at( $buf, 3, $pre_times_pack_len ) },
);
return @vals;
}
1;
} # --- END Cpanel/NanoStat.pm
{ # --- BEGIN Cpanel/NanoUtime.pm
package Cpanel::NanoUtime;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Struct::timespec (); # perlpkg line 211
use constant {
_NR_utimensat => 280,
_AT_FDCWD => -100,
_AT_SYMLINK_NOFOLLOW => 0x100,
};
sub utime {
return _syscall( 0 + _AT_FDCWD(), $_[2], @_[ 0, 1 ], 0 );
}
sub futime {
return _syscall(
0 + ( ref( $_[2] ) ? fileno( $_[2] ) : $_[2] ),
undef,
@_[ 0, 1 ],
0,
);
}
sub lutime {
return _syscall( 0 + _AT_FDCWD(), $_[2], @_[ 0, 1 ], 0 + _AT_SYMLINK_NOFOLLOW() );
}
my ( $path, $buf ) = @_;
sub _syscall {
if ( defined $_[-3] ) {
if ( defined $_[-2] ) {
$buf = Cpanel::Struct::timespec->float_to_binary( $_[-3] ) . Cpanel::Struct::timespec->float_to_binary( $_[-2] );
}
else {
die "atime is “$_[-3]”, but mtime is undef!";
}
}
elsif ( defined $_[-2] ) {
die "atime is undef, but mtime is “$_[-2]”!";
}
else {
$buf = undef;
}
$path = $_[1];
return undef if -1 == syscall( 0 + _NR_utimensat(), $_[0], $path // undef, $buf // undef, $_[-1] );
return 1;
}
1;
} # --- END Cpanel/NanoUtime.pm
{ # --- BEGIN Cpanel/HiRes.pm
package Cpanel::HiRes;
use strict;
use warnings;
no warnings 'once';
my %_routes = (
'fstat' => [ 'NanoStat', 'fstat', 'stat', 1 ],
'lstat' => [ 'NanoStat', 'lstat', 'lstat', 1 ],
'stat' => [ 'NanoStat', 'stat', 'stat', 1 ],
'time' => [ 'TimeHiRes', 'time', 'time' ],
'utime' => [ 'NanoUtime', 'utime', 'utime' ],
'futime' => [ 'NanoUtime', 'futime', 'utime' ],
'lutime' => [ 'NanoUtime', 'lutime', undef ],
);
my $preloaded;
sub import {
my ( $class, %opts ) = @_;
if ( my $preload = $opts{'preload'} ) {
if ( $preload eq 'xs' ) {
require Time::HiRes;
}
elsif ( $preload eq 'perl' ) {
if ( !$preloaded ) {
require Cpanel::TimeHiRes; # PPI USE OK - preload
require Cpanel::NanoStat; # PPI USE OK - preload
require Cpanel::NanoUtime; # PPI USE OK - preload
}
}
else {
die "Unknown “preload”: “$preload”";
}
$preloaded = $preload;
}
return;
}
our $AUTOLOAD;
sub AUTOLOAD { ## no critic qw(Subroutines::RequireArgUnpacking)
substr( $AUTOLOAD, 0, 1 + rindex( $AUTOLOAD, ':' ) ) = q<>;
if ( !$AUTOLOAD || !$_routes{$AUTOLOAD} ) {
die "Unknown function in Cpanel::HiRes::$_[0]";
}
my $function = $AUTOLOAD;
undef $AUTOLOAD;
my ( $pp_module, $pp_function, $xs_function, $xs_needs_closure ) = @{ $_routes{$function} };
no strict 'refs';
if ( $INC{'Time/HiRes.pm'} && $xs_function ) {
*$function = *{"Time::HiRes::$xs_function"};
return Time::HiRes->can($xs_function)->(@_);
}
else {
_require("Cpanel/${pp_module}.pm") if !$INC{"Cpanel/${pp_module}.pm"};
my $pp_cr = "Cpanel::${pp_module}"->can($pp_function);
if ($xs_function) {
*$function = sub {
if ( $INC{'Time/HiRes.pm'} ) {
*$function = *{"Time::HiRes::$xs_function"};
return Time::HiRes->can($xs_function)->(@_);
}
goto &$pp_cr;
};
}
else {
*$function = $pp_cr;
}
}
goto &$function;
}
sub _require {
local ( $!, $^E, $@ );
require $_[0];
return;
}
1;
} # --- END Cpanel/HiRes.pm
{ # --- BEGIN Cpanel/Path/Normalize.pm
package Cpanel::Path::Normalize;
use strict;
use warnings;
no warnings 'once';
sub normalize {
my $uncleanpath = shift || return;
my $is_abspath = ( 0 == index( $uncleanpath, '/' ) );
my @pathdirs = split( m[/], $uncleanpath );
my @cleanpathdirs;
my $leading_dot_dots = 0;
foreach my $dir (@pathdirs) {
next if !length $dir; #Remove extraneous "//" and leading "/"
next if $dir eq '.';
if ( $dir eq '..' ) {
if (@cleanpathdirs) {
pop(@cleanpathdirs);
}
else {
$leading_dot_dots++;
}
}
else {
push( @cleanpathdirs, $dir );
}
}
if ($is_abspath) {
return ( '/' . join( '/', @cleanpathdirs ) );
}
unshift @cleanpathdirs, ('..') x $leading_dot_dots;
return join( '/', @cleanpathdirs );
}
1;
} # --- END Cpanel/Path/Normalize.pm
{ # --- BEGIN Cpanel/JSON/Unicode.pm
package Cpanel::JSON::Unicode;
use strict;
use warnings;
no warnings 'once';
use constant {
_LEAD_SURROGATE_MIN => 0xd800,
_TAIL_SURROGATE_MIN => 0xdc00,
_SURROGATE_MASK => 0xfc00,
_BACKSLASH_ORD => 0x5c,
_DOUBLE_QUOTE_ORD => 0x22,
};
my $UNICODE_ESCAPE_REGEXP = qr/
(?<!\x5c)
(
(?:\x5c\x5c)*
\x5c u ([0-9a-fA-F]{4})
)
/x;
sub replace_unicode_escapes_with_utf8 {
my ($json_sr) = @_;
my $lead_surrogate;
my $ret = $$json_sr =~ s<$UNICODE_ESCAPE_REGEXP><
_replacement(\$lead_surrogate, $json_sr, $+[0], @{^CAPTURE})
>ge;
if ($lead_surrogate) {
die sprintf "Incomplete surrogate pair (0x%04x)", $lead_surrogate;
}
return $ret;
}
sub _replacement {
my ( $lead_surrogate_sr, $json_sr, $match_end, @captures ) = @_;
my $num = hex $captures[1];
if ( ( $num & _SURROGATE_MASK ) == _TAIL_SURROGATE_MIN ) {
if ($$lead_surrogate_sr) {
my $utf8 = _decode_surrogates( $$lead_surrogate_sr, $num );
$$lead_surrogate_sr = undef;
return $utf8;
}
die sprintf "Unpaired trailing surrogate (0x%04x)", $num;
}
elsif ( ( $num & _SURROGATE_MASK ) == _LEAD_SURROGATE_MIN ) {
my $next2 = substr( $$json_sr, $match_end, 2 );
if ( !$next2 || $next2 ne '\\u' ) {
die sprintf "Unpaired leading surrogate (0x%04x)", $num;
}
$$lead_surrogate_sr = $num;
return q<>;
}
elsif ( $num < 0x20 || $num == _BACKSLASH_ORD || $num == _DOUBLE_QUOTE_ORD ) {
return $captures[0];
}
my $utf8 = chr $num;
utf8::encode($utf8);
return $utf8;
}
sub _decode_surrogates {
my ( $lead, $tail ) = @_;
my $uni = 0x10000 + ( ( $lead - 0xd800 ) << 10 ) + ( $tail - 0xdc00 );
my $un = chr $uni;
utf8::encode($un);
return $un;
}
1;
} # --- END Cpanel/JSON/Unicode.pm
{ # --- BEGIN Cpanel/Encoder/ASCII.pm
package Cpanel::Encoder::ASCII;
use strict;
use warnings;
no warnings 'once';
sub to_hex {
my ($readable) = @_;
$readable =~ s<\\><\\\\>g;
$readable =~ s<([\0-\x{1f}\x{7f}-\x{ff}])><sprintf '\x{%02x}', ord $1>eg;
return $readable;
}
1;
} # --- END Cpanel/Encoder/ASCII.pm
{ # --- BEGIN Cpanel/UTF8/Strict.pm
package Cpanel::UTF8::Strict;
use strict;
use warnings;
no warnings 'once';
sub decode {
utf8::decode( $_[0] ) or do {
local ( $@, $! );
require Cpanel::Encoder::ASCII;
die sprintf "Invalid UTF-8 in string: “%s”", Cpanel::Encoder::ASCII::to_hex( $_[0] );
};
return $_[0];
}
1;
} # --- END Cpanel/UTF8/Strict.pm
{ # --- BEGIN Cpanel/JSON.pm
package Cpanel::JSON;
use strict;
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::FHUtils::Tiny (); # perlpkg line 211
# use Cpanel::JSON::Unicode (); # perlpkg line 211
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
use JSON::XS ();
# use Cpanel::UTF8::Strict (); # perlpkg line 211
our $NO_DECODE_UTF8 = 0;
our $DECODE_UTF8 = 1;
our $LOAD_STRICT = 0;
our $LOAD_RELAXED = 1;
our $MAX_LOAD_LENGTH_UNLIMITED = 0;
our $MAX_LOAD_LENGTH = 65535;
our $MAX_PRIV_LOAD_LENGTH = 4194304; # four megs
our $XS_ConvertBlessed_obj;
our $XS_RelaxedConvertBlessed_obj;
our $XS_NoSetUTF8RelaxedConvertBlessed_obj;
our $XS_NoSetUTF8ConvertBlessed_obj;
our $VERSION = '2.5';
my $copied_boolean = 0;
sub DumpFile {
my ( $file, $data ) = @_;
if ( Cpanel::FHUtils::Tiny::is_a($file) ) {
print {$file} Dump($data) || return 0;
}
else {
if ( open( my $fh, '>', $file ) ) {
print {$fh} Dump($data);
close($fh);
}
else {
return 0;
}
}
return 1;
}
sub copy_boolean {
if ( !$copied_boolean ) {
*Types::Serialiser::Boolean:: = *JSON::PP::Boolean::;
$copied_boolean = 1;
}
return;
}
sub _create_new_json_object {
copy_boolean() if !$copied_boolean;
return JSON::XS->new()->shrink(1)->allow_nonref(1)->convert_blessed(1);
}
sub true {
copy_boolean() if !$copied_boolean;
my $x = 1;
return bless \$x, 'Types::Serialiser::Boolean';
}
sub false {
copy_boolean() if !$copied_boolean;
my $x = 0;
return bless \$x, 'Types::Serialiser::Boolean';
}
sub pretty_dump {
return _create_new_json_object()->pretty(1)->encode( $_[0] );
}
my $XS_Canonical_obj;
sub canonical_dump {
return ( $XS_Canonical_obj ||= _create_new_json_object()->canonical(1) )->encode( $_[0] );
}
sub pretty_canonical_dump {
return _create_new_json_object()->canonical(1)->indent->space_before->space_after->encode( $_[0] );
}
sub Dump {
return ( $XS_ConvertBlessed_obj ||= _create_new_json_object() )->encode( $_[0] );
}
sub Load {
local $@;
_replace_unicode_escapes_if_needed( \$_[0] );
return eval { ( $XS_ConvertBlessed_obj ||= _create_new_json_object() )->decode( $_[0] ); } // ( ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef );
}
sub LoadRelaxed {
local $@;
_replace_unicode_escapes_if_needed( \$_[0] );
return eval { ( $XS_RelaxedConvertBlessed_obj ||= _create_new_json_object()->relaxed(1) )->decode( $_[0] ); } // ( ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef );
}
sub _throw_json_error {
my ( $exception, $path, $dataref ) = @_;
local $@;
require Cpanel::Exception;
die $exception if $@;
die 'Cpanel::Exception'->can('create')->( 'JSONParseError', { 'error' => $exception, 'path' => $path, 'dataref' => $dataref } );
}
sub LoadNoSetUTF8 {
local $@;
_replace_unicode_escapes_if_needed( \$_[0] );
return eval { ( $XS_NoSetUTF8ConvertBlessed_obj ||= _create_new_no_set_utf8_json_object() )->decode( $_[0] ); } // ( ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef );
}
sub LoadNoSetUTF8Relaxed {
local $@;
_replace_unicode_escapes_if_needed( \$_[0] );
return eval { ( $XS_NoSetUTF8RelaxedConvertBlessed_obj ||= _create_new_no_set_utf8_json_object()->relaxed(1) )->decode( $_[0] ); } // ( ( $@ && _throw_json_error( $@, $_[1], \$_[0] ) ) || undef );
}
sub _create_new_no_set_utf8_json_object {
my $obj = _create_new_json_object();
if ( $obj->can('no_set_utf8') ) {
$obj->no_set_utf8(1);
}
else {
warn "JSON::XS is missing the no_set_utf8 flag";
}
return $obj;
}
sub _replace_unicode_escapes_if_needed {
my $json_r = shift;
return unless defined $$json_r;
if ( -1 != index( $$json_r, '\\u' ) ) {
Cpanel::JSON::Unicode::replace_unicode_escapes_with_utf8($json_r);
}
return;
}
sub SafeLoadFile { # only allow a small bit of data to be loaded
return _LoadFile( $_[0], $MAX_LOAD_LENGTH, $_[2] || $NO_DECODE_UTF8, $_[1], $LOAD_STRICT );
}
sub LoadFile {
return _LoadFile( $_[0], $MAX_LOAD_LENGTH_UNLIMITED, $_[2] || $NO_DECODE_UTF8, $_[1], $LOAD_STRICT );
}
sub LoadFileRelaxed {
return _LoadFile( $_[0], $MAX_LOAD_LENGTH_UNLIMITED, $_[2] || $NO_DECODE_UTF8, $_[1], $LOAD_RELAXED );
}
sub LoadFileNoSetUTF8 {
return _LoadFile( $_[0], $_[1] || $MAX_LOAD_LENGTH_UNLIMITED, $DECODE_UTF8, $_[2], $LOAD_STRICT );
}
sub _LoadFile {
my ( $file, $max, $decode_utf8, $path, $relaxed ) = @_;
my $data;
if ( Cpanel::FHUtils::Tiny::is_a($file) ) {
if ($max) {
my $togo = $max;
$data = '';
my $bytes_read;
while ( $bytes_read = read( $file, $data, $togo, length $data ) && length $data < $max ) {
$togo -= $bytes_read;
}
}
else {
Cpanel::LoadFile::ReadFast::read_all_fast( $file, $data );
}
}
else {
local $!;
open( my $fh, '<:stdio', $file ) or do {
my $err = $!;
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("Cannot open “$file”: $err");
};
Cpanel::LoadFile::ReadFast::read_all_fast( $fh, $data );
if ( !length $data ) {
require Cpanel::Carp;
die Cpanel::Carp::safe_longmess("“$file” is empty.");
}
close $fh or warn "close($file) failed: $!";
}
if ( $decode_utf8 && $decode_utf8 == $DECODE_UTF8 ) {
Cpanel::UTF8::Strict::decode($data);
return $relaxed ? LoadNoSetUTF8Relaxed( $data, $path || $file ) : LoadNoSetUTF8( $data, $path || $file );
}
return $relaxed ? LoadRelaxed( $data, $path || $file ) : Load( $data, $path || $file );
}
sub SafeDump {
my $raw_json = ( $XS_ConvertBlessed_obj ||= _create_new_json_object() )->encode( $_[0] );
$raw_json =~ s{\/}{\\/}g if $raw_json =~ tr{/}{};
return $raw_json;
}
sub _fh_looks_like_json {
my ($fh) = @_;
my $bytes_read = 0;
my $buffer = q{};
local $!;
while ( $buffer !~ tr{ \t\r\n\f}{}c && !eof $fh ) {
$bytes_read += ( read( $fh, $buffer, 1, length $buffer ) // die "read() failed: $!" );
}
return (
_string_looks_like_json($buffer),
\$buffer,
);
}
sub _string_looks_like_json { ##no critic qw(RequireArgUnpacking)
return $_[0] =~ m/\A\s*[\[\{"0-9]/ ? 1 : 0;
}
sub looks_like_json { ##no critic qw(RequireArgUnpacking)
if ( Cpanel::FHUtils::Tiny::is_a( $_[0] ) ) {
my $fh = $_[0];
my ( $looks_like_json, $fragment_ref ) = _fh_looks_like_json($fh);
my $bytes_read = length $$fragment_ref;
if ($bytes_read) {
seek( $fh, -$bytes_read, $Cpanel::Fcntl::Constants::SEEK_CUR ) or die "seek() failed: $!";
}
return $looks_like_json;
}
return _string_looks_like_json( $_[0] );
}
sub to_bool {
my ($val) = @_;
$val = 0 if defined $val && $val eq 'false';
return !!$val ? true() : false();
}
1;
} # --- END Cpanel/JSON.pm
{ # --- BEGIN Cpanel/JSON/FailOK.pm
package Cpanel::JSON::FailOK;
use strict;
use warnings;
no warnings 'once';
sub LoadJSONModule {
local $@;
my $load_ok = eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
require Cpanel::JSON; # PPI NO PARSE - FailOK
1;
};
if ( !$load_ok && !$ENV{'CPANEL_BASE_INSTALL'} && index( $^X, '/usr/local/cpanel' ) == 0 ) {
warn $@;
}
return $load_ok ? 1 : 0;
}
sub LoadFile {
return undef if !$INC{'Cpanel/JSON.pm'};
return eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
Cpanel::JSON::LoadFile(@_); # PPI NO PARSE - inc check above
};
}
1;
} # --- END Cpanel/JSON/FailOK.pm
{ # --- BEGIN Cpanel/Hash/Stringify.pm
package Cpanel::Hash::Stringify;
use strict;
use warnings;
no warnings 'once';
sub sorted_hashref_string {
my ($hashref) = @_;
return (
( scalar keys %$hashref )
? join(
'_____', map { $_, ( ref $hashref->{$_} eq 'HASH' ? sorted_hashref_string( $hashref->{$_} ) : ref $hashref->{$_} eq 'ARRAY' ? join( '_____', @{ $hashref->{$_} } ) : defined $hashref->{$_} ? $hashref->{$_} : '' ) }
sort keys %$hashref
)
: ''
); #sort is important for order;
}
1;
} # --- END Cpanel/Hash/Stringify.pm
{ # --- BEGIN Cpanel/Destruct.pm
package Cpanel::Destruct;
use strict;
my $in_global_destruction = 0;
my ( $package, $filename, $line, $subroutine ); # preallocate
sub in_dangerous_global_destruction {
if ( !$INC{'Test2/API.pm'} ) {
return 1 if in_global_destruction() && $INC{'Cpanel/BinCheck.pm'};
}
return 0;
}
sub in_global_destruction {
return $in_global_destruction if $in_global_destruction;
if ( defined( ${^GLOBAL_PHASE} ) ) {
if ( ${^GLOBAL_PHASE} eq 'DESTRUCT' ) {
$in_global_destruction = 1;
}
}
else {
local $SIG{'__WARN__'} = \&_detect_global_destruction_pre_514_WARN_handler;
warn;
}
return $in_global_destruction;
}
sub _detect_global_destruction_pre_514_WARN_handler {
if ( length $_[0] > 26 && rindex( $_[0], 'during global destruction.' ) == ( length( $_[0] ) - 26 ) ) {
$in_global_destruction = 1;
}
return;
}
1;
} # --- END Cpanel/Destruct.pm
{ # --- BEGIN Cpanel/Finally.pm
package Cpanel::Finally;
use cPstrict;
no warnings 'once';
# use Cpanel::Carp (); # perlpkg line 211
# use Cpanel::Destruct (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
sub new ( $class, @todo_crs ) {
local $/ = "\n"; # Carp warns when $/ is undef, breaking tests
return bless { 'pid' => $$, 'todo' => [@todo_crs], _from => Cpanel::Carp::safe_longmess() }, $class;
}
sub add ( $self, @new_crs ) {
$self->{'todo'} //= [];
push @{ $self->{'todo'} }, @new_crs;
return;
}
sub skip ($self) {
return delete $self->{'todo'};
}
sub DESTROY ($self) {
return if $$ != $self->{'pid'} || !$self->{'todo'};
if ( Cpanel::Destruct::in_dangerous_global_destruction() ) {
Cpanel::Debug::log_die( qq[PROG $0 - Cpanel::Finally should never be triggered during global destruction\nCalled from ] . $self->{_from} );
}
local $@; #prevent insidious clobber of error messages
while ( @{ $self->{'todo'} } ) {
my $ok = eval {
while ( my $item = shift @{ $self->{'todo'} } ) {
$item->();
}
1;
};
warn $@ if !$ok;
}
return;
}
1;
} # --- END Cpanel/Finally.pm
{ # --- BEGIN Cpanel/Readlink.pm
package Cpanel::Readlink;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Autodie (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
use Cwd ();
our $MAX_SYMLINK_DEPTH = 1024;
sub deep {
my ( $link, $provide_trailing_slash ) = @_;
die Cpanel::Exception::create( 'MissingParameter', 'Provide a link path.' ) if !length $link;
if ( length($link) > 1 && substr( $link, -1, 1 ) eq '/' ) {
$link = substr( $link, 0, length($link) - 1 );
return deep( $link, 1 );
}
if ( !-l $link ) {
return $provide_trailing_slash ? qq{$link/} : $link;
}
my %is_link;
$is_link{$link} = 1;
my $depth = 0;
my $base = _get_base_for($link);
if ( substr( $link, 0, 1 ) ne '/' ) {
$base = Cwd::abs_path() . '/' . $base;
}
while ( ( $is_link{$link} ||= -l $link ) && ++$depth <= $MAX_SYMLINK_DEPTH ) {
$link = Cpanel::Autodie::readlink($link);
if ( substr( $link, 0, 1 ) ne '/' ) {
$link = $base . '/' . $link;
}
$base = _get_base_for($link);
}
return $provide_trailing_slash ? qq{$link/} : $link;
}
sub _get_base_for {
my $basename = shift;
my @path = split( '/', $basename );
pop(@path);
return join( '/', @path );
}
1;
} # --- END Cpanel/Readlink.pm
{ # --- BEGIN Cpanel/FileUtils/Write.pm
package Cpanel::FileUtils::Write;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
use Cpanel::Autodie ( 'rename', 'syswrite_sigguard', 'seek', 'print', 'truncate' );
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::FileUtils::Open (); # perlpkg line 211
# use Cpanel::Finally (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
our $Errno_EEXIST = 17;
our $MAX_TMPFILE_CREATE_ATTEMPTS = 1024;
my $DEFAULT_PERMS = 0600;
my $_WRONLY_CREAT_EXCL;
sub write_fh { ##no critic qw(RequireArgUnpacking)
my $fh = $_[0];
Cpanel::Autodie::seek( $fh, 0, 0 );
Cpanel::Autodie::print( $fh, $_[1] );
Cpanel::Autodie::truncate( $fh, tell($fh) );
return 1;
}
sub write {
return _write_to_tmpfile( @_[ 0 .. 2 ], \&_write_finish );
}
sub overwrite {
return _write_to_tmpfile( @_[ 0 .. 2 ], \&_overwrite_finish );
}
sub overwrite_no_exceptions {
my $fh;
local $@;
eval {
$fh = overwrite(@_);
1;
} or Cpanel::Debug::log_warn("overwrite exception: $@");
return !!$fh;
}
sub _write_to_tmpfile { ##no critic qw(RequireArgUnpacking)
my ( $filename, $perms_or_hr, $finish_cr ) = ( $_[0], $_[2], $_[3] );
if ( !defined $filename ) {
exists $INC{'Carp.pm'} ? Carp::confess("write() called with undefined filename") : die("write() called with undefined filename");
}
if ( ref $filename ) {
die "Use write_fh to write to a file handle. ($filename is a filehandle, right?)";
}
my ( $fh, $tmpfile_is_renamed );
if ( -l $filename ) {
require Cpanel::Readlink;
$filename = Cpanel::Readlink::deep($filename);
}
my ( $callback_cr, $tmp_perms );
if ( 'HASH' eq ref $perms_or_hr ) {
$callback_cr = $perms_or_hr->{'before_installation'};
}
else {
$tmp_perms = $perms_or_hr;
}
$tmp_perms //= $DEFAULT_PERMS;
my ( $tmpfile, $attempts ) = ( '', 0 );
while (1) {
local $!;
my $rand = rand(99999999);
$rand = sprintf( '%x', substr( $rand, 2 ) );
my $last_slash_idx = rindex( $filename, '/' );
$tmpfile = $filename;
substr( $tmpfile, 1 + $last_slash_idx, 0 ) = ".tmp.$rand.";
last if Cpanel::FileUtils::Open::sysopen_with_real_perms(
$fh,
$tmpfile,
( $_WRONLY_CREAT_EXCL ||= ( $Cpanel::Fcntl::Constants::O_CREAT | $Cpanel::Fcntl::Constants::O_EXCL | $Cpanel::Fcntl::Constants::O_WRONLY ) ),
$tmp_perms,
);
if ( $! != $Errno_EEXIST ) {
die Cpanel::Exception::create( 'IO::FileCreateError', [ error => $!, path => $tmpfile, permissions => $tmp_perms ] );
}
++$attempts;
if ( $attempts >= $MAX_TMPFILE_CREATE_ATTEMPTS ) {
die Cpanel::Exception::create_raw( 'IO::FileCreateError', "Too many ($MAX_TMPFILE_CREATE_ATTEMPTS) failed attempts to create a temp file as EUID $> and GID $) based on “$filename”! The last tried file was “$tmpfile”, and the last error was: $!" );
}
}
my $finally = Cpanel::Finally->new(
sub {
if ( !$tmpfile_is_renamed ) {
Cpanel::Autodie::unlink_if_exists($tmpfile);
}
return;
}
);
if ( my $ref = ref $_[1] ) {
if ( $ref eq 'SCALAR' ) {
_write_fh( $fh, ${ $_[1] } );
}
else {
die Cpanel::Exception::create( 'InvalidParameter', 'Invalid content type “[_1]”, expect a scalar.', [$ref] );
}
}
else {
_write_fh( $fh, $_[1] );
}
$callback_cr->($fh) if $callback_cr;
$tmpfile_is_renamed = $finish_cr->( $tmpfile, $filename );
if ( !$tmpfile_is_renamed ) {
Cpanel::Autodie::unlink_if_exists($tmpfile);
}
$finally->skip();
return $fh;
}
*_syswrite = *Cpanel::Autodie::syswrite_sigguard;
our $DEBUG_WRITE;
sub _write_fh {
if ( length $_[1] ) {
my $pos = 0;
do {
local $SIG{'XFSZ'} = 'IGNORE' if $pos;
$pos += _syswrite( $_[0], $_[1], length( $_[1] ), $pos ) || do {
die "Zero bytes written, non-error!";
};
} while $pos < length( $_[1] );
}
return;
}
sub _write_finish {
Cpanel::Autodie::link(@_);
return 0;
}
*_overwrite_finish = *Cpanel::Autodie::rename;
1;
} # --- END Cpanel/FileUtils/Write.pm
{ # --- BEGIN Cpanel/FileUtils/Write/JSON/Lazy.pm
package Cpanel::FileUtils::Write::JSON::Lazy;
use strict;
use warnings;
no warnings 'once';
sub write_file {
my ( $file_or_fh, $data, $perms ) = @_;
if ( exists $INC{'Cpanel/JSON.pm'} && exists $INC{'JSON/XS.pm'} && ( my $Dump = 'Cpanel::JSON'->can('Dump') ) ) { # PPI NO PARSE -- check earlier - must be quoted or it ends up in the stash
require Cpanel::FileUtils::Write if !$INC{'Cpanel/FileUtils/Write.pm'};
require Cpanel::FHUtils::Tiny if !$INC{'Cpanel/FHUtils/Tiny.pm'};
my $func = Cpanel::FHUtils::Tiny::is_a($file_or_fh) ? 'write_fh' : 'overwrite';
if ( $func eq 'write_fh' ) {
if ( !defined $perms ) {
$perms = 0600;
}
chmod( $perms, $file_or_fh ) or die "Failed to set permissions on the file handle passed to Cpanel::FileUtils::Write::JSON::Lazy::write_file because of an error: $!";
}
return Cpanel::FileUtils::Write->can($func)->(
$file_or_fh,
$Dump->($data),
$perms
);
}
return 0;
}
sub write_file_pretty {
my ( $file_or_fh, $data, $perms ) = @_;
if ( exists $INC{'Cpanel/JSON.pm'} && exists $INC{'JSON/XS.pm'} && ( my $Dump = 'Cpanel::JSON'->can('pretty_dump') ) ) { # PPI NO PARSE -- check earlier - must be quoted or it ends up in the stash
require Cpanel::FileUtils::Write if !$INC{'Cpanel/FileUtils/Write.pm'};
require Cpanel::FHUtils::Tiny if !$INC{'Cpanel/FHUtils/Tiny.pm'};
my $func = Cpanel::FHUtils::Tiny::is_a($file_or_fh) ? 'write_fh' : 'overwrite';
if ( $func eq 'write_fh' ) {
if ( !defined $perms ) {
$perms = 0600;
}
chmod( $perms, $file_or_fh ) or die "Failed to set permissions on the file handle passed to Cpanel::FileUtils::Write::JSON::Lazy::write_file because of an error: $!";
}
return Cpanel::FileUtils::Write->can($func)->(
$file_or_fh,
$Dump->($data),
$perms
);
}
return 0;
}
1;
} # --- END Cpanel/FileUtils/Write/JSON/Lazy.pm
{ # --- BEGIN Cpanel/AdminBin/Serializer.pm
package Cpanel::AdminBin::Serializer;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::JSON (); # perlpkg line 211
our $VERSION = '2.4';
our $MAX_LOAD_LENGTH;
our $MAX_PRIV_LOAD_LENGTH;
BEGIN {
*MAX_LOAD_LENGTH = \$Cpanel::JSON::MAX_LOAD_LENGTH;
*MAX_PRIV_LOAD_LENGTH = \$Cpanel::JSON::MAX_PRIV_LOAD_LENGTH;
*DumpFile = *Cpanel::JSON::DumpFile;
}
BEGIN {
*Dump = *Cpanel::JSON::Dump;
*SafeDump = *Cpanel::JSON::SafeDump;
*LoadFile = *Cpanel::JSON::LoadFileNoSetUTF8;
*Load = *Cpanel::JSON::Load;
*looks_like_serialized_data = *Cpanel::JSON::looks_like_json;
}
sub SafeLoadFile {
return Cpanel::JSON::_LoadFile( $_[0], $Cpanel::JSON::MAX_LOAD_LENGTH, $Cpanel::JSON::DECODE_UTF8, $_[1], $Cpanel::JSON::LOAD_STRICT );
}
sub SafeLoad {
utf8::decode( $_[0] );
return Cpanel::JSON::LoadNoSetUTF8(@_);
}
sub clone {
return Cpanel::JSON::LoadNoSetUTF8( Cpanel::JSON::Dump( $_[0] ) );
}
1;
} # --- END Cpanel/AdminBin/Serializer.pm
{ # --- BEGIN Cpanel/AdminBin/Serializer/FailOK.pm
package Cpanel::AdminBin::Serializer::FailOK;
use strict;
use warnings;
no warnings 'once';
sub LoadModule {
local $@;
return 1 if $INC{'Cpanel/AdminBin/Serializer.pm'};
my $load_ok = eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
require Cpanel::AdminBin::Serializer;
1;
};
if ( !$load_ok && !$ENV{'CPANEL_BASE_INSTALL'} && index( $^X, '/usr/local/cpanel' ) == 0 ) {
warn $@;
}
return $load_ok ? 1 : 0;
}
sub LoadFile {
my ( $file_or_fh, $path ) = @_;
return undef if !$INC{'Cpanel/AdminBin/Serializer.pm'};
return eval {
local $SIG{'__DIE__'}; # Suppress spewage as we may be reading an invalid cache
local $SIG{'__WARN__'}; # and since failure is ok to throw it away
Cpanel::AdminBin::Serializer::LoadFile( $file_or_fh, undef, $path );
};
}
1;
} # --- END Cpanel/AdminBin/Serializer/FailOK.pm
{ # --- BEGIN Cpanel/SV.pm
package Cpanel::SV;
use strict;
use warnings;
no warnings 'once';
sub untaint {
return $_[0] unless ${^TAINT};
require # Cpanel::Static OK - we should not untaint variables as part of updatenow.static
Taint::Util;
Taint::Util::untaint( $_[0] );
return $_[0];
}
1;
} # --- END Cpanel/SV.pm
{ # --- BEGIN Cpanel/Umask.pm
package Cpanel::Umask;
use strict;
# use parent Cpanel::Finally (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Finally); }
sub new {
my ( $class, $new ) = @_;
my $old = umask();
umask($new);
return $class->SUPER::new(
sub {
my $cur = umask();
if ( $cur != $new ) {
my ( $cur_o, $old_o, $new_o ) = map { '0' . sprintf( '%o', $_ ) } ( $cur, $old, $new );
warn "I want to umask($old_o). I expected the current umask to be $new_o, but it’s actually $cur_o.";
}
umask($old);
}
);
}
1;
} # --- END Cpanel/Umask.pm
{ # --- BEGIN Cpanel/Config/LoadConfig.pm
package Cpanel::Config::LoadConfig;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Hash::Stringify (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::FileUtils::Write::JSON::Lazy (); # perlpkg line 211
# use Cpanel::AdminBin::Serializer::FailOK (); # perlpkg line 211
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
# use Cpanel::HiRes (); # perlpkg line 211
# use Cpanel::SV (); # perlpkg line 211
use constant _ENOENT => 2;
my $logger;
our $PRODUCT_CONF_DIR = '/var/cpanel';
our $_DEBUG_SAFEFILE = 0;
my %COMMON_CACHE_NAMES = (
':__^\s*[#;]____0__' => 'default_colon',
':\s+__^\s*[#;]____0__' => 'default_colon_any_space',
': __^\s*[#;]____0__' => 'default_colon_with_one_space',
'=__^\s*[#;]____0__skip_readable_check_____1' => 'default_skip_readable',
'=__^\s*[#;]____0__' => 'default',
'=__^\s*[#;]__(?^:\s+)__0__' => 'default_with_preproc_newline',
'=__^\s*[#;]____1__' => 'default_allow_undef',
'\s*[:]\s*__^\s*[#;]____0__' => 'default_colon_before_after_space',
'\s*=\s*__^\s*[#;]____1__' => 'default_equal_before_after_space_allow_undef',
'\s*[\=]\s*__^\s*[#]____0__use_reverse_____0' => 'default_equal_before_after_space',
': __^\s*[#;]____0__limit_____10000000000_____use_reverse_____0' => 'default_with_10000000000_limit',
'\s*[:]\s*__^\s*[#;]____0__use_hash_of_arr_refs_____0_____use_reverse_____0' => 'default_use_hash_of_arr_refs',
': __^\s*[#;]____0__limit__________use_reverse_____0' => 'default_colon_single_space_no_limit',
': __^\s*[#;]____1__skip_keys_____nobody_____use_hash_of_arr_refs_____0_____use_reverse_____0' => 'default_colon_skip_nobody_no_limit',
': __^\s*[#;]____1__use_reverse_____1' => 'default_reverse_allow_undef',
'\s+__^\s*[#;]____0__' => 'default_space_seperated_config',
'\s*=\s*__^\s*[#;]__^\s*__0__' => 'default_equal_space_seperated_config', #ea4.conf
);
my $DEFAULT_DELIMITER = '=';
my $DEFAULT_COMMENT_REGEXP = '^\s*[#;]'; #Keep in sync with tr{} below!!
my @BOOLEAN_OPTIONS = qw(
allow_undef_values
use_hash_of_arr_refs
use_reverse
);
my $CACHE_DIR_PERMS = 0700;
sub _process_parse_args {
my (%opts) = @_;
if ( !defined $opts{'delimiter'} ) {
$opts{'delimiter'} = $DEFAULT_DELIMITER;
}
$opts{'regexp_to_preprune'} ||= q{};
$opts{'comment'} ||= $DEFAULT_COMMENT_REGEXP;
$opts{'comment'} = '' if $opts{'comment'} eq '0E0';
$opts{$_} ||= 0 for @BOOLEAN_OPTIONS;
return %opts;
}
{
no warnings 'once';
*get_homedir_and_cache_dir = *_get_homedir_and_cache_dir;
}
sub _get_homedir_and_cache_dir {
my ( $homedir, $cache_dir );
if ( $> == 0 ) {
$cache_dir = "$PRODUCT_CONF_DIR/configs.cache";
}
else {
{
no warnings 'once';
$homedir = $Cpanel::homedir;
}
if ( !$homedir ) {
eval 'local $SIG{__DIE__}; local $SIG{__WARN__}; require Cpanel::PwCache'; ## no critic qw(ProhibitStringyEval) # PPI USE OK - just after
$homedir = Cpanel::PwCache::gethomedir() if $INC{'Cpanel/PwCache.pm'};
return unless $homedir; # undef for homedir and cache_dir avoid issues later when using undef as hash key
}
Cpanel::SV::untaint($homedir);
$homedir =~ tr{/}{}s;
return ( $homedir, undef ) if $homedir eq '/';
if ( $ENV{'TEAM_USER'} ) {
$cache_dir = "$homedir/$ENV{'TEAM_USER'}/.cpanel/caches/config";
}
else {
$cache_dir = "$homedir/.cpanel/caches/config";
}
}
return ( $homedir, $cache_dir );
}
sub loadConfig { ## no critic qw(Subroutines::ProhibitExcessComplexity Subroutines::ProhibitManyArgs)
my ( $file, $conf_ref, $delimiter, $comment, $regexp_to_preprune, $allow_undef_values, $arg_ref ) = @_;
$conf_ref ||= -1;
my %processed_positional_args = _process_parse_args(
delimiter => $delimiter,
comment => $comment,
regexp_to_preprune => $regexp_to_preprune,
allow_undef_values => $allow_undef_values,
$arg_ref ? %$arg_ref : (),
);
my $empty_is_invalid = ( defined $arg_ref ) ? delete $arg_ref->{'empty_is_invalid'} : undef;
my ( $use_reverse, $use_hash_of_arr_refs );
( $delimiter, $comment, $regexp_to_preprune, $allow_undef_values, $use_reverse, $use_hash_of_arr_refs ) = @processed_positional_args{
qw(
delimiter
comment
regexp_to_preprune
allow_undef_values
use_reverse
use_hash_of_arr_refs
)
};
if ( !$file || $file =~ tr/\0// ) {
_do_logger( 'warn', 'loadConfig requires valid filename' );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, "loadConfig requires valid filename" );
}
return;
}
my $filesys_mtime = ( Cpanel::HiRes::stat($file) )[9] or do {
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, "Unable to stat $file: $!" );
}
return;
};
my $load_into_conf_ref = ( !ref $conf_ref && $conf_ref == -1 ) ? 0 : 1;
if ($load_into_conf_ref) {
$conf_ref = _hashify_ref($conf_ref);
}
my ( $homedir, $cache_dir ) = _get_homedir_and_cache_dir();
my $cache_file;
Cpanel::AdminBin::Serializer::FailOK::LoadModule() if !$INC{'Cpanel/AdminBin/Serializer.pm'};
if ( $cache_dir && $INC{'Cpanel/JSON.pm'} && ( !defined $arg_ref || !ref $arg_ref || !exists $arg_ref->{'nocache'} && !$arg_ref->{'keep_locked_open'} ) ) {
$cache_file = get_cache_file(
'file' => $file,
'cache_dir' => $cache_dir,
'delimiter' => $delimiter,
'comment' => $comment,
'regexp_to_preprune' => $regexp_to_preprune,
'allow_undef_values' => $allow_undef_values,
'arg_ref' => $arg_ref,
);
my ( $cache_valid, $ref ) = load_from_cache_if_valid(
'file' => $file,
'cache_file' => $cache_file,
'filesys_mtime' => $filesys_mtime,
'conf_ref' => $conf_ref,
'load_into_conf_ref' => $load_into_conf_ref,
'empty_is_invalid' => $empty_is_invalid,
);
if ($cache_valid) {
return $ref;
}
}
$conf_ref = {} if !$load_into_conf_ref;
my $conf_fh;
my $conflock;
my $locked;
if ( $arg_ref->{'keep_locked_open'} || $arg_ref->{'rw'} ) {
require Cpanel::SafeFile;
$locked = 1;
$conflock = Cpanel::SafeFile::safeopen( $conf_fh, '+<', $file );
}
else {
$conflock = open( $conf_fh, '<', $file );
}
if ( !$conflock ) {
my $open_err = $! || '(unspecified error)';
local $_DEBUG_SAFEFILE = 1;
require Cpanel::Logger;
my $is_root = ( $> == 0 ? 1 : 0 );
if ( !$is_root && !$arg_ref->{'skip_readable_check'} ) {
if ( !-r $file ) {
my $msg;
if ( my $err = $! ) {
$msg = "$file’s readability check failed: $err";
}
else {
my $euser = getpwuid $>;
$msg = "$file is not readable as $euser.";
}
_do_logger( 'warn', $msg );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, $msg );
}
return;
}
}
my $verb = ( $locked ? 'lock/' : q<> ) . 'open';
my $msg = "Unable to $verb $file as UIDs $</$>: $open_err";
Cpanel::Logger::cplog( $msg, 'warn', __PACKAGE__ );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, $msg );
}
return;
}
my ( $parse_ok, $parsed ) = _parse_from_filehandle(
$conf_fh,
comment => $comment,
delimiter => $delimiter,
regexp_to_preprune => $regexp_to_preprune,
allow_undef_values => $allow_undef_values,
use_reverse => $use_reverse,
use_hash_of_arr_refs => $use_hash_of_arr_refs,
$arg_ref ? %$arg_ref : (),
);
if ( $locked && !$arg_ref->{'keep_locked_open'} ) {
require Cpanel::SafeFile;
Cpanel::SafeFile::safeclose( $conf_fh, $conflock );
}
if ( !$parse_ok ) {
require Cpanel::Logger;
Cpanel::Logger::cplog( "Unable to parse $file: $parsed", 'warn', __PACKAGE__ );
if ( $arg_ref->{'keep_locked_open'} ) {
return ( undef, undef, undef, "Unable to parse $file: $parsed" );
}
return;
}
@{$conf_ref}{ keys %$parsed } = values %$parsed;
if ($cache_file) {
write_cache(
'cache_dir' => $cache_dir,
'cache_file' => $cache_file,
'homedir' => $homedir,
'is_root' => ( $> == 0 ? 1 : 0 ),
'data' => $parsed,
);
}
if ( $arg_ref->{'keep_locked_open'} ) {
return $conf_ref, $conf_fh, $conflock, "open success";
}
return $conf_ref;
}
sub load_from_cache_if_valid {
my (%opts) = @_;
my $cache_file = $opts{'cache_file'} or die "need cache_file!";
my $file = $opts{'file'};
my $conf_ref = $opts{'conf_ref'};
my $load_into_conf_ref = $opts{'load_into_conf_ref'};
my $filesys_mtime = $opts{'filesys_mtime'} || ( Cpanel::HiRes::stat($file) )[9];
open( my $cache_fh, '<:stdio', $cache_file ) or do {
my $err = $!;
my $msg = "non-fatal error: open($cache_file): $err";
warn $msg if $! != _ENOENT();
return ( 0, $msg );
};
my ( $cache_filesys_mtime, $now, $cache_conf_ref ) = ( ( Cpanel::HiRes::fstat($cache_fh) )[9], Cpanel::HiRes::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 && _greater_with_same_precision( $cache_filesys_mtime, $filesys_mtime ) && _greater_with_same_precision( $now, $cache_filesys_mtime ) ) {
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) {
print STDERR __PACKAGE__ . "::loadConfig using cache_file:$cache_file\n";
}
Cpanel::AdminBin::Serializer::FailOK::LoadModule() if !$INC{'Cpanel/AdminBin/Serializer.pm'};
if ( $cache_conf_ref = Cpanel::AdminBin::Serializer::FailOK::LoadFile($cache_fh) ) { #zero keys is a valid file still it may just be all comments or empty
close($cache_fh);
if ( $opts{'empty_is_invalid'} && scalar keys %$cache_conf_ref == 0 ) {
return ( 0, 'Cache is empty' );
}
my $ref_to_return;
if ($load_into_conf_ref) {
@{$conf_ref}{ keys %$cache_conf_ref } = values %$cache_conf_ref;
$ref_to_return = $conf_ref;
}
else {
$ref_to_return = $cache_conf_ref;
}
return ( 1, $ref_to_return );
}
elsif ( ( $Cpanel::Debug::level || 0 ) >= 5 ) {
print STDERR __PACKAGE__ . "::loadConfig failed to load cache_file:$cache_file\n";
}
}
else {
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) {
print STDERR __PACKAGE__ . "::loadConfig NOT using cache_file:$cache_file\n";
}
}
return ( 0, 'Cache not valid' );
}
sub _greater_with_same_precision {
my ( $float1, $float2 ) = @_;
my ( $int1, $int2 ) = ( int($float1), int($float2) );
if ( $float1 == $int1 or $float2 == $int2 ) {
return $int1 > $int2;
}
return $float1 > $float2;
}
sub get_cache_file { ## no critic qw(Subroutines::RequireArgUnpacking) - Args unpacked by _process_parse_args
my %opts = _process_parse_args(@_);
die 'need cache_dir!' if !$opts{'cache_dir'};
my $stringified_args = join(
'__',
@opts{qw(delimiter comment regexp_to_preprune allow_undef_values)}, ( scalar keys %{ $opts{'arg_ref'} } ? Cpanel::Hash::Stringify::sorted_hashref_string( $opts{'arg_ref'} ) : '' )
);
if ( ( $Cpanel::Debug::level || 0 ) >= 5 ) { # PPI NO PARSE - ok missing
print STDERR __PACKAGE__ . "::loadConfig stringified_args[$stringified_args]\n";
}
my $safe_filename = $opts{'file'};
$safe_filename =~ tr{/}{_};
return $opts{'cache_dir'} . '/' . $safe_filename . '___' . ( $COMMON_CACHE_NAMES{$stringified_args} || _get_fastest_hash($stringified_args) );
}
sub _get_fastest_hash {
require Cpanel::Hash;
goto \&Cpanel::Hash::get_fastest_hash;
}
sub write_cache {
my (%opts) = @_;
my $cache_file = $opts{'cache_file'};
my $cache_dir = $opts{'cache_dir'};
my $homedir = $opts{'homedir'};
my $is_root = $opts{'is_root'};
my $parsed = $opts{'data'};
my @dirs = ($cache_dir);
if ( !$is_root ) {
if ( $ENV{'TEAM_USER'} ) {
unshift @dirs, "$homedir/$ENV{'TEAM_USER'}", "$homedir/$ENV{'TEAM_USER'}/.cpanel", "$homedir/$ENV{'TEAM_USER'}/.cpanel/caches";
}
else {
unshift @dirs, "$homedir/.cpanel", "$homedir/.cpanel/caches";
}
}
foreach my $dir (@dirs) {
Cpanel::SV::untaint($dir);
chmod( $CACHE_DIR_PERMS, $dir ) or do {
if ( $! == _ENOENT() ) {
require Cpanel::Umask;
my $umask = Cpanel::Umask->new(0);
mkdir( $dir, $CACHE_DIR_PERMS ) or do {
_do_logger( 'warn', "Failed to create dir “$dir”: $!" );
};
}
else {
_do_logger( 'warn', "chmod($dir): $!" );
}
};
}
my $wrote_ok = eval { Cpanel::FileUtils::Write::JSON::Lazy::write_file( $cache_file, $parsed, 0600 ) };
my $error = $@;
$error ||= "Unknown error" if !defined $wrote_ok;
if ($error) {
_do_logger( 'warn', "Could not create cache file “$cache_file”: $error" );
unlink $cache_file; #outdated
}
if ( ( $Cpanel::Debug::level || 0 ) > 4 ) { # PPI NO PARSE - ok missing
print STDERR __PACKAGE__ . "::loadConfig [lazy write cache file] [$cache_file] wrote_ok:[$wrote_ok]\n";
}
return 1;
}
sub _do_logger {
my ( $action, $msg ) = @_;
require Cpanel::Logger;
$logger ||= Cpanel::Logger->new();
return $logger->$action($msg);
}
sub parse_from_filehandle {
my ( $conf_fh, %opts ) = @_;
return _parse_from_filehandle( $conf_fh, _process_parse_args(%opts) );
}
sub _parse_from_filehandle {
my ( $conf_fh, %opts ) = @_;
my ( $comment, $limit, $regexp_to_preprune, $delimiter, $allow_undef_values, $use_hash_of_arr_refs, $skip_keys, $use_reverse ) = @opts{
qw(
comment
limit
regexp_to_preprune
delimiter
allow_undef_values
use_hash_of_arr_refs
skip_keys
use_reverse
)
};
my $conf_ref = {};
my $parser_code;
my ( $k, $v ); ## no critic qw(Variables::ProhibitUnusedVariables)
my $keys = 0;
my $key_value_text = $use_reverse ? '1,0' : '0,1';
my $cfg_txt = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $conf_fh, $cfg_txt );
my $has_cr = index( $cfg_txt, "\r" ) > -1 ? 1 : 0;
_remove_comments_from_text( \$cfg_txt, $comment, \$has_cr ) if $cfg_txt && $comment;
my $split_on = $has_cr ? '\r?\n' : '\n';
if ( !$limit && !$regexp_to_preprune && !$use_hash_of_arr_refs && length $delimiter ) {
if ($allow_undef_values) {
$parser_code = qq<
\$conf_ref = {
map {
(split(m/> . $delimiter . qq</, \$_, 2))[$key_value_text]
} split(/> . $split_on . qq</, \$cfg_txt)
};
>;
}
else {
$parser_code = ' $conf_ref = { map { ' . '($k,$v) = (split(m/' . $delimiter . '/, $_, 2))[' . $key_value_text . ']; ' . 'defined($v) ? ($k,$v) : () ' . '} split(/' . $split_on . '/, $cfg_txt ) }';
}
}
else {
if ( ( $Cpanel::Debug::level || 0 ) > 4 ) { # PPI NO PARSE - ok if not there
$limit ||= 0;
print STDERR __PACKAGE__ . "::parse_from_filehandle [slow LoadConfig parser used] LIMIT:[!$limit] REGEXP_TO_DELETE[!$regexp_to_preprune] USE_HASH_OF_ARR_REFS[$use_hash_of_arr_refs)]\n";
}
$parser_code = 'foreach (split(m/' . $split_on . '/, $cfg_txt)) {' . "\n" #
. q{next if !length;} . "\n" #
. ( $limit ? q{last if $keys++ == } . $limit . ';' : '' ) . "\n" . ( $regexp_to_preprune ? q{ s/} . $regexp_to_preprune . q{//g;} : '' ) . "\n" #
. (
length $delimiter ? #
(
q{( $k, $v ) = (split( /} . $delimiter . q{/, $_, 2 ))[} . $key_value_text . q{];} . "\n" . #
( !$allow_undef_values ? q{ next if !defined($v); } : '' ) . "\n" . #
( $use_hash_of_arr_refs ? q{ push @{ $conf_ref->{$k} }, $v; } : q{ $conf_ref->{$k} = $v; } ) . "\n" #
)
: q{$conf_ref->{$_} = 1; } . "\n"
) . '};';
}
$parser_code .= "; 1";
$parser_code =~ tr{\n}{\r}; ## no critic qw(Cpanel::TransliterationUsage)
eval($parser_code) or do { ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
$parser_code =~ tr{\r}{\n}; ## no critic qw(Cpanel::TransliterationUsage)
_do_logger( 'panic', "Failed to parse :: $parser_code: $@" );
return ( 0, "$@\n$parser_code" );
};
delete $conf_ref->{''} if !defined( $conf_ref->{''} );
if ($skip_keys) {
my $skip_keys_ar;
if ( ref $skip_keys eq 'ARRAY' ) {
$skip_keys_ar = $skip_keys;
}
elsif ( ref $skip_keys eq 'HASH' ) {
$skip_keys_ar = [ keys %$skip_keys ];
}
else {
return ( 0, 'skip_keys must be an ARRAY or HASH reference' );
}
delete @{$conf_ref}{@$skip_keys_ar};
}
return ( 1, $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 default_product_dir {
$PRODUCT_CONF_DIR = shift if @_;
return $PRODUCT_CONF_DIR;
}
sub _remove_comments_from_text {
my ( $cfg_txt_sr, $comment, $has_cr_sr ) = @_;
if ($$has_cr_sr) {
$$cfg_txt_sr = join( "\n", grep ( !m/$comment/, split( m{\r?\n}, $$cfg_txt_sr ) ) );
$$has_cr_sr = 0;
}
elsif ( $comment eq $DEFAULT_COMMENT_REGEXP ) {
if ( rindex( $$cfg_txt_sr, '#', 0 ) == 0 && index( $$cfg_txt_sr, "\n" ) > -1 ) {
substr( $$cfg_txt_sr, 0, index( $$cfg_txt_sr, "\n" ) + 1, '' );
}
$$cfg_txt_sr =~ s{$DEFAULT_COMMENT_REGEXP.*}{}omg if $$cfg_txt_sr =~ tr{#;}{};
}
else {
$$cfg_txt_sr =~ s{$comment.*}{}mg;
}
return 1;
}
1;
} # --- END Cpanel/Config/LoadConfig.pm
{ # --- BEGIN Cpanel/Config/LoadWwwAcctConf.pm
package Cpanel::Config::LoadWwwAcctConf;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::HiRes (); # perlpkg line 211
# use Cpanel::Path::Normalize (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::JSON::FailOK (); # perlpkg line 211
my $SYSTEM_CONF_DIR = '/etc';
my $wwwconf_cache;
my $wwwconf_mtime = 0;
my $has_serializer;
our $wwwacctconf = "$SYSTEM_CONF_DIR/wwwacct.conf";
our $wwwacctconfshadow = "$SYSTEM_CONF_DIR/wwwacct.conf.shadow";
sub import {
my $this = shift;
if ( !exists $INC{'Cpanel/JSON.pm'} ) {
Cpanel::JSON::FailOK::LoadJSONModule();
}
if ( $INC{'Cpanel/JSON.pm'} ) {
$has_serializer = 1;
}
return Exporter::import( $this, @_ );
}
sub loadwwwacctconf { ## no critic qw(Subroutines::ProhibitExcessComplexity)
if ( $INC{'Cpanel/JSON.pm'} ) { $has_serializer = 1; } #something else loaded it
my $filesys_mtime = ( Cpanel::HiRes::stat($wwwacctconf) )[9];
return if !$filesys_mtime;
if ( $filesys_mtime == $wwwconf_mtime && $wwwconf_cache ) {
return wantarray ? %{$wwwconf_cache} : $wwwconf_cache;
}
my $wwwacctconf_cache = "$wwwacctconf.cache";
my $wwwacctconfshadow_cache = "$wwwacctconfshadow.cache";
my $is_root = $> ? 0 : 1;
if ($has_serializer) {
my $cache_file;
my $cache_filesys_mtime;
my $have_valid_cache = 1;
if ( $is_root && -e $wwwacctconfshadow_cache ) {
$cache_filesys_mtime = ( Cpanel::HiRes::stat($wwwacctconfshadow_cache) )[9]; #shadow cache's mtime
my $shadow_file_mtime = ( Cpanel::HiRes::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 ( -e $wwwacctconf_cache && !( $is_root && -r $wwwacctconfshadow ) ) {
$cache_filesys_mtime = ( Cpanel::HiRes::stat $wwwacctconf_cache )[9]; #regular cache's mtime
$cache_file = $wwwacctconf_cache;
}
else {
$have_valid_cache = undef;
}
my $now = Cpanel::HiRes::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 ) ) {
$wwwconf_ref = Cpanel::JSON::FailOK::LoadFile($conf_fh);
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 ($is_root) { 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 ( $is_root && $has_serializer ) {
$can_write_cache = 1;
}
my %CONF = (
'ADDR' => undef,
'CONTACTEMAIL' => undef,
'DEFMOD' => undef,
'ETHDEV' => undef,
'HOST' => undef,
'NS' => undef,
'NS2' => undef,
);
require Cpanel::Config::LoadConfig;
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{'HOMEMATCH'} =~ s{/+$}{} if defined $CONF{'HOMEMATCH'}; # Remove trailing slashes
$CONF{'HOMEDIR'} = Cpanel::Path::Normalize::normalize( $CONF{'HOMEDIR'} ) if defined $CONF{'HOMEDIR'};
if ($can_write_cache) {
my $cache_file = $configfile . '.cache';
require Cpanel::FileUtils::Write::JSON::Lazy;
Cpanel::FileUtils::Write::JSON::Lazy::write_file( $cache_file, \%CONF, ( $configfile eq $wwwacctconfshadow ) ? 0600 : 0644 );
}
}
$wwwconf_mtime = $filesys_mtime;
$wwwconf_cache = \%CONF;
return wantarray ? %CONF : \%CONF;
}
sub reset_mem_cache {
( $wwwconf_mtime, $wwwconf_cache ) = ( 0, undef );
}
sub reset_has_serializer {
$has_serializer = 0;
}
sub default_conf_dir {
$SYSTEM_CONF_DIR = shift if @_;
$wwwacctconf = "$SYSTEM_CONF_DIR/wwwacct.conf";
$wwwacctconfshadow = "$SYSTEM_CONF_DIR/wwwacct.conf.shadow";
return $SYSTEM_CONF_DIR;
}
sub reset_caches {
my @cache_files = map { "$_.cache" } ( $wwwacctconf, $wwwacctconfshadow );
for my $cache_file (@cache_files) {
unlink $cache_file if -e $cache_file;
}
reset_mem_cache();
return;
}
1;
} # --- END Cpanel/Config/LoadWwwAcctConf.pm
{ # --- BEGIN Cpanel/StatCache.pm
package Cpanel::StatCache;
use strict;
use warnings;
no warnings 'once';
our $VERSION = 0.4;
my %STATCACHE;
our $USE_LSTAT = 0;
sub StatCache_init { }
sub cachedmtime {
return (
exists $STATCACHE{ $_[0] } ? $STATCACHE{ $_[0] }->[0]
: (
$STATCACHE{ $_[0] } = (
$USE_LSTAT && -l $_[0] ? [ ( lstat(_) )[ 9, 7, 10 ] ]
: -e $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ]
: [ 0, 0, 0 ]
)
)->[0]
);
}
sub cachedmtime_size {
return (
exists $STATCACHE{ $_[0] } ? @{ $STATCACHE{ $_[0] } }[ 0, 1 ]
: @{
(
$STATCACHE{ $_[0] } = (
$USE_LSTAT && -l $_[0] ? [ ( lstat(_) )[ 9, 7, 10 ] ]
: -e $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ]
: [ 0, 0, 0 ]
)
)
}[ 0, 1 ]
);
}
sub cachedmtime_ctime {
return (
exists $STATCACHE{ $_[0] } ? @{ $STATCACHE{ $_[0] } }[ 0, 2 ]
: @{
(
$STATCACHE{ $_[0] } = (
$USE_LSTAT && -l $_[0] ? [ ( lstat(_) )[ 9, 7, 10 ] ]
: -e $_[0] ? [ ( stat(_) )[ 9, 7, 10 ] ]
: [ 0, 0, 0 ]
)
)
}[ 0, 2 ]
);
}
sub clearcache {
%STATCACHE = ();
return 1;
}
1;
} # --- END Cpanel/StatCache.pm
{ # --- BEGIN Cpanel/NSCD/Constants.pm
package Cpanel::NSCD::Constants;
use strict;
our $NSCD_CONFIG_FILE = '/etc/nscd.conf';
our $NSCD_SOCKET = '/var/run/nscd/socket';
1;
} # --- END Cpanel/NSCD/Constants.pm
{ # --- BEGIN Cpanel/Socket/UNIX/Micro.pm
package Cpanel::Socket::UNIX::Micro;
use strict;
my $MAX_PATH_LENGTH = 107;
my $LITTLE_ENDIAN_TEMPLATE = 'vZ' . ( 1 + $MAX_PATH_LENGTH ); # x86_64 is always little endian
my $AF_UNIX = 1;
my $SOCK_STREAM = 1;
sub connect {
socket( $_[0], $AF_UNIX, $SOCK_STREAM, 0 ) or warn "socket(AF_UNIX, SOCK_STREAM): $!";
return connect( $_[0], micro_sockaddr_un( $_[1] ) );
}
sub micro_sockaddr_un {
if ( length( $_[0] ) > $MAX_PATH_LENGTH ) {
my $excess = length( $_[0] ) - $MAX_PATH_LENGTH;
die "“$_[0]” is $excess character(s) too long to be a path to a local socket ($MAX_PATH_LENGTH bytes maximum)!";
}
return pack( 'va*', $AF_UNIX, $_[0] ) if 0 == rindex( $_[0], "\0", 0 );
return pack(
$LITTLE_ENDIAN_TEMPLATE, # x86_64 is always little endian
$AF_UNIX,
$_[0],
);
}
sub unpack_sockaddr_un {
return substr( $_[0], 2 ) if 2 == rindex( $_[0], "\0", 2 );
return ( unpack $LITTLE_ENDIAN_TEMPLATE, $_[0] )[1];
}
1;
} # --- END Cpanel/Socket/UNIX/Micro.pm
{ # --- BEGIN Cpanel/NSCD/Check.pm
package Cpanel::NSCD::Check;
use strict;
# use Cpanel::NSCD::Constants (); # perlpkg line 211
# use Cpanel::Socket::UNIX::Micro (); # perlpkg line 211
our $CACHE_TTL = 600;
my $last_check_time = 0;
my $nscd_is_running_cache;
sub nscd_is_running {
my $now = time();
if ( $last_check_time && $last_check_time + $CACHE_TTL > $now ) {
return $nscd_is_running_cache;
}
$last_check_time = $now;
my $socket;
if ( Cpanel::Socket::UNIX::Micro::connect( $socket, $Cpanel::NSCD::Constants::NSCD_SOCKET ) ) {
return ( $nscd_is_running_cache = 1 );
}
return ( $nscd_is_running_cache = 0 );
}
1;
} # --- END Cpanel/NSCD/Check.pm
{ # --- BEGIN Cpanel/PwCache/Helpers.pm
package Cpanel::PwCache::Helpers;
use strict;
use warnings;
no warnings 'once';
my $skip_uid_cache = 0;
sub no_uid_cache { $skip_uid_cache = 1; return }
sub uid_cache { $skip_uid_cache = 0; return }
sub skip_uid_cache {
return $skip_uid_cache;
}
sub init {
my ( $totie, $skip_uid_cache_value ) = @_;
tiedto($totie);
$skip_uid_cache = $skip_uid_cache_value;
return;
}
{ # debugging helpers
sub confess { require Carp; return Carp::confess(@_) }
sub cluck { require Carp; return Carp::cluck(@_) }
}
{ # tie logic and cache
my $pwcacheistied = 0;
my $pwcachetie;
sub istied { return $pwcacheistied }
sub deinit { $pwcacheistied = 0; return; }
sub tiedto {
my $v = shift;
if ( !defined $v ) { # get
return $pwcacheistied ? $pwcachetie : undef;
}
else { # set
$pwcacheistied = 1;
$pwcachetie = $v;
}
return;
}
}
{
my $SYSTEM_CONF_DIR = '/etc';
my $PRODUCT_CONF_DIR = '/var/cpanel';
sub default_conf_dir { return $SYSTEM_CONF_DIR }
sub default_product_dir { return $PRODUCT_CONF_DIR; }
}
1;
} # --- END Cpanel/PwCache/Helpers.pm
{ # --- BEGIN Cpanel/PwCache/Cache.pm
package Cpanel::PwCache::Cache;
use strict;
use warnings;
no warnings 'once';
my %_cache;
my %_homedir_cache;
use constant get_cache => \%_cache;
use constant get_homedir_cache => \%_homedir_cache;
our $pwcache_inited = 0;
my $PWCACHE_IS_SAFE = 1;
sub clear { # clear all
%_cache = ();
%_homedir_cache = ();
$pwcache_inited = 0;
return;
}
sub remove_key {
my ($pwkey) = @_;
return delete $_cache{$pwkey};
}
sub replace {
my $h = shift;
%_cache = %$h if ref $h eq 'HASH';
return;
}
sub is_safe {
$PWCACHE_IS_SAFE = $_[0] if defined $_[0];
return $PWCACHE_IS_SAFE;
}
sub pwmksafecache {
return if $PWCACHE_IS_SAFE;
$_cache{$_}{'contents'}->[1] = 'x' for keys %_cache;
$PWCACHE_IS_SAFE = 1;
return;
}
1;
} # --- END Cpanel/PwCache/Cache.pm
{ # --- BEGIN Cpanel/PwCache/Find.pm
package Cpanel::PwCache::Find;
use strict;
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
our $PW_CHUNK_SIZE = 1 << 17;
sub field_with_value_in_pw_file {
my ( $passwd_fh, $field, $value, $lc_flag ) = @_;
return if ( $value =~ tr{\x{00}-\x{1f}\x{7f}:}{} );
my $needle = $field == 0 ? "\n${value}:" : ":${value}";
my $haystack;
my $match_pos = 0;
my $line_start;
my $line_end;
my $not_eof;
my $data = "\n";
while ( ( $not_eof = Cpanel::LoadFile::ReadFast::read_fast( $passwd_fh, $data, $PW_CHUNK_SIZE, length $data ) ) || length($data) > 1 ) {
$haystack = $not_eof ? substr( $data, 0, rindex( $data, "\n" ), '' ) : $data;
if ( $lc_flag && $lc_flag == 1 ) {
$haystack = lc $haystack;
$needle = lc $needle;
}
while ( -1 < ( $match_pos = index( $haystack, $needle, $match_pos ) ) ) {
$line_start = ( !$field ? $match_pos : rindex( $haystack, "\n", $match_pos ) ) + 1;
if (
!$field || (
$field == ( substr( $haystack, $line_start, $match_pos - $line_start + 1 ) =~ tr{:}{} )
&& ( length($haystack) == $match_pos + length($needle) || substr( $haystack, $match_pos + length($needle), 1 ) =~ tr{:\n}{} )
)
) {
$line_end = index( $haystack, "\n", $match_pos + length($needle) );
my $line = substr( $haystack, $line_start, ( $line_end > -1 ? $line_end : length($haystack) ) - $line_start );
return split( ':', $line );
}
$match_pos += length($needle);
}
last unless $not_eof;
}
return;
}
1;
} # --- END Cpanel/PwCache/Find.pm
{ # --- BEGIN Cpanel/PwCache/Build.pm
package Cpanel::PwCache::Build;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::JSON::FailOK (); # perlpkg line 211
# use Cpanel::FileUtils::Write::JSON::Lazy (); # perlpkg line 211
# use Cpanel::PwCache::Helpers (); # perlpkg line 211
# use Cpanel::PwCache::Cache (); # perlpkg line 211
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
my ( $MIN_FIELDS_FOR_VALID_ENTRY, $pwcache_has_uid_cache ) = ( 0, 6 );
sub pwmksafecache {
return if Cpanel::PwCache::Cache::is_safe();
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
$pwcache_ref->{$_}{'contents'}->[1] = 'x' for keys %{$pwcache_ref};
Cpanel::PwCache::Cache::is_safe(1);
return;
}
sub pwclearcache { # also known as clear_this_process_cache
$pwcache_has_uid_cache = undef;
Cpanel::PwCache::Cache::clear();
return;
}
sub init_pwcache {
Cpanel::PwCache::Cache::is_safe(0);
return _build_pwcache();
}
sub init_passwdless_pwcache {
return _build_pwcache( 'nopasswd' => 1 );
}
sub fetch_pwcache {
init_passwdless_pwcache() unless pwcache_is_initted();
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( scalar keys %$pwcache_ref < 3 ) {
die "The password cache unexpectedly had less than 3 entries";
}
return [ map { $pwcache_ref->{$_}->{'contents'} } grep { substr( $_, 0, 1 ) eq '0' } keys %{$pwcache_ref} ];
}
sub _write_json_cache {
my ($cache_file) = @_;
if ( !Cpanel::PwCache::Helpers::istied() && exists $INC{'Cpanel/JSON.pm'} ) {
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( !ref $pwcache_ref || scalar keys %$pwcache_ref < 3 ) {
die "The system failed build the password cache";
}
Cpanel::FileUtils::Write::JSON::Lazy::write_file( $cache_file, $pwcache_ref, 0600 );
}
return;
}
sub _write_tied_cache {
my ( $crypted_passwd_ref, $passwdmtime, $hpasswdmtime ) = @_;
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
local $!;
if ( open( my $pwcache_passwd_fh, '<:stdio', "$SYSTEM_CONF_DIR/passwd" ) ) {
local $/;
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
my $data = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $pwcache_passwd_fh, $data );
die "The file “$SYSTEM_CONF_DIR/passwd” was unexpectedly empty" if !length $data;
my @fields;
my $skip_uid_cache = Cpanel::PwCache::Helpers::skip_uid_cache();
foreach my $line ( split( /\n/, $data ) ) {
next unless length $line;
@fields = split( /:/, $line );
next if scalar @fields < $MIN_FIELDS_FOR_VALID_ENTRY || $fields[0] =~ tr/[A-Z][a-z][0-9]._-//c;
$pwcache_ref->{ '0:' . $fields[0] } = {
'cachetime' => $passwdmtime,
'hcachetime' => $hpasswdmtime,
'contents' => [ $fields[0], $crypted_passwd_ref->{ $fields[0] } || $fields[1], $fields[2], $fields[3], '', '', $fields[4], $fields[5], $fields[6], -1, -1, $passwdmtime, $hpasswdmtime ]
};
next if $skip_uid_cache || !defined $fields[2] || exists $pwcache_ref->{ '2:' . $fields[2] };
$pwcache_ref->{ '2:' . $fields[2] } = $pwcache_ref->{ '0:' . $fields[0] };
}
close($pwcache_passwd_fh);
}
else {
die "The system failed to read $SYSTEM_CONF_DIR/passwd because of an error: $!";
}
return;
}
sub _cache_ref_is_valid {
my ( $cache_ref, $passwdmtime, $hpasswdmtime ) = @_;
my @keys = qw/0:root 0:cpanel 0:bin/;
return
$cache_ref
&& ( scalar keys %{$cache_ref} ) > 2
&& scalar @keys == grep { #
$cache_ref->{$_}->{'hcachetime'}
&& $cache_ref->{$_}->{'hcachetime'} == $hpasswdmtime
&& $cache_ref->{$_}->{'cachetime'}
&& $cache_ref->{$_}->{'cachetime'} == $passwdmtime
} @keys;
}
sub _build_pwcache {
my %OPTS = @_;
if ( $INC{'B/C.pm'} ) {
Cpanel::PwCache::Helpers::confess("Cpanel::PwCache::Build::_build_pwcache cannot be run under B::C (see case 162857)");
}
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my ( $cache_file, $passwdmtime, $cache_file_mtime, $crypted_passwd_ref, $crypted_passwd_file, $hpasswdmtime ) = ( "$SYSTEM_CONF_DIR/passwd.cache", ( stat("$SYSTEM_CONF_DIR/passwd") )[9] );
if ( $OPTS{'nopasswd'} ) {
$hpasswdmtime = ( stat("$SYSTEM_CONF_DIR/shadow") )[9];
$cache_file = "$SYSTEM_CONF_DIR/passwd" . ( Cpanel::PwCache::Helpers::skip_uid_cache() ? '.nouids' : '' ) . '.cache';
}
elsif ( -r "$SYSTEM_CONF_DIR/shadow" ) {
Cpanel::PwCache::Cache::is_safe(0);
$hpasswdmtime = ( stat(_) )[9];
$crypted_passwd_file = "$SYSTEM_CONF_DIR/shadow";
$cache_file = "$SYSTEM_CONF_DIR/shadow" . ( Cpanel::PwCache::Helpers::skip_uid_cache() ? '.nouids' : '' ) . '.cache';
}
else {
$hpasswdmtime = 0;
}
if ( !Cpanel::PwCache::Helpers::istied() && exists $INC{'Cpanel/JSON.pm'} ) {
if ( open( my $cache_fh, '<:stdio', $cache_file ) ) {
my $cache_file_mtime = ( stat($cache_fh) )[9] || 0;
if ( $cache_file_mtime > $hpasswdmtime && $cache_file_mtime > $passwdmtime ) {
my $cache_ref = Cpanel::JSON::FailOK::LoadFile($cache_fh);
Cpanel::Debug::log_debug("[read pwcache from $cache_file]") if ( $Cpanel::Debug::level > 3 );
if ( _cache_ref_is_valid( $cache_ref, $passwdmtime, $hpasswdmtime ) ) {
Cpanel::Debug::log_debug("[validated pwcache from $cache_file]") if ( $Cpanel::Debug::level > 3 );
my $memory_pwcache_ref = Cpanel::PwCache::Cache::get_cache();
@{$cache_ref}{ keys %$memory_pwcache_ref } = values %$memory_pwcache_ref;
Cpanel::PwCache::Cache::replace($cache_ref);
$Cpanel::PwCache::Cache::pwcache_inited = ( $OPTS{'nopasswd'} ? 1 : 2 );
return;
}
}
}
}
if ($crypted_passwd_file) { $crypted_passwd_ref = _load_pws($crypted_passwd_file); }
$Cpanel::PwCache::Cache::pwcache_inited = ( $OPTS{'nopasswd'} ? 1 : 2 );
$pwcache_has_uid_cache = ( Cpanel::PwCache::Helpers::skip_uid_cache() ? 0 : 1 );
_write_tied_cache( $crypted_passwd_ref, $passwdmtime, $hpasswdmtime );
_write_json_cache($cache_file) if $> == 0;
return 1;
}
sub pwcache_is_initted {
return ( $Cpanel::PwCache::Cache::pwcache_inited ? $Cpanel::PwCache::Cache::pwcache_inited : 0 );
}
sub _load_pws {
my $lookup_file = shift;
if ( $INC{'B/C.pm'} ) {
Cpanel::PwCache::Helpers::confess("Cpanel::PwCache::Build::_load_pws cannot be run under B::C (see case 162857)");
}
my %PW;
if ( open my $lookup_fh, '<:stdio', $lookup_file ) {
my $data = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $lookup_fh, $data );
die "The file “$lookup_file” was unexpectedly empty" if !length $data;
%PW = map { ( split(/:/) )[ 0, 1 ] } split( /\n/, $data );
if ( index( $data, '#' ) > -1 ) {
delete @PW{ '', grep { index( $_, '#' ) == 0 } keys %PW };
}
else {
delete $PW{''};
}
close $lookup_fh;
}
return \%PW;
}
1;
} # --- END Cpanel/PwCache/Build.pm
{ # --- BEGIN Cpanel/PwCache.pm
package Cpanel::PwCache;
use strict;
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::NSCD::Check (); # perlpkg line 211
# use Cpanel::PwCache::Helpers (); # perlpkg line 211
# use Cpanel::PwCache::Cache (); # perlpkg line 211
# use Cpanel::PwCache::Find (); # perlpkg line 211
use constant DUMMY_PW_RETURNS => ( -1, -1, 0, 0 );
use constant DEBUG => 0; # Must set $ENV{'CPANEL_DEBUG_LEVEL'} = 5 as well
our $VERSION = '4.2';
my %FIXED_KEYS = (
'0:root' => 1,
'0:nobody' => 1,
'0:cpanel' => 1,
'0:cpanellogin' => 1,
'0:mail' => 1,
'2:0' => 1,
'2:99' => 1
);
our $_WANT_ENCRYPTED_PASSWORD;
sub getpwnam_noshadow {
$_WANT_ENCRYPTED_PASSWORD = 0;
goto &_getpwnam;
}
sub getpwuid_noshadow {
$_WANT_ENCRYPTED_PASSWORD = 0;
goto &_getpwuid;
}
sub getpwnam {
$_WANT_ENCRYPTED_PASSWORD = !!wantarray;
goto &_getpwnam;
}
sub getpwuid {
$_WANT_ENCRYPTED_PASSWORD = !!wantarray;
goto &_getpwuid;
}
sub gethomedir {
my $uid_or_name = $_[0] // $>;
my $hd = Cpanel::PwCache::Cache::get_homedir_cache();
unless ( exists $hd->{$uid_or_name} ) {
$_WANT_ENCRYPTED_PASSWORD = 0;
if ( $uid_or_name !~ tr{0-9}{}c ) {
$hd->{$uid_or_name} = ( _getpwuid($uid_or_name) )[7];
}
else {
$hd->{$uid_or_name} = ( _getpwnam($uid_or_name) )[7];
}
}
return $hd->{$uid_or_name};
}
sub getusername {
my $uid = defined $_[0] ? $_[0] : $>;
$_WANT_ENCRYPTED_PASSWORD = 0;
return scalar _getpwuid($uid);
}
sub init_passwdless_pwcache {
require Cpanel::PwCache::Build;
*init_passwdless_pwcache = \&Cpanel::PwCache::Build::init_passwdless_pwcache;
goto &Cpanel::PwCache::Build::init_passwdless_pwcache;
}
sub _getpwuid { ## no critic qw(Subroutines::RequireArgUnpacking)
return unless ( length( $_[0] ) && $_[0] !~ tr/0-9//c );
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( !exists $pwcache_ref->{"2:$_[0]"} && $> != 0 && !Cpanel::PwCache::Helpers::istied() && Cpanel::NSCD::Check::nscd_is_running() ) {
return CORE::getpwuid( $_[0] ) if !wantarray;
my @ret = CORE::getpwuid( $_[0] );
return @ret ? ( @ret, DUMMY_PW_RETURNS() ) : ();
}
if ( my $pwref = _pwfunc( $_[0], 2 ) ) {
return wantarray ? @$pwref : $pwref->[0];
}
return; #important not to return 0
}
sub _getpwnam { ## no critic qw(Subroutines::RequireArgUnpacking)
return unless ( length( $_[0] ) && $_[0] !~ tr{\x{00}-\x{20}\x{7f}:/#}{} );
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( !exists $pwcache_ref->{"0:$_[0]"} && $> != 0 && !Cpanel::PwCache::Helpers::istied() && Cpanel::NSCD::Check::nscd_is_running() ) {
return CORE::getpwnam( $_[0] ) if !wantarray;
my @ret = CORE::getpwnam( $_[0] );
return @ret ? ( @ret, DUMMY_PW_RETURNS() ) : ();
}
if ( my $pwref = _pwfunc( $_[0], 0 ) ) {
return wantarray ? @$pwref : $pwref->[2];
}
return; #important not to return 0
}
sub _pwfunc { ## no critic qw(Subroutines::RequireArgUnpacking)
my ( $value, $field, $pwkey ) = ( $_[0], ( $_[1] || 0 ), $_[1] . ':' . ( $_[0] || 0 ) );
if ( Cpanel::PwCache::Helpers::istied() ) {
Cpanel::Debug::log_debug("cache tie (tied) value[$value] field[$field]") if (DEBUG);
my $pwcachetie = Cpanel::PwCache::Helpers::tiedto();
if ( ref $pwcachetie eq 'HASH' ) {
my $cache = $pwcachetie->{$pwkey};
if ( ref $cache eq 'HASH' ) {
return $pwcachetie->{$pwkey}->{'contents'};
}
}
return undef;
}
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my $lookup_encrypted_pass = 0;
if ($_WANT_ENCRYPTED_PASSWORD) {
$lookup_encrypted_pass = $> == 0 ? 1 : 0;
}
my ( $passwdmtime, $hpasswdmtime );
my $pwcache_ref = Cpanel::PwCache::Cache::get_cache();
if ( my $cache_entry = $pwcache_ref->{$pwkey} ) {
Cpanel::Debug::log_debug("exists in cache value[$value] field[$field]") if (DEBUG);
if (
( exists( $cache_entry->{'contents'} ) && $cache_entry->{'contents'}->[1] ne 'x' ) # Has shadow entry
|| !$lookup_encrypted_pass # Or we do not need it
) { # If we are root and missing the password field we could fail authentication
if ( $FIXED_KEYS{$pwkey} ) { # We assume root, nobody, and cpanellogin will never change during execution
Cpanel::Debug::log_debug("cache (never change) hit value[$value] field[$field]") if (DEBUG);
return $cache_entry->{'contents'};
}
$passwdmtime = ( stat("$SYSTEM_CONF_DIR/passwd") )[9];
$hpasswdmtime = $lookup_encrypted_pass ? ( stat("$SYSTEM_CONF_DIR/shadow") )[9] : 0;
if ( ( $lookup_encrypted_pass && $hpasswdmtime && $hpasswdmtime != $cache_entry->{'hcachetime'} )
|| ( $passwdmtime && $passwdmtime != $cache_entry->{'cachetime'} ) ) { #timewarp safe
DEBUG && Cpanel::Debug::log_debug( "cache miss value[$value] field[$field] pwkey[$pwkey] " . qq{hpasswdmtime: $hpasswdmtime != $cache_entry->{hcachetime} } . qq{passwdmtime: $passwdmtime != $cache_entry->{cachetime} } );
if ( defined $cache_entry && defined $cache_entry->{'contents'} ) {
Cpanel::PwCache::Cache::clear(); #If the passwd file mtime changes everything is invalid
}
}
else {
Cpanel::Debug::log_debug("cache hit value[$value] field[$field]") if (DEBUG);
return $cache_entry->{'contents'};
}
}
elsif (DEBUG) {
Cpanel::Debug::log_debug( "cache miss pwkey[$pwkey] value[$value] field[$field] passwdmtime[$passwdmtime] pwcacheistied[" . Cpanel::PwCache::Helpers::istied() . "] hpasswdmtime[$hpasswdmtime]" );
}
}
elsif (DEBUG) {
Cpanel::Debug::log_debug( "cache miss (no entry) pwkey[$pwkey] value[$value] field[$field] pwcacheistied[" . Cpanel::PwCache::Helpers::istied() . "]" );
}
my $pwdata = _getpwdata( $value, $field, $passwdmtime, $hpasswdmtime, $lookup_encrypted_pass );
_cache_pwdata( $pwdata, $pwcache_ref ) if $pwdata && @$pwdata;
return $pwdata;
}
sub _getpwdata {
my ( $value, $field, $passwdmtime, $shadowmtime, $lookup_encrypted_pass ) = @_;
return if ( !defined $value || !defined $field || $value =~ tr/\0// );
if ($lookup_encrypted_pass) {
return [ _readshadow( $value, $field, $passwdmtime, $shadowmtime ) ];
}
return [ _readpasswd( $value, $field, $passwdmtime, $shadowmtime ) ];
}
sub _readshadow { ## no critic qw(Subroutines::RequireArgUnpacking)
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my ( $value, $field, $passwdmtime, $shadowmtime ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat("$SYSTEM_CONF_DIR/passwd") )[9] ), ( $_[3] || ( stat("$SYSTEM_CONF_DIR/shadow") )[9] ) );
my @PW = _readpasswd( $value, $field, $passwdmtime, $shadowmtime );
return if !@PW;
$value = $PW[0];
if ( open my $shadow_fh, '<', "$SYSTEM_CONF_DIR/shadow" ) {
if ( my @SH = Cpanel::PwCache::Find::field_with_value_in_pw_file( $shadow_fh, 0, $value ) ) {
( $PW[1], $PW[9], $PW[10], $PW[11], $PW[12] ) = (
$SH[1], #encrypted pass
$SH[5], #expire time
$SH[2], #change time
$passwdmtime,
$shadowmtime
);
close $shadow_fh;
Cpanel::PwCache::Cache::is_safe(0);
return @PW;
}
}
else {
Cpanel::PwCache::Helpers::cluck("Unable to open $SYSTEM_CONF_DIR/shadow: $!");
}
Cpanel::PwCache::Helpers::cluck("Entry for $value missing in $SYSTEM_CONF_DIR/shadow");
return @PW;
}
sub _readpasswd { ## no critic qw(Subroutines::RequireArgUnpacking)
my $SYSTEM_CONF_DIR = Cpanel::PwCache::Helpers::default_conf_dir();
my ( $value, $field, $passwdmtime, $shadowmtime, $block ) = ( $_[0], ( $_[1] || 0 ), ( $_[2] || ( stat("$SYSTEM_CONF_DIR/passwd") )[9] ), $_[3] );
if ( $INC{'B/C.pm'} ) {
die("Cpanel::PwCache::_readpasswd cannot be run under B::C (see case 162857)");
}
if ( open( my $passwd_fh, '<', "$SYSTEM_CONF_DIR/passwd" ) ) {
if ( my @PW = Cpanel::PwCache::Find::field_with_value_in_pw_file( $passwd_fh, $field, $value ) ) {
return ( $PW[0], $PW[1], $PW[2], $PW[3], '', '', $PW[4], $PW[5], $PW[6], -1, -1, $passwdmtime, ( $shadowmtime || $passwdmtime ) );
}
close($passwd_fh);
}
else {
Cpanel::PwCache::Helpers::cluck("open($SYSTEM_CONF_DIR/passwd): $!");
}
return;
}
sub _cache_pwdata {
my ( $pwdata, $pwcache_ref ) = @_;
$pwcache_ref ||= Cpanel::PwCache::Cache::get_cache();
if ( $pwdata->[2] != 0 || $pwdata->[0] eq 'root' ) { # special case for multiple uid 0 users
@{ $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 1;
}
1;
} # --- END Cpanel/PwCache.pm
{ # --- BEGIN Cpanel/SafeDir/MK.pm
package Cpanel::SafeDir::MK;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Debug (); # perlpkg line 211
my $DEFAULT_PERMISSIONS = 0755;
sub safemkdir_or_die {
my ( $dir, $mode, $created ) = @_;
my $ok = safemkdir( $dir, $mode, $created );
if ( !$ok ) {
my $error = $!;
require Cpanel::Exception;
die Cpanel::Exception::create(
'IO::DirectoryCreateError',
[
path => $dir,
error => $error,
]
);
}
return $ok;
}
sub safemkdir { ## no critic(Subroutines::ProhibitExcessComplexity) -- Refactoring this function is a project, not a bug fix
my ( $dir, $mode, $errors, $created ) = @_;
if ( defined $mode ) {
if ( $mode eq '' ) {
$mode = undef;
}
elsif ( index( $mode, '0' ) == 0 ) {
if ( length $mode < 3 || $mode =~ tr{0-7}{}c || !defined oct $mode ) {
$mode = $DEFAULT_PERMISSIONS;
}
else {
$mode = oct($mode);
}
}
elsif ( $mode =~ tr{0-9}{}c ) {
$mode = $DEFAULT_PERMISSIONS;
}
}
$dir =~ tr{/}{}s;
my $default = '';
if ( index( $dir, '/' ) == 0 ) {
$default = '/';
}
elsif ( $dir eq '.' || $dir eq './' ) {
if ( !-l $dir && defined $mode ) {
return chmod $mode, $dir;
}
return 1;
}
else {
substr( $dir, 0, 2, '' ) if index( $dir, './' ) == 0;
}
if ( _has_dot_dot($dir) ) {
Cpanel::Debug::log_warn("Possible improper directory $dir specified");
my @dir_parts = split m{/}, $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::Debug::log_warn("Will not proceed above first directory part $first");
return 0;
}
if ( $first eq $good_parts[$#good_parts] ) {
undef $first;
}
pop @good_parts;
next;
}
elsif ( $part !~ tr{.}{}c ) {
Cpanel::Debug::log_warn("Total stupidity found in directory $dir");
return 0;
}
push @good_parts, $part;
if ( !$first ) { $first = $part }
}
$dir = $default . join '/', @good_parts;
if ( !$dir ) {
Cpanel::Debug::log_warn("Could not validate given directory");
return;
}
Cpanel::Debug::log_warn("Improper directory updated to $dir");
}
if ( -d $dir ) {
if ( !-l $dir && defined $mode ) {
return chmod $mode, $dir;
}
return 1;
}
elsif ( -e _ ) {
Cpanel::Debug::log_warn("$dir was expected to be a directory!");
require Errno;
$! = Errno::ENOTDIR(); ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- for legacy reasons
return 0;
}
my @dir_parts = split m{/}, $dir;
if ( scalar @dir_parts > 100 ) {
Cpanel::Debug::log_warn("Encountered excessive directory length. This should never happen.");
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 = defined $mode ? $mode : $DEFAULT_PERMISSIONS;
if ( mkdir( $newdir, $local_mode ) ) {
push @{$created}, $newdir if $created;
$returnvalue++;
}
else {
Cpanel::Debug::log_warn("mkdir $newdir failed: $!");
return;
}
}
elsif ( !$is_dir ) {
Cpanel::Debug::log_warn("Encountered non-directory $newdir in path of $dir: $!");
require Errno;
$! = Errno::ENOTDIR(); ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- for legacy reasons
last;
}
}
return $returnvalue;
}
sub _has_dot_dot { ## no critic qw(RequireArgUnpacking)
return 1 if $_[0] eq '..';
return 1 if -1 != index( $_[0], '/../' );
return 1 if 0 == index( $_[0], '../' );
return 1 if ( length( $_[0] ) - 3 ) == rindex( $_[0], '/..' );
return 0;
}
1;
} # --- END Cpanel/SafeDir/MK.pm
{ # --- BEGIN Cpanel/CachedCommand/Utils.pm
package Cpanel::CachedCommand::Utils;
# use Cpanel::SV (); # perlpkg line 211
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;
}
*get_datastore_filename = *_get_datastore_filename;
sub _get_datastore_filename {
my ( $bin, @args ) = @_;
my $file = join( '_', $bin, @args );
$file =~ tr{/}{_};
Cpanel::SV::untaint($file);
my $datastore_dir = _get_datastore_dir($file);
Cpanel::SV::untaint($datastore_dir);
return $datastore_dir . '/' . $file;
}
sub _get_datastore_dir {
my $file = shift;
my $myuid = $>;
if ( defined $cached_datastore_dir && length $cached_datastore_dir > 1 && defined $cached_datastore_myuid && $myuid == $cached_datastore_myuid ) {
my $homedir = Cpanel::PwCache::gethomedir();
$cached_datastore_dir = "$homedir/$ENV{'TEAM_USER'}/.cpanel/datastore" if $ENV{'TEAM_USER'} && $file =~ /^AVAILABLE_APPLICATIONS_CACHE/;
return $cached_datastore_dir;
}
require Cpanel::PwCache;
$cached_datastore_dir = Cpanel::SV::untaint( Cpanel::PwCache::gethomedir() );
$cached_datastore_dir .= "/$ENV{'TEAM_USER'}" if $ENV{'TEAM_USER'} && $file =~ /^AVAILABLE_APPLICATIONS_CACHE/;
if ( !-e $cached_datastore_dir . '/.cpanel/datastore' && $cached_datastore_dir ne '/' ) { # nobody's homedir is /
require Cpanel::SafeDir::MK;
Cpanel::SafeDir::MK::safemkdir( "$cached_datastore_dir/.cpanel/datastore", 0700 ) or warn "Failed to mkdir($cached_datastore_dir/.cpanel/datastore): $!";
}
$cached_datastore_myuid = $myuid;
$cached_datastore_dir .= '/.cpanel/datastore';
return $cached_datastore_dir;
}
sub invalidate_cache {
my $ds_file = get_datastore_filename(@_);
unlink $ds_file;
return $ds_file;
}
sub clearcache {
$cached_datastore_dir = undef;
$cached_datastore_myuid = undef;
return;
}
1;
} # --- END Cpanel/CachedCommand/Utils.pm
{ # --- BEGIN Cpanel/FindBin.pm
package Cpanel::FindBin;
use strict;
use warnings;
no warnings 'once';
use constant _ENOENT => 2;
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 { ## no critic qw(Subroutines::RequireArgUnpacking)
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) {
next unless -d $path;
$path .= "/$binname";
if ( -e $path ) {
if ( -x _ ) {
$bin_cache{$binname} = $path unless $nocache;
return $path;
}
else {
warn "“$path” exists but is not executable; ignoring.\n";
}
}
elsif ( $! != _ENOENT() ) {
warn "stat($path): $!\n";
}
}
return;
}
sub get_path {
if ( !$_[0] ) {
return @default_path;
}
elsif ( scalar @_ > 1 ) {
my %opts;
%opts = @_ if ( scalar @_ % 2 == 0 );
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/CachedCommand/Valid.pm
package Cpanel::CachedCommand::Valid;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::StatCache (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
sub is_cache_valid { ## no critic qw(Subroutines::ProhibitExcessComplexity) -- needs to be refactored
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_mtime <= 0 ) {
print STDERR "is_cache_valid: rejecting $datastore_file as mtime is zero.\n" if $Cpanel::Debug::level;
return 0;
}
if ($binary) {
if ( substr( $binary, 0, 1 ) ne '/' ) {
require Cpanel::FindBin;
$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 ) ) {
if ($Cpanel::Debug::level) {
print STDERR "is_cache_valid: rejecting $datastore_file as binary ($binary) ctime or mtime is newer.\n";
print STDERR "is_cache_valid: datastore_file:$datastore_file mtime[$datastore_file_mtime]\n";
print STDERR "is_cache_valid: binary_file:$binary mtime[$binary_mtime] ctime[$binary_ctime]\n";
}
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;
}
1;
} # --- END Cpanel/CachedCommand/Valid.pm
{ # --- BEGIN Cpanel/CachedCommand/Save.pm
package Cpanel::CachedCommand::Save;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::CachedCommand::Utils (); # perlpkg line 211
# use Cpanel::FileUtils::Write (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
use Try::Tiny;
sub _savefile {
my ( $filename, $content ) = @_;
return if !defined $content; #should be able to store 0
$filename =~ tr{/}{}s; # collapse //s to /
my @path = split( /\//, $filename );
my $file = pop(@path);
my $dir = join( '/', @path );
my $dir_uid = ( stat($dir) )[4];
if ( !defined $dir_uid ) {
Cpanel::Debug::log_warn("Unable to write datastore file: $filename: target directory: $dir does not exist.");
return;
}
elsif ( $dir_uid != $> ) {
Cpanel::Debug::log_warn("Unable to write datastore file: $filename: target directory: $dir does not match uid $>");
return;
}
local $!;
my $ret;
try {
$ret = Cpanel::FileUtils::Write::overwrite( $filename, ( ref $content ? $$content : $content ), 0600 );
}
catch {
my $err = $_;
Cpanel::Debug::log_warn( Cpanel::Exception::get_string($err) );
};
return $ret;
}
sub store {
my %OPTS = @_;
_savefile( Cpanel::CachedCommand::Utils::_get_datastore_filename( $OPTS{'name'} ), $OPTS{'data'} );
}
1;
} # --- END Cpanel/CachedCommand/Save.pm
{ # --- BEGIN Cpanel/LocaleString.pm
package Cpanel::LocaleString;
use strict;
use warnings;
no warnings 'once';
sub DESTROY { }
sub new {
if ( !length $_[1] ) {
die 'Must include at least a string!';
}
return bless \@_, shift;
}
sub set_json_to_freeze {
no warnings 'redefine';
*TO_JSON = \&_to_list_ref;
return ( __PACKAGE__ . '::_JSON_MODE' )->new();
}
sub thaw {
if ( ref( $_[1] ) ne 'ARRAY' ) {
die "Call thaw() on an ARRAY reference, not “$_[1]”!";
}
return $_[0]->new( @{ $_[1] }[ 1 .. $#{ $_[1] } ] );
}
sub is_frozen {
{
last if ref( $_[1] ) ne 'ARRAY';
last if !$_[1][0]->isa( $_[0] );
last if @{ $_[1] } < 2;
return 1;
}
return 0;
}
sub to_string {
return _locale()->makevar( @{ $_[0] } );
}
sub to_en_string {
return _locale()->makethis_base( @{ $_[0] } );
}
sub clone_with_args {
return ( ref $_[0] )->new(
$_[0][0], #the phrase, currently stored in the object
@_[ 1 .. $#_ ], #the new args, supplied by the caller
);
}
sub to_list {
if ( !wantarray ) {
require Cpanel::Context;
Cpanel::Context::must_be_list();
}
return @{ $_[0] };
}
*TO_JSON = \&to_string;
my $_locale;
sub _locale {
return $_locale if $_locale;
local $@;
eval 'require Cpanel::Locale;' or do { ## no critic qw(BuiltinFunctions::ProhibitStringyEval)
warn "Failed to load Cpanel::Locale; falling back to substitute. Error was: $@";
};
eval { $_locale = Cpanel::Locale->get_handle() };
return $_locale || bless {}, 'Cpanel::LocaleString::_Cpanel_Locale_unavailable';
}
sub _put_back {
no warnings 'redefine';
*TO_JSON = \&to_string;
return;
}
sub _to_list_ref {
return [ ref( $_[0] ), @{ $_[0] } ];
}
package Cpanel::LocaleString::_JSON_MODE;
sub new {
require Cpanel::Finally; # PPI USE OK - loaded only when needed
return $_[0]->SUPER::new( \&Cpanel::LocaleString::_put_back );
}
package Cpanel::LocaleString::_Cpanel_Locale_unavailable;
BEGIN {
*Cpanel::LocaleString::_Cpanel_Locale_unavailable::makethis_base = *Cpanel::LocaleString::_Cpanel_Locale_unavailable::makevar;
}
sub makevar {
my ( $self, $str, @maketext_opts ) = @_;
local ( $@, $! );
require Cpanel::Locale::Utils::Fallback;
return Cpanel::Locale::Utils::Fallback::interpolate_variables( $str, @maketext_opts );
}
1;
} # --- END Cpanel/LocaleString.pm
{ # --- BEGIN Cpanel/Errno.pm
package Cpanel::Errno;
use strict;
my %_err_name_cache;
sub get_name_for_errno_number {
my ($number) = @_;
if ( !$INC{'Errno.pm'} ) {
local ( $@, $! );
require Errno;
}
die 'need number!' if !length $number;
if ( !%_err_name_cache ) {
my $s = scalar keys %Errno::; # init iterator
foreach my $k ( sort keys %Errno:: ) {
if ( Errno->EXISTS($k) ) {
my $v = 'Errno'->can($k)->();
$_err_name_cache{$v} = $k;
}
}
}
return $_err_name_cache{$number};
}
1;
} # --- END Cpanel/Errno.pm
{ # --- BEGIN Cpanel/Config/Constants/Perl.pm
package Cpanel::Config::Constants::Perl;
use strict;
our $ABRT = 6;
our $ALRM = 14;
our $BUS = 7;
our $CHLD = 17;
our $CLD = 17;
our $CONT = 18;
our $FPE = 8;
our $HUP = 1;
our $ILL = 4;
our $INT = 2;
our $IO = 29;
our $IOT = 6;
our $KILL = 9;
our $NUM32 = 32;
our $NUM33 = 33;
our $NUM35 = 35;
our $NUM36 = 36;
our $NUM37 = 37;
our $NUM38 = 38;
our $NUM39 = 39;
our $NUM40 = 40;
our $NUM41 = 41;
our $NUM42 = 42;
our $NUM43 = 43;
our $NUM44 = 44;
our $NUM45 = 45;
our $NUM46 = 46;
our $NUM47 = 47;
our $NUM48 = 48;
our $NUM49 = 49;
our $NUM50 = 50;
our $NUM51 = 51;
our $NUM52 = 52;
our $NUM53 = 53;
our $NUM54 = 54;
our $NUM55 = 55;
our $NUM56 = 56;
our $NUM57 = 57;
our $NUM58 = 58;
our $NUM59 = 59;
our $NUM60 = 60;
our $NUM61 = 61;
our $NUM62 = 62;
our $NUM63 = 63;
our $PIPE = 13;
our $POLL = 29;
our $PROF = 27;
our $PWR = 30;
our $QUIT = 3;
our $RTMAX = 64;
our $RTMIN = 34;
our $SEGV = 11;
our $STKFLT = 16;
our $STOP = 19;
our $SYS = 31;
our $TERM = 15;
our $TRAP = 5;
our $TSTP = 20;
our $TTIN = 21;
our $TTOU = 22;
our $UNUSED = 31;
our $URG = 23;
our $USR1 = 10;
our $USR2 = 12;
our $VTALRM = 26;
our $WINCH = 28;
our $XCPU = 24;
our $XFSZ = 25;
our $ZERO = 0;
our %SIGNAL_NAME = qw(
0 ZERO
1 HUP
10 USR1
11 SEGV
12 USR2
13 PIPE
14 ALRM
15 TERM
16 STKFLT
17 CHLD
18 CONT
19 STOP
2 INT
20 TSTP
21 TTIN
22 TTOU
23 URG
24 XCPU
25 XFSZ
26 VTALRM
27 PROF
28 WINCH
29 IO
3 QUIT
30 PWR
31 SYS
32 NUM32
33 NUM33
34 RTMIN
35 NUM35
36 NUM36
37 NUM37
38 NUM38
39 NUM39
4 ILL
40 NUM40
41 NUM41
42 NUM42
43 NUM43
44 NUM44
45 NUM45
46 NUM46
47 NUM47
48 NUM48
49 NUM49
5 TRAP
50 NUM50
51 NUM51
52 NUM52
53 NUM53
54 NUM54
55 NUM55
56 NUM56
57 NUM57
58 NUM58
59 NUM59
6 ABRT
60 NUM60
61 NUM61
62 NUM62
63 NUM63
64 RTMAX
7 BUS
8 FPE
9 KILL
);
1;
} # --- END Cpanel/Config/Constants/Perl.pm
{ # --- BEGIN Cpanel/ChildErrorStringifier.pm
package Cpanel::ChildErrorStringifier;
use strict;
# use Cpanel::LocaleString (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
sub new {
my ( $class, $CHILD_ERROR, $PROGRAM_NAME ) = @_;
return bless { _CHILD_ERROR => $CHILD_ERROR, _PROGRAM_NAME => $PROGRAM_NAME }, $class;
}
sub CHILD_ERROR {
my ($self) = @_;
return $self->{'_CHILD_ERROR'};
}
sub error_code {
my ($self) = @_;
return undef if !$self->CHILD_ERROR();
return $self->CHILD_ERROR() >> 8;
}
sub error_name {
my ($self) = @_;
my $error_number = $self->error_code();
return '' if ( !defined $error_number ); # Can't index a hash with undef
require Cpanel::Errno;
return Cpanel::Errno::get_name_for_errno_number($error_number) || q<>;
}
sub dumped_core {
my ($self) = @_;
return $self->CHILD_ERROR() && ( $self->CHILD_ERROR() & 128 ) ? 1 : 0;
}
sub signal_code {
my ($self) = @_;
return if !$self->CHILD_ERROR();
return $self->CHILD_ERROR() & 127;
}
sub signal_name {
my ($self) = @_;
require Cpanel::Config::Constants::Perl;
return $Cpanel::Config::Constants::Perl::SIGNAL_NAME{ $self->signal_code() };
}
sub exec_failed {
return $_[0]->{'_exec_failed'} ? 1 : 0;
}
sub program {
my ($self) = @_;
return $self->{'_PROGRAM_NAME'} || undef;
}
sub set_program {
my ( $self, $program ) = @_;
return ( $self->{'_PROGRAM_NAME'} = $program );
}
sub autopsy {
my ($self) = @_;
return undef if !$self->CHILD_ERROR();
my @localized_strings = (
$self->error_code() ? $self->_ERROR_PHRASE() : $self->_SIGNAL_PHRASE(),
$self->_core_dump_for_phrase_if_needed(),
$self->_additional_phrases_for_autopsy(),
);
return join ' ', map { $_->to_string() } @localized_strings;
}
sub terse_autopsy {
my ($self) = @_;
my $str;
if ( $self->signal_code() ) {
$str .= 'SIG' . $self->signal_name() . " (#" . $self->signal_code() . ")";
}
elsif ( my $code = $self->error_code() ) {
$str .= "exit $code";
}
else {
$str = 'OK';
}
if ( $self->dumped_core() ) {
$str .= ' (+core)';
}
return $str;
}
sub die_if_error {
my ($self) = @_;
my $err = $self->to_exception();
die $err if $err;
return $self;
}
sub to_exception {
my ($self) = @_;
if ( $self->signal_code() ) {
return Cpanel::Exception::create(
'ProcessFailed::Signal',
[
process_name => $self->program(),
signal_code => $self->signal_code(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
if ( $self->error_code() ) {
return Cpanel::Exception::create(
'ProcessFailed::Error',
[
process_name => $self->program(),
error_code => $self->error_code(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
return undef;
}
sub _extra_error_args_for_die_if_error { }
sub _additional_phrases_for_autopsy { }
sub _core_dump_for_phrase_if_needed {
my ($self) = @_;
if ( $self->dumped_core() ) {
return Cpanel::LocaleString->new('The process dumped a core file.');
}
return;
}
sub _ERROR_PHRASE {
my ($self) = @_;
if ( $self->program() ) {
return Cpanel::LocaleString->new( 'The subprocess “[_1]” reported error number [numf,_2] when it ended.', $self->program(), $self->error_code() );
}
return Cpanel::LocaleString->new( 'The subprocess reported error number [numf,_1] when it ended.', $self->error_code() );
}
sub _SIGNAL_PHRASE {
my ($self) = @_;
if ( $self->program() ) {
return Cpanel::LocaleString->new( 'The subprocess “[_1]” ended prematurely because it received the “[_2]” ([_3]) signal.', $self->program(), $self->signal_name(), $self->signal_code() );
}
return Cpanel::LocaleString->new( 'The subprocess ended prematurely because it received the “[_1]” ([_2]) signal.', $self->signal_name(), $self->signal_code() );
}
1;
} # --- END Cpanel/ChildErrorStringifier.pm
{ # --- BEGIN Cpanel/Env.pm
package Cpanel::Env;
use strict;
use warnings;
no warnings 'once';
our $VERSION = '1.7';
my $SAFE_ENV_VARS;
BEGIN {
$SAFE_ENV_VARS = q<
ALLUSERSPROFILE
APPDATA
BUNDLE_PATH
CLIENTNAME
COMMONPROGRAMFILES
COMPUTERNAME
COMSPEC
CPANEL_BASE_INSTALL
CPANEL_IS_CRON
CPANEL_RPM_LOCKED_IN_PARENT
CPBACKUP
DEBIAN_FRONTEND
DEBIAN_PRIORITY
DOCUMENT_ROOT
FORCEDCPUPDATE
FP_NO_HOST_CHECK
HOMEDRIVE
HOMEPATH
LANG
LANGUAGE
LC_ALL
LC_MESSAGES
LC_CTYPE
LOGONSERVER
NEWWHMUPDATE
NOTIFY_SOCKET
NUMBER_OF_PROCESSORS
OPENSSL_NO_DEFAULT_ZLIB
OS
PATH
PATHEXT
PROCESSOR_ARCHITECTURE
PROCESSOR_IDENTIFIER
PROCESSOR_LEVEL
PROCESSOR_REVISION
PROGRAMFILES
PROMPT
PYTHONIOENCODING
SERVER_SOFTWARE
SESSIONNAME
SKIP_DEFERRAL_CHECK
SSH_CLIENT
SYSTEMDRIVE
SYSTEMROOT
TEMP
TERM
TMP
UPDATENOW_NO_RETRY
UPDATENOW_PRESERVE_FAILED_FILES
USERDOMAIN
USERNAME
USERPROFILE
WINDIR
>;
$SAFE_ENV_VARS =~ tr<\n >< >s;
$SAFE_ENV_VARS =~ s<\A\s+><>;
}
{
no warnings 'once';
*cleanenv = *clean_env;
}
sub clean_env {
my %OPTS = @_;
my %SAFE_ENV_VARS = map { $_ => undef } split( m{ }, $SAFE_ENV_VARS );
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{ grep { !exists $SAFE_ENV_VARS{$_} } keys %ENV };
if ( $OPTS{'http_purge'} ) {
delete @ENV{ 'SERVER_SOFTWARE', 'DOCUMENT_ROOT' };
}
return;
}
sub get_safe_env_vars {
return $SAFE_ENV_VARS;
}
sub get_safe_path {
return '/usr/local/jdk/bin:/usr/kerberos/sbin:/usr/kerberos/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';
}
sub set_safe_path {
return ( $ENV{'PATH'} = get_safe_path() );
}
1;
} # --- END Cpanel/Env.pm
{ # --- BEGIN Cpanel/FHUtils/Autoflush.pm
package Cpanel::FHUtils::Autoflush;
use strict;
use warnings;
no warnings 'once';
sub enable {
select( ( select( $_[0] ), $| = 1 )[0] ); ## no critic qw(InputOutput::ProhibitOneArgSelect Variables::RequireLocalizedPunctuationVars) - aka $socket->autoflush(1) without importing IO::Socket
return;
}
1;
} # --- END Cpanel/FHUtils/Autoflush.pm
{ # --- BEGIN Cpanel/FHUtils/OS.pm
package Cpanel::FHUtils::OS;
use strict;
use warnings;
no warnings 'once';
my $fileno;
sub is_os_filehandle {
local $@;
$fileno = eval { fileno $_[0] };
return ( defined $fileno ) && ( $fileno != -1 );
}
1;
} # --- END Cpanel/FHUtils/OS.pm
{ # --- BEGIN Cpanel/FHUtils/Blocking.pm
package Cpanel::FHUtils::Blocking;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Fcntl::Constants (); # perlpkg line 211
# use Cpanel::Autodie qw(fcntl); # perlpkg line 248
INIT { Cpanel::Autodie->import(qw{fcntl}); }
sub set_non_blocking {
return Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_SETFL, _get_fl_flags( $_[0] ) | $Cpanel::Fcntl::Constants::O_NONBLOCK ) && 1;
}
sub set_blocking {
return Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_SETFL, _get_fl_flags( $_[0] ) & ~$Cpanel::Fcntl::Constants::O_NONBLOCK ) && 1;
}
sub is_set_to_block {
return !( _get_fl_flags( $_[0] ) & $Cpanel::Fcntl::Constants::O_NONBLOCK ) ? 1 : 0;
}
sub _get_fl_flags {
return int Cpanel::Autodie::fcntl( $_[0], $Cpanel::Fcntl::Constants::F_GETFL, 0 );
}
1;
} # --- END Cpanel/FHUtils/Blocking.pm
{ # --- BEGIN Cpanel/IO/Flush.pm
package Cpanel::IO::Flush;
use strict;
use warnings;
no warnings 'once';
use constant {
_EAGAIN => 11,
_EINTR => 4,
};
# use Cpanel::Exception (); # perlpkg line 211
use IO::SigGuard ();
sub write_all { ##no critic qw( RequireArgUnpacking )
my ( $fh, $timeout ) = @_; # $_[2] = payload
local ( $!, $^E );
my $offset = 0;
{
my $this_time = IO::SigGuard::syswrite( $fh, $_[2], length( $_[2] ), $offset );
if ($this_time) {
$offset += $this_time;
}
elsif ( $! == _EAGAIN() ) {
_wait_until_ready( $fh, $timeout );
}
else {
die Cpanel::Exception::create( 'IO::WriteError', [ error => $!, length => length( $_[2] ) - $offset ] );
}
redo if $offset < length( $_[2] );
}
return;
}
sub _wait_until_ready {
my ( $fh, $timeout ) = @_;
my $win;
vec( $win, fileno($fh), 1 ) = 1;
my $ready = select( undef, my $wout = $win, undef, $timeout );
if ( $ready == -1 ) {
redo if $! == _EINTR();
die Cpanel::Exception::create( 'IO::SelectError', [ error => $! ] );
}
elsif ( !$ready ) {
die Cpanel::Exception::create_raw( 'Timeout', 'write timeout!' );
}
return;
}
1;
} # --- END Cpanel/IO/Flush.pm
{ # --- BEGIN Cpanel/ReadMultipleFH.pm
package Cpanel::ReadMultipleFH;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::FHUtils::Blocking (); # perlpkg line 211
# use Cpanel::FHUtils::OS (); # perlpkg line 211
# use Cpanel::IO::Flush (); # perlpkg line 211
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
my $CHUNK_SIZE = 2 << 16;
my $DEFAULT_TIMEOUT = 600; #10 minutes
my $DEFAULT_READ_TIMEOUT = 0;
sub new { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $class, %opts ) = @_;
my %fh_buffer;
my %output;
my @fhs = @{ $opts{'filehandles'} };
my $read_input = '';
my $read_output = '';
my %fhmap;
my %is_os_filehandle;
for my $fh_buf_ar (@fhs) {
if ( UNIVERSAL::isa( $fh_buf_ar, 'GLOB' ) ) {
$fh_buf_ar = [$fh_buf_ar];
}
elsif ( !UNIVERSAL::isa( $fh_buf_ar, 'ARRAY' ) ) {
die 'items in “filehandles” must be either a filehandle or ARRAY';
}
my $fh = $fh_buf_ar->[0];
Cpanel::FHUtils::Blocking::set_non_blocking($fh);
$fhmap{ fileno($fh) } = $fh;
vec( $read_input, fileno($fh), 1 ) = 1;
if ( defined $fh_buf_ar->[1] && UNIVERSAL::isa( $fh_buf_ar->[1], 'SCALAR' ) ) {
$fh_buffer{$fh} = $fh_buf_ar->[1];
}
else {
my $buf = q{};
$fh_buffer{$fh} = \$buf;
if ( defined $fh_buf_ar->[1] && UNIVERSAL::isa( $fh_buf_ar->[1], 'GLOB' ) ) {
$output{$fh} = $fh_buf_ar->[1];
$is_os_filehandle{$fh} = Cpanel::FHUtils::OS::is_os_filehandle( $fh_buf_ar->[1] );
}
elsif ( defined $fh_buf_ar->[1] ) {
die '2nd value in “filehandles” array member must be undef, SCALAR, or GLOB!';
}
}
}
my $finished;
my $self = {
_fh_buffer => \%fh_buffer,
_finished => 0,
};
bless $self, $class;
my ( $nfound, $select_time_left, $select_timeout );
my $overall_timeout = defined $opts{'timeout'} ? $opts{'timeout'} : $DEFAULT_TIMEOUT;
my $read_timeout = defined $opts{'read_timeout'} ? $opts{'read_timeout'} : $DEFAULT_READ_TIMEOUT;
my $has_overall_timeout = $overall_timeout ? 1 : 0;
my $overall_time_left = $overall_timeout || undef;
READ_LOOP:
while (
!$finished && # has not finished
( !$has_overall_timeout || $overall_time_left > 0 ) # has not reached overall timeout
) {
$select_timeout = _get_shortest_timeout( $overall_time_left, $read_timeout );
( $nfound, $select_time_left ) = select( $read_output = $read_input, undef, undef, $select_timeout );
if ( !$nfound ) {
$self->{'_timed_out'} = ( $select_timeout == $read_timeout ) ? $read_timeout : $overall_timeout;
last;
}
elsif ( $nfound != -1 ) { # case 47309: If we get -1 it probably means we got interrupted by a signal
for my $fileno ( grep { vec( $read_output, $_, 1 ) } keys %fhmap ) {
my $fh = $fhmap{$fileno};
Cpanel::LoadFile::ReadFast::read_fast( $fh, ${ $fh_buffer{$fh} }, $CHUNK_SIZE, length ${ $fh_buffer{$fh} } ) or do {
delete $fhmap{$fileno};
$finished = !( scalar keys %fhmap );
last READ_LOOP if $finished;
vec( $read_input, $fileno, 1 ) = 0;
next;
};
if ( $output{$fh} ) {
my $payload_sr = \substr( ${ $fh_buffer{$fh} }, 0, length ${ $fh_buffer{$fh} }, q<> );
if ( $is_os_filehandle{$fh} ) {
Cpanel::IO::Flush::write_all( $output{$fh}, $read_timeout, $$payload_sr );
}
else {
print { $output{$fh} } $$payload_sr;
}
}
}
}
$overall_time_left -= ( $select_timeout - $select_time_left ) if $has_overall_timeout;
}
delete $fh_buffer{$_} for keys %output;
%fhmap = ();
$self->{'_finished'} = $finished;
if ( !$finished && defined $overall_time_left && $overall_time_left <= 0 ) {
$self->{'_timed_out'} = $overall_timeout;
}
return $self;
}
sub _get_shortest_timeout {
my ( $overall_time_left, $read_timeout ) = @_;
return undef if ( !$overall_time_left && !$read_timeout );
return $read_timeout if !defined $overall_time_left;
return ( !$read_timeout || $overall_time_left <= $read_timeout )
?
$overall_time_left
:
$read_timeout;
}
sub get_buffer {
return $_[0]->{'_fh_buffer'}{ $_[1] };
}
sub did_finish {
return $_[0]->{'_finished'} ? 1 : 0;
}
sub timed_out {
return defined $_[0]->{'_timed_out'} ? $_[0]->{'_timed_out'} : 0;
}
1;
} # --- END Cpanel/ReadMultipleFH.pm
{ # --- BEGIN Cpanel/ForkAsync.pm
package Cpanel::ForkAsync;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
my $DEFAULT_ERROR_CODE = 127; #EKEYEXPIRED
our $quiet = 0;
our $no_warn = 0;
sub do_in_child {
my ( $code, @args ) = @_;
local ( $!, $^E );
my $pid = fork();
die Cpanel::Exception::create( 'IO::ForkError', [ error => $! ] ) if !defined $pid;
if ( !$pid ) {
local $@;
if ( !eval { $code->(@args); 1 } ) {
my $err = $@;
my $io_err = 0 + $!;
_print($err) unless $quiet;
exit( $io_err || $DEFAULT_ERROR_CODE );
}
exit 0;
}
return $pid;
}
sub do_in_child_quiet {
my ( $code, @args ) = @_;
local $quiet = 1;
return do_in_child( $code, @args );
}
sub _print {
my ($msg) = @_;
warn $msg unless $no_warn;
print STDERR $msg;
return;
}
1;
} # --- END Cpanel/ForkAsync.pm
{ # --- BEGIN Cpanel/SafeRun/Object.pm
package Cpanel::SafeRun::Object;
use cPstrict;
no warnings 'once';
# use parent Cpanel::ChildErrorStringifier (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::ChildErrorStringifier); }
BEGIN {
eval { require Proc::FastSpawn; };
}
use IO::SigGuard ();
# use Cpanel::Env (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::FHUtils::Autoflush (); # perlpkg line 211
# use Cpanel::FHUtils::OS (); # perlpkg line 211
# use Cpanel::ReadMultipleFH (); # perlpkg line 211
# use Cpanel::LoadModule (); # perlpkg line 211
# use Cpanel::LocaleString (); # perlpkg line 211
use constant _ENOENT => 2;
my $CHUNK_SIZE = 2 << 16;
my $DEFAULT_TIMEOUT = 3600; # 1 hour
my $DEFAULT_READ_TIMEOUT = 0;
our $SAFEKILL_TIMEOUT = 1;
my @_allowed_env_vars_cache;
sub new { ## no critic qw(Subroutines::ProhibitExcessComplexity)
my ( $class, %OPTS ) = @_;
die "No “program”!" if !length $OPTS{'program'};
if ( !defined $OPTS{'timeout'} ) {
$OPTS{'timeout'} = $DEFAULT_TIMEOUT;
}
if ( !defined $OPTS{'read_timeout'} ) {
$OPTS{'read_timeout'} = $DEFAULT_READ_TIMEOUT;
}
if ( $OPTS{'program'} =~ tr{><*?[]`$()|;&#$\\\r\n\t }{} && !-e $OPTS{'program'} ) {
die Cpanel::Exception::create( 'InvalidParameter', 'A value of “[_1]” is invalid for “[_2]” as it does not permit the following characters: “[_3]”', [ $OPTS{'program'}, 'program', '><*?[]`$()|;&#$\\\\\r\\n\\t' ] );
}
my $args_ar = $OPTS{'args'} || [];
die "“args” must be an arrayref" if defined $args_ar && ref $args_ar ne 'ARRAY';
die Cpanel::Exception::create( 'InvalidParameter', 'The “[_1]” argument is invalid.', ['logger'] ) if $OPTS{'logger'};
die "Undefined value given as argument! (@$args_ar)" if grep { !defined } @$args_ar;
my $pump_stdin_filehandle_into_child;
my ( %parent_read_fh, %child_write_fh );
my $merge_output_yn = $OPTS{'stdout'} && $OPTS{'stderr'} && ( $OPTS{'stdout'} eq $OPTS{'stderr'} );
local $!;
for my $handle_name (qw(stdout stderr)) {
my $custom_fh = $OPTS{$handle_name} && UNIVERSAL::isa( $OPTS{$handle_name}, 'GLOB' ) && $OPTS{$handle_name};
my $dupe_filehandle_will_work = $custom_fh && !tied(*$custom_fh) && ( fileno($custom_fh) > -1 );
if ( !$custom_fh && $OPTS{$handle_name} ) {
die "“$handle_name” must be a filehandle or undef, not $OPTS{$handle_name}";
}
if ($dupe_filehandle_will_work) {
if ( fileno($custom_fh) < 3 ) {
open my $copy, '>&', $custom_fh or die "dup($handle_name): $!";
$child_write_fh{$handle_name} = $copy;
}
else {
$child_write_fh{$handle_name} = $custom_fh;
}
}
elsif ( $merge_output_yn && $handle_name eq 'stderr' ) {
$parent_read_fh{'stderr'} = $parent_read_fh{'stdout'};
$child_write_fh{'stderr'} = $child_write_fh{'stdout'};
}
else {
pipe $parent_read_fh{$handle_name}, $child_write_fh{$handle_name} #
or die "pipe() failed: $!";
}
}
my ( $child_reads, $parent_writes );
my $close_child_reads = 0;
if ( !defined $OPTS{'stdin'} || !length $OPTS{'stdin'} ) {
open $child_reads, '<', '/dev/null' or die "open(<, /dev/null) failed: $!";
$close_child_reads = 1;
}
elsif ( UNIVERSAL::isa( $OPTS{'stdin'}, 'GLOB' ) ) {
my $fileno = fileno $OPTS{'stdin'};
if ( !defined $fileno || $fileno == -1 ) {
$pump_stdin_filehandle_into_child = 1;
}
else {
$child_reads = $OPTS{'stdin'};
}
}
if ( !$child_reads ) {
$close_child_reads = 1;
pipe( $child_reads, $parent_writes ) or die "pipe() failed: $!";
}
my $self = bless {
_program => $OPTS{'program'},
_args => $OPTS{'args'} || [],
}, $class;
local $SIG{'CHLD'} = 'DEFAULT';
my $exec_failed_message = "exec($OPTS{'program'}) failed:";
my $used_fastspawn = 0;
if (
$INC{'Proc/FastSpawn.pm'} # may not be available yet due to upcp.static or updatenow.static
&& !$OPTS{'before_exec'}
&& !$Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED # PPI NO PARSE - We not ever be set if its not loaded
) {
$used_fastspawn = 1;
my @env;
if ( !$OPTS{'keep_env'} ) {
if ( !@_allowed_env_vars_cache ) {
@_allowed_env_vars_cache = ( split( m{ }, Cpanel::Env::get_safe_env_vars() ) );
}
@env = map { exists $ENV{$_} ? ( $_ . '=' . ( $ENV{$_} // '' ) ) : () } @_allowed_env_vars_cache;
}
my $user = $OPTS{'user'};
my $homedir = $OPTS{'homedir'};
if ( !$user || !$homedir ) {
my ( $pw_user, $pw_homedir ) = ( getpwuid $> )[ 0, 7 ];
$user ||= $pw_user;
$homedir ||= $pw_homedir;
}
die "Invalid EUID: $>" if !$user || !$homedir;
push @env, "HOME=$homedir", "USER=$user"; # need to always be set since we start clean and don't have before_exec
push @env, "TMP=$homedir/tmp", "TEMP=$homedir/tmp" if !defined $ENV{'TMP'};
$self->{'_child_pid'} = Proc::FastSpawn::spawn_open3(
fileno($child_reads), # stdin
defined $child_write_fh{'stdout'} ? fileno( $child_write_fh{'stdout'} ) : -1, # stdout
defined $child_write_fh{'stderr'} ? fileno( $child_write_fh{'stderr'} ) : -1, # stderr
$OPTS{'program'}, # program
[ $OPTS{'program'}, @$args_ar ], # args
$OPTS{'keep_env'} ? () : \@env # env
);
if ( !$self->{_child_pid} ) {
$self->{'_CHILD_ERROR'} = $! << 8;
$self->{'_exec_failed'} = 1;
${ $self->{'_stdout'} } = '';
${ $self->{'_stderr'} } .= "$exec_failed_message $!";
}
}
else {
require Cpanel::ForkAsync;
$self->{'_child_pid'} = Cpanel::ForkAsync::do_in_child(
sub {
$SIG{'__DIE__'} = 'DEFAULT'; ## no critic qw(Variables::RequireLocalizedPunctuationVars) -- will never be unset
if ( $parent_read_fh{'stdout'} ) {
close $parent_read_fh{'stdout'} or die "child close parent stdout failed: $!";
}
if ( $parent_read_fh{'stderr'} && !$merge_output_yn ) {
close $parent_read_fh{'stderr'} or die "child close parent stderr failed: $!";
}
if ($parent_writes) {
close $parent_writes or die "close() failed: $!";
}
open( *STDIN, '<&=' . fileno $child_reads ) or die "open(STDIN) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
my $fileno_stdout = fileno \*STDOUT;
if ( $fileno_stdout != fileno( $child_write_fh{'stdout'} ) ) {
if ( $fileno_stdout != 1 ) {
close *STDOUT or die "close(STDOUT) failed: $!";
open( *STDOUT, '>>&=1' ) or die "open(STDOUT, '>>&=1') failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
open( *STDOUT, '>>&=' . fileno $child_write_fh{'stdout'} ) or die "open(STDOUT) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
my $fileno_stderr = fileno \*STDERR;
if ( $fileno_stderr != fileno( $child_write_fh{'stderr'} ) ) {
if ( $fileno_stderr != 2 ) {
close *STDERR or die "close(STDOUT) failed: $!";
open( *STDERR, '>>&=2' ) or die "open(STDERR, '>>&=2') failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
open( *STDERR, '>>&=' . fileno $child_write_fh{'stderr'} ) or die "open(STDERR) failed: $!"; ##no critic qw(ProhibitTwoArgOpen)
}
if ( !$OPTS{'keep_env'} ) {
Cpanel::Env::clean_env();
}
if ($Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED) { # PPI NO PARSE -- can't be reduced if the module isn't loaded
my $target_euid = "$>";
my $target_egid = ( split( m{ }, "$)" ) )[0];
Cpanel::AccessIds::ReducedPrivileges::_restore_privileges( 0, 0 ); # PPI NO PARSE -- we will never get here if ReducedPrivileges wasn't loaded
Cpanel::LoadModule::load_perl_module('Cpanel::Sys::Setsid::Fast') if !$INC{'Cpanel/Sys/Setsid/Fast.pm'};
Cpanel::Sys::Setsid::Fast::fast_setsid();
Cpanel::LoadModule::load_perl_module('Cpanel::AccessIds::SetUids') if !$INC{'Cpanel/AccessIds/SetUids.pm'};
Cpanel::AccessIds::SetUids::setuids( $target_euid, $target_egid );
}
if ( $OPTS{'before_exec'} ) {
$OPTS{'before_exec'}->();
}
my $user = $OPTS{'user'};
my $homedir = $OPTS{'homedir'};
if ( !$user || !$homedir ) {
Cpanel::LoadModule::load_perl_module('Cpanel::PwCache') if !$INC{'Cpanel/PwCache.pm'};
my ( $pw_user, $pw_homedir ) = ( Cpanel::PwCache::getpwuid_noshadow($>) )[ 0, 7 ];
$user ||= $pw_user;
$homedir ||= $pw_homedir;
}
die "Invalid EUID: $>" if !$user || !$homedir;
$ENV{'HOME'} = $homedir if !defined $ENV{'HOME'}; # always cleared by clean_env, but may be reset in before_exec
$ENV{'USER'} = $user if !defined $ENV{'USER'}; # always cleared by clean_env, but may be reset in before_exec
$ENV{'TMP'} = "$homedir/tmp" if !defined $ENV{'TMP'};
$ENV{'TEMP'} = "$homedir/tmp" if !defined $ENV{'TEMP'};
exec( $OPTS{'program'}, @$args_ar ) or die "$exec_failed_message $!";
}
);
}
if ( $OPTS{'after_fork'} ) {
$OPTS{'after_fork'}->( $self->{'_child_pid'} );
}
if ($close_child_reads) { #only close it if we opened it
close $child_reads or die "close() failed: $!";
}
if ( $parent_read_fh{'stdout'} ) {
close $child_write_fh{'stdout'} or die "close() failed: $!";
}
if ( !$merge_output_yn && $parent_read_fh{'stderr'} ) {
close $child_write_fh{'stderr'} or die "close() failed: $!";
}
if ($parent_writes) {
if ( ref $OPTS{'stdin'} eq 'CODE' ) {
$OPTS{'stdin'}->($parent_writes);
}
else {
local $SIG{'PIPE'} = 'IGNORE';
Cpanel::FHUtils::Autoflush::enable($parent_writes);
if ($pump_stdin_filehandle_into_child) {
my $buffer;
my $is_os_stdin = Cpanel::FHUtils::OS::is_os_filehandle( $OPTS{'stdin'} );
local $!;
if ($is_os_stdin) {
while ( IO::SigGuard::sysread( $OPTS{'stdin'}, $buffer, $CHUNK_SIZE ) ) {
$self->_write_buffer_to_fh( $buffer, $parent_writes );
}
}
else {
while ( read $OPTS{'stdin'}, $buffer, $CHUNK_SIZE ) {
$self->_write_buffer_to_fh( $buffer, $parent_writes );
}
}
if ($!) {
die Cpanel::Exception::create( 'IO::ReadError', 'The system failed to read up to [format_bytes,_1] from the filehandle that contains standard input for the process that is running the command “[_2]”. This failure happened because of an error: [_3]', [ $CHUNK_SIZE, "$OPTS{'program'} @$args_ar", "$!" ] );
}
}
else {
my $to_print_r = ( ref $OPTS{'stdin'} eq 'SCALAR' ) ? $OPTS{'stdin'} : \$OPTS{'stdin'};
if ( length $$to_print_r ) {
$self->_write_buffer_to_fh( $$to_print_r, $parent_writes );
}
}
}
close $parent_writes or warn "close() failed: $!";
}
my $reader;
my $err_obj;
my @filehandles = map { $parent_read_fh{$_} ? [ $parent_read_fh{$_}, $OPTS{$_} ] : () } qw( stdout stderr );
if (@filehandles) {
local $@;
eval {
$reader = Cpanel::ReadMultipleFH->new(
filehandles => \@filehandles,
timeout => $OPTS{'timeout'},
read_timeout => $OPTS{'read_timeout'},
);
};
$err_obj = $@;
}
if ( $parent_read_fh{'stdout'} ) {
close $parent_read_fh{'stdout'} or warn "parent close(stdout) failed: $!";
}
if ( $parent_read_fh{'stderr'} && !$merge_output_yn ) {
close $parent_read_fh{'stderr'} or warn "parent close(stderr) failed: $!";
}
if ($err_obj) {
$self->{'_CHILD_ERROR'} = $self->_safe_kill_child();
die $err_obj;
}
elsif ($reader) {
if ( !$reader->did_finish() ) {
$self->{'_timed_out_after'} = $reader->timed_out();
$self->{'_CHILD_ERROR'} = $self->_safe_kill_child();
}
$self->{"_stdout"} = $parent_read_fh{stdout} && $reader->get_buffer( $parent_read_fh{stdout} );
if ( !$self->{"_stderr"} ) {
$self->{"_stderr"} = $parent_read_fh{stderr} && $reader->get_buffer( $parent_read_fh{stderr} );
}
}
if ( !defined $self->{'_CHILD_ERROR'} ) {
local $?;
waitpid( $self->{'_child_pid'}, 0 ) if defined $self->{'_child_pid'};
$self->{'_CHILD_ERROR'} = $?;
if ( $self->{'_CHILD_ERROR'} ) {
$self->{'_exec_failed'} = 1;
}
}
if ( $used_fastspawn && $self->{'_CHILD_ERROR'} == 32512 ) {
$self->{'_CHILD_ERROR'} = _ENOENT() << 8;
$self->{'_exec_failed'} = 1;
${ $self->{'_stderr'} } .= "$exec_failed_message $!";
}
elsif ( !$used_fastspawn && $self->{'_stderr'} && $self->{'_CHILD_ERROR'} && ( $self->{'_CHILD_ERROR'} >> 8 ) == 2 && index( ${ $self->{'_stderr'} }, $exec_failed_message ) > -1 ) {
$self->{'_exec_failed'} = 1;
}
return $self;
}
sub _write_buffer_to_fh ( $self, $buffer, $fh ) {
while ( length $buffer ) {
my $wrote = IO::SigGuard::syswrite( $fh, $buffer ) or die $self->_write_error( \$buffer, $! );
substr( $buffer, 0, $wrote, q<> );
}
return;
}
sub new_or_die {
my ( $class, @args ) = @_;
return $class->new(@args)->die_if_error();
}
sub to_exception {
my ($self) = @_;
if ( $self->timed_out() ) {
return Cpanel::Exception::create(
'ProcessFailed::Timeout',
[
process_name => $self->program(),
( $self->child_pid() ? ( pid => $self->child_pid() ) : () ),
timeout => $self->timed_out(),
$self->_extra_error_args_for_die_if_error(),
],
);
}
return $self->SUPER::to_exception();
}
sub _extra_error_args_for_die_if_error {
my ($self) = @_;
return (
stdout => $self->{'_stdout'} ? $self->stdout() : '',
stderr => $self->{'_stderr'} ? $self->stderr() : '',
);
}
sub _safe_kill_child {
my ($self) = @_;
Cpanel::LoadModule::load_perl_module('Cpanel::Kill::Single');
return 'Cpanel::Kill::Single'->can('safekill_single_pid')->( $self->{'_child_pid'}, $SAFEKILL_TIMEOUT ); # One second to die
}
sub stdout_r {
if ( !$_[0]->{'_stdout'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Carp');
die 'Cpanel::Carp'->can('safe_longmess')->("STDOUT output went to filehandle!");
}
return $_[0]->{'_stdout'};
}
sub _additional_phrases_for_autopsy {
if ( $_[0]->timed_out() ) {
return Cpanel::LocaleString->new( 'The system aborted the subprocess because it reached the timeout of [quant,_1,second,seconds].', $_[0]->timed_out() );
}
return;
}
sub stdout {
return ${ $_[0]->stdout_r() };
}
sub stderr_r {
if ( !$_[0]->{'_stderr'} ) {
Cpanel::LoadModule::load_perl_module('Cpanel::Carp');
die 'Cpanel::Carp'->can('safe_longmess')->("STDERR output went to filehandle!");
}
return $_[0]->{'_stderr'};
}
sub stderr {
return ${ $_[0]->stderr_r() };
}
sub child_pid {
return $_[0]->{'_child_pid'};
}
sub timed_out {
return $_[0]->{'_timed_out_after'};
}
sub program {
return $_[0]->{'_program'};
}
sub _program_with_args_str {
my $args_ar = $_[0]->{'_args'};
return $_[0]->{'_program'} . ( ( $args_ar && ref $args_ar && scalar @$args_ar ) ? " @$args_ar" : '' );
}
sub _ERROR_PHRASE {
my ($self) = @_;
return Cpanel::LocaleString->new( 'The “[_1]” command (process [_2]) reported error number [_3] when it ended.', $self->_program_with_args_str(), $self->{'_child_pid'}, $self->error_code() );
}
sub _SIGNAL_PHRASE {
my ($self) = @_;
return Cpanel::LocaleString->new( 'The “[_1]” command (process [_2]) ended prematurely because it received the “[_3]” ([_4]) signal.', $self->_program_with_args_str(), $self->{'_child_pid'}, $self->signal_name(), $self->signal_code() );
}
sub _write_error {
my ( $self, $buffer_sr, $OS_ERROR ) = @_;
my @cmd = ( $self->{'_program'}, @{ $self->{'_args'} } );
return Cpanel::Exception::create( 'IO::WriteError', 'The system failed to send [format_bytes,_1] to the process that is running the command “[_2]” because of an error: [_3]', [ length($$buffer_sr), "@cmd", $OS_ERROR ], { length => length($$buffer_sr), error => $OS_ERROR } );
}
1;
} # --- END Cpanel/SafeRun/Object.pm
{ # --- BEGIN Cpanel/SafeRun/Env.pm
package Cpanel::SafeRun::Env;
use strict;
# use Cpanel::Env (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
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' );
if ($Cpanel::AccessIds::ReducedPrivileges::PRIVS_REDUCED) { # PPI NO PARSE -- can't be reduced if the module isn't loaded
die __PACKAGE__ . " cannot be used with ReducedPrivileges. Use Cpanel::SafeRun::Object instead";
}
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::Debug::log_warn('Cannot execute a null program');
return \$output if $return_reference;
return $output;
}
require Cpanel::Env;
local ( $/, *PROG, *RNULL );
no strict 'refs';
open( RNULL, '<', '/dev/null' ); ## no critic(InputOutput::ProhibitBarewordFileHandles InputOutput::RequireCheckedOpen)
my $pid = open( PROG, "-|" ); ## no critic(InputOutput::ProhibitBarewordFileHandles)
if ( $pid > 0 ) {
$output = <PROG>;
}
elsif ( $pid == 0 ) {
open( STDIN, '<&RNULL' );
if ($error_output) {
open STDERR, '>&STDOUT';
}
Cpanel::Env::clean_env(%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) or exit(1); # Not reached
}
else {
Cpanel::Debug::log_warn('Could not fork new process');
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/CachedCommand.pm
package Cpanel::CachedCommand;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::StatCache (); # perlpkg line 211
# use Cpanel::LoadFile (); # perlpkg line 211
# use Cpanel::CachedCommand::Utils (); # perlpkg line 211
# use Cpanel::CachedCommand::Valid (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
our $VERSION = '2.8';
my %MEMORY_CACHE;
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 invalidate_cache {
my $ds_file = Cpanel::CachedCommand::Utils::invalidate_cache(@_);
delete $MEMORY_CACHE{$ds_file};
return;
}
sub _cached_cmd {
my %OPTS = @_;
my ( $binary, $ttl, $mtime, $exact, $regexcheck, $args_hr, $min_expire_time, $get_result_cr ) = (
( $OPTS{'binary'} || '' ),
( $OPTS{'ttl'} || 0 ),
( $OPTS{'mtime'} || 0 ),
( $OPTS{'exact'} || 0 ),
( $OPTS{'regexcheck'} || '' ),
( $OPTS{'args_hr'} || {} ),
( $OPTS{'min_expire_time'} || 0 ),
( $OPTS{'get_result_cr'} || \&_default_get_result_cr ),
);
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 && scalar @SAFEAG > 4 ) {
splice( @SAFEAG, 4 );
}
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 ( $used_cache, $res );
if (
Cpanel::CachedCommand::Valid::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;
if ( $res && ( !$regexcheck || $$res =~ m/$regexcheck/ ) ) {
$used_cache = 1;
}
}
if ( !$used_cache ) {
$data_mtime = _time();
$res = $get_result_cr->( { binary => $binary, args => \@AG } );
if ( !$regexcheck || ( defined $res && ( ref $res ? $$res : $res ) =~ m/$regexcheck/ ) ) {
print STDERR "_cached_command: writing datastore file: $datastore_file " . ( $regexcheck ? "regex_check: $regexcheck" : '' ) . "\n" if $Cpanel::Debug::level;
require Cpanel::CachedCommand::Save;
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;
}
}
return _cache_res_if_needed( $res, $ttl, $datastore_file, $data_mtime );
}
sub _cache_res_if_needed {
my ( $res, $ttl, $datastore_file, $data_mtime ) = @_;
if ( ref $res ) {
if ( $ttl && ( !defined $$res || length($$res) < 32768 ) ) { $MEMORY_CACHE{$datastore_file} = { 'mtime' => $data_mtime, 'contents' => $res }; }
return $res;
}
else {
if ( $ttl && ( !defined $res || length($res) < 32768 ) ) { $MEMORY_CACHE{$datastore_file} = { 'mtime' => $data_mtime, 'contents' => \$res }; }
return \$res;
}
}
sub _default_get_result_cr {
my ($opts) = @_;
return _get_cmd_output( 'program' => $opts->{binary}, 'args' => $opts->{args}, 'stderr' => \*STDERR );
}
sub _get_memory_cache {
return \%MEMORY_CACHE;
}
sub _time {
return time();
}
sub _get_cmd_output {
my (@key_val) = @_;
return eval {
require Cpanel::SafeRun::Object;
my $run = Cpanel::SafeRun::Object->new(@key_val);
$run->stdout();
};
}
sub has_cache {
my ( $ttl, $bin, @AG ) = @_;
my @SAFEAG = @AG;
if ( scalar @SAFEAG > 3 ) {
splice( @SAFEAG, 3 );
}
my $datastore_file = Cpanel::CachedCommand::Utils::_get_datastore_filename( $bin, @SAFEAG );
return (
Cpanel::CachedCommand::Valid::is_cache_valid(
'datastore_file' => $datastore_file,
'binary' => $bin,
'ttl' => $ttl
)
) ? 1 : 0;
}
sub cachedcommand {
my ( $binary, @ARGS ) = @_;
my $cache_ref = _cached_cmd(
'binary' => $binary,
'regexcheck' => qr/./, # only cache data that actually exists
'args' => \@ARGS
);
if ( ref $cache_ref eq 'SCALAR' ) { return $$cache_ref; }
return $cache_ref;
}
sub cachedcommand_no_errors {
my (%OPTS) = @_;
return _cached_cmd(
binary => $OPTS{'binary'},
args => $OPTS{'args'},
( defined $OPTS{'mtime'} ? ( mtime => $OPTS{'mtime'} ) : () ),
( defined $OPTS{'ttl'} ? ( ttl => $OPTS{'ttl'} ) : () ),
get_result_cr => sub {
my ($opts) = @_;
return _get_cmd_output( 'program' => $opts->{binary}, 'args' => $opts->{args}, ( $OPTS{ttl} ? ( 'timeout' => $OPTS{ttl}, 'read_timeout' => $OPTS{ttl} ) : () ) );
}
);
}
sub cachedcommand_multifile {
my ( $test_file_ar, $binary, @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, $binary, @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, $binary, @ARGS ) = @_;
my $cache_ref = _cached_cmd(
'ttl' => $ttl,
'binary' => $binary,
'args' => \@ARGS,
'get_result_cr' => sub {
my ($opts) = @_;
require Cpanel::SafeRun::Env;
return Cpanel::SafeRun::Env::saferun_r_cleanenv( $opts->{binary}, @{ $opts->{args} } );
},
);
if ( ref $cache_ref ne 'SCALAR' ) { return \$cache_ref; }
return $cache_ref;
}
sub cachedmcommand_cleanenv2 {
my ( $ttl, $args_hr ) = @_;
my @cmd = @{ $args_hr->{'command'} };
my $binary = shift @cmd;
my @ARGS = @cmd;
my $cache_ref = _cached_cmd(
'ttl' => $ttl,
'binary' => $binary,
'args' => \@ARGS,
'get_result_cr' => sub {
require Cpanel::SafeRun::Env;
return Cpanel::SafeRun::Env::saferun_cleanenv2($args_hr);
},
);
return $cache_ref;
}
sub cachedmcommand_r {
my ( $ttl, $binary, @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 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,
'exact' => $exact,
'regexcheck' => $regexcheck,
'args' => \@AG,
'get_result_cr' => sub {
my ($opts) = @_;
return _get_cmd_output( 'program' => $opts->{binary}, 'args' => $opts->{'args'}, 'stderr' => \*STDERR, ( int($timer) > 0 ? ( 'timeout' => $timer, 'read_timeout' => $timer ) : () ) );
},
);
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 _get_cmd_output( 'program' => $bin, 'args' => \@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/GlobalCache.pm
package Cpanel::GlobalCache;
use strict;
# use Cpanel::JSON::FailOK (); # perlpkg line 211
my $GCACHEref = {};
our $PRODUCT_CONF_DIR = '/var/cpanel';
sub get_cache_mtime {
my ($cachename) = @_;
if ( !exists $GCACHEref->{$cachename} ) { load_cache($cachename); }
return $GCACHEref->{$cachename}{'mtime'};
}
sub load_cache {
my ($cachename) = @_;
if ( open( my $cache_fh, '<', "$PRODUCT_CONF_DIR/globalcache/$cachename.cache" ) ) {
$GCACHEref->{$cachename} ||= {};
my $cache_ref = $GCACHEref->{$cachename};
require Cpanel::JSON;
$cache_ref->{'data'} = Cpanel::JSON::FailOK::LoadFile($cache_fh);
if ( ref $cache_ref->{'data'} eq 'HASH' ) {
$cache_ref->{'mtime'} = ( stat($cache_fh) )[9];
}
else {
$cache_ref->{'data'} = {};
}
close($cache_fh);
}
return;
}
sub cachedmcommand { ## no critic(RequireArgUnpacking)
my $cachename = shift;
require Cpanel::CachedCommand;
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'->can('cachedmcommand')->( $cache_max_mtime, @_ )
);
}
sub cachedcommand { ## no critic(RequireArgUnpacking)
my $cachename = shift;
require Cpanel::CachedCommand;
require Cpanel::StatCache;
if ( !exists $GCACHEref->{$cachename} ) { load_cache($cachename); }
my ( $file_mtime, $file_ctime ) = 'Cpanel::StatCache'->can('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'->can('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;
}
require Cpanel::LoadFile;
return (
( exists $GCACHEref->{$cachename}{'data'}{'file'}{$file} && $GCACHEref->{$cachename}{'mtime'} > $file_mtime )
? $GCACHEref->{$cachename}{'data'}{'file'}{$file}
: 'Cpanel::LoadFile'->can('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 );
}
sub clearcache {
$GCACHEref = {};
return;
}
sub default_product_dir {
$PRODUCT_CONF_DIR = shift if @_;
return $PRODUCT_CONF_DIR;
}
1;
} # --- END Cpanel/GlobalCache.pm
{ # --- BEGIN Cpanel/IP/NonlocalBind/Cache.pm
package Cpanel::IP::NonlocalBind::Cache;
use strict;
use warnings;
no warnings 'once';
use constant {
DISABLED => '', # 0-bytes
ENABLED => 1, # 1-byte
UNKNOWN => 22, # 2-bytes
_ENOENT => 2,
};
our $CACHE_FILE = '/var/cpanel/ipv4_ip_nonlocal_bind';
our $_ipv4_ip_nonlocal_bind_cache_length;
sub ipv4_ip_nonlocal_bind_is_enabled {
if ( !defined $_ipv4_ip_nonlocal_bind_cache_length ) {
$_ipv4_ip_nonlocal_bind_cache_length = ( stat($CACHE_FILE) )[7];
if ( !defined $_ipv4_ip_nonlocal_bind_cache_length ) {
if ( $! != _ENOENT() ) {
warn "stat($CACHE_FILE): $!";
}
}
}
if ( defined $_ipv4_ip_nonlocal_bind_cache_length ) {
return 1 if $_ipv4_ip_nonlocal_bind_cache_length == length ENABLED();
return 0 if $_ipv4_ip_nonlocal_bind_cache_length == length DISABLED();
if ( $_ipv4_ip_nonlocal_bind_cache_length != length UNKNOWN() ) {
warn "“$CACHE_FILE” has unrecognized length: $_ipv4_ip_nonlocal_bind_cache_length";
}
}
return undef;
}
1;
} # --- END Cpanel/IP/NonlocalBind/Cache.pm
{ # --- BEGIN Cpanel/FileUtils/TouchFile.pm
package Cpanel::FileUtils::TouchFile;
use strict;
use warnings;
no warnings 'once';
use constant {
_ENOENT => 2,
};
my $logger;
our $VERSION = '1.3';
sub _log {
my ( $level, $msg ) = @_;
require Cpanel::Logger;
$logger ||= Cpanel::Logger->new();
$logger->$level($msg);
return;
}
my $mtime;
sub touchfile {
my ( $file, $verbose, $fail_ok ) = @_;
if ( !defined $file ) {
_log( 'warn', "touchfile called with undefined file" );
return;
}
my $mtime;
if ( utime undef, undef, $file ) {
return 1;
}
elsif ( $! != _ENOENT() ) {
_log( 'warn', "utime($file) as $>: $!" );
$mtime = -e $file ? ( stat _ )[9] : 0; # for warnings-safe numeric comparison
if ( !$mtime && $! != _ENOENT ) {
_log( 'warn', "Failed to stat($file) as $>: $!" );
return;
}
}
$mtime = ( stat $file )[9] // 0;
if ( open my $fh, '>>', $file ) { # append so we don't wipe out contents
my $mtime_after_open = ( stat $fh )[9] || 0; # for warnings safe numeric comparison
return 1 if $mtime != $mtime_after_open; # in case open does not change it, see comment below
}
else {
_log( 'warn', "Failed to open(>> $file) as $>: $!" ) unless $fail_ok;
}
if ($fail_ok) { return; }
my $at_this_point = ( stat $file )[9] || 0; # for warnings safe numeric comparison
if ( $mtime == $at_this_point ) {
my $new_at_this_point = ( stat $file )[9] || 0; # for warnings safe numeric comparison
if ( $mtime == $new_at_this_point ) {
if ($verbose) {
_log( 'info', 'Trying to do system “touch” command!' );
}
if ( system( 'touch', $file ) != 0 ) {
if ($verbose) {
_log( 'info', 'system method 1 failed.' );
}
}
}
}
if ( !-e $file ) { # obvisouly it didn't touch it if it doesn't exist...
_log( 'warn', "Failed to create $file: $!" );
return;
}
else {
my $after_all_that = ( stat $file )[9] || 0; # for warnings safe numeric comparison
if ( $mtime && $mtime == $after_all_that ) {
_log( 'warn', "mtime of “$file” not changed!" );
return;
}
return 1;
}
}
1;
} # --- END Cpanel/FileUtils/TouchFile.pm
{ # --- BEGIN Cpanel/Linux/NetlinkConstants.pm
package Cpanel::Linux::NetlinkConstants;
use strict;
use warnings;
no warnings 'once';
our $VERSION = '1.00';
# use Cpanel::Pack::Template (); # perlpkg line 211
use constant IFA_ADDRESS => 1;
use constant IFA_LOCAL => 2;
use constant IFA_LABEL => 3;
use constant IFA_CACHEINFO => 6;
use constant RT_SCOPE_UNIVERSE => 0;
use constant RT_SCOPE_SITE => 200;
use constant RT_SCOPE_LINK => 253;
use constant RT_SCOPE_HOST => 254;
use constant RT_SCOPE_NOWHERE => 255;
use constant RTM_GETLINK => 18;
use constant RTM_GETADDR => 22;
use constant RTM_GETROUTE => 26;
use constant RTA_DST => 1;
use constant RTA_PREFSRC => 7;
our @IFINFOMSG_TEMPLATE = ( #struct ifinfomsg
'ifi_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # unsigned char ifi_family;
'__ifi_pad' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # unsigned char __ifi_pad;
'ifi_type' => Cpanel::Pack::Template::PACK_TEMPLATE_U16, # unsigned short ifi_type; /* ARPHRD_* */
'ifi_index' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, # int ifi_index; /* Link index */
'ifi_flags' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, # unsigned ifi_flags; /* IFF_* flags */
'ifi_change' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 # unsigned ifi_change; /* IFF_* change mask */
);
our @IFA_CACHEINFO_TEMPLATE = ( #struct ifa_cacheinfo
'ifa_prefered' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, # __u32 ifa_prefered; # See: https://en,wiktionary,org/wiki/prefered -- It is mispelled upstream
'ifa_valid' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, # __u32 ifa_valid;
'cstamp' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, # __u32 cstamp; /* created timestamp, hundredths of seconds */
'tstamp' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 # __u32 tstamp; /* updated timestamp, hundredths of seconds */
);
our @IFADDRMSG_TEMPLATE = ( # struct ifaddrmsg
'ifa_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 ifa_family;
'ifa_prefixlen' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 ifa_prefixlen; /* The prefix length */
'ifa_flags' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 ifa_flags; /* Flags */
'ifa_scope' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 ifa_scope; /* Address scope */
'ifa_index' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 # __u32 ifa_index; /* Link index */
);
our @RTMSG_TEMPLATE = ( # struct rtmsg
'rtm_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_family;
'rtm_dst_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_dst_len;
'rtm_src_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_src_len;
'rtm_tos' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_tos;
'rtm_table' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_table; /* Routing table id */
'rtm_protocol' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_protocol; /* Routing protocol */
'rtm_scope' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_scope; /* Address scope */
'rtm_type' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 rtm_type;
'rtm_flags' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 # __u32 rtm_flags; /* Flags */
);
our @RTATTR_HEADER_TEMPLATE = (
'rta_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U16,
'rta_type' => Cpanel::Pack::Template::PACK_TEMPLATE_U16,
);
1;
} # --- END Cpanel/Linux/NetlinkConstants.pm
{ # --- BEGIN Cpanel/Linux/RtNetlink.pm
package Cpanel::Linux::RtNetlink;
use cPstrict;
no warnings 'once';
# use Cpanel::Linux::Netlink (); # perlpkg line 211
# use Cpanel::Linux::NetlinkConstants (); # perlpkg line 211
# use Cpanel::Pack (); # perlpkg line 211
# use Cpanel::Pack::Template (); # perlpkg line 211
# use Cpanel::Socket::Constants (); # perlpkg line 211
# use Socket qw(inet_pton inet_ntop); # perlpkg line 248
INIT { Socket->import(qw{inet_pton inet_ntop}); }
use constant {
IFLA_IFNAME => 4,
DEBUG => 0,
AF_INET6 => $Cpanel::Linux::Netlink::AF_INET6,
IFA_LOCAL => Cpanel::Linux::NetlinkConstants::IFA_LOCAL(),
IFA_ADDRESS => Cpanel::Linux::NetlinkConstants::IFA_ADDRESS(),
IFA_CACHEINFO => Cpanel::Linux::NetlinkConstants::IFA_CACHEINFO(),
IFA_LABEL => Cpanel::Linux::NetlinkConstants::IFA_LABEL(),
PACK_TEMPLATE_U16 => Cpanel::Pack::Template::PACK_TEMPLATE_U16,
U16_BYTES_LENGTH => Cpanel::Pack::Template::U16_BYTES_LENGTH,
RTA_DST => Cpanel::Linux::NetlinkConstants::RTA_DST(),
RTA_PREFSRC => Cpanel::Linux::NetlinkConstants::RTA_PREFSRC(),
};
my $INFINITY_LIFE_TIME = 4294967295;
my $NETLINK_ROUTE_SOCKET = 0;
my $PF_NETLINK = 16;
my $IFINFOMSG_PACK_OBJ;
my $IFA_CACHEINFO_PACK_OBJ;
my $IFADDRMSG_PACK_OBJ;
my $RTMSG_PACK_OBJ;
sub get_first_interface_and_address {
my ($address_family) = @_;
die "List context only!" if !wantarray;
$address_family = _address_family_string_to_number($address_family);
my $socket = _make_netlink_socket();
my $addresses = _get_interface_addresses( $socket, $address_family );
my @fallback;
foreach my $address ( sort { $a->{'scope'} <=> $b->{'scope'} } @{$addresses} ) { # Prefer the largest global scope
$address->{'ip'} ||= _unpack_address_to_ip( $address->{'address'} || '' );
my @candidate = ( $address->{'ifindex'}, $address->{'ip'} );
if ( defined $address->{label} && index( $address->{label}, ':' ) > 0 ) { # do nothing if at position 0
my ( $interface, $virtual ) = split( ':', $address->{label}, 2 );
$candidate[0] .= ':' . $virtual;
}
@fallback = @candidate unless scalar @fallback;
next if is_reserved_ipv4( $address->{'ip'} );
return @candidate;
}
return @fallback;
}
sub is_reserved_ipv4 ($ip) {
return unless defined $ip;
return 1 if index( $ip, '127.' ) == 0 # 127.0.0.0/8
|| index( $ip, '10.' ) == 0 # 10.0.0.0/8
|| index( $ip, '11.' ) == 0 # 11.0.0.0/8
|| index( $ip, '192.168.' ) == 0 # 192.168.0.0/16
;
if ( index( $ip, '172.' ) == 0 || index( $ip, '2' ) == 0 ) {
if ( $ip =~ qr{^([0-9]+)\.([0-9]+)\.[0-9]+\.[0-9]+$} ) {
return 1 if $1 == 172 && ( 16 <= $2 && $2 <= 31 ); # 172.16.0.0/12
return 1 if $1 >= 224; # 224.0.0.0/4 & 240.0.0.0/4 & 255.255.255.255/32
}
}
return;
}
sub is_reserved_ipv6 ($ip) {
return unless defined $ip;
return 1 if $ip eq '::1';
$ip = lc $ip;
return 1 if index( $ip, 'fe80:' ) == 0;
return;
}
sub get_addresses_by_interface ($address_family) {
$address_family = _address_family_string_to_number($address_family);
my $socket = _make_netlink_socket();
my $addresses = _get_interface_addresses( $socket, $address_family, { 'ip' => 1 } );
my $interfaces = _get_interfaces( $socket, $address_family );
my %ifcount;
my %combined;
foreach my $address ( @{$addresses} ) {
next if $address->{'scope'} != Cpanel::Linux::NetlinkConstants::RT_SCOPE_UNIVERSE(); # only want global
my $if = $interfaces->[ $address->{'ifindex'} - 1 ];
$combined{$if}{ ++$ifcount{$if} } = $address;
}
return \%combined;
}
sub get_interfaces {
my ($address_family) = @_;
$address_family = _address_family_string_to_number($address_family);
return _get_interfaces( _make_netlink_socket(), $address_family );
}
sub get_interface_addresses ($address_family) {
$address_family = _address_family_string_to_number($address_family);
return _get_interface_addresses( _make_netlink_socket(), $address_family, { 'ip' => 1 } );
}
sub _get_interfaces ( $sock, $address_family ) {
my @interfaces;
$IFINFOMSG_PACK_OBJ ||= Cpanel::Pack->new( \@Cpanel::Linux::NetlinkConstants::IFINFOMSG_TEMPLATE );
Cpanel::Linux::Netlink::netlink_transaction(
'header' => [
'nlmsg_flags' => $Cpanel::Linux::Netlink::NLM_F_ROOT | $Cpanel::Linux::Netlink::NLM_F_MATCH,
'nlmsg_type' => Cpanel::Linux::NetlinkConstants::RTM_GETLINK(),
],
'message' => {
'ifi_family' => $address_family,
},
'sock' => $sock,
'send_pack_obj' => $IFINFOMSG_PACK_OBJ,
'recv_pack_obj' => $IFINFOMSG_PACK_OBJ,
'payload_parser' => _make_payload_parser(
sub {
my ( $nl_msgcount, $nl_response_hr, $rta_type, $value ) = @_;
print STDERR "toto-[$nl_msgcount]\ntype:[$rta_type]==value:[$value]\n" if DEBUG;
if ( $rta_type == Cpanel::Linux::NetlinkConstants::IFA_LABEL() ) {
$interfaces[ $nl_response_hr->{'ifi_index'} - 1 ] = $value =~ tr{\0}{}dr;
}
elsif (DEBUG) {
warn "Unknown rta_type: [$rta_type]";
}
},
),
);
return \@interfaces;
}
sub get_route_to ( $address_family, $dst_ip ) {
$address_family = _address_family_string_to_number($address_family);
$dst_ip = Socket::inet_pton $address_family, $dst_ip;
return _get_route_to( _make_netlink_socket(), $address_family, $dst_ip );
}
my @RTATTR_DATA = (
undef,
{
'name' => 'rta_dst',
'handler' => \&_rtattr_address_handler,
},
undef,
undef,
undef,
undef,
undef,
{
'name' => 'rta_prefsrc',
'handler' => \&_rtattr_address_handler,
},
);
sub _rtattr_address_handler ( $value, $address_family ) {
return Socket::inet_ntop( $address_family, $value );
}
sub _get_route_to ( $sock, $address_family, $dst_ip_packed ) { ## no critic qw(ProhibitManyArgs)
my ( $address_length, @attributes );
$address_length = ( $address_family == AF_INET6 ) ? 16 : 4;
$RTMSG_PACK_OBJ ||= Cpanel::Pack->new( \@Cpanel::Linux::NetlinkConstants::RTMSG_TEMPLATE );
my $RTMSG_WITH_DST_PACK_OBJ = Cpanel::Pack->new(
[
@Cpanel::Linux::NetlinkConstants::RTMSG_TEMPLATE,
@Cpanel::Linux::NetlinkConstants::RTATTR_HEADER_TEMPLATE,
'rta_dst' => 'a' . $address_length,
]
);
Cpanel::Linux::Netlink::netlink_transaction(
'header' => [
'nlmsg_type' => Cpanel::Linux::NetlinkConstants::RTM_GETROUTE(),
'nlmsg_seq' => 1, #seems unnecessary??
],
'message' => {
'rtm_family' => $address_family,
'rtm_dst_len' => $address_length * 8, # /32 for v4, /128 for v6
'rta_len' => 4 + $address_length, # includes rtattr header size (4 bytes)
'rta_type' => RTA_DST,
'rta_dst' => $dst_ip_packed,
},
'sock' => $sock,
'send_pack_obj' => $RTMSG_WITH_DST_PACK_OBJ,
'recv_pack_obj' => $RTMSG_PACK_OBJ,
'payload_parser' => _make_payload_parser(
sub {
my ( $msgcount, $response_ref, $rta_type, $value ) = @_;
$attributes[$msgcount] = {} unless defined $attributes[$msgcount];
if ( defined $RTATTR_DATA[$rta_type] ) {
my $rtattr_hr = $RTATTR_DATA[$rta_type];
$attributes[$msgcount]->{ $rtattr_hr->{'name'} } = $rtattr_hr->{'handler'}->( $value, $address_family );
}
else {
$attributes[$msgcount]->{$rta_type} = $value;
}
},
),
);
return \@attributes;
}
my %_u16_cache;
sub _make_payload_parser ($for_each_rtmsg_cr) {
return sub {
my ( $nl_msgcount, $nlresponse_hr, $payload_sr ) = ( $_[0], $_[1], \$_[2] );
my ( $u16, $rta_length, $rta_type, $value );
RTATTR_LOOP:
while ( length $$payload_sr ) {
$u16 = substr( $$payload_sr, 0, U16_BYTES_LENGTH, '' );
$rta_length = ( $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 ) ) or last RTATTR_LOOP; # unsigned short rta_len;
$u16 = substr( $$payload_sr, 0, U16_BYTES_LENGTH, '' );
$rta_type = ( $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 ) );
$value = substr( $$payload_sr, 0, $rta_length - ( U16_BYTES_LENGTH * 2 ), '' );
$for_each_rtmsg_cr->(
$nl_msgcount,
$nlresponse_hr,
$rta_type,
$value
);
}
};
}
sub _get_interface_addresses ( $sock, $address_family, $want = undef ) {
$want //= {};
my $want_ip = $want->{'ip'};
my @addresses;
$IFADDRMSG_PACK_OBJ ||= Cpanel::Pack->new( \@Cpanel::Linux::NetlinkConstants::IFADDRMSG_TEMPLATE );
$IFA_CACHEINFO_PACK_OBJ ||= Cpanel::Pack->new( \@Cpanel::Linux::NetlinkConstants::IFA_CACHEINFO_TEMPLATE );
Cpanel::Linux::Netlink::netlink_transaction(
'header' => [
'nlmsg_type' => Cpanel::Linux::NetlinkConstants::RTM_GETADDR(),
'nlmsg_flags' => $Cpanel::Linux::Netlink::NLM_F_ROOT,
'nlmsg_seq' => 1, #seems unnecessary??
],
'message' => {
'ifa_family' => $address_family,
},
'sock' => $sock,
'send_pack_obj' => $IFADDRMSG_PACK_OBJ,
'recv_pack_obj' => $IFADDRMSG_PACK_OBJ,
'payload_parser' => _make_payload_parser(
sub {
my ( $msgcount, $response_ref, $rta_type, $value ) = @_;
print STDERR "haha-[$msgcount]\n[$rta_type]==[$value]\n" if DEBUG;
if ( $rta_type == IFA_LOCAL || ( $rta_type == IFA_ADDRESS && !$addresses[$msgcount]->{'ip'} ) ) {
@{ $addresses[$msgcount] }{ 'scope', 'ifindex', 'prefix' } = @{$response_ref}{ 'ifa_scope', 'ifa_index', 'ifa_prefixlen' };
if ($want_ip) {
$addresses[$msgcount]->{'ip'} = ( $address_family == AF_INET6 ) ? join( ":", unpack( "H4H4H4H4H4H4H4H4", $value ) ) : join( '.', unpack( 'C4', $value ) );
}
else {
$addresses[$msgcount]->{'address'} = $value;
}
print STDERR "[address][$addresses[$msgcount]->{'ip'}]\n" if DEBUG;
}
elsif ( $rta_type == IFA_CACHEINFO ) {
$addresses[$msgcount]->{'cacheinfo'} = $IFA_CACHEINFO_PACK_OBJ->unpack_to_hashref($value);
if ( $addresses[$msgcount]->{'cacheinfo'}{'ifa_valid'} == $INFINITY_LIFE_TIME ) {
$addresses[$msgcount]->{'type'} = 0;
}
else {
$addresses[$msgcount]->{'temporary'} = 1;
}
}
elsif ( $rta_type == IFA_LABEL ) {
$addresses[$msgcount]->{'label'} = $value =~ tr{\0}{}dr;
}
elsif (DEBUG) {
warn "Unknown rta_type: [$rta_type]";
}
},
),
);
return \@addresses;
}
sub _make_netlink_socket() {
my $sock;
socket( $sock, $Cpanel::Linux::Netlink::PF_NETLINK, $Cpanel::Linux::Netlink::SOCK_DGRAM, $NETLINK_ROUTE_SOCKET ) or die "socket: $!";
return $sock;
}
my @ALLOWED_FAMILIES = qw(
AF_INET
AF_INET6
);
sub _address_family_string_to_number ($addr_fam) {
if ( !grep { $_ eq $addr_fam } @ALLOWED_FAMILIES ) {
die "“$addr_fam” is not a recognized address family; must be one of: @ALLOWED_FAMILIES";
}
return ${ *{ $Cpanel::Socket::Constants::{$addr_fam} }{'SCALAR'} };
}
sub _unpack_address_to_ip ($ip) {
return length $ip > 10 ? join( ":", unpack( "H4H4H4H4H4H4H4H4", $ip ) ) : join( '.', unpack( 'C4', $ip ) );
}
1;
} # --- END Cpanel/Linux/RtNetlink.pm
{ # --- BEGIN Cpanel/IP/Loopback.pm
package Cpanel::IP::Loopback;
use strict;
use warnings;
no warnings 'once';
sub is_loopback {
return (
length $_[0]
&& (
$_[0] eq 'localhost' #
|| $_[0] eq 'localhost.localdomain' #
|| $_[0] eq '0000:0000:0000:0000:0000:0000:0000:0001' #
|| index( $_[0], '0000:0000:0000:0000:0000:ffff:7f' ) == 0 # ipv4 inside of ipv6 match 127.*
|| index( $_[0], '::ffff:127.' ) == 0 # ipv4 inside of ipv6 match 127.*
|| index( $_[0], '127.' ) == 0 # 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
{ # --- BEGIN Cpanel/IP/Configured.pm
package Cpanel::IP::Configured;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::CachedCommand::Utils (); # perlpkg line 211
# use Cpanel::FileUtils::TouchFile (); # perlpkg line 211
# use Cpanel::JSON::FailOK (); # perlpkg line 211
# use Cpanel::FileUtils::Write::JSON::Lazy (); # perlpkg line 211
# use Cpanel::PwCache (); # perlpkg line 211
use Try::Tiny;
our $VERSION = '1.7';
my $PRODUCT_CONF_DIR = '/var/cpanel';
my $SYSTEM_CONF_DIR = '/etc';
my $SYSTEM_SBIN_DIR = '/sbin';
my $DB_FILE = 'all_iplist.db';
my $configuredips;
sub clear_configured_ips_cache {
Cpanel::FileUtils::TouchFile::touchfile("$SYSTEM_CONF_DIR/ips"); # Reset mtime
Cpanel::CachedCommand::Utils::destroy( 'name' => $DB_FILE );
$configuredips = undef;
return 1;
}
sub getconfiguredips {
if ($configuredips) {
return wantarray ? @$configuredips : $configuredips;
}
my $iplist_cachefile = Cpanel::CachedCommand::Utils::get_datastore_filename($DB_FILE);
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 "$SYSTEM_CONF_DIR/ips" )[9] || 0 );
my $wwwacctconf_age = $now - ( ( stat "$SYSTEM_CONF_DIR/wwwacct.conf" )[9] || 0 );
if ( $iplist_cache_age > $ips_age || $iplist_cache_age > $wwwacctconf_age ) {
$use_cache = 0;
}
}
if ($use_cache) {
$configuredips = Cpanel::JSON::FailOK::LoadFile($iplist_cachefile);
}
if ( !$configuredips || !@$configuredips ) {
require Cpanel::Linux::RtNetlink;
require Cpanel::IP::Loopback;
my $ips = Cpanel::Linux::RtNetlink::get_interface_addresses('AF_INET');
@$configuredips = map { $_->{'ip'} } grep { !Cpanel::IP::Loopback::is_loopback( $_->{'ip'} ) } @$ips;
if ( Cpanel::PwCache::getusername() ne 'nobody' ) {
try {
Cpanel::FileUtils::Write::JSON::Lazy::write_file( $iplist_cachefile, $configuredips, 0644 );
}
catch {
_logger()->warn( Cpanel::Exception::get_string($_) );
};
}
}
$configuredips = [] unless ( defined $configuredips );
return wantarray ? @$configuredips : $configuredips;
}
sub clearcache {
$configuredips = undef;
return 1;
}
sub default_product_dir {
$PRODUCT_CONF_DIR = shift if @_;
return $PRODUCT_CONF_DIR;
}
sub default_conf_dir {
$SYSTEM_CONF_DIR = shift if @_;
return $SYSTEM_CONF_DIR;
}
sub default_sbin_dir {
$SYSTEM_SBIN_DIR = shift if @_;
return $SYSTEM_SBIN_DIR;
}
my $logger;
sub _logger {
return $logger if $logger;
require Cpanel::Logger;
return ( $logger = Cpanel::Logger->new() );
}
1;
} # --- END Cpanel/IP/Configured.pm
{ # --- BEGIN Cpanel/IP/Bound.pm
package Cpanel::IP::Bound;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Socket::Constants (); # perlpkg line 211
# use Cpanel::Validate::IP::v4 (); # perlpkg line 211
# use Cpanel::IP::NonlocalBind::Cache (); # perlpkg line 211
# use Cpanel::SV (); # perlpkg line 211
use constant {
_EADDRNOTAVAIL => 99,
_EADDRINUSE => 98
};
sub ipv4_is_bound {
my ($addr) = @_;
my $fd;
return 0 unless Cpanel::Validate::IP::v4::is_valid_ipv4($addr);
if ( index( $addr, '.' ) == 3 ) {
if ( ( substr( $addr, 0, 3 ) >= 224 ) && ( substr( $addr, 0, 3 ) < 240 ) ) {
warn "Multicast address ($addr) cannot be tested via this interface!\n";
return 0;
}
}
my $ipv4_ip_nonlocal_bind_is_enabled = Cpanel::IP::NonlocalBind::Cache::ipv4_ip_nonlocal_bind_is_enabled();
if ( !defined $ipv4_ip_nonlocal_bind_is_enabled || $ipv4_ip_nonlocal_bind_is_enabled ) {
return _slow_ipv4_is_bound_via_configured_ips($addr);
}
local $!;
socket( $fd, $Cpanel::Socket::Constants::PF_INET, $Cpanel::Socket::Constants::SOCK_STREAM, $Cpanel::Socket::Constants::IPPROTO_TCP ) or die "socket(PF_INET, SOCK_STREAM, IPPROTO_TCP): $!";
Cpanel::SV::untaint($addr);
bind( $fd, pack( 'SnC4x8', $Cpanel::Socket::Constants::AF_INET, 0, split( m{\.}, $addr ) ) ) or do {
return 1 if $! == _EADDRINUSE();
warn "bind($addr): $!\n" if $! != _EADDRNOTAVAIL();
return 0;
};
return 1;
}
sub _slow_ipv4_is_bound_via_configured_ips {
my ($addr) = @_;
require Cpanel::IP::Configured;
my $configured_ips_ar = Cpanel::IP::Configured::getconfiguredips();
foreach my $check_ip (@$configured_ips_ar) {
return 1 if $check_ip eq $addr;
}
return 0;
}
1;
} # --- END Cpanel/IP/Bound.pm
{ # --- BEGIN Cpanel/DIp/MainIP.pm
package Cpanel::DIp::MainIP;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Config::LoadWwwAcctConf (); # perlpkg line 211
# use Cpanel::GlobalCache (); # perlpkg line 211
# use Cpanel::IP::Bound (); # perlpkg line 211
# use Cpanel::LoadFile (); # perlpkg line 211
# use Cpanel::NAT (); # perlpkg line 211
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::Validate::IP::v4 (); # perlpkg line 211
our $VERSION = '1.5';
my $PRODUCT_CONF_DIR = '/var/cpanel';
my $SYSTEM_CONF_DIR = '/etc';
my $SYSTEM_SBIN_DIR = '/sbin';
my $cachedmainip = q{};
my $cachedserverip = q{};
*getmainip = *getmainsharedip;
sub getmainsharedip {
return $cachedmainip if ( $cachedmainip ne '' );
my $wwwaccthash_ref = Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf();
my $addr = q{};
if ( exists $wwwaccthash_ref->{'ADDR'} ) {
if ( !length $wwwaccthash_ref->{'ADDR'} ) {
return ( $cachedmainip = getmainserverip() );
}
elsif ( !Cpanel::Validate::IP::v4::is_valid_ipv4( $wwwaccthash_ref->{'ADDR'} ) && -x "$SYSTEM_SBIN_DIR/ip" ) {
return ( $cachedmainip = getmainserverip() );
}
elsif ( !-x "$SYSTEM_SBIN_DIR/ip" ) {
return ( $cachedmainip = $wwwaccthash_ref->{'ADDR'} );
}
$addr = $wwwaccthash_ref->{'ADDR'};
}
if ( !-x "$SYSTEM_SBIN_DIR/ip" ) {
Cpanel::Debug::log_warn("Working ip binary required to determine IP address. Please check the permissions of $SYSTEM_SBIN_DIR/ip");
return;
}
return ( $cachedmainip = $addr ) if Cpanel::IP::Bound::ipv4_is_bound($addr);
my $mainserverip = getmainserverip();
$cachedmainip = $mainserverip;
return $mainserverip;
}
sub getmainserverip {
return $cachedserverip if length $cachedserverip;
my $oldmainip = Cpanel::LoadFile::loadfile("$PRODUCT_CONF_DIR/mainip");
$oldmainip =~ tr{ \t\r\n}{}d if length $oldmainip;
if ( Cpanel::Validate::IP::v4::is_valid_ipv4($oldmainip) ) {
$cachedserverip = $oldmainip;
return $oldmainip;
}
my $wwwaccthash_ref = Cpanel::Config::LoadWwwAcctConf::loadwwwacctconf();
my $addr = $wwwaccthash_ref->{'ADDR'} // q{};
my $ethdev = $wwwaccthash_ref->{'ETHDEV'} // q{};
if ( !-x "$SYSTEM_SBIN_DIR/ip" ) {
return $addr if length $addr;
Cpanel::Debug::log_die("Fatal error: $SYSTEM_SBIN_DIR/ip is not executable, determining main server IP impossible");
}
my $wwwacct_conf_mtime = ( stat($Cpanel::Config::LoadWwwAcctConf::wwwacctconf) )[9];
my $ipconfig_mtime = 43200; #12 hours
if ( !$wwwacct_conf_mtime ) {
$ipconfig_mtime = 1;
}
else {
my $sec_since_wwwacct_conf_modified = ( time() - $wwwacct_conf_mtime );
if ( $sec_since_wwwacct_conf_modified < $ipconfig_mtime ) {
$ipconfig_mtime = $sec_since_wwwacct_conf_modified - 60;
}
}
my $thisip = _get_first_valid_ip( [ split( /\n/, Cpanel::GlobalCache::cachedmcommand( 'cpanel', $ipconfig_mtime, "$SYSTEM_SBIN_DIR/ip", '-4', 'addr', 'show', $ethdev eq '' ? () : $ethdev ) ) ] );
return ( $cachedserverip = $thisip ) if $thisip;
my $ips;
my $retry_ok = 0;
if ( !length $ethdev ) {
require Cpanel::CachedCommand;
$ips = Cpanel::CachedCommand::noncachedcommand( "$SYSTEM_SBIN_DIR/ip", '-4', 'addr', 'show' );
}
else {
$retry_ok = 1;
require Cpanel::CachedCommand;
$ips = Cpanel::CachedCommand::noncachedcommand( "$SYSTEM_SBIN_DIR/ip", '-4', 'addr', 'show', $ethdev );
}
$thisip = _get_first_valid_ip( [ split( /\n/, $ips ) ] );
return ( $cachedserverip = $thisip ) if $thisip;
if ($retry_ok) {
require Cpanel::CachedCommand;
$ips = Cpanel::CachedCommand::noncachedcommand( "$SYSTEM_SBIN_DIR/ip", '-4', 'addr', 'show' );
$thisip = _get_first_valid_ip( [ split( /\n/, $ips ) ] );
return ( $cachedserverip = $thisip ) if $thisip;
}
if ( $ethdev ne '' ) {
Cpanel::Debug::log_warn("No IP address found on $ethdev, make sure device is correctly configured, returning 0.0.0.0");
}
else {
Cpanel::Debug::log_warn("No IP address found, returning 0.0.0.0");
}
return '0.0.0.0';
}
sub getpublicmainserverip {
return Cpanel::NAT::get_public_ip( getmainserverip() );
}
sub clearcache {
$cachedmainip = '';
$cachedserverip = '';
if ( $INC{'Cpanel/DIp.pm'} ) {
Cpanel::DIp::clearcache();
}
return;
}
sub default_product_dir {
$PRODUCT_CONF_DIR = shift if @_;
return $PRODUCT_CONF_DIR;
}
sub default_conf_dir {
$SYSTEM_CONF_DIR = shift if @_;
return $SYSTEM_CONF_DIR;
}
sub default_sbin_dir {
$SYSTEM_SBIN_DIR = shift if @_;
return $SYSTEM_SBIN_DIR;
}
sub _get_first_valid_ip {
my ($ips_ref) = @_;
require Cpanel::Regex;
require Cpanel::IP::Loopback;
foreach my $ip (@$ips_ref) {
if ( $ip =~ m{ [\s\:] ($Cpanel::Regex::regex{'ipv4'}) }xoms ) {
my $thisip = $1;
if ( !Cpanel::IP::Loopback::is_loopback($thisip) ) {
return $thisip;
}
}
}
return undef;
}
1;
} # --- END Cpanel/DIp/MainIP.pm
{ # --- BEGIN Cpanel/StringFunc/Trim.pm
package Cpanel::StringFunc::Trim;
use strict;
use warnings;
no warnings 'once';
$Cpanel::StringFunc::Trim::VERSION = '1.02';
my %ws_chars = ( "\r" => undef, "\n" => undef, " " => undef, "\t" => undef, "\f" => undef );
sub trim {
my ( $str, $totrim ) = @_;
$str = rtrim( ltrim( $str, $totrim ), $totrim );
return $str;
}
sub ltrim {
my ( $str, $totrim ) = @_;
$str =~ s/^$totrim*//;
return $str;
}
sub rtrim {
my ( $str, $totrim ) = @_;
$str =~ s/$totrim*$//;
return $str;
}
sub endtrim {
my ( $str, $totrim ) = @_;
if ( substr( $str, ( length($totrim) * -1 ), length($totrim) ) eq $totrim ) {
return substr( $str, 0, ( length($str) - length($totrim) ) );
}
return $str;
}
sub begintrim {
my ( $str, $totrim ) = @_;
if (
defined $str && defined $totrim # .
&& substr( $str, 0, length($totrim) ) eq $totrim
) {
return substr( $str, length($totrim) );
}
return $str;
}
sub ws_trim {
my ($this) = @_;
return unless defined $this;
my $fix = ref $this eq 'SCALAR' ? $this : \$this;
return unless defined $$fix;
if ( $$fix =~ tr{\r\n \t\f}{} ) {
${$fix} =~ s/^\s+// if exists $ws_chars{ substr( $$fix, 0, 1 ) };
${$fix} =~ s/\s+$// if exists $ws_chars{ substr( $$fix, -1, 1 ) };
}
return ${$fix};
}
sub ws_trim_array {
my $ar = ref $_[0] eq 'ARRAY' ? $_[0] : [@_]; # [@_] :: copy @_ w/ out unpack first: !! not \@_ in this case !!
foreach my $idx ( 0 .. scalar( @{$ar} ) - 1 ) {
$ar->[$idx] = ws_trim( $ar->[$idx] );
}
return wantarray ? @{$ar} : $ar;
}
sub ws_trim_hash_values {
my $hr = ref $_[0] eq 'HASH' ? $_[0] : {@_}; # {@_} :: copy @_ w/ out unpack first:
foreach my $key ( keys %{$hr} ) {
$hr->{$key} = ws_trim( $hr->{$key} );
}
return wantarray ? %{$hr} : $hr;
}
1;
} # --- END Cpanel/StringFunc/Trim.pm
{ # --- BEGIN Cpanel/Encoder/Punycode.pm
package Cpanel::Encoder::Punycode;
use strict;
use warnings;
no warnings 'once';
our $VERSION = '1.0';
sub punycode_encode_str {
my ($string) = @_;
return $string if $string !~ tr<\x00-\x7f><>c;
my $at_at = index( $string, '@' );
require Cpanel::UTF8::Strict;
require Net::IDN::Encode;
if ( $at_at > -1 ) {
my $local_part = substr( $string, 0, $at_at );
my $domain = substr( $string, 1 + $at_at );
Cpanel::UTF8::Strict::decode($local_part);
Cpanel::UTF8::Strict::decode($domain);
return Net::IDN::Encode::domain_to_ascii($local_part) . '@' . Net::IDN::Encode::domain_to_ascii($domain);
}
Cpanel::UTF8::Strict::decode($string);
return Net::IDN::Encode::domain_to_ascii($string);
}
sub punycode_decode_str {
my ($string) = @_;
return $string if index( $string, 'xn--' ) == -1;
require Net::IDN::Encode;
my $at_at = index( $string, '@' );
if ( -1 != $at_at ) {
my $local_part = Net::IDN::Encode::domain_to_unicode( substr( $string, 0, $at_at ) );
my $domain = Net::IDN::Encode::domain_to_unicode( substr( $string, 1 + $at_at ) );
utf8::encode($local_part);
utf8::encode($domain);
return $local_part . '@' . $domain;
}
my $str = Net::IDN::Encode::domain_to_unicode($string);
utf8::encode($str);
return $str;
}
1;
} # --- END Cpanel/Encoder/Punycode.pm
{ # --- BEGIN Cpanel/Validate/Domain/Tiny.pm
package Cpanel::Validate::Domain::Tiny;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::Validate::IP (); # perlpkg line 211
sub domain_meets_basic_requirements {
my ( $domainname, $quiet ) = @_;
return wantarray ? ( 0, 'invalid domain name specified' ) : 0 unless defined $domainname;
if (
$domainname =~ tr{:0-9}{} && # It cannot be an ip address if it does not have a digit or a : in it
$domainname !~ tr{g-z}{} && # It cannot be an ip address if has non-hex characters
Cpanel::Validate::IP::is_valid_ip($domainname)
) {
Cpanel::Debug::log_warn( $domainname . ' is an IP address, not a domain name' ) if !$quiet;
return wantarray ? ( 0, 'argument is an IP address, not a domain name' ) : 0;
}
if ( length($domainname) > 254 ) {
Cpanel::Debug::log_warn( $domainname . ' domain name exceeds 254 characters' ) if !$quiet;
return wantarray ? ( 0, 'domain name exceeds 254 characters' ) : 0;
}
elsif ($domainname !~ m/[.][a-z0-9]+$/i
&& $domainname !~ m/[.]xn--[a-z0-9-]+$/i ) {
Cpanel::Debug::log_warn( $domainname . ' domain name must have a valid TLD label' ) if !$quiet;
return wantarray ? ( 0, 'domain name must have a valid TLD label' ) : 0;
}
if ( index( $domainname, '.' ) == -1 ) {
Cpanel::Debug::log_warn("invalid domain name $domainname") if !$quiet;
return wantarray ? ( 0, "invalid domain name $domainname" ) : 0;
}
return wantarray ? ( 1, 'ok' ) : 1;
}
sub validdomainname {
my ( $domainname, $quiet ) = @_;
my ( $status, $msg ) = domain_meets_basic_requirements( $domainname, $quiet );
return wantarray ? ( $status, $msg ) : $status if !$status;
LABELS_LOOP:
foreach my $label ( split( /\./, $domainname ) ) {
if (
length($label) < 64
&& length($label) > 0
&& (
$label =~ m{
\A
[a-z0-9]
[a-z0-9-]*
[a-z0-9]
\z
}xmsi
||
$label =~ m{
\A
[a-z0-9]
\z
}xmsi
)
) {
next LABELS_LOOP;
}
Cpanel::Debug::log_warn("domain name element $label does not conform to requirements") if !$quiet;
return wantarray ? ( 0, "domain name element $label does not conform to requirements" ) : 0;
}
return wantarray ? ( 1, $domainname ) : 1;
}
1;
} # --- END Cpanel/Validate/Domain/Tiny.pm
{ # --- BEGIN Cpanel/Validate/Domain/Normalize.pm
package Cpanel::Validate::Domain::Normalize;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Debug (); # perlpkg line 211
# use Cpanel::StringFunc::Trim (); # perlpkg line 211
# use Cpanel::Encoder::Punycode (); # perlpkg line 211
# use Cpanel::Validate::Domain::Tiny (); # perlpkg line 211
our $VERSION = '1.0';
sub normalize {
my ( $domain, $quiet ) = @_;
if ( !defined $domain ) {
return;
}
$domain = _just_normalize($domain);
if ( !$quiet && !Cpanel::Validate::Domain::Tiny::validdomainname($domain) ) {
Cpanel::Debug::log_info("Invalid domain $domain specified.");
}
return $domain;
}
sub normalize_wildcard {
my ($domain) = @_;
die "Domain is missing!" if !length $domain;
return _just_normalize($domain);
}
sub normalize_to_root_domain {
my ( $domain, $quiet ) = @_;
return undef unless defined $domain;
$domain = normalize( $domain, $quiet );
substr( $domain, 0, 2, '' ) if rindex( $domain, '*.', 0 ) == 0;
substr( $domain, 0, 4, '' ) if rindex( $domain, 'www.', 0 ) == 0;
return $domain;
}
sub _just_normalize {
my ($domain) = @_;
Cpanel::StringFunc::Trim::ws_trim( \$domain );
$domain =~ tr{A-Z}{a-z};
return Cpanel::Encoder::Punycode::punycode_encode_str($domain);
}
1;
} # --- END Cpanel/Validate/Domain/Normalize.pm
{ # --- BEGIN Cpanel/IP/AutoDomain/Base.pm
package Cpanel::IP::AutoDomain::Base;
use cPstrict;
no warnings 'once';
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::NAT (); # perlpkg line 211
# use Cpanel::DIp::MainIP (); # perlpkg line 211
# use Cpanel::Validate::Domain::Normalize (); # perlpkg line 211
use Simple::Accessor qw< auto_domain >;
sub build ( $self, %opts ) {
if ( my $domain = delete $opts{domain} ) {
$domain = '.' . $domain unless $domain =~ m{^\.};
$self->auto_domain($domain);
}
return $self;
}
sub _build_auto_domain {
die Cpanel::Exception::create( 'MissingParameter', 'Provide a domain or auto_domain.' );
}
sub ipv4_to_name ( $self, $ipv4 ) {
$ipv4 //= '';
return ( $ipv4 =~ tr/./-/r ) . $self->auto_domain();
}
sub get_hostname ($self) {
my $public_ip = Cpanel::NAT::get_public_ip( Cpanel::DIp::MainIP::getmainip() );
return $self->ipv4_to_name($public_ip);
}
sub is_subdomain_of_autodomain ( $self, $domain ) {
return unless defined $domain;
$domain =~ s/\.$//;
$domain =~ s!^https?://!!;
$domain = Cpanel::Validate::Domain::Normalize::normalize( $domain, 1 );
my @domain_parts = reverse( split /\./, $domain );
my @auto_domain_parts = grep { $_ ne '' } reverse( split /\./, $self->auto_domain() );
return if ( scalar(@domain_parts) < scalar(@auto_domain_parts) );
for my $i ( 0 .. $#auto_domain_parts ) {
return if $domain_parts[$i] ne $auto_domain_parts[$i];
}
return 1;
}
1;
} # --- END Cpanel/IP/AutoDomain/Base.pm
{ # --- BEGIN Cpanel/Version/Tiny.pm
package Cpanel::Version::Tiny;
use strict;
our $VERSION = '11.130.0';
our $VERSION_BUILD = '11.130.0.13';
our $VERSION_TEXT = '130.0 (build 13)';
our $VERSION_DISPLAY = '130.0.13';
our $parent_version = 11;
our $major_version = 130;
our $minor_version = 0;
our $build_number = 13;
our $build_time_text = 'Tue Sep 23 09:54:15 2025';
our $buildtime = 1758639255;
1;
} # --- END Cpanel/Version/Tiny.pm
{ # --- BEGIN Cpanel/Version/Full.pm
package Cpanel::Version::Full;
use strict;
my $full_version;
our $VERSION_FILE = '/usr/local/cpanel/version';
sub getversion {
if ( !$full_version ) {
if ( open my $ver_fh, '<', $VERSION_FILE ) {
if ( read $ver_fh, $full_version, 32 ) {
chomp($full_version);
}
elsif ($!) {
warn "read($VERSION_FILE): $!";
}
}
else {
warn "open($VERSION_FILE): $!";
}
if ( !$full_version || $full_version =~ tr{.}{} < 3 ) {
require Cpanel::Version::Tiny;
$full_version = $Cpanel::Version::Tiny::VERSION_BUILD;
}
}
return $full_version;
}
sub _clear_cache {
undef $full_version;
return;
}
1;
} # --- END Cpanel/Version/Full.pm
{ # --- BEGIN Cpanel/Version/Compare.pm
package Cpanel::Version::Compare;
use cPstrict;
no warnings 'once';
my %modes = (
'>' => sub ( $check, $against ) {
return if $check eq $against; # no need to continue if they are the same
return ( cmp_versions( $check, $against ) > 0 );
},
'<' => sub ( $check, $against ) {
return if $check eq $against; # no need to continue if they are the same
return ( cmp_versions( $check, $against ) < 0 );
},
'==' => sub ( $check, $against ) {
return ( $check eq $against || cmp_versions( $check, $against ) == 0 );
},
'!=' => sub ( $check, $against ) {
return ( $check ne $against && cmp_versions( $check, $against ) != 0 );
},
'>=' => sub ( $check, $against ) {
return 1 if $check eq $against; # no need to continue if they are the same
return ( cmp_versions( $check, $against ) >= 0 );
},
'<=' => sub ( $check, $against ) {
return 1 if $check eq $against; # no need to continue if they are the same
return ( cmp_versions( $check, $against ) <= 0 );
},
'<=>' => sub ( $check, $against ) {
return cmp_versions( $check, $against );
},
);
sub compare ( $check, $mode, $against ) {
if ( !defined $mode || !exists $modes{$mode} ) {
return;
}
foreach my $ver ( $check, $against ) {
$ver //= '';
if ( $ver !~ m{ ^((?:\d+[._]){0,}\d+[a-z]?).*?$ }axms ) {
return;
}
$ver = $1;
}
$check =~ s/_/\./g;
$against =~ s/_/\./g;
$check =~ s/([a-z])$/'.' . ord($1)/e;
$against =~ s/([a-z])$/'.' . ord($1)/e;
my @check_len = split( /[_\.]/, $check );
my @against_len = split( /[_\.]/, $against );
if ( @check_len > 4 ) {
return;
}
elsif ( @check_len < 4 ) {
for ( 1 .. 4 - @check_len ) {
$check .= '.0';
}
}
if ( @against_len > 4 ) {
return;
}
elsif ( @against_len < 4 ) {
for ( 1 .. 4 - @against_len ) {
$against .= '.0';
}
}
return if $check !~ m { \A \d+\.\d+\.\d+\.\d+ \z }axms;
return if $against !~ m { \A \d+\.\d+\.\d+\.\d+ \z }axms;
return $modes{$mode}->( $check, $against );
}
sub cmp_versions ( $left, $right ) {
my ( $maj, $min, $rev, $sup ) = split /[\._]/, $left;
my ( $mj, $mn, $rv, $sp ) = split /[\._]/, $right;
return $maj <=> $mj || $min <=> $mn || $rev <=> $rv || $sup <=> $sp;
}
sub get_major_release ( $version = '' ) {
$version =~ s/\s*//g;
my ( $major, $minor );
if ( $version =~ m/^([0-9]+)\.([0-9]+)/ ) {
$major = int $1;
$minor = int $2;
}
else {
return;
}
$minor++ if $minor % 2;
return "$major.$minor";
}
sub compare_major_release ( $check, $mode, $against ) {
return unless defined $check && defined $mode && defined $against;
my $maj1 = get_major_release($check);
return unless defined $maj1;
my $maj2 = get_major_release($against);
return unless defined $maj2;
return $modes{$mode}->( $maj1, $maj2 );
}
1;
} # --- END Cpanel/Version/Compare.pm
{ # --- BEGIN Cpanel/Version.pm
package Cpanel::Version;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Version::Full (); # perlpkg line 211
our ( $VERSION, $MAJORVERSION, $LTS ) = ( '4.0', '11.130', '11.130' );
sub get_version_text {
return sprintf( "%d.%d (build %d)", ( split( m{\.}, Cpanel::Version::Full::getversion() ) )[ 1, 2, 3 ] );
}
sub get_version_parent {
return _ver_key('parent_version');
}
sub get_version_display {
return sprintf( "%d.%d.%d", ( split( m{\.}, Cpanel::Version::Full::getversion() ) )[ 1, 2, 3 ] );
}
{
no warnings 'once'; # for updatenow
*get_version_full = *Cpanel::Version::Full::getversion;
}
sub getversionnumber {
return sprintf( "%d.%d.%d", ( split( m{\.}, Cpanel::Version::Full::getversion() ) )[ 0, 1, 2 ] );
}
sub get_lts {
return $LTS;
}
sub get_short_release_number {
my $current_ver = ( split( m{\.}, Cpanel::Version::Full::getversion() ) )[1];
if ( $current_ver % 2 == 0 ) {
return $current_ver;
}
return $current_ver + 1;
}
sub _ver_key {
require Cpanel::Version::Tiny if !$INC{'Cpanel/Version/Tiny.pm'};
return ${ $Cpanel::Version::Tiny::{ $_[0] } };
}
sub compare {
require Cpanel::Version::Compare;
goto &Cpanel::Version::Compare::compare;
}
sub is_major_version {
my ( $ver, $major ) = @_;
require Cpanel::Version::Compare;
return ( $ver eq $major || Cpanel::Version::Compare::get_major_release($ver) eq $major ) ? 1 : 0;
}
sub is_development_version {
return substr( $MAJORVERSION, -1 ) % 2 ? 1 : 0;
}
sub display_version {
my ($ver) = @_;
if ( defined $ver && $ver =~ tr{\.}{} >= 2 ) {
my @v = split( m{\.}, $ver );
if ( $v[0] == 11 && $v[1] >= 54 ) {
return join( '.', (@v)[ 1, 2, 3 ] );
}
return $ver;
}
return;
}
1;
} # --- END Cpanel/Version.pm
{ # --- BEGIN Cpanel/IP/AutoDomain.pm
package Cpanel::IP::AutoDomain;
use cPstrict;
no warnings 'once';
# use Cpanel::Hostname (); # perlpkg line 211
# use Cpanel::IP::AutoDomain::Base (); # perlpkg line 211
use constant AUTO_DOMAIN_FOR_CPANEL => 'cprapid.com'; # value used by cPanel builds (used by Whostmgr::Transfers::Systems::ServiceProxy)
use constant AUTO_DOMAIN_FOR_WP2 => 'wp2.host';
use constant AUTO_DOMAIN_FOR_HOST => 'cprapid.com';
use constant AUTO_DOMAIN_FOR_WEBSITE => 'cpanel.site';
use constant TEMP_DOMAIN_FIRST_SUPPORTED_VERSION => '130';
sub for_regular_cpanel() {
return Cpanel::IP::AutoDomain::Base->new( domain => AUTO_DOMAIN_FOR_CPANEL );
}
sub for_host() {
return Cpanel::IP::AutoDomain::Base->new( domain => AUTO_DOMAIN_FOR_HOST );
}
sub for_website() {
return Cpanel::IP::AutoDomain::Base->new( domain => AUTO_DOMAIN_FOR_WEBSITE );
}
sub for_any() {
return [ for_regular_cpanel(), for_host(), for_website() ];
}
sub is_subdomain_of_autodomain ($domain) {
foreach my $ad ( for_any()->@* ) {
return 1 if $ad->is_subdomain_of_autodomain($domain);
}
return;
}
sub for_domain ($domain) {
if ( length $domain && $domain eq Cpanel::Hostname::gethostname() ) {
return for_host();
}
return for_website();
}
sub is_temp_domains_supported {
require Cpanel::Version;
require Cpanel::Version::Compare;
return Cpanel::Version::Compare::compare( Cpanel::Version::get_short_release_number(), '>=', TEMP_DOMAIN_FIRST_SUPPORTED_VERSION );
}
sub is_for_regular_cpanel ($domain) {
return _is_for_auto_domain( $domain, AUTO_DOMAIN_FOR_CPANEL );
}
sub is_for_host ($domain) {
return _is_for_auto_domain( $domain, AUTO_DOMAIN_FOR_HOST );
}
sub is_for_website ($domain) {
return _is_for_auto_domain( $domain, AUTO_DOMAIN_FOR_WEBSITE );
}
sub _is_for_auto_domain ( $domain, $root_domain ) {
my $diff = length($domain) - length(".$root_domain");
return 0 if $diff < 0;
return rindex( $domain, ".$root_domain", $diff ) != -1 ? 1 : 0;
}
1;
} # --- END Cpanel/IP/AutoDomain.pm
{ # --- BEGIN Cpanel/IP/AutoDomain/TemporaryDomain/Constants.pm
package Cpanel::IP::AutoDomain::TemporaryDomain::Constants;
use cPstrict;
no warnings 'once';
our $TEMPORARY_DOMAIN_COMPONENTS = '/usr/local/cpanel/etc/temp-domain-components.yaml';
our $TEMPORARY_DOMAINS_IN_USE = '/var/cpanel/temporary-domains.yaml';
our $TEMPORARY_ISSUANCE_TIMEOUT = 60 * 5; # Allow up to 5 minutes for generating a unique random domain
1;
} # --- END Cpanel/IP/AutoDomain/TemporaryDomain/Constants.pm
{ # --- BEGIN Cpanel/YAML/Syck.pm
package Cpanel::YAML::Syck;
use YAML::Syck ();
sub _init {
$YAML::Syck::LoadBlessed = 0;
{
no warnings 'redefine';
*Cpanel::YAML::Syck::_init = sub { };
}
return;
}
_init();
1;
} # --- END Cpanel/YAML/Syck.pm
{ # --- BEGIN Cpanel/YAML.pm
package Cpanel::YAML;
use strict;
use YAML::Syck ();
# use Cpanel::YAML::Syck (); # perlpkg line 211
BEGIN {
*Load = *YAML::Syck::Load;
*SafeDump = \&Dump;
*DumpFile = *YAML::Syck::DumpFile;
}
our $MAX_LOAD_LENGTH = 65535;
our $MAX_PRIV_LOAD_LENGTH = 4194304; # four megs
sub _is_openhandle {
my $h = shift;
if ( my $isa = ref($h) ) {
return 1 if ( $isa eq 'GLOB' );
return 1 if ( $isa =~ m/^IO::/ );
}
return 1 if ( ref( \$h ) eq 'GLOB' );
return;
}
sub SafeLoadFile { # only allow a small bit of data to be loaded
return LoadFile( $_[0], $MAX_LOAD_LENGTH );
}
sub LoadFile {
my $file = shift;
my $max = shift;
my $str_r;
if ( _is_openhandle($file) ) {
if ($max) {
my $togo = $max;
my $buffer = '';
my $bytes_read;
while ( $bytes_read = read( $file, $buffer, $togo, length $buffer ) && length $buffer < $max ) {
$togo -= $bytes_read;
}
$str_r = \$buffer;
}
else {
$str_r = \do { local $/; <$file> };
}
}
else {
if ( !-e $file || -z $file ) {
require Carp;
Carp::croak("'$file' is non-existent or empty");
}
open( my $fh, '<', $file ) or do {
require Carp;
Carp::croak("Cannot read from $file: $!");
};
$str_r = \do { local $/; <$fh> };
}
return YAML::Syck::LoadYAML($$str_r);
}
sub Dump {
my ( $data, @extra ) = @_;
$data = _convert_json_boolean( $data, {} );
return YAML::Syck::Dump( $data, @extra );
}
sub _convert_json_boolean {
my ( $data, $seen ) = @_;
return unless defined $data;
return $data if $seen->{"$data"};
if ( my $isa = ref($data) ) {
if ( index( $isa, 'JSON::' ) == 0 ) {
if ( $isa eq 'JSON::PP::Boolean' || $isa eq 'JSON::XS::Boolean' ) {
return $data ? 1 : 0; # true / false: prefer 1/0 for roundtrip purpose
}
}
$seen->{"$data"} = 1;
if ( $isa eq 'HASH' ) {
foreach my $key ( keys %$data ) {
$data->{$key} = _convert_json_boolean( $data->{$key}, $seen );
}
}
elsif ( $isa eq 'ARRAY' ) {
foreach my $i ( 0 .. $#$data ) {
$data->[$i] = _convert_json_boolean( $data->[$i], $seen );
}
}
}
return $data;
}
1;
} # --- END Cpanel/YAML.pm
{ # --- BEGIN Cpanel/Transaction/File/Read/YAML.pm
package Cpanel::Transaction::File::Read::YAML;
use cPstrict;
no warnings 'once';
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
# use Cpanel::YAML (); # perlpkg line 211
sub _init_data {
my ( $self, %opts ) = @_;
return \undef if -z $self->{'_fh'};
my $func = \&Cpanel::YAML::Load;
my $txt = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $self->{'_fh'}, $txt );
my $load = $func->($txt);
return ref($load) ? $load : \$load;
}
1;
} # --- END Cpanel/Transaction/File/Read/YAML.pm
{ # --- BEGIN Cpanel/Transaction/File/BaseReader.pm
package Cpanel::Transaction::File::BaseReader;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Autodie qw(open exists stat close); # perlpkg line 248
INIT { Cpanel::Autodie->import(qw{open exists stat close}); }
# use Cpanel::Exception (); # perlpkg line 211
my $PACKAGE = __PACKAGE__;
sub new {
my ( $class, %opts ) = @_;
die "No file!" if !length $opts{'path'};
my $path = $opts{'path'};
my $self = bless {}, $class;
my $data;
if ( !Cpanel::Autodie::exists($path) ) {
$data = \undef;
}
else {
Cpanel::Autodie::open( my $read_fh, '<', $path );
$self->{'_original_mtime'} = ( Cpanel::Autodie::stat($read_fh) )[9];
local $self->{'_fh'} = $read_fh;
$data = $self->_init_data_with_catch(%opts);
Cpanel::Autodie::close( $read_fh, $path );
}
return bless { _data => $data, _did_init_data => 1 }, $class;
}
sub _init_data_with_catch {
my ( $self, %opts ) = @_;
my $data;
local $@;
eval { $data = $self->_init_data(%opts); 1 } or do {
die Cpanel::Exception->create(
'The system failed to load and to parse the file “[_1]” because of an error: [_2]',
[ $opts{'path'}, Cpanel::Exception::get_string($@) ]
);
};
return $data;
}
sub _init_data {
die "Do not instantiate $PACKAGE directly; use a subclass instead.";
}
sub _get_data {
if ( !$_[0]->{'_did_init_data'} ) {
$_[0]->{'_data'} = $_[0]->_init_data_with_catch( %{ $_[0]->{'_opts'} } );
$_[0]->{'_did_init_data'} = 1;
}
return $_[0]->{'_data'};
}
sub get_original_mtime {
return $_[0]->{'_original_mtime'};
}
sub path_is_newer {
return ( Cpanel::Autodie::stat( $_[0]->{'_path'} ) )[9] != $_[0]->{'_original_mtime'} ? 1 : 0;
}
sub get_fh {
return $_[0]->{'_fh'};
}
sub get_mtime {
return ( stat( $_[0]->{'_fh'} ) )[9];
}
no warnings 'once';
*get_data = \&_get_data;
1;
} # --- END Cpanel/Transaction/File/BaseReader.pm
{ # --- BEGIN Cpanel/Transaction/File/YAMLReader.pm
package Cpanel::Transaction::File::YAMLReader;
use cPstrict;
no warnings 'once';
# use Cpanel::Transaction::File::Read::YAML (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Transaction::File::Read::YAML); }
# use Cpanel::Transaction::File::BaseReader (); # perlpkg line 238
BEGIN { push @ISA, qw(Cpanel::Transaction::File::BaseReader); }
1;
} # --- END Cpanel/Transaction/File/YAMLReader.pm
{ # --- BEGIN Cpanel/Transaction/File/Read/JSON.pm
package Cpanel::Transaction::File::Read::JSON;
use strict;
# use Cpanel::LoadFile::ReadFast (); # perlpkg line 211
# use Cpanel::JSON (); # perlpkg line 211
my $READ_SIZE = 262140;
sub _init_data {
my ( $self, %opts ) = @_;
return \undef if -z $self->{'_fh'};
my $func = \&Cpanel::JSON::Load;
my $txt = '';
Cpanel::LoadFile::ReadFast::read_all_fast( $self->{'_fh'}, $txt );
my $load = $func->($txt);
return ref($load) ? $load : \$load;
}
1;
} # --- END Cpanel/Transaction/File/Read/JSON.pm
{ # --- BEGIN Cpanel/Transaction/File/JSONReader.pm
package Cpanel::Transaction::File::JSONReader;
use strict;
# use Cpanel::Transaction::File::Read::JSON (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Transaction::File::Read::JSON); }
# use Cpanel::Transaction::File::BaseReader (); # perlpkg line 238
BEGIN { push @ISA, qw(Cpanel::Transaction::File::BaseReader); }
1;
} # --- END Cpanel/Transaction/File/JSONReader.pm
{ # --- BEGIN Cpanel/IP/AutoDomain/TemporaryDomain/Check.pm
package Cpanel::IP::AutoDomain::TemporaryDomain::Check;
use cPstrict;
no warnings 'once';
# use Cpanel::Logger (); # perlpkg line 211
# use Cpanel::Exception (); # perlpkg line 211
# use Cpanel::IP::AutoDomain (); # perlpkg line 211
# use Cpanel::IP::AutoDomain::TemporaryDomain::Constants (); # perlpkg line 211
# use Cpanel::Transaction::File::YAMLReader (); # perlpkg line 211
# use Cpanel::Transaction::File::JSONReader (); # perlpkg line 211
our $_CACHED_EA4_TECH_DOMAINS_PATTERN;
our $ea4_metainfo = '/etc/cpanel/ea4/ea4-metainfo.json';
sub get_ea4_techdomains_pattern {
return $_CACHED_EA4_TECH_DOMAINS_PATTERN
if defined $_CACHED_EA4_TECH_DOMAINS_PATTERN;
return unless -f $ea4_metainfo;
my $txn = Cpanel::Transaction::File::JSONReader->new( path => $ea4_metainfo );
my $data = $txn->get_data();
my $arr_ref = $data->{tech_domains} // [];
my @valid = grep { $_ } $arr_ref->@*;
unless (@valid) {
Cpanel::Logger->new->info("'tech_domains' array in $ea4_metainfo contained only invalid or empty entries.");
return;
}
my $inner = join '|',
map { '(?:.*\.)?' . quotemeta($_) } @valid;
return ( $_CACHED_EA4_TECH_DOMAINS_PATTERN = "^(?:$inner)\$" );
}
sub domain_is_temporary_subdomain ($domain) {
return 0 unless $domain;
if ( Cpanel::IP::AutoDomain::for_website()->is_subdomain_of_autodomain($domain) ) {
return 1;
}
my $tech_pattern = get_ea4_techdomains_pattern();
return 0 unless $tech_pattern;
if ( $domain =~ qr{$tech_pattern}i ) {
return 1;
}
return 0;
}
sub is_domain_in_use ($domain) {
return 0 if !domain_is_temporary_subdomain($domain);
my $tx = Cpanel::Transaction::File::YAMLReader->new( path => $Cpanel::IP::AutoDomain::TemporaryDomain::Constants::TEMPORARY_DOMAINS_IN_USE );
my $data = $tx->get_data();
return 0 if !defined $data || ref $data ne 'HASH';
return $data->{$domain} ? 1 : 0;
}
sub domain_assigned_to ($domain) {
return undef if !domain_is_temporary_subdomain($domain);
my $tx = Cpanel::Transaction::File::YAMLReader->new( path => $Cpanel::IP::AutoDomain::TemporaryDomain::Constants::TEMPORARY_DOMAINS_IN_USE );
my $data = $tx->get_data();
return undef if ref($data) eq 'SCALAR';
return $data->{$domain};
}
sub temporary_domain_contains_local_server_ip ($domain) {
return 0 unless $domain;
return 0 unless domain_is_temporary_subdomain($domain);
my $dashed_local_ip = Cpanel::IP::AutoDomain::for_website()->get_hostname();
return 0 unless $dashed_local_ip;
return $domain =~ /\Q$dashed_local_ip\E/ ? 1 : 0;
}
sub verify_domain_assigned_to ( $domain, $user ) {
die "domain parameter undef or missing" if !length $domain;
die "user parameter undef or missing" if !length $user;
my $assigned_user = domain_assigned_to($domain);
if ( !$assigned_user ) {
die Cpanel::Exception::create( 'InvalidParameter', 'The domain “[_1]” is not temporary or does not exist.', [$domain] );
}
elsif ( $assigned_user ne $user ) {
die Cpanel::Exception::create( 'DomainOwnership', 'The account “[_1]” does not own the domain “[_2]”.', [ $user, $domain ] );
}
return 1;
}
1;
} # --- END Cpanel/IP/AutoDomain/TemporaryDomain/Check.pm
{ # --- BEGIN Cpanel/Server/Type.pm
package Cpanel::Server::Type;
use cPstrict;
no warnings 'once';
use constant NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE => 1;
sub _get_license_file_path { return q{/usr/local/cpanel/cpanel.lisc} }
sub _get_dnsonly_file_path { return q{/var/cpanel/dnsonly} }
use constant _ENOENT => 2;
use constant SERVER_TYPE => q[cpanel];
my @server_config;
our %PRODUCTS;
our $MAXUSERS;
our %FIELDS;
our ( $DNSONLY_MODE, $NODE_MODE );
sub is_dnsonly {
return $DNSONLY_MODE if defined $DNSONLY_MODE;
return 1 if -e _get_dnsonly_file_path();
return 0 if $! == _ENOENT();
my $err = $!;
if ( _read_license() ) {
return $PRODUCTS{'dnsonly'} ? 1 : 0;
}
die sprintf( 'stat(%s): %s', _get_dnsonly_file_path(), "$err" );
}
sub is_wp_squared {
return SERVER_TYPE eq 'wp2';
}
sub get_producttype {
return $NODE_MODE if defined $NODE_MODE;
return 'DNSONLY' unless _read_license();
return 'STANDARD' if $PRODUCTS{'cpanel'};
foreach my $product (qw/dnsnode mailnode databasenode dnsonly/) {
return uc($product) if $PRODUCTS{$product};
}
return 'DNSONLY';
}
sub get_max_users {
return $MAXUSERS if defined $MAXUSERS;
return NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE unless _read_license();
return $MAXUSERS // NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE;
}
sub get_license_expire_gmt_date {
return $FIELDS{'license_expire_gmt_date'} if defined $FIELDS{'license_expire_gmt_date'};
return 0 unless _read_license();
return $FIELDS{'license_expire_gmt_date'} // 0;
}
sub is_licensed_for_product ($product) {
return unless $product;
$product = lc $product;
return unless _read_license();
return exists $PRODUCTS{$product};
}
sub get_features {
return unless _read_license();
my @features = split( ",", $FIELDS{'features'} // '' );
return @features;
}
sub has_feature ( $feature = undef ) {
length $feature or return;
return ( grep { $_ eq $feature } get_features() ) ? 1 : 0;
}
sub get_products {
return unless _read_license();
return keys %PRODUCTS;
}
sub _read_license {
my $LICENSE_FILE = _get_license_file_path();
my @new_stat = stat($LICENSE_FILE) if @server_config;
if ( @server_config && @new_stat && $new_stat[9] == $server_config[9] && $new_stat[7] == $server_config[7] ) {
return 1;
}
open( my $fh, '<', $LICENSE_FILE ) or do {
if ( $! != _ENOENT() ) {
warn "open($LICENSE_FILE): $!";
}
return;
};
_reset_cache();
my $content;
read( $fh, $content, 1024 ) // do {
warn "read($LICENSE_FILE): $!";
$content = q<>;
};
return _parse_license_contents_sr( $fh, \$content );
}
sub _parse_license_contents_to_hashref ($content_sr) {
my %vals = map { ( split( m{: }, $_ ) )[ 0, 1 ] } split( m{\n}, $$content_sr );
return \%vals;
}
sub _parse_license_contents_sr ( $fh, $content_sr ) {
my $vals_hr = _parse_license_contents_to_hashref($content_sr);
if ( length $vals_hr->{'products'} ) {
%PRODUCTS = map { ( $_ => 1 ) } split( ",", $vals_hr->{'products'} );
}
else {
return;
}
if ( length $vals_hr->{'maxusers'} ) {
$MAXUSERS //= int $vals_hr->{'maxusers'};
}
else {
return;
}
foreach my $field (qw/license_expire_time license_expire_gmt_date support_expire_time updates_expire_time/) {
$FIELDS{$field} = $vals_hr->{$field} // 0;
}
foreach my $field (qw/client features/) {
$FIELDS{$field} = $vals_hr->{$field} // '';
}
if ( length $vals_hr->{'fields'} ) {
foreach my $field ( split( ",", $vals_hr->{'fields'} ) ) {
my ( $k, $v ) = split( '=', $field, 2 );
$FIELDS{$k} = $v;
}
}
else {
return;
}
@server_config = stat($fh);
return 1;
}
sub _reset_cache {
undef %PRODUCTS;
undef %FIELDS;
undef @server_config;
undef $MAXUSERS;
undef $DNSONLY_MODE;
return;
}
1;
} # --- END Cpanel/Server/Type.pm
{ # --- BEGIN Cpanel/Server/Type/Profile/Constants.pm
package Cpanel::Server::Type::Profile::Constants;
use strict;
use warnings;
no warnings 'once';
use constant {
DNSNODE => "DNSNODE",
DATABASENODE => "DATABASENODE",
DNSONLY => "DNSONLY",
MAILNODE => "MAILNODE",
STANDARD => "STANDARD"
};
our %PROFILE_CHILD_WORKLOADS = (
MAILNODE() => ['Mail'],
);
1;
} # --- END Cpanel/Server/Type/Profile/Constants.pm
{ # --- BEGIN Cpanel/Validate/AnyAllMatcher.pm
package Cpanel::Validate::AnyAllMatcher;
use cPstrict;
no warnings 'once';
sub match {
my ( $args, $callback ) = @_;
if ( !defined $args ) {
require Cpanel::Exception;
die Cpanel::Exception::create( 'MissingParameter', 'No parameter value specified.' );
}
if ( !defined $callback ) {
require Cpanel::Exception;
die Cpanel::Exception::create( 'MissingParameter', 'No callback specified.' );
}
if ( !ref $args ) {
return $callback->($args) ? 1 : 0;
}
elsif ( ref $args eq 'HASH' ) {
my $match = $args->{match} || 'all';
my $items = $args->{items};
if ( $match ne 'any' && $match ne 'all' && $match ne 'none' ) {
require Cpanel::Exception;
die Cpanel::Exception::create( 'InvalidParameter', 'The “[_1]” parameter must be “[_2]”, “[_3]” or “[_4]” value.', [qw(match any all none)] );
}
if ( !$items || ref $items ne 'ARRAY' ) {
require Cpanel::Exception;
die Cpanel::Exception::create( 'InvalidParameter', 'The “[_1]” parameter must be an array reference.', ["items"] );
}
foreach my $item (@$items) {
my $bool = $callback->($item);
return 1 if $bool && $match eq 'any';
return 0 if $bool && $match eq 'none';
return 0 if !$bool && $match eq 'all';
}
return $match eq 'any' ? 0 : 1;
}
require Cpanel::Exception;
die Cpanel::Exception::create( 'InvalidParameter', 'The input parameter must be a string or a hash reference.' );
}
1;
} # --- END Cpanel/Validate/AnyAllMatcher.pm
{ # --- BEGIN Cpanel/Server/Type/Profile.pm
package Cpanel::Server::Type::Profile;
use cPstrict;
no warnings 'once';
# use Cpanel::Server::Type (); # PPI USE OK # perlpkg line 211
# use Cpanel::Server::Type::Profile::Constants (); # PPI USE OK # perlpkg line 211
our %ENABLED_IN_ALL_PROFILES = (
'Cpanel::Server::Type::Role::JetBackup' => 1,
'Cpanel::Server::Type::Role::LiteSpeed' => 1,
'Cpanel::Server::Type::Role::MailSend' => 1,
'Cpanel::Server::Type::Role::MailLocal' => 1,
'Cpanel::Server::Type::Role::RegularCpanel' => 1,
'Cpanel::Server::Type::Role::Reseller' => 1,
);
use constant all_roles => sort map { 'Cpanel::Server::Type::Role::' . $_ } qw/
CalendarContact
DNS
FTP
FileStorage
LiteSpeed
JetBackup
MailLocal
MailReceive
MailRelay
MailSend
MySQL
Postgres
RegularCpanel
Reseller
SpamFilter
Webmail
WebDisk
WebServer
/;
our %_META = (
STANDARD => {
experimental => 0,
enabled_roles => [all_roles]
},
MAILNODE => {
experimental => 0,
enabled_roles => [
qw(
Cpanel::Server::Type::Role::CalendarContact
Cpanel::Server::Type::Role::MailReceive
Cpanel::Server::Type::Role::MailRelay
Cpanel::Server::Type::Role::Webmail
), keys %ENABLED_IN_ALL_PROFILES
],
optional_roles => [
qw(
Cpanel::Server::Type::Role::MySQL
Cpanel::Server::Type::Role::Postgres
Cpanel::Server::Type::Role::DNS
Cpanel::Server::Type::Role::SpamFilter
)
]
},
DNSNODE => {
experimental => 0,
enabled_roles => [
qw(
Cpanel::Server::Type::Role::DNS
), keys %ENABLED_IN_ALL_PROFILES
],
optional_roles => [
qw(
Cpanel::Server::Type::Role::MySQL
Cpanel::Server::Type::Role::MailRelay
)
],
},
DATABASENODE => {
experimental => 1,
enabled_roles => [
qw(
Cpanel::Server::Type::Role::MySQL
), keys %ENABLED_IN_ALL_PROFILES
],
optional_roles => [
qw(
Cpanel::Server::Type::Role::Postgres
)
]
}
);
our ( $DNSNODE_MODE, $MAILNODE_MODE, $DATABASENODE_MODE );
my $_CURRENT_PROFILE;
sub get_current_profile {
return $_CURRENT_PROFILE if defined $_CURRENT_PROFILE;
my $product_type = Cpanel::Server::Type::get_producttype();
if ( $product_type && $product_type ne Cpanel::Server::Type::Profile::Constants::STANDARD() ) {
return $_CURRENT_PROFILE = $product_type;
}
my $roles = {};
require Cpanel::LoadModule;
PROFILE: foreach my $profile ( keys %_META ) {
next if $profile eq Cpanel::Server::Type::Profile::Constants::STANDARD();
my $disabled_roles_ar = get_disabled_roles_for_profile($profile);
if ($disabled_roles_ar) {
foreach my $role (@$disabled_roles_ar) {
if ( !exists $roles->{$role} ) {
Cpanel::LoadModule::load_perl_module($role);
$roles->{$role} = $role->is_enabled();
}
next PROFILE if $roles->{$role};
}
}
if ( $_META{$profile}{enabled_roles} ) {
foreach my $role ( @{ $_META{$profile}{enabled_roles} } ) {
if ( !exists $roles->{$role} ) {
Cpanel::LoadModule::load_perl_module($role);
$roles->{$role} = $role->is_enabled();
}
next PROFILE if !$roles->{$role};
}
}
return $_CURRENT_PROFILE = $profile;
}
return $_CURRENT_PROFILE = Cpanel::Server::Type::Profile::Constants::STANDARD();
}
sub current_profile_matches {
my ($profiles_ar) = @_;
$profiles_ar = [$profiles_ar] if 'ARRAY' ne ref $profiles_ar;
my $current_profile = get_current_profile();
return grep { $_ eq $current_profile } @{$profiles_ar};
}
sub is_valid_for_profile ($rule) {
if ( ref $rule ne 'HASH' ) {
return current_profile_matches($rule);
}
if ( !ref $rule->{items} ) {
require Data::Dumper;
die q[Invalid rule 'missing items entry' ] . Data::Dumper::Dumper($rule);
}
require Cpanel::Validate::AnyAllMatcher;
return Cpanel::Validate::AnyAllMatcher::match( $rule, \¤t_profile_matches );
}
my $_loaded_descriptions;
sub get_meta {
if ($_loaded_descriptions) {
foreach my $profile ( keys %_META ) {
delete @{ $_META{$profile} }{qw(name description)};
$_loaded_descriptions = 0;
}
}
return \%_META;
}
sub get_meta_with_descriptions {
if ( !$_loaded_descriptions ) {
require 'Cpanel/Server/Type/Profile/Descriptions.pm'; ## no critic qw(Bareword) - hide from perlpkg
my $add_hr = \%Cpanel::Server::Type::Profile::Descriptions::_META;
foreach my $profile ( keys %$add_hr ) {
@{ $_META{$profile} }{ keys %{ $add_hr->{$profile} } } = values %{ $add_hr->{$profile} };
}
}
return \%_META;
}
sub get_disabled_roles_for_profile {
my ($profile) = @_;
my $all_possible_roles = get_all_possible_roles();
my $meta = get_meta(); # call get_meta since it may be mocked
die "No META for profile “$profile”!" if !defined $meta->{$profile};
my %profile_roles = map { $_ => 1 } ( ( $meta->{$profile}{enabled_roles} ? @{ $meta->{$profile}{enabled_roles} } : () ), ( $meta->{$profile}{optional_roles} ? @{ $meta->{$profile}{optional_roles} } : () ) );
my @disabled_roles = grep { !$profile_roles{$_} } @$all_possible_roles;
return @disabled_roles ? \@disabled_roles : undef;
}
sub get_all_possible_roles {
return [all_roles];
}
sub get_service_subdomains_for_profile {
my ($profile) = @_;
my $meta = get_meta(); # call get_meta since it may be mocked
die "No META for profile “$profile”!" if !defined $meta->{$profile};
my @profile_roles = ( ( $meta->{$profile}{enabled_roles} ? @{ $meta->{$profile}{enabled_roles} } : () ), ( $meta->{$profile}{optional_roles} ? @{ $meta->{$profile}{optional_roles} } : () ) );
require 'Cpanel/Server/Type/Change/Backend.pm'; ## no critic qw(Bareword) - hide from perlpkg
my @service_subdomains;
push @service_subdomains, Cpanel::Server::Type::Change::Backend::get_role_service_subs($_) for @profile_roles;
return \@service_subdomains;
}
sub _reset_cache {
undef $_CURRENT_PROFILE;
return;
}
1;
} # --- END Cpanel/Server/Type/Profile.pm
{ # --- BEGIN Cpanel/Server/Type/Role/EnabledCache.pm
package Cpanel::Server::Type::Role::EnabledCache;
use cPstrict;
no warnings 'once';
use Carp ();
my %_THE_CACHE;
sub set ( $class, $value ) {
_validate_class($class);
if ( $value ne '0' && $value ne '1' ) {
_confess("Value must be 0 or 1, not “$value”.");
}
return $_THE_CACHE{$class} = $value;
}
sub get ($class) {
_validate_class($class);
return $_THE_CACHE{$class};
}
sub unset ($class) {
_validate_class($class);
return delete $_THE_CACHE{$class};
}
sub _confess ($msg) {
local $Carp::Internal{ (__PACKAGE__) } = 1;
return Carp::confess($msg);
}
sub _validate_class ($class) {
_confess("Give a class name, not $class!") if ref $class;
return;
}
sub _unset_all () {
%_THE_CACHE = ();
return;
}
1;
} # --- END Cpanel/Server/Type/Role/EnabledCache.pm
{ # --- BEGIN Cpanel/Server/Type/Role.pm
package Cpanel::Server::Type::Role;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Server::Type::Profile (); # perlpkg line 211
# use Cpanel::Server::Type::Profile::Constants (); # perlpkg line 211
# use Cpanel::Server::Type (); # perlpkg line 211
# use Cpanel::Server::Type::Role::EnabledCache (); # perlpkg line 211
sub new {
return bless {}, $_[0];
}
sub is_enabled {
my ($obj_or_class) = @_;
my $ref = ref($obj_or_class) || $obj_or_class;
my $product_type = Cpanel::Server::Type::get_producttype();
if ( $product_type eq Cpanel::Server::Type::Profile::Constants::DNSONLY() ) {
return Cpanel::Server::Type::Role::EnabledCache::set( $ref, 1 );
}
if ( $product_type ne Cpanel::Server::Type::Profile::Constants::STANDARD() ) {
my $META = Cpanel::Server::Type::Profile::get_meta();
return Cpanel::Server::Type::Role::EnabledCache::set( $ref, 1 ) if grep { $_ eq $ref } @{ $META->{$product_type}{enabled_roles} };
return Cpanel::Server::Type::Role::EnabledCache::set( $ref, 0 ) if !grep { $_ eq $ref } @{ $META->{$product_type}{optional_roles} };
}
my $val = Cpanel::Server::Type::Role::EnabledCache::get($ref);
$val //= Cpanel::Server::Type::Role::EnabledCache::set(
$ref,
$obj_or_class->is_available() && $obj_or_class->_is_enabled() ? 1 : 0,
);
return $val;
}
our %_AVAILABLE_CACHE;
sub is_available {
my ($obj_or_class) = @_;
my $ref = ref($obj_or_class) || $obj_or_class;
return $_AVAILABLE_CACHE{$ref} //= $obj_or_class->_is_available();
}
sub verify_enabled {
my ($class) = @_;
if ( !$class->is_enabled() ) {
my $role = substr( $class, 1 + rindex( $class, ':' ) );
require Cpanel::Exception;
die Cpanel::Exception::create( 'System::RequiredRoleDisabled', [ role => $role ] );
}
return;
}
sub SERVICES { return [] }
sub RESTART_SERVICES { return [] }
sub SERVICE_SUBDOMAINS {
return shift()->_SERVICE_SUBDOMAINS();
}
use constant _SERVICE_SUBDOMAINS => [];
sub RPM_TARGETS {
return shift()->_RPM_TARGETS();
}
use constant _RPM_TARGETS => [];
sub _is_available { return 1 }
sub _NAME {
require Cpanel::Exception;
die Cpanel::Exception::create( 'AbstractClass', [__PACKAGE__] );
}
*_DESCRIPTION = *_NAME;
1;
} # --- END Cpanel/Server/Type/Role.pm
{ # --- BEGIN Cpanel/Server/Type/Role/TouchFileRole.pm
package Cpanel::Server::Type::Role::TouchFileRole;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Server::Type::Role (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Server::Type::Role); }
our $ROLES_TOUCHFILE_BASE_PATH = "/var/cpanel/disabled_roles";
sub _is_enabled {
return !$_[0]->check_touchfile();
}
sub check_touchfile {
require Cpanel::Autodie;
return Cpanel::Autodie::exists( $_[0]->_TOUCHFILE() );
}
sub _TOUCHFILE {
require Cpanel::Exception;
die Cpanel::Exception::create( 'AbstractClass', [__PACKAGE__] );
}
1;
} # --- END Cpanel/Server/Type/Role/TouchFileRole.pm
{ # --- BEGIN Cpanel/Server/Type/Role/MailRelay.pm
package Cpanel::Server::Type::Role::MailRelay;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Server::Type::Role::TouchFileRole (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Server::Type::Role::TouchFileRole); }
my ( $NAME, $DESCRIPTION );
our $TOUCHFILE = $Cpanel::Server::Type::Role::TouchFileRole::ROLES_TOUCHFILE_BASE_PATH . "/mailrelay";
our $SERVICES = [
'exim',
'exim-altport',
];
sub _NAME {
require 'Cpanel/LocaleString.pm'; ## no critic qw(Bareword) - hide from perlpkg
$NAME ||= Cpanel::LocaleString->new("Relay Mail");
return $NAME;
}
sub _DESCRIPTION {
require 'Cpanel/LocaleString.pm'; ## no critic qw(Bareword) - hide from perlpkg
$DESCRIPTION ||= Cpanel::LocaleString->new("This role allows users to relay email through this server.");
return $DESCRIPTION;
}
sub _TOUCHFILE { return $TOUCHFILE; }
sub SERVICES { return $SERVICES; }
1;
} # --- END Cpanel/Server/Type/Role/MailRelay.pm
{ # --- BEGIN Cpanel/Server/Type/Role/MailSend.pm
package Cpanel::Server::Type::Role::MailSend;
use strict;
use warnings;
no warnings 'once';
# use Cpanel::Server::Type::Role::TouchFileRole (); # perlpkg line 238
our @ISA;
BEGIN { push @ISA, qw(Cpanel::Server::Type::Role::TouchFileRole); }
my ( $NAME, $DESCRIPTION );
our $TOUCHFILE = $Cpanel::Server::Type::Role::TouchFileRole::ROLES_TOUCHFILE_BASE_PATH . "/mailsend";
our $SERVICES = [
'exim',
'exim-altport',
];
sub _NAME {
require 'Cpanel/LocaleString.pm'; ## no critic qw(Bareword) - hide from perlpkg
$NAME ||= Cpanel::LocaleString->new("Send Mail");
return $NAME;
}
sub _DESCRIPTION {
require 'Cpanel/LocaleString.pm'; ## no critic qw(Bareword) - hide from perlpkg
$DESCRIPTION ||= Cpanel::LocaleString->new("Send Mail allows users to send email.");
return $DESCRIPTION;
}
sub _TOUCHFILE { return $TOUCHFILE; }
sub SERVICES { return $SERVICES; }
1;
} # --- END Cpanel/Server/Type/Role/MailSend.pm
package main;