Test byte-exact SMTP 8BITMIME delivery

This commit is contained in:
Mario Fetka
2026-07-27 13:49:24 +02:00
parent 222ea4b213
commit 2a8eb4f357
4 changed files with 208 additions and 1 deletions
+5
View File
@@ -36,6 +36,11 @@ one-off files in `/tmp`:
command, and commands already buffered after the message terminator. The
accepted message must reach Store exactly once and all marked fixtures are
removed afterward.
- `smtp-8bitmime-check.py` requires `8BITMIME` after EHLO, rejects its use
after HELO and an invalid BODY value, then submits an ISO-8859-1 message
containing deliberately non-UTF-8 high octets and a dot-stuffed line. The
bytes following Bongo's added transport headers must match the submitted
message exactly in Store.
- `smtp-submission-auth-check.py` exercises port 587 STARTTLS and port 465
implicit TLS directly, including the shared GSASL PLAIN and LOGIN
mechanisms, pre-TLS AUTH rejection, invalid credentials, and repeated
+166
View File
@@ -0,0 +1,166 @@
#!/usr/bin/env python3
# This program is free software, licensed under the terms of the GNU GPL.
# See the Bongo COPYING file for full details.
# Copyright (c) 2026 Bongo Project contributors
"""Verify byte-exact inbound SMTP 8BITMIME delivery."""
from __future__ import annotations
import importlib.util
import os
from pathlib import Path
import sys
import time
TOKEN = f"smtp13-{os.getpid()}-{int(time.time())}"
TOKEN_PREFIX = b"smtp13-"
TIMEOUT = float(os.environ.get("BONGO_TEST_TIMEOUT", "30"))
ALLOW_LIVE = os.environ.get("BONGO_ALLOW_LIVE_SMTP_TEST") == "1"
class SMTP13Error(RuntimeError):
"""Raised when an 8BITMIME transaction changes message bytes."""
def load_helpers():
path = Path(__file__).with_name("smtp-pipelining-check.py")
specification = importlib.util.spec_from_file_location(
"bongo_smtp13_helpers", path
)
if specification is None or specification.loader is None:
raise SMTP13Error(f"cannot load SMTP test helpers from {path}")
module = importlib.util.module_from_spec(specification)
sys.modules[specification.name] = module
specification.loader.exec_module(module)
module.TOKEN_PREFIX = TOKEN_PREFIX
return module
HELPERS = load_helpers()
def desired_message() -> bytes:
headers = (
f"From: SMTP-13 <sender@example.test>\r\n"
f"To: {HELPERS.USER}@{HELPERS.DOMAIN}\r\n"
f"Subject: SMTP-13 byte preservation {TOKEN}\r\n"
f"Message-ID: <{TOKEN}@{HELPERS.DOMAIN}>\r\n"
f"X-Bongo-Test: {TOKEN}\r\n"
"MIME-Version: 1.0\r\n"
"Content-Type: text/plain; charset=iso-8859-1\r\n"
"Content-Transfer-Encoding: 8bit\r\n"
"\r\n"
).encode("ascii")
body = (
b"Gr\xfc\xdfe aus dem 8-Bit-Test.\r\n"
b".Diese Zeile beginnt mit einem Punkt.\r\n"
b"Alle hohen Oktette bleiben erhalten: \x80\xa0\xc4\xe4\xfe\xff\r\n"
)
return headers + body
def dot_stuff(message: bytes) -> bytes:
if message.startswith(b"."):
message = b"." + message
return message.replace(b"\r\n.", b"\r\n..")
def wait_for_message(store) -> tuple[str, bytes]:
deadline = time.monotonic() + TIMEOUT
while time.monotonic() < deadline:
identifiers = HELPERS.store_token_documents(store)
if len(identifiers) > 1:
raise SMTP13Error(
f"8BITMIME message delivered more than once: {identifiers!r}"
)
if identifiers:
return identifiers[0], store.Read(identifiers[0])
time.sleep(0.25)
raise SMTP13Error("8BITMIME message did not reach the Store")
def main() -> int:
if not ALLOW_LIVE:
raise SMTP13Error(
"set BONGO_ALLOW_LIVE_SMTP_TEST=1 for the live delivery test"
)
if not HELPERS.PASSWORD:
raise SMTP13Error("BONGO_TEST_PASSWORD is required")
expected = desired_message()
store = HELPERS.open_store()
try:
HELPERS.cleanup(store)
client = HELPERS.SMTPConnection()
try:
client.require(220, "greeting")
client.command("HELO smtp13-client.example", 250)
client.command(
"MAIL FROM:<sender@example.test> BODY=8BITMIME", 555
)
capabilities = client.command(
"EHLO smtp13-client.example", 250
)
if b"8BITMIME" not in capabilities:
raise SMTP13Error(
f"EHLO did not advertise 8BITMIME: {capabilities!r}"
)
client.command(
"MAIL FROM:<sender@example.test> BODY=8BIT", 555
)
client.command(
"MAIL FROM:<sender@example.test> BODY=8BITMIME", 250
)
client.command(
f"RCPT TO:<{HELPERS.USER}@{HELPERS.DOMAIN}>", 250
)
client.command("DATA", 354)
client.socket.sendall(dot_stuff(expected) + b".\r\n")
client.require(250, "8BITMIME DATA completion")
client.command("QUIT", 221)
finally:
client.close()
identifier, stored = wait_for_message(store)
if not stored.endswith(expected):
common = 0
maximum = min(len(stored), len(expected))
while common < maximum and stored[-maximum + common] == expected[common]:
common += 1
raise SMTP13Error(
"stored message does not end in the exact submitted bytes "
f"(stored={len(stored)}, submitted={len(expected)}, "
f"matching-prefix={common})"
)
if b"\x80\xa0\xc4\xe4\xfe\xff\r\n" not in stored:
raise SMTP13Error("stored message omitted high-bit octets")
if b"\r\n.Diese Zeile" not in stored:
raise SMTP13Error("SMTP dot transparency changed the leading dot")
store.Delete(identifier)
HELPERS.cleanup(store)
if HELPERS.store_token_documents(store) or HELPERS.queue_token_ids():
raise SMTP13Error("test content remains after cleanup")
finally:
try:
HELPERS.cleanup(store)
finally:
store.Quit()
print(
"SMTP-13 PASS advertised=yes helo-parameter=555 "
"invalid-body=555 body-8bitmime=250 high-octets=exact "
"dot-transparency=exact duplicate=no queue-clean=yes store-clean=yes"
)
return 0
if __name__ == "__main__":
try:
raise SystemExit(main())
except (OSError, SMTP13Error, ValueError) as error:
print(f"SMTP-13 FAIL: {type(error).__name__}: {error}")
raise SystemExit(1)
+1 -1
View File
@@ -140,7 +140,7 @@ inputs and outputs is absent from the ZIP.
| SMTP-10 | Open-relay tests reject unauthenticated/untrusted third-party relay | [PASS](test-evidence/0.7-r1.md#smtp-10) | | |
| SMTP-11 | `SIZE` exact limit, over-limit, absent size, and disk-space responses | [PASS](test-evidence/0.7-r1.md#smtp-11) | | |
| SMTP-12 | `PIPELINING` ordering and error recovery | [PASS](test-evidence/0.7-r1.md#smtp-12) | | |
| SMTP-13 | `8BITMIME` remains byte-correct | | | |
| SMTP-13 | `8BITMIME` remains byte-correct | [PASS](test-evidence/0.7-r1.md#smtp-13) | | |
| SMTP-14 | DSN `RET`, `ENVID`, `NOTIFY`, and `ORCPT` cases | | | |
| SMTP-15 | `CHUNKING`/bounded `BDAT`, including zero/last and malformed chunks | | | |
| SMTP-16 | `BINARYMIME` remains unadvertised and rejected | | | |
+36
View File
@@ -2603,6 +2603,42 @@ No configuration or service state changed. The fixture removed the delivered
Store object and any uniquely marked interrupted-run entries; the final Queue
and Store checks were empty.
## SMTP-13
Result: **PASS**
The reusable `smtp-8bitmime-check.py` first used classic HELO and verified
that the ESMTP-only `BODY=8BITMIME` parameter received `555`. After EHLO it
required the advertised `8BITMIME` capability, rejected the invalid
`BODY=8BIT` value with `555`, and accepted `BODY=8BITMIME`.
The test message used ASCII headers declaring `charset=iso-8859-1` and
`Content-Transfer-Encoding: 8bit`. Its body deliberately contained the raw
octet sequence `80 a0 c4 e4 fe ff`, including bytes which are not a valid
standalone UTF-8 sequence. Another body line began with a dot and was
dot-stuffed only on the SMTP wire.
After local Queue-to-Store delivery, the fixture allowed Bongo's expected
transport headers before the submitted message but required every submitted
byte from `From:` through the final CRLF to be an exact suffix of the stored
object. The high-octet sequence was unchanged and dot transparency produced
exactly one leading dot.
```sh
export BONGO_ALLOW_LIVE_SMTP_TEST=1
export BONGO_TEST_PASSWORD='the-disposable-test-password'
./contrib/testing/smtp-8bitmime-check.py
```
The installed `d782d593` debug build reported on 2026-07-27:
```text
SMTP-13 PASS advertised=yes helo-parameter=555 invalid-body=555 body-8bitmime=250 high-octets=exact dot-transparency=exact duplicate=no queue-clean=yes store-clean=yes
```
No configuration or service state changed. The delivered Store object and
all uniquely marked interrupted-run Queue or Store fixtures were removed.
## SMTP-20
Result: **PASS**