base.m4: Tweakable TLS parameters in `smtp' transport.
[exim-config] / base.m4
diff --git a/base.m4 b/base.m4
index 9182a7c..6c2dc7b 100644 (file)
--- a/base.m4
+++ b/base.m4
@@ -59,11 +59,10 @@ gecos_pattern = ([^,:]*)
 
 SECTION(global, incoming)m4_dnl
 received_header_text = Received: \
-       ${if def:sender_rcvhost {from $sender_rcvhost\n\t} \
+       ${if def:sender_rcvhost \
+            {from $sender_rcvhost\n\t} \
             {${if def:sender_ident \
-                  {from ${quote_local_part:$sender_ident} }}\
-             ${if def:sender_helo_name \
-                  {(helo=$sender_helo_name)\n\t}}}}\
+                  {from ${quote_local_part:$sender_ident} }}}}\
        by $primary_hostname \
        (Exim $version_number)\
        ${if def:tls_cipher {\n\t} { }}\
@@ -71,7 +70,7 @@ 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}}\
        id $message_exim_id\
@@ -89,6 +88,18 @@ qualify_domain = CONF_master_domain
 SECTION(global, bounce)m4_dnl
 delay_warning = 1h : 24h : 2d
 
+SECTION(global, tls)m4_dnl
+tls_certificate = CONF_sysconf_dir/server.cert
+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.
@@ -98,9 +109,39 @@ SECTION(global, acl)m4_dnl
 acl_smtp_helo = helo
 SECTION(acl, misc)m4_dnl
 helo:
-       require  message = The other one has bells on
-                verify = helo
+       ## 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.
+       ## Only we can't do this the easy way, so save it up for use in MAIL.
+       ## Also, we're liable to get a subsequent HELO (e.g., after STARTTLS)
+       ## 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}}}}}}
+               !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
 
+       ## Done.
        accept
 
 SECTION(global, acl)m4_dnl
@@ -108,6 +149,15 @@ 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.
+       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
+
        ## Always allow the empty sender, so that we can receive bounces.
        accept   senders = :
 
@@ -121,6 +171,15 @@ mail:
        warn     condition = ${if eq{$acl_c_mode}{submission}}
                 control = submission
 
+       ## 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
@@ -138,7 +197,7 @@ 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
 
@@ -187,6 +246,51 @@ expn_vrfy:
 
 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 = *
+                set acl_c_user = $sender_ident
+
+       ## User must be authenticated.
+       deny     message = Sender not authenticated
+               !hosts = +thishost
+               !authenticated = *
+
+       ## Make sure that the local part is one that the authenticated sender
+       ## is allowed to claim.
+       deny     message = Sender address forbidden to calling user
+               !condition = ${LOOKUP_DOMAIN($sender_address_domain,
+                              {${if and {{match_local_part \
+                                           {$acl_c_user} \
+                                           {+dom_users}} \
+                                         {match_local_part \
+                                           {$sender_address_local_part} \
+                                           {+dom_locals}}}}},
+                              {${if and {{match_local_part \
+                                           {$sender_address_local_part} \
+                                           {+user_extaddr}} \
+                                         {or {{eq {$sender_address_domain} \
+                                                  {}} \
+                                              {match_domain \
+                                                {$sender_address_domain} \
+                                                {+public}}}}}}})}
+
+       ## All done.
+       accept
+
+DIVERT(null)
+###--------------------------------------------------------------------------
 ### Common options for forwarding routers.
 
 ## We're pretty permissive here.
@@ -222,6 +326,15 @@ 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)
 ###--------------------------------------------------------------------------
 ### Some standard transports.
@@ -232,12 +345,26 @@ m4_define(<:USER_DELIVERY:>,
        return_path_add = true:>)
 
 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.
 smtp:
        driver = smtp
-       tls_require_ciphers = CONF_acceptable_ciphers
+       hosts_require_tls = DOMKV(tls-peer-ca, {*}{})
+       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})
+       ## Can't set this to an expansion. :-(
+       m4_dnl tls_dh_min_bits = DOMKV(dh-min-bits, {$value}{1020})
        tls_dh_min_bits = 1020
        tls_tempfail_tryclear = true