SECTION(global, priv)m4_dnl
admin_groups = CONF_admin_groups
+trusted_groups = CONF_trusted_groups
prod_requires_admin = false
SECTION(global, logging)m4_dnl
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
gecos_pattern = ([^,:]*)
SECTION(global, incoming)m4_dnl
+rfc1413_hosts = *
+rfc1413_query_timeout = 10s
received_header_text = Received: \
${if def:sender_rcvhost \
{from $sender_rcvhost\n\t} \
${if def: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.
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.
{CONF_sysconf_dir/helo.conf} \
{${if match_ip \
{$sender_host_address} \
- {$value}}}}}}
+ {<; $value}}}}}}
!verify = helo
set acl_c_helo_warning = true
not_smtp_start:
## Record the user's name.
warn set acl_c_user = $sender_ident
+ set acl_m_user = $sender_ident
## Done.
accept
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
## 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
## Remember to apply submission controls.
warn set acl_c_mode = submission
+ control = no_enforce_sync
## Done.
accept
## 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
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
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
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.
+ ## User must be authenticated by now.
deny message = Sender not authenticated
- !hosts = +thishost
- !authenticated = *
+ condition = ${if !def:acl_c_user}
- ## 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}}}}}}})}
+ ## 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 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.
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
+ dkim_sign_headers = CONF_dkim_headers : \
+ X-CONF_header_token-DKIM-Key-Publication
+ headers_add = \
+ ${if DKIM_SIGN_P \
+ {DKIM_KEYS_INFO(<:m4_dnl
+ {X-CONF_header_token-DKIM-Key-Publication: \
+ DKIM signature not suitable for \
+ as evidence after delivery; \
+ DKIM private key KV(k) will be \
+ published at KV(u) 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
## 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
### 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