Sovite.Core.Milters (sovite v0.6.0)

Copy Markdown View Source

The mail filters ([[milter]]) of an SMTP session, such as Rspamd, OpenDKIM, OpenDMARC, or ClamAV-milter, run with Sovite.Milter as Postfix runs smtpd_milters.

Each milter of the listener (listener.milters, by default all of them except on LMTP and re-injection listeners) gets its own connection when the client connects, and sees every step of the session: the client, EHLO, MAIL, each RCPT, then the header fields and body of the message as they arrive. Milters run in order; the first that rejects decides. Each sees the message as received.

Replies:

  • reject: 550 5.7.1 Command rejected; temporary failure: 451 4.7.1 Service unavailable - try again later; or the milter's own reply. At RCPT, for that recipient only.
  • discard: the message is accepted and dropped.
  • quarantine: the message goes to the hold queue.
  • shutdown: 421 and the session closes.

At the end of the message, the milters' changes are applied in order: header fields added, inserted, changed, or deleted, recipients added or removed, the sender changed, and the body replaced. The message is then written again (Sovite.Queue.Spool.replace/3).

A milter that cannot be reached, times out, or breaks the protocol gets its default_action: tempfail (the default) answers every later command of the session with 451 4.7.1, reject with 550 5.7.1, and accept goes on without it.

Macros

Milters get the macros Postfix sends by default: j, {daemon_name}, {daemon_addr}, v, _, {client_addr}, {client_name}, {client_port}, {client_ptr} at connect; {tls_version}, {cipher}, {cipher_bits} at EHLO; i, {auth_type}, {auth_authen}, {mail_addr}, {mail_host}, {mail_mailer} at MAIL; {rcpt_addr}, {rcpt_host}, {rcpt_mailer} at RCPT; and i with the data, the end of the header, and the end of the message. i is the queue ID.

Summary

Types

What the SMTP handler knows, for the macros: :hostname, :connection, :client_dns, :tls, :identity, :mechanism, :queue_id.

One milter of the session.

The outcome of a step.

t()

The milters of a session: nil when the listener has none.

The outcome of the end of the message: what to do with it, and the changes to make.

Functions

The transaction ended without a message (RSET, or an aborted message): the milters forget it.

A part of the body.

The session ended.

Connects to the milters and tells them about the client. Returns {:close, reply, milters} when one refuses it.

The end of the message. Returns the verdict and the changes the milters asked for, in order.

The header fields of the message (Sovite.Message.Headers fields, Received: first), then the end of the header.

The [[milter]] sections a listener uses, by name, in order.

RCPT TO. A rejection is about this recipient only.

Types

info()

@type info() :: map()

What the SMTP handler knows, for the macros: :hostname, :connection, :client_dns, :tls, :identity, :mechanism, :queue_id.

milter()

@type milter() :: %{
  config: map(),
  client: Sovite.Milter.t() | nil,
  failed: nil | :tempfail | :reject
}

One milter of the session.

result()

@type result() ::
  {:ok, t()}
  | {:reply, Sovite.SMTP.Reply.t(), t()}
  | {:close, Sovite.SMTP.Reply.t(), t()}

The outcome of a step.

t()

@type t() :: [milter()] | nil

The milters of a session: nil when the listener has none.

verdict()

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

The outcome of the end of the message: what to do with it, and the changes to make.

Functions

abort(milters)

@spec abort(t()) :: t()

The transaction ended without a message (RSET, or an aborted message): the milters forget it.

body(milters, chunk)

@spec body(t(), iodata()) :: result()

A part of the body.

close(milters)

@spec close(t()) :: :ok

The session ended.

connect(configs, info)

@spec connect([map()], info()) :: result()

Connects to the milters and tells them about the client. Returns {:close, reply, milters} when one refuses it.

data(milters, info)

@spec data(t(), info()) :: result()

DATA.

end_of_message(milters, info)

@spec end_of_message(t(), info()) :: {verdict(), [Sovite.Milter.modification()], t()}

The end of the message. Returns the verdict and the changes the milters asked for, in order.

header(milters, fields, info)

@spec header(t(), [Sovite.Message.Headers.field()], info()) :: result()

The header fields of the message (Sovite.Message.Headers fields, Received: first), then the end of the header.

helo(milters, name, info)

@spec helo(t(), String.t(), info()) :: result()

EHLO or HELO.

mail(milters, sender, params, info)

@spec mail(t(), String.t(), map(), info()) :: result()

MAIL FROM.

opts(config, names)

@spec opts(Sovite.Core.Config.t(), [String.t()]) :: [map()]

The [[milter]] sections a listener uses, by name, in order.

rcpt(milters, recipient)

@spec rcpt(t(), String.t()) :: result()

RCPT TO. A rejection is about this recipient only.