ÿØÿà JFIF ÿþ; %PDF-1.5 %���� ºaâÚÎΞ-ÌE1ÍØÄ÷{òò2ÿ ÛÖ^ÔÀá TÎ{¦?§®¥kuµùÕ5sLOšuY
Server IP : 157.90.209.209 / Your IP : 216.73.216.129 [ Web Server : Apache System : Linux hcomm124.dns-wk.info 4.18.0-553.64.1.el8_10.x86_64 #1 SMP Mon Jul 28 12:01:56 EDT 2025 x86_64 User : evidenciarevista ( 1049) PHP Version : 7.2.34 Disable Function : exec,passthru,shell_exec,system Domains : 216 Domains MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : OFF | Sudo : ON | Pkexec : ON Directory : /etc/ |
Upload File : |
=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/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/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/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/Debug.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/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/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/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/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/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::Destruct (); # perlpkg line 211 # use Cpanel::Debug (); # perlpkg line 211 sub new ( $class, @todo_crs ) { return bless { 'pid' => $$, 'todo' => [@todo_crs] }, $class; } sub add ( $self, @new_crs ) { $self->{'todo'} //= []; push @{ $self->{'todo'} }, @new_crs; return; } sub skip ($self) { return delete $self->{'todo'}; } sub DESTROY ($self) { if ( Cpanel::Destruct::in_dangerous_global_destruction() ) { Cpanel::Debug::log_die(q[Cpanel::Finally should never be triggered during global destruction\n]); } return if $$ != $self->{'pid'} || !$self->{'todo'}; 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.5'; our $VERSION_TEXT = '130.0 (build 5)'; our $VERSION_DISPLAY = '130.0.5'; our $parent_version = 11; our $major_version = 130; our $minor_version = 0; our $build_number = 5; our $build_time_text = 'Thu Aug 21 10:32:39 2025'; our $buildtime = 1755790359; 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::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) { warn "'tech_domains' array in $ea4_metainfo contained only invalid or empty entries.\n"; 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;