Milters are a quasi-standardized protocol to allow a Mail Transfer Agent (MTA) to delegate modifying, as well as making decisions about, e-mail messages to an external program.
The milter protocol was implemented as part of Sendmail:
protocol version | Sendmail version |
---|---|
1 | 8.10 |
2 | 8.11 |
3 | 8.13 |
4 | 8.13 |
4.1 | 8.13.5 |
6 | 8.14 |
It eventually also found its way into Postfix, starting with version 2.3.
The MTA connects to the milter via a stream socket -- Unix, IPv4 or IPv6. However, this stream can be subdivided into individual packets. The packets sent between the MTA and the milter have the following structure:
struct MilterPacket {
length: i32be, // comprises the length of `command` and `data`
command: u8,
data: [u8; length - 1],
}
length
must be at least 1. In protocol version 1, length
cannot be greater than 65536; since
version 2, that limitation is lifted.
A NulTerminatedString
is a sequence of bytes that continues up to and including a terminating zero
(0x00) byte. The single-L spelling refers to the character NUL (U+0000) as opposed to a NULL
pointer.
A RestOfPacketString
may only occur at the end of a packet's data; it is a sequence of bytes that
continues up to the end of the packet data (as announced by the length
field in each packet).
Hexadecimal numbers are specified with the 0x
prefix; digits ten through fifteen are represented
by the letters A
through F
. For example, 0x2A corresponds to 42.
The =
character is encoded as 0x3D.
This process illustrates the general flow between a client, the MTA and the milter. It is assumed
that the MTA supports a sufficiently current version of the milter protocol (otherwise some events
might not trigger a request-response sequence) and the milter has neither opted out of specific
events (using the IGNORE_*
flags) nor announced that it will not respond to specific
events (using the NO_*_RESPONSE
flags).
-
Right after connecting, the MTA sends a
NegotiationRequest
to the milter. -
The milter replies with a
NegotiationResponse
in return. -
A client connects to the MTA.
-
The MTA sends a
ConnectRequest
to the milter. The milter responds with one of the decision responses (exceptReplyCodeResponse
, because no custom codes are allowed at this stage.) -
The client sends a
HELO
orEHLO
command to the MTA. -
The MTA sends a
HeloRequest
to the milter. The milter responds with one of the decision responses. -
The client sends a
MAIL FROM
command to the MTA. -
The MTA sends a
MailFromRequest
to the milter. The milter responds with one of the decision responses. -
The client sends a
RCPT TO
command to the MTA. -
The MTA sends a
RcptToRequest
to the milter. The milter responds with one of the decision responses. -
Steps 9 and 10 are repeated as appropriate for any additional recipient.
-
The client sends a
DATA
command to the MTA. -
The MTA sends a
DataRequest
to the milter. The milter responds with one of the decision responses. -
The client starts supplying the content of the e-mail message, consisting of headers followed by the body.
-
For every header supplied by the client, the MTA sends a
HeaderRequest
to the milter. The milter responds with one of the decision responses. -
Once the client terminates the headers with the
CR LF CR LF
sequence, the MTA sends anEndOfHeadersRequest
. The milter responds with one of the decision responses. -
As the client supplies more and more of the body, the MTA transmits it using
BodyRequest
s to the milter. The milter always responds with one of the decision responses. -
Once the client is finished transmitting the body (
CR LF . CR LF
), the MTA transmits anEndOfBodyRequest
to the milter. -
The milter may now (finally) make changes to the message by sending as many of the mutating responses as required.
-
Finally, the milter sends one of the decision responses (except
ContinueResponse
andSkipResponse
) to voice its ultimate opinion about the message. It should now reset any message-specific state. -
If the client remains connected and wishes to send another e-mail, the whole process repeats from step 7.
-
When the client quits/disconnects, the MTA either sends a
QuitRequest
to the milter and immediately closes the connection to it, whereupon this process ends. Alternatively, it sends aQuitNewRequest
to the milter, whereupon this process repeats from step 3.
The MTA may also send an AbortRequest
, which means that the current message is no longer being
processed. The milter should then reset any message-specific state and continue from step 7.
The format of the data
field of each MilterPacket
depends on the command
.
structure name | command | direction | data length | since version | response types | description |
---|---|---|---|---|---|---|
NegotiationRequest |
O |
MTA → milter | 8/12 | 1 | O | initial packet, capability negotiation |
NegotiationResponse |
O |
milter → MTA | 8/12 | 1 | =O | capability negotiation |
SetMacrosResponse |
l |
milter → MTA | n/a | 6 | - | only send subset of macros |
AbortRequest |
A |
MTA → milter | 0 | 1 | - | current message abandoned |
QuitRequest |
Q |
MTA → milter | 0 | 1 | - | client connection closed |
QuitNewRequest |
K |
MTA → milter | 0 | 6 | - | client connection closed but a new one will follow |
DefineMacroRequest |
D |
MTA → milter | variable | 1 | - | macro keys and values for a future request |
ConnectRequest |
C |
MTA → milter | variable | 1 | C D | new client connected |
HeloRequest |
H |
MTA → milter | variable | 1 | C D R | client issued HELO /EHLO |
MailFromRequest |
M |
MTA → milter | variable | 1 | C D R | client issued MAIL FROM |
RcptToRequest |
R |
MTA → milter | variable | 1 | C D R | client issued RCPT TO |
DataRequest |
T |
MTA → milter | 0 | 4 | C D R | client issued DATA |
UnknownRequest |
U |
MTA → milter | variable | 3 | C D R | client issued a non-standard command |
HeaderRequest |
L |
MTA → milter | variable | 1 | C D R | client passed a header as part of a mail's DATA |
EndOfHeadersRequest |
N |
MTA → milter | 0 | 2 | C D R | no more headers as part of a mail's DATA |
BodyRequest |
B |
MTA → milter | variable | 1 | C D R | chunk of mail's body passed in DATA |
EndOfBodyRequest |
E |
MTA → milter | variable/0 | 1 | D M R | last chunk of mail's body passed in DATA , now the fun starts |
AddHeaderResponse |
h |
milter → MTA | variable | 1 | =M | add header to message |
InsertHeaderResponse |
i |
milter → MTA | variable | 3 | =M | insert header at specific location in message |
ModifyHeaderResponse |
m |
milter → MTA | variable | 2 | =M | change or delete existing header in message |
ChangeSenderResponse |
e |
milter → MTA | variable | 6 | =M | change message sender |
AddRecipientResponse |
+ |
milter → MTA | variable | 1 | =M | add recipient to message |
AddRecipientArgsResponse |
2 |
milter → MTA | variable | 6 | =M | add recipient to message including ESMTP args |
DeleteRecipientResponse |
- |
milter → MTA | variable | 1 | =M | remove recipient from message |
ProgressResponse |
p |
milter → MTA | 0 | 1 | =M | wait, I'm not done yet |
ReplaceBodyResponse |
b |
milter → MTA | variable | 1 | =M | replace body |
AcceptResponse |
a |
milter → MTA | 0 | 1 | =D | final decision: deliver it |
ContinueResponse |
c |
milter → MTA | 0 | 1 | =C | go ahead to the next stage |
SkipResponse |
s |
milter → MTA | 0 | 6 | =C | go ahead to the next stage but skip some requests (depending on MTA) |
DiscardResponse |
d |
milter → MTA | 0 | 1 | =D | final decision: pretend to deliver it but don't |
QuarantineResponse |
q |
milter → MTA | 0 | 3 | =D | final decision: quarantine it |
RejectResponse |
r |
milter → MTA | 0 | 1 | =D | final decision: reject it with a 5xx |
ReplyCodeResponse |
y |
milter → MTA | variable | 1 | =R | final decision: reject it with a custom 4xx/5xx message |
TempFailResponse |
t |
milter → MTA | 0 | 1 | =D | final decision: reject it with a 4xx |
ShutdownResponse |
4 |
milter → MTA | 0 | 3 | =D | final decision: cut off the connection |
ConnFailureResponse |
f |
milter → MTA | n/a | 6 | =D | cause a connection failure |
The "response types" column lists the classes of responses expected to a given request. For example, "O" means that a response of class O is expected, while "=O" means that this is a response of class O. A single hyphen means that the MTA does not expect a response to this request.
The NegotiationRequest
is the first packet transmitted between the MTA and the milter after the
connection is established; specifically, it is transmitted from the MTA to the milter, which is then
expected to respond with a NegotiationResponse
.
#[command = 'O']
struct NegotiationRequest {
version: u32be, // protocol version supported by MTA
if version == 1 {
flags: u32be, // always 0
} else {
supported_filter_flags: u32be,
supported_protocol_flags: u32be,
}
}
Minor revisions such as 4.1 are advertised as the major version (4) with additional supported flags.
The NegotiationResponse
is transmitted from the milter to the MTA after the MTA's
NegotiationRequest
greeting.
#[command = 'O']
struct NegotiationResponse {
version: u32be, // must match server's version number
if version == 1 {
flags: u32be,
} else {
filter_flags: u32be,
protocol_flags: u32be,
}
}
flags
(version 1) or filter_flags
and protocol_flags
(version 2 and above) are bitwise OR
values of flags documented in their own section. Since version 2, the milter must not
specify any filter or protocol flags that the server does not support according to the
NegotiationRequest
.
The SetMacrosResponse
is never actually transmitted from the milter to the MTA; the MTA always
sends all macros and the filtering is done by the milter itself (within libmilter
).
The command character l
is reserved since version 6 for a potential future MTA-side
implementation.
An AbortRequest
is sent by the MTA to inform the milter that the current message has been
abandoned. (However, the client's connection is still open and it might send a new message.) The MTA
does not expect a response to this request.
#[command = 'A']
struct AbortRequest {
}
A QuitRequest
is sent by the MTA to inform the milter that the client has disconnected. The MTA
then closes the connection to the milter without waiting for a response.
#[command = 'Q']
struct QuitRequest {
}
A QuitNewRequest
is sent by the MTA to inform the milter that the client has disconnected but a
new client connection will follow. The milter is expected not to send a response.
#[command = 'K']
#[version >= 6]
struct QuitNewRequest {
}
A DefineMacroRequest
is sent by the MTA to define macro values which the milter can use as an
additional source of metadata. The MTA does not expect a response to this request.
#[command = 'D']
struct DefineMacroRequest {
for_command: u8,
zero_or_more {
macro_name: NulTerminatedString,
macro_value: NulTerminatedString,
},
}
for_command
contains the byte representing the command (generally an upcoming request command) for
which these macro values are relevant.
A ConnectRequest
is sent by the MTA to the milter when a new client connects to the MTA.
#[command = 'C']
struct ConnectRequest {
hostname: NulTerminatedString,
family: u8, // 'U'|'L'|'4'|'6'
if family != 'U' {
port: u16,
if version == 1 {
socket_info: RestOfPacketString,
} else {
socket_info: NulTerminatedString,
}
}
}
The hostname
is the hostname of the connecting client, as resolved by the system or MTA.
The values for family
are the following:
character | hex | description | port | socket info |
---|---|---|---|---|
U |
0x55 | unknown socket type | not in packet | not in packet |
L |
0x4C | local/Unix socket | always 0 | socket path |
4 |
0x34 | IPv4 socket | TCP port | client's IP address as string |
6 |
0x36 | IPv6 socket | TCP port | client's IP address as string |
Note that an IP connection may have been established using a different stream protocol than TCP; in such a case, TCP port may contain the value of a similar parameter of that protocol, such as the SCTP port number.
This request is not sent if the milter specified the IGNORE_CONNECT
flag flag in its
NegotiationResponse
.
If the protocol flag NO_CONNECT_RESPONSE
(version 6 or above) is specified, the milter is expected
not to respond to ConnectRequest
s. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ShutdownResponse
(since version 3)TempFailResponse
The milter should not respond with any other response, including ReplyCodeResponse
(as SMTP does
not allow for custom error codes in the greeting).
A HeloRequest
is sent by the MTA to the milter when the client sends a HELO
or EHLO
command to
the MTA.
#[command = 'H']
struct HeloRequest {
parameter: NulTerminatedString,
}
parameter
is the parameter passed to the HELO
or EHLO
command (generally the client's
hostname). Note that a milter cannot differentiate between HELO
and EHLO
.
This request is not sent if the milter specified the IGNORE_HELO
flag flag in its
NegotiationResponse
.
If the protocol flag NO_HELO_RESPONSE
(version 6 or above) is specified, the milter is expected
not to respond to HeloRequest
s. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
A MailFromRequest
is sent by the MTA to the milter when the client sends a MAIL FROM
command to
the MTA.
#[command = 'M']
struct MailFromRequest {
sender: NulTerminatedString,
zero_or_more {
mail_parameter: NulTerminatedString,
},
}
The sender
is the specified sender address (including the angled brackets). ESMTP allows for
additional parameters to be specified after the sender address, which are transferred as additional
NUL-terminated strings. Generally, these additional parameters have a key-value format (separated by
the first occurrence of the =
character), but this might not be strictly enforced by every MTA.
This request is not sent if the milter specified the IGNORE_MAIL_FROM
flag flag in its
NegotiationResponse
.
If the protocol flag NO_MAIL_FROM_RESPONSE
(version 6 or above) is specified, the milter is
expected not to respond to MailFromRequest
s. Otherwise, the milter is expected to respond with one
of the following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
A RcptToRequest
is sent by the MTA to the milter when the client sends a RCPT TO
command to the
MTA.
#[command = 'R']
struct RcptToRequest {
recipient: NulTerminatedString,
zero_or_more {
mail_parameter: NulTerminatedString,
},
}
The recipient
is the specified recipient address (including the angled brackets). ESMTP allows for
additional parameters to be specified after the recipient address, which are transferred as
additional NUL-terminated strings. Generally, these additional parameters have a key-value format
(separated by the first occurrence of the =
character), but this might not be strictly enforced by
every MTA.
This request is not sent if the milter specified the IGNORE_RCPT_TO
flag flag in its
NegotiationResponse
.
If the protocol flag NO_RCPT_TO_RESPONSE
(version 6 or above) is specified, the milter is expected
not to respond to RcptToRequest
s. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
A DataRequest
is sent by the MTA to the milter when the client sends a DATA
command to the MTA.
#[command = 'T']
#[version >= 4]
struct DataRequest {
}
This request is not sent if the milter specified the IGNORE_DATA
flag flag in its
NegotiationResponse
.
If the protocol flag NO_DATA_RESPONSE
(version 6 or above) is specified, the milter is expected
not to respond to DataRequest
s. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
An UnknownRequest
is sent by the MTA to the milter when the client sends some non-standard command
to the MTA.
#[command = 'U']
#[version >= 3]
struct UnknownRequest {
command: NulTerminatedString,
}
This request is not sent if the milter specified the IGNORE_UNKNOWN
flag flag in its
NegotiationResponse
.
If the protocol flag NO_UNKNOWN_RESPONSE
(version 6 or above) is specified, the milter is expected
not to respond to UnknownRequest
s. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
A HeaderRequest
is sent by the MTA to the milter when the client sends a header as part of the
data of an e-mail message.
#[command = 'L']
struct HeaderRequest {
name: NulTerminatedString,
value: NulTerminatedString,
}
This request is not sent if the milter specified the IGNORE_HEADERS
flag flag in its
NegotiationResponse
.
If the protocol flag NO_HEADER_RESPONSE
(version 3 or above) is specified, the milter is expected
not to respond to HeaderRequest
s. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
An EndOfHeadersRequest
is sent by the MTA to the milter after the final header in the data of an
e-mail message has been transmitted.
#[command = 'N']
#[version >= 2]
struct EndOfHeadersRequest {
}
This request is not sent if the milter specified the IGNORE_END_HEADERS
flag flag in
its NegotiationResponse
. It is also not sent by MTAs that only support the version 1 protocol; in
that case, the first BodyRequest
can be taken as an indirect statement that the headers have
ended.
If the protocol flag NO_END_HEADERS_RESPONSE
(version 6 or above) is specified, the milter is
expected not to respond to EndOfHeadersRequest
s. Otherwise, the milter is expected to respond with
one of the following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
Multiple BodyRequest
s are sent by the MTA to the milter to represent chunks of the e-mail's body.
#[command = 'B']
struct BodyRequest {
body_chunk: RestOfPacketString,
}
body_chunk
should never be longer than 65535 bytes.
If the protocol flag NO_BODY_RESPONSE
(version 6 or above) is specified, the milter is expected
not to respond to BodyRequest
s. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponse
ContinueResponse
SkipResponse
(since version 6)DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter should not respond with any other response.
The EndOfBodyRequest
is sent by the MTA to the milter after the message body has been transmitted.
The milter may then perform modifications on the message body before voicing its opinion on the
message's ultimate fate.
#[command = 'E']
struct EndOfBodyRequest {
if version == 1 {
body_chunk: RestOfPacketString,
}
}
In protocol version 1, the request contained the last chunk of the message body. Since protocol
version 2, the last chunk of the message body is sent in a BodyRequest
and the EndOfBodyRequest
contains no data.
This request is sent even if the milter specified the IGNORE_BODY
flag flag in its
NegotiationResponse
.
The milter is expected to respond with zero or more of the following:
AddHeaderResponse
InsertHeaderResponse
(since version 3)ModifyHeaderResponse
(since version 2)ChangeSenderResponse
(since version 6)AddRecipientResponse
AddRecipientArgsResponse
(since version 6)DeleteRecipientResponse
ProgressResponse
QuarantineResponse
(since version 3)ReplaceBodyResponse
Finally, the milter must respond with one of the following:
AcceptResponse
ContinueResponse
DiscardResponse
RejectResponse
ReplyCodeResponse
TempFailResponse
The milter may send zero or more instances of AddHeaderResponse
as a response to an
EndOfBodyRequest
if it wishes to add additional headers to the e-mail message data.
#[command = 'h']
#[filter_flag_gate = ADD_HEADERS]
struct AddHeaderResponse {
name: NulTerminatedString,
value: NulTerminatedString,
}
The MTA modifies the message accordingly, then waits for another response from the milter.
The milter may send zero or more instances of InsertHeaderResponse
as a response to an
EndOfBodyRequest
if it wishes to insert additional headers at specific locations to the e-mail
message data.
#[command = 'i']
#[version >= 3]
#[filter_flag_gate = ADD_HEADERS]
struct InsertHeaderResponse {
index: u32be,
name: NulTerminatedString,
value: NulTerminatedString,
}
The MTA modifies the message accordingly, then waits for another response from the milter.
The milter may send zero or more instances of ModifyHeaderResponse
as a response to an
EndOfBodyRequest
if it wishes to change or delete headers in the e-mail message data.
#[command = 'm']
#[version >= 2]
#[filter_flag_gate = MODIFY_HEADERS]
struct ChangeHeaderResponse {
index: u32be,
name: NulTerminatedString,
value: NulTerminatedString,
}
If the MTA finds a header at index index
whose name case-insensitively matches name
, that
header's value is replaced with the contents of value
. If value
is empty (only consists of the
NUL byte), the header is removed instead.
If the MTA does not find a header at index index
whose name case-insensitively matches name
, a
new header is added with name
and the contents of value
. If value
is empty, the header is
skipped.
The MTA then waits for another response from the milter.
The milter may send an instance of ChangeSenderResponse
as a response to an EndOfBodyRequest
if
it wishes to change the sender of an e-mail message.
#[command = 'e']
#[version >= 6]
#[filter_flag_gate = CHANGE_SENDER]
struct ChangeSenderResponse {
address: NulTerminatedString,
optional {
esmtp_options: NulTerminatedString,
},
}
The MTA modifies the message accordingly, then waits for another response from the milter.
The milter may send zero or more instances of AddRecipientResponse
as a response to an
EndOfBodyRequest
if it wishes to add additional e-mail addresses to the recipient list.
#[command = '+']
#[filter_flag_gate = ADD_RECIPIENTS]
struct AddRecipientResponse {
address: NulTerminatedString,
}
The MTA modifies the message accordingly, then waits for another response from the milter.
The milter may send zero or more instances of AddRecipientArgsResponse
as a response to an
EndOfBodyRequest
if it wishes to add additional e-mail addresses with ESMTP arguments to the
recipient list.
#[command = '2']
#[filter_flag_gate = ADD_RECIPIENTS_ARG]
struct AddRecipientArgsResponse {
address: NulTerminatedString,
optional {
esmtp_options: NulTerminatedString,
},
}
The MTA modifies the message accordingly, then waits for another response from the milter.
The milter may send zero or more instances of DeleteRecipientResponse
as a response to an
EndOfBodyRequest
if it wishes to remove e-mail addresses from the recipient list.
#[command = '-']
#[filter_flag_gate = DELETE_RECIPIENTS]
struct DeleteRecipientResponse {
address: NulTerminatedString,
}
The MTA modifies the message accordingly, then waits for another response from the milter.
The milter may send zero or more instances of ProgressResponse
as a response to an
EndOfBodyRequest
if processing the message is taking a longer time and might risk running into a
timeout configured on the MTA.
#[command = 'p']
struct ProgressResponse {
}
The MTA then waits for another response from the milter.
The milter may send a QuarantineResponse
as a response to an EndOfBodyRequest
if it wishes the
MTA to quarantine the message with the given text.
#[command = 'q']
#[version >= 3]
#[filter_flag_gate = QUARANTINE]
struct QuarantineResponse {
quarantine_text: NulTerminatedString,
}
The MTA modifies its internal metadata about the message accordingly, then waits for another response from the milter.
The milter may send zero or more instances of ReplaceBodyResponse
as a response to an
EndOfBodyRequest
if it wishes to change the e-mail body.
#[command = 'b']
#[filter_flag_gate = MODIFY_BODY]
struct ReplaceBodyResponse {
body_chunk: NulTerminatedString,
}
The first instance of ReplaceBodyResponse
replaces the message body with the contents of
body_chunk
; any subsequent ReplaceBodyResponse
appends to it.
The MTA replaces the body accordingly, then waits for another response from the milter.
The milter may send an AcceptResponse
as a response to most requests to signal to the MTA that it
is no longer interested in the message and wishes for it to be delivered.
#[command = 'a']
struct AcceptResponse {
}
The MTA then stops passing further information about the message to the milter and, provided approval by other milters and internal decision processes is given, ultimately enqueues the message for delivery.
The milter may send a ContinueResponse
as a response to most requests to signal to the MTA that it
is still interested in the message and has not yet come across a reason to reject or discard it.
#[command = 'c']
struct ContinueResponse {
}
The MTA then continues processing the message.
A ContinueResponse
should not be used as the final response to an EndOfBodyRequest
, as the MTA
cannot provide any further information about the message for the milter and it is time for the
milter's final verdict.
The milter may send a SkipResponse
as a response to most requests to signal to the MTA that it
is still interested in the message and has not yet come across a reason to reject or discard it;
it is thus rather similar to ContinueResponse
.
The actual semantics differ between Sendmail and Postfix:
-
With Sendmail, when replying with
SkipResponse
to aBodyRequest
, skips all the remainingBodyRequest
s (as if the milter had responded to each of them with aContinueResponse
) and transmits anEndOfBodyRequest
. -
With Sendmail, when replying with
SkipResponse
to an earlier request (e.g. aConnectRequest
, aRcptToRequest
, aHeaderRequest
, etc.), Sendmail acts as if it were aContinueResponse
and "remembers" theSkipResponse
for later: when it reaches the stage where the message body is to be transmitted to the milter, it only transmits the first chunk of the body in aBodyRequest
, awaits the response (unless theNO_BODY_RESPONSE
flag is set), then skips the rest of the body and sends anEndOfBodyRequest
. -
With Postfix, replying with
SkipResponse
to any request skips all immediately following requests of the same type (as if the milter had responded to each of them with aContinueResponse
. This is mainly interesting for requests that are issued multiple times per message, e.g.RcptToRequest
,HeaderRequest
andBodyRequest
; it is equivalent to aContinueResponse
otherwise.
#[command = 's']
#[version >= 6]
#[protocol_flag_gate = KNOWS_SKIP]
struct SkipResponse {
}
The behavior between Sendmail and Postfix is consistent when responding with SkipResponse
to a
BodyRequest
; it should probably be avoided in other contexts.
Just like ContinueResponse
, it should not be used as the final response to an EndOfBodyRequest
,
as the MTA cannot provide any further information about the message for the milter and it is time
for the milter's final verdict.
The milter may send an DiscardResponse
as a response to most requests to signal to the MTA that it
is no longer interested in the message and wishes for it to be discarded, i.e. deleted while
pretending to the client that it will be delivered.
#[command = 'd']
struct DiscardResponse {
}
The MTA then stops passing further information about the message to the milter and ultimately discards the message.
The milter may send a RejectResponse
as a response to most requests to signal to the MTA that it
is no longer interested in the message and wishes for it to be rejected, i.e. deleted while telling
the client that it will not be delivered.
#[command = 'r']
struct RejectResponse {
}
The MTA then sends a rejection code (5xx) to the client, stops passing further information about the message to the milter and ultimately discards the message.
The milter may send a ReplyCodeResponse
as a response to most requests to signal to the MTA that
it is no longer interested in the message and wishes for it to be rejected with a specific code,
i.e. deleted while telling the client that it will not be delivered.
#[command = 'y']
struct ReplyCodeResponse {
code: NulTerminatedString,
}
The MTA sends code
as the response to the client; it must begin with a sequence of three digits
0
-9
that represent the SMTP reply code and the code must be in the 400-499 (temporary failure)
or 500-599 (permanent failure) ranges.
The MTA then stops passing further information about the message to the milter and ultimately discards the message.
The milter may send a ShutdownResponse
as a response to a ConnectRequest
to signal to the MTA
that it should immediately shut down the connection.
#[command = '4']
#[version >= 3]
struct ShutdownResponse {
}
The MTA then shuts down the connection. The client will probably try to deliver the message again later.
The milter may send a TempFailResponse
as a response to most requests to signal to the MTA that it
is no longer interested in the message and wishes for it to be rejected, i.e. deleted while telling
the client that it will not be delivered.
#[command = 't']
struct TempFailResponse {
}
The MTA then sends a temporary rejection code (4xx) to the client, stops passing further information about the message to the milter and ultimately discards the message. The client will probably try to deliver this message again later.
The ConnFailureResponse
is never actually transmitted from the milter to the MTA.
The command character f
is reserved since version 6 for a potential future implementation.
The milter can tell the MTA:
- which operations it might perform on an e-mail message (filter flags)
- which events it is not interested in (protocol flags)
Since version 2, the values have been separated into filter_flags
and protocol_flags
in the
NegotiationResponse
, with values combined using bitwise OR.
The following filter_flags
exist:
symbolic name | value | since version | description |
---|---|---|---|
ADD_HEADERS |
0x00000001 | 1 | The milter might add (insert or append) headers to the message. |
MODIFY_BODY |
0x00000002 | 1 | The milter might modify the body of the message. |
ADD_RECIPIENTS |
0x00000004 | 1 | The milter might add recipients to the message. |
DELETE_RECIPIENTS |
0x00000008 | 1 | The milter might remove recipients from the message. |
MODIFY_HEADERS |
0x00000010 | 2 | The milter might change or delete the message's headers. |
QUARANTINE |
0x00000020 | 3 | The milter might quarantine a message. |
CHANGE_SENDER |
0x00000040 | 6 | The milter might change a message's sender. |
ADD_RECIPIENTS_ARG |
0x00000080 | 6 | The milter might add recipients with ESMTP arguments to the message. |
SET_MACROS |
0x00000100 | 6 | The milter might request that only a subset of macros is transmitted. |
The following protocol_flags
exist:
symbolic name | value | since version | description |
---|---|---|---|
IGNORE_CONNECT |
0x00000001 | 1 | The MTA should not consult the milter when a new connection is opened. |
IGNORE_HELO |
0x00000002 | 1 | The MTA should not consult the milter when a HELO command is received. |
IGNORE_MAIL_FROM |
0x00000004 | 1 | The MTA should not consult the milter when a MAIL FROM command is received. |
IGNORE_RCPT_TO |
0x00000008 | 1 | The MTA should not consult the milter when a RCPT TO command is received. |
IGNORE_BODY |
0x00000010 | 1 | The MTA should not send the message body to the milter. |
IGNORE_HEADERS |
0x00000020 | 1 | The MTA should not send the message headers to the milter. |
IGNORE_END_HEADERS |
0x00000040 | 2 | The MTA should not send an end-of-headers request to the milter. |
NO_HEADER_RESPONSE |
0x00000080 | 3 | The milter will not respond to HeaderRequest s. |
IGNORE_UNKNOWN |
0x00000100 | 4.1 | The MTA should not consult the milter when a non-standard command is received. |
IGNORE_DATA |
0x00000200 | 4.1 | The MTA should not consult the milter when a DATA command is received. |
KNOWS_SKIP |
0x00000400 | 6 | The MTA is telling the milter that it understands the SkipResponse . |
RCPT_REJ |
0x00000800 | 6 | The MTA should also send rejected recipients to the milter. |
NO_CONNECT_RESPONSE |
0x00001000 | 6 | The milter will not respond to ConnectRequest s. |
NO_HELO_RESPONSE |
0x00002000 | 6 | The milter will not respond to HeloRequest s. |
NO_MAIL_FROM_RESPONSE |
0x00004000 | 6 | The milter will not respond to MailFromRequest s. |
NO_RCPT_TO_RESPONSE |
0x00008000 | 6 | The milter will not respond to RcptToRequest s. |
NO_DATA_RESPONSE |
0x00010000 | 6 | The milter will not respond to DataRequest s. |
NO_UNKNOWN_RESPONSE |
0x00020000 | 6 | The milter will not respond to UnknownRequest s. |
NO_END_HEADERS_RESPONSE |
0x00040000 | 6 | The milter will not respond to EndOfHeadersRequest s. |
NO_BODY_RESPONSE |
0x00080000 | 6 | The milter will not respond to BodyRequest s (only to the EndOfBodyRequest ). |
HEADER_LEADING_SPACE |
0x00100000 | 6 | The milter wants leading spaces before values left intact when reading headers and no additional leading spaces to be added when setting them. |
If a NO_*_RESPONSE
flag is set, right after the MTA sends the corresponding request to the milter,
it pretends that the milter responded with a ContinueResponse
and acts accordingly.
In version 1 of the protocol, these were supplied together in a value named flags
:
symbolic name | value | description |
---|---|---|
ADD_HEADERS |
0x00000001 | The milter might add headers to the message. |
MODIFY_BODY |
0x00000002 | The milter might modify the body of the message. |
ADD_RECIPIENTS |
0x00000004 | The milter might add recipients to the message. |
DELETE_RECIPIENTS |
0x00000008 | The milter might remove recipients from the message. |
IGNORE_CONNECT |
0x00000010 | The MTA should not consult the milter when a new connection is opened. |
IGNORE_HELO |
0x00000020 | The MTA should not consult the milter when a HELO command is received. |
IGNORE_MAIL_FROM |
0x00000040 | The MTA should not consult the milter when a MAIL FROM command is received. |
IGNORE_RCPT_TO |
0x00000080 | The MTA should not consult the milter when a RCPT TO command is received. |
IGNORE_BODY |
0x00000100 | The MTA should not send the message body to the milter. |
IGNORE_HEADERS |
0x00000200 | The MTA should not send the message headers to the milter. |