From 5631d09aa821fe6b5087c93771c8d04b5a809663 Mon Sep 17 00:00:00 2001 From: anti Date: Tue, 14 Apr 2026 00:30:46 -0400 Subject: [PATCH] fix: reject empty HELO/EHLO with 501 per RFC 5321 EHLO/HELO require a domain or address-literal argument. Previously the server accepted bare EHLO with no argument and responded 250, which deviates from the spec and makes the honeypot easier to fingerprint. --- templates/smtp/server.py | 5 +++++ tests/service_testing/test_smtp.py | 14 ++++++++++++++ 2 files changed, 19 insertions(+) diff --git a/templates/smtp/server.py b/templates/smtp/server.py index 7b22181..8bf21a3 100644 --- a/templates/smtp/server.py +++ b/templates/smtp/server.py @@ -142,6 +142,11 @@ class SMTPProtocol(asyncio.Protocol): args = parts[1] if len(parts) > 1 else "" if cmd in ("EHLO", "HELO"): + if not args: + self._transport.write( + f"501 5.5.4 Syntax: {cmd} hostname\r\n".encode() + ) + return _log("ehlo", src=self._peer[0], domain=args) self._transport.write( f"250-{_SMTP_MTA}\r\n" diff --git a/tests/service_testing/test_smtp.py b/tests/service_testing/test_smtp.py index b4005e3..64051b9 100644 --- a/tests/service_testing/test_smtp.py +++ b/tests/service_testing/test_smtp.py @@ -114,6 +114,20 @@ def test_ehlo_returns_250_multiline(relay_mod): assert "PIPELINING" in combined +def test_ehlo_empty_domain_rejected(relay_mod): + proto, _, written = _make_protocol(relay_mod) + _send(proto, "EHLO") + replies = _replies(written) + assert any(r.startswith("501") for r in replies) + + +def test_helo_empty_domain_rejected(relay_mod): + proto, _, written = _make_protocol(relay_mod) + _send(proto, "HELO") + replies = _replies(written) + assert any(r.startswith("501") for r in replies) + + # ── OPEN RELAY MODE ─────────────────────────────────────────────────────────── class TestOpenRelay: