diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 625523b..a2bbb85 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -60,7 +60,7 @@ jobs: 3.8 - name: Install Python dependencies - run: sudo pip install pytest miltertest dkimpy[ARC] + run: sudo pip install pytest miltertest inline_snapshot dkimpy[ARC] dirty_equals - name: Build OpenARC run: | diff --git a/.gitignore b/.gitignore index c0222da..f9515ea 100644 --- a/.gitignore +++ b/.gitignore @@ -54,6 +54,7 @@ stamp-h1 *~ *.cache *#* +.venv .vscode # compilation artifacts diff --git a/CHANGELOG.md b/CHANGELOG.md index ed1406f..2882f46 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -7,6 +7,7 @@ All notable changes to this project will be documented in this file. ### Added ### Changed +* tests - migrated from manual "snapshots" to `inline-snapshot`. ### Fixed diff --git a/README.md b/README.md index 2db10db..8ee10ed 100644 --- a/README.md +++ b/README.md @@ -116,6 +116,8 @@ Tests can be run with `make check`. OpenARC's test suite requires: * Python >= 3.8 * [pytest](https://pytest.org) +* [dirty-equals](https://github.com/samuelcolvin/dirty-equals) +* [inline-snapshot](https://15r10nk.github.io/inline-snapshot/) * The Python [miltertest](https://pypi.org/project/miltertest/) library There are also optional test dependencies whose associated tests will be diff --git a/test/test_interop.py b/test/test_interop.py index 96730c8..076292e 100644 --- a/test/test_interop.py +++ b/test/test_interop.py @@ -4,6 +4,9 @@ import pytest +from dirty_equals import IsStr +from inline_snapshot import snapshot + HAS_DKIMPY = True try: from dkim import ARC @@ -46,11 +49,17 @@ def test_dkimpy_sign(run_miltertest, private_key, dkimpy): ] res = run_miltertest(hdrs, False) - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] - assert res['headers'][1][0] == 'ARC-Seal' - assert 'cv=pass' in res['headers'][1][1] - assert res['headers'][2][0] == 'ARC-Message-Signature' - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ['ARC-Seal', IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)')], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=Subject:From:To;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) def test_dkimpy_verify(run_miltertest, private_key, dkimpy): @@ -108,8 +117,17 @@ def test_perl_sign(run_miltertest, private_key, perl_mail_dkim): ] res = run_miltertest(hdrs, False) - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] - assert res['headers'][1][0] == 'ARC-Seal' - assert 'cv=pass' in res['headers'][1][1] - assert res['headers'][2][0] == 'ARC-Message-Signature' - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=Subject:From:To;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) diff --git a/test/test_milter.py b/test/test_milter.py index 977ea9d..2795443 100644 --- a/test/test_milter.py +++ b/test/test_milter.py @@ -3,37 +3,79 @@ import miltertest import pytest +from dirty_equals import IsStr +from inline_snapshot import snapshot + def test_milter_basic(run_miltertest): """Basic signing""" res = run_miltertest() - - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1'] - assert res['headers'][1][0] == 'ARC-Seal' - assert 'cv=none' in res['headers'][1][1] - assert res['headers'][2][0] == 'ARC-Message-Signature' - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_v2(run_miltertest): """Basic signing""" res = run_miltertest(protocol=miltertest.SMFI_V2_PROT) - - assert res['headers'][0] == ['Authentication-Results', 'example.com; arc=none smtp.remote-ip=127.0.0.1'] - assert res['headers'][1][0] == 'ARC-Seal' - assert 'cv=none' in res['headers'][1][1] - assert res['headers'][2][0] == 'ARC-Message-Signature' - assert res['headers'][3] == ['ARC-Authentication-Results', 'i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', 'example.com; arc=none smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r'i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r'i=1; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', 'i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_canon_simple(run_miltertest): """Sign a message with simple canonicalization and then verify it""" res = run_miltertest() - assert 'c=relaxed' not in res['headers'][1][1] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'], + ] + ) res = run_miltertest(res['headers']) - assert 'cv=pass' in res['headers'][0][1] - assert res['headers'][2] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_resign(run_miltertest): @@ -45,33 +87,58 @@ def test_milter_resign(run_miltertest): headers = [*res['headers'], *headers] res = run_miltertest(headers) - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] - if i <= 50: - assert res['headers'][3] == ['ARC-Authentication-Results', f' i={i}; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] - assert 'cv=pass' in res['headers'][1][1] - - # quick and dirty parsing - ams = {x[0].strip(): x[1].strip() for x in [y.split('=', 1) for y in ''.join(res['headers'][2][1].splitlines()).split(';')]} - ams_h = [x.strip() for x in ams['h'].lower().split(':')] - assert not any(x in ams_h for x in ['authentication-results', 'arc-seal', 'arc-message-signature', 'arc-authentication-results']) + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=[0-9]{1,2}; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr( + regex=r' i=[0-9]{1,2}; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)' + ), + ], + ['ARC-Authentication-Results', IsStr(regex=r' i=[0-9]{1,2}; example\.com; arc=pass header\.oldest-pass=0 smtp\.remote-ip=127.0.0.1')], + ] + ) else: - assert len(res['headers']) == 1 + assert res['headers'] == snapshot([['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1']]) def test_milter_mode_s(run_miltertest): """Sign mode""" res = run_miltertest() - - assert len(res['headers']) == 3 - assert 'cv=none' in res['headers'][0][1] - assert res['headers'][1][0] == 'ARC-Message-Signature' - assert res['headers'][2] == ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'], + ] + ) res = run_miltertest(res['headers']) - assert len(res['headers']) == 3 - assert 'cv=pass' in res['headers'][0][1] - assert res['headers'][2] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_resign_s(run_miltertest): @@ -84,45 +151,57 @@ def test_milter_resign_s(run_miltertest): res = run_miltertest(headers) if i <= 50: - assert res['headers'][2] == ['ARC-Authentication-Results', f' i={i}; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] - assert 'cv=pass' in res['headers'][0][1] - - # quick and dirty parsing - ams = {x[0].strip(): x[1].strip() for x in [y.split('=', 1) for y in ''.join(res['headers'][1][1].splitlines()).split(';')]} - ams_h = [x.strip() for x in ams['h'].lower().split(':')] - assert not any(x in ams_h for x in ['authentication-results', 'arc-seal', 'arc-message-signature', 'arc-authentication-results']) + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=[0-9]{1,2}; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr( + regex=r' i=[0-9]{1,2}; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)' + ), + ], + ['ARC-Authentication-Results', IsStr(regex=r' i=[0-9]{1,2}; example\.com; arc=pass header\.oldest-pass=0 smtp\.remote-ip=127\.0\.0\.1')], + ] + ) else: - assert len(res['headers']) == 0 + assert res['headers'] == snapshot([]) def test_milter_mode_v(run_miltertest): """Verify mode""" res = run_miltertest() - - assert len(res['headers']) == 1 - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot([['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1']]) def test_milter_mode_none_verify(run_miltertest): """No configured mode, from a host that's not in InternalHosts""" res = run_miltertest() - - assert len(res['headers']) == 1 - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot([['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1']]) def test_milter_mode_none_sign(run_miltertest): """No configured mode, from a host that's in InternalHosts""" res = run_miltertest() - - assert len(res['headers']) == 3 - assert 'cv=none' in res['headers'][0][1] - assert res['headers'][1][0] == 'ARC-Message-Signature' - assert res['headers'][2] == ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'], + ] + ) @pytest.mark.parametrize( - 'data', + 'data,result', [ # Single header [ @@ -134,12 +213,12 @@ def test_milter_mode_none_sign(run_miltertest): ' dkim=pass header.i=@example.com header.s=foo' ) ], - ( - 'iprev=pass policy.iprev=192.0.2.1 (mail.example.com);' - '\n\tspf=pass (domain of foo@example.com designates 192.0.2.1 as permitted sender);' - '\n\tdkim=pass header.i=@example.com header.s=foo;' - '\n\tarc=none smtp.remote-ip=127.0.0.1' - ), + snapshot("""\ + i=1; example.com; iprev=pass policy.iprev=192.0.2.1 (mail.example.com); + spf=pass (domain of foo@example.com designates 192.0.2.1 as permitted sender); + dkim=pass header.i=@example.com header.s=foo; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # Multiple headers [ @@ -148,12 +227,12 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com; spf=pass (domain of foo@example.com\n\t designates 192.0.2.1 as permitted sender)', 'example.com; dkim=pass header.i=@example.com header.s=foo', ], - ( - 'iprev=pass policy.iprev=192.0.2.1 (mail.example.com);' - '\n\tspf=pass (domain of foo@example.com designates 192.0.2.1 as permitted sender);' - '\n\tdkim=pass header.i=@example.com header.s=foo;' - '\n\tarc=none smtp.remote-ip=127.0.0.1' - ), + snapshot("""\ + i=1; example.com; iprev=pass policy.iprev=192.0.2.1 (mail.example.com); + spf=pass (domain of foo@example.com designates 192.0.2.1 as permitted sender); + dkim=pass header.i=@example.com header.s=foo; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # Multiple headers for the same method [ @@ -162,12 +241,18 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com; spf=fail', 'example.com; spf=none', ], - 'spf=pass;\n\tarc=none smtp.remote-ip=127.0.0.1', + snapshot("""\ + i=1; example.com; spf=pass; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # Same method multiple times [ ['example.com; spf=pass; spf=fail; spf=none'], - 'spf=pass;\n\tarc=none smtp.remote-ip=127.0.0.1', + snapshot("""\ + i=1; example.com; spf=pass; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # Header with more results than we're willing to store [ @@ -195,25 +280,25 @@ def test_milter_mode_none_sign(run_miltertest): ' spf=pass' ) ], - ( - 'dkim=pass header.i=@example.com header.s=foo;' - '\n\tdkim=pass header.i=@example.com header.s=bar;' - '\n\tdkim=pass header.i=@example.com header.s=baz;' - '\n\tdkim=pass header.i=@example.com header.s=qux;' - '\n\tdkim=pass header.i=@example.com header.s=quux;' - '\n\tdkim=pass header.i=@example.com header.s=quuux;' - '\n\tdkim=fail header.i=@example.com header.s=foo;' - '\n\tdkim=fail header.i=@example.com header.s=bar;' - '\n\tdkim=fail header.i=@example.com header.s=baz;' - '\n\tdkim=fail header.i=@example.com header.s=qux;' - '\n\tdkim=fail header.i=@example.com header.s=quux;' - '\n\tdkim=fail header.i=@example.com header.s=quuux;' - '\n\tdkim=policy header.i=@example.com header.s=foo;' - '\n\tdkim=policy header.i=@example.com header.s=bar;' - '\n\tdkim=policy header.i=@example.com header.s=baz;' - '\n\tdkim=policy header.i=@example.com header.s=qux;' - '\n\tarc=none smtp.remote-ip=127.0.0.1' - ), + snapshot("""\ + i=1; example.com; dkim=pass header.i=@example.com header.s=foo; + dkim=pass header.i=@example.com header.s=bar; + dkim=pass header.i=@example.com header.s=baz; + dkim=pass header.i=@example.com header.s=qux; + dkim=pass header.i=@example.com header.s=quux; + dkim=pass header.i=@example.com header.s=quuux; + dkim=fail header.i=@example.com header.s=foo; + dkim=fail header.i=@example.com header.s=bar; + dkim=fail header.i=@example.com header.s=baz; + dkim=fail header.i=@example.com header.s=qux; + dkim=fail header.i=@example.com header.s=quux; + dkim=fail header.i=@example.com header.s=quuux; + dkim=policy header.i=@example.com header.s=foo; + dkim=policy header.i=@example.com header.s=bar; + dkim=policy header.i=@example.com header.s=baz; + dkim=policy header.i=@example.com header.s=qux; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # Non-matching authserv-id [ @@ -222,17 +307,23 @@ def test_milter_mode_none_sign(run_miltertest): 'otheradmd.example.com; spf=tempfail', 'example.net; spf=permfail', ], - 'arc=none smtp.remote-ip=127.0.0.1', + snapshot(' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'), ], # CFWS [ ['example.com; (a)spf (Sender Policy Framework) = pass (good) smtp (mail transfer) . (protocol) mailfrom = foo@example.com;'], - 'spf=pass (good) smtp.mailfrom=foo@example.com;\n\tarc=none smtp.remote-ip=127.0.0.1', + snapshot("""\ + i=1; example.com; spf=pass (good) smtp.mailfrom=foo@example.com; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # Unknown method [ ['example.com; spf=pass; superspf=pass; arc=pass; superarc=fail policy.krypton=foo;'], - 'spf=pass;\n\tarc=pass', + snapshot("""\ + i=1; example.com; spf=pass; + arc=pass\ +"""), ], # Unknown ptype [ @@ -240,17 +331,21 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com; spf=pass imap.override=true', 'example.com; spf=pass; iprev=pass dnssec.signed=true', ], - 'arc=none smtp.remote-ip=127.0.0.1', + snapshot(' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'), ], # reason [ ['example.com; spf=pass (ip4)reason="192.0.2.1 matched ip4:192.0.2.0/27 in _spf.example.com"; dmarc=pass'], - 'spf=pass reason="192.0.2.1 matched ip4:192.0.2.0/27 in _spf.example.com" (ip4);\n\tdmarc=pass;\n\tarc=none smtp.remote-ip=127.0.0.1', + snapshot("""\ + i=1; example.com; spf=pass reason="192.0.2.1 matched ip4:192.0.2.0/27 in _spf.example.com" (ip4); + dmarc=pass; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # misplaced reason [ ['example.com; spf=pass; iprev=pass policy.iprev=192.0.2.1 reason="because"'], - 'arc=none smtp.remote-ip=127.0.0.1', + snapshot(' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'), ], # no-result [ @@ -259,7 +354,7 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com; none; spf=pass', 'example.com; spf=fail; none', ], - 'arc=none smtp.remote-ip=127.0.0.1', + snapshot(' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'), ], # truncations [ @@ -276,7 +371,7 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com; dmarc=pass; iprev=pass policy.iprev="1" (', 'example.com; dmarc=pass; iprev=pass policy.iprev="1" ( a c', ], - 'arc=none smtp.remote-ip=127.0.0.1', + snapshot(' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'), ], # bad sequences [ @@ -286,17 +381,23 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com; dmarc=pass; iprev=pass policy=iprev=192.0.2.1', 'example.com; dmarc=pass reason "because";', ], - 'arc=none smtp.remote-ip=127.0.0.1', + snapshot(' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'), ], # RFC 8904 [ ['example.com; dnswl=pass dns.zone=accept.example.com policy.ip=192.0.2.1 policy.txt="sure, yeah" dns.sec=yes'], - 'dnswl=pass dns.zone=accept.example.com policy.ip=192.0.2.1 policy.txt="sure, yeah" dns.sec=yes;\n\tarc=none smtp.remote-ip=127.0.0.1', + snapshot("""\ + i=1; example.com; dnswl=pass dns.zone=accept.example.com policy.ip=192.0.2.1 policy.txt="sure, yeah" dns.sec=yes; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # quoted-string [ ['example.com; auth=pass smtp.auth="花木蘭\\"\\\\ []"'], - 'auth=pass smtp.auth="花木蘭\\"\\\\ []";\n\tarc=none smtp.remote-ip=127.0.0.1', + snapshot("""\ + i=1; example.com; auth=pass smtp.auth="花木蘭\\"\\\\ []"; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # version [ @@ -304,7 +405,11 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com 1; spf=pass', 'example.com 1 ; dmarc=pass', ], - 'spf=pass;\n\tdmarc=pass;\n\tarc=none smtp.remote-ip=127.0.0.1', + snapshot("""\ + i=1; example.com; spf=pass; + dmarc=pass; + arc=none smtp.remote-ip=127.0.0.1\ +"""), ], # invalid version [ @@ -313,15 +418,14 @@ def test_milter_mode_none_sign(run_miltertest): 'example.com a; spf=pass', 'example.com 1 1; spf=pass', ], - 'arc=none smtp.remote-ip=127.0.0.1', + snapshot(' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'), ], ], ) -def test_milter_ar(run_miltertest, data): +def test_milter_ar(run_miltertest, data, result): """Test Authentication-Results parsing""" - res = run_miltertest([['Authentication-Results', x] for x in data[0]]) - - assert res['headers'][3] == ['ARC-Authentication-Results', f' i=1; example.com; {data[1]}'] + res = run_miltertest([['Authentication-Results', x] for x in data]) + assert res['headers'][3] == ['ARC-Authentication-Results', result] def test_milter_authrescomments(run_miltertest): @@ -334,10 +438,15 @@ def test_milter_authrescomments(run_miltertest): ] ] ) - assert res['headers'][3] == [ - 'ARC-Authentication-Results', - ' i=1; example.com; spf=pass smtp.mailfrom=foo@example.com;\n\tarc=none smtp.remote-ip=127.0.0.1', - ] + assert res['headers'][3] == snapshot( + [ + 'ARC-Authentication-Results', + """\ + i=1; example.com; spf=pass smtp.mailfrom=foo@example.com; + arc=none smtp.remote-ip=127.0.0.1\ +""", + ] + ) def test_milter_ar_override(run_miltertest): @@ -349,10 +458,20 @@ def test_milter_ar_override(run_miltertest): headers[0][1] = 'example.com; arc=fail' res = run_miltertest(headers) - - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=fail smtp.remote-ip=127.0.0.1'] - assert 'cv=fail' in res['headers'][1][1] - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=fail'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=fail smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=fail; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=fail'], + ] + ) # override the result to "pass" headers = [*res['headers'], *headers] @@ -360,8 +479,7 @@ def test_milter_ar_override(run_miltertest): res = run_miltertest(headers) # the chain is dead because it came in as failed, no matter what A-R says - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=fail smtp.remote-ip=127.0.0.1'] - assert len(res['headers']) == 1 + assert res['headers'] == snapshot([['Authentication-Results', ' example.com; arc=fail smtp.remote-ip=127.0.0.1']]) def test_milter_ar_override_disabled(run_miltertest): @@ -373,10 +491,20 @@ def test_milter_ar_override_disabled(run_miltertest): headers[0][1] = ' example.com; arc=fail' res = run_miltertest(headers) - - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] - assert 'cv=pass' in res['headers'][1][1] - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_ar_override_multi(run_miltertest): @@ -389,10 +517,20 @@ def test_milter_ar_override_multi(run_miltertest): *[x for x in res['headers'] if x[0] != 'Authentication-Results'], ] res = run_miltertest(headers) - - assert res['headers'][0] == ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] - assert 'cv=pass' in res['headers'][1][1] - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass'], + ] + ) def test_milter_seal_failed(run_miltertest): @@ -419,7 +557,20 @@ def test_milter_duplicate_header(run_miltertest): headers.append(headers[0]) res = run_miltertest(headers) - assert 'cv=fail' in res['headers'][1][1] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=fail smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=fail; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=fail smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_idna(run_miltertest): @@ -429,13 +580,36 @@ def test_milter_idna(run_miltertest): ['Authentication-Results', ' 시험.example.com; spf=pass smtp.mailfrom=привіт@시험.example.com'], ] ) - - assert res['headers'][1][1].startswith(' i=1; d=시험.example.com; s=예;') - assert res['headers'][2][1] == ' i=1; 시험.example.com; spf=pass smtp.mailfrom=привіт@시험.example.com;\n\tarc=none smtp.remote-ip=127.0.0.1' + assert res['headers'] == snapshot( + [ + ['ARC-Seal', IsStr(regex=r' i=1; d=시험\.example\.com; s=예; a=rsa-sha256; cv=none;\s+t=1234567890;\s+(?s:.+)')], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=시험\.example\.com; s=예; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + [ + 'ARC-Authentication-Results', + """ i=1; 시험.example.com; spf=pass smtp.mailfrom=привіт@시험.example.com; + arc=none smtp.remote-ip=127.0.0.1\ +""", + ], + ] + ) res = run_miltertest(res['headers']) - assert 'cv=pass' in res['headers'][0][1] - assert res['headers'][2] == ['ARC-Authentication-Results', ' i=2; 시험.example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=시험\.example\.com; s=예; a=rsa-sha256; cv=pass;\s+t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=시험\.example\.com; s=예; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; 시험.example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_oldest_pass(run_miltertest): @@ -448,20 +622,58 @@ def test_milter_oldest_pass(run_miltertest): # This doesn't have an oldest-pass because verification failed and was # overridden by A-R. In this situation we could try to parse it from A-R, # but currently that is not done. - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=2; example.com; arc=pass smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass smtp.remote-ip=127.0.0.1'], + ] + ) headers = [x for x in res['headers'] + headers if x[0] != 'Authentication-Results'] res = run_miltertest(headers, body='second test body\r\n') - assert res['headers'][3] == ['ARC-Authentication-Results', ' i=3; example.com; arc=pass header.oldest-pass=2 smtp.remote-ip=127.0.0.1'] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=2 smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=3; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=3; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=3; example.com; arc=pass header.oldest-pass=2 smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_authresip(run_miltertest): """AuthResIP false disables smtp.remote-ip""" res = run_miltertest() - assert res['headers'][0][1] == ' example.com; arc=none' - assert res['headers'][3][1] == ' i=1; example.com; arc=none' + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=none'], + [ + 'ARC-Seal', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=1; example.com; arc=none'], + ] + ) def test_milter_finalreceiver(run_miltertest): @@ -470,8 +682,20 @@ def test_milter_finalreceiver(run_miltertest): for i in range(0, 3): res = run_miltertest(headers) headers = [*res['headers'], *headers] - - assert res['headers'][0][1] == ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1 arc.chain="example.com:example.com"' + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1 arc.chain="example.com:example.com"'], + [ + 'ARC-Seal', + IsStr(regex=r' i=3; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=3; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=3; example.com; arc=pass smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_maximumheaders(run_miltertest): @@ -484,14 +708,38 @@ def test_milter_minimum_key_bits(run_miltertest): """A 2048-bit key passes when that is the minimum""" res = run_miltertest() res = run_miltertest(res['headers']) - assert 'cv=pass' in res['headers'][0][1] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_minimum_key_bits_fail(run_miltertest): """A 2048-bit key fails when the minimum is 2049""" res = run_miltertest() res = run_miltertest(res['headers']) - assert 'cv=fail' in res['headers'][0][1] + assert res['headers'] == snapshot( + [ + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=fail; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=fail smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_peerlist(run_miltertest): @@ -516,18 +764,70 @@ def test_milter_signaturettl(run_miltertest): """Setting a TTL tags AMS with x=""" ttl_res = run_miltertest() - assert 'x=1234567895' in ttl_res['headers'][2][1] + assert ttl_res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890; x=1234567895;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'], + ] + ) res = run_miltertest(ttl_res['headers'], milter_instance=1) - assert 'cv=pass' in res['headers'][1][1] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=pass; t=1234567895;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567895;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=pass header.oldest-pass=0 smtp.remote-ip=127.0.0.1'], + ] + ) res = run_miltertest(ttl_res['headers'], milter_instance=2) - assert 'cv=fail' in res['headers'][1][1] + assert res['headers'] == snapshot( + [ + ['Authentication-Results', ' example.com; arc=fail smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=fail; t=1234567896;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=2; d=example\.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567896;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=2; example.com; arc=fail smtp.remote-ip=127.0.0.1'], + ] + ) def test_milter_softwareheader(run_miltertest): """Advertise software name, version""" res = run_miltertest() - assert res['headers'][0][0] == 'ARC-Filter' - assert res['headers'][0][1].startswith(' OpenARC Filter v') + assert res['headers'] == snapshot( + [ + ['ARC-Filter', IsStr(regex=r' OpenARC Filter v[0-9a-z\.]+ unknown-host \(unknown-jobid\)')], + ['Authentication-Results', ' example.com; arc=none smtp.remote-ip=127.0.0.1'], + [ + 'ARC-Seal', + IsStr(regex=r' i=1; d=example\.com; s=elpmaxe; a=rsa-sha256; cv=none; t=1234567890;\s+(?s:.+)'), + ], + [ + 'ARC-Message-Signature', + IsStr(regex=r' i=1; d=example.com; s=elpmaxe; a=rsa-sha256;\s+c=relaxed/simple; t=1234567890;\s+h=From:Date:Subject;\s+(?s:.+)'), + ], + ['ARC-Authentication-Results', ' i=1; example.com; arc=none smtp.remote-ip=127.0.0.1'], + ] + )