satellite.m4: Apply `/etc/aliases' to master-domain-qualified addresses.
[exim-config] / base.m4
diff --git a/base.m4 b/base.m4
index e0d9979..0aadb78 100644 (file)
--- a/base.m4
+++ b/base.m4
@@ -26,6 +26,7 @@
 
 SECTION(global, priv)m4_dnl
 admin_groups = CONF_admin_groups
+trusted_groups = CONF_trusted_groups
 prod_requires_admin = false
 
 SECTION(global, logging)m4_dnl
@@ -39,10 +40,11 @@ syslog_timestamp = false
 
 SECTION(global, daemon)m4_dnl
 local_interfaces = <; CONF_interfaces
-extra_local_interfaces = <; 0.0.0.0 ; ::
+extra_local_interfaces = <; 0.0.0.0 ; ::0
 
 SECTION(global, resource)m4_dnl
 deliver_queue_load_max = 8
+message_size_limit = 500M
 queue_only_load = 12
 smtp_accept_max = 16
 smtp_accept_queue = 32
@@ -58,11 +60,11 @@ gecos_name = $1
 gecos_pattern = ([^,:]*)
 
 SECTION(global, incoming)m4_dnl
+rfc1413_hosts = *
+rfc1413_query_timeout = 10s
 received_header_text = Received: \
        ${if def:sender_rcvhost \
-            {from $sender_rcvhost\
-             ${if def:sender_helo_name \
-                  { (helo=$sender_helo_name)}}\n\t} \
+            {from $sender_rcvhost\n\t} \
             {${if def:sender_ident \
                   {from ${quote_local_part:$sender_ident} }}}}\
        by $primary_hostname \
@@ -72,24 +74,49 @@ received_header_text = Received: \
             {with $received_protocol \
              ${if def:tls_cipher {(cipher=$tls_cipher)}}}}\n\t\
        ${if def:sender_address \
-            {(envelope-from <$sender_address>\
+            {(envelope-from $sender_address\
              ${if def:authenticated_id \
-                  {; auth=$authenticated_id}})\n\t}}\
+                  {; auth=${quote_local_part:$authenticated_id}} \
+                  {${if and {{def:authenticated_sender} \
+                             {match_address{$authenticated_sender} \
+                                           {*@CONF_master_domain}}} \
+                        {; auth=${quote_local_part:\
+                                   ${local_part:\
+                                     $authenticated_sender}}}}}})\n\t}}\
        id $message_exim_id\
        ${if def:received_for {\n\tfor $received_for}}
 
 SECTION(global, smtp)m4_dnl
 smtp_return_error_details = true
 accept_8bitmime = true
+chunking_advertise_hosts =
+
+SECTION(global, env)m4_dnl
+keep_environment =
 
 SECTION(global, process)m4_dnl
 extract_addresses_remove_arguments = false
 headers_charset = utf-8
 qualify_domain = CONF_master_domain
+untrusted_set_sender = *
+local_from_check = false
+local_sender_retain = true
 
 SECTION(global, bounce)m4_dnl
 delay_warning = 1h : 24h : 2d
 
+SECTION(global, tls)m4_dnl
+tls_certificate = CONF_certlist
+tls_privatekey = CONF_sysconf_dir/server.key
+tls_advertise_hosts = ${if exists {CONF_sysconf_dir/server.key} {*}{}}
+tls_dhparam = CONF_ca_dir/dh-param-2048.pem
+tls_require_ciphers = ${if or {{={$received_port}{CONF_submission_port}} \
+                              {match_ip {$sender_host_address}{+trusted}}} \
+                          {CONF_good_ciphers} \
+                          {CONF_acceptable_ciphers}}
+tls_verify_certificates = CONF_ca_dir/ca.cert
+tls_verify_hosts = ${if eq{$acl_c_mode}{submission} {} {+allnets}}
+
 DIVERT(null)
 ###--------------------------------------------------------------------------
 ### Access control lists.
@@ -99,6 +126,10 @@ SECTION(global, acl)m4_dnl
 acl_smtp_helo = helo
 SECTION(acl, misc)m4_dnl
 helo:
+       ## Don't worry if this is local submission.  MUAs won't necessarily
+       ## have a clear idea of their hostnames.  (For some reason.)
+       accept   condition = ${if eq{$acl_c_mode}{submission}}
+
        ## Check that the caller's claimed identity is actually plausible.
        ## This seems like it's a fairly effective filter on spamminess, but
        ## it's too blunt a tool.  Rather than reject, add a warning header.
@@ -107,49 +138,92 @@ helo:
        ## and we should only care about the most recent one.
        warn     set acl_c_helo_warning = false
                !condition = \
+                       ${if and {{match_ip {$sender_host_address} \
+                                           {<; 127.0.0.0/8 ; ::1}} \
+                                 {match_domain {$sender_helo_name} \
+                                               {localhost : +thishost}}}}
+               !condition = \
                        ${if exists {CONF_sysconf_dir/helo.conf} \
                             {${lookup {$sender_helo_name} \
                                       partial0-lsearch \
                                       {CONF_sysconf_dir/helo.conf} \
                                       {${if match_ip \
                                             {$sender_host_address} \
-                                            {$value}}}}}}
+                                            {<; $value}}}}}}
                !verify = helo
                 set acl_c_helo_warning = true
 
        accept
 
 SECTION(global, acl)m4_dnl
+acl_not_smtp_start = not_smtp_start
+SECTION(acl, misc)m4_dnl
+not_smtp_start:
+       ## Record the user's name.
+       warn     set acl_c_user = $sender_ident
+                set acl_m_user = $sender_ident
+
+       ## Done.
+       accept
+
+SECTION(global, acl)m4_dnl
 acl_smtp_mail = mail
 SECTION(acl, mail)m4_dnl
 mail:
 
        ## If we stashed a warning header about HELO from earlier, we should
-       ## add it now.
+       ## add it now.  Only don't bother if the client has authenticated
+       ## successfully for submission (because we can't expect mobile
+       ## clients to be properly set up knowing their names), or it's one of
+       ## our own satellites (because they're either properly set up anyway,
+       ## or satellites using us as a smarthost).
        warn     condition = $acl_c_helo_warning
-                add_header = :after_received:X-Distorted-Warning: \
-                       BADHELO \
-                       Client's HELO doesn't match its IP address.\n\t\
-                       HELO name = $sender_helo_name, \
-                       address = $sender_host_address
+               !condition = ${if eq{$acl_c_mode}{submission}}
+               !hosts = +allnets
+                WARNING_HEADER(BADHELO,
+                               <:Client's HELO doesn't match its IP address.\n\t\
+                                 helo-name=$sender_helo_name \
+                                 address=$sender_host_address:>)
 
        ## Always allow the empty sender, so that we can receive bounces.
        accept   senders = :
 
-       ## Ensure that the sender is routable.  This is important to prevent
-       ## undeliverable bounces.
-       require  message = Invalid sender; \
-                       ($sender_verify_failure; $acl_verify_message)
-                verify = sender
+       ## Ensure that the sender looks valid.
+       require  acl = mail_check_sender
 
        ## If this is directly from a client then hack on it for a while.
        warn     condition = ${if eq{$acl_c_mode}{submission}}
-                control = submission
+                control = submission/sender_retain
+
+       ## Insist that a local client connect through TLS.
+       deny     message = Hosts within CONF_master_domain must use TLS
+               !condition = ${if eq{$acl_c_mode}{submission}}
+                hosts = +allnets
+               !encrypted = *
+
+       ## Check that a submitted message's sender address is allowable.
+       require  acl = mail_check_auth
 
 SECTION(acl, mail-tail)m4_dnl
        ## And we're done.
        accept
 
+SECTION(acl, misc)m4_dnl
+mail_check_sender:
+
+       ## See whether there's a special exception for this sender domain.
+       accept   senders = ${LOOKUP_DOMAIN($sender_address_domain,
+                            {KV(senders)})}
+
+       ## Ensure that the sender is routable.  This is important to prevent
+       ## undeliverable bounces.
+       require  message = Invalid sender; \
+                       ($sender_verify_failure; $acl_verify_message)
+                verify = sender
+
+       ## We're good, then.
+       accept
+
 SECTION(global, acl)m4_dnl
 acl_smtp_connect = connect
 SECTION(acl, connect)m4_dnl
@@ -163,12 +237,13 @@ SECTION(acl, connect-tail)m4_dnl
 
 check_submission:
        ## See whether this message needs hacking on.
-       accept  !hosts = +localnet
+       accept  !hosts = +thishost
                !condition = ${if ={$received_port}{CONF_submission_port}}
                 set acl_c_mode = relay
 
        ## Remember to apply submission controls.
        warn     set acl_c_mode = submission
+                control = no_enforce_sync
 
        ## Done.
        accept
@@ -180,16 +255,35 @@ rcpt:
 
        ## Reject if the client isn't allowed to relay and the recipient
        ## isn't in one of our known domains.
-       deny     message = Relaying not permitted
-               !hosts = CONF_relay_clients
-               !authenticated = *
-               !domains = +known
+       require  message = Relaying not permitted
+                acl = check_relay
 
        ## Ensure that the recipient is routable.
        require  message = Invalid recipient \
                        ($recipient_verify_failure; $acl_verify_message)
                 verify = recipient
 
+SECTION(acl, misc)m4_dnl
+check_relay:
+       ## Accept either if the client is allowed to relay through us, or if
+       ## we're the correct place to send this mail.
+
+       ## Known clients and authenticated users are OK.
+       accept   hosts = CONF_relay_clients
+       accept   authenticated = *
+
+       ## Known domains are OK.
+       accept   domains = +public
+
+       ## Finally, domains in our table are OK, unless they say they aren't.
+       accept   domains = \
+                ${if exists{CONF_sysconf_dir/domains.conf} \
+                    {partial0-lsearch; CONF_sysconf_dir/domains.conf}}
+                condition = DOMKV(service, {$value}{true})
+
+       ## Nope, that's not allowed.
+       deny
+
 SECTION(acl, rcpt-tail)m4_dnl
        ## Everything checks out OK: let this one go through.
        accept
@@ -198,6 +292,10 @@ SECTION(global, acl)m4_dnl
 acl_smtp_data = data
 SECTION(acl, data)m4_dnl
 data:
+       ## Don't accept messages with overly-long lines.
+       deny     message = line length exceeds SMTP permitted maximum: \
+                       $max_received_linelength > 998
+                condition = ${if >{$max_received_linelength}{998}}
 
 SECTION(acl, data-tail)m4_dnl
        accept
@@ -205,13 +303,43 @@ SECTION(acl, data-tail)m4_dnl
 SECTION(global, acl)m4_dnl
 acl_smtp_expn = expn_vrfy
 acl_smtp_vrfy = expn_vrfy
-SECTION(acl)m4_dnl
+SECTION(acl, misc)m4_dnl
 expn_vrfy:
        accept   hosts = +trusted
        deny     message = Suck it and see
 
 DIVERT(null)
 ###--------------------------------------------------------------------------
+### Verification of sender address.
+
+SECTION(acl, misc)m4_dnl
+mail_check_auth:
+
+       ## If this isn't a submission then it doesn't need checking.
+       accept   condition = ${if !eq{$acl_c_mode}{submission}}
+
+       ## If the caller hasn't formally authenticated, but this is a
+       ## loopback connection, then we can trust identd to tell us the right
+       ## answer.  So we should stash the right name somewhere consistent.
+       warn     set acl_c_user = $authenticated_id
+                hosts = +thishost
+               !authenticated = *
+                condition = ${if def:sender_ident}
+                set acl_c_user = $sender_ident
+
+       ## User must be authenticated by now.
+       deny     message = Sender not authenticated
+                condition = ${if !def:acl_c_user}
+
+       ## Set the per-message authentication flag, since we now know that
+       ## there's a sensible value.
+       warn     set acl_m_user = $acl_c_user
+
+       ## All done.
+       accept
+
+DIVERT(null)
+###--------------------------------------------------------------------------
 ### Common options for forwarding routers.
 
 ## We're pretty permissive here.
@@ -247,6 +375,31 @@ m4_define(<:FILTER_TRANSPORTS:>,
        pipe_transport = pipe
        reply_transport = reply:>)
 
+m4_define(<:FILTER_ROUTER:>,
+<:$1_vrf:
+       $2
+       FILTER_VERIFY<::>$3
+$1:
+       $2
+       verify = no
+       FILTER_TRANSPORTS<::>$4:>)
+
+DIVERT(null)
+###--------------------------------------------------------------------------
+### Common routers.
+
+SECTION(routers, alias)m4_dnl
+## Look up the local part in the address map.
+alias:
+       driver = redirect
+       allow_fail = true
+       allow_defer = true
+       user = CONF_filter_user
+       FILTER_TRANSPORTS
+       local_parts = nwildlsearch; CONF_alias_file
+       data = ${expand:$local_part_data}
+SECTION(routers, alias-opts)m4_dnl
+
 DIVERT(null)
 ###--------------------------------------------------------------------------
 ### Some standard transports.
@@ -256,34 +409,149 @@ m4_define(<:USER_DELIVERY:>,
        envelope_to_add = true
        return_path_add = true:>)
 
+m4_define(<:APPLY_HEADER_CHANGES:>,
+       <:headers_add = m4_ifelse(<:$1:>, <::>,
+               <:$acl_m_hdradd:>,
+               <:${if def:acl_m_hdradd{$acl_m_hdradd\n}}\
+               $1:>)
+       headers_remove = m4_ifelse(<:$2:>, <::>,
+               <:$acl_m_hdrrm:>,
+               <:${if def:acl_m_hdrrm{$acl_m_hdrrm:}}\
+               $2:>):>)
+
+m4_define(<:DKIM_SIGN_P:>,
+       <:and {{exists{CONF_sysconf_dir/dkim-sign.conf}} \
+              {!def:h_DKIM-Signature:} \
+              {!def:h_List-ID:} \
+              {or {{def:authenticated_id} \
+                   {def:authenticated_sender}}}}:>)
+
+m4_define(<:DKIM_KEYS_INSTANCE:>,
+       <:${lookup {${domain:$h_From:}} partial0-lsearch \
+                       {CONF_sysconf_dir/dkim-sign.conf} \
+               _LOOKUP_ARGS(<:$1:>, <:$2:>)}:>)
+m4_define(<:DKIM_KEYS_STATE:>, <:${lookup {$1} lsearch \
+               {DKIM_KEYS_INSTANCE(<:{CONF_dkim_keys_dir/$value/active/dkim-keys.state}:>)} \
+       _LOOKUP_ARGS(<:$2:>, <:$3:>, <:fail:>)}:>)
+m4_define(<:DKIM_KEYS_INFO:>, <:DKIM_KEYS_STATE(<:params:>,
+       <:{${if and {{>={$tod_epoch}{KV(t0)}} \
+                   {<{$tod_epoch}{${eval:KV(t0) + KV(n)*KV(step)}}}} \
+               {DKIM_KEYS_STATE(<:info.${eval:($tod_epoch - KV(t0))/KV(step)}:>,
+                       <:$1:>, <:$2:>)} \
+               m4_ifelse(<:$2:>, <::>, <:fail:>, <:$2:>)}}:>,
+       m4_ifelse(<:$2:>, <::>, <:fail:>, <:$2:>)):>)
+
+m4_define(<:DKIM_SIGN:>,
+       <:dkim_domain = \
+               ${if DKIM_SIGN_P \
+                       {DKIM_KEYS_INSTANCE({${domain:$h_From:}})}}
+       dkim_selector = DKIM_KEYS_INFO(<:{KV(k)}:>)
+       dkim_private_key = \
+               DKIM_KEYS_INSTANCE(<:m4_dnl
+                       {CONF_dkim_keys_dir/$value/active/$dkim_selector.priv}:>)
+       dkim_canon = relaxed
+       dkim_strict = true
+       ## The following ridiculous stunt does two important jobs.  Firstly,
+       ## and more obviously, it arranges to include one more copy of each
+       ## header name than the message actually contains, thereby causing
+       ## the signature to fail if another header with the same name is
+       ## added.  And secondly, and far more subtly, it also trims the
+       ## spaces from the header names so that they're in the format that
+       ## the signing machinery secretly wants.
+       dkim_sign_headers = \
+               ${sg {${map {CONF_dkim_headers : \
+                            X-CONF_header_token-DKIM-Key-Publication} \
+                           {$item${sg {${expand:\$h_$item:}\n} \
+                                      {((?:[^\n]+|\n\\s+)*)\n} \
+                                      {:$item}}}}} \
+                    {::}{:}}
+       headers_add = \
+               ${if DKIM_SIGN_P \
+                       {DKIM_KEYS_INFO(<:m4_dnl
+                               {X-CONF_header_token-DKIM-Key-Publication: \
+                                       DKIM signature not suitable \
+                                       as evidence after delivery;\n\t\
+                                       DKIM private key KV(k) will be \
+                                       published\n\t\
+                                       at KV(u)\n\t\
+                                       on or before KV(tpub)}:>)}}:>)
+
+
+m4_define(<:SMTP_DELIVERY:>,
+       <:## Prevent sending messages with overly long lines.  The use of
+       ## `message_size_limit' here is somewhat misleading.
+       message_size_limit = ${if >{$max_received_linelength}{998}{1}{0}}:>)
+
 SECTION(transports)m4_dnl
