ÿØÿà JFIF ÿþ >CREATOR: gd-jpeg v1.0 (using IJG JPEG v62), default quality
ÿÛ C
Server IP : 104.21.29.46 / Your IP : 216.73.216.123 Web Server : Apache System : Linux server1.morocco-tours.com 3.10.0-1127.19.1.el7.x86_64 #1 SMP Tue Aug 25 17:23:54 UTC 2020 x86_64 User : zagoradraa ( 1005) PHP Version : 7.4.33 Disable Function : NONE MySQL : OFF | cURL : ON | WGET : ON | Perl : ON | Python : ON | Sudo : ON | Pkexec : ON Directory : /etc/ |
Upload File : |
| Current File : /etc//exim.pl.local |
=encoding utf-8
=head1 NAME
/etc/exim.pl.local - Perl functions for exim that are loaded by /etc/exim.pl
=cut
my $VALIASES_DIR = '/etc/valiases';
my $VDOMAINALIASES_DIR = '/etc/vdomainaliases';
my $outgoing_mail_suspended_message;
my $outgoing_sender;
my $outgoing_sender_domain;
my $outgoing_sender_counted_domain;
my $outgoing_sender_sysuser;
my $outgoing_sender_is_mailman;
my $outgoing_sender_archive_directory = 'outgoing';
my $mail_gid;
my $nobody_uid;
my $nobody_gid;
my $mailtrap_gid;
my $check_mail_permissions_domain = '';
my $check_mail_permissions_sender = '';
my $check_mail_permissions_msgid = '';
my $check_mail_permissions_data = '';
my $check_mail_permissions_is_mailman = 0;
my $enforce_mail_permissions_data = '';
my $primary_hostname;
my %uid_cache = ( 0 => 'root', 47 => 'mailnull', 99 => 'nobody' );
my %user_cache = ( 'root' => 0, 'mailnull' => 47, 'nobody' => 99 );
my $reattempt_message = 'Message will be reattempted later';
my $sender_lookup;
my $sender_lookup_method;
# TEST VARIABLES
my $check_mail_permissions_result;
my %file_exists_cache;
sub file_exists {
return $file_exists_cache{ $_[0] } if exists $file_exists_cache{ $_[0] };
$file_exists_cache{ $_[0] } = -e $_[0] ? 1 : 0;
return $file_exists_cache{ $_[0] };
}
sub checkbx_autowhitelist {
my $address = shift;
my $phost = Exim::expand_string('$primary_hostname');
my $rp = Exim::expand_string('$received_protocol');
if ( $rp eq 'local' || $rp !~ /^e?smtps?a$/i || !$address || $address eq '' ) { return 'no'; }
my ( $localpart, $domain ) = split( /\@/, $address );
if ( ( !$domain || $domain eq '' || $domain eq $phost ) ) {
my $homedir = gethomedir($localpart);
unless ( $homedir ne '' ) {
return 'no';
}
if ( -e $homedir . '/etc/.boxtrapperenable' && !-e $homedir . '/etc/.boxtrapperautowhitelistdisable' ) {
return 'yes';
}
else {
return 'no';
}
}
else {
my $owner = getdomainowner($domain);
my $homedir = gethomedir($owner);
unless ( $homedir ne '' ) {
return 'no';
}
my $passwd = "${homedir}/etc/${domain}/passwd";
my $addressexists = user_exists_in_db( $localpart, $passwd );
if ( $addressexists && ( -e $homedir . "/etc/${domain}/${localpart}/.boxtrapperenable" && !-e $homedir . "/etc/${domain}/${localpart}/.boxtrapperautowhitelistdisable" ) ) {
return 'yes';
}
else {
return 'no';
}
}
}
sub getemailuser {
my ( $address, $received_protocol, $sender_ident ) = @_;
my $primary_hostname = Exim::expand_string('$primary_hostname');
my ( $local_part, $domain ) = split( m/[\@\+\%\:]/, ( $address || ( $received_protocol && $received_protocol eq 'local' ? $sender_ident : '' ) ) );
if ( !$domain || $domain eq '' || $domain eq $primary_hostname ) {
return $local_part;
}
else {
my $user = getdomainowner($domain);
if ($user) { return $user; }
}
return 'nobody';
}
#DO NOT REMOVE THIS COMMENT AS IT TELLS CPANEL TO ENABLE SERVICE AUTH CHECKING
#exim:serviceauth=1
#
# Checkpass not used since auth is passed to dovecot SASL
{
no warnings 'redefine';
sub checkuserpass { 0; }
sub checkpass { 0; }
}
sub checkspam {
# This is an old code block that should never be reached unless there is a serious
# problem installing their exim configuration
Exim::log_write("Something went very wrong during the exim configuration update. Please try reinstalling your exim configuration.");
1;
}
sub convert_address_directory_to_dovecot_lda_destination_username {
my $local_part = Exim::expand_string('$local_part');
my $domain = Exim::expand_string('$domain');
$primary_hostname ||= Exim::expand_string('$primary_hostname');
my $address_file = Exim::expand_string('$address_file');
if ( $address_file !~ m{mail/\Q$domain\E} ) {
return ( getpwuid($>) )[0];
}
else {
return $local_part . '@' . $domain;
}
}
sub convert_address_directory_to_dovecot_lda_mailbox {
my $address_file = Exim::expand_string('$address_file');
my ($mailbox) = $address_file =~ m{/\.([^\/]+)};
if ($mailbox) {
return "INBOX.$mailbox";
}
return 'INBOX';
}
sub call_cpwrap {
my ( $function, @ARGS ) = @_;
my @JSON_ENCODED_ARGS = map { aggressive_json_safe_encode($_) } @ARGS;
my $data = join( ' ', @JSON_ENCODED_ARGS );
my $json_template = qq[{"function":"$function","namespace":"Cpanel","version":2,"action":"run","data":"$data","send_data_only":1,"module":"exim"}\r\n\r\n];
require Cpanel::Encoder::Exim;
return eval { Exim::expand_string( '${readsocket{/usr/local/cpanel/var/cpwrapd.sock}{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($json_template) . '}{10s}}' ); };
}
sub aggressive_json_safe_encode {
my ($arg) = @_;
$arg =~ tr/^a-zA-Z0-9!#\$\-=?^_{}~:.//cd;
return $arg;
}
my $archived_at_domain_level = 0;
my $archived_outgoing = 0;
my $archived_mailman = 0;
sub should_archive_incoming_domain_message {
return ( $archived_at_domain_level = !_message_has_been_seen() );
}
sub _message_has_been_seen {
#ARCHIVE ONLY IF
#
#$parent_domain = ""
#
#OR
#
#$parent_domain != $domain
# Delivery was not a result of an expansion
my $parent_domain = Exim::expand_string('$parent_domain');
if ( !length $parent_domain ) {
return 0;
}
# Delivery was the result of an expansion / alias. Since its a diffrent domain we don't
# know if it was archived so we need to archive if enabled
my $domain = Exim::expand_string('$domain');
if ( $domain ne $parent_domain ) {
return 0;
}
my $parent_local_part = Exim::expand_string('$parent_local_part');
my $local_part = Exim::expand_string('$local_part');
# case 60975: If any deliveries happened, parent_domain and parent_local_part
# will get set to match domain and local_part. Since we need to
# still archive outgoing if it to our same domain or a local
# user we need to accept when they all match
if ( $parent_domain eq $domain && $local_part && $parent_local_part ) {
return 0;
}
# parent_local_part ne local_part and
# parent_domain == domain so it already got archived if we have it on
return 1;
}
sub archive_headers {
my ($router) = @_;
if ( $router eq 'archive_incoming_email_domain_method' ) {
return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part') . '@' . Exim::expand_string('$domain');
}
elsif ( $router eq 'archive_incoming_email_local_user_method' ) {
return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part');
}
elsif ( $router eq 'archive_outgoing_email' ) {
return "X-Archive-Type: " . $outgoing_sender_archive_directory . "\nX-Archive-Sender: $outgoing_sender";
}
}
sub should_archive_incoming_localuser_message {
# case 60999: Do not archive a message at the localuser level
# if we have already archived it at the domain level (avoid two copies)
return 0 if $archived_at_domain_level;
my $local_part = Exim::expand_string('$local_part');
my $incoming_domain = getusersdomain($local_part);
if ($incoming_domain) {
my $home = gethomedir($local_part);
if ( file_exists("$home/etc/$incoming_domain/archive/incoming") ) {
return 1;
}
}
return 0;
}
sub get_incoming_domain {
return getusersdomain( Exim::expand_string('$local_part') );
}
sub should_archive_outgoing_message {
return 0 if _message_has_been_seen();
return determine_sender_and_check_if_archive_needed();
}
sub determine_sender_and_check_if_archive_needed {
my $uid = int( Exim::expand_string('$originator_uid') );
my $gid = int( Exim::expand_string('$originator_gid') );
# outgoing_sender_domain is the domain of the actual sender
# outgoing_sender_counted_domain is the domain we actually count the message against
# Currently these are always the same except domain may be
# rewritten if we are coming from a mailman list in order
# to count against the owner of the list instead of the mailman
# user assuming /var/cpanel/email_send_limits/count_mailman exists
( $outgoing_sender, $outgoing_sender_domain, $outgoing_sender_counted_domain, $outgoing_sender_is_mailman ) = get_message_sender( $uid, $gid );
if ( $outgoing_sender_domain && $outgoing_sender_domain ne '-system-' ) {
$outgoing_sender_sysuser = getdomainowner($outgoing_sender_domain);
my $home = gethomedir($outgoing_sender_sysuser);
if ( $outgoing_sender_is_mailman && file_exists("$home/etc/$outgoing_sender_domain/archive/mailman") ) {
$outgoing_sender_archive_directory = 'mailman';
return 0 if $archived_mailman; # already archived
return ( $archived_mailman = 1 );
}
elsif ( file_exists("$home/etc/$outgoing_sender_domain/archive/outgoing") ) {
$outgoing_sender_archive_directory = 'outgoing';
return 0 if $archived_outgoing; # already archived
return ( $archived_outgoing = 1 );
}
}
return 0;
}
sub pack_archive_address_data {
my ($router) = @_;
return join( ' ',
'router=' . Cpanel::Encoder::Exim::encode_string_literal($router),
'sender=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender),
'sender_domain=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_domain),
'sender_sysuser=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_sysuser),
'sender_archive_directory=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_archive_directory)
);
}
sub get_outgoing_sender {
return ( $outgoing_sender // Exim::expand_string('${extract{sender}{$address_data}}'));
}
sub get_outgoing_sender_domain {
return ( $outgoing_sender_domain // Exim::expand_string('${extract{sender_domain}{$address_data}}'));
}
sub get_outgoing_sender_sysuser {
return ( $outgoing_sender_sysuser // Exim::expand_string('${extract{sender_sysuser}{$address_data}}'));
}
sub get_outgoing_archive_directory {
return ( $outgoing_sender_archive_directory // Exim::expand_string('${extract{sender_archive_directory}{$address_data}}'));
}
sub YYYYMMDDGMT {
my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime( $_[0] || time() );
return sprintf( '%04d-%02d-%02d', $year + 1900, $mon + 1, $mday );
}
our $DEFAULT_EMAIL_SEND_LIMITS_DEFER_CUTOFF_PERCENTAGE = 125;
sub getmaxemailsperhour {
my $domain = shift;
return 0 if $domain eq '-system-';
$domain =~ s/\///g; #jic
my $maxemails = 0; # Defaults to "unlimited"
my $master_email_send_limits_mtime = ( stat('/etc/email_send_limits') )[9];
my $max_fh;
if ( open( $max_fh, '<', '/var/cpanel/email_send_limits/cache/' . $domain ) && ( stat($max_fh) )[9] > $master_email_send_limits_mtime ) { # This is the user's main domain. All user's domains are aggregated here
$maxemails = readline $max_fh;
close $max_fh;
return 0 if !$maxemails || $maxemails eq 'unlimited';
return ( $maxemails ? int($maxemails) : 0 );
}
my $search_regex = qr/^\Q$domain\E:/;
my $search_wildcard_regex = qr/^\Q*\E:/;
_check_cache_dir();
my $old_umask = umask();
umask(0027);
#format DOMAIN: MAX_EMAIL_PER_HOUR,MAX_DEFER_FAIL_PERCENTAGE,MIN_DEFER_FAIL_TO_TRIGGER_PROTECTION
if ( open( my $max_fh, '>', '/var/cpanel/email_send_limits/cache/.' . $domain ) ) {
umask($old_umask);
if ( open( my $email_limits_fh, '<', '/etc/email_send_limits' ) ) {
while ( readline($email_limits_fh) ) {
if ( $_ =~ $search_regex ) {
$maxemails = ( split( /\,/, ( split( /:\s+/, $_ ) )[1] ) )[0];
last if $maxemails || $maxemails eq '0'; # case 51568: if there is no value we use the wildcard
}
elsif ( $_ =~ $search_wildcard_regex ) {
$maxemails = ( split( /\,/, ( split( /:\s+/, $_ ) )[1] ) )[0];
last;
}
}
}
chomp $maxemails;
print {$max_fh} $maxemails;
close($max_fh);
rename( '/var/cpanel/email_send_limits/cache/.' . $domain, '/var/cpanel/email_send_limits/cache/' . $domain ); #rename is atomic and will overwrite the file
return int $maxemails; # case 51568: must transform 'unlimited' to 0
}
else {
umask($old_umask);
}
return 0;
}
sub increment_max_emails_per_hour {
my ( $domain, $time, $msgid ) = @_;
$domain =~ s/\///g; #jic
_check_tracker_dir($domain);
$time ||= time();
Exim::log_write( "SMTP connection outbound $time $msgid $domain " . Exim::expand_string('$local_part') . '@' . Exim::expand_string('$domain') );
if ( open( my $emailt_fh, '>>', "/var/cpanel/email_send_limits/track/$domain/" . join( '.', ( gmtime($time) )[ 2, 3, 4, 5 ] ) ) ) {
print {$emailt_fh} '1';
close($emailt_fh);
}
# !DEBUG!
# if ( open( my $emailt_fh, '>>', "/var/cpanel/email_send_limits/track/$domain/msgids_" . join( '.', ( gmtime( $time ) )[ 2, 3, 4, 5 ] ) ) ) {
#
# print {$emailt_fh} $msgid . "\n";
# close($emailt_fh);
# }
}
sub _check_cache_dir {
mkdir( '/var/cpanel/email_send_limits/cache', 0750 ) if !-e '/var/cpanel/email_send_limits/cache';
}
sub _check_tracker_dir {
my $domain = shift;
$domain =~ s/\///g; #jic
if ( !-e '/var/cpanel/email_send_limits/track/' . $domain ) {
mkdir( '/var/cpanel/email_send_limits', 0751 );
mkdir( '/var/cpanel/email_send_limits/track', 0750 );
mkdir( '/var/cpanel/email_send_limits/track/' . $domain, 0750 );
}
}
sub get_current_emails_per_hour {
( ( stat( "/var/cpanel/email_send_limits/track/$_[0]/" . join( '.', ( gmtime( $_[1] || time() ) )[ 2, 3, 4, 5 ] ) ) )[7] || 0 );
}
sub get_current_emails_per_day {
my $domain = shift;
$domain =~ s/\///g; #jic
return 0 if ( !-e '/var/cpanel/email_send_limits/track/' . $domain );
my $total_size = 0;
if ( opendir( my $domain_track_fh, '/var/cpanel/email_send_limits/track/' . $domain ) ) {
while ( my $domaintime = readdir($domain_track_fh) ) {
next if ( $domaintime =~ /^\.\.?$/ );
my $tracker_file_size = ( stat("/var/cpanel/email_send_limits/track/$domain/$domaintime") )[7];
$total_size += $tracker_file_size;
}
}
return $total_size;
}
sub reached_max_emails_per_hour {
my $domain = shift;
$domain =~ s/\///g; #jic
my $max_allowed = int( shift || 0 );
my $time = shift || time();
if ($max_allowed) {
# AKA number_of_emails_sent >= $max_allowed
if ( get_current_emails_per_hour( $domain, $time ) >= $max_allowed ) {
return 1;
}
else {
return 0;
}
}
return 0;
}
#
# This converse function for reference only
#
#sub set_email_send_limits_defer_cutoff {
# my $percentage = int shift ;
#
# # The value is the size of the file so we can avoid the open/close overhead (just a stat)
# if ( open(my $cut_off_percentage_fh,'>','/var/cpanel/email_send_limits/defer_cutoff') ) {
# print {$cut_off_percentage_fh} 'x' x $percentage;
# return 1;
# }
#
# return 0;
# }
sub get_email_send_limits_defer_cutoff {
# The value is the size of the file so we can avoid the open/close overhead (just a stat)
my $cut_off_percentage = ( stat('/var/cpanel/email_send_limits/defer_cutoff') )[7];
if ( !defined $cut_off_percentage ) { $cut_off_percentage = $DEFAULT_EMAIL_SEND_LIMITS_DEFER_CUTOFF_PERCENTAGE; }
return $cut_off_percentage;
}
#
# This converse function for reference only
#
# sub set_email_daily_limit_notify {
# my $limit = int shift ;
# if ( $limit == 0 ) {
# unlink '/var/cpanel/email_send_limits/daily_limit_notify';
# return 1;
# }
# # The value is the size of the file so we can avoid the open/close overhead (just a stat)
# if ( open(my $daily_limit_fh,'>','/var/cpanel/email_send_limits/daily_limit_notify') ) {
# print {$daily_limit_fh} 'x' x $limit;
# return 1;
# }
# return 0;
# }
sub get_email_daily_limit_notify {
# The value is the size of the file so we can avoid the open/close overhead (just a stat)
my $limit = ( stat('/var/cpanel/email_send_limits/daily_limit_notify') )[7];
if ( !defined $limit ) { $limit = 0; }
return $limit;
}
sub create_daily_notify_touchfile {
my $domain = shift;
$domain =~ s/\///g; #jic
mkdir( '/var/cpanel/email_send_limits/daily_notify', 0750 ) if !-e '/var/cpanel/email_send_limits/daily_notify';
if ( open( my $daily_limit_fh, '>', '/var/cpanel/email_send_limits/daily_notify/' . $domain ) ) {
close $daily_limit_fh;
}
return undef;
}
BEGIN {
unshift @INC, '/usr/local/cpanel';
}
#DO NOT USE lib here
# use Cpanel::Encoder::Exim (); -- no loaded with require or preload
sub gethomedir {
my $user = shift;
require Cpanel::Encoder::Exim;
return Exim::expand_string( '${extract{5}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}{$value}}}}' ) || '';
}
sub getuid {
my $user = shift;
require Cpanel::Encoder::Exim;
my $uid = Exim::expand_string( '${extract{2}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}{$value}}}}' );
return defined $uid ? $uid : '';
}
sub getdomainowner {
my $domain = shift;
require Cpanel::Encoder::Exim;
substr($domain,0,4,'') if index($domain,'www.') == 0;
return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($domain) . '}lsearch{/etc/userdomains}{$value}}' ) || '';
}
my %domain_to_user_cache;
# This must be cached because we call getusersdomain as root in the archive_incoming_email_local_user_method router
# and then we need to read the user out of the memory cache in archiver_incoming_local_user_method since
# we no longer have access to read /etc/domainusers at that point. Note, we need to be able to cache multiple
# users in case they send a message to multiple system users
sub getusersdomain {
return '' if !$_[0] || $_[0] eq 'root' || $_[0] =~ tr{/}{} || !-e "/var/cpanel/users/$_[0]";
return ( $domain_to_user_cache{ $_[0] } || ( $domain_to_user_cache{ $_[0] } = lookup_key_in_file( '/etc/domainusers', $_[0] ) ) );
}
sub lookup_key_in_file {
my ( $file, $key ) = @_;
require Cpanel::Encoder::Exim;
return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($key) . '}lsearch{' . $file . '}{$value}}' ) || '';
}
sub isdemo {
my $user = shift;
return if ( !$user );
return 0 if $user eq '0' || $user eq '8' || $user eq 'mail' || $user eq 'mailnull' || $user eq 'root';
if ( $user =~ /^\d+$/ ) {
return user_exists_in_db( $user, '/etc/demouids' );
}
return user_exists_in_db( $user, '/etc/demousers' );
}
sub user_exists_in_db {
my ( $user, $db ) = @_;
# If the user is empty, '0' or only whitespace
# we should return 0 as $lookup will always return
# 1 even if it does not exist
return 0 if !$user || $user !~ tr{ \t}{}c;
require Cpanel::Encoder::Exim;
return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}lsearch{' . $db . '}{1}{0}}' ) || '0';
}
my %sender_recent_authed_mail_ips_address_cache;
my $get_recent_authed_mail_ips_lookup_method;
sub get_recent_authed_mail_ips_text_entry {
my ( $sender, $domain ) = get_recent_authed_mail_ips_entry(@_);
return join( '|', ( $sender || '' ), $domain );
}
sub popbeforesmtpwarn {
if ( my @possible_users = _get_possible_users_from_recent_authed_mail_ips_users() ) {
return ( "X-PopBeforeSMTPSenders: " . join( ",", @possible_users ) );
}
return '';
}
sub get_recent_authed_mail_ips_entry {
my $log = shift;
# SENDING OVER POP B4 SMTP or NOAUTH
# case 43151, case 43150
$get_recent_authed_mail_ips_lookup_method = '';
my $sender_host_address = Exim::expand_string('$sender_host_address');
# Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] log=[$log]");
my ( $sender, $domain );
if ( exists $sender_recent_authed_mail_ips_address_cache{$sender_host_address} ) {
# Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] USING CACHE");
( $sender, $domain, $get_recent_authed_mail_ips_lookup_method ) = @{ $sender_recent_authed_mail_ips_address_cache{$sender_host_address} };
$get_recent_authed_mail_ips_lookup_method = "cached: " . $get_recent_authed_mail_ips_lookup_method;
$log = 0;
}
else {
my $recent_authed_mail_ips_users_is_up_to_date = ( stat('/etc/recent_authed_mail_ips_users') )[9] + 7200 > time() ? 1 : 0;
my $sender_address_domain;
# Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] recent_authed_mail_ips_users_is_up_to_date= $recent_authed_mail_ips_users_is_up_to_date");
# If we have a recent_authed_mail_ips_users file that is up to date, we can verify the ip matches
if ($recent_authed_mail_ips_users_is_up_to_date) {
# This is what the user has claimed as the sender
my $sender_address = Exim::expand_string('$sender_address');
my $from_h_domain = Exim::expand_string('${domain:$h_from:}');
my $from_h_localpart = Exim::expand_string('${local_part:$h_from:}');
my $from_h = "$from_h_localpart\@$from_h_domain";
# First we try to find the address in the recent_authed_mail_ips_users file (with a cached exim lookup)
if ( my @possible_users = _get_possible_users_from_recent_authed_mail_ips_users() ) {
if ( grep { tr/@// ? $from_h eq $_ : $from_h eq $_ . '@' . $primary_hostname } @possible_users ) {
$sender = $from_h;
$domain = getdomainfromaddress($from_h);
$get_recent_authed_mail_ips_lookup_method = "full match of from_h in recent_authed_mail_ips_users";
}
elsif ( grep { tr/@// ? $sender_address eq $_ : $sender_address eq $_ . '@' . $primary_hostname } @possible_users ) {
$sender = $sender_address;
$domain = getdomainfromaddress($sender_address);
$get_recent_authed_mail_ips_lookup_method = "full match of sender_address in recent_authed_mail_ips_users";
}
elsif ( ( $sender_address_domain = ( split( m/\@/, $sender_address ) )[1] ) && grep( m/\@\Q$sender_address_domain\E$/, @possible_users ) ) {
$domain = $sender_address_domain;
$sender = '-unknown-@' . $domain;
$get_recent_authed_mail_ips_lookup_method = "match of sender_address_domain in recent_authed_mail_ips_users";
}
elsif ( grep { tr/@// ? ( $from_h eq $_ ) : ( $from_h_localpart eq $_ && ( !length $from_h_domain || $from_h_domain eq $primary_hostname ) ) } @possible_users ) {
$sender = $from_h;
$domain = $from_h_domain;
$get_recent_authed_mail_ips_lookup_method = "full match of from_h in recent_authed_mail_ips_users";
}
elsif ( grep( m/\@\Q$from_h_domain\E$/, @possible_users ) ) {
$domain = $from_h_domain;
$sender = '-unknown-@' . $from_h_domain;
$get_recent_authed_mail_ips_lookup_method = "match of from_h_domain in recent_authed_mail_ips_users";
}
elsif ( $possible_users[0] && $possible_users[0] eq '-alwaysrelay-' ) {
if ($from_h_domain) {
Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting from_h_domain of: $from_h_domain and from_h_localpart: $from_h_localpart");
$domain = $from_h_domain;
$sender = $from_h;
$get_recent_authed_mail_ips_lookup_method = "in alwaysrelay trusted from_h";
}
else {
Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting sender_address_domain of: $sender_address_domain");
$domain = $sender_address_domain;
$sender = $sender_address;
$get_recent_authed_mail_ips_lookup_method = "in alwaysrelay trusted sender_address";
}
}
else {
# If none of them matched, we have to assume they authenticated in some we so we go with the first one
$domain = getdomainfromaddress( $possible_users[0] );
$sender = $possible_users[0];
$get_recent_authed_mail_ips_lookup_method = "in recent_authed_mail_ips_users using first address";
}
if ( $sender =~ m/^\*/ ) {
$sender =~ s/^\*/-unknown-/;
}
$sender_recent_authed_mail_ips_address_cache{$sender_host_address} = [ $sender, $domain, $get_recent_authed_mail_ips_lookup_method ];
}
}
# we need to check alwaysrelay since we don't require recentauthedmailiptracker to be enabled
if ( !$domain && -e '/etc/alwaysrelay' ) {
my $alwaysrelay_result = Exim::expand_string('${lookup{$sender_host_address}iplsearch{/etc/alwaysrelay}{$sender_host_address $value}}');
if ($alwaysrelay_result) {
my ( $alwaysrelay_ip, $alwaysrelay_user ) = split( /\s+/, $alwaysrelay_result );
if ($alwaysrelay_user) {
$domain = getdomainfromaddress($alwaysrelay_user);
$sender = $alwaysrelay_user;
$get_recent_authed_mail_ips_lookup_method = "full match in alwaysrelay with recentauthedmailiptracker disabled";
Exim::log_write("$sender_host_address in /etc/alwaysrelay using domain $domain from lookup of $alwaysrelay_user");
}
if ( !$domain ) {
$domain = $sender_address_domain = ( split( /\@/, Exim::expand_string('$sender_address') ) )[1];
$sender = "-unknown-\@$domain";
$get_recent_authed_mail_ips_lookup_method = "in alwaysrelay with recentauthedmailiptracker disabled";
Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting sender_address_domain of: $sender_address_domain");
}
}
# no need to check /etc/alwaysrelay as they are automaticlly built into recent_authed_mail_ips_users
}
}
if ($domain) {
if ($log) {
my $message_exim_id = Exim::expand_string('$message_exim_id');
my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}');
my $sender_host_port = Exim::expand_string('$sender_host_port');
my $recent_authed_mail_ips_local_user = getdomainowner($domain);
my $recent_authed_mail_ips_local_uid = user2uid($recent_authed_mail_ips_local_user);
Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port U=$recent_authed_mail_ips_local_user ID=$recent_authed_mail_ips_local_uid S=$sender B=get_recent_authed_mail_ips_entry");
}
return ( $sender, $domain, $get_recent_authed_mail_ips_lookup_method );
}
return ( '', '', '' );
}
sub _get_possible_users_from_recent_authed_mail_ips_users {
my $recent_authed_mail_ips_users_result = Exim::expand_string('${lookup{$sender_host_address}lsearch{/etc/recent_authed_mail_ips_users}{$value}}');
return map {
s/\/.*$//g if tr/\///;
tr/+%:/@/;
$_;
} split( m/\s*\,\s*/, $recent_authed_mail_ips_users_result );
}
my $local_connection_uid;
my $local_connection_user;
my %sender_host_address_cache;
sub get_identified_local_connection_uid {
$local_connection_uid;
}
sub get_identified_local_connection_user {
$local_connection_user;
}
sub identify_local_connection {
# passes but not for production
# use strict;
# On Linux we can identify users by reading /proc/net/tcp*
# Since this requires access kernel memory on bsd and we don't have a way
# do that under exim users MUST authenticate to send messages from localhost
my ( $sender_host_address, $sender_host_port, $received_ip_address, $received_port, $log ) = @_;
undef $local_connection_uid;
undef $local_connection_user;
my $uid;
if ( exists $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } ) {
$uid = $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port };
$log = 0;
}
else {
local @INC = ( '/usr/local/cpanel', @INC ) if !grep { '/usr/local/cpanel' } @INC;
require Cpanel::Ident;
$uid = Cpanel::Ident::identify_local_connection( $sender_host_address, $sender_host_port, $received_ip_address, $received_port );
if ( !defined $uid ) {
$uid = identify_local_connection_wrapped( $sender_host_address, $sender_host_port, $received_ip_address, $received_port );
}
}
if ( defined $uid ) {
$local_connection_uid = $uid;
$sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = $local_connection_uid;
if ( $uid == -1 ) {
Exim::log_write("Could not identify the local connection from $sender_host_address on port $sender_host_port. Please authenticate") if $log;
return 0;
}
$local_connection_user = uid2user($uid);
# Log this for tailwatchd
Exim::log_write("SMTP connection identification H=localhost A=$sender_host_address P=$sender_host_port U=$local_connection_user ID=$local_connection_uid S=$local_connection_user B=identify_local_connection") if $log;
return 1;
}
else {
$sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = undef;
Exim::log_write("could not identify the local connection from $sender_host_address on port $sender_host_port. Please authenticate") if $log;
return 0;
}
}
sub identify_local_connection_wrapped {
my ( $address, $port, $localaddress, $localport ) = @_;
my $uidline = call_cpwrap( 'IDENTIFYLOCALCONNECTION', $address, $port, $localaddress, $localport );
chomp($uidline) if defined $uidline;
my ( $uidkey, $uid ) = split( /:/, $uidline, 2 );
$uid = undef if $uid eq '';
Exim::log_write("/usr/local/cpanel/bin/eximwrap IDENTIFYLOCALCONNECTION $address $port $localaddress $localport failed to return the uid key.") if ( !defined $uidkey || $uidkey ne 'uid' );
return $uid;
}
my $headers_rewrite_notice = '';
my $new_from_header;
use constant {
_ENOENT => 2,
_EEXIST => 17,
_SENDER_SYSTEM => '-system-',
};
sub spamd_is_available {
require Cpanel::Services::Enabled::Spamd;
return eval { Cpanel::Services::Enabled::Spamd::is_enabled() } // do {
warn;
1; # this defaults to on for historical reasons
};
}
sub get_dkim_domain {
my $msg_sender_domain = get_message_sender_domain();
if ($msg_sender_domain eq _SENDER_SYSTEM) {
$msg_sender_domain = Exim::expand_string('$sender_address_domain');
}
return $msg_sender_domain =~ tr<A-Z><a-z>r;
}
sub sender_domain_can_dkim_sign {
require Cpanel::DKIM::ValidityCache;
my $sender_domain = get_dkim_domain();
local $@;
return eval { Cpanel::DKIM::ValidityCache->get($sender_domain) } // do {
warn;
q<>;
};
}
sub discover_sender_information {
# If $sender_lookup_method and $check_mail_permissions_sender is already set
# we have already discovered the sender
if ( !$sender_lookup_method || !$check_mail_permissions_sender ) {
my $uid = int( Exim::expand_string('$originator_uid') );
my $gid = int( Exim::expand_string('$originator_gid') );
#Exim::log_write("discover_sender_information calling get_message_sender");
my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 1 );
$check_mail_permissions_sender = $sender if $sender;
$check_mail_permissions_is_mailman = $is_mailman;
}
#Exim::log_write("discover_sender_information calling discover_sender_information");
$new_from_header = get_from_header_rewrite_target();
return 0;
}
sub get_headers_rewrite {
return $new_from_header if $new_from_header;
my ($from_h_sender) = _get_from_h_sender();
Exim::log_write("discover_sender_information failed to set the from header rewrite for $from_h_sender");
return $from_h_sender;
}
sub get_from_header_rewrite_target {
$headers_rewrite_notice = '';
my ( $from_h_sender, $from_h_localpart, $from_h_domain ) = _get_from_h_sender();
if ( $sender_lookup_method && $check_mail_permissions_sender ) {
my $actual_sender = _get_login_from_check_mail_permissions_sender($check_mail_permissions_sender);
#Exim::log_write("!DEBUG! get_from_header_rewrite_target() actual_sender=[$actual_sender] from_h_sender=[$from_h_sender]");
my $qualified_actual_sender = _qualify_as_email_address($actual_sender);
my ( $status, $statusmsg );
if ( $sender_lookup_method =~ m{^redirect/forwarder} ) {
$headers_rewrite_notice = 'unmodified, forwarded message';
return $from_h_sender;
}
elsif ($check_mail_permissions_is_mailman) {
$headers_rewrite_notice = 'unmodified, sender is mailman';
return $from_h_sender;
}
elsif ( $from_h_sender eq $actual_sender ) {
$headers_rewrite_notice = 'unmodified, already matched';
return $from_h_sender;
}
else {
if ( $actual_sender eq 'mailnull' ) { # handle Mailer-Daemon messages
$headers_rewrite_notice = 'unmodified, actual sender is mailnull';
return $from_h_sender;
}
my $from_h_sender_domainowner = getdomainowner($from_h_domain);
# Actual Sender is a system user.
if ( $from_h_sender_domainowner && $from_h_sender_domainowner eq $actual_sender ) {
$headers_rewrite_notice = 'unmodified, actual sender is system user that owns from domain in the from header';
return $from_h_sender;
}
elsif ( $from_h_sender eq $qualified_actual_sender ) {
$headers_rewrite_notice = 'unmodified, actual sender is the system user';
return $from_h_sender;
}
elsif ( $actual_sender eq 'root' ) {
$headers_rewrite_notice = 'unmodified, actual sender is root';
return $from_h_sender;
}
elsif ( $actual_sender eq 'mailman' ) {
$headers_rewrite_notice = 'unmodified, actual sender is mailman';
return $from_h_sender;
}
elsif ( $actual_sender !~ tr/\@// && _is_trusted_user($actual_sender) ) {
$headers_rewrite_notice = 'unmodified, actual sender is a trusted user';
return $from_h_sender;
}
elsif ( ( ( $status, $statusmsg ) = _has_valias_pointing_to_actual_sender( $from_h_sender, $actual_sender ) )[0] ) {
if ( $statusmsg eq 'valias_exact_match' ) {
$headers_rewrite_notice = 'unmodified, there is a forwarder that points to the actual sender.';
}
elsif ( $statusmsg eq 'valias_domainowner_match' ) {
$headers_rewrite_notice = 'unmodified, there is a forwarder that points to a user owned by actual sender.';
}
elsif ( $statusmsg eq 'vdomainaliases_match' ) {
$headers_rewrite_notice = 'unmodified, there is a domain forwarder that maps to the actual sender.';
}
return $from_h_sender;
}
else {
if ( $actual_sender !~ tr/\@// ) {
$headers_rewrite_notice = 'rewritten was: [' . $from_h_sender . '], actual sender is not the same system user';
}
else {
$headers_rewrite_notice = 'rewritten was: [' . $from_h_sender . '], actual sender does not match';
}
Exim::log_write("From: header ($headers_rewrite_notice) original=[$from_h_sender] actual_sender=[$qualified_actual_sender]");
return $qualified_actual_sender;
}
}
}
# We have no sender set so we leave it unmodified
# AKA unable to determine sender would get here
$headers_rewrite_notice = 'unmodified, no actual sender determined from check mail permissions';
return $from_h_sender;
}
sub get_headers_rewritten_notice {
if ($headers_rewrite_notice) {
return "X-From-Rewrite: $headers_rewrite_notice";
}
return '';
}
#
# This converts an unqualified address which is just a system
# account IE local_part. Into local_part@primary_hostname.
#
# If the address is already qualified ie has @, it returns returns the
# address.
#
sub _qualify_as_email_address {
my ($address) = @_;
return $address if $address =~ tr/@//;
$primary_hostname ||= Exim::expand_string('$primary_hostname');
return $address . '@' . $primary_hostname;
}
#
# Convert the $check_mail_permissions_sender variable
# into the real login that the user has authenticated as
# in most cases this is already their email address, however it may
# be USER@PRIMARY_HOSTNAME, in which case we want to strip PRIMARY_HOSTNAME
#
sub _get_login_from_check_mail_permissions_sender {
my ($sender) = @_;
$primary_hostname ||= Exim::expand_string('$primary_hostname');
$sender =~ s/\@\Q$primary_hostname\E$//;
return $sender;
}
# _has_valias_pointing_to_target lets us know if there
# if a forwarder for the address pointing at the target.
#
# For example ORIGIN bob@cpanel.net
# might point to a user account DEST 'bob'
#
sub _has_valias_pointing_to_actual_sender {
my ( $origin, $actual_sender ) = @_;
#Exim::log_write("!DEBUG! _has_valias_pointing_to_actual_sender() actual_sender=[$actual_sender] origin=[$origin]");
my $qualified_origin = _qualify_as_email_address($origin);
my $qualified_actual_sender = _qualify_as_email_address($actual_sender);
my ( $origin_local_part, $origin_domain ) = split( m{@}, $qualified_origin, 2 );
my ( $actual_sender_local_part, $actual_sender_domain ) = split( m{@}, $qualified_actual_sender, 2 );
my $actual_sender_domainowner;
require Cpanel::Encoder::Exim;
return ( 0, 'invalid_origin_domain' ) if $origin_domain =~ m{/};
if ( file_exists("$VALIASES_DIR/$origin_domain") ) {
if ( my $valiases_alias_line = Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($origin) . '}lsearch*{' . $VALIASES_DIR . '/' . $origin_domain . '}{$value}}' ) ) {
if ( my @forwarders = _get_forwarders_from_string($valiases_alias_line) ) {
foreach my $forwarder_destination (@forwarders) {
#
# Handle exact matches
# IE bob@cpanel.net is forwarded to the actual sender
#
if ( _qualify_as_email_address($forwarder_destination) eq $qualified_actual_sender ) {
return ( 1, 'valias_exact_match' );
}
# $VALIASES_DIR/dog.com: nick@dog.org: me@samsdomain.org
# I send email From: nick@dog.org and I am authenticated as 'sam' it should likely be allowed
if ( $actual_sender !~ tr/\@// && $forwarder_destination =~ tr/\@// ) {
my ( $forwarder_destination_local_part, $forwarder_destination_domain ) = split( m{@}, $forwarder_destination, 2 );
my $forwarder_destination_domainowner = getdomainowner($forwarder_destination_domain);
if ( $actual_sender eq $forwarder_destination_domainowner ) {
return ( 1, 'valias_domainowner_match' );
}
}
}
}
}
}
if ( file_exists("$VDOMAINALIASES_DIR/$origin_domain") ) {
if ( my $vdomainaliases_alias_line = Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($origin_domain) . '}lsearch{' . $VDOMAINALIASES_DIR . '/' . $origin_domain . '}{$value}}' ) ) {
my $vdomainaliases_domain = _ws_trim($vdomainaliases_alias_line);
if ( ( $origin_local_part . '@' . $vdomainaliases_domain ) eq $qualified_actual_sender ) {
return ( 1, 'vdomainaliases_match' );
}
}
}
return ( 0, 'no_match' );
}
sub _is_trusted_user {
my ($user) = @_;
return 0 if !file_exists('/etc/trusted_mail_users');
local $/;
open my $trusted_mail_users_fh, '<', '/etc/trusted_mail_users' or return 0;
my @trusted_mail_users = split( qq{\n}, <$trusted_mail_users_fh> );
close $trusted_mail_users_fh;
return scalar grep { $_ eq $user } @trusted_mail_users;
}
#
# From Cpanel::StringFunc::Trim
#
sub _ws_trim {
my ($this) = @_;
my $fix = ref $this eq 'SCALAR' ? $this : \$this;
${$fix} =~ s/^\s+//;
${$fix} =~ s/\s+$//;
return ${$fix};
}
#
# From Cpanel::API::Email
#
sub _get_forwarders_from_string {
my ($forwarder_csv) = @_;
# to leave \, as \, uncomment this:
# $forwarder_csv =~ s{\\,}{\\\\,}g;
my @forwarders =
$forwarder_csv =~ /^[\s"]*\:(fail|defer|blackhole|include)\:/
? ($forwarder_csv)
: split( /(?<![\\]),/, $forwarder_csv );
my @parsed_forwarders;
for my $forward (@forwarders) {
$forward = _ws_trim($forward);
next if ( $forward =~ m{^"} );
push @parsed_forwarders, $forward;
}
return wantarray ? @parsed_forwarders : \@parsed_forwarders;
}
sub check_mail_permissions_results {
return $check_mail_permissions_data;
}
sub enforce_mail_permissions_results {
$enforce_mail_permissions_data;
}
sub uid2user {
my $uid = shift;
return exists $uid_cache{$uid} ? $uid_cache{$uid} : ( $uid_cache{$uid} = ( getpwuid($uid) )[0] );
}
sub user2uid {
my $user = shift;
return exists $user_cache{$user} ? $user_cache{$user} : ( $user_cache{$user} = getuid($user) );
}
sub get_sender_from_uid {
my $uid = int( Exim::expand_string('$originator_uid') );
my $user = uid2user($uid);
return getdomainfromaddress($user);
}
sub mailtrapheaders {
$primary_hostname ||= Exim::expand_string('$primary_hostname');
my $original_domain = Exim::expand_string('$original_domain');
my $sender_address_domain = Exim::expand_string('$sender_address_domain');
my $originator_uid = Exim::expand_string('$originator_uid');
my $originator_gid = Exim::expand_string('$originator_gid');
my $caller_uid = Exim::expand_string('$caller_uid');
my $caller_gid = Exim::expand_string('$caller_gid');
my $headers =
"X-AntiAbuse: This header was added to track abuse, please include it with any abuse report\n"
. "X-AntiAbuse: Primary Hostname - $primary_hostname\n"
. "X-AntiAbuse: Original Domain - $original_domain\n"
. "X-AntiAbuse: Originator/Caller UID/GID - [$originator_uid $originator_gid] / [$caller_uid $caller_gid]\n"
. "X-AntiAbuse: Sender Address Domain - $sender_address_domain\n"
. check_mail_permissions_headers() . "\n";
if ( file_exists('/etc/eximmailtrap') ) {
my $xsource = $ENV{'X-SOURCE'};
my $xsourceargs = $ENV{'X-SOURCE-ARGS'};
my $xsourcedir = maskdir( $ENV{'X-SOURCE-DIR'} );
$headers .= "X-Source: ${xsource}\n" . "X-Source-Args: ${xsourceargs}\n" . "X-Source-Dir: ${xsourcedir}";
}
return ($headers);
}
sub getdomainfromaddress {
my $address = shift;
$address =~ s/\/.*$//g if $address =~ tr/\///; # remove /spam
if ( $address =~ tr/@+%:// ) {
unless ( $address =~ tr/@// ) {
# This matches exactly how authentication occurs
$address =~ s/[+:%]/@/;
}
$primary_hostname ||= Exim::expand_string('$primary_hostname');
if ( $address =~ m/[@]\Q$primary_hostname\E$/ ) {
return getusersdomain( ( split( m/[@]/, $address, 2 ) )[0] ) || _SENDER_SYSTEM; #from MailAuth.pm
}
else {
return ( split( m/[@]/, $address, 2 ) )[1]; #from MailAuth.pm
}
}
else {
return getusersdomain($address) || _SENDER_SYSTEM;
}
}
sub get_message_sender_domain {
my ( $uid, $gid, $log ) = @_;
$uid = int( Exim::expand_string('$originator_uid') ) if !defined $uid;
$gid = int( Exim::expand_string('$originator_gid') ) if !defined $gid;
return ( ( get_message_sender( $uid, $gid, $log ) )[1] ) || '';
}
sub get_sender_lookup_method {
return $sender_lookup_method || 'none';
}
sub get_sender_lookup {
return $sender_lookup || '';
}
sub check_mail_permissions_headers {
return "X-Get-Message-Sender-Via: " . ( $primary_hostname ||= Exim::expand_string('$primary_hostname') ) . ": " . get_sender_lookup_method() . "\n" . "X-Authenticated-Sender: " . ( $primary_hostname ||= Exim::expand_string('$primary_hostname') ) . ": " . get_sender_lookup();
}
# This must match the logic extactly for Cpanel::TailWatch::EximStats ($direction eq '<=')
sub get_message_sender {
#passes but not for production
#use strict;
my ( $uid, $gid, $log ) = @_;
my ( $authenticated_local_user, $authenticated_id, $recent_authed_mail_ips_text_entry, $domain, $counted_domain, $sender, $is_mailman, $username );
$sender_lookup_method = '';
my ( $acl_c_vhost_owner, $acl_c_vhost_owner_url ) = split( m{:}, Exim::expand_string('$acl_c_vhost_owner') || '', 2 );
my $message_exim_id = Exim::expand_string('$message_exim_id');
# SMTP AUTH
if ( $authenticated_id = Exim::expand_string('$authenticated_id') ) {
$authenticated_id =~ s/[\r\n\f]//g;
if ( $authenticated_id eq 'nobody' ) {
if ($acl_c_vhost_owner) {
$authenticated_id = uid2user($acl_c_vhost_owner);
}
$sender_lookup_method = 'uid via acl_c_vhost_owner from authenticated_id: ' . $authenticated_id . ' from ' . $acl_c_vhost_owner_url;
}
else {
$sender_lookup_method = 'authenticated_id: ' . $authenticated_id;
}
$sender = $authenticated_id;
$domain = getdomainfromaddress($authenticated_id);
# If the sender owns the domain they are sending
# from we can trust it
if ( length $sender && $sender !~ tr/\@// ) {
( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method );
}
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from authenticated_id ($authenticated_id)");
}
# FROM A CONNECTION TO LOCALHOST (linux only)
elsif ( $authenticated_local_user = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{$acl_c_authenticated_local_user}{}}') ) {
my $authenticated_local_uid = user2uid($authenticated_local_user);
my $sender_host_address = Exim::expand_string('$sender_host_address');
my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}');
my $sender_host_port = Exim::expand_string('$sender_host_port');
$domain = getusersdomain($authenticated_local_user) || _SENDER_SYSTEM;
$sender = $authenticated_local_user;
$sender_lookup_method = 'acl_c_authenticated_local_user: ' . $authenticated_local_user;
if ($log) { Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port M=$message_exim_id U=$authenticated_local_user ID=$authenticated_local_uid S=$sender B=authenticated_local_user"); } #replay for tailwatchd
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from acl_c_authenticated_local_user");
}
# RELAY HOSTS
elsif ( $recent_authed_mail_ips_text_entry = Exim::expand_string('$acl_c_recent_authed_mail_ips_text_entry') ) {
#FIXME: need to get sender
( $sender, $domain ) = split( /\|/, $recent_authed_mail_ips_text_entry );
my $sender_host_address = Exim::expand_string('$sender_host_address');
my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}');
my $sender_host_port = Exim::expand_string('$sender_host_port');
my $recent_authed_mail_ips_local_user = getdomainowner($domain);
my $recent_authed_mail_ips_local_uid = user2uid($recent_authed_mail_ips_local_user);
$sender_lookup_method = 'acl_c_recent_authed_mail_ips_text_entry: ' . $recent_authed_mail_ips_text_entry;
if ($log) { Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port M=$message_exim_id U=$recent_authed_mail_ips_local_user ID=$recent_authed_mail_ips_local_uid S=$sender B=recent_authed_mail_ips_domain") }
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from acl_c_recent_authed_mail_ips_text_entry");
}
elsif ( Exim::expand_string('$received_protocol') eq 'local' ) {
my $sender_ident = Exim::expand_string('$sender_ident');
$sender_ident =~ s/[\r\n\f]//g;
my $used_vhost_owner_lookup = 0;
if ( $sender_ident eq 'nobody' ) {
if ($acl_c_vhost_owner) {
$used_vhost_owner_lookup = 1;
$sender_ident = uid2user($acl_c_vhost_owner);
}
}
$sender = $sender_ident;
$domain = getusersdomain($sender_ident) || _SENDER_SYSTEM;
$sender_lookup_method = 'sender_ident via received_protocol == local: ' . $sender_ident . ( $used_vhost_owner_lookup ? ' : used vhost owner lookup from: ' . $acl_c_vhost_owner_url : '' );
# If the sender owns the domain they are sending
# from we can trust it
if ( length $sender && $sender !~ tr/\@// ) {
( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method );
}
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from local user ($sender_ident)");
}
else {
$mail_gid ||= int( ( getgrnam('mail') )[2] );
#Exim::log_write("!DEBUG! mailgid=$mail_gid == gid=$gid (uid=$uid)");
if ( $gid == $mail_gid ) {
my ( $recent_authed_mail_ips_sender, $recent_authed_mail_ips_domain, $recent_authed_mail_ips_lookup_method ) = get_recent_authed_mail_ips_entry();
if ($recent_authed_mail_ips_domain) {
$sender = $recent_authed_mail_ips_sender;
$sender =~ s/[\r\n\f]//g;
$domain = $recent_authed_mail_ips_domain;
$sender_lookup_method = 'mailgid via get_recent_authed_mail_ips_entry: ' . $sender . "/$recent_authed_mail_ips_lookup_method";
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from get_recent_authed_mail_ips_entry() or sender_address_domain");
}
$primary_hostname ||= Exim::expand_string('$primary_hostname');
if ( $domain && $domain eq $primary_hostname ) {
$username = Exim::expand_string('$sender_address_local_part');
$sender = $username;
$domain = getusersdomain($username) || _SENDER_SYSTEM;
$sender_lookup_method = 'mailgid via primary_hostname' . "/$recent_authed_mail_ips_lookup_method";
}
if ( !$domain ) {
# If we cannot find the sender and it is not _SENDER_SYSTEM it is a redirected/forwarded message
my $parent_domain = Exim::expand_string('$parent_domain');
my $parent_local_part = Exim::expand_string('$parent_local_part');
my $local_part = Exim::expand_string('$local_part');
my $delivery_domain = Exim::expand_string('$domain');
$parent_domain =~ s/[^\w\.\-\/]//g;
$parent_local_part =~ s/[^\w\.\-\/]//g;
$local_part =~ s/[^\w\.\-\/]//g;
$delivery_domain =~ s/[^\w\.\-\/]//g;
# If we have a parent_domain its probably a redirect
if ( $parent_domain && ( $parent_domain ne $delivery_domain || $parent_local_part ne $local_part ) ) {
# If the parent_domain is the primary_hostname its a localuser redirect
if ( my $local_user = $parent_domain eq $primary_hostname ? $parent_local_part : getdomainowner($parent_domain) ) {
my $local_uid = user2uid($local_user);
my $redirected_domain = $parent_domain eq $primary_hostname ? getusersdomain($parent_local_part) : $parent_domain;
if ($log) { Exim::log_write("SMTP connection identification D=$redirected_domain O=$parent_local_part\@$parent_domain E=$local_part\@$delivery_domain M=$message_exim_id U=$local_user ID=$local_uid B=redirect_resolver") }
; #replay for tailwatchd
$domain = $redirected_domain;
$sender = $parent_domain eq $primary_hostname ? $local_user : "$parent_local_part\@$parent_domain";
$sender_lookup_method = "redirect/forwarder owner $parent_local_part\@$parent_domain -> $local_part\@$delivery_domain";
}
}
}
if ( !$domain ) {
$sender_lookup_method = 'mailgid no entry from get_recent_authed_mail_ips_entry';
#Exim::log_write("!DEBUG! get_message_sender() failed to get the domain. However the sender domain claims to be $sender_address_domain");
}
}
else {
# FROM A SHELL OR CGI
$username = uid2user($uid);
if ($username) {
if ( $username eq 'nobody' ) {
if ($acl_c_vhost_owner) {
$username = uid2user($acl_c_vhost_owner);
}
$sender_lookup_method = 'uid via acl_c_vhost_owner from shell cgi: ' . $username . ' from: ' . $acl_c_vhost_owner_url;
}
else {
$sender_lookup_method = 'uid via shell cgi: ' . $username;
}
$domain = getusersdomain($username) || _SENDER_SYSTEM;
$sender = $username;
}
# If the sender owns the domain they are sending
# from we can trust it
if ( length $sender && $sender !~ tr/\@// ) {
( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method );
}
#Exim::log_write("!DEBUG! get_message_sender() got domain $domain from UID");
}
}
if ($domain) {
$domain =~ s/[^\w\.\-\/]//g;
$domain = lc $domain;
$counted_domain = $domain;
if ($sender) {
$sender =~ tr/+%:/@/;
$sender =~ s/[^\w\.\-\/\@]//g;
if ( $sender eq 'mailman' ) {
$is_mailman = 1;
$domain = lc Exim::expand_string('$sender_address_domain');
$sender_lookup_method .= '/mailman';
$sender = 'mailman@' . $domain;
$counted_domain = $domain if ( file_exists('/var/cpanel/email_send_limits/count_mailman') );
}
}
}
$sender_lookup = $sender;
if ( $log && $message_exim_id ) {
$username ||= ( ( $sender =~ tr{@}{} ) ? getdomainowner( ( split( m{@}, $sender ) )[1] ) : $sender );
if ($username) {
# Will log as 2017-05-26 13:42:22 1dEKBq-0007HB-6R Sender identification S=nick
Exim::log_write("Sender identification U=$username D=$domain S=$sender"); #replay for tailwatchd
}
}
return ( $sender, $domain, $counted_domain, $is_mailman );
}
sub get_message_sender_address {
return ( get_message_sender(@_) )[0];
}
sub enforce_mail_permissions {
$enforce_mail_permissions_data ? 1 : 0;
}
sub check_mail_permissions {
$check_mail_permissions_domain = undef;
#Exim::log_write("!DEBUG! running check_mail_permissions");
my $uid = int( Exim::expand_string('$originator_uid') );
$enforce_mail_permissions_data = ':fail: check_mail_permissions failed to complete or set a status';
$check_mail_permissions_result = '';
$check_mail_permissions_data = ':unknown:';
$check_mail_permissions_domain = '';
$check_mail_permissions_sender = '';
$check_mail_permissions_is_mailman = 0;
$nobody_uid ||= user2uid('nobody');
my $acl_c_vhost_owner = ( split( m{:}, Exim::expand_string('$acl_c_vhost_owner') || '' ) )[0];
my $acl_c_vhost_owner_known_user = ( $acl_c_vhost_owner && $acl_c_vhost_owner != $nobody_uid ) ? 1 : 0;
if ( $uid == $nobody_uid && !$acl_c_vhost_owner_known_user && file_exists('/etc/webspam') ) {
$enforce_mail_permissions_data = ':fail: Mail sent by user nobody being discarded due to sender restrictions in WHM->Tweak Settings';
$check_mail_permissions_result = "uid ($uid) is the nobody_uid ($nobody_uid) and /etc/webspam exists"; # for tests (only set when enforce_mail_permissions_data is empty)
return 'no';
}
my $gid = int( Exim::expand_string('$originator_gid') );
#MAILTRAP
if ( file_exists('/etc/eximmailtrap') ) {
$mailtrap_gid ||= int( ( getgrnam('mailtrap') )[2] );
$nobody_gid ||= int( ( getgrnam('nobody') )[2] );
if ( $uid >= $nobody_uid && $gid >= $nobody_gid && $gid != $mailtrap_gid ) {
$enforce_mail_permissions_data = ":fail: Gid $gid is not permitted to relay mail, or has directly called /usr/sbin/exim instead of /usr/sbin/sendmail.";
return 'no';
}
}
#MAILTRAP
if ( Exim::expand_string('$received_protocol') eq 'local' && isdemo($uid) ) {
$enforce_mail_permissions_data = ":fail: User with uid $uid is a demo user. You cannot send mail if your account is in demo mode.";
return 'no';
}
my $message_exim_id = Exim::expand_string('$message_exim_id');
if ( !$message_exim_id && !Exim::expand_string('$sender_address') ) {
$enforce_mail_permissions_data = ''; # permit normal acction
#Exim::log_write("!DEBUG! check_mail_permissions called without sender_address set from $sender_host_address (rcount: $recipients_count)");
$check_mail_permissions_result = "webspam check, mailtrap check, demo check passed and no sender_address"; # for tests (only set when enforce_mail_permissions_data is empty)
return 'no';
}
# real_domain is the domain of the actual sender
# domain is the domain we actually count the message against
# Currently these are always the same except domain may be
# rewritten if we are coming from a mailman list in order
# to count against the owner of the list instead of the mailman
# user assuming /var/cpanel/email_send_limits/count_mailman exists
my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 1 );
if ( $sender =~ m/^_archive\@/ ) {
$enforce_mail_permissions_data = ":fail: Archive Users are not permitted to send email. Message discarded.";
$check_mail_permissions_result = "get_message_sender returned an archive user";
return 'no';
}
if ( !Cpanel::Server::Type::Role::MailRelay->is_enabled() ) {
$enforce_mail_permissions_data = ":fail: This server does not relay mail.";
$check_mail_permissions_result = "This server does not relay mail.";
return 'no';
}
if ( !$domain || $domain eq '' ) {
my $sender_host_address = Exim::expand_string('$received_protocol') eq 'local' ? 'localhost' : Exim::expand_string('$sender_host_address');
my $recipients_count = Exim::expand_string('$recipients_count');
my $routed_domain = Exim::expand_string('$domain');
if ( $sender eq 'nobody' && file_exists('/etc/webspam') ) {
Exim::log_write("check_mail_permissions could not determine the sender domain for a nobody message [routed_domain=$routed_domain message_exim_id=$message_exim_id sender_host_address=$sender_host_address recipients_count=$recipients_count]") if $recipients_count && !getdomainowner($routed_domain);
$enforce_mail_permissions_data = ':fail: Mail sent by user nobody that cannot be linked to a user is being discarded due to sender restrictions in WHM->Tweak Settings';
$check_mail_permissions_result = "The sender of the message nobody and /etc/webspam exists"; # for tests (only set when enforce_mail_permissions_data is empty)
}
else {
Exim::log_write("check_mail_permissions could not determine the sender domain [routed_domain=$routed_domain message_exim_id=$message_exim_id sender_host_address=$sender_host_address recipients_count=$recipients_count]") if $recipients_count && !getdomainowner($routed_domain);
# If delivery is to a userdomain that its expected that we cannot get the sender domain
$enforce_mail_permissions_data = ''; # permit normal acction
$check_mail_permissions_result = "get_message_sender returned no domain"; # for tests (only set when enforce_mail_permissions_data is empty)
}
return 'no';
}
else {
if ( !$message_exim_id ) {
#Exim::log_write("check_mail_permissions !DEBUG! got the domain ($domain) of a message before the message id!");
}
}
#Exim::log_write("check_mail_permissions !DEBUG! found sender domain of message: $message_exim_id to be $domain with sender [$sender]");
$check_mail_permissions_msgid = $message_exim_id if $message_exim_id;
$check_mail_permissions_domain = $domain if $domain;
$check_mail_permissions_sender = $sender if $sender;
$check_mail_permissions_is_mailman = $is_mailman;
if ( $domain && $domain ne _SENDER_SYSTEM ) {
my $now;
# Just before we check to see if we've exceeded the allowable mail counts for this domain,
# check to see if we need to notify the admin about someone exceeding the warning level
my $mail_count = get_current_emails_per_day($domain) + 1; # +1 for the one we're *about* to send, but haven't yet!
my $emails_to_notify = get_email_daily_limit_notify();
if ( ( $emails_to_notify > 0 ) && ( $mail_count > $emails_to_notify ) ) {
if ( !file_exists( '/var/cpanel/email_send_limits/daily_notify/' . $domain ) ) {
create_daily_notify_touchfile($domain);
Exim::log_write("check_mail_permissions Hit daily email notify limit for domain $domain");
}
}
if ( file_exists( '/var/cpanel/email_send_limits/max_deferfail_' . $domain ) ) {
local $/;
my $limit_data;
if ( open( my $email_fh, '<', '/var/cpanel/email_send_limits/max_deferfail_' . $domain ) ) {
$limit_data = readline($email_fh);
close($email_fh);
}
my ( $currentmail, $maxmails, $percentage ) = $limit_data =~ /([0-9]+)\/([0-9]+)\s+\(([0-9]+)/;
$currentmail ||= 'unknown';
$maxmails ||= 'unknown';
$percentage ||= 100;
$enforce_mail_permissions_data = ":fail: Domain $domain has exceeded the max defers and failures per hour ($currentmail/$maxmails ($percentage\%)) allowed. Message discarded.";
return 'no';
}
elsif ( my $maxmails = getmaxemailsperhour($domain) ) {
my $currentmail = get_current_emails_per_hour( $domain, ( $now ||= time() ) );
if ( $currentmail >= $maxmails ) {
my $cutoff_percentage = get_email_send_limits_defer_cutoff();
my $percentage = int( ( $currentmail / $maxmails ) * 100 );
if ( $percentage >= $cutoff_percentage ) {
$enforce_mail_permissions_data = ":fail: Domain $domain has exceeded the max emails per hour ($currentmail/$maxmails ($percentage\%)) allowed. Message discarded.";
return 'no';
}
else {
increment_max_emails_per_hour( $domain, ( $now ||= time() ), $message_exim_id ); # need to count it because we will try it later
# this will result in percentages above 100% which may be confusing however correct
# this is how we decide to defer or fail the message
return _check_mail_permission_defer_with_message("Domain $domain has exceeded the max emails per hour ($currentmail/$maxmails ($percentage\%)) allowed. $reattempt_message");
}
}
}
if ( domain_has_outgoing_mail_suspended($domain) ) {
# We already check this in the ACL, however if the sender domain
# is forged we have to check it again here to ensure that
# we are checking against the actual sender and not the
# domain in the from: field
$enforce_mail_permissions_data = ":fail: Domain $domain has an outgoing mail suspension. Message discarded.";
return 'no';
}
elsif ( domain_has_outgoing_mail_hold($domain) ) {
track_held_message($domain);
return _check_mail_permission_defer_with_message("Domain $domain has an outgoing mail hold. $reattempt_message");
}
elsif ($sender) {
if ( user_has_outgoing_mail_suspended($sender) ) {
# We already check this in the ACL, however if the sender domain
# is forged we have to check it again here to ensure that
# we are checking against the actual sender and not the
# domain in the from: field
$enforce_mail_permissions_data = ":fail: Sender $sender has an outgoing mail suspension. Message discarded.";
return 'no';
}
elsif ( user_has_outgoing_mail_hold($sender) ) {
track_held_message($sender);
return _check_mail_permission_defer_with_message("Sender $sender has an outgoing mail hold. $reattempt_message");
}
}
}
$enforce_mail_permissions_data = ''; # permit normal action
$check_mail_permissions_result = "reached end of check_mail_permissions"; # for tests (only set when enforce_mail_permissions_data is empty)
return 'no';
}
sub _check_mail_permission_defer_with_message {
my ($message) = @_;
my $message_body = Exim::expand_string('$message_body');
my $message_body_size = Exim::expand_string('$message_body_size');
my $message_body_length = length($message_body);
$check_mail_permissions_data =
qq{# Exim filter\n\nunseen mail }
. ( $check_mail_permissions_sender ? qq{to } . Cpanel::Encoder::Exim::unquoted_encode_string_literal($check_mail_permissions_sender) . qq{\n} : '' )
. q{subject "Mail delivery deferred: returning message to sender" }
. q{from "Mail Delivery System <Mailer-Daemon@$primary_hostname>" }
. q{text "This message was created automatically by mail delivery software.\n} . q{\n}
. q{A message that you sent could not be delivered to one or more of its\n}
. q{recipients. This is a temporary error. The following address(es) deferred:\n} . q{\n}
. q{ $local_part@$domain\n}
. qq{ $message} . q{\n\n}
. q{------- This is a copy of the message, including all the headers. ------\n}
. ( ( $message_body_length < $message_body_size ) ? ( q{------ The body of the message is $message_body_size characters long; only the first\n} . q{------ } . $message_body_length . q{ or so are included here.\n} ) : () )
. q{$message_headers\n\n}
. q{$message_body"}
. qq{\nfinish};
$enforce_mail_permissions_data = ":defer: \"$message\"";
return 'yes';
}
sub domain_has_outgoing_mail_hold {
my ($domain) = @_;
my $user = getdomainowner($domain);
if ( $user && user_has_outgoing_mail_hold($user) ) {
return 1;
}
return 0;
}
sub domain_has_outgoing_mail_suspended {
my ($domain) = @_;
my $user = getdomainowner($domain);
if ( $user && user_has_outgoing_mail_suspended($user) ) {
return 1;
}
return 0;
}
sub user_has_outgoing_mail_suspended {
my ($user) = @_;
if ( -e '/etc/outgoing_mail_suspended_users' ) {
return user_exists_in_db( $user, '/etc/outgoing_mail_suspended_users' );
}
return 0;
}
sub user_has_outgoing_mail_hold {
my ($user) = @_;
if ( -e '/etc/outgoing_mail_hold_users' ) {
return user_exists_in_db( $user, '/etc/outgoing_mail_hold_users' );
}
return 0;
}
sub check_outgoing_mail_suspended {
if ( !Cpanel::Server::Type::Role::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 _SENDER_SYSTEM && domain_has_outgoing_mail_suspended($real_domain) ) {
$outgoing_mail_suspended_message = "Outgoing mail from \"$real_domain\" has been suspended.";
return 1;
}
elsif ( $sender && user_has_outgoing_mail_suspended($sender) ) {
$outgoing_mail_suspended_message = "Outgoing mail from \"$sender\" has been suspended.";
return 1;
}
return 0;
}
sub get_outgoing_mail_suspended_message {
return $outgoing_mail_suspended_message;
}
sub increment_max_emails_per_hour_if_needed {
# Exim::log_write("!DEBUG! increment_max_emails_per_hour_if_needed entered");
if ( $check_mail_permissions_domain && $check_mail_permissions_domain ne _SENDER_SYSTEM ) {
if ( Exim::expand_string('${if first_delivery{1}{0}}') || ( $check_mail_permissions_msgid && _get_last_delivery_message($check_mail_permissions_msgid) =~ m/$reattempt_message/o ) ) {
# if FIRST_DELIVERY or last line of msglog is our $reattempt_message
# example == f@kos.net R=check_mail_permissions defer (-1): Domain pigdog.org has exceeded the max emails per hour (12/10 (120%)) allowed. Message will be reattempted later
# we need to tell the next function to charge us for the message since it was deferred before and we did not get here
# Exim::log_write("!DEBUG! increment_max_emails_per_hour=$check_mail_permissions_domain msgid=$check_mail_permissions_msgid");
increment_max_emails_per_hour( $check_mail_permissions_domain, time(), $check_mail_permissions_msgid );
}
}
return 'no';
}
sub store_spam {
my $sender_host_address = shift;
my $spam_score = shift;
my $now = time();
open( my $spam_fh, '>>', '/var/cpanel/spamstore' );
#uncomment to deploy
# syswrite($spam_fh, $now . ':' . $sender_host_address . ':' . $spam_score . ":.\n");
close($spam_fh);
}
sub _get_last_delivery_message {
my $message_exim_id = shift;
my ( $last_message, $msglog_file, $msglog_size );
my $spool_directory = Exim::expand_string('$spool_directory');
my $spool_split_directory = substr( ( split( /-/, $message_exim_id ) )[0], -1, 1 );
if ( file_exists("$spool_directory/msglog/$spool_split_directory/$message_exim_id") ) { #split spool
$msglog_size = ( stat(_) )[7];
$msglog_file = "$spool_directory/msglog/$spool_split_directory/$message_exim_id";
}
elsif ( file_exists("$spool_directory/msglog/$message_exim_id") ) { #not split
$msglog_size = ( stat(_) )[7];
$msglog_file = "$spool_directory/msglog/$message_exim_id";
}
if ( $msglog_file && open( my $msg_log_fh, '<', $msglog_file ) ) {
seek( $msg_log_fh, $msglog_size - 4096, 0 ) if $msglog_size > 8192;
local $/;
$last_message = ( split( /\n/, readline($msg_log_fh) ) )[-1];
}
# Exim::log_write("!DEBUG! _get_last_delivery_message for [$message_exim_id] is $last_message");
return $last_message || '';
}
sub resolve_authenticated_sender {
my ( $sender, $domain, $sender_lookup_method ) = @_;
my $sender_address = Exim::expand_string('$sender_address');
my $sender_address_domain = Exim::expand_string('$sender_address_domain');
# We only want to use the sender in the from header if they have already
# authenticated with at least the permissions of the account
my ( $from_h_sender, $from_h_localpart, $from_h_domain ) = _get_from_h_sender();
$primary_hostname ||= Exim::expand_string('$primary_hostname');
# The user expects to be able to just set the From: headers
# we try to accomodate that first if they have permissions on the account
if ( $from_h_domain eq $primary_hostname ) {
$sender_lookup_method .= "/primary_hostname/system user";
}
elsif ( $sender eq getdomainowner($from_h_domain) ) {
$sender = $from_h_localpart . '@' . $from_h_domain;
$domain = $from_h_domain;
$sender_lookup_method .= "/from_h";
}
# otherwise we fallback to the sender_address_domain
elsif ( $sender eq getdomainowner($sender_address_domain) ) {
$sender = $sender_address;
$domain = $sender_address_domain;
$sender_lookup_method .= "/sender_address_domain";
}
else {
# finally we accept that we don't know who sent it besdies the
# authenticated user
$sender_lookup_method .= "/only user confirmed/virtual account not confirmed";
}
return ( $sender, $domain, $sender_lookup_method );
}
sub resolve_vhost_owner {
if ( file_exists('/var/cpanel/config/email/trust_x_php_script') ) {
if ( my $x_php_script = Exim::expand_string('$h_x-php-script:') ) {
#X-PHP-Script: <servername><php-self> for <remote-addr>
#X-PHP-Script: www.example.com/~user/testapp/send-mail.php for 10.0.0.1
my ( $servername, $uri ) = split( m{/}, $x_php_script, 2 );
if ( $uri =~ m/^\/?\~([^\/\s]+)/ ) {
my $http_user = $1;
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=trust_x_php_script");
return $uid . ':' . '//' . $servername . '/' . $uri . ' ';
}
elsif ( my $http_user = getdomainowner($servername) ) {
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=trust_x_php_script");
return $uid . ':' . '//' . $servername . '/' . $uri . ' ';
}
}
}
if ( file_exists('/var/cpanel/config/email/query_apache_for_nobody_senders') ) {
# Lets lookup the real uid by querying apache
require Cpanel::ProcessInfo;
require Cpanel::ApacheServerStatus;
my $server_status = Cpanel::ApacheServerStatus->new();
my $httpd_pid;
my $http_status_data;
my $current_pid = $$;
while ( ( $current_pid = Cpanel::ProcessInfo::get_parent_pid($current_pid) ) && $current_pid != 1 ) {
if ( my $status_data = $server_status->get_status_by_pid($current_pid) ) {
$httpd_pid = $current_pid;
$http_status_data = $status_data;
last;
}
}
if ($http_status_data) {
my $uri = ( split( /\s+/, $http_status_data->{'request'} ) )[1];
if ( $uri =~ m/^\/?\~([^\/\s]+)/ ) {
my $http_user = $1;
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=query_apache_for_nobody_senders");
return $uid . ':' . '//' . $http_status_data->{'vhost'} . $uri . ' ';
}
elsif ( my $http_user = getdomainowner( $http_status_data->{'vhost'} ) ) {
my $uid = user2uid($http_user);
Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=query_apache_for_nobody_senders");
return $uid . ':' . '//' . $http_status_data->{'vhost'} . $uri . ' ';
}
}
}
return;
}
# Obtain the from header from the message
# We fallback to the envelope sender if there
# is no from header set (ie sendmail -bt or missing From header)
sub _get_from_h_sender {
my $from_h_domain = Exim::expand_string('${domain:$h_from:}');
my $from_h_local_part = Exim::expand_string('${local_part:$h_from:}');
if ( length $from_h_local_part ) {
if ( length $from_h_domain ) {
return ( $from_h_local_part . '@' . $from_h_domain, $from_h_local_part, $from_h_domain );
}
else {
$primary_hostname ||= Exim::expand_string('$primary_hostname');
return ( $from_h_local_part . '@' . $primary_hostname, $from_h_local_part, $primary_hostname );
}
}
else {
# Handle fallback to sender_address when message is missing a from header
my $sender_address_domain = Exim::expand_string('$sender_address_domain');
my $sender_address_local_part = Exim::expand_string('$sender_address_local_part');
return ( $sender_address_local_part . '@' . $sender_address_domain, $sender_address_local_part, $sender_address_domain );
}
}
my $email_holds_dir = '/var/cpanel/email_holds';
sub track_held_message {
my ($holder) = @_;
if ( -1 != index( $holder, '/' ) ) {
warn "Holder “$holder” should not have “/” in it!";
$holder =~ s/\///g; #jic
}
my $message_exim_id = Exim::expand_string('$message_exim_id');
_check_hold_dir($holder);
my $path = "$email_holds_dir/track/$holder/$message_exim_id";
if ( !-e $path ) {
if ( $! == _ENOENT() ) {
open( my $fh, '>>', $path ) or do {
warn "open(>>, $path): $!";
};
}
else {
warn "stat($path): $!";
}
}
return 1;
}
sub _mkdir_if_not_exists_or_warn {
my ( $path, $mode ) = @_;
mkdir( $path, $mode ) or do {
if ( $! != _EEXIST() ) {
warn "mkdir($path, $mode): $!";
}
return undef;
};
return 1;
}
sub _check_hold_dir {
my ($holder) = @_;
if ( !-e "$email_holds_dir/track/$holder" ) {
if ( $! == _ENOENT() ) {
_mkdir_if_not_exists_or_warn( $email_holds_dir, 0751 );
_mkdir_if_not_exists_or_warn( "$email_holds_dir/track", 0750 );
_mkdir_if_not_exists_or_warn( "$email_holds_dir/track/$holder", 0750 );
}
else {
warn "stat($email_holds_dir/track/$holder): $!";
}
}
return;
}
=head2 maskdir($dir)
This function converts a path on the system to a path relative to the users home directory that it contains. The relative path is prefixed with the user's primary domain in the below format:
domain.tld:/public_html/cgi-bin/xyz.cgi
If the path is not contained within a user's home directory, the path is returned without modification.
=cut
sub maskdir {
my ($dir) = @_;
# Try the user first
my $maskeddir = $dir;
my ($likely_user) = ( split( m{/}, $dir ) )[2];
if ( my $likely_homedir = gethomedir($likely_user) ) {
chop $likely_homedir if substr( $likely_homedir, -1 ) eq '/';
if ( rindex( $dir, "$likely_homedir/", 0 ) == 0 ) {
substr( $maskeddir, 0, length($likely_homedir), getusersdomain($likely_user) . ":" );
return $maskeddir;
}
}
# Next try all users in /etc/passwd
if ( open my $passwd_fh, '<', "/etc/passwd" ) {
while ( readline($passwd_fh) ) {
my ( $homedir, $uid, $user ) = ( split( /:/, $_ ) )[ 0, 2, 5 ];
next if $uid < 100 || length $homedir < 3;
chop $homedir if substr( $homedir, -1 ) eq '/';
if ( rindex( $dir, "$homedir/", 0 ) == 0 ) {
substr( $maskeddir, 0, length($homedir), getusersdomain($user) . ":" );
return $maskeddir;
}
}
}
else {
warn "open(/etc/passwd): $!";
}
return $dir;
}
sub extract_hosts_from_route_list_item {
my $item = shift;
my (undef, $hosts, undef) = Exim::parse_route_item($item);
return $hosts;
}
sub convert_to_hostlist_item {
my ($item, $separator) = @_;
$separator //= '\n';
$item =~ s/^\s+//;
$item =~ s/\s+$//;
# Ignore group separator:
if ($item eq '+') {
$item = '';
}
# Extract bracketed IP address:
elsif ( $item !~ s/^\[(\S*)\]:\d+$/$1/ ) {
# If nothing subbed, what's left is an unbracketed IPv4 or a hostname.
# Remove port if present:
$item =~ s/:\d+$//;
# Finally, if the hostname specified /mx, do a lookup of its MX records and sub in the entire list:
if ($item =~ s{^(\S+)/mx$}{$1}i) {
$item = Exim::expand_string('${lookup dnsdb{>' . $separator . ' mxh=' . $item . '}{$value}}');
}
}
return $item;
}
sub get_suspended_shell {
my ($user) = @_;
my $passwd_file_shell = Exim::expand_string( '${extract{6}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}}}}' );
if ( !length($passwd_file_shell) ) {
return '';
}
if ( $passwd_file_shell ne '/bin/false' ) {
return $passwd_file_shell;
}
if ( open my $fh, '<', "/var/cpanel/suspendinfo/${user}" ) {
while ( my $ln = readline($fh) ) {
if ( $ln =~ m{\Ashell=\s*(\S+)} ) {
close $fh;
return $1;
}
}
close $fh;
}
return '/usr/local/cpanel/bin/noshell';
}
# Untaint a string for exim. This is not a perl untaint
sub untaint {
return $_[0];
}
require Cpanel::Encoder::Exim;
require Cpanel::Server::Type::Role::MailRelay;
1;
BEGIN { # Suppress load of all of these at earliest point.
$INC{'cPstrict.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Encoder/Exim.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ExceptionMessage.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Locale/Utils/Fallback.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ExceptionMessage/Raw.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/LoadModule/Utils.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ScalarUtil.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Exception/CORE.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Pack.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Pack/Template.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/IP/v4.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/IP.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Validate/IP/Expand.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/IP/Expand.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Linux/Netlink.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Linux/Proc/Net/Tcp.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Ident.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie/CORE/exists.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie/CORE/exists_nofollow.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Autodie/More/Lite.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Services/Enabled/Spamd.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/FileUtils/Dir.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/DKIM/ValidityCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Context.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ProcessInfo.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Fcntl/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Socket/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Hulk/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/ApacheServerStatus.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/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/EnabledCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role/TouchFileRole.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
$INC{'Cpanel/Server/Type/Role/MailRelay.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static';
}
{ # --- BEGIN cPstrict
package cPstrict;
# cpanel - cPstrict.pm Copyright 2022 cPanel, L.L.C.
# All rights Reserved.
# copyright@cpanel.net http://cpanel.net
# This code is subject to the cPanel license. Unauthorized copying is prohibited
use strict;
use warnings;
=pod
This is importing the following to your namespace
use strict;
use warnings;
use v5.30;
use feature 'signatures';
no warnings 'experimental::signatures';
=cut
sub import {
# auto import strict and warnings to our caller
warnings->import();
strict->import();
require feature;
feature->import( ':5.30', 'signatures' );
warnings->unimport('experimental::signatures');
return;
}
1;
} # --- END cPstrict
{ # --- BEGIN Cpanel/Encoder/Exim.pm
package Cpanel::Encoder::Exim;
my %encodes = (
q{\\} => q{\\\\\\\\}, #\ -> \\\\
q{"} => q{\\"}, #" -> \"
q{$} => q{\\\\$}, #$ -> \\$
"\x0a" => q{\\n}, #newline -> \n
"\x0d" => q{\\r}, #carriage return -> \r
"\x09" => q{\\t}, #tab => \t
);
sub encode_string_literal {
return if !defined $_[0];
return q{"} . join( q{}, map { $encodes{$_} || $_ } split( m{}, $_[0] ) ) . q{"};
}
sub unquoted_encode_string_literal {
my $string = shift;
return if !defined $string;
$string =~ s/\\N/\\N\\\\N\\N/g; # Only use / here for perl compat
return "\\N$string\\N";
}
1;
} # --- END Cpanel/Encoder/Exim.pm
{ # --- BEGIN Cpanel/ExceptionMessage.pm
package Cpanel::ExceptionMessage;
use strict;
# use Cpanel::Exception ();
*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::CORE;
1;
package Cpanel::Exception;
use strict;
BEGIN {
$INC{'Cpanel/Exception.pm'} = '__BYPASSED__';
}
our $_SUPPRESS_STACK_TRACES = 0;
our $_EXCEPTION_MODULE_PREFIX = 'Cpanel::Exception';
our $IN_EXCEPTION_CREATION = 0;
our $_suppressed_msg = '__STACK_TRACE_SUPPRESSED__YOU_SHOULD_NEVER_SEE_THIS_MESSAGE__';
my $PACKAGE = 'Cpanel::Exception';
my $locale;
my @ID_CHARS = qw( a b c d e f g h j k m n p q r s t u v w x y z 2 3 4 5 6 7 8 9 );
my $ID_LENGTH = 6;
# use Cpanel::ExceptionMessage::Raw ();
# 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'} && $0 !~ m{cpkeyclt|cpsrvd\.so|t/large};
}
sub _init { return 1 } # legacy
sub create {
my ( $exception_type, @args ) = @_;
_init();
if ($IN_EXCEPTION_CREATION) {
_load_cpanel_carp();
die 'Cpanel::Carp'->can('safe_longmess')->("Attempted to create a “$exception_type” exception with arguments “@args” while creating exception “$IN_EXCEPTION_CREATION->[0]” with arguments “@{$IN_EXCEPTION_CREATION->[1]}”.");
}
local $IN_EXCEPTION_CREATION = [ $exception_type, \@args ];
if ( $exception_type !~ m/\A[A-Za-z0-9_]+(?:\:\:[A-Za-z0-9_]+)*\z/ ) {
die "Invalid exception type: $exception_type";
}
my $perl_class;
if ( $exception_type eq __PACKAGE__ ) {
$perl_class = $exception_type;
}
else {
$perl_class = "${_EXCEPTION_MODULE_PREFIX}::$exception_type";
}
_load_perl_module($perl_class) unless $perl_class->can('new');
if ( $args[0] && ref $args[0] eq 'ARRAY' && scalar @{ $args[0] } > 1 ) {
$args[0] = { @{ $args[0] } };
}
return $perl_class->new(@args);
}
sub create_raw {
my ( $class, $msg, @extra_args ) = @_;
_init();
my $msg_obj = 'Cpanel::ExceptionMessage::Raw'->new($msg);
if ( $class =~ m<\A(?:\Q${_EXCEPTION_MODULE_PREFIX}::\E)?Collection\z> ) {
die "Use create('Collection', ..) to create a Cpanel::Exception::Collection object.";
}
return create( $class, $msg_obj, @extra_args );
}
sub _load_perl_module {
my ($module) = @_;
local ( $!, $@ );
if ( !defined $module ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a module name.") );
}
return 1 if Cpanel::LoadModule::Utils::module_is_loaded($module);
my $module_name = $module;
$module_name =~ s{\.pm$}{};
if ( !Cpanel::LoadModule::Utils::is_valid_module_name($module_name) ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a valid module name: '$module_name'.") );
}
{
eval qq{use $module (); 1 }
or die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module cannot load '$module_name': $@") )
}
return 1;
}
sub new {
my ( $class, @args ) = @_;
@args = grep { defined } @args;
my $self = {};
bless $self, $class;
if ( ref $args[-1] eq 'HASH' ) {
$self->{'_metadata'} = pop @args;
}
if ( defined $self->{'_metadata'}->{'longmess'} ) {
$self->{'_longmess'} = &{ $self->{'_metadata'}->{'longmess'} }($self)
if $self->{'_metadata'}->{'longmess'};
}
elsif ($_SUPPRESS_STACK_TRACES) {
$self->{'_longmess'} = $_suppressed_msg;
}
else {
if ( !$INC{'Carp.pm'} ) { _load_carp(); }
$self->{'_longmess'} = scalar do {
local $Carp::CarpInternal{'Cpanel::Exception'} = 1;
local $Carp::CarpInternal{$class} = 1;
'Carp'->can('longmess')->();
};
}
_init();
$self->{'_auxiliaries'} = [];
if ( UNIVERSAL::isa( $args[0], 'Cpanel::ExceptionMessage' ) ) {
$self->{'_message'} = shift @args;
}
else {
my @mt_args;
if ( @args && !ref $args[0] ) {
@mt_args = ( shift @args );
if ( ref $args[0] eq 'ARRAY' ) {
push @mt_args, @{ $args[0] };
}
}
else {
$self->{'_orig_mt_args'} = $args[0];
my $phrase = $self->_default_phrase( $args[0] );
if ($phrase) {
if ( ref $phrase ) {
@mt_args = $phrase->to_list();
}
else {
$self->{'_message'} = Cpanel::ExceptionMessage::Raw->new($phrase);
return $self;
}
}
}
if ( my @extras = grep { !ref } @args ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("Extra scalar(s) passed to $PACKAGE! (@extras)") );
}
if ( !length $mt_args[0] ) {
die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("No args passed to $PACKAGE constructor!") );
}
$self->{'_mt_args'} = \@mt_args;
}
return $self;
}
sub get_string {
my ( $exc, $no_id_yn ) = @_;
return get_string_no_id($exc) if $no_id_yn;
return _get_string( $exc, 'to_string' );
}
sub get_string_no_id {
my ($exc) = @_;
return _get_string( $exc, 'to_string_no_id' );
}
sub _get_string {
my ( $exc, $cp_exc_stringifier_name ) = @_;
return $exc if !ref $exc;
{
local $@;
my $ret = eval { $exc->$cp_exc_stringifier_name() };
return $ret if defined $ret && !$@ && !ref $ret;
}
if ( ref $exc eq 'HASH' && $exc->{'message'} ) {
return $exc->{'message'};
}
if ( $INC{'Cpanel/YAML.pm'} ) {
local $@;
my $ret = eval { 'Cpanel::YAML'->can('Dump')->($exc); };
return $ret if defined $ret && !$@;
}
if ( $INC{'Cpanel/JSON.pm'} ) {
local $@;
my $ret = eval { 'Cpanel::JSON'->can('Dump')->($exc); };
return $ret if defined $ret && !$@;
}
return $exc;
}
sub _create_id {
srand();
return join(
q<>,
map { $ID_CHARS[ int rand( 0 + @ID_CHARS ) ]; } ( 1 .. $ID_LENGTH ),
);
}
sub get_stack_trace_suppressor {
return Cpanel::Exception::_StackTraceSuppression->new();
}
sub set_id {
my ( $self, $new_id ) = @_;
$self->{'_id'} = $new_id;
return $self;
}
sub id {
my ($self) = @_;
return $self->{'_id'} ||= _create_id();
}
sub set {
my ( $self, $key ) = @_;
$self->{'_metadata'}{$key} = $_[2];
if ( exists $self->{'_orig_mt_args'} ) {
my $phrase = $self->_default_phrase( $self->{'_orig_mt_args'} );
if ($phrase) {
if ( ref $phrase ) {
$self->{'_mt_args'} = [ $phrase->to_list() ];
undef $self->{'_message'};
}
else {
$self->{'_message'} = Cpanel::ExceptionMessage::Raw->new($phrase);
}
}
}
return $self;
}
sub get {
my ( $self, $key ) = @_;
my $v = $self->{'_metadata'}{$key};
if ( my $reftype = ref $v ) {
local $@;
if ( $reftype eq 'HASH' ) {
$v = { %{$v} }; # shallow copy
}
elsif ( $reftype eq 'ARRAY' ) {
$v = [ @{$v} ]; # shallow copy
}
elsif ( $reftype eq 'SCALAR' ) {
$v = \${$v}; # shallow copy
}
else {
local ( $@, $! );
require Cpanel::ScalarUtil;
if ( $reftype ne 'GLOB' && !Cpanel::ScalarUtil::blessed($v) ) {
warn if !eval {
_load_perl_module('Clone') if !$INC{'Clone.pm'};
$v = 'Clone'->can('clone')->($v);
};
}
}
}
return $v;
}
sub get_all_metadata {
my $self = shift;
my %metadata_copy;
for my $key ( keys %{ $self->{'_metadata'} } ) {
$metadata_copy{$key} = $self->get($key);
}
return \%metadata_copy;
}
my $loaded_LocaleString;
sub _require_LocaleString {
return $loaded_LocaleString ||= do {
local $@;
eval 'require Cpanel::LocaleString; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand
1;
};
}
my $loaded_ExceptionMessage_Locale;
sub _require_ExceptionMessage_Locale {
return $loaded_ExceptionMessage_Locale ||= do {
local $@;
eval 'require Cpanel::ExceptionMessage::Locale; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand
1;
};
}
sub _default_phrase {
_require_LocaleString();
return 'Cpanel::LocaleString'->new( 'An unknown error in the “[_1]” package has occurred.', scalar ref $_[0] ); # PPI NO PARSE - loaded above
}
sub longmess {
my ($self) = @_;
return '' if $self->{'_longmess'} eq $_suppressed_msg;
_load_cpanel_carp() if !$INC{'Cpanel/Carp.pm'};
return Cpanel::Carp::sanitize_longmess( $self->{'_longmess'} );
}
sub to_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_string_no_id() );
}
sub to_string_no_id {
my ($self) = @_;
my $string = $self->to_locale_string_no_id();
if ( $self->_message()->get_language_tag() ne 'en' ) {
my $en_string = $self->to_en_string_no_id();
$string .= "\n$en_string" if ( $en_string ne $string );
}
return $string;
}
sub _apply_id_prefix {
my ( $id, $msg ) = @_;
return sprintf "(XID %s) %s", $id, $msg;
}
sub to_en_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_en_string_no_id() );
}
sub to_en_string_no_id {
my ($self) = @_;
return $self->_message()->to_en_string() . $self->_stringify_auxiliaries('to_en_string');
}
sub to_locale_string {
my ($self) = @_;
return _apply_id_prefix( $self->id(), $self->to_locale_string_no_id() );
}
sub to_locale_string_no_id {
my ($self) = @_;
return $self->_message()->to_locale_string() . $self->_stringify_auxiliaries('to_locale_string');
}
sub add_auxiliary_exception {
my ( $self, $aux ) = @_;
return push @{ $self->{'_auxiliaries'} }, $aux;
}
sub get_auxiliary_exceptions {
my ($self) = @_;
die 'List context only!' if !wantarray; #Can’t use Cpanel::Context
return @{ $self->{'_auxiliaries'} };
}
sub __spew {
my ($self) = @_;
return $self->_spew();
}
sub _spew {
my ($self) = @_;
return ref($self) . '/' . join "\n", $self->to_string() || '<no message>', $self->longmess() || ();
}
sub _stringify_auxiliaries {
my ( $self, $method ) = @_;
my @lines;
if ( @{ $self->{'_auxiliaries'} } ) {
local $@;
_require_LocaleString();
my $intro = 'Cpanel::LocaleString'->new( 'The following additional [numerate,_1,error,errors] occurred:', 0 + @{ $self->{'_auxiliaries'} } ); # PPI NO PARSE - required above
if ( $method eq 'to_locale_string' ) {
push @lines, _locale()->makevar( $intro->to_list() );
}
elsif ( $method eq 'to_en_string' ) {
push @lines, _locale()->makethis_base( $intro->to_list() );
}
else {
die "Invalid method: $method";
}
push @lines, map { UNIVERSAL::isa( $_, __PACKAGE__ ) ? $_->$method() : $_ } @{ $self->{'_auxiliaries'} };
}
return join q<>, map { "\n$_" } @lines;
}
*TO_JSON = \&to_string;
sub _locale {
return $locale ||= do {
local $@;
eval 'require Cpanel::Locale; 1;' or die $@;
'Cpanel::Locale'->get_handle(); # hide from perlcc
};
}
sub _reset_locale {
return undef $locale;
}
sub _load_carp {
if ( !$INC{'Carp.pm'} ) {
local $@;
eval 'require Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc
}
return;
}
sub _load_cpanel_carp {
if ( !$INC{'Cpanel/Carp.pm'} ) {
local $@;
eval 'require Cpanel::Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc
}
return;
}
sub _message {
my ($self) = @_;
return $self->{'_message'} if $self->{'_message'};
local $!;
if ($Cpanel::Exception::LOCALIZE_STRINGS) { # the default
_require_ExceptionMessage_Locale();
return ( $self->{'_message'} ||= 'Cpanel::ExceptionMessage::Locale'->new( @{ $self->{'_mt_args'} } ) ); # PPI NO PARSE - required above
}
return ( $self->{'_message'} ||= Cpanel::ExceptionMessage::Raw->new( Cpanel::ExceptionMessage::Raw::convert_localized_to_raw( @{ $self->{'_mt_args'} } ) ) );
}
package Cpanel::Exception::_StackTraceSuppression;
sub new {
my ($class) = @_;
$Cpanel::Exception::_SUPPRESS_STACK_TRACES++;
return bless [], $class;
}
sub DESTROY {
$Cpanel::Exception::_SUPPRESS_STACK_TRACES--;
return;
}
1;
} # --- END Cpanel/Exception/CORE.pm
{ # --- BEGIN Cpanel/Pack.pm
package Cpanel::Pack;
use strict;
sub new {
my ( $class, $template_ar ) = @_;
if ( @$template_ar % 2 ) {
die "Cpanel::Pack::new detected an odd number of elements in hash assignment!";
}
my $self = bless {
'template_str' => '',
'keys' => [],
}, $class;
my $ti = 0;
while ( $ti < $#$template_ar ) {
push @{ $self->{'keys'} }, $template_ar->[$ti];
$self->{'template_str'} .= $template_ar->[ 1 + $ti ];
$ti += 2;
}
return $self;
}
sub unpack_to_hashref { ## no critic (RequireArgUnpacking)
my %result;
@result{ @{ $_[0]->{'keys'} } } = unpack( $_[0]->{'template_str'}, $_[1] );
return \%result;
}
sub pack_from_hashref {
my ( $self, $opts_ref ) = @_;
no warnings 'uninitialized';
return pack( $self->{'template_str'}, @{$opts_ref}{ @{ $self->{'keys'} } } );
}
sub sizeof {
my ($self) = @_;
return ( $self->{'sizeof'} ||= length pack( $self->{'template_str'}, () ) );
}
sub malloc {
my ($self) = @_;
return pack( $self->{'template_str'} );
}
1;
} # --- END Cpanel/Pack.pm
{ # --- BEGIN Cpanel/Pack/Template.pm
package Cpanel::Pack::Template;
use strict;
use warnings;
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 {
my ($ip) = @_;
return unless $ip; # False scalars are never an _[0].
my @segments = split /\./, $ip, -1;
return unless scalar @segments == 4;
my $octet_index;
for my $octet_value (@segments) {
return if !_valid_octet( $octet_value, ++$octet_index );
}
return 1;
}
sub is_valid_cidr4 {
my ($ip) = @_;
return unless defined $ip && $ip;
my ( $ip4, $mask ) = split /\//, $ip;
return if !defined $mask || !length $mask || $mask =~ tr/0-9//c;
return is_valid_ipv4($ip4) && 0 < $mask && $mask <= 32;
}
sub _valid_octet {
my ( $octet_value, $octet_index ) = @_;
return (
!length $octet_value || #
$octet_value =~ tr/0-9//c || #
$octet_value > 255 || #
( substr( $octet_value, 0, 1 ) == 0 && length($octet_value) > 1 ) || # Only dec values are permitted
$octet_index == 1 && length($octet_value) && !$octet_value # First oct can't be zero.
) ? 0 : 1;
}
1;
} # --- END Cpanel/Validate/IP/v4.pm
{ # --- BEGIN Cpanel/Validate/IP.pm
package Cpanel::Validate::IP;
use strict;
use warnings;
# 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 0 if !$str;
return 1 if is_valid_ip_cidr_or_prefix($str);
my @pieces = split /-/, $str, 2;
return 1 if 2 == grep { defined($_) } map { Cpanel::Validate::IP::v4::is_valid_ipv4($_) } @pieces;
return 1 if 2 == grep { defined($_) } map { is_valid_ipv6($_) } @pieces;
return 0;
}
1;
} # --- END Cpanel/Validate/IP.pm
{ # --- BEGIN Cpanel/Validate/IP/Expand.pm
package Cpanel::Validate::IP::Expand;
use strict;
use warnings;
# 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_26_KERNEL = 0;
our $NETLINK_INET_DIAG = 4;
our $NLM_F_REQUEST = 1;
our $NLM_F_MULTI = 2; # /* Multipart message, terminated by NLMSG_DONE */
our $NLM_F_ROOT = 0x100;
our $NLM_F_MATCH = 0x200; # in queries, return all matches
our $NLM_F_EXCL = 0x200; # in commands, don't alter if it exists
our $NLM_F_CREATE = 0x400; # in commands, create if it does not exist
our $NLM_F_ACK = 4;
our $SOCK_DGRAM = 2;
our $TCPDIAG_GETSOCK = 18;
our $INET_DIAG_NOCOOKIE = 0xFFFFFFFF;
use constant {
PACK_TEMPLATE_U16 => Cpanel::Pack::Template::PACK_TEMPLATE_U16,
U16_BYTES_LENGTH => Cpanel::Pack::Template::U16_BYTES_LENGTH,
PACK_TEMPLATE_U32 => Cpanel::Pack::Template::PACK_TEMPLATE_U32,
U32_BYTES_LENGTH => Cpanel::Pack::Template::U32_BYTES_LENGTH,
};
my $NLMSG_HEADER_PACK_OBJ;
my $NLMSG_HEADER_PACK_OBJ_SIZE;
our @NLMSG_HEADER_TEMPLATE;
BEGIN {
@NLMSG_HEADER_TEMPLATE = (
'nlmsg_length' => PACK_TEMPLATE_U32(), #__u32 nlmsg_len; /* Length of message including header. */
'nlmsg_type' => PACK_TEMPLATE_U16(), #__u16 nlmsg_type; /* Type of message content. */
'nlmsg_flags' => PACK_TEMPLATE_U16(), #__u16 nlmsg_flags; /* Additional flags. */
'nlmsg_seq' => PACK_TEMPLATE_U32(), #__u32 nlmsg_seq; /* Sequence number. */
'nlmsg_pid' => PACK_TEMPLATE_U32(), #__u32 nlmsg_pid; /* Sender port ID. */
);
}
my @NETLINK_XACTION_REQUIRED = (
'message', #hashref, to be sent via “send_pack_obj”
'send_pack_obj', #Cpanel::Pack instance
'recv_pack_obj', #Cpanel::Pack instance
'sock', #Perl socket
);
my %_u16_cache;
my %_u32_cache;
sub netlink_transaction {
my (%OPTS) = @_;
foreach (@NETLINK_XACTION_REQUIRED) {
die "$_ is required for netlink_transaction" if !$OPTS{$_};
}
my ( $message_ref, $send_pack_obj, $recv_pack_obj, $sock, $parser, $payload_parser, $header_parms_ar ) = @OPTS{ @NETLINK_XACTION_REQUIRED, 'parser', 'payload_parser', 'header' };
my $packed_nlmsg = _pack_nlmsg_with_header( $send_pack_obj, $message_ref, $header_parms_ar );
if (DEBUG) {
require Data::Dumper;
print STDERR "[request]:" . Data::Dumper::Dumper($message_ref);
}
printf STDERR "Send %v02x\n", $packed_nlmsg if DEBUG;
send( $sock, $packed_nlmsg, 0 ) or die "send: $!";
my $message_hr;
my $packed_response = '';
my $header_pack_size = $NLMSG_HEADER_PACK_OBJ->sizeof();
my $recv_pack_size = $recv_pack_obj->sizeof();
my $msgcount = 0;
my ( $msg, $u32, $u16, $nlmsg_length, $nlmsg_type, $nlmsg_flags );
READ_LOOP:
while ( !_nlmsg_type_indicates_finished_reading($message_hr) ) {
sysread( $sock, $packed_response, $NETLINK_READ_SIZE, length $packed_response ) or die "sysread: $!";
PARSE_LOOP:
while (1) {
$msg = substr( $packed_response, 0, $header_pack_size, q<> );
$u32 = substr( $msg, 0, U32_BYTES_LENGTH, '' );
$nlmsg_length = $_u32_cache{$u32} //= unpack( PACK_TEMPLATE_U32, $u32 );
$u16 = substr( $msg, 0, U16_BYTES_LENGTH, '' );
$nlmsg_type = $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 );
$u16 = substr( $msg, 0, U16_BYTES_LENGTH );
$nlmsg_flags = $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 );
last PARSE_LOOP if !$nlmsg_length || length $packed_response < $nlmsg_length - $NLMSG_HEADER_PACK_OBJ_SIZE;
print STDERR "Received message, total size: [$nlmsg_length]\n" if DEBUG;
if ( $nlmsg_type == $NLMSG_ERROR ) {
require Data::Dumper;
my ( $errno, $msg ) = unpack 'i a*', $packed_response;
die Cpanel::Exception::create( 'Netlink', [ error => do { local $! = -$errno }, message => $msg ] );
}
if ( $recv_pack_size <= length $packed_response ) {
my $main_msg = substr( $packed_response, 0, $recv_pack_size, '' );
$message_hr = $recv_pack_obj->unpack_to_hashref($main_msg);
if (DEBUG) {
require Data::Dumper;
printf STDERR "Received %v02x\n", $main_msg;
print STDERR "[response]:" . Data::Dumper::Dumper($message_hr);
}
my $payload = substr(
$packed_response,
0,
$nlmsg_length - $NLMSG_HEADER_PACK_OBJ_SIZE - $recv_pack_size,
q<>,
);
if ( $payload_parser && length $payload ) {
printf STDERR "payload: Received [%v02x]\n", $payload if DEBUG;
$payload_parser->( $msgcount, $message_hr, $payload );
}
}
last READ_LOOP if _nlmsg_type_flags_indicates_finished_reading( $nlmsg_type, $nlmsg_flags );
$msgcount++;
}
}
$parser->( $msgcount, $message_hr ) if $parser && $nlmsg_type;
return 1;
}
our @INET_DIAG_SOCKID_TEMPLATE = (
'idiag_sport' => Cpanel::Pack::Template::PACK_TEMPLATE_BE16, #__be16 idiag_sport;
'idiag_dport' => Cpanel::Pack::Template::PACK_TEMPLATE_BE16, #__be16 idiag_dport;
'idiag_src_0' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[0];
'idiag_src_1' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[1];
'idiag_src_2' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[2];
'idiag_src_3' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[3];
'idiag_dst_0' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[0];
'idiag_dst_1' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[1];
'idiag_dst_2' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[2];
'idiag_dst_3' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[3];
'idiag_if' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_if;
'idiag_cookie_0' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_cookie[0];
'idiag_cookie_1' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_cookie[1];
);
my $INET_DIAG_MSG_PACK_OBJ;
our @INET_DIAG_MSG_TEMPLATE = (
'idiag_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_family; /* Family of addresses. */
'idiag_state' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_state;
'idiag_timer' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_timer;
'idiag_retrans' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_retrans;
@INET_DIAG_SOCKID_TEMPLATE, # inet_diag_sockid
'idiag_expires' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_expires;
'idiag_rqueue' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_rqueue;
'idiag_wqueue' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_wqueue;
'idiag_uid' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_uid;
'idiag_inode' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 #__u32 idiag_inode;
);
my $INET_DIAG_REQ_PACK_OBJ;
our @INET_DIAG_REQ_TEMPLATE = (
'idiag_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_family; /* Family of addresses. */
'idiag_src_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_src_len;
'idiag_dst_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_dst_len;
'idiag_ext' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_ext; /* Query extended information */
@INET_DIAG_SOCKID_TEMPLATE, #inet_diag_sockid
'idiag_states' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_states; /* States to dump */
'idiag_dbs' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 #__u32 idiag_dbs; /* Tables to dump (NI) */
);
sub connection_lookup {
my ( $source_address, $source_port, $dest_address, $dest_port ) = @_;
die "A source port is required." if !defined $source_port;
die "A destination port is required." if !defined $dest_port;
my ( $idiag_dst_0, $idiag_dst_1, $idiag_dst_2, $idiag_dst_3 );
my ( $idiag_src_0, $idiag_src_1, $idiag_src_2, $idiag_src_3 );
my ($idiag_family);
if ( $dest_address =~ tr/:// ) {
require Cpanel::IP::Expand; # hide from exim but not perlcc - not eval quoted
( $idiag_dst_0, $idiag_dst_1, $idiag_dst_2, $idiag_dst_3 ) = unpack( 'N4', pack( 'n8', split /:/, Cpanel::IP::Expand::expand_ip($dest_address) ) );
( $idiag_src_0, $idiag_src_1, $idiag_src_2, $idiag_src_3 ) = unpack( 'N4', pack( 'n8', split /:/, Cpanel::IP::Expand::expand_ip($source_address) ) );
$idiag_family = $AF_INET6;
}
else {
my $u32_dest_address = unpack( 'N', pack( 'C4', split( /\D/, $dest_address, 4 ) ) );
my $u32_source_address = unpack( 'N', pack( 'C4', split( /\D/, $source_address, 4 ) ) );
$idiag_src_0 = $u32_source_address;
$idiag_dst_0 = $u32_dest_address;
$idiag_family = $AF_INET;
}
my $sock;
socket( $sock, $PF_NETLINK, $SOCK_DGRAM, $NETLINK_INET_DIAG ) or die "socket: $!";
$INET_DIAG_REQ_PACK_OBJ ||= Cpanel::Pack->new( \@INET_DIAG_REQ_TEMPLATE );
$INET_DIAG_MSG_PACK_OBJ ||= Cpanel::Pack->new( \@INET_DIAG_MSG_TEMPLATE );
my %RESPONSE;
netlink_transaction(
'message' => {
'idiag_family' => $idiag_family,
'idiag_dst_0' => $idiag_dst_0,
'idiag_dst_1' => $idiag_dst_1,
'idiag_dst_2' => $idiag_dst_2,
'idiag_dst_3' => $idiag_dst_3,
'idiag_dport' => $dest_port,
'idiag_src_0' => $idiag_src_0,
'idiag_src_1' => $idiag_src_1,
'idiag_src_2' => $idiag_src_2,
'idiag_src_3' => $idiag_src_3,
'idiag_sport' => $source_port,
'idiag_cookie_0' => $INET_DIAG_NOCOOKIE,
'idiag_cookie_1' => $INET_DIAG_NOCOOKIE,
},
'sock' => $sock,
'send_pack_obj' => $INET_DIAG_REQ_PACK_OBJ,
'recv_pack_obj' => $INET_DIAG_MSG_PACK_OBJ,
'parser' => sub {
my ( undef, $response_ref ) = @_;
%RESPONSE = %$response_ref if ( $response_ref && 'HASH' eq ref $response_ref );
}
);
return \%RESPONSE;
}
my @NETLINK_SEND_HEADER = (
'nlmsg_length' => undef, #gets put in place
'nlmsg_type' => $TCPDIAG_GETSOCK,
'nlmsg_flags' => 0, #gets |=’d with $NLM_F_REQUEST
'nlmsg_pid' => undef, #gets put in place
'nlmsg_seq' => 2, #default
);
sub _pack_nlmsg_with_header {
my ( $send_pack_obj, $message_ref, $header_parms_ar ) = @_;
my $nlmsg = $send_pack_obj->pack_from_hashref($message_ref);
if ( !$NLMSG_HEADER_PACK_OBJ ) {
$NLMSG_HEADER_PACK_OBJ = Cpanel::Pack->new( \@NLMSG_HEADER_TEMPLATE );
$NLMSG_HEADER_PACK_OBJ_SIZE = $NLMSG_HEADER_PACK_OBJ->sizeof();
}
my %header_data = (
@NETLINK_SEND_HEADER,
( $header_parms_ar ? @$header_parms_ar : () ),
nlmsg_length => $NLMSG_HEADER_PACK_OBJ_SIZE + length $nlmsg,
nlmsg_pid => $$,
);
$header_data{'nlmsg_flags'} |= $NLM_F_REQUEST;
my $hdr_str = $NLMSG_HEADER_PACK_OBJ->pack_from_hashref( \%header_data );
return $hdr_str . $nlmsg;
}
sub _nlmsg_type_indicates_finished_reading {
return _nlmsg_type_flags_indicates_finished_reading( $_[0]->{'nlmsg_type'}, $_[0]->{'nlmsg_flags'} );
}
sub _nlmsg_type_flags_indicates_finished_reading {
return 0 if !length $_[0];
return ( $_[0] == $NLMSG_ERROR || ( $_[1] & $NLM_F_MULTI && $_[0] == $NLMSG_DONE ) || !( $_[1] & $NLM_F_MULTI ) ) ? 1 : 0;
}
sub expect_acknowledgment {
my ( $my_sysread, $socket, $sequence ) = @_;
my $NETLINK_HEADER = Cpanel::Pack->new( \@NLMSG_HEADER_TEMPLATE );
my $response_buffer = '';
my $header_hr;
my $error_code;
do {
while ( length $response_buffer < $NETLINK_HEADER->sizeof() ) {
$my_sysread->( $socket, \$response_buffer, READ_SIZE(), length $response_buffer ) or return "sysread, message header: $!";
}
$header_hr = $NETLINK_HEADER->unpack_to_hashref( substr( $response_buffer, 0, $NETLINK_HEADER->sizeof() ) );
while ( length $response_buffer < $header_hr->{nlmsg_length} ) {
$my_sysread->( $socket, \$response_buffer, READ_SIZE(), length $response_buffer ) or return "sysread, message body: $!";
}
my $message = substr( $response_buffer, 0, $header_hr->{nlmsg_length}, '' );
$error_code = 0;
if ( $header_hr->{nlmsg_type} == $NLMSG_ERROR ) {
$error_code = unpack( Cpanel::Pack::Template::PACK_TEMPLATE_U32, substr( $message, $NETLINK_HEADER->sizeof(), Cpanel::Pack::Template::U32_BYTES_LENGTH ) );
}
if ( $header_hr->{nlmsg_seq} eq $sequence ) {
if ( $header_hr->{nlmsg_type} == $NLMSG_ERROR && $error_code != 0 ) {
local $! = -$error_code;
return "Received error code when expecting acknowledgement: $!\n";
}
if ( $header_hr->{nlmsg_type} == $NLMSG_OVERRUN ) {
return "Data lost due to message overrun";
}
if ( $header_hr->{nlmsg_type} == $NLMSG_DONE ) {
return "Received multipart data when expecting ACK";
}
}
} while ( $header_hr->{nlmsg_seq} ne $sequence || $header_hr->{nlmsg_type} != $NLMSG_ERROR || $error_code != 0 );
return undef;
}
1;
} # --- END Cpanel/Linux/Netlink.pm
{ # --- BEGIN Cpanel/Linux/Proc/Net/Tcp.pm
package Cpanel::Linux::Proc::Net::Tcp;
use strict;
our $PROC_NET_TCP = '/proc/net/tcp';
our $PROC_NET_TCP6 = '/proc/net/tcp6';
sub connection_lookup {
my ( $remote_address, $remote_port, $local_address, $local_port ) = @_;
my ( $tcp_file, $remote_ltl_endian_hex_address, $remote_hex_port, $local_ltl_endian_hex_address, $local_hex_port );
$remote_hex_port = _dec_port_to_hex_port($remote_port);
$local_hex_port = _dec_port_to_hex_port($local_port);
if ( $remote_address =~ tr/:// ) { #ipv6
$tcp_file = $PROC_NET_TCP6;
$remote_ltl_endian_hex_address = _ipv6_text_to_little_endian_hex_address($remote_address);
$local_ltl_endian_hex_address = _ipv6_text_to_little_endian_hex_address($local_address);
}
else {
$tcp_file = $PROC_NET_TCP;
$remote_ltl_endian_hex_address = _ipv4_txt_to_little_endian_hex_address($remote_address);
$local_ltl_endian_hex_address = _ipv4_txt_to_little_endian_hex_address($local_address);
}
if ( open( my $tcp_fh, '<', $tcp_file ) ) {
my $uid;
while ( readline($tcp_fh) ) {
if ( m/^\s*\d+:\s+([\dA-F]{8}(?:[\dA-F]{24})?):([\dA-F]{4})\s+([\dA-F]{8}(?:[\dA-F]{24})?):([\dA-F]{4})\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\d+)/
&& $remote_ltl_endian_hex_address eq $1
&& $remote_hex_port eq $2
&& $local_ltl_endian_hex_address eq $3
&& $local_hex_port eq $4 ) {
$uid = $6;
last;
}
}
return $uid;
}
return;
}
sub _dec_port_to_hex_port {
my ($dec_port) = @_;
return sprintf( '%04X', $dec_port );
}
sub _ipv4_txt_to_little_endian_hex_address {
my ($ipv4_txt) = @_;
return sprintf( "%08X", unpack( 'V', pack( 'C4', split( /\D/, $ipv4_txt, 4 ) ) ) );
}
sub _ipv6_text_to_little_endian_hex_address {
my ($ipv6_txt) = @_;
require Cpanel::IP::Expand; # hide from exim but not perlcc - not eval quoted
my $hexip = '';
my @ip = split /:/, Cpanel::IP::Expand::expand_ip( $ipv6_txt, 6 );
while (@ip) {
my $block1 = shift @ip;
my $block2 = shift @ip;
$hexip .= uc substr( $block2, 2, 2 ) . uc substr( $block2, 0, 2 ) . uc substr( $block1, 2, 2 ) . uc substr( $block1, 0, 2 );
}
return $hexip;
}
1;
} # --- END Cpanel/Linux/Proc/Net/Tcp.pm
{ # --- BEGIN Cpanel/Ident.pm
package Cpanel::Ident;
use strict;
our $TESTING_FLAGS = 0; # FOR TESTING
our $USE_NETLINK = 1; # FOR TESTING
our $USE_PROC = 2; # FOR TESTING
use constant NOTFOUND => 0xff_ff_ff_ff;
sub identify_local_connection {
my ( $source_address, $source_port, $dest_address, $dest_port ) = @_;
if ( !defined($source_port) || !defined($dest_port) ) {
die 'Need source and destination ports!';
}
my $netlink_failed;
if ( !$TESTING_FLAGS || $TESTING_FLAGS == $USE_NETLINK ) {
require Cpanel::Linux::Netlink; # hide from exim but not perlcc - not eval quoted
my $response;
local $@;
eval {
$response = Cpanel::Linux::Netlink::connection_lookup(
$source_address, $source_port,
$dest_address, $dest_port,
);
};
if ($@) {
$netlink_failed = 1;
warn;
}
elsif ($response
&& defined $response->{'idiag_state'}
&& ( $response->{'idiag_state'} != 1 && $response->{'idiag_state'} != 8 && $response->{'idiag_state'} != 10 ) ) {
return -1;
}
elsif ($response
&& ref $response
&& $response->{'idiag_dport'}
&& defined( $response->{'idiag_uid'} )
&& $response->{'idiag_uid'} != NOTFOUND() ) {
return $response->{'idiag_uid'};
}
}
if ( $netlink_failed || $TESTING_FLAGS == $USE_PROC ) {
require Cpanel::Linux::Proc::Net::Tcp; # hide from exim but not perlcc - not eval quoted
my $uid = Cpanel::Linux::Proc::Net::Tcp::connection_lookup( $source_address, $source_port, $dest_address, $dest_port );
return $uid if defined $uid;
}
return;
}
1;
} # --- END Cpanel/Ident.pm
{ # --- BEGIN Cpanel/Autodie.pm
package Cpanel::Autodie;
use strict;
use warnings;
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 $SOCK_NONBLOCK = 2048;
our $SHUT_RD = 0;
our $SHUT_WR = 1;
our $SHUT_RDWR = 2;
our $MSG_PEEK = 2;
our $MSG_NOSIGNAL = 16384;
1;
} # --- END Cpanel/Socket/Constants.pm
{ # --- BEGIN Cpanel/Hulk/Constants.pm
package Cpanel::Hulk::Constants;
use strict;
# use Cpanel::Fcntl::Constants ();
# 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 cPstrict;
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 has_els {
return $FIELDS{els} if defined $FIELDS{els};
return 0 unless _read_license();
return $FIELDS{els} // 0;
}
sub get_license_expire_gmt_date {
return $FIELDS{'license_expire_gmt_date'} if defined $FIELDS{'license_expire_gmt_date'};
return 0 unless _read_license();
return $FIELDS{'license_expire_gmt_date'} // 0;
}
sub is_licensed_for_product ($product) {
return unless $product;
$product = lc $product;
return unless _read_license();
return exists $PRODUCTS{$product};
}
sub get_features {
return unless _read_license();
my @features = split( ",", $FIELDS{'features'} // '' );
return @features;
}
sub has_feature ( $feature = undef ) {
length $feature or return;
return ( grep { $_ eq $feature } get_features() ) ? 1 : 0;
}
sub get_products {
return unless _read_license();
return keys %PRODUCTS;
}
sub _read_license {
my $LICENSE_FILE = _get_license_file_path();
my @new_stat = stat($LICENSE_FILE) if @server_config;
if ( @server_config && @new_stat && $new_stat[9] == $server_config[9] && $new_stat[7] == $server_config[7] ) {
return 1;
}
open( my $fh, '<', $LICENSE_FILE ) or do {
if ( $! != _ENOENT() ) {
warn "open($LICENSE_FILE): $!";
}
return;
};
_reset_cache();
my $content;
read( $fh, $content, 1024 ) // do {
warn "read($LICENSE_FILE): $!";
$content = q<>;
};
return _parse_license_contents_sr( $fh, \$content );
}
sub _parse_license_contents_to_hashref ($content_sr) {
my %vals = map { ( split( m{: }, $_ ) )[ 0, 1 ] } split( m{\n}, $$content_sr );
return \%vals;
}
sub _parse_license_contents_sr ( $fh, $content_sr ) {
my $vals_hr = _parse_license_contents_to_hashref($content_sr);
if ( length $vals_hr->{'products'} ) {
%PRODUCTS = map { ( $_ => 1 ) } split( ",", $vals_hr->{'products'} );
}
else {
return;
}
if ( length $vals_hr->{'maxusers'} ) {
$MAXUSERS //= int $vals_hr->{'maxusers'};
}
else {
return;
}
foreach my $field (qw/license_expire_time license_expire_gmt_date support_expire_time updates_expire_time/) {
$FIELDS{$field} = $vals_hr->{$field} // 0;
}
foreach my $field (qw/client features/) {
$FIELDS{$field} = $vals_hr->{$field} // '';
}
if ( length $vals_hr->{'fields'} ) {
foreach my $field ( split( ",", $vals_hr->{'fields'} ) ) {
my ( $k, $v ) = split( '=', $field, 2 );
$FIELDS{$k} = $v;
}
}
else {
return;
}
@server_config = stat($fh);
return 1;
}
sub _reset_cache {
undef %PRODUCTS;
undef %FIELDS;
undef @server_config;
undef $MAXUSERS;
undef $DNSONLY_MODE;
return;
}
1;
} # --- END Cpanel/Server/Type.pm
{ # --- BEGIN Cpanel/Server/Type/Profile/Constants.pm
package Cpanel::Server::Type::Profile::Constants;
use strict;
use warnings;
use constant {
DNSNODE => "DNSNODE",
DATABASENODE => "DATABASENODE",
DNSONLY => "DNSONLY",
MAILNODE => "MAILNODE",
STANDARD => "STANDARD"
};
our %PROFILE_CHILD_WORKLOADS = (
MAILNODE() => ['Mail'],
);
1;
} # --- END Cpanel/Server/Type/Profile/Constants.pm
{ # --- BEGIN Cpanel/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;
return \@service_subdomains;
}
sub _reset_cache {
undef $_CURRENT_PROFILE;
return;
}
1;
} # --- END Cpanel/Server/Type/Profile.pm
{ # --- BEGIN Cpanel/Server/Type/Role/EnabledCache.pm
package Cpanel::Server::Type::Role::EnabledCache;
use cPstrict;
use Carp ();
my %_THE_CACHE;
sub set ( $class, $value ) {
_validate_class($class);
if ( $value ne '0' && $value ne '1' ) {
_confess("Value must be 0 or 1, not “$value”.");
}
return $_THE_CACHE{$class} = $value;
}
sub get ($class) {
_validate_class($class);
return $_THE_CACHE{$class};
}
sub unset ($class) {
_validate_class($class);
return delete $_THE_CACHE{$class};
}
sub _confess ($msg) {
local $Carp::Internal{ (__PACKAGE__) } = 1;
return Carp::confess($msg);
}
sub _validate_class ($class) {
_confess("Give a class name, not $class!") if ref $class;
return;
}
sub _unset_all () {
%_THE_CACHE = ();
return;
}
1;
} # --- END Cpanel/Server/Type/Role/EnabledCache.pm
{ # --- BEGIN Cpanel/Server/Type/Role.pm
package Cpanel::Server::Type::Role;
use strict;
use warnings;
# use Cpanel::Server::Type::Profile ();
# use Cpanel::Server::Type::Profile::Constants ();
# use Cpanel::Server::Type ();
# use Cpanel::Server::Type::Role::EnabledCache ();
sub new {
return bless {}, $_[0];
}
sub is_enabled {
my ($obj_or_class) = @_;
my $ref = ref($obj_or_class) || $obj_or_class;
my $product_type = Cpanel::Server::Type::get_producttype();
if ( $product_type eq Cpanel::Server::Type::Profile::Constants::DNSONLY() ) {
return Cpanel::Server::Type::Role::EnabledCache::set( $ref, 1 );
}
if ( $product_type ne Cpanel::Server::Type::Profile::Constants::STANDARD() ) {
my $META = Cpanel::Server::Type::Profile::get_meta();
return Cpanel::Server::Type::Role::EnabledCache::set( $ref, 1 ) if grep { $_ eq $ref } @{ $META->{$product_type}{enabled_roles} };
return Cpanel::Server::Type::Role::EnabledCache::set( $ref, 0 ) if !grep { $_ eq $ref } @{ $META->{$product_type}{optional_roles} };
}
my $val = Cpanel::Server::Type::Role::EnabledCache::get($ref);
$val //= Cpanel::Server::Type::Role::EnabledCache::set(
$ref,
$obj_or_class->is_available() && $obj_or_class->_is_enabled() ? 1 : 0,
);
return $val;
}
our %_AVAILABLE_CACHE;
sub is_available {
my ($obj_or_class) = @_;
my $ref = ref($obj_or_class) || $obj_or_class;
return $_AVAILABLE_CACHE{$ref} //= $obj_or_class->_is_available();
}
sub verify_enabled {
my ($class) = @_;
if ( !$class->is_enabled() ) {
my $role = substr( $class, 1 + rindex( $class, ':' ) );
require Cpanel::Exception;
die Cpanel::Exception::create( 'System::RequiredRoleDisabled', [ role => $role ] );
}
return;
}
sub SERVICES { return [] }
sub RESTART_SERVICES { return [] }
sub SERVICE_SUBDOMAINS {
return shift()->_SERVICE_SUBDOMAINS();
}
use constant _SERVICE_SUBDOMAINS => [];
sub RPM_TARGETS {
return shift()->_RPM_TARGETS();
}
use constant _RPM_TARGETS => [];
sub _is_available { return 1 }
sub _NAME {
require Cpanel::Exception;
die Cpanel::Exception::create( 'AbstractClass', [__PACKAGE__] );
}
*_DESCRIPTION = *_NAME;
1;
} # --- END Cpanel/Server/Type/Role.pm
{ # --- BEGIN Cpanel/Server/Type/Role/TouchFileRole.pm
package Cpanel::Server::Type::Role::TouchFileRole;
use strict;
use warnings;
# 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 _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;
| N4m3 |
5!z3 |
L45t M0d!f!3d |
0wn3r / Gr0up |
P3Rm!55!0n5 |
0pt!0n5 |
| .. |
-- |
March 07 2026 15:03:55 |
root / root |
0555 |
|
| ImageMagick-6 |
-- |
November 27 2024 04:08:35 |
root / root |
0755 |
|
| NetworkManager |
-- |
June 11 2024 14:35:41 |
root / root |
0755 |
|
| X11 |
-- |
October 23 2020 09:07:04 |
root / root |
0755 |
|
| acpi |
-- |
October 23 2020 09:17:33 |
root / root |
0755 |
|
| alternatives |
-- |
June 11 2025 04:09:17 |
root / root |
0755 |
|
| apache2 |
-- |
June 11 2025 04:09:21 |
root / root |
0755 |
|
| audisp |
-- |
October 23 2020 09:08:05 |
root / root |
0750 |
|
| audit |
-- |
October 23 2020 09:17:10 |
root / root |
0750 |
|
| bash_completion.d |
-- |
August 15 2024 04:08:51 |
root / root |
0755 |
|
| binfmt.d |
-- |
March 26 2024 12:56:27 |
root / root |
0755 |
|
| chkconfig.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| chkserv.d |
-- |
May 31 2022 03:27:31 |
root / root |
0755 |
|
| cpanel |
-- |
March 07 2026 04:08:06 |
root / root |
0751 |
|
| cron.d |
-- |
February 27 2025 20:09:35 |
root / root |
0755 |
|
| cron.daily |
-- |
October 10 2023 04:08:33 |
root / root |
0755 |
|
| cron.hourly |
-- |
May 19 2023 04:08:20 |
root / root |
0755 |
|
| cron.monthly |
-- |
June 09 2014 22:14:31 |
root / root |
0755 |
|
| cron.weekly |
-- |
June 09 2014 22:14:31 |
root / root |
0755 |
|
| csf |
-- |
February 27 2025 20:09:36 |
root / root |
0600 |
|
| dbus-1 |
-- |
August 03 2021 20:11:09 |
root / root |
0755 |
|
| default |
-- |
July 19 2024 04:08:29 |
root / root |
0755 |
|
| depmod.d |
-- |
October 23 2020 09:06:59 |
root / root |
0755 |
|
| dhcp |
-- |
June 11 2024 14:35:41 |
root / root |
0750 |
|
| dovecot |
-- |
January 14 2026 10:05:23 |
root / root |
0755 |
|
| dracut.conf.d |
-- |
September 30 2020 15:57:57 |
root / root |
0755 |
|
| egl |
-- |
August 03 2021 20:07:35 |
root / root |
0755 |
|
| exports.d |
-- |
October 14 2021 12:29:26 |
root / root |
0755 |
|
| firewalld |
-- |
August 03 2021 20:11:32 |
root / root |
0750 |
|
| fonts |
-- |
October 23 2020 09:20:03 |
root / root |
0755 |
|
| gcrypt |
-- |
November 02 2023 15:22:54 |
root / root |
0755 |
|
| ghostscript |
-- |
September 30 2020 16:20:55 |
root / root |
0755 |
|
| glvnd |
-- |
August 03 2021 20:07:35 |
root / root |
0755 |
|
| gnupg |
-- |
July 13 2018 13:05:22 |
root / root |
0755 |
|
| groff |
-- |
October 23 2020 09:05:07 |
root / root |
0755 |
|
| grub.d |
-- |
June 11 2025 04:08:52 |
root / root |
0700 |
|
| gss |
-- |
February 12 2025 13:26:19 |
root / root |
0755 |
|
| gssproxy |
-- |
October 16 2021 04:08:46 |
root / root |
0755 |
|
| init.d |
-- |
July 04 2024 04:08:43 |
root / root |
0755 |
|
| iproute2 |
-- |
August 03 2021 20:11:02 |
root / root |
0755 |
|
| kernel |
-- |
October 23 2020 09:08:17 |
root / root |
0755 |
|
| krb5.conf.d |
-- |
February 12 2025 13:26:19 |
root / root |
0755 |
|
| ld.so.conf.d |
-- |
June 11 2025 04:10:24 |
root / root |
0755 |
|
| libnl |
-- |
October 23 2020 09:05:06 |
root / root |
0755 |
|
| libpaper.d |
-- |
September 30 2020 16:48:18 |
root / root |
0755 |
|
| logrotate.d |
-- |
March 07 2026 14:51:04 |
root / root |
0755 |
|
| mail |
-- |
October 23 2020 09:28:44 |
root / root |
0755 |
|
| mc |
-- |
October 23 2020 09:08:24 |
root / root |
0755 |
|
| modprobe.d |
-- |
September 24 2022 04:08:23 |
root / root |
0755 |
|
| modules-load.d |
-- |
March 26 2024 12:56:27 |
root / root |
0755 |
|
| my.cnf.d |
-- |
May 11 2023 04:08:45 |
root / root |
0755 |
|
| nagios |
-- |
August 03 2021 20:06:07 |
root / root |
0775 |
|
| named |
-- |
March 28 2025 11:31:52 |
root / named |
0750 |
|
| nginx |
-- |
February 12 2025 05:55:38 |
root / root |
0755 |
|
| nrpe.d |
-- |
September 08 2020 14:36:02 |
root / root |
0755 |
|
| openldap |
-- |
July 04 2024 04:08:42 |
root / root |
0755 |
|
| opt |
-- |
April 11 2018 04:59:55 |
root / root |
0755 |
|
| pam.d |
-- |
January 16 2025 04:09:11 |
root / root |
0755 |
|
| pdns |
-- |
September 19 2024 04:08:45 |
root / root |
0755 |
|
| pkcs11 |
-- |
October 23 2020 09:05:06 |
root / root |
0755 |
|
| pki |
-- |
October 23 2020 09:28:29 |
root / root |
0755 |
|
| plymouth |
-- |
August 03 2021 20:11:15 |
root / root |
0755 |
|
| pm |
-- |
October 23 2020 09:04:33 |
root / root |
0755 |
|
| polkit-1 |
-- |
January 25 2022 19:42:48 |
root / root |
0755 |
|
| popt.d |
-- |
June 10 2014 04:03:22 |
root / root |
0755 |
|
| ppp |
-- |
August 03 2021 20:11:14 |
root / root |
0755 |
|
| prelink.conf.d |
-- |
June 11 2025 04:09:17 |
root / root |
0755 |
|
| profile.d |
-- |
July 29 2024 04:09:41 |
root / root |
0755 |
|
| proftpd |
-- |
August 07 2025 13:58:34 |
root / root |
0751 |
|
| pure-ftpd |
-- |
October 30 2024 12:41:43 |
root / root |
0755 |
|
| python |
-- |
October 16 2024 04:08:47 |
root / root |
0755 |
|
| qemu-ga |
-- |
October 23 2020 09:08:12 |
root / root |
0755 |
|
| rc.d |
-- |
July 04 2024 04:08:43 |
root / root |
0755 |
|
| rc0.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| rc1.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| rc2.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| rc3.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| rc4.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| rc5.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| rc6.d |
-- |
October 13 2020 15:46:48 |
root / root |
0755 |
|
| request-key.d |
-- |
October 16 2021 04:08:46 |
root / root |
0755 |
|
| rpm |
-- |
June 12 2025 04:08:38 |
root / root |
0755 |
|
| rsyslog.d |
-- |
July 04 2024 04:08:43 |
root / root |
0755 |
|
| rwtab.d |
-- |
June 11 2025 04:10:06 |
root / root |
0755 |
|
| sasl2 |
-- |
February 24 2022 13:27:39 |
root / root |
0755 |
|
| scl |
-- |
October 23 2020 09:25:22 |
root / root |
0755 |
|
| security |
-- |
May 11 2023 04:08:45 |
root / root |
0755 |
|
| selinux |
-- |
January 16 2025 04:09:11 |
root / root |
0755 |
|
| skel |
-- |
December 02 2021 04:09:03 |
root / root |
0755 |
|
| smartmontools |
-- |
October 23 2020 09:20:25 |
root / root |
0755 |
|
| ssh |
-- |
June 13 2025 04:12:35 |
root / root |
0755 |
|
| ssl |
-- |
November 27 2024 04:08:33 |
root / root |
0755 |
|
| statetab.d |
-- |
November 16 2020 16:20:16 |
root / root |
0755 |
|
| sudoers.d |
-- |
March 28 2024 17:37:49 |
root / root |
0750 |
|
| sw-engine |
-- |
January 14 2026 17:56:25 |
root / root |
0755 |
|
| sysconfig |
-- |
June 11 2025 04:10:46 |
root / root |
0755 |
|
| sysctl.d |
-- |
March 26 2024 12:56:27 |
root / root |
0755 |
|
| systemd |
-- |
July 04 2024 04:08:43 |
root / root |
0755 |
|
| terminfo |
-- |
May 17 2024 07:49:49 |
root / root |
0755 |
|
| tmpfiles.d |
-- |
March 26 2024 12:56:27 |
root / root |
0755 |
|
| tuned |
-- |
September 24 2022 04:08:23 |
root / root |
0755 |
|
| udev |
-- |
July 20 2025 02:09:56 |
root / root |
0755 |
|
| valiases |
-- |
July 24 2025 18:08:28 |
root / mail |
0751 |
|
| vdomainaliases |
-- |
July 24 2025 18:08:28 |
root / mail |
0751 |
|
| vfilters |
-- |
July 24 2025 18:08:28 |
root / mail |
0751 |
|
| wpa_supplicant |
-- |
August 03 2021 20:11:10 |
root / root |
0755 |
|
| xdg |
-- |
October 23 2020 09:07:04 |
root / root |
0755 |
|
| xinetd.d |
-- |
April 11 2018 04:59:55 |
root / root |
0755 |
|
| yum |
-- |
August 03 2021 20:11:17 |
root / root |
0755 |
|
| yum.repos.d |
-- |
June 13 2025 04:09:26 |
root / root |
0755 |
|
| | | | | |
| .pwd.lock |
0 KB |
October 23 2020 09:06:54 |
root / root |
0600 |
|
| .updated |
0.159 KB |
July 20 2025 02:04:41 |
root / root |
0644 |
|
| .whostmgrft |
0 KB |
January 05 2022 10:29:14 |
root / root |
0644 |
|
| DIR_COLORS |
4.971 KB |
November 16 2020 14:40:20 |
root / root |
0644 |
|
| DIR_COLORS.256color |
5.591 KB |
November 16 2020 14:40:20 |
root / root |
0644 |
|
| DIR_COLORS.lightbgcolor |
4.56 KB |
November 16 2020 14:40:20 |
root / root |
0644 |
|
| GREP_COLORS |
0.092 KB |
March 24 2017 16:39:09 |
root / root |
0644 |
|
| GeoIP.conf |
1.664 KB |
June 12 2023 14:00:57 |
root / root |
0644 |
|
| adjtime |
0.018 KB |
October 23 2020 09:14:51 |
root / root |
0644 |
|
| aliases |
1.493 KB |
August 19 2021 04:11:21 |
root / root |
0644 |
|
| aliases.db |
12 KB |
October 23 2020 09:17:15 |
root / root |
0644 |
|
| anacrontab |
0.528 KB |
May 16 2023 14:28:21 |
root / root |
0600 |
|
| antivirus.exim |
10.385 KB |
July 22 2024 18:49:25 |
root / root |
0644 |
|
| asound.conf |
0.054 KB |
August 08 2019 11:47:50 |
root / root |
0644 |
|
| at.deny |
0.001 KB |
May 18 2022 15:54:00 |
root / root |
0644 |
|
| backupmxhosts |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| bashrc |
3.435 KB |
August 03 2021 20:14:50 |
root / root |
0644 |
|
| bento-metadata.json |
0.032 KB |
October 23 2020 09:17:22 |
root / root |
0444 |
|
| blocked_incoming_email_countries |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| blocked_incoming_email_country_ips |
0 KB |
October 23 2020 09:31:34 |
root / mail |
0640 |
|
| blocked_incoming_email_domains |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| centos-release |
0.036 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| centos-release-upstream |
0.05 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| chrony.conf |
1.082 KB |
August 08 2019 11:40:15 |
root / root |
0644 |
|
| chrony.keys |
0.47 KB |
August 08 2019 11:40:15 |
root / chrony |
0640 |
|
| cpanel_exim_system_filter |
11.859 KB |
January 16 2025 04:09:11 |
root / root |
0644 |
|
| cpanel_mail_netblocks |
0.015 KB |
January 16 2025 04:09:11 |
root / mail |
0640 |
|
| cpspamd.conf |
0 KB |
February 22 2023 04:10:01 |
root / root |
0644 |
|
| cpupdate.conf |
0.084 KB |
June 13 2025 04:08:47 |
root / root |
0644 |
|
| cron.deny |
0.007 KB |
October 23 2020 09:31:28 |
root / root |
0600 |
|
| crontab |
0.44 KB |
June 09 2014 22:14:31 |
root / root |
0644 |
|
| crypttab |
0 KB |
October 23 2020 09:04:14 |
root / root |
0600 |
|
| csh.cshrc |
1.582 KB |
April 01 2020 04:29:31 |
root / root |
0644 |
|
| csh.login |
1.077 KB |
April 01 2020 04:29:32 |
root / root |
0644 |
|
| dbowners |
0.072 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| demodomains |
0 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| demouids |
0 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| demousers |
0 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| digestshadow |
0 KB |
October 23 2020 09:31:24 |
root / root |
0640 |
|
| domain_remote_mx_ips.cdb |
2.413 KB |
June 29 2022 16:03:57 |
root / mail |
0640 |
|
| domain_secondary_mx_ips.cdb |
2 KB |
August 04 2021 23:31:47 |
root / mail |
0640 |
|
| domainips |
0.015 KB |
July 24 2025 16:55:20 |
root / root |
0644 |
|
| domainusers |
0.071 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| dracut.conf |
1.255 KB |
September 30 2020 15:57:57 |
root / root |
0644 |
|
| e2fsck.conf |
0.109 KB |
March 26 2024 13:11:56 |
root / root |
0644 |
|
| elinks.conf |
1.067 KB |
January 10 2019 17:00:54 |
root / root |
0644 |
|
| email_send_limits |
0.266 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| environment |
0 KB |
April 01 2020 04:29:33 |
root / root |
0644 |
|
| ethertypes |
1.286 KB |
April 11 2018 02:44:54 |
root / root |
0644 |
|
| exim.conf |
85.989 KB |
January 16 2025 04:09:11 |
root / root |
0644 |
|
| exim.conf.dist |
25.789 KB |
July 22 2024 18:49:25 |
root / root |
0644 |
|
| exim.conf.local |
0.641 KB |
October 23 2020 09:38:21 |
root / root |
0644 |
|
| exim.conf.localopts |
1.764 KB |
January 16 2025 04:09:20 |
root / root |
0644 |
|
| exim.conf.mailman2.dist |
29.032 KB |
July 22 2024 18:49:25 |
root / root |
0644 |
|
| exim.conf.mailman2.exiscan.dist |
29.203 KB |
July 22 2024 18:49:25 |
root / root |
0644 |
|
| exim.crt |
1.441 KB |
December 05 2023 17:00:38 |
mailnull / mail |
0660 |
|
| exim.key |
1.636 KB |
December 05 2023 17:00:38 |
mailnull / mail |
0660 |
|
| exim.pl |
0.226 KB |
July 22 2024 18:49:25 |
root / root |
0644 |
|
| exim.pl.local |
164.227 KB |
January 16 2025 04:09:11 |
root / root |
0644 |
|
| exim_suspended_list |
0.673 KB |
August 04 2021 23:27:57 |
root / mail |
0640 |
|
| exim_trusted_configs |
0.023 KB |
October 23 2020 09:28:51 |
root / root |
0644 |
|
| eximmailtrap |
0 KB |
October 23 2020 09:24:13 |
root / root |
0644 |
|
| eximrejects |
0.159 KB |
January 16 2025 04:09:12 |
root / root |
0644 |
|
| eximrejects.rpmorig |
0.358 KB |
October 23 2020 09:24:14 |
root / root |
0644 |
|
| exports |
0 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| favicon.png |
1.054 KB |
March 08 2014 05:48:08 |
root / root |
0644 |
|
| filesystems |
0.068 KB |
April 01 2020 04:29:31 |
root / root |
0644 |
|
| fstab |
0.348 KB |
October 23 2020 09:31:28 |
root / root |
0644 |
|
| ftpd-ca.pem |
0 KB |
December 05 2023 17:00:38 |
root / wheel |
0660 |
|
| ftpd-rsa-key.pem |
1.64 KB |
December 05 2023 17:00:38 |
root / wheel |
0660 |
|
| ftpd-rsa.pem |
1.441 KB |
December 05 2023 17:00:38 |
root / wheel |
0660 |
|
| greylist_common_mail_providers |
68.199 KB |
June 13 2025 04:09:25 |
root / root |
0644 |
|
| greylist_trusted_netblocks |
0 KB |
January 16 2025 04:09:11 |
root / mail |
0640 |
|
| group |
1.066 KB |
July 24 2025 16:55:20 |
root / root |
0644 |
|
| group- |
1.028 KB |
October 16 2021 04:08:46 |
root / root |
0644 |
|
| gshadow |
0.865 KB |
July 24 2025 16:55:20 |
root / root |
0600 |
|
| gshadow- |
0.811 KB |
August 04 2021 23:28:20 |
root / root |
0600 |
|
| host.conf |
0.009 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| hostname |
0.024 KB |
August 03 2021 20:02:49 |
root / root |
0644 |
|
| hosts |
0.147 KB |
June 13 2025 04:08:50 |
root / root |
0644 |
|
| hosts.allow |
0.361 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| hosts.deny |
0.449 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| idmapd.conf |
4.735 KB |
April 11 2018 04:07:10 |
root / root |
0644 |
|
| inittab |
0.499 KB |
November 16 2020 16:20:16 |
root / root |
0644 |
|
| inputrc |
0.92 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| ipaddrpool |
0 KB |
July 24 2025 16:55:20 |
root / root |
0644 |
|
| ips |
0 KB |
October 23 2020 09:32:01 |
root / root |
0644 |
|
| issue |
0.022 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| issue.net |
0.021 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| kdump.conf |
7.104 KB |
August 03 2021 20:11:17 |
root / root |
0644 |
|
| krb5.conf |
0.631 KB |
February 12 2025 13:09:05 |
root / root |
0644 |
|
| ld.so.cache |
43.053 KB |
August 27 2025 03:05:39 |
root / root |
0644 |
|
| ld.so.conf |
0.027 KB |
February 27 2013 20:29:02 |
root / root |
0644 |
|
| libaudit.conf |
0.187 KB |
March 01 2019 21:11:04 |
root / root |
0640 |
|
| libuser.conf |
2.335 KB |
October 12 2013 21:56:07 |
root / root |
0644 |
|
| localaliases |
0.049 KB |
September 21 2022 12:27:45 |
root / root |
0644 |
|
| localdomains |
0.139 KB |
July 24 2025 18:08:28 |
root / mail |
0640 |
|
| locale.conf |
0.019 KB |
October 23 2020 09:14:51 |
root / root |
0644 |
|
| localtime |
0.115 KB |
February 14 2024 15:04:17 |
root / root |
0644 |
|
| lock_manager_local.ini |
0.81 KB |
January 01 1990 12:00:00 |
root / root |
0644 |
|
| login.defs |
1.979 KB |
August 06 2019 13:44:46 |
root / root |
0644 |
|
| logrotate.conf |
0.646 KB |
July 31 2013 11:46:23 |
root / root |
0644 |
|
| machine-id |
0.032 KB |
October 23 2020 09:44:36 |
root / root |
0444 |
|
| magic |
0.108 KB |
September 30 2020 16:07:43 |
root / root |
0644 |
|
| mail.rc |
1.922 KB |
April 11 2018 07:07:53 |
root / root |
0644 |
|
| mailbox_formats |
0.074 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| mailcap |
0.266 KB |
May 14 2013 20:23:29 |
root / root |
0644 |
|
| mailhelo |
0.025 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| mailips |
0 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| makedumpfile.conf.sample |
5.002 KB |
June 09 2021 16:09:58 |
root / root |
0644 |
|
| man_db.conf |
5.05 KB |
October 30 2018 20:26:28 |
root / root |
0644 |
|
| manualmx |
0.001 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| mime.types |
50.573 KB |
May 14 2013 20:23:29 |
root / root |
0644 |
|
| mke2fs.conf |
1.08 KB |
March 26 2024 13:19:01 |
root / root |
0644 |
|
| motd |
0 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| mtab |
0 KB |
March 07 2026 15:10:48 |
zagoradraa / zagoradraa |
0444 |
|
| my.cnf |
0.43 KB |
May 11 2023 04:09:18 |
root / root |
0644 |
|
| named.conf |
4.28 KB |
July 24 2025 16:55:20 |
named / named |
0644 |
|
| named.conf.cache |
0.277 KB |
July 24 2025 16:55:20 |
root / root |
0600 |
|
| named.conf.precpanelinstall |
1.851 KB |
October 23 2020 09:24:22 |
root / named |
0640 |
|
| named.conf.prerebuilddnsconfig |
3.606 KB |
October 23 2020 09:31:01 |
root / root |
0644 |
|
| named.conf.rebuilddnsconfig |
3.606 KB |
October 23 2020 09:31:01 |
root / root |
0644 |
|
| named.conf.zonedir.cache |
0.056 KB |
July 24 2025 16:55:20 |
root / root |
0600 |
|
| named.iscdlv.key |
3.831 KB |
March 28 2025 11:31:52 |
root / named |
0644 |
|
| named.rfc1912.zones |
0.909 KB |
June 21 2007 10:09:32 |
root / named |
0640 |
|
| named.root.key |
1.842 KB |
April 13 2017 14:17:40 |
root / named |
0644 |
|
| nameserverips |
0.068 KB |
August 03 2021 20:06:07 |
root / root |
0644 |
|
| nanorc |
8.684 KB |
June 10 2014 04:47:53 |
root / root |
0644 |
|
| neighbor_netblocks |
0.014 KB |
June 13 2025 04:08:51 |
root / mail |
0640 |
|
| netconfig |
0.749 KB |
August 09 2019 00:35:22 |
root / root |
0644 |
|
| networks |
0.057 KB |
November 16 2020 16:20:16 |
root / root |
0644 |
|
| nfs.conf |
0.999 KB |
October 14 2021 12:29:26 |
root / root |
0644 |
|
| nfsmount.conf |
3.312 KB |
October 14 2021 12:29:26 |
root / root |
0644 |
|
| nocgiusers |
0 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| nscd.conf |
2.696 KB |
October 23 2020 09:24:07 |
root / root |
0644 |
|
| nsswitch.conf |
1.903 KB |
October 23 2020 09:07:07 |
root / root |
0644 |
|
| odbcinst.ini |
0.563 KB |
August 09 2019 03:11:45 |
root / root |
0644 |
|
| os-release |
0.384 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| outgoing_mail_hold_users |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| outgoing_mail_suspended_users |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| p0fdisable |
0 KB |
October 23 2020 09:31:30 |
root / root |
0644 |
|
| papersize |
0.066 KB |
September 30 2020 16:48:18 |
root / root |
0644 |
|
| passwd |
2.882 KB |
July 24 2025 16:55:20 |
root / root |
0644 |
|
| passwd- |
2.667 KB |
October 23 2020 09:32:18 |
root / root |
0644 |
|
| passwd.cache |
16.788 KB |
July 24 2025 16:57:19 |
root / root |
0600 |
|
| passwd.nouids.cache |
8.694 KB |
July 24 2025 16:57:19 |
root / root |
0600 |
|
| portassignments |
0 KB |
June 29 2022 16:02:57 |
root / root |
0600 |
|
| printcap |
0.228 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| profile |
2.425 KB |
August 03 2021 20:14:50 |
root / root |
0644 |
|
| protocols |
6.392 KB |
April 01 2020 04:29:32 |
root / root |
0644 |
|
| pure-ftpd.conf |
10.596 KB |
January 16 2025 04:09:13 |
root / root |
0600 |
|
| pure-ftpd.conf.rpmnew |
11.33 KB |
March 10 2022 17:28:54 |
root / root |
0755 |
|
| pure-ftpd.pem |
3.081 KB |
December 05 2023 17:00:38 |
root / wheel |
0660 |
|
| rc.local |
0.462 KB |
March 26 2024 12:56:28 |
root / root |
0644 |
|
| recent_authed_mail_ips |
0 KB |
March 07 2026 14:57:21 |
root / root |
0644 |
|
| recent_authed_mail_ips_users |
0 KB |
March 07 2026 14:57:21 |
root / root |
0644 |
|
| recent_recipient_mail_server_ips |
0 KB |
March 07 2026 15:00:01 |
root / mail |
0640 |
|
| redhat-release |
0.036 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| relayhosts |
0 KB |
March 07 2026 14:57:21 |
root / root |
0644 |
|
| relayhostsusers |
0 KB |
March 07 2026 14:57:21 |
root / root |
0644 |
|
| remotedomains |
0 KB |
October 23 2020 09:32:57 |
root / mail |
0644 |
|
| request-key.conf |
1.745 KB |
June 10 2014 02:17:54 |
root / root |
0644 |
|
| resolv.conf |
0.072 KB |
August 03 2021 20:02:50 |
root / root |
0644 |
|
| rpc |
1.596 KB |
December 25 2012 03:02:13 |
root / root |
0644 |
|
| rsyncd.conf |
0.447 KB |
February 12 2025 10:01:57 |
root / root |
0644 |
|
| rsyslog.conf |
3.156 KB |
January 13 2022 19:10:32 |
root / root |
0644 |
|
| rwtab |
0.984 KB |
November 16 2020 16:20:16 |
root / root |
0644 |
|
| screenrc |
6.564 KB |
March 09 2021 15:26:52 |
root / root |
0644 |
|
| secondarymx |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| securetty |
0.216 KB |
April 01 2020 04:29:31 |
root / root |
0600 |
|
| senderverifybypasshosts |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| services |
654.583 KB |
June 07 2013 14:31:32 |
root / root |
0644 |
|
| sestatus.conf |
0.211 KB |
April 01 2020 04:04:49 |
root / root |
0644 |
|
| shadow |
1.577 KB |
July 24 2025 16:55:20 |
root / root |
0600 |
|
| shadow- |
1.156 KB |
August 03 2021 20:02:48 |
root / root |
0600 |
|
| shadow.nouids.cache |
8.878 KB |
July 24 2025 16:55:30 |
root / root |
0600 |
|
| shells |
0.125 KB |
June 13 2025 04:08:47 |
root / root |
0644 |
|
| skipsmtpcheckhosts |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| smi.conf |
1.242 KB |
January 26 2014 13:19:08 |
root / root |
0644 |
|
| spammeripblocks |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| spammers |
0 KB |
July 22 2024 18:49:25 |
root / root |
0644 |
|
| ssldomains |
0 KB |
October 23 2020 09:31:26 |
root / root |
0600 |
|
| statetab |
0.207 KB |
November 16 2020 16:20:16 |
root / root |
0644 |
|
| stats.conf |
0.036 KB |
October 23 2020 09:31:28 |
root / root |
0644 |
|
| subgid |
0 KB |
April 01 2020 04:29:33 |
root / root |
0644 |
|
| subuid |
0 KB |
April 01 2020 04:29:33 |
root / root |
0644 |
|
| sudo-ldap.conf |
3.106 KB |
March 28 2024 17:31:02 |
root / root |
0640 |
|
| sudo.conf |
1.744 KB |
March 28 2024 17:31:02 |
root / root |
0640 |
|
| sudoers |
4.227 KB |
March 28 2024 17:31:02 |
root / root |
0440 |
|
| suphp.conf |
3.678 KB |
June 12 2025 04:09:00 |
root / root |
0644 |
|
| suphp.conf.rpmnew |
4.417 KB |
December 04 2024 21:43:39 |
root / root |
0644 |
|
| sysctl.conf |
0.623 KB |
October 23 2020 09:38:21 |
root / root |
0644 |
|
| system-release |
0.036 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| system-release-cpe |
0.022 KB |
May 21 2024 14:48:02 |
root / root |
0644 |
|
| tcsd.conf |
6.881 KB |
August 03 2017 17:16:03 |
tss / tss |
0600 |
|
| trueuserdomains |
0.071 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| trueuserowners |
0.046 KB |
July 24 2025 18:08:29 |
root / mail |
0644 |
|
| trusted-key.key |
0.732 KB |
March 28 2025 11:31:52 |
root / root |
0644 |
|
| trusted_mail_users |
0 KB |
October 23 2020 09:24:14 |
root / mail |
0640 |
|
| trustedmailhosts |
0 KB |
January 16 2025 04:09:11 |
root / mail |
0640 |
|
| userbwlimits |
0.054 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| userdatadomains |
0.495 KB |
January 14 2026 10:05:22 |
root / mail |
0640 |
|
| userdatadomains.json |
0.551 KB |
January 14 2026 10:05:22 |
root / root |
0640 |
|
| userdomains |
0.111 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| userips |
0.081 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| userplans |
0.068 KB |
July 24 2025 18:08:29 |
root / mail |
0640 |
|
| vconsole.conf |
0.036 KB |
October 23 2020 09:14:51 |
root / root |
0644 |
|
| vimrc |
1.936 KB |
July 09 2024 16:25:53 |
root / root |
0644 |
|
| virc |
1.936 KB |
July 09 2024 16:25:53 |
root / root |
0644 |
|
| webspam |
0 KB |
October 23 2020 09:24:13 |
root / root |
0644 |
|
| wgetrc |
4.374 KB |
August 15 2024 10:22:15 |
root / root |
0644 |
|
| wwwacct.conf |
0.289 KB |
September 21 2022 12:27:45 |
root / root |
0644 |
|
| wwwacct.conf.cache |
0.36 KB |
September 21 2022 12:32:31 |
root / root |
0644 |
|
| wwwacct.conf.shadow |
0.077 KB |
September 21 2022 12:27:45 |
root / root |
0600 |
|
| wwwacct.conf.shadow.cache |
0.461 KB |
September 21 2022 12:32:31 |
root / root |
0600 |
|
| yum.conf |
1.082 KB |
March 07 2026 04:08:24 |
root / root |
0644 |
|
$.' ",#(7),01444'9=82<.342ÿÛ C
2!!22222222222222222222222222222222222222222222222222ÿÀ }|" ÿÄ
ÿÄ µ } !1AQa "q2‘¡#B±ÁRÑð$3br‚
%&'()*456789:CDEFGHIJSTUVWXYZcdefghijstuvwxyzƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚáâãäåæçèéêñòóôõö÷øùúÿÄ
ÿÄ µ w !1AQ aq"2B‘¡±Á #3RðbrÑ
$4á%ñ&'()*56789:CDEFGHIJSTUVWXYZcdefghijstuvwxyz‚ƒ„…†‡ˆ‰Š’“”•–—˜™š¢£¤¥¦§¨©ª²³´µ¶·¸¹ºÂÃÄÅÆÇÈÉÊÒÓÔÕÖרÙÚâãäåæçèéêòóôõö÷øùúÿÚ ? ÷HR÷j¹ûA <̃.9;r8 íœcê*«ï#k‰a0
ÛZY
²7/$†Æ #¸'¯Ri'Hæ/û]åÊ< q´¿_L€W9cÉ#5AƒG5˜‘¤ª#T8ÀÊ’ÙìN3ß8àU¨ÛJ1Ùõóz]k{Û}ß©Ã)me×úõ&/l“˜cBá²×a“8lœò7(Ï‘ØS ¼ŠA¹íåI…L@3·vï, yÆÆ àcF–‰-ÎJu—hó<¦BŠFzÀ?tãúguR‹u#
‡{~?Ú•£=n¾qo~öôüô¸¾³$õüÑ»jò]Mä¦
>ÎÈ[¢à–?) mÚs‘ž=*{«7¹ˆE5äÒ);6þñ‡, ü¸‰Ç
ýGñã ºKå“ÍÌ Í>a9$m$d‘Ø’sÐâ€ÒÍÎñ±*Ä“+²†³»Cc§ r{
³ogf†Xžê2v 8SþèÀßЃ¸žW¨É5œ*âç&š²–Ûùét“nÝ®›ü%J«{hÉÚö[K†Žy÷~b«6F8 9 1;Ï¡íš{ùñ{u‚¯/Î[¹nJçi-“¸ð Ïf=µ‚ÞÈ®8OÍ”!c H%N@<ŽqÈlu"š…xHm®ä<*ó7•…Á
Á#‡|‘Ó¦õq“êífÛüŸ•oNÚ{ËFý;– ŠÙ–!½Òq–‹væRqŒ®?„ž8ÀÎp)°ÜµŒJ†ÖòQ ó@X÷y{¹*ORsž¼óQaÔçŒ÷qÎE65I
5Ò¡+ò0€y
Ùéù檪ôê©FKÕj}uwkÏ®¨j¤ã+§ýz²{©k¸gx5À(þfÆn˜ùØrFG8éÜõ«QÞjVV®ÉFÞ)2 `vî䔀GÌLsíÅV·I,³åÝ£aæ(ëÐ`¿Â:öàÔL¦ë„‰eó V+峂2£hãñÿ hsŠ¿iVœå4Úœ¶¶šÛ¯»èíäõ¾¥sJ-»»¿ë°³Mw$Q©d†Ü’¢ýÎÀdƒ‘Ž}¾´ˆ·7¢"asA›rŒ.v@ ÞÇj”Y´%Š–·–5\ܲõåË2Hã×°*¾d_(˜»#'<ŒîØ1œuþ!ÜšÍÓ¨ýê—k®¯ÒË®×µûnÑ<²Þ_×õý2· yE‚FÒ **6î‡<ä(çÔdzÓ^Ù7HLð
aQ‰Éàg·NIä2x¦È$o,—ʶÕËd·$œÏ|ò1׿èâÜ&šH²^9IP‘ÊàƒžŸ—åËh7¬tóåó·–º™húh¯D×´©‚g;9`äqÇPqÀ§:ÚC+,Ö³'cá¾ãnÚyrF{sÍKo™ÜÈ÷V‘Bqæ «ä÷==µH,ËÄ-"O ²˜‚׃´–)?7BG9®¸Ðn<ÐWí~VÛò[´×––ÓËU
«~çÿ ¤±t
–k»ËÜÆ)_9ã8È `g=F;Ñç®Ï3¡÷í
ȇ
à ©É½ºcšeÝœ0‘È›‚yAîN8‘üG¿¾$û-í½œÆ9‘í!ˆ9F9çxëøž*o_žIÆÖZò¥ÓºVùöõ¿w¦Ýˆæ•´ÓYÄ®³ËV£êƒæõç?áNòîn.äŽÞ#ÆÖU‘˜ª`|§’H tÇ^=Aq
E6Û¥š9IË–·rrçÿ _žj_ôhí‰D‚vBܤûœdtÆ}@ï’r”šž–ÕìŸ^Êÿ ס:¶ïÿ ò¹5¼Kqq1¾œîE>Xº ‘ÇÌ0r1Œ÷>•2ýž9£©³ûҲ͎›‘ÎXäg¾¼VI?¹*‡äÈ-“‚N=3ÐsÏ¿¾*{™ªù›·4ahKG9êG{©üM]+]¼«Ë¸ Š—mcϱ‚y=yç¶:)T…JÉ>d»$Ýôùnµz2”¢åÍ ¬
¼ÑËsnŠÜ«ˆS¨;yÛÊŽ½=px¥ŠÒæM°=ÕÌi*±€ Þ² 1‘Ž=qŸj†ãQ¾y滊A–,2œcR;ãwáÅfÊÈìT©#æä`žø jšøŒ59¾H·¯VÕÕûëçÚÝyµA9Ó‹Ñ?Çúþºš—QÇ
ÔvòßNqù«¼!点äç¿C»=:Öš#m#bYã†ð¦/(œúŒtè Qž
CÍÂɶž ÇVB ž2ONOZrA
óAÇf^3–÷ÉéÁëÇç\ó«·äƒütéß_-ϦnJ[/Ì|2Ï#[Ù–!’,Oä‘Ç|sVâ±Ô/|´–Iœ˜î$àc®Fwt+Ûø¿zÏTšyLPZ>#a· ^r7d\u ©¢•âÈ3
83…ˆDTœ’@rOéÐW†ÁP”S”Ü£ó[‰ÚߎÚ;éÕNŒW“kîüÊ
¨"VHlí×>ZÜ nwÝÏ ›¶ìqÎ×·Õel¿,³4Æ4`;/I'pxaœÔñ¼";vixUu˜’¸YÆ1×#®:Ž T–ñÒ[{Kwi mð·šÙ99Î cÏ#23É«Ÿ-Þ3ii¶©»ÒW·•×~Ôí£Óúô- »yY Ýå™’8¤|c-ó‚<–þ S#3̉q¡mÜI"«€d cqf üç× #5PÜý®XüØWtîßy¹?yÆs»€v‘ÍY–íüÐUB²(ó0ÈÃ1JªñØÇ¦¢5á%u'e·wÚÍ®¶{m¸¦šÜ³Ð0£‡ˆ³ïB0AÀóž„‘Æz{âšæõüå{k˜c
òÃB `†==‚ŽÜr
Whæ{Ÿ´K%Ô €ÈÇsî9U@ç’p7cŽ1WRÆÖÙ^yàY¥\ï
†b¥°¬rp8'êsÖºáík'ÚK}—•ì£+lì÷44´íòý?«Ö÷0¤I"Ú³.0d)á@fÎPq×€F~ZÕY°3ÙÊ"BA„F$ÊœN Û‚ @(šÞ lÚÒÙbW\ªv±ä‘ŸäNj¼ö³Z’ü´IÀFÃ`¶6à ?!
NxÇÒ©Ò†Oª²½’·ŸM¶{êºjÚqŒ©®èþ
‰ ’&yL%?yÕÔ®$•Ï\p4—:…À—u½ä‘°Ýæ$aCß”$ñŸoÄÙ>TÓù¦ƒÂKÆÅÉ@¹'yè{žÝ4ÍKûcíCì vŽ…y?]Ol©Ê|Íê¾Þ_;üÿ Ï¡Rçånÿ rÔ’[m²»˜¡Ž4ùDŽ›Ë) $’XxËëšY8¹i•†Á!‘þpJ•V^0
Œ±õèi²Å²en%·„†8eeù²Yˆ,S†=?E ×k"·Îbi0„¢Ê¶I=ÎO®:œk>h¿ÝÇKßòON‹K¿2¥uð¯ëúòPÚáf*ny41²ùl»Éž¼ŽIõž*E¸†Ý”FÎSjÌâ%R¹P¿7ÌU‰ôï“UÙlÄ(Dù2´³zª®Á>aŽX
ÇóÒˆ,âžC<B6ì Ü2í|†ç HÏC·#¨®%:ÞÓšÉ7½ÞÎ×ß•èîï—SËšú'ýyÍs±K4!Ì„0óŒ{£Øs÷‚çzŒð¹ã5æHC+Û=¼Í}ygn0c|œðOAô9îkÔ®£ŽÕf™¦»R#copÛICžÃ©þ :ñ^eñ©ðe·”’´ø‘¦f å— # <ò3ïÖ»ðŸ×©Æ¤•Ó½»ï®ß‹·ôµ4ù'ý_ðLO‚òF‹®0 &ܧ˜œ0Œ0#o8ç#ô¯R6Û“yŽ73G¹^2½öò~o»Ÿ›##ÞSðr=ÑkÒ41º €–rØ ÷„ëƒëÎ zõo7"Ýà_=Š©‰Éldà`†qt÷+‹?æxù©%m,ö{.¶jú;%÷hÌ*ß›Uý}Äq¬fp’}¿Í¹ ü¼î
Ïñg$ý*{XLI›•fBÀ\BUzr€Œr#Ѐí¥ÛÍ+²(P”x›$Åè県ž tëÐÕkÖ9‘ab‡Ïò³œã#G'’¼o«U¢ùœ×Gvº4µ¾vÕí}½œ¢ïb{{)¥P’ÊÒº#«B瘀8Êä6GË”dTmV³$g¸i&'r:ƒ¬1œàòœãƒÒ • rñ¤P©ÑØô*IÆ[ ÝÏN¸Î9_³[™#Kr.Fí¤í*IÁ?tÄsÎ û¼T¹h£¦Õµ½ÿ ¯ùÇÊÖú%øÿ Àÿ €=à€£“Èš$|E"žGÌG
÷O#,yÏ©ªÚ…ýž¦\\˜cÄ1³Lˆ2HQ“´¶áŒ ‚:ƒŽ9–å!Š–Í‚É¾F''‘÷yÇNüûãëpÆ|=~¢D•䵕vn2„sÓžGLë
IUP´Uíw®Ú-/mm£²×Ì–ìíeý]? øÑüa¨ÞZÏeki,q‰c10PTpAÜÀg%zSß°2Ĥ¡U]®ØŠÜçžI;€èpx?_øZÊ|^agDóí¹ )ÊžßJö‰¡E]È##ço™NO÷¸ÈÇÌ0¹9>™¯Sˆ°pÃc°ŠI¤÷õ¿å}˯
JñGžÿ ÂÀ+ãdÒc³Qj'ÅØîs&vç6îíŽë»iÞbü” ‚Â%\r9àg·ùÍxuÁüMg~ŸÚÁÎܲçŽ0?*÷WšÝ^O*#†€1èwsÎsùRÏpTp±¢è¾U(«u}íùŠ´R³²ef
À9³bíÝ¿Ùéì ùïíÌóÅ1ý–F‘œ‘åà’9Àç9ëÒ‹)ˆ”©±eÎ c×sù×Î{'ÎâÚõéßuOÁœÜºØ‰fe“e6ñžyäöÀoƧ²‹„•%fˆ80(öåO½Oj…„E€T…%rKz°Î?.;{šXÙ‡ŸeUÚd!üx9þtã%wO_øoòcM-
j–ÒHX_iK#*) ž@Ž{ôǽBd¹‰RÝn–ê0«7ˆìyÀ÷Í@¬Ì¢³³’ 9é÷½?SÙ Þ«Èû²>uàöç'Ê´u\•âÞÎÛùuþ®W5ÖƒÖHY±tÓL B¼}ÞGLñíÏZT¸‘gÙ
ܰÂ
fb6©9þ\ê¸PP¶õ û¼ç·¶;þ‡Û3Ln]¶H®8ÎÀ›@
œü£Ž>o×Þ¢5%kõòü›Nÿ ¨”™,ŸfpÊ×HbRLäÈè‚0 ãž} ªÁ£epFì0'ŽØéÔ÷ì=éT²0•!…Îzt9ç¾?”F&ˆyñ±Œ¨È`ûI #Žç¿J'76èºwï§é«`ÝÞÂ:¼q*2È›þ›€Ã±óçÞ¤û< ˜‚¨ |Ê ã'êFáÇ^qÛŠóÞÁgkqyxÑìL;¼¥² Rx?‡¯Y7PŽwnù¶†û¾Ü·.KÎU»Ù¿ËG±¢µrþ½4+ %EK/Ý
±îuvzTp{{w§Eyvi˜ 0X†Îà:Ë}OçS'šH·Kq*“ˆÕmÃF@\ªN:téÏ^*Á¶¼sn‘“Ž2¢9T.½„\ýò@>˜7NFïNRÓ·wèôßEÕua'¬[þ¾cö¡ÌOæ¦âÅŠ². Ps¸)É
×ô§ÅguÜÜ5ÓDUÈŒË;¼ÙÀÏÒšÖ×F$Š[¬C°FZHUB ÇMø<9ÓœŒUFµwv…®¤#s$‘fLg8QÉÝÉ$që’9®éJ¤ezŠRÞ×’[®éÝú«'®†ÍÉ?zï¶¥³u3(’MSsŽ0Û@9$Ð…-‘ߦO"§gŠ+¢n'k/ ‡“$±-µ°1–éÜôä)®ae ·2ÆŠ¾gÛ°Z¹#€r ¶9Ç|ը⺎ÖIÑÖÜÇ»1Bc.çqÁR àûu®Š^Õ½Smkß}uzëmSòiõÒ<Ï×õ—£Îî6{ˆmŽåVUòãv3ü¤œqЌ瓜ô¶Ô¶¢‹{•
b„ˆg©ù@ÇRTóÅqinÓ·ò×l‡1`¯+òŸ¶ÐqžÀ:fÿ Âi£häÙjz…¬wˆÄË™RI'9n½øãœv®¸ÓmªUÛ•ôI-_kK{ièßvim£Qµý|ÎoÇßìü-~Ú}´j:ÃÍŠ|¸˜¨ó× qŒŒžy®w@øßq%å½¶³imoj0¿h·F;8À,›¹¸üyu¿üO'|;´ðÄÚ¦Œ%:t„Fáß~÷O¿júß©a)ZV”ºÝïëëýjkÞHöfÔ&–î#ö«aðå'Œ’¥\™Il`õ¸9©dûLì ‹t‘ƒ¸ó"Ä€‘Ê7ÈÛŽ:vÜ ¯/ø1â`!»Ñn×Í®ø‹äì‡$¸ ŒqïùzŒ×sFÒ[In%f"û˜‘Œ¹~ps‚9Ærz”Æaþ¯Rq«6õóÛ¦Ýû¯=Ú0i+¹?ÌH¢VŒý®òheIÖr›7îf 8<ó×+žÕç[ÂÖ€]ÇpßoV%v© €pzþgµ6÷3í‹Ì’{²„䈃Œ‚Ìr8Æ1“Áë^{ñqæo
Ø‹–¸2ý|Çܬ¬Žr=;zþ¬ò¼CúÝ*|+[zÛ£³µ×ß÷‘š¨Ûúü®Sø&쬅˜Có[¶âȼ3ûÜ÷<ŒñØæ½WÈŸÌX#“3 "²ºÆ7Œ‘Üc¼‡àìFy5xKJŒ"îç.r@ï×Þ½Ä-ÿ þ“}ª}’*Þ!,Fm¸Î@†9b?1W{Yæ3„`Ú¼VõŠÚÛ_kùöG.mhÎñ ôíhí§Ô$.ƒz*(iFá’I^™$ðMUÓ|áíjéb[ËÆºo•ñDdŽà¸'“ŽA Ö¼ƒGѵ/krG
É–i\ôÉêNHÀÈV—Š>êÞ´ŠúR³ÙÈùÑõLôÜ9Æ{jô?°°Kýš¥WíZ¿V—m6·E}{X~Æ?
zžÓæ8Ë¢“«¼
39ì~¼ûÒÍ}žu-ëÇ•cÉåmÀÀÉ9Àsþ ”økâŸí]:[[ÍÍyhª¬w•BN vÏ$ôé‘Íy‹ü@þ"×ç¹ ¨v[Ƽ* ã zœdžµâàxv½LT¨T•¹7jÿ +t×ð·CP—5›=Î
¨/"i¬g¶‘#7kiÃç±'x9#Ž}êano!òKD‘ílï”('¿SÔð?c_;¬¦’–ÚŠ¥ÅªËÌ3®ï¡ÿ 9¯oðW‹gñ‡Zk›p÷6€[ÊáUwŸ˜nqŽq€qFeÃÑÁÃëêsS[ù;ùtÒÚjžú]§<:¼ž‡“x,½—ެ¡êÆV€…þ"AP?ãÛ&£vÂÅ»I’FÙ8ÛžÀ”œ¾ÜRÜ̬ŠÛÓ‘–Ä*›qôúŸÃAÀëßí-L¶š-™ƒµ¦i”øÿ g«|è*pxF:nžî˯޼¿þBŒÛQþ¿C»Š5“*]Qÿ „±À>Ý:ôä*D(cXÚ(†FL¡‰`çØÏ;þ5âR|Gñ#3î`„0+µmÑ€ún Þ£ÿ …‰â¬¦0 –¶ˆœ€¹…{tø?ʯ(_çþ_Š5XY[¡Ù|Q¿ú
µŠ2︛sO* Бÿ ×â°<+à›MkÂ÷š…ij
·Ü–ˆ«ò‚?ˆœúäc½øåunû]¹Iïåè› ç ¯[ð&©¥Ýxn;6>}²’'`IË0ÁèN}zö5éâ©âr\¢0¥ñs^Ml¿«%®ýM$¥F•–ç‘Øj÷Ze¦£k
2¥ô"FqÀ`„~5Ùü+Ò¤—QºÕ†GÙ—Ë‹ çqä°=¶ÏûÔÍcá¶¡/ˆ¤[ý†iK ™°"ó•Æp;`t¯MÑt}+@²¶Óí·Ídy’3mÕË‘’zc€0 íyÎq„ž ¬4×5[_]Rë{]ì¬UZ±p÷^åØÞÈ[©&OúÝÛ‚‚s÷zžIïßó btÎΪ\ya¾U;C¤t*IÎFF3Џ™c
1žYD…U° êÄàõë\oŒ¼a ‡c[[GŽãP‘7 â znÈ>Ãü3ñ˜,=lUENŒäô¾ÚÀÓ[_ð9 œ´JçMy©E¢Àí}x,bpAó¦üdcûŒW9?Å[Há$¿¹pÄ™#^9O88©zO=«Ë!µÖüY¨³ªÍy9ûÒ1 úôÚ»M?àô÷«ÞëÖ–ÙMÌ#C&ßnJ“Üp#Ђ~²†G–àíekϵío»_žŸuΨQ„t“ÔÛ²øáû›´W6»Øoy FQÎr $Óõìk¬„‹ïÞÚ¼sÆíòÉ67\míÎyF¯ð¯TÓã’K;ë[ð·ld«7üyíšÉ𯊵 êáeYžÏq[«&vMÀðßFà}p3ÅgW‡°8ØßVín›þšõ³¹/ ü,÷ií|’‘´R,®ŠÉ‡W“Ž1ØöëÓ¾xžÖÞ¹xÞݬXZGù\’vŒž˜ÆsØúÓïí&ÒÒ{]Qž9£Ê¡ù·ÄÀ»¶áHäž™5—ìö« -&ù¤U<±ÉÆA>½ý+æg
jžö륢þNÛ=÷JÖÛfdÔ õýËúû‹ÓØB²¬fInZ8wÌÉЮ~aƒÎ=3ìx‚+/¶äÁlŠ‚?™Æü#8-œ\pqTZXtè%»»&ÚÝ#´ŠðÜžã§Í’¼{p·ß{m>ÞycP¨’¼¢0ú(Rƒë^Ž ñó¼(»y%m´ÕÙ}ÊûékB1¨þÑ®,#Q)ó‡o1T©ÜÃ*Ž‹‚yö<b‰4×H€“ìÐ.
¤²9ÌŠ>„Žãøgšñ
¯Š~)¸ßå\ÛÛoBŒa·L²œg$‚Iã¯ZÈ—Æ~%”äë—È8â)Œcƒ‘Âàu9¯b%)ÞS²¿Ïïÿ 4Öºù}Z/[H%¤vÉ#Ì’x§†b
© ³´tÜ{gn=iï%õªÇç]ܧ—!åw„SÓp ·VÈÏ¡?5Âcâb¥_ĤŠz¬—nàþÖΟñKÄöJé=ÌWèêT‹¸÷qÎჟ•q’zWUN«N/ØO^Ÿe|í¾©k{üõ4öV^ïù~G¹êzÂèº|·÷×[’Þ31†rpjg·n
Æ0Ý}kåË‹‰nîe¹ËÍ+™ÏVbrOç]'‰¼o®xÎh`¹Ç*±ÙÚ!T$d/$žN>¼WqᯅZ9ÑÒO\ÜÛê1o&,-z ~^NCgNÕéá)ÒÊ©7‰¨¯'Õþ¯þ_¿Ehîþóâ €ï¬uÛûý*ÎK9ä.â-öv<²‘×h$àãúW%ö¯~«g-ÕõÀàG~>Zú¾Iš+(šM³ Û#9äl%ðc¬ ûÝ xÖKG´x®|¸¤Ï™O:Ê8Ã’qÉcÔä‚yÇNJyËŒTj¥&µOmztjÿ ?KëaµÔù¯áýóXøãLeb¾tžAÇû`¨êGBAõ¾•:g˜’ù·,þhÀ`¬qÜ` e·~+å[±ý“âYÄjWì—µHé±ø?Nõô>½âX<5 Ç©ÏѼM¶8cܪXŽÉ^r?¼IróÈS•ZmÇ›™5»òÚÚ7ïu«&|·÷•Ά
>[©ÞXHeS$Œyà€ ÷ù²:ò2|óãDf? Z¼PD¶ÓßC(xÆ0|©ßR;ôMsÿ µ´ÔVi¬,͹›Ìxâi˜`¹,GAéÇlV§ÄýF×Yø§ê–‘:Ã=ò2³9n±ÉžØÏ@yÎWžæ±Ãàe„ÄÒN ]ïòêìú_Go'¦ŽÑ’_×õЯðR66þ!›ÑÄ gFMÙ— äžäqôÈ;ÿ eX<#%»Aö‰ãR¤ Í”Ž¹È G&¹Ÿƒ&á?¶Zˆ±keRè Kãnz·ãŠÕøÄÒÂ9j%@®×q±ÜŒý[õ-É$uíè&¤¶9zÇï·Oøï®ÄJKšÖìdü"µˆ[jײÎc;ã…B(g<9nàȯG½µŸPÓ.´Éfâ¼FŽP
31 ‘ÏR}<3šä~
Ã2xVöî Dr
Ç\›}Ý#S÷ÈÀëŽHÆI®à\OçKuäI¹†ó(”—GWî ñ³¹¸æ2¨›‹ºÚû%¾ýÖ_3ºNú¯ëúì|ÕÅÖ‰}ylM’ZËîTÿ á[ðÐñ/ˆ9Àû
¸ón3 Mòd‘÷ döª^.Êñް›BâîNp>cëÏçÍzïÃôÏ
YÍ%ª¬·ãÏ-*9ÜÂãhéŒc¾dÈêú¼Ë,. VŠ÷çeÿ n/¡¼äãõâ=‹xGQKx”|¹bÌŠD@2Œ 8'Ž àúƒŽ+áDÒ&¡¨"Œ§–Žr22 Ç·s]ŸÄ‹«ð%ÚÄ<¹ä’(×{e›HÀqÁç©Ç½`üŽÚõK饚9ƒÄ±€<–úƒú~ çðñO#Í%iKKlµ¦¾F)'Iê¬Î+Ç(`ñ¾£œdÈ’`™ºcßéé^ÿ i¸”Û\ý¡æhÔB«aq¸}ãÀÆ:ÜWƒ|FÛÿ BŒÇÀeaŸ-sÊ€:úW½ÜÝÜ<%$µ†%CóDªÀí%IÈÏʤ…ôäñÞŒ÷‘a0“ôŽÚë¤nŸoW÷0«e¶y'Å»aΗ2r’# Û°A^ý9ÉQÔõ=ù5¬£Öü.(Þ’M$~V«=éSÄFN½®©ÔWô»ÿ þHžkR‹ìÏ+µµžöê;khÚI¤m¨‹Ôš–âÖçJ¾_Z•’6a”Èô> ÕÉaÕ<%®£2n bQŠå\tÈõUÿ ø»þ‹k15‚ÃuCL$ݹp P1=Oøýs¯^u éEJ”–éêŸê½5ýzy›jÛ³á›Ûkÿ ÚOcn±ÛÏîW;boºz{ãžüVÆ¡a£a5½äÎÂks¸J@?1è¿{$ä‘=k”øsÖ^nŒ¦)ÝåXÃíùN1ØõÚOJë–xF÷h¸ Œ"Ž?x䜚ü³ì¨c*Fœ¯i;7~ñí׫Ðó¥Ë»3Ãü púw ‰°<Á%»ñž ÿ P+Û^ ¾Ye£ŽCÄŒ„/>˜>•á¶Ìm~&&À>M[hÈÈÿ [Ž•íd…RO@3^Ç(ʽ*¶ÖQZyßþ
1Vº}Ñç?¼O4Rh6R€ª£í¡ûÙ
a‚3ß·Õ
ü=mRÍ/µ9¤‚0ÑC¼Iè:cŽsÛ¾™x£ÆÐ¬ªÍöˢ샒W$•€Å{¨ÀPG
ÀÀàŸZìÍ1RÉ0´ðxEË9+Éÿ ^rEÕ—±Š„70l¼áË@û.' ¼¹Žz€N3úUÉ<3á×*?²¬‚ä†"Ùc=p íÛ'¡ª1ñ"økJ†HÒ'»Ÿ+
oÏN¬Ã9 dÙãÜדÏâÍ~æc+j·Jzâ7(£ðW]•æ™?nê´º6åwéåç÷N•ZŠíž›¬|?Ðõ?Ñ-E…®³ÇV$~X¯/…õ x‘LˆÑÜÚÈ7¦pzãÜüë½ðÄ^õtÝYËÍ7ÉÖÕ8ÏUe# #€r=sU¾/é’E§jRC4mxNÝ´9†íuá»›V‘
ZI€×cr1Ÿpzsøf»¨åV‹ìû`qËLÊIã?\~¼³áËC©êhªOîO»‘ÃmçÛçút×¢x“Z}?Üê#b-¤X7õÄò gž zzbº3œm*qvs·M=íúéw}¿&Úª°^Ö×µÏ(ø‡â†Öµƒenñý†×åQáYûœ÷ÇLœôÎNk¡ð‡¼/µ¸n0æÉ0¬ƒ‚üîÉÆvŒw®Sáö”š¯‹-üÕVŠØÙ[$`(9cqƒÔ_@BëqûÙ`Ýæ0;79È?w<ó |ÙÜkßÌ1±Ëã¿ìÒ»ðlìï«ÓnªèèrP´NÏš&ŽéöÙ¸÷æ°~-_O'‰`°!RÚÚÝ%]Ø%þbß1'¿ÿ XÕáOöÎŒ·‹¬+Åæ*ÛÛ™0¤ƒOÍÔ`u¯¦ÂaèÐÃÓ«‹¨Ô¥µœ¿¯ÉyÅÙ.oÔôŸ Úx&(STðݽ¦õ] ’ÒNóÁäÈùr3í·žÚ[™ƒ¼veÈ÷ÞIõÎGlqÎ=M|«gsªxÅI6
]Z·Îªä,¨zŒŽÄ~#ØŠúFñiÉqc©éÐD>S딑 GñŽ1éÐ^+
Ëi;Ô„µVÕú»i¯ÈÒ-ZÍ]òܘ®ì`bÛÙ¥_/y(@÷qÐúg Ô÷W0.Ø›
6Ò© r>QƒŒ0+Èîzb¨É+I0TbNñ"$~)ÕÒ6Þ‹{0VÆ27œWWñcÄcX×íôûyKZéðªc'iQ¿¯LaWŠŸS\·Š“źʸ…ôÙÂí|öÀÇåV|!¤ÂGâÛ[[’ï
3OrÙËPY¹=Î1õ5öåTžÑè Ú64/üö?Zëžk}¬¶éàoá¾á}3“ü]8Éæ¿´n²Žš_6¾pœ)2?úWÓÚ¥¾¨iWúdŽq{*ª1rXŒd…m»‰äcô¯–dâ•ã‘Jº¬§¨#¨®§,df«8ÉÅßN¾hˆ;îÓ=7áùpën®É 6ûJžO2^œÐò JÖø¥²ã›Ò6Ü·‰!wbÍ‚¬O©»õ¬ÿ ƒP=Ä:â¤-&ÙŽ
`È9 r9íϧzë> XÅ7ƒ5X–krÑ¢L7€ìw}ÑŸNHëŒüþ:2†á¼+u·á÷N/Û'Ðç~ߘô«ëh!ónRéeQ´6QÛÿ èEwëÅÒ|¸Yqó1uêyùzð8 ƒŠù¦Ò;¹ä6öi<'ü³„[ÃZhu½ ùÍ¡g‚>r¯×ŠîÌx}bñ2“k꣧oø~›hTèóËWò4|ki"xßQ˜Ï6øÀLnß‚0 ¹Æ{±–¶Öe#¨27È@^Ìß.1N¾œyç€õ†ñeé·Õã†çQ°€=Ì©ºB€Ø8<‚ÃSõ®ùcc>×Ú .Fr:žÝGæ=kÁâ,^!Fž
¬,àµ}%¶«îõ¹†"r²ƒGœüYÕd?aÑÃY®49PyU ÷þ!žxÅm|/‚ãNð˜¼PcûTÒ,¹/Ý=FkÏ|u¨¶«âë…{¤m¢]Û¾ïP>®XãÞ½iÓÁ¾
‰'¬–6ß¼(„ï— í!úÙäzôë^–:œ¨å|,_¿&š×]uÓѵÛô4’j”bž§x‘Æ©ã›á,‚[Ô
ÎÞ= ŒËæ ÀùYÁ?ŽïÚ¼?ÁªxºÕÛ,°1¸‘¿ÝäãØ¯v…@¤åq½ºã œàûââ·z8Xýˆþz~—û»™âµj=Ž
â~ãáh@'h¼F#·Üp?ŸëQü-løvépx»cŸø…lxâÃûG·‰¶ø”L£©%y?¦úõÆü-Õ¶¥y`Òl7>q’2üA?•F}c‡jB:¸Jÿ +§¹¿¸Q÷°ív=VÑìu[Qml%R7a×IèTõéŽx¬
?†š7
1†îã-ˆã’L¡lŽ0OÓ=ÅuˆpÇ•¼3ÛùÒ¶W/!|’wŽw^qÔ×ÏaóM8Q¨ãÑ?ëï0IEhÄa¸X•`a
?!ÐñùQ!Rä žqŽžÝO`I0ÿ J“y|ñ!Îã@99>þ8–+éáu…!ù—ä
ʰ<÷6’I®z
ÅS„¾)Zþ_Öýµ×ËPåOwø÷þ*üïænÖùmØÝûþ¹=>¦½öî×Jh]¼ç&@§nTŒ6ITÀõ^Fxð7Å3!Ö·aÛ$þÿ ¹ã5îIo:ȪmËY[’8ÇӾlj*òû¢¥xõ¾¼ú•åk+\ð¯ HÚoŽl•Ûk,¯ ç²²cõÅ{²Z\
´ìQ åpzŽ3Ôð}ÿ Jð¯XO¡øÎé€hÙ¥ûLdŒ`““ù6Gá^ÃáÝ^Ë[Ñb¾YåŒÊ»dŽ4†2§,;ÿ CQÄ´¾°¨c–±”mºV{«ßÕýÄW\ÖŸ‘çŸ,çMRÆí“l-ƒn~ë©ÉÈê Ü?#Ž•¹ðãSÒ¥ÐWNíà½;ãž)™ÎSÈ9cóLj뵿ūiÍk¨ió¶X‚7÷ƒ€yãnyÏŽëÞ Öt`×À×V's$È9Ú:ä{wÆEk€«†Çàc—â$éÎ.éí~Ýëk}ÅAÆpörÑ¢‡Šl¡ÑüSs‹¨‰IÄóÀ×wñ&eºðf™pŒÆ9gŽTø£lñëÀçŽ NkÊUK0U’p ï^¡ãÈ¥´ø{£ÙHp`’ØåbqÏ©äó^Æ:
Ž' ÊóM«õz+ß×ó5Ÿ»('¹ð¦C„$˜Å¢_ºÈI?»^äã'ñêzž+ë€ñ-½»´}¡Ë*õ?.xÇ^1ŽMyǸ&“—L–îëöâ7…' bqéÎGé]˪â1$o²¸R8Ã`.q€}sÖ¾C98cêÆÞíïóòvÓòùœÕfÔÚéýuèÖ·Ú
Å‚_¤³ÜۺƑß”àרý:׃xPþÅÕî-/üØmnQìïGΊÙRqê=>¢½õnæ·r!—h`+’;ò3È<“Û©éšóŸx*÷V¹¸×tÈiˆßwiÔÿ |cŒñÏ®3ֽ̰‰Ë Qr©ö½®¼ÛoÑÙZÅÑ«O൯ýw8;k›ÿ x†;ˆJa;‘º9÷÷R+¡ñgŽí|Iáë{ôáo2ʲ9 029ÉÏLí\‰¿¸Ÿb˜ "Bv$£ßiê>=ªª©f
’N ëí>¡NXW~5×úíø\‰»½Ï^ø(—wÖú¥¤2íŽÞXæÁ$°eÈ888^nÝë²ñÝÔ^ ÖÚ9Q~Ëå7ï
DC¶ÑµƒsËÇè9®Wáþƒ6‡£´·°2\Ý:ÈÑ?(#¨'$õèGJ¥ñW\ÿ ‰E¶—¸™g˜ÌÀ¹;Pv ú±ÎNs·ëŸ’–"Ž/:té+ûË]öJöÓM»ëø˜*‘•^Uý—êd|‰åñMæÔÝ‹23å™6æHùÛ‚ëüñ^…ñ1¢oêûÑEØ.õ7*ÅHtÎp{g<·Á«+¸c¿¿pÓ¾Æby=8É_ÄsÆk¬ñB\jÞÔì••Ë[9Píb‹Bヅ =93§ð§LšÛáÖšÆæXÌÞdÛP.0\ãïÛ0?™úJ¸™Ë
”•œº+=<µI£¦í¯õêt¬d‹T¬P=ËFêT>ÍØØ@Ï9<÷AQÌ×»Õ¡xùk",JÎæù±Éç$œŽŸZWH®¯"·UÌQ ’ÙÈ]ÅXg<ã
ߨg3-Üqe€0¢¨*Œ$܃
’Sû 8㎼_/e'+Ï–-èÓ¶¶Õíß[·ÙÙ½îì—¼sk%§µxä‰â-pÒeÆCrú
ôσžû=”šÅô(QW‚Õd\ƒæ. \àö¹¯F½°³½0M>‘gr÷q+œ¶NïºHO— ¤ ܥݔn·J|ÆP6Kµc=Isó}Ò çGš)a=—#vK›åoK§ßóÙ¤¶¿õú…ÄRÚ[ËsöÙ¼Ë•Ë ópw®qœŒ·Ø
ùÇâ‹ý‡ãKèS&ÞvûDAù‘É9ŒîqÅ}
$SnIV[]Ñ´Ó}ØÜ¾A Ü|½kÅþÓ|EMuR¼.I¼¶däò‚ÃkÆ}ðy¹vciUœZ…Õõ»z¾÷¿n¦*j-É/àœHã\y5 Û ß™ó0—äŸnzôã#Ô¯,†¥ÚeÔ÷ÜÅ´„“'c…<íÝ€<·SŠ¥k§Ã¢éÆÆÙna‚8–=«Êª[Ÿ™°pNî02z“ÔÙ–K8.È’Þî(vƒ2®@ äÈûãçžxäÇf¯ˆu¹yUÕîýWšÙ|›ëÒ%Q^í[æ|éo5ZY•^{96ˆY‚§v*x>âº_|U¹Ö´©tûMÒÂ9PÇ#«£#€ éÉñ‘ƒÍz/‰´-į¹°dd,Б›p03ƒœ{ç9=+
Ûᧇ¬¦[‡‚ê婺¸#±ß=³ý¿•Õµjñ½HÙh›Û[§ÚýÊöô÷{˜?ô÷·Ô.u©–_%còcAÀ˜’
}0x9Î>žñÇáÍ9,ahï¦Ì2òÓ ñÛAäry$V²Nð
]=$Ž
‚#Ù‚1ƒƒødõMax‡ÂÖ^!±KkÛ‘
«“Çó²FN8+ëÎ{Ò¼oí§[«ÕMRoËeç×[_m/¦¦k.kôgŽxsSÓ´ý`êzªÜÜKo‰cPC9ÎY‰#§^üý9¹âïÞx£Ë·Ú`±‰‹¤;³–=ÏaôÕAð‚÷kêÁNBéÎælcõö®£Fð†ô2Ò¬]ßÂK$ÓÜ®•”/ÊHàã$ä¸÷ëf¹Oµúâ“”’²øè´µþöjçNü÷üÌ¿ xNïFÒd»¼·h®îT9ŽAµÖ>qÁçÔœtïÒ»\ȶÎîcÞäîó3¶@#ÉIÎ ÔñW.<´’¥–ÑÑ€ÕšA‚ ;†qÓë‚2q
ÒÂó$# Çí‡
!Ë}Õ9ÈÎÑÉã=;ŒÇÎuñ+ÉûÏ¥öíeÙ+$úíÜ娯'+êZH4ƒq¶FV‹gïŒ208ÆÌ)íб>M|÷âÍã¾"iì‹¥£Jd´™OÝç;sÈúr+ÜäˆË)DŒ¥šF°*3Õ”d{zÔwºQ¿·UžÉf†~>I+ŒqÔ`ð3œ“Ü×f]œTÁÔn4“ƒø’Ýßõ_«*5šzGCÊ,þ+ê1ò÷O¶¸cœºb2yÇ;cùÕ£ñh¬›áÑŠr¤ÝäNBk¥—á—†gxšX/쑘hŸ*Tçn =ûã¦2|(ð¿e·ºÖ$
ýìŸ!'åΰyîî+×öœ=Y:²¦ÓÞ×iü’—ü
-BK™£˜›âÆ¡&véðõ-ûÉY¹=Onj¹ø¯¯yf4·±T Pó`çœ7={×mÃ/¢˜ZÚòK…G½¥b„’G AãÜœ*í¯Ã¿ IoæI¦NU8‘RwÈã;·€ Û×ëÒ”1Y
•£E»ÿ Oyto¢<£Áö·šï,䉧ûA¼sû»Nò}¹üE{ÜÖªò1’õÞr0â}ÎØ#>à/8ïéÎ~—áÍ#ñÎlí§³2f'h”?C÷YËdð:qëõÓ·‚ïeÄ©
ÔÈØÜRL+žAÎ3¼g=åšó³Œt3
ÑQ¦ùRÙßE®¼±w_;þhš’Sirÿ ^ˆã¼iੇ|RòO„m°J/“$·l“ ÇÓ¿ÿ [ÑŠÆ“„†Õø>cFÆ6Ø1ƒ– àz7Ldòxäüwá‹ÝAXùO•Úý’é®ähm •NÀ±ÌTÈç
ƒ‘I$pGž:‚ÄbêW¢®œ´|¦nÍ>¶ÖÏ¢§ÎÜ¢ºö¹•%ÄqL^öÛKpNA<ã¡ …î==ª¸óffËF‡yÌcÉ ©ç$ð=ñÏYþÊ’Ú]—¥‚¬‚eDïÎH>Ÿ_ÌTP™a‰ch['çÆÜò7a‡?w°Ïn§âÎ5”’¨¹uÚÛ|´ÓÓc§{O—ü1•ªxsÃZ…ÊÏy¡Ã3¸Ë2Èé» ‘ƒÎ äžÜðA§cáOéúÛ4ý5-fŒï„ù¬ûô.Ç Üsž•Ò¾•wo<¶Ÿ"¬¡º|£
î2sÇ¡éE²ÉFѱrU°dÜ6œ¨ mc†Îxë׺Þ'0²¡Rr„{j¾í·è›µ÷)º·å–‹î2|I®Y¼ºÍË·–ÃÆàã£'óÆxƒOÆÞ&>\lóÌxP Xc¸ì Sþ5§qà/ê>#žÞW¸if$\3 ® ûÄ“ùŽÕê¾ð<Ó‹H¶óÏ" å·( á‘€:ã†8Ï=+ꨬUA×ÃËÚT’ÑÞöù¥¢]{»ms¥F0\ÑÕ—ô}&ÛB´ƒOŽÚ+›xíÄÀ1
,v± žIëíZ0ǧ™3í2®0ทp9öÝÔž)ÓZËoq/Ú“‘L ²ŒmùŽï‘Ó9§[Û#Ä‘\ÞB¬Çs [;à à«g‚2ôòªœÝV§»·¯/[uó½õÛï¾
/šÍ}öüÿ «=x»HŸÂÞ.™ ÌQùŸh´‘#a$‚'¡u<Š›Æ>2>+ƒLSiöwµFó1!eg`£åœ ÷ëÛö}Á¿ÛVÙêv $¬ƒ|,s÷z€ð΃¨x÷ÅD\ÜŒÞmåÔ„ ˆ o| :{ÇÓ¶–òÁn!´0Ål€, ƒ ( ÛŒŒc¶rsšæ,4‹MÛOH!@¢ ÇŽ„`å²9ÝÃw;AÍt0®¤¡…¯ØÄ.Àìí´ƒ‘ßñ5Í,Óëu-ÈÔc¢KÃÓ£òÖ̺U.õL¯0…%2È—"~x
‚[`có±nHàŽyàö™¥keˆìŒÛFç{(Ø©†`Jã#Žwg<“:ÚÉ;M
^\yhûX‡vB·÷zrF?§BÊÔ/s<ÐÈB)Û± ·ÍÔwç5Âã:så§e{mѤï«Òíh—]Wm4âí¿ùþW4bC3¶ª¾Ùr$pw`àädzt!yŠI„hÂîàM)!edŒm'æ>Ç?wzºKìcŒ´¯Ìq6fp$)ãw¡éUl`µ»ARAˆÝÕgr:äŒgƒéé[Ôö±”iYs5Ýï«ÙG—K=þF’æMG«óÿ `ŠKɦuOQ!ÕåŒ/ÎGÞ`@ËqÕzdõâ«Ê/Ö(ƒK´%ŽbMüåÜŸö—>¤óŒŒV‘°„I¢Yž#™¥ùÏÊ@8
œgqöö5ª4vד[¬(q cò¨À!FGaÁõõ¯?§†¥ÏU½í¿WªZ$úyú½Žz×§Éþ?>Ã×È•6°{™™ŽÙ.$`ÎUœ…çè ' ¤r$1Ø(y7 ðV<ž:È ÁÎMw¾Â'Øb§øxb7gãО½óÉÊë²,i„Fȹ£§8ãä½k¹¥¦ê/ç{ïê驪2œ/«ü?¯Ô›ìñÜ$þeýœRIåŒg9Ác’zrrNO bÚi¢
ѺË/$,“ª¯Ýä;Œ× ´<ÛÑn³IvŸb™¥ nm–ÄŸ—nÝÀãŽ3ëÍG,.öó³˜Ù£¹uÊÌrŠ[<±!@Æ:c9ÅZh
ì’M5ÄìÌ-‚¼ëÉùqŽGì9¬á ;¨A-ž—évþÖ–^ON·Ô”ŸEý}ú×PO&e[]ÒG¸˜Ûp ƒÃà/Ë·8ûÀ€1ž@¿ÚB*²¼ñì8@p™8Q“žÆH'8«I-%¸‚
F»“åó6°Uù|¶Ú¸ã ò^Äw¥ŠÖK–1ÜÝK,Žddlí²0PÀü“×ükG…¯U«·¶–´w¶ŽÍ¾©yÞú[Zös•¯Á[™6°
¨¼ÉVæq·,#
ìãï‘×8îry®A››¨,ãc66»Ë´ã'æÉù?t}¢æH--Òá"›|ˆ¬[í 7¶ö#¸9«––‹$,+Ëqœ\Êøc€yê^ݸÄa°«™B-9%«×®‹V´w~vÜTéꢷþ¼ˆ%·¹• ’[xç•÷2gØS?6åÀÚ õ9É#š@÷bT¸º²C*3Bá¤òÎA9 =úU§Ó"2Ãlá0iÝIc‚2Î@%öç94ùô»'»HÄ¥Ô¾@à Tp£šíx:úÊ:5eºßMý×wµ›Ó_+šº3Ýyvÿ "ºÇ<ÂI>Õ1G·Ë«È«É# àÈÇ øp Jv·šæDûE¿›†Ë’NFr2qŸ½ÇAÜšu•´éí#Ħ8£2”Ú2Ã/€[ÎTr;qŠz*ý’Îþ(≠;¡TÆâ›;ºÿ àçœk‘Þ8¾Uª¾íé{^×IZéwÓkXÉûÑZo¯_øo×È¡¬ â–ÞR§2„‚Àœü½ùç® SVa†Âüª¼±D‘ŒísŸàä|ä2 æ[‹z”¯s{wn„ÆmáóCO+†GO8Ïeçåº`¯^¼ðG5f{Xžä,k‰<á y™¥voÆ éÛõëI=œ1‹éíÔÀÑ)R#;AÂncäŽ:tÏ#¶TkB.0Œ-ÖÞZÛgumß}fÎJÉ+#2êÔP£žùÈÅi¢%œ3P*Yƒò‚A쓎2r:ƒÐúñiRUQq‰H9!”={~¼“JŽV¥»×²m.ÛߺiYl¾òk˜gL³·rT•
’…wHÁ6ä`–Î3ùÌ4Øe³†&òL‘•%clyîAÂäà0 žüç$[3uŘpNOÀÉ=† cï{rYK
ååä~FÁ
•a»"Lär1Ó¯2Äõæ<™C•.fÕ»è¥~½-¿g½Â4¡{[ør¨¶·Žõäx¥’l®qpwÇ»8ärF \cޏܯÓ-g‚yciÏÀ¾rÎwèØÈ#o°Á9ã5¢šfÔxÞæfGusÏÌJÿ µ×œ/LtãÅT7²¶w,l
ɳ;”eúà·¨çîŒsÜgTÃS¦^ '~‹®›¯+k÷ZÖd©Æ*Ó[Ü«%Œk0ŽXƒ”$k#Ȩ P2bv‘ƒŸáÇ™ÆÕb)m$É*8óLE‘8'–ÜN Úyàúô+{uº±I'wvš4fÜr íì½=úuú
sFlìV$‘ö†HÑù€$§ õ=½¸«Ž]
:Ž+•¦ïmRþ½l´îÊT#nkiøÿ _ðÆT¶7Ò½ºÒ£Î¸d\ã8=yãŽÜäR{x]ZâÚé#¸r²#»ÎHÆ6õ ç® ÎFkr;sºÄ.&;só±Ç9êH÷ýSšÕtÐU¢-n Ì| vqœ„{gŒt§S.P‹’މ_[;m¥ÞZýRûÂX{+¥úü¼ú•-àÓ7!„G"“´‹žƒnrYXã¸îp éœ!ÓoPÌtÑ (‰Þ¹é€sÓ#GLçÕšÑnJý¡!‘Tä#“ß?îýp}xÇ‚I¥Õn#·¸–y'qó@r[ Êô÷<ÔWÃÓ¢áN¥4Ô’I&ݼ¬¬¼ÞºvéÆ
FQV~_ÒüJÖÚt¥¦Xá3BÄP^%ÈÎW-×c¡ú©¤·Iþèk¥š?–UQåIR[’O 5x\ÉhÆI¶K4«2ùªŠŒ<¼óœçØ`u«‚Í.VHä€ Ëgfx''9ÆI#±®Z8
sISºku¢ßÞ]úk»Jößl¡B.Ü»ÿ MWe
°·Ž%šêɆ¼»Âù³´œ O¿cÐÓÄh©"ÛÜÏ.ÖV’3nüÄmnq[ŒòznšÖ>J¬òˆæ…qýØP Ž:ä7^0yëWšÍ_79äoaÈ °#q0{ää×mœy”R{vÒÞ¶ÚÏe¥“ÚÆÐ¥Ì®—õýjR •íç›Ìb„+JyÜØÙ•Ç]¿Ôd þËOL²”9-Œ—õÃc'æÝלçÚ²ìejP“½
âù°¨†ðqòädЃÉäÖÜj÷PÇp“ÍšŠå«‘î
<iWNsmª»¶vÓz5»ûì:Rs\Ðßôû×uÔÿÙ