Sovite.SMTP.Command (sovite v0.6.0)

Copy Markdown View Source

Parses SMTP command lines (RFC 5321 §4.1).

The line is given without its CRLF. Verbs and parameter keywords are case-insensitive. Only printable ASCII is accepted, apart from UTF-8 characters in the arguments of MAIL, RCPT, and VRFY, for internationalized addresses (SMTPUTF8, RFC 6531): whether they are allowed depends on the transaction, so the session checks that. Verbs map to atoms from a fixed table; parameter keywords stay strings, upper-cased.

iex> Sovite.SMTP.Command.parse("MAIL FROM:<a@example.com> SIZE=1024")
{:ok, {:mail, "a@example.com", [{"SIZE", "1024"}]}}
iex> Sovite.SMTP.Command.parse("RCPT TO:<@relay.example:b@example.net>")
{:ok, {:rcpt, "b@example.net", []}}
iex> Sovite.SMTP.Command.parse("rcpt to:<Postmaster>")
{:ok, {:rcpt, "Postmaster", []}}

Paths must be in angle brackets. Whitespace after FROM: and TO: is tolerated, since common clients send it. Source routes are accepted and ignored (RFC 5321 §4.1.1.3 and Appendix C). The mailbox is checked with Sovite.Validators.split_mailbox/2, accepting UTF-8.

Summary

Types

XCLIENT and XFORWARD attributes: upper-cased names and decoded values, nil for [UNAVAILABLE] and [TEMPUNAVAIL].

Why a line did not parse

What ETRN (RFC 1985) asks for: the mail queued for a domain, for a domain and its subdomains (@example.com), or in a named queue (#queue). Domains are lower-cased.

t()

Functions

Parses one command line. Returns the verb, if known, with errors.

Types

attributes()

@type attributes() :: [{String.t(), String.t() | nil}]

XCLIENT and XFORWARD attributes: upper-cased names and decoded values, nil for [UNAVAILABLE] and [TEMPUNAVAIL].

error()

@type error() ::
  :unrecognized
  | :not_implemented
  | :non_smtp
  | :invalid_characters
  | :syntax
  | :invalid_sender
  | :invalid_recipient
  | :invalid_parameter

Why a line did not parse:

  • :unrecognized - unknown verb
  • :not_implemented - a known SMTP verb this server does not support
  • :non_smtp - an HTTP request or header line, as in cross-protocol attacks
  • :invalid_characters - control characters, invalid UTF-8, or non-ASCII characters outside the arguments of MAIL, RCPT, and VRFY
  • :syntax - wrong arguments for the verb
  • :invalid_sender / :invalid_recipient - bad path or mailbox
  • :invalid_parameter - malformed KEYWORD=value parameter

etrn_node()

@type etrn_node() ::
  {:domain, String.t()} | {:subdomains, String.t()} | {:queue, String.t()}

What ETRN (RFC 1985) asks for: the mail queued for a domain, for a domain and its subdomains (@example.com), or in a named queue (#queue). Domains are lower-cased.

params()

@type params() :: [{String.t(), String.t() | nil}]

t()

@type t() ::
  {:ehlo, String.t()}
  | {:helo, String.t()}
  | {:lhlo, String.t()}
  | {:mail, String.t(), params()}
  | {:rcpt, String.t(), params()}
  | :data
  | :rset
  | :quit
  | {:noop, String.t()}
  | {:vrfy, String.t()}
  | {:help, String.t()}
  | :starttls
  | {:auth, mechanism :: String.t(), initial_response :: String.t() | nil}
  | {:xclient, attributes()}
  | {:xforward, attributes()}
  | {:bdat, size :: non_neg_integer(), last :: boolean()}
  | {:etrn, etrn_node()}

Functions

parse(line)

@spec parse(binary()) :: {:ok, t()} | {:error, atom() | nil, error()}

Parses one command line. Returns the verb, if known, with errors.