EHLO stage
After the greeting, an SMTP client introduces itself. It sends the EHLO command, or the older HELO, or LHLO in the case of LMTP, and gives its host name as the argument. In the same exchange it asks which SMTP extensions the server supports. The server answers with the list of extensions available to that client, such as STARTTLS, AUTH and SIZE. This list determines what the client may do in the rest of the session, for example whether it may encrypt the connection or authenticate.
The EHLO stage therefore serves two purposes. It lets the server judge the client by the name it gives, and it decides which features the server offers. The first is configured on the MtaStageEhlo singleton (found in the WebUI under Settings › MTA › Session › EHLO Stage), and the second on the MtaExtensions singleton.
Client host name
Section titled “Client host name”The first decision is whether a client must introduce itself at all. require makes the client send EHLO or HELO before it starts a transaction, and it is true by default. A client that sends MAIL FROM without it is refused with 503 5.5.1 Polite people say EHLO first.
The second decision is whether the name must be a fully qualified domain name. RFC 5321 requires a client to give its fully qualified domain name, or an address literal when it has no meaningful name. Mail servers on the internet have such a name, so a check on port 25 refuses mostly misconfigured hosts and spam software. rejectNonFqdn performs this check, and by default it applies on port 25 only. A name that fails it is refused with 550 5.5.0 Invalid EHLO domain. Mail clients that submit messages often send a bare host name or an address literal, which is why the check is off on the submission ports.
The EHLO name is used beyond this stage as well. It is the identity that SPF checks for the EHLO domain. It is also available as helo_domain to the RCPT TO and DATA expressions, to throttles and to Sieve scripts.
Filtering by EHLO name
Section titled “Filtering by EHLO name”A filter at this stage can refuse clients by the name they give. script selects a system Sieve script to run after each EHLO. A rejection at this stage refuses only the EHLO command. The session stays open, so the client may try again. The following script refuses clients whose EHLO name is in the blocked-helo list:
require ["variables", "extlists", "reject"];
if string :list "${env.helo_domain}" "blocked-helo" { reject "550 5.7.1 Sending host '${env.helo_domain}' is blocklisted.";}The list that the script consults is a set of MemoryLookupKey entries with the namespace blocked-helo, one entry per name. An entry can also match a pattern rather than a single name: with isGlobPattern set to true, the following entry matches *.spam.example.net:
{ "namespace": "blocked-helo", "key": "*.spam.example.net", "isGlobPattern": true}The script is then selected on MtaStageEhlo, here under the name ehlo-filter:
{ "script": {"else": "'ehlo-filter'"}}SMTP extensions
Section titled “SMTP extensions”SMTP extensions add features to the basic protocol, and a client may use only the extensions that the server lists in its EHLO response. Four of them are always advertised: ENHANCEDSTATUSCODES, 8BITMIME, BINARYMIME and SMTPUTF8. The others depend on the connection and on the settings:
| Extension | Advertised when | Setting |
|---|---|---|
STARTTLS | The connection is not yet encrypted and the listener has TLS enabled. | Listener useTls |
AUTH | The session is not yet authenticated and the expression returns at least one mechanism. | saslMechanisms |
SIZE | The maximum message size is greater than 0. | maxMessageSize |
PIPELINING (RFC 2920) | By default true for every session. | pipelining |
CHUNKING (RFC 3030) | By default true for every session. Clients need it to send BINARYMIME messages with BDAT. | chunking |
REQUIRETLS (RFC 8689) | By default true for every session. | requireTls |
DSN (RFC 3461) | By default, authenticated sessions only. | dsn |
VRFY, EXPN | By default, authenticated sessions only. | vrfy, expn |
FUTURERELEASE (RFC 4865) | The expression returns a duration, the longest a message may be held. By default, authenticated sessions only. | futureRelease |
DELIVERBY (RFC 2852) | The expression returns a duration. By default, authenticated sessions only. | deliverBy |
MT-PRIORITY (RFC 6710) | The expression returns a priority policy, mixer, stanag4406 or nsep. By default, authenticated sessions only. | mtPriority |
NO-SOLICITING (RFC 3865) | The expression returns a string, advertised as the solicitation keywords. By default the extension is advertised without keywords. | noSoliciting |
Each setting of MtaExtensions is an expression, so an extension can be offered to some clients and not to others. An expression that returns false withdraws its extension. A client that uses an extension that was not advertised to it receives a 501 5.5.4 reply naming the extension, for example 501 5.5.4 DSN extension has been disabled.
VRFY and EXPN reveal which addresses exist and who is on a mailing list, which is why they are not offered to unauthenticated clients. Some commands are not available at all: ETRN, ATRN and BURL are not implemented and are answered with 502 5.5.1 Command not implemented.
The following object body offers delivery status notifications and future release to every client, and keeps VRFY disabled:
{ "dsn": {"else": "true"}, "futureRelease": {"else": "1d"}, "vrfy": {"else": "false"}}