Taint: reject tainted list-separator change
[exim.git] / doc / doc-docbook / spec.xfpt
index 661421be1ba1b9de7238a05cbbe78c0450f42fa5..ae30cb88653a9c77f03df2c1b2036084e0f92df6 100644 (file)
@@ -3435,7 +3435,7 @@ Unqualified addresses are automatically qualified using &%qualify_domain%& and
 &%qualify_recipient%&, as appropriate, unless the &%-bnq%& option is used.
 
 Some other SMTP commands are recognized in the input. HELO and EHLO act
-as RSET; VRFY, EXPN, ETRN, and HELP act as NOOP;
+as RSET; VRFY, EXPN, ETRN, ATRN, and HELP act as NOOP;
 QUIT quits, ignoring the rest of the standard input.
 
 .cindex "return code" "for &%-bS%&"
@@ -8261,12 +8261,21 @@ An option group name for MySQL option files can be specified in square brackets;
 the default value is &"exim"&.
 The full syntax of each item in &%mysql_servers%& is:
 .display
-<&'hostname'&>::<&'port'&>(<&'socket name'&>)[<&'option group'&>]/&&&
-  <&'database'&>/<&'user'&>/<&'password'&>
+<&'hostspec'&><&'portspec'&>(<&'socket name'&>)[<&'option group'&>]/&&&
+   <&'database'&>/<&'user'&>/<&'password'&>
 .endd
 Any of the four sub-parts of the first field can be omitted. For normal use on
 the local host it can be left blank or set to just &"localhost"&.
 
+.new
+A &'hostspec'& can be a hostname, an IPv4 address or an IPv6 address.
+For the latter, a &'portspec'& is a dot followed by a port number;
+for the other two a &'portspec'& is a colon followed by a port number.
+.wen
+
+Note that the default list-separator for the list of servers is a colon so
+(unless that is changed) all colons in list items must be doubled.
+
 No database need be supplied &-- but if it is absent here, it must be given in
 the queries.
 
@@ -8282,6 +8291,10 @@ parameters for the connection.
 
 
 .subsection "Special PostgreSQL features" SECID74
+.new
+The &'hostspec'& for PostgreSQL follows the same rules as for MySQL above.
+.wen
+
 PostgreSQL lookups can also use Unix domain socket connections to the database.
 This is usually faster and costs less CPU time than a TCP/IP connection.
 However it can be used only if the mail server runs on the same machine as the
@@ -8428,10 +8441,18 @@ type of match and is given below as the &*value*& information.
 
 .section "Expansion of lists" "SECTlistexpand"
 .cindex "expansion" "of lists"
-Each list is expanded as a single string before it is used.
+.new
+Each list, after any leading change-of-separator specification
+(see &<<SECTlistsepchange>>&) is expanded as a single string,
 .cindex "tainted data" tracking
-&*Note*&: As a result, if any componend was tainted then the
-entire result string becomes tainted.
+&*Note*&: As a result, if any component was tainted then the
+entire expansion result string becomes tainted.
+
+Splitting out a leading explicit change-of-separator permits
+one being safely used on a list that has tainted components
+while still detecting the use of a tainted setting.
+The latter is not permitted.
+.wen
 
 &'Exception: the router headers_remove option, where list-item
 splitting is done before string-expansion.'&
@@ -10083,11 +10104,15 @@ leading and trailing quotes are removed from the returned value.
 .cindex "list" "selecting by condition"
 .cindex "expansion" "selecting from list by condition"
 .vindex "&$item$&"
-After expansion, <&'string'&> is interpreted as a list, colon-separated by
-default, but the separator can be changed in the usual way (&<<SECTlistsepchange>>&).
-For each item
-in this list, its value is placed in &$item$&, and then the condition is
-evaluated.
+.new
+<&'string1'&> first has the part after any change-of-list-separator
+(see &<<SECTlistsepchange>>&) expanded,
+then the whole is taken as a list.
+.wen
+The default separator for the list is a colon.
+
+For each item in this list,
+its value is placed in &$item$&, and then the condition is evaluated.
 Any modification of &$value$& by this evaluation is discarded.
 If the condition is true, &$item$& is added to the output as an
 item in a new list; if the condition is false, the item is discarded. The
@@ -10351,8 +10376,12 @@ The <&'number'&> argument must consist entirely of decimal digits,
 apart from an optional leading minus,
 and leading and trailing white space (which is ignored).
 
