Restriction chains ([restrictions]): lists of checks run at each
stage of an SMTP session.
Each stage's list runs in order until a check decides: permit ends
the list, a rejection ends the session's request. Restrictions only add
checks: relay control and recipient validation always apply, so no
restriction can make Sovite an open relay.
Checks
| Check | Stages | Effect |
|---|---|---|
permit, reject, defer | all | Accept, 554 5.7.1, or 450 4.7.1. |
permit_trusted | all | Accept clients in smtp.trusted_networks. |
permit_authenticated | all | Accept clients that logged in. |
client_access | all | The access rules for the client IP address. |
helo_access | from helo | The access rules for the EHLO name. |
sender_access | from mail | The access rules for the sender address. |
recipient_access | rcpt | The access rules for the recipient address. |
require_fqdn_helo | from helo | 504 unless the name has a dot or is an address literal. |
require_fqdn_sender / require_fqdn_recipient | from mail / rcpt | 504 unless the domain has a dot. |
require_known_sender_domain / require_known_recipient_domain | from mail / rcpt | 550 if the domain has no MX or address records, or a Null MX; 450 when DNS fails. |
require_reverse_hostname | all | 550 5.7.25 unless the client address has a reverse DNS (PTR) name. |
require_fcrdns | all | 550 5.7.25 unless that name also resolves back to the client address (forward-confirmed reverse DNS). Stricter. |
reject_forged_helo | from helo | 550 if the EHLO name is this server's host name or one of its local domains, localhost, or an address literal other than the client's address. |
require_known_helo | from helo | 550 unless the EHLO name has address or MX records. Address literals pass. |
require_matching_helo | from helo | 550 unless the EHLO name resolves to the client address, or is its address literal. Strictest. |
check_policy_service ADDRESS | all | Ask a Postfix policy server, see Sovite.Core.PolicyService. |
The DNS checks reply 450 (4.7.25 or 4.7.1) when DNS fails, so a
client is never refused for a temporary problem.
Checks whose information is not known yet are skipped: a helo_access
in the mail stage of a client that sent no EHLO does nothing.
Access rules
Access rules live in the database (sovitectl access). A rule matches
a pattern and has an action:
ACCEPT- accept, ending the list.CONTINUE- as if no rule matched: go on with the next check.REJECT [text]-554 5.7.1.DEFER [text]-450 4.7.1.4NN [x.y.z] text/5NN [x.y.z] text- that reply.DISCARD [text]- accept, then silently drop the message. Ends the list.HOLD [text]- accept, and put the message in the hold queue.WARN text- log, and go on.
Patterns tried, in order:
- client: the IP address, then for IPv4 the networks
192.0.2,192.0,192. EHLOnames and domains: the name, then each parent domain as.example.com(subdomains only) andexample.com(the domain and its subdomains).- addresses: the address, the address without its extension
(
routing.extension_delimiter), the domain patterns as above, thenuser@(any domain). The null sender is<>.
Summary
Types
What a chain looks at
What policy servers added to the message, see Sovite.Core.PolicyService.
The verdict of a chain.
Functions
Whether check name can run at stage.
Like run/3, and also returns what policy servers added to the
message, in order.
Checks a restriction name. Returns it, or an error message.
Whether any of the chains (a map of stage to checks) looks at the client's reverse DNS, so it is worth looking up once per session.
Runs checks for stage. WARN results are reported with telemetry
[:sovite, :restrictions, :warn] (%{stage, check, text}).
The stages, in session order.
Types
@type context() :: map()
What a chain looks at:
:client_ip,:helo,:sender,:recipient-nilwhen not known yet.:trusted,:authenticated- booleans.:access- the access rule tables (Sovite.Core.Lookup.tables()) by kind::client,:helo,:sender,:recipient.:resolver- for the DNS checks.:delimiter- the extension delimiter characters.:own_names- this server's host name and local domains, lower-cased, forreject_forged_helo.:client_dns- the result ofSovite.Abuse.ReverseDNS.check/2for the client, if already known. Looked up when needed otherwise.:policy,:policy_request,:esmtp- forcheck_policy_service, seeSovite.Core.PolicyService.check/3.
@type effect() :: Sovite.Core.PolicyService.effect()
What policy servers added to the message, see Sovite.Core.PolicyService.
@type verdict() :: :ok | {:reject, Sovite.SMTP.Reply.t()} | {:discard, String.t()} | {:hold, String.t()}
The verdict of a chain.
Functions
Whether check name can run at stage.
Like run/3, and also returns what policy servers added to the
message, in order.
Checks a restriction name. Returns it, or an error message.
Whether any of the chains (a map of stage to checks) looks at the client's reverse DNS, so it is worth looking up once per session.
Runs checks for stage. WARN results are reported with telemetry
[:sovite, :restrictions, :warn] (%{stage, check, text}).
@spec stages() :: [atom()]
The stages, in session order.