-## A standard transport for remote delivery.  Try to do TLS, and don't worry
-## too much if it's not very secure: the alternative is sending in plaintext
-## anyway.
+## A standard transport for remote delivery.  By default, try to do TLS, and
+## don't worry too much if it's not very secure: the alternative is sending
+## in plaintext anyway.  But all of this can be overridden from the
+## `domains.conf' file.  Annoyingly, the `tls_dh_min_bits' setting isn't
+## expanded before use, so we can't set it the obvious way.  Instead, encode
+## it into the transport name.  This is very unpleasant, of course.
 smtp:
        driver = smtp
+       SMTP_DELIVERY
+       APPLY_HEADER_CHANGES
+       DKIM_SIGN
        tls_require_ciphers = CONF_acceptable_ciphers
-       tls_dh_min_bits = 1020
+       tls_dh_min_bits = 508
        tls_tempfail_tryclear = true
 
+m4_define(<:SMTP_TRANS_DHBITS:>,
+       <:driver = smtp
+       SMTP_DELIVERY
+       APPLY_HEADER_CHANGES
+       DKIM_SIGN
+       hosts_try_auth = *
+       hosts_require_tls = DOMKV(tls-peer-ca, {*}{})
+       hosts_require_auth = \
+               ${if bool {DOMKV(require-auth, {$value}{false})} {*}{}}
+       tls_certificate = DOMKV(tls-certificate, {${expand:$value}}fail)
+       tls_privatekey = DOMKV(tls-private-key, {${expand:$value}}fail)
+       tls_verify_certificates = DOMKV(tls-peer-ca, {${expand:$value}}fail)
+       tls_require_ciphers = \
+               DOMKV(tls-ciphers,
+                     {${extract {${expand:$value}} \
+                                { good = CONF_good_ciphers \
+                                  any = CONF_acceptable_ciphers } \
+                                {$value} \
+                                {${expand:$value}}}} \
+                     {CONF_acceptable_ciphers})
+       tls_dh_min_bits = $1
+       tls_tempfail_tryclear = true:>)m4_dnl
+smtp_dhbits_512:
+       SMTP_TRANS_DHBITS(508)
+smtp_dhbits_768:
+       SMTP_TRANS_DHBITS(764)
+smtp_dhbits_1024:
+       SMTP_TRANS_DHBITS(1020)
+smtp_dhbits_2048:
+       SMTP_TRANS_DHBITS(2044)
+
 ## Transport to a local SMTP server; use TLS and perform client
 ## authentication.
 smtp_local:
        driver = smtp
