Sovite.Core.Restrictions (sovite v0.2.0)

Copy Markdown View Source

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

CheckStagesEffect
permit, reject, deferallAccept, 554 5.7.1, or 450 4.7.1.
permit_trustedallAccept clients in smtp.trusted_networks.
permit_authenticatedallAccept clients that logged in.
client_accessallThe access rules for the client IP address.
helo_accessfrom heloThe access rules for the EHLO name.
sender_accessfrom mailThe access rules for the sender address.
recipient_accessrcptThe access rules for the recipient address.
require_fqdn_helofrom helo504 unless the name has a dot or is an address literal.
require_fqdn_sender / require_fqdn_recipientfrom mail / rcpt504 unless the domain has a dot.
require_known_sender_domain / require_known_recipient_domainfrom mail / rcpt550 if the domain has no MX or address records, or a Null MX; 450 when DNS fails.

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.
  • EHLO names and domains: the name, then each parent domain as .example.com (subdomains only) and example.com (the domain and its subdomains).
  • addresses: the address, the address without its extension (routing.extension_delimiter), the domain patterns as above, then user@ (any domain). The null sender is <>.

Summary

Types

What a chain looks at

The verdict of a chain.

Functions

Whether check name can run at stage.

Checks a restriction name. Returns it, or an error message.

Runs checks for stage. WARN results are reported with telemetry [:sovite, :restrictions, :warn] (%{stage, check, text}).

The stages, in session order.

Types

context()

@type context() :: map()

What a chain looks at:

  • :client_ip, :helo, :sender, :recipient - nil when not known yet.
  • :trusted, :authenticated - booleans.
  • :access - the access rule tables (Sovite.Core.Lookup.tables()) by kind: :client, :helo, :sender, :recipient.
  • :resolver - for the known-domain checks.
  • :delimiter - the extension delimiter characters.

verdict()

@type verdict() ::
  :ok
  | {:reject, Sovite.SMTP.Reply.t()}
  | {:discard, String.t()}
  | {:hold, String.t()}

The verdict of a chain.

Functions

allowed?(name, stage)

@spec allowed?(String.t(), atom()) :: boolean()

Whether check name can run at stage.

parse(name)

@spec parse(String.t()) :: {:ok, String.t()} | {:error, String.t()}

Checks a restriction name. Returns it, or an error message.

run(checks, stage, context)

@spec run([String.t()], atom(), context()) :: verdict()

Runs checks for stage. WARN results are reported with telemetry [:sovite, :restrictions, :warn] (%{stage, check, text}).

stages()

@spec stages() :: [atom()]

The stages, in session order.