fix for forwarding save option, added SRS

This commit is contained in:
Matthew Saunders Brown 2021-12-13 16:35:42 -08:00
parent 9186bc4a01
commit 6d1dd40484
4 changed files with 133 additions and 32 deletions

View File

@ -0,0 +1,12 @@
****
This message was created automatically by the mail delivery software on $primary_hostname:
A message ${if eq{$sender_address}{$bounce_recipient}{that you sent}{sent by\n}}
${if ! eq{$sender_address}{$bounce_recipient} {${if match {$sender_address} {\N(?i)^SRS[01][=+-]\N} {${sg {${sg {$sender_address} {\N(?i)^SRS[01][=+-][^ @]+?[=+-][a-zA-Z0-9]{2}[=+-]([^ @]+?)[=+-]([^ @]++)@.*$\N} {\N $2@$1\N}} } {\N^ prvs=[^=]+?=(.*)$\N} {\N $1\N} }\n} {${if match {$sender_address} {\N(?i)^prvs=\N} {${sg {$sender_address} {\N(?i)^prvs=[^ @]+?=(.*@.*)$\N} {\N $1\n\N}}} { $sender_address\n}}} }}}
could not be delivered to one or more of its recipients. The following
address(es) failed:
****
****
****
****

View File

@ -7,6 +7,7 @@ disable_ipv6 = true
keep_environment = keep_environment =
add_environment = PATH=/usr/sbin:/usr/bin:/sbin:/bin add_environment = PATH=/usr/sbin:/usr/bin:/sbin:/bin
smtp_enforce_sync = false smtp_enforce_sync = false
bounce_message_file = /etc/exim4/bounce_message_text
smtp_accept_max = 50 smtp_accept_max = 50
smtp_accept_max_per_host = 10 smtp_accept_max_per_host = 10
@ -202,7 +203,7 @@ rfc1413_query_timeout = 0s
ignore_bounce_errors_after = 0s ignore_bounce_errors_after = 0s
# This option cancels (removes) frozen messages that are older than a week. # This option cancels (removes) frozen messages that are older than a day.
timeout_frozen_after = 1d timeout_frozen_after = 1d
@ -433,6 +434,32 @@ acl_rcpt_to:
# Recipent Address Checks # Recipent Address Checks
###################################################################### ######################################################################
# Ensure only valid SRS prefixed bounce message get accepted
deny
senders = :
domains = +local_domains
local_parts = ${if match {$local_part} {(?i)\N^SRS[01][=+-]\N} {$local_part}}
control = caseful_local_part
condition = ${if match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{\n}}}{^ERROR: .* Invalid hash at .*}}
message = Invalid reverse path (SRS check failed on $local_part@$domain).
warn
senders = :
domains = +local_domains
local_parts = ${if match {$local_part} {\N^srs[01][=+-]\N} {$local_part}}
control = caseful_local_part
condition = ${if match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{ }}}{^SRS: Case insensitive hash match detected. Someone smashed case in the local-part. .*}}
log_message = SRS hash smashed on the way for $local_part@$domain by case insensitive MTA.
# this is for debugging only. can be safely removed any time
warn
senders = :
domains = +local_domains
local_parts = ${if match {$local_part} {(?i)\N^SRS[01][=+-]\N} {$local_part}}
control = caseful_local_part
condition = ${if !match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{\n}}}{^ERROR: .* Invalid hash at .*}}
log_message = Incoming SRS bounce to $local_part@$domain
# Deny if the local part contains @ or % or / or | or !. These are # Deny if the local part contains @ or % or / or | or !. These are
# rarely found in genuine local parts, but are often tried by people # rarely found in genuine local parts, but are often tried by people
# looking to circumvent relaying restrictions. # looking to circumvent relaying restrictions.
@ -697,6 +724,33 @@ autowhitelist_filter:
unseen unseen
allow_filter = true allow_filter = true
srs_bounce:
senders = :
driver = redirect
domains = +local_domains
allow_fail
allow_defer
local_part_prefix = srs0+ : srs0- : srs0= : srs1+ : srs1- : srs1=
caseful_local_part
address_data = ${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part_prefix$local_part@$domain}{5s}{ }{:defer: SRS daemon failure}}
data = ${sg {$address_data} {^SRS: Case insensitive hash match detected. Someone smashed case in the local-part\. .* ([^ ]+)@([^ ]+)\$} {\N$1@$2\N} }
headers_add = X-SRS: Decoded valid SRS return address to ${quote_local_part:${local_part:$address_data}}@${domain:$address_data} by $primary_hostname
srs_forward:
driver = redirect
senders = ! : ! *@+local_domains
domains = ! +local_domains : ! +relay_to_domains
condition = ${lookup mysql{SELECT vm_domains.id FROM vm_domains WHERE vm_domains.domain='${original_domain}' AND vm_domains.status = '1'}}
address_data = ${readsocket{/run/srsd/srsd.sock}\
{FORWARD $sender_address_local_part@$sender_address_domain $original_domain\n}\
{5s}{\n}{:defer: SRS daemon failure}}
errors_to = ${quote_local_part:${local_part:$address_data}}@${domain:$address_data}
data = ${quote_local_part:$local_part}@$domain
headers_add = X-SRS-Forward: from $sender_address to $original_local_part@$original_domain forwarded to $local_part@$domain by $primary_hostname
repeat_use = false
allow_defer
no_verify
# This router routes addresses that are not in local domains by doing a DNS # This router routes addresses that are not in local domains by doing a DNS
# lookup on the domain name. Any domain that resolves to 0.0.0.0 or to a # lookup on the domain name. Any domain that resolves to 0.0.0.0 or to a
# loopback interface address (127.0.0.0/8) is treated as if it had no DNS # loopback interface address (127.0.0.0/8) is treated as if it had no DNS
@ -735,24 +789,6 @@ junk_filter:
condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_mboxes.filter > '0'}} condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_mboxes.filter > '0'}}
transport = junk_delivery transport = junk_delivery
spamcheck_router:
driver = accept
domains = +local_domains
local_part_suffix = +*
local_part_suffix_optional = true
condition = ${if and { \
{ !eq {$received_protocol}{spam-scanned}} \
{ !eq {$sender_address_domain}{$domain}} \
{ < {$message_size}{512k}} \
{ !eq {$header_X-Junk-Flag:}{YES}} \
{ !eq {$header_X-Whitelist-Flag:}{YES}} \
{ eq {${lookup mysql{SELECT vm_mboxes.status FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1'}{$value}fail}}{1} } \
} {yes} {no}}
# check domain & mbox 'status'?
# Check for other headers too? Blacklist, SPF, DKIM failers go directly to Spam folder without spam scan??? - actually they should go to spam folder before this router is hit?
headers_remove = X-Spam-Checker-Version:X-Spam-Flag:X-Spam-Level:X-Spam-Status:X-Spam-Score:X-Spam-Report
transport = spamcheck
spam_filter: spam_filter:
driver = accept driver = accept
domains = +local_domains domains = +local_domains
@ -771,8 +807,9 @@ virtual_vacation:
domains = +local_domains domains = +local_domains
# do not reply to errors or lists or spam-scanned messages, require vacation message in db # do not reply to errors or lists or spam-scanned messages, require vacation message in db
condition = ${if and { \ condition = ${if and { \
{!match {$h_precedence:} {(?i)junk|bulk|list}} \ { !match {$h_precedence:} {(?i)junk|bulk|list}} \
{!eq {$sender_address} {}} \ { !eq {$received_protocol}{spam-scanned}} \
{ !eq {$sender_address} {}} \
{ eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Vacation}} \ { eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Vacation}} \
} {yes} {no}} } {yes} {no}}
# add options for start & end date fields # add options for start & end date fields
@ -796,8 +833,9 @@ virtual_autoresponder:
#local_part_suffix_optional = true #local_part_suffix_optional = true
# do not reply to errors or lists or spam-scanned messages, require autoresponder message in db # do not reply to errors or lists or spam-scanned messages, require autoresponder message in db
condition = ${if and { \ condition = ${if and { \
{!match {$h_precedence:} {(?i)junk|bulk|list}} \ { !match {$h_precedence:} {(?i)junk|bulk|list}} \
{!eq {$sender_address} {}} \ { !eq {$received_protocol}{spam-scanned}} \
{ !eq {$sender_address} {}} \
{ eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Autoresponder} } \ { eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Autoresponder} } \
} {yes} {no}} } {yes} {no}}
# add options for start & end date fields # add options for start & end date fields
@ -813,13 +851,38 @@ virtual_autoresponder:
unseen unseen
no_verify no_verify
virtual_forward: virtual_forward_and_drop:
driver = redirect driver = redirect
domains = +local_domains domains = +local_domains
condition = ${if !eq {$received_protocol}{spam-scanned}}
local_part_suffix = +*
local_part_suffix_optional = true
data = ${lookup mysql{SELECT vm_forwards.forward_to FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_forwards.save_local='0'}}
virtual_forward_and_keep:
driver = redirect
domains = +local_domains
condition = ${if !eq {$received_protocol}{spam-scanned}}
local_part_suffix = +*
local_part_suffix_optional = true
data = ${lookup mysql{SELECT CONCAT('${local_part}@${domain}\n', vm_forwards.forward_to) FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_forwards.save_local='1'}}
spamcheck_router:
driver = accept
domains = +local_domains
local_part_suffix = +* local_part_suffix = +*
local_part_suffix_optional = true local_part_suffix_optional = true
data = ${lookup mysql{SELECT vm_forwards.forward_to FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' }} condition = ${if and { \
unseen = ${lookup mysql{SELECT vm_forwards.id FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_forwards.save_local='1'}{true}{false}} { !eq {$received_protocol}{spam-scanned}} \
{ !eq {$sender_address_domain}{$domain}} \
{ < {$message_size}{512k}} \
{ !eq {$header_X-Junk-Flag:}{YES}} \
{ !eq {$header_X-Whitelist-Flag:}{YES}} \
{ eq {${lookup mysql{SELECT vm_mboxes.status FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1'}{$value}fail}}{1} } \
} {yes} {no}}
# Check for other headers too? Blacklist, SPF, DKIM failers go directly to Spam folder without spam scan??? - actually they should go to spam folder before this router is hit?
headers_remove = X-Spam-Checker-Version:X-Spam-Flag:X-Spam-Level:X-Spam-Status:X-Spam-Score:X-Spam-Report
transport = spamcheck
user_filter: user_filter:
driver = redirect driver = redirect
@ -1094,10 +1157,8 @@ address_reply:
begin retry begin retry
# This single retry rule applies to all domains and all errors. It specifies # This single retry rule applies to all domains and all errors. It specifies
# retries every 15 minutes for 2 hours, then increasing retry intervals, # retries every 15 minutes for 2 hours, then every 2 hours until 1 full day
# starting at 1 hour and increasing each time by a factor of 1.5, up to 16 # has passed since the first delivery failed.
# hours, then retries every 6 hours until 4 days have passed since the first
# failed delivery.
# Domain Error Retries # Domain Error Retries
# ------ ----- ------- # ------ ----- -------
@ -1114,7 +1175,6 @@ begin retry
begin rewrite begin rewrite
###################################################################### ######################################################################
# AUTHENTICATION CONFIGURATION # # AUTHENTICATION CONFIGURATION #
###################################################################### ######################################################################