+       SMTP_DELIVERY
+       APPLY_HEADER_CHANGES
        hosts_require_tls = *
-       tls_certificate = CONF_sysconf_dir/client.cert
+       tls_certificate = CONF_sysconf_dir/client.certlist
        tls_privatekey = CONF_sysconf_dir/client.key
        tls_verify_certificates = CONF_ca_dir/ca.cert
        tls_require_ciphers = CONF_good_ciphers
        tls_dh_min_bits = 2046
        tls_tempfail_tryclear = false
-       authenticated_sender = ${if def:authenticated_id \
-                                   {$authenticated_id@CONF_master_domain} \
-                                   fail}
+       authenticated_sender_force = true
+       authenticated_sender = \
+               ${if def:acl_m_user {$acl_m_user@CONF_master_domain} \
+                    {${if def:authenticated_sender {$authenticated_sender} \
+                          fail}}}
 
 ## A standard transport for local delivery.
 deliver:
        driver = appendfile
+       APPLY_HEADER_CHANGES
        file = /var/mail/$local_part
        group = mail
        mode = 0600
@@ -293,17 +561,20 @@ deliver:
 ## Transports for user filters.
 mailbox:
        driver = appendfile
+       APPLY_HEADER_CHANGES
        initgroups = true
        USER_DELIVERY
 
 maildir:
        driver = appendfile
+       APPLY_HEADER_CHANGES
        maildir_format = true
        initgroups = true
        USER_DELIVERY
 
 pipe:
        driver = pipe
+       APPLY_HEADER_CHANGES
        path = ${if and {{def:home} {exists{$home/bin}}} {$home/bin:} {}}\
                /usr/local/bin:/usr/local/sbin:\
                /usr/bin:/usr/sbin:/bin:/sbin
@@ -322,6 +593,13 @@ DIVERT(null)
 ### Retry configuration.
 
 SECTION(retry, default)m4_dnl
+## Be persistent when sending to the site relay.  It ought to work, but
+## particularly satellites such as laptops often encounter annoying temporary
+## failures due to network unavailability, and the usual gradual policy can
+## leave mail building up for no good reason.
+CONF_smarthost                         * \
+       F,4d,15m
+
 ## Default.
 *                                      * \
        F,2h,15m; G,16h,2h,1.5; F,4d,6h