diff --git a/.github/workflows/test-and-deploy-ipv4only.yaml b/.github/workflows/test-and-deploy-ipv4only.yaml index 990963ec..0029f061 100644 --- a/.github/workflows/test-and-deploy-ipv4only.yaml +++ b/.github/workflows/test-and-deploy-ipv4only.yaml @@ -84,6 +84,7 @@ jobs: ssh root@staging-ipv4.testrun.org "cd relay && scripts/cmdeploy init staging-ipv4.testrun.org" ssh root@staging-ipv4.testrun.org "sed -i 's#disable_ipv6 = False#disable_ipv6 = True#' relay/chatmail.ini" ssh root@staging-ipv4.testrun.org "sed -i 's/#\s*mtail_address/mtail_address/' relay/chatmail.ini" + ssh root@staging-ipv4.testrun.org "sed -i 's/max_mailbox_size = 500M/max_mailbox_size = 10k/' relay/chatmail.ini" - run: ssh root@staging-ipv4.testrun.org "cd relay && scripts/cmdeploy run --verbose --skip-dns-check --ssh-host localhost" diff --git a/.github/workflows/test-and-deploy.yaml b/.github/workflows/test-and-deploy.yaml index 2f744cb8..731a29f8 100644 --- a/.github/workflows/test-and-deploy.yaml +++ b/.github/workflows/test-and-deploy.yaml @@ -76,7 +76,7 @@ jobs: - run: | cmdeploy init staging2.testrun.org - sed -i 's/#\s*mtail_address/mtail_address/' chatmail.ini + sed -i 's/#\s*mtail_address/mtail_address/;s/max_mailbox_size = 500M/max_mailbox_size = 10k/' chatmail.ini - run: cmdeploy run --verbose --skip-dns-check diff --git a/cmdeploy/src/cmdeploy/cmdeploy.py b/cmdeploy/src/cmdeploy/cmdeploy.py index 52c7de0d..3877fe88 100644 --- a/cmdeploy/src/cmdeploy/cmdeploy.py +++ b/cmdeploy/src/cmdeploy/cmdeploy.py @@ -376,14 +376,14 @@ def get_parser(): return parser -def get_sshexec(ssh_host: str, verbose=True): +def get_sshexec(ssh_host: str, verbose=True, **kwargs): if ssh_host in ["localhost", "@local"]: return LocalExec(verbose, docker=False) elif ssh_host == "@docker": return LocalExec(verbose, docker=True) if verbose: print(f"[ssh] login to {ssh_host}") - return SSHExec(ssh_host, verbose=verbose) + return SSHExec(ssh_host, verbose=verbose, **kwargs) def main(args=None): diff --git a/cmdeploy/src/cmdeploy/postfix/master.cf.j2 b/cmdeploy/src/cmdeploy/postfix/master.cf.j2 index 4914767c..abd16c6f 100644 --- a/cmdeploy/src/cmdeploy/postfix/master.cf.j2 +++ b/cmdeploy/src/cmdeploy/postfix/master.cf.j2 @@ -51,12 +51,15 @@ smtps inet n - y - 5000 smtpd -o smtpd_proxy_filter=127.0.0.1:{{ config.filtermail_smtp_port }} #628 inet n - y - - qmqpd pickup unix n - y 60 1 pickup + -o cleanup_service_name=signlocal cleanup unix n - y - 0 cleanup qmgr unix n - n 300 1 qmgr #qmgr unix n - n 300 1 oqmgr tlsmgr unix - - y 1000? 1 tlsmgr rewrite unix - - y - - trivial-rewrite bounce unix - - y - 0 bounce + -o internal_mail_filter_classes=bounce + -o cleanup_service_name=signlocal defer unix - - y - 0 bounce trace unix - - y - 0 bounce verify unix - - y - 1 verify @@ -100,3 +103,9 @@ filter unix - n n - - lmtp # cannot send unprotected Subject. authclean unix n - - - 0 cleanup -o header_checks=regexp:/etc/postfix/submission_header_cleanup + +# Signs locally generated bounce messages. +# These can't be signed using smtpd as they are not non-smtpd. +signlocal unix n - - - 0 cleanup + -o milter_macro_daemon_name=ORIGINATING + -o non_smtpd_milters=unix:opendkim/opendkim.sock diff --git a/cmdeploy/src/cmdeploy/sshexec.py b/cmdeploy/src/cmdeploy/sshexec.py index 7470d927..90a2a69e 100644 --- a/cmdeploy/src/cmdeploy/sshexec.py +++ b/cmdeploy/src/cmdeploy/sshexec.py @@ -49,8 +49,9 @@ class SSHExec: RemoteError = execnet.RemoteError FuncError = FuncError - def __init__(self, host, verbose=False, python="python3", timeout=60): - self.gateway = execnet.makegateway(f"ssh=root@{host}//python={python}") + def __init__(self, host, verbose=False, python="python3", timeout=60, ssh_options=None): + ssh_options = f"{ssh_options.strip()} " if ssh_options is not None else "" + self.gateway = execnet.makegateway(f"ssh={ssh_options}root@{host}//python={python}") self._remote_cmdloop_channel = bootstrap_remote(self.gateway, remote) self.timeout = timeout self.verbose = verbose diff --git a/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py b/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py index 7ff23b5b..f2fcbf76 100644 --- a/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py +++ b/cmdeploy/src/cmdeploy/tests/online/test_1_basic.py @@ -8,6 +8,7 @@ import pytest from cmdeploy import remote from cmdeploy.cmdeploy import get_sshexec +from cmdeploy.sshexec import FuncError class TestSSHExecutor: @@ -117,6 +118,33 @@ def test_reject_forged_from(cmsetup, maildata, gencreds, lp, forgeaddr): assert "500" in str(e.value) +@pytest.mark.slow +def test_bounces_are_signed(cmsetup, cmsetup2, maildata, sshdomain2): + """Test that bounce messages are dkim signed""" + + dkim_rejects_dir = "/tmp/filtermail-rejected/dkim-verify" + sshexec2 = get_sshexec(sshdomain2, ssh_options="-oStrictHostKeyChecking=accept-new") + sshexec2(call=remote.rdns.shell, kwargs=dict(command=f"rm -rf {dkim_rejects_dir}")) + + our_user = cmsetup.gen_users(1)[0] + other_user = cmsetup2.gen_users(1)[0] + msg = maildata("encrypted.eml", from_addr=other_user.addr, to_addr=our_user.addr) + + # exceed the 10kB quota of our_user mailbox to trigger a bounce message. + def bounce_received(): + other_user.smtp.sendmail( + from_addr=other_user.addr, to_addrs=[our_user.addr], msg=msg.as_string() + ) + out = sshexec2(call=remote.rdns.shell, kwargs=dict(command=f"journalctl -n 5 -u filtermail-incoming")) + assert "Filtering unencrypted mail." in out + + try_n_times(20, bounce_received) + + time.sleep(1) + # if bounce was dkim-signed, filtermail shouldn't log the eml. + with pytest.raises(FuncError): + sshexec2(call=remote.rdns.shell, kwargs=dict(command=f"ls {dkim_rejects_dir}")) + def test_authenticated_from(cmsetup, maildata): """Test that envelope FROM must be the same as login.""" user1, user2, user3 = cmsetup.gen_users(3) diff --git a/cmdeploy/src/cmdeploy/tests/plugin.py b/cmdeploy/src/cmdeploy/tests/plugin.py index 269aa828..adb1f2a3 100644 --- a/cmdeploy/src/cmdeploy/tests/plugin.py +++ b/cmdeploy/src/cmdeploy/tests/plugin.py @@ -456,6 +456,11 @@ def 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: def __init__(self, maildomain, gencreds, ssl_context): self.maildomain = maildomain @@ -466,7 +471,7 @@ class CMSetup: print(f"Creating {num} online users") users = [] for i in range(num): - addr, password = self.gencreds() + addr, password = self.gencreds(self.maildomain) user = CMUser(self.maildomain, addr, password, self.ssl_context) assert user.smtp users.append(user)