SMTP Mail Guard (smtp)
The SMTP Mail Guard plugin inspects SMTP and ESMTP mail transfer streams (RFC 5321). It enforces recipient count limits per session, blocks disposable or blacklisted sender domains on MAIL FROM, intercepts authentication failures (535 Authentication credentials invalid), and provides transparent handover upon STARTTLS negotiation.
Capabilities & Threat Defense
| Threat / Attack Vector | Defense Mechanism | Action Taken |
|---|---|---|
| Credential Brute-Force & Stuffing | Tracks 535 Authentication failed response codes; enforces connection close with 535 Too many failures on excess attempts (since v3.4.0) | Abusive IP banned after max_auth_failures threshold; connection terminated immediately |
| Recipient Flooding / Directory Harvest | Counts RCPT TO commands per session | Rejects excess recipients with 452 Too many recipients |
| Spam & Disposable Senders | Checks MAIL FROM domain against blocked_sender_domains | Rejects sender with 554 Sender domain rejected |
| STARTTLS Handover | Detects successful 220 Ready to start TLS | Transparently transitions to bidirectional TLS pipe |
Installation
Install the plugin via CLI or declaratively:
# Install using short nametcp-warden plugins install smtp# Or install via Git repository URLtcp-warden plugins install https://github.com/routewarden/plugins/smtpConfiguration Reference
Add an SMTP guard service to tcp-warden.yaml:
services: smtp_guard: listen: ":2525" upstream: "127.0.0.1:25" protocol: "smtp" rate_limit: connections_per_minute: 30 burst: 10 max_auth_failures: 3 ban_after_failures: 3 ban_duration: "2h" plugin_config: banner: "220 mail.example.com RouteWarden ESMTP Ready" max_recipients: 10 blocked_sender_domains: - "*.tempmail.com" - "mailinator.com" - "spam.org" require_starttls: falseConfiguration Options
| Field | Type | Default | Description |
|---|---|---|---|
listen | string | ":2525" | Local proxy listen address and port. |
upstream | string | "127.0.0.1:25" | Target MTA (Postfix, Exim, Haraka) address and port. |
protocol | string | "smtp" | Must be set to "smtp" or "mail". |
max_auth_failures | int | 3 | Failed AUTH exchanges before the client IP is banned. Since v3.4.0 the connection is actively closed with a 535 Too many failures response when the cap is reached, preventing clients from retrying indefinitely. |
ban_duration | string | "2h" | Duration of the automated IP ban ("30m", "2h", "24h"). |
plugin_config.banner | string | "" | Optional synthetic greeting banner returned to clients. |
plugin_config.max_recipients | int | 10 | Max RCPT TO addresses allowed per single SMTP transaction. |
plugin_config.blocked_sender_domains | []string | [...] | List of sender domains to reject with 554. Supports wildcards. |
Network & Deployment Architecture
Option A: Kernel Firewall Redirection (nftables / iptables)
Keep Postfix listening on port 25 on loopback, and redirect incoming external connections from interface eth0:
# Redirect incoming external port 25 traffic to RouteWarden port 2525sudo nft add rule ip routewarden_nat prerouting iifname "eth0" tcp dport 25 redirect to :2525Option B: Submission Port Protection (Port 587)
RouteWarden can also protect client submission ports (port 587):
# Optional: Protecting submission port 587services: smtp_submission: listen: ":5870" upstream: "127.0.0.1:587" protocol: "smtp" max_auth_failures: 3 ban_after_failures: 3Option C: Docker Compose Network Isolation
Run the mail transfer agent inside an isolated container network, exposing both SMTP (25) and Submission (587) services only through RouteWarden:
services: routewarden: image: ghcr.io/routewarden/tcp-warden:latest ports: - "25:2525" # Map host SMTP port 25 to RouteWarden - "587:5870" # Map host Submission port 587 to RouteWarden - "9091:9091" # Management API volumes: - ./tcp-warden.yaml:/etc/routewarden/tcp-warden.yaml:ro depends_on: - mailserver mailserver: image: mailserver/docker-mailserver:latest environment: - OVERRIDE_HOSTNAME=mail.example.com # Do NOT publish host ports (25/587); keep internal to bridge networkTesting & Verification
Verify session commands, test domain rejection, and test recipient throttling:
# Connect to SMTP service via netcat$ nc 127.0.0.1 2525220 mail.example.com ESMTP RouteWardenEHLO client.test250-mail.example.com250-8BITMIME250 OKMAIL FROM:<spammer@tempmail.com>554 5.7.1 Sender domain rejected by RouteWardenQUIT221 2.0.0 Bye