Record outbound relay and LMTP live tests
This commit is contained in:
@@ -60,7 +60,9 @@ one-off files in `/tmp`:
|
||||
envelope plus an unsigned, non-SRS internal LMTP message while an
|
||||
unreachable global relayhost is enabled. This proves that internal LMTP
|
||||
routing takes precedence, after which the test restores the exact SMTP
|
||||
configuration and active service.
|
||||
configuration and active service. The fixture itself is unprivileged; the
|
||||
script uses sudo only for the already-authorized `bongo-admin` and
|
||||
`systemctl` operations.
|
||||
- `smtp-client-interoperability-check.py` repeats real delivery with curl,
|
||||
swaks, Xeams Email Sender, GNU Mailutils, and paced Telnet input. It covers
|
||||
authenticated STARTTLS submission plus unauthenticated local delivery on
|
||||
|
||||
@@ -52,6 +52,12 @@ def load_helpers():
|
||||
SMTP07 = load_helpers()
|
||||
|
||||
|
||||
def privileged(arguments: list[str]) -> list[str]:
|
||||
if os.geteuid() == 0:
|
||||
return arguments
|
||||
return ["/usr/bin/sudo", "-n", *arguments]
|
||||
|
||||
|
||||
@dataclass
|
||||
class LMTPCapture:
|
||||
lock: threading.Lock = field(default_factory=threading.Lock)
|
||||
@@ -167,7 +173,9 @@ class ThreadingLMTPServer(
|
||||
|
||||
|
||||
def read_configuration(name: str) -> dict:
|
||||
completed = SMTP07.run([SMTP07.ADMIN, "__config-read", name])
|
||||
completed = SMTP07.run(
|
||||
privileged([SMTP07.ADMIN, "__config-read", name])
|
||||
)
|
||||
try:
|
||||
value = json.loads(completed.stdout)
|
||||
except json.JSONDecodeError as error:
|
||||
@@ -181,11 +189,26 @@ def read_configuration(name: str) -> dict:
|
||||
|
||||
def replace_configuration(name: str, value: dict) -> None:
|
||||
SMTP07.run(
|
||||
[SMTP07.ADMIN, "__config-replace", name],
|
||||
privileged([SMTP07.ADMIN, "__config-replace", name]),
|
||||
input_text=json.dumps(value, separators=(",", ":")),
|
||||
)
|
||||
|
||||
|
||||
def service_active() -> bool:
|
||||
return SMTP07.run(
|
||||
privileged(
|
||||
[SMTP07.SYSTEMCTL, "is-active", "--quiet", "bongo.service"]
|
||||
),
|
||||
check=False,
|
||||
).returncode == 0
|
||||
|
||||
|
||||
def restart_bongo() -> None:
|
||||
SMTP07.run(
|
||||
privileged([SMTP07.SYSTEMCTL, "restart", "bongo.service"])
|
||||
)
|
||||
|
||||
|
||||
def submit(host: str, port: int) -> None:
|
||||
message = (
|
||||
f"From: Mailman return <{SENDER}>\r\n"
|
||||
@@ -254,9 +277,7 @@ def validate(delivery: tuple[bytes, bytes, bytes]) -> None:
|
||||
|
||||
|
||||
def perform_test() -> None:
|
||||
if os.geteuid() != 0:
|
||||
raise SMTP20Error("the live LMTP test must run as root")
|
||||
originally_active = SMTP07.service_active()
|
||||
originally_active = service_active()
|
||||
if not originally_active:
|
||||
raise SMTP20Error("bongo.service must be active before this test")
|
||||
|
||||
@@ -305,7 +326,7 @@ def perform_test() -> None:
|
||||
try:
|
||||
replace_configuration("smtp", test_smtp)
|
||||
configuration_changed = True
|
||||
SMTP07.run([SMTP07.SYSTEMCTL, "restart", "bongo.service"])
|
||||
restart_bongo()
|
||||
SMTP07.INTERNAL_HOST = internal_host
|
||||
SMTP07.INTERNAL_PORT = internal_port
|
||||
SMTP07.wait_for_bongo()
|
||||
@@ -331,7 +352,7 @@ def perform_test() -> None:
|
||||
restoration_errors.append(f"LMTP fixture shutdown failed: {error}")
|
||||
if originally_active:
|
||||
try:
|
||||
SMTP07.run([SMTP07.SYSTEMCTL, "restart", "bongo.service"])
|
||||
restart_bongo()
|
||||
SMTP07.INTERNAL_HOST = internal_host
|
||||
SMTP07.INTERNAL_PORT = internal_port
|
||||
SMTP07.wait_for_bongo()
|
||||
|
||||
@@ -148,7 +148,7 @@ inputs and outputs is absent from the ZIP.
|
||||
| SMTP-26 | IDNA2008 domains use one canonical A-label in config, aliases, DNS lookup, SMTP and Web compose | | | |
|
||||
| SMTP-18 | Recipient validation, multiple recipients, partial refusal, aliases | | | |
|
||||
| SMTP-19 | Domain/address aliases, forwarding loops, and SRS forwarding | | | |
|
||||
| SMTP-20 | Trusted internal SMTP to disposable LMTP preserves the envelope and adds neither DKIM nor SRS | | | |
|
||||
| SMTP-20 | Trusted internal SMTP to disposable LMTP preserves the envelope and adds neither DKIM nor SRS | [PASS](test-evidence/0.7-r1.md#smtp-20) | | |
|
||||
| SMTP-21 | LMTP per-recipient 2xx, 4xx, 5xx, retry, and duplicate prevention | | | |
|
||||
| SMTP-22 | XCLIENT/XFORWARD negotiation only to trusted destinations | | | |
|
||||
| SMTP-23 | Fallback client-address header only for configured trusted peers | | | |
|
||||
|
||||
@@ -2452,22 +2452,46 @@ This models mail returned from Mailman over the trusted internal SMTP
|
||||
listener: internal ingress is not signed merely on receipt, but the message
|
||||
is signed before it leaves Bongo for an external relay or recipient.
|
||||
|
||||
The recovery phase deliberately installs an incorrect relay password,
|
||||
verifies that the message remains in Queue 7 and is not accepted by the
|
||||
relay, repairs the secret, explicitly retries the retained entry, and
|
||||
requires successful signed delivery. Commit `8f0c1518` makes this
|
||||
administrative retry reliable by reserving the queue entry before `QRUN`
|
||||
acknowledges it; a concurrent attempt is now reported as busy instead of
|
||||
silently losing the retry.
|
||||
The two recovery phases first select a bound but non-listening loopback port
|
||||
and then install an incorrect relay password. In both cases the test verifies
|
||||
that the message remains in Queue 7 and is not accepted by the relay. It then
|
||||
repairs the endpoint or secret, retries the retained entry, and requires
|
||||
successful signed delivery. Commit `8f0c1518` makes this administrative retry
|
||||
reliable by reserving the queue entry before `QRUN` acknowledges it; a
|
||||
concurrent attempt is now reported as busy instead of silently losing the
|
||||
retry.
|
||||
|
||||
```text
|
||||
SMTP-09 PASS hostname=localhost ports=12525/12465 starttls=no-auth implicit-tls=auth failure=retained recovery=delivered trusted-port26=dkim-signed dkim=verified queue-clean=yes config-restored=yes
|
||||
SMTP-09 PASS hostname=localhost ports=12525/12465 starttls=no-auth implicit-tls=auth unreachable=retained/recovered auth-failure=retained/recovered trusted-port26=dkim-signed dkim=verified queue-clean=yes config-restored=yes
|
||||
```
|
||||
|
||||
The test restored the exact SMTP Store document, removed its queue entries,
|
||||
password and CA files, and returned `bongo.service` to its original active
|
||||
state.
|
||||
|
||||
## SMTP-20
|
||||
|
||||
Result: **PASS**
|
||||
|
||||
The reusable `smtp-lmtp-delivery-check.py` starts a loopback-only,
|
||||
status-aware LMTP fixture on an unprivileged port. It adds one temporary
|
||||
transport-domain mapping, enables an intentionally unreachable global
|
||||
relayhost, and submits a Mailman-style message through trusted port 26.
|
||||
|
||||
The installed Bongo client selected the internal LMTP transport before the
|
||||
global relayhost, preserved the original envelope sender and recipient, and
|
||||
delivered exactly one message. The captured internal copy had neither a
|
||||
`DKIM-Signature` nor an SRS envelope. This proves both the internal-delivery
|
||||
exception and that enabling a global relayhost cannot redirect LMTP traffic
|
||||
outside Bongo.
|
||||
|
||||
```text
|
||||
SMTP-20 PASS ingress=trusted-internal-smtp transport=lmtp relayhost=bypassed envelope=original dkim=absent srs=absent duplicate=no queue-clean=yes config-restored=yes service-restored=active
|
||||
```
|
||||
|
||||
The test removed its marked Queue data, stopped the fixture, restored the
|
||||
exact SMTP Store document, and returned `bongo.service` to the active state.
|
||||
|
||||
## SMTP-27
|
||||
|
||||
Result: **PASS**
|
||||
|
||||
Reference in New Issue
Block a user