Current Path : /proc/2/root/etc/ |
|
Current File : //proc/2/root/etc/exim.pl.local |
=encoding utf-8
=head1 NAME
/etc/exim.pl.local - Perl functions for exim that are loaded by /etc/exim.pl
=cut
my $VALIASES_DIR = '/etc/valiases';
my $VDOMAINALIASES_DIR = '/etc/vdomainaliases';
my $outgoing_mail_suspended_message;
my $outgoing_sender;
my $outgoing_sender_domain;
my $outgoing_sender_counted_domain;
my $outgoing_sender_sysuser;
my $outgoing_sender_is_mailman;
my $outgoing_sender_archive_directory = 'outgoing';
my $mail_gid;
my $nobody_uid;
my $nobody_gid;
my $mailtrap_gid;
my $check_mail_permissions_domain = '';
my $check_mail_permissions_sender = '';
my $check_mail_permissions_msgid = '';
my $check_mail_permissions_data = '';
my $check_mail_permissions_is_mailman = 0;
my $enforce_mail_permissions_data = '';
my $primary_hostname;
my %uid_cache = ( 0 => 'root', 47 => 'mailnull', 99 => 'nobody' );
my %user_cache = ( 'root' => 0, 'mailnull' => 47, 'nobody' => 99 );
my $reattempt_message = 'Message will be reattempted later';
my $sender_lookup;
my $sender_lookup_method;
# TEST VARIABLES
my $check_mail_permissions_result;
my %file_exists_cache;
sub file_exists {
return $file_exists_cache{ $_[0] } if exists $file_exists_cache{ $_[0] };
$file_exists_cache{ $_[0] } = -e $_[0] ? 1 : 0;
return $file_exists_cache{ $_[0] };
}
sub checkbx_autowhitelist {
my $address = shift;
my $phost = Exim::expand_string('$primary_hostname');
my $rp = Exim::expand_string('$received_protocol');
if ( $rp eq 'local' || $rp !~ /^e?smtps?a$/i || !$address || $address eq '' ) { return 'no'; }
my ( $localpart, $domain ) = split( /\@/, $address );
if ( ( !$domain || $domain eq '' || $domain eq $phost ) ) {
my $homedir = gethomedir($localpart);
unless ( $homedir ne '' ) {
return 'no';
}
if ( -e $homedir . '/etc/.boxtrapperenable' && !-e $homedir . '/etc/.boxtrapperautowhitelistdisable' ) {
return 'yes';
}
else {
return 'no';
}
}
else {
my $owner = getdomainowner($domain);
my $homedir = gethomedir($owner);
unless ( $homedir ne '' ) {
return 'no';
}
my $passwd = "${homedir}/etc/${domain}/passwd";
my $addressexists = user_exists_in_db( $localpart, $passwd );
if ( $addressexists && ( -e $homedir . "/etc/${domain}/${localpart}/.boxtrapperenable" && !-e $homedir . "/etc/${domain}/${localpart}/.boxtrapperautowhitelistdisable" ) ) {
return 'yes';
}
else {
return 'no';
}
}
}
sub getemailuser {
my ( $address, $received_protocol, $sender_ident ) = @_;
my $primary_hostname = Exim::expand_string('$primary_hostname');
my ( $local_part, $domain ) = split( m/[\@\+\%\:]/, ( $address || ( $received_protocol && $received_protocol eq 'local' ? $sender_ident : '' ) ) );
if ( !$domain || $domain eq '' || $domain eq $primary_hostname ) {
return $local_part;
}
else {
my $user = getdomainowner($domain);
if ($user) { return $user; }
}
return 'nobody';
}
#DO NOT REMOVE THIS COMMENT AS IT TELLS CPANEL TO ENABLE SERVICE AUTH CHECKING
#exim:serviceauth=1
#
# Checkpass not used since auth is passed to dovecot SASL
{
no warnings 'redefine';
sub checkuserpass { 0; }
sub checkpass { 0; }
}
sub checkspam {
# This is an old code block that should never be reached unless there is a serious
# problem installing their exim configuration
Exim::log_write("Something went very wrong during the exim configuration update. Please try reinstalling your exim configuration.");
1;
}
sub convert_address_directory_to_dovecot_lda_destination_username {
my $local_part = Exim::expand_string('$local_part');
my $domain = Exim::expand_string('$domain');
$primary_hostname ||= Exim::expand_string('$primary_hostname');
my $address_file = Exim::expand_string('$address_file');
if ( $address_file !~ m{mail/\Q$domain\E} ) {
return ( getpwuid($>) )[0];
}
else {
return $local_part . '@' . $domain;
}
}
sub convert_address_directory_to_dovecot_lda_mailbox {
my $address_file = Exim::expand_string('$address_file');
my ($mailbox) = $address_file =~ m{/\.([^\/]+)};
if ($mailbox) {
return "INBOX.$mailbox";
}
return 'INBOX';
}
sub call_cpwrap {
my ( $function, @ARGS ) = @_;
my @JSON_ENCODED_ARGS = map { aggressive_json_safe_encode($_) } @ARGS;
my $data = join( ' ', @JSON_ENCODED_ARGS );
my $json_template = qq[{"function":"$function","namespace":"Cpanel","version":2,"action":"run","data":"$data","send_data_only":1,"module":"exim"}\r\n\r\n];
require Cpanel::Encoder::Exim;
return eval { Exim::expand_string( '${readsocket{/usr/local/cpanel/var/cpwrapd.sock}{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($json_template) . '}{10s}}' ); };
}
sub aggressive_json_safe_encode {
my ($arg) = @_;
$arg =~ tr/^a-zA-Z0-9!#\$\-=?^_{}~:.//cd;
return $arg;
}
my $archived_at_domain_level = 0;
my $archived_outgoing = 0;
my $archived_mailman = 0;
sub should_archive_incoming_domain_message {
return ( $archived_at_domain_level = !_message_has_been_seen() );
}
sub _message_has_been_seen {
#ARCHIVE ONLY IF
#
#$parent_domain = ""
#
#OR
#
#$parent_domain != $domain
# Delivery was not a result of an expansion
my $parent_domain = Exim::expand_string('$parent_domain');
if ( !length $parent_domain ) {
return 0;
}
# Delivery was the result of an expansion / alias. Since its a diffrent domain we don't
# know if it was archived so we need to archive if enabled
my $domain = Exim::expand_string('$domain');
if ( $domain ne $parent_domain ) {
return 0;
}
my $parent_local_part = Exim::expand_string('$parent_local_part');
my $local_part = Exim::expand_string('$local_part');
# case 60975: If any deliveries happened, parent_domain and parent_local_part
# will get set to match domain and local_part. Since we need to
# still archive outgoing if it to our same domain or a local
# user we need to accept when they all match
if ( $parent_domain eq $domain && $local_part && $parent_local_part ) {
return 0;
}
# parent_local_part ne local_part and
# parent_domain == domain so it already got archived if we have it on
return 1;
}
sub archive_headers {
my ($archive_router) = @_;
if ( $archive_router eq 'incoming_domain' ) {
return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part') . '@' . Exim::expand_string('$domain');
}
elsif ( $archive_router eq 'incoming_localuser' ) {
return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part');
}
elsif ( $archive_router eq 'outgoing' ) {
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;
}
# The pack and unpack functions save and restore the state of the
# sender globals so that when the delivery happens in a separate
# process then the reception of the message the state is restored
# and the correct domain/sender is assigned/archived.
my $SOH_BYTE = chr(1);
my $PACK_KEY = 'packed_archive_address_data';
sub pack_archive_address_data {
return join( $SOH_BYTE, map { s/$SOH_BYTE//g; $_; } ( $PACK_KEY, $outgoing_sender, $outgoing_sender_domain, $outgoing_sender_sysuser, $outgoing_sender_archive_directory ) );
}
#
# Sometimes the transport happens in a different process than the router
# so we need to extract the data that was saved in exim's $address_data which
# was preserved between the router and transport so we can restore the variables
# we use to determine how to archive the message.
#
sub unpack_archive_address_data {
my ($packed_address_data) = @_;
my @unpacked = split( m{$SOH_BYTE}, $packed_address_data );
if ( shift @unpacked eq $PACK_KEY ) {
( $outgoing_sender, $outgoing_sender_domain, $outgoing_sender_sysuser, $outgoing_sender_archive_directory ) = @unpacked;
return 1;
}
return 0;
}
sub get_outgoing_sender {
unpack_archive_address_data( Exim::expand_string('$address_data') ) if !defined $outgoing_sender_domain;
return $outgoing_sender;
}
sub get_outgoing_sender_domain {
unpack_archive_address_data( Exim::expand_string('$address_data') ) if !defined $outgoing_sender_domain;
return $outgoing_sender_domain;
}
sub get_outgoing_sender_sysuser {
unpack_archive_address_data( Exim::expand_string('$address_data') ) if !defined $outgoing_sender_domain;
return $outgoing_sender_sysuser;
}
sub get_outgoing_archive_directory {
unpack_archive_address_data( Exim::expand_string('$address_data') ) if !defined $outgoing_sender_domain;
return $outgoing_sender_archive_directory;
}
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;
$local_connection_user = uid2user($uid);
$sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = $local_connection_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,
};
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 sender_domain_can_dkim_sign {
require Cpanel::DKIM::ValidityCache;
my $sender_domain = get_message_sender_domain();
if ( $sender_domain eq '-system-' ) {
$sender_domain = Exim::expand_string('$sender_address_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] ) || '-system-'; #from MailAuth.pm
}
else {
return ( split( m/[@]/, $address, 2 ) )[1]; #from MailAuth.pm
}
}
else {
return getusersdomain($address) || '-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) || '-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) || '-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) || '-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 -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) || '-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 '-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::MailRelay->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 '-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 '-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;
}
require Cpanel::Encoder::Exim;
require Cpanel::Server::Type::Role::MailRelay;
1;
BEGIN { # Suppress load of all of these at earliest point.
$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/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/LoadModule.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.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';
}
{ # --- 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 ();
*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;
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;
# use Cpanel::ExceptionMessage();
our @ISA;
BEGIN { push @ISA, qw(Cpanel::ExceptionMessage); }
# use Cpanel::Locale::Utils::Fallback ();
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;
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;
sub blessed {
return ref( $_[0] ) && UNIVERSAL::isa( $_[0], 'UNIVERSAL' ) || undef;
}
1;
} # --- END Cpanel/ScalarUtil.pm
{ # --- BEGIN Cpanel/Exception/CORE.pm
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 ();
# use Cpanel::LoadModule::Utils ();
use constant _TRUE => 1;
use overload (
'""' => \&__spew,
bool => \&_TRUE,
fallback => 1,
);
BEGIN {
die "Cannot compile Cpanel::Exception::CORE" if $INC{'B/C.pm'};
}
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 {
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];
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;
}
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;
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;
sub is_valid_ipv4 {
return unless $_[0]; # False scalars are never an _[0].
my @segments = split /\./, $_[0], -1;
return #
(
scalar @segments != 4 || #
( !length $segments[3] || $segments[3] =~ tr/0-9//c || $segments[3] > 255 ) || #
( !length $segments[2] || $segments[2] =~ tr/0-9//c || $segments[2] > 255 ) || #
( !length $segments[1] || $segments[1] =~ tr/0-9//c || $segments[1] > 255 ) || #
( !length $segments[0] || !$segments[0] || $segments[0] =~ tr/0-9//c || $segments[0] > 255 )
) ? undef : 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;
}
1;
} # --- END Cpanel/Validate/IP/v4.pm
{ # --- BEGIN Cpanel/Validate/IP.pm
package Cpanel::Validate::IP;
use strict;
use warnings;
# use Cpanel::Validate::IP::v4 ();
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 1 if is_valid_ip_cidr_or_prefix($str);
my @pieces = split /-/, $str, 2;
return 1 if 2 == map { Cpanel::Validate::IP::v4::is_valid_ipv4($_) } @pieces;
return 1 if 2 == 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;
# use Cpanel::Validate::IP ();
# use Cpanel::Validate::IP::v4 ();
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;
# use Cpanel::Validate::IP::v4 ();
# use Cpanel::Validate::IP::Expand ();
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;
use constant DEBUG => 0;
# use Cpanel::Exception ();
# use Cpanel::Pack ();
# use Cpanel::Pack::Template ();
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 = 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
&& 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;
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;
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;
# use Cpanel::Autodie::CORE::exists(); # PPI NO PARSE
1;
} # --- END Cpanel/Autodie/CORE/exists_nofollow.pm
{ # --- BEGIN Cpanel/Autodie/More/Lite.pm
package Cpanel::Autodie::More::Lite;
use strict;
use warnings;
# use Cpanel::Autodie ();
# use Cpanel::Autodie::CORE::exists (); # PPI USE OK - reload so we can map the symbol below
# use Cpanel::Autodie::CORE::exists_nofollow (); # PPI USE OK - reload so we can map the symbol below
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;
# use Cpanel::Autodie::More::Lite ();
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;
# use Cpanel::Exception ();
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;
# use Cpanel::Autodie ();
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;
# use Cpanel::Exception ();
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;
# use Cpanel::Context ();
# use Cpanel::Autodie ();
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;
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;
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 $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 ();
# use Cpanel::Socket::Constants ();
*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 ();
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/Server/Type.pm
package Cpanel::Server::Type;
use strict;
use warnings;
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;
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 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 _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, 512 ) // do {
warn "read($LICENSE_FILE): $!";
$content = q<>;
};
return _parse_license_contents_sr( $fh, \$content );
}
sub _parse_license_contents_to_hashref {
my ($content_sr) = @_;
my %vals = map { ( split( m{: }, $_ ) )[ 0, 1 ] } split( m{\n}, $$content_sr );
return \%vals;
}
sub _parse_license_contents_sr {
my ( $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;
}
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;
use constant {
DNSNODE => "DNSNODE",
DATABASENODE => "DATABASENODE",
DNSONLY => "DNSONLY",
MAILNODE => "MAILNODE",
STANDARD => "STANDARD"
};
1;
} # --- END Cpanel/Server/Type/Profile/Constants.pm
{ # --- BEGIN Cpanel/LoadModule.pm
package Cpanel::LoadModule;
use strict;
# use Cpanel::Exception ();
# use Cpanel::LoadModule::Utils ();
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/Server/Type/Profile.pm
package Cpanel::Server::Type::Profile;
use strict;
use warnings;
# use Cpanel::Server::Type ();
# use Cpanel::Server::Type::Profile::Constants ();
our %ENABLED_IN_ALL_ROLES = (
'Cpanel::Server::Type::Role::MailSend' => 1,
'Cpanel::Server::Type::Role::MailLocal' => 1,
);
our %_META = (
STANDARD => {
experimental => 0,
enabled_roles => [
qw(
Cpanel::Server::Type::Role::CalendarContact
Cpanel::Server::Type::Role::DNS
Cpanel::Server::Type::Role::FTP
Cpanel::Server::Type::Role::FileStorage
Cpanel::Server::Type::Role::MailReceive
Cpanel::Server::Type::Role::MailRelay
Cpanel::Server::Type::Role::MySQL
Cpanel::Server::Type::Role::Postgres
Cpanel::Server::Type::Role::SpamFilter
Cpanel::Server::Type::Role::Webmail
Cpanel::Server::Type::Role::WebDisk
Cpanel::Server::Type::Role::WebServer
), keys %ENABLED_IN_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_ROLES
],
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_ROLES
],
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_ROLES
],
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};
}
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;
}
my $_all_possible_roles;
sub get_all_possible_roles {
return $_all_possible_roles if $_all_possible_roles;
my $meta_std_hr = get_meta()->{ Cpanel::Server::Type::Profile::Constants::STANDARD() };
for my $nonono (qw( disabled optional )) {
die "STANDARD is expected not to have “$nonono”!" if $meta_std_hr->{"${nonono}_roles"};
}
return ( $_all_possible_roles = $meta_std_hr->{'enabled_roles'} );
}
sub _clear_all_possible_roles {
undef $_all_possible_roles;
return;
}
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; # PPI NO PARSE - require to hide from perlpkg
return \@service_subdomains;
}
sub _reset_cache {
undef $_CURRENT_PROFILE;
return;
}
1;
} # --- END Cpanel/Server/Type/Profile.pm
{ # --- BEGIN Cpanel/Server/Type/Role.pm
package Cpanel::Server::Type::Role;
use strict;
use warnings;
# use Cpanel::Server::Type::Profile ();
# use Cpanel::Server::Type::Profile::Constants ();
# use Cpanel::Server::Type ();
sub new {
return bless {}, $_[0];
}
our %_ENABLED_CACHE;
sub is_enabled {
my ($obj_or_class) = @_;
my $ref = ref($obj_or_class) || $obj_or_class;
my $product_type = Cpanel::Server::Type::get_producttype();
return $_ENABLED_CACHE{$ref} = 1 if $product_type eq Cpanel::Server::Type::Profile::Constants::DNSONLY();
if ( $product_type ne Cpanel::Server::Type::Profile::Constants::STANDARD() ) {
my $META = Cpanel::Server::Type::Profile::get_meta();
return $_ENABLED_CACHE{$ref} = 1 if grep { $_ eq $ref } @{ $META->{$product_type}{enabled_roles} };
return $_ENABLED_CACHE{$ref} = 0 if !grep { $_ eq $ref } @{ $META->{$product_type}{optional_roles} };
}
return $_ENABLED_CACHE{$ref} //= $obj_or_class->is_available() && $obj_or_class->_is_enabled();
}
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 { die 'abstract' }
*_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;
# use Cpanel::Server::Type::Role();
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 _NAME {
require Cpanel::Exception;
die Cpanel::Exception::create( 'AbstractClass', [__PACKAGE__] );
}
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;
# use Cpanel::Server::Type::Role::TouchFileRole();
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
package main;
Copyright 2K16 - 2K18 Indonesian Hacker Rulez