GIF89a; Mini Shell

Mini Shell

Direktori : /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;
        $local_connection_user = uid2user($uid);

        $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = $local_connection_uid;

        # Log this for tailwatchd
        Exim::log_write("SMTP connection identification H=localhost A=$sender_host_address P=$sender_host_port U=$local_connection_user ID=$local_connection_uid S=$local_connection_user B=identify_local_connection") if $log;
        return 1;
    }
    else {
        $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = undef;

        Exim::log_write("could not identify the local connection from $sender_host_address on port $sender_host_port.  Please authenticate") if $log;
        return 0;
    }
}

sub identify_local_connection_wrapped {
    my ( $address, $port, $localaddress, $localport ) = @_;

    my $uidline = call_cpwrap( 'IDENTIFYLOCALCONNECTION', $address, $port, $localaddress, $localport );

    chomp($uidline) if defined $uidline;

    my ( $uidkey, $uid ) = split( /:/, $uidline, 2 );
    $uid = undef if $uid eq '';

    Exim::log_write("/usr/local/cpanel/bin/eximwrap IDENTIFYLOCALCONNECTION $address $port $localaddress $localport failed to return the uid key.") if ( !defined $uidkey || $uidkey ne 'uid' );

    return $uid;
}

my $headers_rewrite_notice = '';
my $new_from_header;

use constant {
    _ENOENT => 2,
    _EEXIST => 17,

    _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
            && 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 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;


./BlackJoker Mini Shell 1.0