View File

@ -59,7 +59,7 @@ mysql -e "GRANT ALL PRIVILEGES ON vmail.* TO 'vmail'@'localhost';"
mysqladmin flush-privileges mysqladmin flush-privileges
# install mail server software # install mail server software
apt -y install exim4-daemon-heavy spf-tools-perl spamassassin libclass-dbi-mysql-perl dovecot-core dovecot-imapd dovecot-mysql dovecot-pop3d dovecot-lmtpd apt -y install exim4-daemon-heavy spf-tools-perl spamassassin srs libclass-dbi-mysql-perl dovecot-core dovecot-imapd dovecot-mysql dovecot-pop3d dovecot-lmtpd
# configure system users # configure system users
apt -y install ssl-cert apt -y install ssl-cert
@ -97,6 +97,16 @@ chmod 644 /etc/spamassassin/local.cf
chown debian-spamd:mail /etc/spamassassin/sql.cf chown debian-spamd:mail /etc/spamassassin/sql.cf
chmod 640 /etc/spamassassin/sql.cf chmod 640 /etc/spamassassin/sql.cf
# srsd
# bug fixes for libmail-srs-perl. still needed as of v0.31-6 on Ubuntu 20.04
sed -i 's|/tmp/srsd|/run/srsd/srsd.sock|' /usr/share/perl5/Mail/SRS/Daemon.pm
sed -i '/Until we decide that forward/,+3d' /usr/share/perl5/Mail/SRS/Daemon.pm
cp systemd/srsd.service /lib/systemd/system/srsd.service
chmod 644 /lib/systemd/system/srsd.service
systemctl daemon-reload
systemctl enable srsd
systemctl start srsd
# exim config # exim config
maildomain=`hostname -d` maildomain=`hostname -d`
sed -i 's/size 10M/daily/g' /etc/logrotate.d/exim4-paniclog sed -i 's/size 10M/daily/g' /etc/logrotate.d/exim4-paniclog
@ -112,6 +122,10 @@ chmod 640 /etc/exim4/skip_greylisting_hosts
sed -i "s|example.com|$maildomain|g" /etc/exim4/skip_greylisting_hosts sed -i "s|example.com|$maildomain|g" /etc/exim4/skip_greylisting_hosts
sed -i "s|password|$VMAILPASS|g" /etc/exim4/exim_local.conf sed -i "s|password|$VMAILPASS|g" /etc/exim4/exim_local.conf
sed -i "s|example.com|$maildomain|g" /etc/exim4/exim_local.conf sed -i "s|example.com|$maildomain|g" /etc/exim4/exim_local.conf
touch /etc/exim4/srsd.secret
chmod 640 /etc/exim4/srsd.secret
chown Debian-exim:Debian-exim /etc/exim4/srsd.secret
pwgen -N 1 -cny 64 > /etc/exim4/srsd.secret
# dovecot config # dovecot config
mkdir /etc/dovecot/sites.d mkdir /etc/dovecot/sites.d

15
systemd/srsd.service Normal file
View File

@ -0,0 +1,15 @@
[Unit]
Description=Sender Rewriting Scheme Daemon
[Service]
Type=exec
User=Debian-exim
Group=Debian-exim
ExecStart=/usr/bin/srsd --secretfile /etc/exim4/srsd.secret --hashlength 24
PIDFile=/run/srsd/srsd.pid
Restart=on-failure
RuntimeDirectory=srsd
RuntimeDirectoryMode=0750
[Install]
WantedBy=multi-user.target