mirror of
https://github.com/chatmail/relay.git
synced 2026-05-19 12:28:06 +00:00
feat: DKIM-sign bounce messages (mainly "user does not exist")
This is based on Jagoda's https://github.com/chatmail/relay/pull/874 but comes with a simpler and more robust test. TODO: requires https://github.com/chatmail/filtermail/pull/149
This commit is contained in:
@@ -51,12 +51,16 @@ smtps inet n - y - 5000 smtpd
|
|||||||
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }}
|
-o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }}
|
||||||
#628 inet n - y - - qmqpd
|
#628 inet n - y - - qmqpd
|
||||||
pickup unix n - y 60 1 pickup
|
pickup unix n - y 60 1 pickup
|
||||||
cleanup unix n - y - 0 cleanup
|
{% if not config.ipv4_relay %} -o cleanup_service_name=signlocal
|
||||||
|
{% endif %}cleanup unix n - y - 0 cleanup
|
||||||
qmgr unix n - n 300 1 qmgr
|
qmgr unix n - n 300 1 qmgr
|
||||||
#qmgr unix n - n 300 1 oqmgr
|
#qmgr unix n - n 300 1 oqmgr
|
||||||
tlsmgr unix - - y 1000? 1 tlsmgr
|
tlsmgr unix - - y 1000? 1 tlsmgr
|
||||||
rewrite unix - - y - - trivial-rewrite
|
rewrite unix - - y - - trivial-rewrite
|
||||||
bounce unix - - y - 0 bounce
|
bounce unix - - y - 0 bounce
|
||||||
|
{% if not config.ipv4_relay %} -o internal_mail_filter_classes=bounce
|
||||||
|
-o cleanup_service_name=signlocal
|
||||||
|
{% endif %}
|
||||||
defer unix - - y - 0 bounce
|
defer unix - - y - 0 bounce
|
||||||
trace unix - - y - 0 bounce
|
trace unix - - y - 0 bounce
|
||||||
verify unix - - y - 1 verify
|
verify unix - - y - 1 verify
|
||||||
@@ -102,6 +106,15 @@ filter unix - n n - - lmtp
|
|||||||
authclean unix n - - - 0 cleanup
|
authclean unix n - - - 0 cleanup
|
||||||
-o header_checks=regexp:/etc/postfix/submission_header_cleanup
|
-o header_checks=regexp:/etc/postfix/submission_header_cleanup
|
||||||
|
|
||||||
|
{% if not config.ipv4_relay %}
|
||||||
|
# DKIM-sign locally generated mail (bounces, DSNs).
|
||||||
|
# These bypass smtpd, so they need explicit milter configuration.
|
||||||
|
signlocal unix n - - - 0 cleanup
|
||||||
|
-o syslog_name=postfix/signlocal
|
||||||
|
-o milter_macro_daemon_name=ORIGINATING
|
||||||
|
-o non_smtpd_milters=unix:opendkim/opendkim.sock
|
||||||
|
{% endif %}
|
||||||
|
|
||||||
lmtp-filtermail unix - - y - 10000 lmtp
|
lmtp-filtermail unix - - y - 10000 lmtp
|
||||||
-o syslog_name=postfix/lmtp-filtermail
|
-o syslog_name=postfix/lmtp-filtermail
|
||||||
-o lmtp_header_checks=
|
-o lmtp_header_checks=
|
||||||
|
|||||||
@@ -194,6 +194,34 @@ def test_reject_missing_dkim(cmsetup, maildata, from_addr):
|
|||||||
s.sendmail(from_addr=from_addr, to_addrs=recipient.addr, msg=msg)
|
s.sendmail(from_addr=from_addr, to_addrs=recipient.addr, msg=msg)
|
||||||
|
|
||||||
|
|
||||||
|
def test_bounces_are_dkim_signed(cmsetup, cmsetup2, maildata, maildomain):
|
||||||
|
# we send a message to non-existant user and expect a bounce message
|
||||||
|
# which will only get through if the bounce message was DKIM-signed
|
||||||
|
|
||||||
|
if is_valid_ipv4(maildomain):
|
||||||
|
pytest.skip("DKIM is not configured on IPv4-only relays")
|
||||||
|
|
||||||
|
sender = cmsetup2.gen_users(1)[0]
|
||||||
|
nonexistent = f"nosuchuser_test42@{cmsetup.maildomain}"
|
||||||
|
|
||||||
|
msg = maildata(
|
||||||
|
"encrypted.eml",
|
||||||
|
from_addr=sender.addr,
|
||||||
|
to_addr=nonexistent,
|
||||||
|
).as_string()
|
||||||
|
sender.smtp.sendmail(sender.addr, [nonexistent], msg)
|
||||||
|
|
||||||
|
def bounce_in_inbox():
|
||||||
|
messages = sender.imap.fetch_all_messages()
|
||||||
|
for m in messages:
|
||||||
|
if "mail delivery" in m.lower() or "undelivered" in m.lower():
|
||||||
|
return m
|
||||||
|
raise ValueError("bounce not yet in inbox")
|
||||||
|
|
||||||
|
bounce = try_n_times(30, bounce_in_inbox)
|
||||||
|
assert "nosuchuser_test42" in bounce
|
||||||
|
|
||||||
|
|
||||||
def try_n_times(n, f):
|
def try_n_times(n, f):
|
||||||
for _ in range(n - 1):
|
for _ in range(n - 1):
|
||||||
try:
|
try:
|
||||||
|
|||||||
@@ -19,6 +19,7 @@ def format_mail_domain(raw_domain: str) -> str:
|
|||||||
DomainValidator().validate_domain_re(raw_domain)
|
DomainValidator().validate_domain_re(raw_domain)
|
||||||
return raw_domain
|
return raw_domain
|
||||||
|
|
||||||
|
|
||||||
conftestdir = Path(__file__).parent
|
conftestdir = Path(__file__).parent
|
||||||
|
|
||||||
|
|
||||||
@@ -466,6 +467,11 @@ def cmsetup(maildomain, gencreds, ssl_context):
|
|||||||
return CMSetup(maildomain, gencreds, ssl_context)
|
return CMSetup(maildomain, gencreds, ssl_context)
|
||||||
|
|
||||||
|
|
||||||
|
@pytest.fixture
|
||||||
|
def cmsetup2(maildomain2, gencreds, ssl_context):
|
||||||
|
return CMSetup(maildomain2, gencreds, ssl_context)
|
||||||
|
|
||||||
|
|
||||||
class CMSetup:
|
class CMSetup:
|
||||||
def __init__(self, maildomain, gencreds, ssl_context):
|
def __init__(self, maildomain, gencreds, ssl_context):
|
||||||
self.maildomain = maildomain
|
self.maildomain = maildomain
|
||||||
@@ -476,7 +482,7 @@ class CMSetup:
|
|||||||
print(f"Creating {num} online users")
|
print(f"Creating {num} online users")
|
||||||
users = []
|
users = []
|
||||||
for i in range(num):
|
for i in range(num):
|
||||||
addr, password = self.gencreds()
|
addr, password = self.gencreds(format_mail_domain(self.maildomain))
|
||||||
user = CMUser(self.maildomain, addr, password, self.ssl_context)
|
user = CMUser(self.maildomain, addr, password, self.ssl_context)
|
||||||
assert user.smtp
|
assert user.smtp
|
||||||
users.append(user)
|
users.append(user)
|
||||||
|
|||||||
Reference in New Issue
Block a user