<?xml version="1.0" encoding="utf-8"?>
<!DOCTYPE rfc [
  <!ENTITY nbsp "&#160;">
]>
<rfc xmlns:xi="http://www.w3.org/2001/XInclude"
     category="info"
     docName="draft-adams-milter-protocol-00"
     ipr="trust200902"
     obsoletes=""
     updates=""
     submissionType="IETF"
     xml:lang="en"
     version="3"
     consensus="true"
     sortRefs="true"
     symRefs="true"
     tocInclude="true"
     tocDepth="1">

  <front>
    <title abbrev="Milter Protocol">The Mail Filter (milter) Protocol</title>
    <seriesInfo name="Internet-Draft" value="draft-adams-milter-protocol-00"/>

    <author fullname="Trent Adams" initials="T." surname="Adams">
      <organization>Proofpoint, Inc.</organization>
      <address><email>tadams@proofpoint.com</email></address>
    </author>

    <author fullname="Murray Kucherawy" initials="M." surname="Kucherawy" role="editor">
      <organization></organization>
      <address><email>superuser@gmail.com</email></address>
    </author>

    <date/>

    <area>ART</area>
    <workgroup>TBD</workgroup>
    <keyword>milter</keyword>
    <keyword>SMTP</keyword>
    <keyword>mail filter</keyword>

    <abstract>
      <t>This document specifies the "milter" 
      protocol, a binary request/response protocol by which a Mail Transfer
      Agent (MTA) can delegate inspection and modification of an SMTP
      transaction to one or more external filtering processes.</t>
    </abstract>

    <note removeInRFC="true">
      <name>Editorial Note</name>
      <t>This document describes existing widely deployed practice
      originating in the sendmail "libmilter" implementation. Prose sections
      marked [[TODO]] are placeholders.</t>

      <t> Publication is being sought to archive the protocol which has never been publicly documented other than
      in its code form.  The protocol has not evolved substantially since the release of version 6, and prior
      versions are considered historical. </t>
    </note>
  </front>

  <middle>
    <section anchor="intro"><name>Introduction</name>
      <t>Milter (a portmanteau of "mail filter") was officially introduced into open source Sendmail 8.12.0
      released on September 8, 2001.  It provided a plugin architecture by which a Mail Transfer Agent (MTA)
      can communicate with an arbitrary number of local filtering agents to observe and possibly modify
      messages in transit or influence their delivery.  Filters implementing the milter protocol (called
      "milters") are optionally involved at each phase of an SMTP <xref target="RFC5321"/> transaction. </t>

      <t>Milter is also supported by Postfix, which together with Sendmail provides milter capabilities to the
      bulk of open source MTA deployment worldwide. </t>
    </section>

    <section anchor="conventions"><name>Conventions and Terminology</name>
      <t>The key words "<bcp14>MUST</bcp14>", "<bcp14>MUST NOT</bcp14>",
      "<bcp14>REQUIRED</bcp14>", "<bcp14>SHALL</bcp14>",
      "<bcp14>SHALL NOT</bcp14>", "<bcp14>SHOULD</bcp14>",
      "<bcp14>SHOULD NOT</bcp14>", "<bcp14>RECOMMENDED</bcp14>",
      "<bcp14>MAY</bcp14>", and "<bcp14>OPTIONAL</bcp14>" in this document
      are to be interpreted as described in BCP&nbsp;14
      <xref target="RFC2119"/> <xref target="RFC8174"/> when, and only when, they
      appear in all capitals, as shown here.</t>

      <dl newline="false" spacing="normal">
        <dt>MTA:</dt><dd>The Mail Transfer Agent; the client of this protocol.</dd>
        <dt>Filter (or milter):</dt><dd>The server of this protocol.</dd>
        <dt>Octet:</dt><dd>An 8-bit byte.</dd>
        <dt>Network byte order:</dt><dd>Big-endian, most significant octet first.</dd>
        <dt>Null-terminated string:</dt>
        <dd>A sequence of zero or more non-NUL octets followed by one octet
        of value 0x00. Unless stated otherwise, such strings are US-ASCII.</dd>
      </dl>

      <t>Numeric constants are given in hexadecimal with a "0x" prefix.
      Command and response identifiers are single US-ASCII characters and are
      shown in single quotes; the quotes are not part of the protocol.</t>
    </section>

    <section anchor="model"><name>Protocol Model</name>
      <t>The milter protocol is a connection-oriented, binary, largely
      half-duplex request/response protocol. The MTA connects to the filter
      and drives the exchange; the filter reacts. There is no filter-initiated
      request other than responses.</t>
      <section anchor="transport"><name>Transport</name>
        <t>The protocol runs over any reliable, ordered, octet-stream
        transport. In practice this is TCP <xref target="RFC9293"/> over IPv4
        or IPv6, or a local (AF_UNIX) stream socket. No port number is
        reserved; the port or socket path is a matter of local configuration.
        The protocol provides no authentication, integrity, or
        confidentiality of its own (see <xref target="security"/>).</t>
      </section>
      <section anchor="phases"><name>Transaction Phases</name>
        <t>One milter connection carries the negotiation phase followed by
        zero or more SMTP connection contexts, each carrying zero or more
        message transactions:</t>
        <ol spacing="normal">
          <li>Option negotiation (<xref target="optneg"/>), exactly once per
          milter connection, before anything else.</li>
          <li>SMTP connection context: connection information, then HELO/EHLO.</li>
          <li>Per-message transaction: envelope sender, envelope
          recipients, DATA, headers, end of headers, body chunks, end of
          message.</li>
          <li>End of message is the only point at which message
          modifications may be requested.</li>
        </ol>
        <t>An abort resets to the start of a message transaction while
        preserving the SMTP connection context. A quit ends the milter
        connection; a "quit but new connection follows" resets to the start
        of an SMTP connection context while preserving negotiated options.</t>
      </section>
      <section anchor="flow"><name>Normal Message Flow</name>
        <artwork type="ascii-art"><![CDATA[
  MTA                                     Filter
   |--- 'O' OPTNEG ----------------------->|
   |<-- 'O' OPTNEG (agreed) --------------|
   |--- 'D' MACRO(for 'C') --------------->|   (optional, no reply)
   |--- 'C' CONNECT ---------------------->|
   |<-- 'c' CONTINUE ---------------------|
   |--- 'H' HELO ------------------------->|
   |<-- 'c' CONTINUE ---------------------|
   |--- 'M' MAIL ------------------------->|
   |<-- 'c' CONTINUE ---------------------|
   |--- 'R' RCPT ------------------------->|   (once per recipient)
   |<-- 'c' CONTINUE ---------------------|
   |--- 'T' DATA ------------------------->|
   |<-- 'c' CONTINUE ---------------------|
   |--- 'L' HEADER ----------------------->|   (once per header field)
   |<-- 'c' CONTINUE ---------------------|
   |--- 'N' EOH -------------------------->|
   |<-- 'c' CONTINUE ---------------------|
   |--- 'B' BODY ------------------------->|   (repeated)
   |<-- 'c' CONTINUE  or  'S' SKIP -------|
   |--- 'E' BODYEOB ---------------------->|
   |<-- zero or more modification actions -|
   |<-- final disposition ----------------|
   |--- 'A' ABORT / 'Q' QUIT / 'K' QUIT_NC >|
        ]]></artwork>
      </section>
    </section>

    <section anchor="framing"><name>Packet Framing</name>
      <t>All protocol data units ("packets") share one framing:</t>
      <artwork type="ascii-art"><![CDATA[
   0                   1                   2                   3
   0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1 2 3 4 5 6 7 8 9 0 1
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |                          len (uint32)                         |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
  |    cmd/resp   |                                               |
  +-+-+-+-+-+-+-+-+          data (len - 1 octets)                +
  |                             ...                               |
  +-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+-+
      ]]></artwork>
      <dl spacing="normal">
        <dt>len:</dt>
        <dd>Unsigned 32-bit integer in network byte order, giving the number
        of octets that follow, i.e. one octet of command/response identifier
        plus the length of the data. The value <bcp14>MUST</bcp14> be at
        least 1. The length field itself is not counted.</dd>
        <dt>cmd/resp:</dt>
        <dd>One octet identifying the command (<xref target="commands"/>) or
        response (<xref target="responses"/>).</dd>
        <dt>data:</dt>
        <dd>Exactly len-1 octets, whose interpretation depends on the
        identifier. Where a field is a null-terminated string, the
        terminating NUL is included in len.</dd>
      </dl>
      <t>A receiver <bcp14>MUST</bcp14> impose an upper bound on len and
      <bcp14>MUST</bcp14> close the connection rather than attempt to buffer
      an implausibly large packet. Implementations commonly bound the
      negotiated maximum body chunk (<xref target="protoflags"/>) and reject
      packets exceeding it. A receiver <bcp14>MUST</bcp14> treat an unknown
      identifier, a truncated packet, a missing NUL terminator, or a data
      length inconsistent with the identifier's definition as a protocol
      error and close the connection.</t>
      <t>Multiple packets <bcp14>MAY</bcp14> be coalesced into a single
      transport write; receivers <bcp14>MUST NOT</bcp14> rely on message
      boundaries in the transport stream.</t>
    </section>

    <section anchor="optneg"><name>Option Negotiation</name>
      <t>Immediately after the connection is established, the MTA
      <bcp14>MUST</bcp14> send SMFIC_OPTNEG ('O') and the filter
      <bcp14>MUST</bcp14> reply with SMFIC_OPTNEG ('O'). No other command may
      precede this exchange.</t>
      <artwork type="ascii-art"><![CDATA[
  'O'  version   (uint32)   protocol version, 6 for this specification
       actions   (uint32)   bitmask of SMFIF_* (Section 5.2)
       protocol  (uint32)   bitmask of SMFIP_* (Section 5.3)
      ]]></artwork>
      <t>In the MTA's offer, "actions" and "protocol" advertise everything
      the MTA is able to support. In the filter's reply, the filter
      <bcp14>MUST NOT</bcp14> set any bit the MTA did not offer; the reply
      bits are the operative agreement for the remainder of the connection.
      The filter <bcp14>MUST</bcp14> request in "actions" every modification
      it may later attempt, and <bcp14>MUST</bcp14> set in "protocol" the
      bits describing the commands it wishes to be spared and the replies it
      will omit. An MTA that receives a version it does not support, or a bit
      it did not offer, <bcp14>MUST</bcp14> close the connection.</t>
      <t>The filter's reply <bcp14>MAY</bcp14> be followed by zero or more
      macro list entries, permitted only if SMFIF_SETSYMLIST was negotiated:</t>
      <artwork type="ascii-art"><![CDATA[
       stage     (uint32)   0=connect 1=helo 2=mail 3=rcpt
                            4=data 5=eom 6=eoh
       macros    (string)   space-separated macro names, NUL-terminated
      ]]></artwork>
      <t>Each entry replaces the MTA's default macro set for that stage. A
      stage <bcp14>MUST NOT</bcp14> appear more than once.</t>

      <section anchor="version"><name>Protocol Versions</name>
        <t>Version 6 is specified by this document and is the only version
        implementations are expected to support. Versions 2 and 4 exist in
        historical deployments; they lack, among other things, the DATA and
        UNKNOWN commands, recipient-parameter and sender-change actions, the
        SKIP response, and the no-reply protocol bits. Implementations
        <bcp14>SHOULD</bcp14> offer version 6 and <bcp14>MAY</bcp14> accept
        earlier versions for interoperability.</t>
      </section>

      <section anchor="actionflags"><name>Action Flags (SMFIF_*)</name>
        <table anchor="tbl-actions">
          <name>Action Flags</name>
          <thead><tr><th>Value</th><th>Name</th><th>Permits</th></tr></thead>
          <tbody>
            <tr><td>0x00000001</td><td>SMFIF_ADDHDRS</td><td>Add header fields ('h')</td></tr>
            <tr><td>0x00000002</td><td>SMFIF_CHGBODY</td><td>Replace the body ('b')</td></tr>
            <tr><td>0x00000004</td><td>SMFIF_ADDRCPT</td><td>Add a recipient ('+')</td></tr>
            <tr><td>0x00000008</td><td>SMFIF_DELRCPT</td><td>Delete a recipient ('-')</td></tr>
            <tr><td>0x00000010</td><td>SMFIF_CHGHDRS</td><td>Change or delete header fields ('m'), insert ('i')</td></tr>
            <tr><td>0x00000020</td><td>SMFIF_QUARANTINE</td><td>Quarantine the message ('q')</td></tr>
            <tr><td>0x00000040</td><td>SMFIF_CHGFROM</td><td>Change the envelope sender ('e')</td></tr>
            <tr><td>0x00000080</td><td>SMFIF_ADDRCPT_PAR</td><td>Add a recipient with ESMTP parameters ('2')</td></tr>
            <tr><td>0x00000100</td><td>SMFIF_SETSYMLIST</td><td>Send macro lists during negotiation</td></tr>
          </tbody>
        </table>
      </section>

      <section anchor="protoflags"><name>Protocol Flags (SMFIP_*)</name>
        <t>Bits in the first group suppress a command entirely: the MTA will
        not send it and the filter will not reply to it. Bits in the second
        group ("no reply") keep the command but declare that the filter will
        send no response packet for it; the MTA <bcp14>MUST</bcp14> proceed
        as if SMFIR_CONTINUE had been received. Filters
        <bcp14>MUST NOT</bcp14> send a response for a stage whose no-reply
        bit was negotiated.</t>
        <table anchor="tbl-proto">
          <name>Protocol Flags</name>
          <thead><tr><th>Value</th><th>Name</th><th>Meaning</th></tr></thead>
          <tbody>
            <tr><td>0x00000001</td><td>SMFIP_NOCONNECT</td><td>Do not send 'C'</td></tr>
            <tr><td>0x00000002</td><td>SMFIP_NOHELO</td><td>Do not send 'H'</td></tr>
            <tr><td>0x00000004</td><td>SMFIP_NOMAIL</td><td>Do not send 'M'</td></tr>
            <tr><td>0x00000008</td><td>SMFIP_NORCPT</td><td>Do not send 'R'</td></tr>
            <tr><td>0x00000010</td><td>SMFIP_NOBODY</td><td>Do not send 'B'</td></tr>
            <tr><td>0x00000020</td><td>SMFIP_NOHDRS</td><td>Do not send 'L'</td></tr>
            <tr><td>0x00000040</td><td>SMFIP_NOEOH</td><td>Do not send 'N'</td></tr>
            <tr><td>0x00000080</td><td>SMFIP_NR_HDR</td><td>No reply for 'L'</td></tr>
            <tr><td>0x00000100</td><td>SMFIP_NOUNKNOWN</td><td>Do not send 'U'</td></tr>
            <tr><td>0x00000200</td><td>SMFIP_NODATA</td><td>Do not send 'T'</td></tr>
            <tr><td>0x00000400</td><td>SMFIP_SKIP</td><td>Filter may send 'S' (SKIP)</td></tr>
            <tr><td>0x00000800</td><td>SMFIP_RCPT_REJ</td><td>Also report recipients the MTA rejected</td></tr>
            <tr><td>0x00001000</td><td>SMFIP_NR_CONN</td><td>No reply for 'C'</td></tr>
            <tr><td>0x00002000</td><td>SMFIP_NR_HELO</td><td>No reply for 'H'</td></tr>
            <tr><td>0x00004000</td><td>SMFIP_NR_MAIL</td><td>No reply for 'M'</td></tr>
            <tr><td>0x00008000</td><td>SMFIP_NR_RCPT</td><td>No reply for 'R'</td></tr>
            <tr><td>0x00010000</td><td>SMFIP_NR_DATA</td><td>No reply for 'T'</td></tr>
            <tr><td>0x00020000</td><td>SMFIP_NR_UNKN</td><td>No reply for 'U'</td></tr>
            <tr><td>0x00040000</td><td>SMFIP_NR_EOH</td><td>No reply for 'N'</td></tr>
            <tr><td>0x00080000</td><td>SMFIP_NR_BODY</td><td>No reply for 'B'</td></tr>
            <tr><td>0x00100000</td><td>SMFIP_HDR_LEADSPC</td><td>Header values retain leading space; no space is added on output</td></tr>
            <tr><td>0x10000000</td><td>SMFIP_MDS_256K</td><td>Maximum body chunk 262144 octets</td></tr>
            <tr><td>0x20000000</td><td>SMFIP_MDS_1M</td><td>Maximum body chunk 1048576 octets</td></tr>
          </tbody>
        </table>
        <t>Absent an MDS bit, the maximum body chunk is 65535 octets. If both
        MDS bits are offered the filter <bcp14>MUST</bcp14> select at most
        one. There is no no-reply bit for end of message ('E'); a filter
        <bcp14>MUST</bcp14> always answer 'E'.</t>
      </section>
    </section>

    <section anchor="commands"><name>Commands (MTA to Filter)</name>
      <t>Unless a no-reply or suppression bit was negotiated, the MTA
      <bcp14>MUST</bcp14> wait for a response to each command before sending
      the next, and the filter <bcp14>MUST</bcp14> send exactly one
      terminal response per command.</t>
      <table anchor="tbl-cmds">
        <name>Command Codes</name>
        <thead><tr><th>Code</th><th>Name</th><th>Stage</th></tr></thead>
        <tbody>
          <tr><td>'O' (0x4F)</td><td>SMFIC_OPTNEG</td><td>Negotiation</td></tr>
          <tr><td>'D' (0x44)</td><td>SMFIC_MACRO</td><td>Any (no reply)</td></tr>
          <tr><td>'C' (0x43)</td><td>SMFIC_CONNECT</td><td>Connection</td></tr>
          <tr><td>'H' (0x48)</td><td>SMFIC_HELO</td><td>Connection</td></tr>
          <tr><td>'M' (0x4D)</td><td>SMFIC_MAIL</td><td>Envelope</td></tr>
          <tr><td>'R' (0x52)</td><td>SMFIC_RCPT</td><td>Envelope</td></tr>
          <tr><td>'T' (0x54)</td><td>SMFIC_DATA</td><td>Envelope</td></tr>
          <tr><td>'L' (0x4C)</td><td>SMFIC_HEADER</td><td>Message</td></tr>
          <tr><td>'N' (0x4E)</td><td>SMFIC_EOH</td><td>Message</td></tr>
          <tr><td>'B' (0x42)</td><td>SMFIC_BODY</td><td>Message</td></tr>
          <tr><td>'E' (0x45)</td><td>SMFIC_BODYEOB</td><td>End of message</td></tr>
          <tr><td>'U' (0x55)</td><td>SMFIC_UNKNOWN</td><td>Any</td></tr>
          <tr><td>'A' (0x41)</td><td>SMFIC_ABORT</td><td>Any (no reply)</td></tr>
          <tr><td>'Q' (0x51)</td><td>SMFIC_QUIT</td><td>Any (no reply)</td></tr>
          <tr><td>'K' (0x4B)</td><td>SMFIC_QUIT_NC</td><td>Any (no reply)</td></tr>
        </tbody>
      </table>

      <section><name>SMFIC_MACRO ('D')</name>
        <artwork type="ascii-art"><![CDATA[
  'D'  cmdcode   (char)     command letter these macros apply to
       pairs      ...       name NUL value NUL, repeated to end of packet
      ]]></artwork>
        <t>Conveys named symbols ("macros") describing the state of the SMTP
        session. It is not a command in its own right: it carries no reply,
        and it <bcp14>MUST</bcp14> immediately precede the command identified
        by cmdcode. Macro values persist for the scope of the stage in which
        they were sent and are discarded when that scope ends. Names are
        conveyed without the leading '$' or enclosing braces used in MTA
        configuration syntax. See <xref target="macros"/>.</t>
      </section>

      <section><name>SMFIC_CONNECT ('C')</name>
        <artwork type="ascii-art"><![CDATA[
  'C'  hostname  (string)   validated client name, or bracketed address
       family    (char)     'U' unknown, 'L' unix, '4' inet, '6' inet6
       port      (uint16)   client source port, present unless family 'U'
       address   (string)   textual address, present unless family 'U'
      ]]></artwork>
        <t>Sent once per SMTP connection, before any other transaction
        command. When family is 'U', port and address are absent. For 'L',
        port is present but meaningless and address is the socket path.
        Address literals follow <xref target="RFC5321"/> textual form.</t>
      </section>

      <section><name>SMFIC_HELO ('H')</name>
        <artwork type="ascii-art"><![CDATA[
  'H'  helo      (string)   argument of the HELO or EHLO command
      ]]></artwork>
        <t>May occur more than once per SMTP connection. A second HELO/EHLO
        resets the SMTP transaction but not the milter connection context.</t>
      </section>

      <section><name>SMFIC_MAIL ('M')</name>
        <artwork type="ascii-art"><![CDATA[
  'M'  args       ...       one or more NUL-terminated strings
      ]]></artwork>
        <t>The first string is the envelope sender in angle-bracketed
        <xref target="RFC5321"/> form; the remainder, if any, are the ESMTP
        parameters of the MAIL command, one per string. This command begins a
        message transaction.</t>
      </section>

      <section><name>SMFIC_RCPT ('R')</name>
        <artwork type="ascii-art"><![CDATA[
  'R'  args       ...       one or more NUL-terminated strings
      ]]></artwork>
        <t>As for 'M', but the first string is one envelope recipient. Sent
        once per recipient. A rejection response applies only to that
        recipient and does not terminate the transaction. If SMFIP_RCPT_REJ
        was negotiated, recipients the MTA itself rejected are also reported,
        distinguishable through macros.</t>
      </section>

      <section><name>SMFIC_DATA ('T')</name>
        <artwork type="ascii-art"><![CDATA[
  'T'  (no data)
      ]]></artwork>
        <t>Signals the SMTP DATA command, after the last recipient and before
        any header. A rejection here rejects the whole message.</t>
      </section>

      <section><name>SMFIC_HEADER ('L')</name>
        <artwork type="ascii-art"><![CDATA[
  'L'  name      (string)   header field name, without the colon
       value     (string)   header field value
      ]]></artwork>
        <t>Sent once per header field, in the order the fields appear.
        Folded fields are delivered with their embedded CRLF sequences intact
        and are not unfolded. Unless SMFIP_HDR_LEADSPC was negotiated, the
        whitespace separating the colon from the value is removed from value,
        and the MTA reinserts a single space when emitting header fields
        supplied by the filter.</t>
      </section>

      <section><name>SMFIC_EOH ('N')</name>
        <artwork type="ascii-art"><![CDATA[
  'N'  (no data)
      ]]></artwork>
        <t>Marks the end of the header block.</t>
      </section>

      <section><name>SMFIC_BODY ('B')</name>
        <artwork type="ascii-art"><![CDATA[
  'B'  chunk      ...       opaque body octets, not NUL-terminated
      ]]></artwork>
        <t>Sent zero or more times. Each chunk carries at most the negotiated
        maximum (<xref target="protoflags"/>) minus framing overhead. Chunk
        boundaries are arbitrary and carry no semantics: they need not align
        with line boundaries, and a filter <bcp14>MUST NOT</bcp14> assume
        otherwise. Line endings are CRLF as received on the wire. Dot-stuffing
        as defined in <xref target="RFC5321"/> is removed by the MTA before
        the body is presented.</t>
      </section>

      <section><name>SMFIC_BODYEOB ('E')</name>
        <artwork type="ascii-art"><![CDATA[
  'E'  (no data)
      ]]></artwork>
        <t>Marks the end of the message body. This is the only stage at which
        the filter may send modification actions
        (<xref target="modifications"/>). The filter <bcp14>MUST</bcp14>
        conclude with exactly one terminal response.</t>
      </section>

      <section><name>SMFIC_UNKNOWN ('U')</name>
        <artwork type="ascii-art"><![CDATA[
  'U'  command   (string)   the unrecognized SMTP command text
      ]]></artwork>
        <t>Reports an SMTP command the MTA did not recognize or could not
        process. It does not change the transaction state.</t>
      </section>

      <section><name>SMFIC_ABORT ('A')</name>
        <artwork type="ascii-art"><![CDATA[
  'A'  (no data)
      ]]></artwork>
        <t>Abandons the current message transaction. The filter
        <bcp14>MUST NOT</bcp14> reply and <bcp14>MUST</bcp14> discard all
        state accumulated since 'M', retaining connection and HELO state. It
        may arrive at any point in a transaction, including after 'E'.</t>
      </section>

      <section><name>SMFIC_QUIT ('Q') and SMFIC_QUIT_NC ('K')</name>
        <artwork type="ascii-art"><![CDATA[
  'Q'  (no data)     close the milter connection
  'K'  (no data)     close the SMTP connection context; reuse this one
      ]]></artwork>
        <t>Neither is answered. After 'Q' the filter <bcp14>MUST</bcp14>
        release all state and close the connection. After 'K' the filter
        <bcp14>MUST</bcp14> discard transaction and connection state but
        retain the negotiated options and await a new 'C' on the same milter
        connection.</t>
      </section>
    </section>

    <section anchor="responses"><name>Responses (Filter to MTA)</name>
      <t>Responses fall into three classes: terminal dispositions, which
      conclude the handling of a command; non-terminal responses, which do not
      conclude it; and modification actions, valid only at end of message and
      always followed by a terminal disposition.</t>
      <table anchor="tbl-resp">
        <name>Response Codes</name>
        <thead><tr><th>Code</th><th>Name</th><th>Class</th></tr></thead>
        <tbody>
          <tr><td>'c' (0x63)</td><td>SMFIR_CONTINUE</td><td>Terminal</td></tr>
          <tr><td>'a' (0x61)</td><td>SMFIR_ACCEPT</td><td>Terminal</td></tr>
          <tr><td>'r' (0x72)</td><td>SMFIR_REJECT</td><td>Terminal</td></tr>
          <tr><td>'d' (0x64)</td><td>SMFIR_DISCARD</td><td>Terminal</td></tr>
          <tr><td>'t' (0x74)</td><td>SMFIR_TEMPFAIL</td><td>Terminal</td></tr>
          <tr><td>'y' (0x79)</td><td>SMFIR_REPLYCODE</td><td>Terminal</td></tr>
          <tr><td>'f' (0x66)</td><td>SMFIR_SHUTDOWN</td><td>Terminal</td></tr>
          <tr><td>'p' (0x70)</td><td>SMFIR_PROGRESS</td><td>Non-terminal</td></tr>
          <tr><td>'S' (0x53)</td><td>SMFIR_SKIP</td><td>Terminal (body only)</td></tr>
          <tr><td>'O' (0x4F)</td><td>SMFIC_OPTNEG</td><td>Negotiation only</td></tr>
          <tr><td>'+' (0x2B)</td><td>SMFIR_ADDRCPT</td><td>Modification</td></tr>
          <tr><td>'2' (0x32)</td><td>SMFIR_ADDRCPT_PAR</td><td>Modification</td></tr>
          <tr><td>'-' (0x2D)</td><td>SMFIR_DELRCPT</td><td>Modification</td></tr>
          <tr><td>'e' (0x65)</td><td>SMFIR_CHGFROM</td><td>Modification</td></tr>
          <tr><td>'h' (0x68)</td><td>SMFIR_ADDHEADER</td><td>Modification</td></tr>
          <tr><td>'i' (0x69)</td><td>SMFIR_INSHEADER</td><td>Modification</td></tr>
          <tr><td>'m' (0x6D)</td><td>SMFIR_CHGHEADER</td><td>Modification</td></tr>
          <tr><td>'b' (0x62)</td><td>SMFIR_REPLBODY</td><td>Modification</td></tr>
          <tr><td>'q' (0x71)</td><td>SMFIR_QUARANTINE</td><td>Modification</td></tr>
        </tbody>
      </table>

      <section anchor="dispositions"><name>Terminal Dispositions</name>
        <dl spacing="normal">
          <dt>SMFIR_CONTINUE ('c'), no data:</dt>
          <dd>Proceed with normal processing. This is the implied response
          whenever a no-reply protocol bit is in effect.</dd>
          <dt>SMFIR_ACCEPT ('a'), no data:</dt>
          <dd>Accept without further filtering by this filter. Sent at a
          recipient stage it accepts that recipient; sent at any other stage
          it accepts the message and this filter is not consulted again for
          the remainder of the transaction.</dd>
          <dt>SMFIR_REJECT ('r'), no data:</dt>
          <dd>Reject permanently. The MTA issues a 5yz reply. At a recipient
          stage only that recipient is rejected; elsewhere the message is
          rejected.</dd>
          <dt>SMFIR_TEMPFAIL ('t'), no data:</dt>
          <dd>Reject temporarily. The MTA issues a 4yz reply, inviting a
          later retry. Scope is as for SMFIR_REJECT.</dd>
          <dt>SMFIR_DISCARD ('d'), no data:</dt>
          <dd>Accept the message at the SMTP level and then silently discard
          it. The sending client is given a success reply. If sent at a
          recipient stage, that recipient's copy is discarded.</dd>
          <dt>SMFIR_SHUTDOWN ('f'), no data:</dt>
          <dd>Reject and close the SMTP connection immediately. Not supported
          by all MTAs; filters <bcp14>SHOULD NOT</bcp14> depend on it.</dd>
        </dl>
        <t>The effect of a disposition sent before the message transaction
        begins — that is, at connect or HELO — applies to the SMTP connection
        rather than to a single message.</t>
      </section>

      <section anchor="replycode"><name>SMFIR_REPLYCODE ('y')</name>
        <artwork type="ascii-art"><![CDATA[
  'y'  reply     (string)   "NNN[ ENH.S.C] text", NUL-terminated
      ]]></artwork>
        <t>Rejects with a filter-supplied SMTP reply. The first three octets
        <bcp14>MUST</bcp14> be a three-digit reply code beginning with '4' or
        '5' as defined in <xref target="RFC5321"/>, optionally followed by a
        space, an enhanced status code <xref target="RFC3463"/>, and free
        text. Whether the reply is treated as temporary or permanent follows
        its leading digit. The text <bcp14>MUST NOT</bcp14> contain bare CR or
        LF; a multiline reply is expressed by embedding CRLF-separated lines
        formatted per <xref target="RFC5321"/>, and MTAs
        <bcp14>MAY</bcp14> refuse such replies. A "%" in the text is
        interpreted by some MTAs as introducing a format directive and
        <bcp14>MUST</bcp14> therefore be escaped as "%%".</t>
      </section>

      <section anchor="progress"><name>SMFIR_PROGRESS ('p')</name>
        <artwork type="ascii-art"><![CDATA[
  'p'  (no data)
      ]]></artwork>
        <t>Indicates that the filter is still working and requests that the
        MTA reset its response timer. It does not conclude the command; the
        filter <bcp14>MUST</bcp14> still send a terminal response. It
        <bcp14>MAY</bcp14> be sent repeatedly and is intended chiefly for
        long-running end-of-message processing.</t>
      </section>

      <section anchor="skip"><name>SMFIR_SKIP ('S')</name>
        <artwork type="ascii-art"><![CDATA[
  'S'  (no data)
      ]]></artwork>
        <t>Valid only in response to SMFIC_BODY and only if SMFIP_SKIP was
        negotiated. The filter has seen as much of the body as it needs; the
        MTA <bcp14>MUST</bcp14> stop sending 'B' packets for this message and
        proceed to 'E'. It is equivalent to SMFIR_CONTINUE with respect to
        disposition.</t>
      </section>

      <section anchor="modifications"><name>Modification Actions</name>
        <t>Modification actions <bcp14>MUST</bcp14> be sent only in response
        to SMFIC_BODYEOB, <bcp14>MUST</bcp14> be permitted by the action flags
        agreed during negotiation, and <bcp14>MUST</bcp14> be followed by
        exactly one terminal disposition. Any number may be sent, and their
        effects are applied by the MTA in the order received. A filter
        <bcp14>MUST NOT</bcp14> expect the MTA to report the outcome of an
        individual action; the MTA <bcp14>MAY</bcp14> reject the whole
        connection if an action is malformed or unnegotiated.</t>
        <artwork type="ascii-art"><![CDATA[
  'h'  name      (string)   append a header field
       value     (string)
  ---
  'i'  index     (uint32)   insert a header field; 1 = before the first
       name      (string)   existing field, 0 is treated as 1; an index
       value     (string)   past the end appends
  ---
  'm'  index     (uint32)   1-based occurrence of this field name to
       name      (string)   change; empty value deletes the field
       value     (string)
  ---
  'b'  data       ...       replacement body octets, not NUL-terminated
  ---
  '+'  rcpt      (string)   add envelope recipient
  ---
  '2'  rcpt      (string)   add envelope recipient with ESMTP parameters
       args      (string)
  ---
  '-'  rcpt      (string)   delete envelope recipient
  ---
  'e'  from      (string)   replace envelope sender
       args      (string)   optional ESMTP parameters; may be absent
  ---
  'q'  reason    (string)   quarantine; empty reason releases
      ]]></artwork>
        <t>Notes on individual actions:</t>
        <ul spacing="normal">
          <li>Header names and values in 'h', 'i', and 'm' are subject to the
          SMFIP_HDR_LEADSPC convention described in
          <xref target="commands"/>. Values <bcp14>MUST NOT</bcp14> contain
          bare CR or LF; folding is expressed with CRLF followed by
          whitespace.</li>
          <li>'m' with an index greater than the number of occurrences of
          that field name has no effect. Deletion renumbers subsequent
          occurrences, so a filter deleting several occurrences
          <bcp14>SHOULD</bcp14> work from the highest index downward.</li>
          <li>'b' replaces the entire body. It <bcp14>MAY</bcp14> be sent
          repeatedly, in which case the chunks are concatenated in order to
          form the new body; sending 'b' with no data yields an empty body.
          Each packet is bounded as for SMFIC_BODY. Lines
          <bcp14>MUST</bcp14> be CRLF-terminated and
          <bcp14>MUST NOT</bcp14> be dot-stuffed.</li>
          <li>Recipient and sender addresses are in angle-bracketed
          <xref target="RFC5321"/> form. Adding a recipient that is already
          present, or deleting one that is not, has no effect.</li>
          <li>'q' places the message in the MTA's held queue rather than
          delivering it; the reason is recorded for the administrator. It is
          normally paired with a terminal SMFIR_ACCEPT.</li>
        </ul>
      </section>
    </section>

    <section anchor="macros"><name>Macros</name>
      <t>Macros are name/value pairs supplied through SMFIC_MACRO
      (<xref target="commands"/>) and give the filter access to MTA state not
      otherwise carried on the wire. Availability is MTA-specific and, when
      SMFIF_SETSYMLIST is negotiated, configurable per stage. A filter
      <bcp14>MUST</bcp14> tolerate the absence of any macro. The following
      are widely implemented.</t>
      <table anchor="tbl-macros">
        <name>Commonly Available Macros</name>
        <thead><tr><th>Name</th><th>Earliest stage</th><th>Value</th></tr></thead>
        <tbody>
          <tr><td>j</td><td>connect</td><td>Canonical name of the MTA host</td></tr>
          <tr><td>_</td><td>connect</td><td>Validated client name and address</td></tr>
          <tr><td>{daemon_name}</td><td>connect</td><td>Name of the MTA daemon instance</td></tr>
          <tr><td>{daemon_port}</td><td>connect</td><td>Local port the client connected to</td></tr>
          <tr><td>{if_name}, {if_addr}</td><td>connect</td><td>Local interface name and address</td></tr>
          <tr><td>{client_addr}, {client_port}</td><td>connect</td><td>Client address and source port</td></tr>
          <tr><td>{client_name}</td><td>connect</td><td>Client PTR name, if verified</td></tr>
          <tr><td>{client_ptr}</td><td>connect</td><td>Result of the reverse lookup</td></tr>
          <tr><td>{client_connections}</td><td>connect</td><td>Concurrent connections from this client</td></tr>
          <tr><td>{tls_version}, {cipher}, {cipher_bits}</td><td>helo</td><td>TLS session parameters</td></tr>
          <tr><td>{cert_subject}, {cert_issuer}</td><td>helo</td><td>Client certificate identity</td></tr>
          <tr><td>{verify}</td><td>helo</td><td>Certificate verification result</td></tr>
          <tr><td>{auth_type}, {auth_authen}</td><td>mail</td><td>SASL mechanism and authenticated identity</td></tr>
          <tr><td>{auth_author}</td><td>mail</td><td>Authorization identity, if asserted</td></tr>
          <tr><td>{auth_ssf}</td><td>mail</td><td>SASL security strength factor</td></tr>
          <tr><td>{mail_addr}, {mail_host}, {mail_mailer}</td><td>mail</td><td>Resolved envelope sender</td></tr>
          <tr><td>i</td><td>mail</td><td>MTA queue identifier for this message</td></tr>
          <tr><td>{rcpt_addr}, {rcpt_host}, {rcpt_mailer}</td><td>rcpt</td><td>Resolved current recipient</td></tr>
          <tr><td>{rcpt_reject}</td><td>rcpt</td><td>Set when the MTA itself rejected this recipient</td></tr>
        </tbody>
      </table>
    </section>

    <section anchor="errors"><name>Error Handling and Timeouts</name>
      <t>The protocol carries no error response. Any
      framing violation, unexpected or out-of-sequence identifier, or action
      that was not negotiated is a protocol error, and the detecting party
      <bcp14>MUST</bcp14> close the connection without further exchange.</t>

      <t>An MTA <bcp14>MUST</bcp14> apply a timeout to each response and
      <bcp14>SHOULD</bcp14> apply separate timeouts to connection
      establishment, to sending, and to end-of-message processing.
      SMFIR_PROGRESS restarts the response timer. On timeout, or on any
      failure to connect to or communicate with a filter, the MTA
      <bcp14>MUST</bcp14> apply a locally configured default action; a
      temporary rejection is <bcp14>RECOMMENDED</bcp14> as it is fail-closed
      without permanently losing mail.</t>

      <t>Where an MTA consults several filters, it does so in a configured
      order, passing each stage through the filters in turn; the first
      non-continue disposition terminates consideration of that stage, and
      modifications made by one filter are visible to those following it.</t>
    </section>

    <section anchor="iana"><name>IANA Considerations</name>
      <t>This document specifies no actions for IANA.</t>
    </section>

    <section anchor="security"><name>Security Considerations</name>
      <t>The protocol provides no authentication, integrity
      protection, or confidentiality. A milter connection can carry the full
      content of every message, including credentials asserted through
      macros, in cleartext, and a filter can reject, discard, or rewrite any
      message. Deployments <bcp14>MUST</bcp14> therefore confine milter
      traffic to a trusted path — a local socket with restrictive
      permissions, a loopback interface, or a channel protected by
      TLS <xref target="RFC8446"/> or an equivalent — and MTAs
      <bcp14>MUST NOT</bcp14> accept filter connections from, nor connect to
      filters at, unauthenticated network locations.</t>

      <t>Further considerations: length fields are attacker-influenced and
      invite memory exhaustion if not bounded; a slow or wedged filter is a
      denial-of-service vector against the MTA, and the fail-open default
      action is a filtering-bypass vector; header and body modifications can
      invalidate signatures such as DKIM <xref target="RFC6376"/>;
      filter-supplied reply text is echoed to untrusted clients and can leak
      internal detail or be used for injection if not sanitized.</t>
    </section>
  </middle>

  <back>
    <references><name>References</name>
      <references><name>Normative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.2119.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8174.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5321.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.5322.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.3463.xml"/>
      </references>
      <references><name>Informative References</name>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.9293.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.8446.xml"/>
        <xi:include href="https://bib.ietf.org/public/rfc/bibxml/reference.RFC.6376.xml"/>
      </references>
    </references>

    <section anchor="ack" numbered="false"><name>Acknowledgments</name>
      <t>[[TODO]]</t>
    </section>
  </back>
</rfc>
