From dc1434d4fa9c38c45f9289456e9e1f91fd0bdeab Mon Sep 17 00:00:00 2001 From: link2xt Date: Mon, 20 Oct 2025 22:19:56 +0000 Subject: [PATCH] Require STARTTLS for incoming port 25 connections We already require that outgoing connections use STARTTLS so other servers need a valid TLS certificate to accept messages from us. It is then very unlikely that they cannot use TLS to send messages to us. Conversely, if they only can send messages to use without TLS, it likely does not have STARTLS on its port 25 and then we don't want to accept messages from them because we will likely not be able to reply. --- CHANGELOG.md | 3 ++ cmdeploy/src/cmdeploy/postfix/master.cf.j2 | 1 + .../src/cmdeploy/tests/online/test_0_login.py | 36 +++++++++---------- .../src/cmdeploy/tests/online/test_1_basic.py | 1 + 4 files changed, 22 insertions(+), 19 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 778f72e8..d6509a66 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -8,6 +8,9 @@ - Require TLS 1.2 for outgoing SMTP connections ([#685](https://github.com/chatmail/relay/pull/685)) +- require STARTTLS for incoming port 25 connections + ([#684](https://github.com/chatmail/relay/pull/684)) + - filtermail: run CPU-intensive handle_DATA in a thread pool executor ([#676](https://github.com/chatmail/relay/pull/676)) diff --git a/cmdeploy/src/cmdeploy/postfix/master.cf.j2 b/cmdeploy/src/cmdeploy/postfix/master.cf.j2 index 5e460d19..e298aa84 100644 --- a/cmdeploy/src/cmdeploy/postfix/master.cf.j2 +++ b/cmdeploy/src/cmdeploy/postfix/master.cf.j2 @@ -14,6 +14,7 @@ smtp inet n - y - - smtpd -v {%- else %} smtp inet n - y - - smtpd {%- endif %} + -o smtpd_tls_security_level=encrypt -o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port_incoming }} submission inet n - y - 5000 smtpd -o syslog_name=postfix/submission diff --git a/cmdeploy/src/cmdeploy/tests/online/test_0_login.py b/cmdeploy/src/cmdeploy/tests/online/test_0_login.py index 69f79f7d..e5c2b857 100644 --- a/cmdeploy/src/cmdeploy/tests/online/test_0_login.py +++ b/cmdeploy/src/cmdeploy/tests/online/test_0_login.py @@ -1,5 +1,5 @@ import queue -import socket +import smtplib import threading import pytest @@ -91,25 +91,23 @@ def login_smtp_imap(smtp, imap): def test_no_vrfy(chatmail_config): domain = chatmail_config.mail_domain - sock = socket.socket(socket.AF_INET, socket.SOCK_STREAM) - sock.settimeout(10) - try: - sock.connect((domain, 25)) - except socket.timeout: - pytest.skip(f"port 25 not reachable for {domain}") - banner = sock.recv(1024) - print(banner) - sock.send(b"VRFY wrongaddress@%s\r\n" % (chatmail_config.mail_domain.encode(),)) - result = sock.recv(1024) + + s = smtplib.SMTP(domain) + s.starttls() + + s.putcmd("vrfy", f"wrongaddress@{chatmail_config.mail_domain}") + result = s.getreply() print(result) - sock.send(b"VRFY echo@%s\r\n" % (chatmail_config.mail_domain.encode(),)) - result2 = sock.recv(1024) + s.putcmd("vrfy", f"echo@{chatmail_config.mail_domain}") + result2 = s.getreply() print(result2) - assert result[0:10] == result2[0:10] - sock.send(b"VRFY wrongaddress\r\n") - result = sock.recv(1024) + assert result[0] == result2[0] == 252 + assert result[1][0:6] == result2[1][0:6] == b"2.0.0 " + s.putcmd("vrfy", "wrongaddress") + result = s.getreply() print(result) - sock.send(b"VRFY echo\r\n") - result2 = sock.recv(1024) + s.putcmd("vrfy", "echo") + result2 = s.getreply() print(result2) - assert result[0:10] == result2[0:10] == b"252 2.0.0 " + assert result[0] == result2[0] == 252 + assert result[1][0:6] == result2[1][0:6] == b"2.0.0 " diff --git a/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py b/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py index 28a4a519..c9944466 100644 --- a/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py +++ b/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py @@ -143,6 +143,7 @@ def test_reject_missing_dkim(cmsetup, maildata, from_addr): "encrypted.eml", from_addr=from_addr, to_addr=recipient.addr ).as_string() conn = smtplib.SMTP(cmsetup.maildomain, 25, timeout=10) + conn.starttls() with conn as s: with pytest.raises(smtplib.SMTPDataError, match="No valid DKIM signature"):