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
NegotiationRequestto the milter. -
The milter replies with a
NegotiationResponsein return. -
A client connects to the MTA.
-
The MTA sends a
ConnectRequestto 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
HELOorEHLOcommand to the MTA. -
The MTA sends a
HeloRequestto the milter. The milter responds with one of the decision responses. -
The client sends a
MAIL FROMcommand to the MTA. -
The MTA sends a
MailFromRequestto the milter. The milter responds with one of the decision responses. -
The client sends a
RCPT TOcommand to the MTA. -
The MTA sends a
RcptToRequestto 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
DATAcommand to the MTA. -
The MTA sends a
DataRequestto 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
HeaderRequestto the milter. The milter responds with one of the decision responses. -
Once the client terminates the headers with the
CR LF CR LFsequence, 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
BodyRequests 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 anEndOfBodyRequestto 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
ContinueResponseandSkipResponse) 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
QuitRequestto the milter and immediately closes the connection to it, whereupon this process ends. Alternatively, it sends aQuitNewRequestto 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 ConnectRequests. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseShutdownResponse(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 HeloRequests. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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 MailFromRequests. Otherwise, the milter is expected to respond with one
of the following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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 RcptToRequests. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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 DataRequests. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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 UnknownRequests. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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 HeaderRequests. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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 EndOfHeadersRequests. Otherwise, the milter is expected to respond with
one of the following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
The milter should not respond with any other response.
Multiple BodyRequests 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 BodyRequests. Otherwise, the milter is expected to respond with one of the
following:
AcceptResponseContinueResponseSkipResponse(since version 6)DiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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:
AddHeaderResponseInsertHeaderResponse(since version 3)ModifyHeaderResponse(since version 2)ChangeSenderResponse(since version 6)AddRecipientResponseAddRecipientArgsResponse(since version 6)DeleteRecipientResponseProgressResponseQuarantineResponse(since version 3)ReplaceBodyResponse
Finally, the milter must respond with one of the following:
AcceptResponseContinueResponseDiscardResponseRejectResponseReplyCodeResponseTempFailResponse
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
SkipResponseto aBodyRequest, skips all the remainingBodyRequests (as if the milter had responded to each of them with aContinueResponse) and transmits anEndOfBodyRequest. -
With Sendmail, when replying with
SkipResponseto an earlier request (e.g. aConnectRequest, aRcptToRequest, aHeaderRequest, etc.), Sendmail acts as if it were aContinueResponseand "remembers" theSkipResponsefor 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_RESPONSEflag is set), then skips the rest of the body and sends anEndOfBodyRequest. -
With Postfix, replying with
SkipResponseto 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,HeaderRequestandBodyRequest; it is equivalent to aContinueResponseotherwise.
#[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 HeaderRequests. |
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 ConnectRequests. |
NO_HELO_RESPONSE |
0x00002000 | 6 | The milter will not respond to HeloRequests. |
NO_MAIL_FROM_RESPONSE |
0x00004000 | 6 | The milter will not respond to MailFromRequests. |
NO_RCPT_TO_RESPONSE |
0x00008000 | 6 | The milter will not respond to RcptToRequests. |
NO_DATA_RESPONSE |
0x00010000 | 6 | The milter will not respond to DataRequests. |
NO_UNKNOWN_RESPONSE |
0x00020000 | 6 | The milter will not respond to UnknownRequests. |
NO_END_HEADERS_RESPONSE |
0x00040000 | 6 | The milter will not respond to EndOfHeadersRequests. |
NO_BODY_RESPONSE |
0x00080000 | 6 | The milter will not respond to BodyRequests (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. |