-After expansion, <&'string1'&> is interpreted as a list, colon-separated by
-default, but the separator can be changed in the usual way (&<<SECTlistsepchange>>&).
+.new
+The <&'string1'&> argument, after any leading change-of-separator
+(see &<<SECTlistsepchange>>&),
+is expanded and the whole forms the list.
+.wen
+By default, the list separator is a colon.
 
 The first field of the list is numbered one.
 If the number is negative, the fields are
@@ -10452,10 +10481,15 @@ ${lookup nisplus {[name=$local_part],passwd.org_dir:gcos} \
 .vitem &*${map{*&<&'string1'&>&*}{*&<&'string2'&>&*}}*&
 .cindex "expansion" "list creation"
 .vindex "&$item$&"
-After expansion, <&'string1'&> is interpreted as a list, colon-separated by
-default, but the separator can be changed in the usual way (&<<SECTlistsepchange>>&).
-For each item
-in this list, its value is place in &$item$&, and then <&'string2'&> is
+.new
+<&'string1'&> first has the part after any change-of-list-separator
+(see &<<SECTlistsepchange>>&) expanded,
+then the whole is taken as a list.
+.wen
+The default separator for the list is a colon.
+
+For each item in this list,
+its value is place in &$item$&, and then <&'string2'&> is
 expanded and added to the output as an item in a new list. The separator used
 for the output list is the same as the one used for the input, but a separator
 setting is not included in the output. For example:
@@ -10675,9 +10709,15 @@ locks out the use of this expansion item in filter files.
 .cindex "list" "reducing to a scalar"
 .vindex "&$value$&"
 .vindex "&$item$&"
-This operation reduces a list to a single, scalar string. After expansion,
-<&'string1'&> is interpreted as a list, colon-separated by default, but the
-separator can be changed in the usual way (&<<SECTlistsepchange>>&).
+This operation reduces a list to a single, scalar string.
+
+.new
+<&'string1'&> first has the part after any change-of-list-separator
+(see &<<SECTlistsepchange>>&) expanded,
+then the whole is taken as a list.
+.wen
+The default separator for the list is a colon.
+
 Then <&'string2'&> is expanded and
 assigned to the &$value$& variable. After this, each item in the <&'string1'&>
 list is assigned to &$item$&, in turn, and <&'string3'&> is expanded for each of
@@ -10834,8 +10874,13 @@ rather than any Unicode-aware character handling.
 .cindex sorting "a list"
 .cindex list sorting
 .cindex expansion "list sorting"
-After expansion, <&'string'&> is interpreted as a list, colon-separated by
-default, but the separator can be changed in the usual way (&<<SECTlistsepchange>>&).
+.new
+<&'string'&> first has the part after any change-of-list-separator
+(see &<<SECTlistsepchange>>&) expanded,
+then the whole is taken as a list.
+.wen
+The default separator for the list is a colon.
+
 The <&'comparator'&> argument is interpreted as the operator
 of a two-argument expansion condition.
 The numeric operators plus ge, gt, le, lt (and ~i variants) are supported.
@@ -11291,7 +11336,8 @@ All measurement is done in bytes and is not UTF-8 aware.
 .cindex "list" "item count"
 .cindex "list" "count of items"
 .cindex "&%listcount%& expansion item"
-The string is interpreted as a list and the number of items is returned.
+The part of the string after any leading change-of-separator is expanded,
+then the whole is interpreted as a list and the number of items is returned.
 
 
 .vitem &*${listnamed:*&<&'name'&>&*}*&&~and&~&*${listnamed_*&<&'type'&>&*:*&<&'name'&>&*}*&
@@ -11912,9 +11958,14 @@ attempt. It is false during any subsequent delivery attempts.
 .cindex "expansion" "&*forall*& condition"
 .cindex "expansion" "&*forany*& condition"
 .vindex "&$item$&"
-These conditions iterate over a list. The first argument is expanded to form
-the list. By default, the list separator is a colon, but it can be changed by
-the normal method (&<<SECTlistsepchange>>&).
+These conditions iterate over a list.
+.new
+The first argument, after any leading change-of-separator
+(see &<<SECTlistsepchange>>&),
+is expanded and the whole forms the list.
+.wen
+By default, the list separator is a colon.
+
 The second argument is interpreted as a condition that is to
 be applied to each item in the list in turn. During the interpretation of the
 condition, the current list item is placed in a variable called &$item$&.
@@ -11991,9 +12042,14 @@ SRS decode.  See SECT &<<SECTSRS>>& for details.
        &*inlisti&~{*&<&'subject'&>&*}{*&<&'list'&>&*}*&
 .cindex "string" "comparison"
 .cindex "list" "iterative conditions"
-Both strings are expanded; the second string is treated as a list of simple
-strings; if the first string is a member of the second, then the condition
-is true.
+The <&'subject'&> string is expanded.
+.new
+The <&'list'&> first has any change-of-list-separator
++(see &<<SECTlistsepchange>>&) retained verbatim,
++then the remainder is expanded.
++.wen
+The whole is treated as a list of simple strings;
+if the subject string is a member of that list, then the condition is true.
 For the case-independent &%inlisti%& condition, case is defined per the system C locale.
 
 These are simpler to use versions of the more powerful &*forany*& condition.
@@ -12186,6 +12242,10 @@ just as easy to use the fact that a lookup is itself a condition, and write:
 
 Note that <&'string2'&> is not itself subject to string expansion, unless
 Exim was built with the EXPAND_LISTMATCH_RHS option.
+.new
+For the latter case, only the part after any leading
+change-of-separator specification is expanded.
+.wen
 
 Consult section &<<SECThoslispatip>>& for further details of these patterns.
 
@@ -12214,9 +12274,10 @@ ${if match_domain{$domain}{+local_domains}{...
 .endd
 .cindex "&`+caseful`&"
 For address lists, the matching starts off caselessly, but the &`+caseful`&
-item can be used, as in all address lists, to cause subsequent items to
-have their local parts matched casefully. Domains are always matched
-caselessly.
+item can be used, as in all address lists, to cause subsequent items
+(including those of referenced named lists)
+to have their local parts matched casefully.
+Domains are always matched caselessly.
 
 The variable &$value$& will be set for a successful match and can be
 used in the success clause of an &%if%& expansion item using the condition.
@@ -12231,6 +12292,10 @@ Any previous &$value$& is restored after the if.
 
 Note that <&'string2'&> is not itself subject to string expansion, unless
 Exim was built with the EXPAND_LISTMATCH_RHS option.
+.new
+For the latter case, only the part after any leading
+change-of-separator specification is expanded.
+.wen
 
 &*Note*&: Host lists are &'not'& supported in this way. This is because
 hosts have two identities: a name and an IP address, and it is not clear
@@ -12540,6 +12605,13 @@ to the relevant file.
 When, as a result of aliasing or forwarding, a message is directed to a pipe,
 this variable holds the pipe command when the transport is running.
 
+.new
+.vitem &$atrn_host$&
+.vindex ATRN "data for routing"
+When an ATRN command is accepted, this variable is filled in with the client
+IP and port, for use in a manualroute router.
+.wen
+
 .vitem "&$auth1$& &-- &$auth4$&"
 .vindex "&$auth1$&, &$auth2$&, etc"
 These variables are used in SMTP authenticators (see chapters
@@ -14916,6 +14988,7 @@ listed in more than one group.
 .row &%acl_not_smtp%&                "ACL for non-SMTP messages"
 .row &%acl_not_smtp_mime%&           "ACL for non-SMTP MIME parts"
 .row &%acl_not_smtp_start%&          "ACL for start of non-SMTP message"
+.row &%acl_smtp_atrn%&               "ACL for ATRN"
 .row &%acl_smtp_auth%&               "ACL for AUTH"
 .row &%acl_smtp_connect%&            "ACL for connection"
 .row &%acl_smtp_data%&               "ACL for DATA"
@@ -15238,6 +15311,19 @@ SMTP messages.
 This option defines the ACL that is run before Exim starts reading a
 non-SMTP message. See section &<<SECnonSMTP>>& for further details.
 
+.new
+.option acl_smtp_atrn main string&!! unset
+.cindex ATRN "ACL for"
+.cindex ATRN advertisement
+.cindex "ESMTP extensions" ATRN
+This option defines the ACL that is run when an SMTP ATRN command is
+received.
+If no value is set, or the result after expansion is an empty string,
+then the ATRN facility is not advertised.
+See chapter &<<CHAPACL>>& for general information on ACLs,
+and section &<<SECTATRN>>& for description of ATRN.
+.wen
+
 .option acl_smtp_auth main string&!! unset
 .cindex "&ACL;" "setting up for SMTP commands"
 .cindex "AUTH" "ACL for"
@@ -15281,7 +15367,8 @@ See section &<<SECDKIMVFY>>& for further details.
 This option defines the ACL that is run when an SMTP ETRN command is
 received.
 If no value is set then the ETRN facility is not advertised.
-See chapter &<<CHAPACL>>& for further details.
+See chapter &<<CHAPACL>>& for general information on ACLs,
+and section &<<SECTETRN>>& for description of ETRN.
 
 .option acl_smtp_expn main string&!! unset
 .cindex "EXPN" "ACL for"
@@ -18047,7 +18134,7 @@ hosts), you can do so by an appropriate use of a &%control%& modifier in an ACL
 
 
 .option smtp_etrn_command main string&!! unset
-.cindex "ETRN" "command to be run"
+.cindex ETRN "command to be run"
 .cindex "ESMTP extensions" ETRN
 .vindex "&$domain$&"
 If this option is set, the given command is run whenever an SMTP ETRN
@@ -18074,7 +18161,7 @@ the command.
 
 
 .option smtp_etrn_serialize main boolean true
-.cindex "ETRN" "serializing"
+.cindex ETRN serializing
 When this option is set, it prevents the simultaneous execution of more than
 one identical command as a result of ETRN in an SMTP connection. See
 section &<<SECTETRN>>& for details.
@@ -19689,8 +19776,8 @@ real_localuser:
 For security, it would probably be a good idea to restrict the use of this
 router to locally-generated messages, using a condition such as this:
 .code
-  condition = ${if match {$sender_host_address}\
-                         {\N^(|127\.0\.0\.1)$\N}}
+  condition = ${if match_ip {$sender_host_address} \
+                       {<; ; 127.0.0.1 ; ::1}}
 .endd
 
 If both &%local_part_prefix%& and &%local_part_suffix%& are set for a router,
@@ -22432,8 +22519,8 @@ real_localuser:
 For security, it would probably be a good idea to restrict the use of this
 router to locally-generated messages, using a condition such as this:
 .code
-  condition = ${if match {$sender_host_address}\
-                         {\N^(|127\.0\.0\.1)$\N}}
+  condition = ${if match_ip {$sender_host_address} \
+                       {<; ; 127.0.0.1 ; ::1}}
 .endd
 
 
@@ -28003,17 +28090,17 @@ Successful authentication sets up information used by the
 
 .cindex authentication "failure event, server"
 If an authenticator is run and does not succeed,
-an event (see &<<CHAPevents>>&) of type "auth:fail" is raised.
+an event of type "auth:fail" is raised.
 While the event is being processed the variables
 &$sender_host_authenticated$& (with the authenticator name)
 and &$authenticated_fail_id$& (as set by the authenticator &%server_set_id%& option)
 will be valid.
 If the event is serviced and a string is returned then the string will be logged
 instead of the default log line.
-See <<CHAPevents>> for details on events.
+See &<<CHAPevents>>& for details on events.
 
 
-.section "Testing server authentication" "SECID169"
+.subsection "Testing server authentication" "SECID169"
 .cindex "authentication" "testing a server"
 .cindex "AUTH" "testing a server"
 .cindex "base64 encoding" "creating authentication test data"
@@ -28092,12 +28179,12 @@ usual way.
 .next
 .cindex authentication "failure event, client"
 If the response to authentication is a permanent error (5&'xx'& code),
-an event (see &<<CHAPevents>>&) of type "auth:fail" is raised.
+an event of type "auth:fail" is raised.
 While the event is being processed the variable
 &$sender_host_authenticated$& (with the authenticator name)
 will be valid.
 If the event is serviced and a string is returned then the string will be logged.
-See <<CHAPevents>> for details on events.
+See &<<CHAPevents>>& for details on events.
 
 .next
 If the response to authentication is a permanent error (5&'xx'& code), Exim
@@ -30833,15 +30920,17 @@ configuration locally by running a fake SMTP session with which you interact.
 .cindex "&ACL;" "options for specifying"
 In order to cause an ACL to be used, you have to name it in one of the relevant
 options in the main part of the configuration. These options are:
+.cindex "ATRN" "ACL for"
 .cindex "AUTH" "ACL for"
 .cindex "DATA" "ACLs for"
+.cindex "DKIM" "ACL for"
 .cindex "ETRN" "ACL for"
 .cindex "EXPN" "ACL for"
 .cindex "HELO" "ACL for"
 .cindex "EHLO" "ACL for"
-.cindex "DKIM" "ACL for"
 .cindex "MAIL" "ACL for"
-.cindex "QUIT, ACL for"
+.cindex "QUIT" "ACL for"
+.cindex "PRDR" "ACL for"
 .cindex "RCPT" "ACL for"
 .cindex "STARTTLS, ACL for"
 .cindex "VRFY" "ACL for"
@@ -30849,12 +30938,12 @@ options in the main part of the configuration. These options are:
 .cindex "SMTP" "connection, ACL for"
 .cindex "non-SMTP messages" "ACLs for"
 .cindex "MIME content scanning" "ACL for"
-.cindex "PRDR" "ACL for"
 
 .table2 140pt
 .irow &%acl_not_smtp%&         "ACL for non-SMTP messages"
 .irow &%acl_not_smtp_mime%&    "ACL for non-SMTP MIME parts"
 .irow &%acl_not_smtp_start%&   "ACL at start of non-SMTP message"
+.irow &%acl_smtp_atrn%&        "ACL for ATRN"
 .irow &%acl_smtp_auth%&        "ACL for AUTH"
 .irow &%acl_smtp_connect%&     "ACL for start of SMTP connection"
 .irow &%acl_smtp_data%&        "ACL after DATA is complete"
@@ -31286,9 +31375,11 @@ For &%acl_not_smtp%&, &%acl_smtp_auth%&, &%acl_smtp_connect%&,
 &%acl_smtp_mime%&, &%acl_smtp_predata%&, and &%acl_smtp_starttls%&, the action
 when the ACL is not defined is &"accept"&.
 
-For the others (&%acl_smtp_etrn%&, &%acl_smtp_expn%&, &%acl_smtp_rcpt%&,
-&%acl_smtp_vrfy%&
-and &%acl_smtp_wellknown%&),
+.new
+For the others (&%acl_smtp_atrn%&,
+.wen
+&%acl_smtp_etrn%&, &%acl_smtp_expn%&, &%acl_smtp_rcpt%&,
+&%acl_smtp_vrfy%& and &%acl_smtp_wellknown%&),
 the action when the ACL
 is not defined is &"deny"&.  This means that &%acl_smtp_rcpt%& must be
 defined in order to receive any messages over an SMTP connection.
@@ -31339,7 +31430,7 @@ of previously accepted recipients. At DATA time (for both the DATA ACLs),
 .cindex "&ACL;" "data for non-message ACL"
 .vindex &$smtp_command_argument$&
 .vindex &$smtp_command$&
-When an ACL is being run for AUTH, EHLO, ETRN, EXPN, HELO, STARTTLS, or VRFY,
+When an ACL is being run for ATRN, AUTH, EHLO, ETRN, EXPN, HELO, STARTTLS, or VRFY,
 the remainder of the SMTP command line is placed in &$smtp_command_argument$&,
 and the entire SMTP command is available in &$smtp_command$&.
 These variables can be tested using a &%condition%& condition. For example,
@@ -32670,6 +32761,18 @@ loops. This condition allows you to use different ACLs in different
 circumstances. For example, different ACLs can be used to handle RCPT commands
 for different local users or different local domains.
 
+.new
+.vitem &*atrn_domains&~=&~*&<&'domain&~list'&>
+.cindex ATRN "checking for queued messages"
+This condition is only usable in the ATRN ACL.
+It returns true if there are any messages queued for any of the domains given
+in the list.
+The list supplied must not be tainted
+.cindex "tainted data" "de-tainting"
+and should contain only domains relevant for the authenticated user
+(to avoid leaking information about other users).
+.wen
+
 .vitem &*authenticated&~=&~*&<&'string&~list'&>
 .cindex "&%authenticated%& ACL condition"
 .cindex "authentication" "ACL checking"
@@ -37928,6 +38031,114 @@ under its own uid and gid when receiving incoming SMTP, so it is not possible
 for it to change them before running the command.
 
 
+.new
+.subsection "The ATRN command" SECTATRN
+.cindex ATRN processing
+.cindex "ESMTP extensions" ATRN
+A second method for intermittently-connecting destinations
+is specified by
+&url(https://www.rfc-editor.org/rfc/rfc2645.html,RFC 2645).
+
+This describes an ESMTP command called ATRN which requests
+a swap in server/client roles of the communicating endpoints, and delivery
+of queued messages.
+Note that this supports customers having IP addresses that
+change frequently.
+
+Exim supports the &"provider"& side of ATRN, using the terms
+of that specification:
+initially as an SMTP server, then transferring to an SMTP client
+role if an ATRN command is accepted.
+
+.oindex "&%acl_smtp_atrn%&"
+The command is only available if permitted by an ACL
+specfied by the main-section &%acl_smtp_atrn%& option.
+Per the standard, this should only be for a specific
+provider port number (386, named "odmr");
+Exim should be configured to listen on that port
+(in addition to other duties) via &%daemon_smtp_ports%&
+or equivalent commandline options, and restrict the
+advertising of the facility to the port:
+.code
+acl_smtp_atrn = ${if = {$received_port}{386} {check_atrn}{}}
+.endd
+
+A recieved ATRN command will be rejected unless
+authentication has previously been done on the connection.
+
+Any arguments supplied with an ATRN command are (per standard)
+a comma-separated list of requested domains,
+and will be available in the &$smtp_command_argument$&
+variable.
+
+The ACL configured may return &"deny"& for any policy reaons
+(for example, the authenticated user is not permitted the facility).
+Otherwise it should use the ACL &"atrn_domains"& condition,
+which returns true if there are queued messages for any of
+the given list of domains.
+If that condition fails the ACL should return &"defer"&
+with a "453 You have no mail" response;
+else it should return &"accept"&.
+
+For example (with default domain handling, and one possible de-taint method) :
+.code
+check_atrn:
+  warn  set acl_m0 = clientdom.net
+  deny  condition = ${if def:smtp_command_argument}
+        set acl_m0 = ${map \
+          {<, $smtp_command_argument} \
+          {${if inlist{$item}{clientdom.net:cl2dom.net} {$value}}} \
+          }
+        condition = ${if !def:acl_m0}
+  defer !atrn_domains = <, $acl_m0
+        message = 453 You have no mail
+  accept
+.endd
+
+Acceptance by the ACL will result in a queue-run for messages
+having addresses with the given domains.
+A suitable router and transport must be configured for the deliveries.
+
+To access a named queue
+.cindex queue named
+the ACL should use a "queue =" modifier before the "atrn_domains"
+condition.
+If the ACL does not accept, re-set the queue to an empty value
+so as to not disrupt any later SMTP operations on the connection.
+
+Use of the &"atrn_domains"& condition additionally sets up
+the &$atrn_host$& variable, which can be used by a manualroute
+router.  Being otherwise empty, this router will decline in
+other situations so can be safely placed in a general router chain.
+
+For example:
+.code
+begin routers
+odmr_client:
+  driver =      manualroute
+  route_data =  <;$atrn_host
+  transport =   client_smtp
+
+begin transports
+client_smtp:
+  driver =      smtp
+.endd
+
+Although not discssed in the specification document,
+Exim supports use of ATRN within a STARTTLS-
+or TLS-on-connect- encrypted connection
+(which is wise if a plaintext authentication mechanism is used).
+In such cases the TLS connection will remain open across the
+role-swap, and be used for the sending of queued messages.
+
+Note that the RFC requires that the CRAM-MD5 authentication
+method be supported.
+Exim does not enforce this, but leaves it up to the configuration;
+see chapter &<<CHID9>>&.
+
+.wen
+
+
 
 .section "Incoming local SMTP" "SECID238"
 .cindex "SMTP" "local incoming"
@@ -37947,7 +38158,8 @@ This accepts SMTP messages from local processes without doing any other tests.
 
 
 
-.section "Outgoing batched SMTP" "SECTbatchSMTP"
+.section "Batched SMTP" "SECTgenbatchSMTP"
+.subsection "Outgoing batched SMTP" "SECTbatchSMTP"
 .cindex "SMTP" "batched outgoing"
 .cindex "batched SMTP output"
 Both the &(appendfile)& and &(pipe)& transports can be used for handling
@@ -37994,7 +38206,7 @@ message (unless there are more than 1000 recipients).
 
 
 
-.section "Incoming batched SMTP" "SECTincomingbatchedSMTP"
+.subsection "Incoming batched SMTP" "SECTincomingbatchedSMTP"
 .cindex "SMTP" "batched incoming"
 .cindex "batched SMTP input"
 The &%-bS%& command line option causes Exim to accept one or more messages by