vmail-stack/etc/exim4/exim4.conf
2021-12-13 16:35:42 -08:00

1199 lines
47 KiB
Plaintext

# $Id: exim.conf 228 2005-07-27 17:06:36Z root $
######################################################################
# Runtime configuration file for Exim #
######################################################################
disable_ipv6 = true
keep_environment =
add_environment = PATH=/usr/sbin:/usr/bin:/sbin:/bin
smtp_enforce_sync = false
bounce_message_file = /etc/exim4/bounce_message_text
smtp_accept_max = 50
smtp_accept_max_per_host = 10
smtp_accept_queue_per_connection = 50
.include /etc/exim4/exim_local.conf
tls_advertise_hosts = *
tls_certificate = ${if exists{/etc/ssl/letsencrypt/${tls_sni}.pem}{/etc/ssl/letsencrypt/${tls_sni}.pem}}
tls_on_connect_ports = 465
daemon_smtp_ports = 25 : 465 : 587
# This is a default configuration file which will operate correctly in
# uncomplicated installations. Please see the manual for a complete list
# of all the runtime configuration options that can be included in a
# configuration file. There are many more than are mentioned here. The
# manual is in the file doc/spec.txt in the Exim distribution as a plain
# ASCII file. Other formats (PostScript, Texinfo, HTML, PDF) are available
# from the Exim ftp sites. The manual is also online at the Exim web sites.
# This file is divided into several parts, all but the first of which are
# headed by a line starting with the word "begin". Only those parts that
# are required need to be present. Blank lines, and lines starting with #
# are ignored.
########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ###########
# #
# Whenever you change Exim's configuration file, you *must* remember to #
# HUP the Exim daemon, because it will not pick up the new configuration #
# until you do. However, any other Exim processes that are started, for #
# example, a process started by an MUA in order to send a message, will #
# see the new configuration as soon as it is in place. #
# #
# You do not need to HUP the daemon for changes in auxiliary files that #
# are referenced from this file. They are read every time they are used. #
# #
# It is usually a good idea to test a new configuration for syntactic #
# correctness before installing it (for example, by running the command #
# "exim -C /config/file.new -bV"). #
# #
########### IMPORTANT ########## IMPORTANT ########### IMPORTANT ###########
######################################################################
# MAIN CONFIGURATION SETTINGS #
######################################################################
# Specify your host's canonical name here. This should normally be the fully
# qualified "official" name of your host. If this option is not set, the
# uname() function is called to obtain the name. In many cases this does
# the right thing and you need not set anything explicitly.
queue_only_load = 3
log_selector = +all
# The next three settings create two lists of domains and one list of hosts.
# These lists are referred to later in this configuration using the syntax
# +local_domains, +relay_to_domains, and +relay_from_hosts, respectively. They
# are all colon-separated lists:
domainlist local_domains = ${lookup mysql{SELECT domain FROM vm_domains WHERE domain='$domain' AND status = '1'}}
domainlist relay_to_domains = /etc/exim4/relay_domains
hostlist relay_from_hosts = 127.0.0.1
hostlist skip_greylisting_hosts = /etc/exim4/skip_greylisting_hosts
# overide default sender checks
untrusted_set_sender = *
local_from_check = false
local_sender_retain = true
# trusted users, needed by spamcheck
trusted_users = mail
# local whitelist check macro:
WHITELISTED = ${lookup mysql{\
SELECT prefid FROM sa_userpref \
WHERE (username = '${quote_mysql:$local_part@$domain}' \
OR username = '\$GLOBAL' \
OR username = CONCAT('%','${quote_mysql:$domain}')) \
AND preference = 'whitelist_from' \
AND (value = '${quote_mysql:$sender_address}' \
OR value = CONCAT('\*@','${quote_mysql:$sender_address_domain}')) \
}{true}{false}}
# greylist options
# these need to be valid as xxx in mysql's DATE_ADD(..,INTERVAL xxx)
# not valid, for example, are plurals: "2 HOUR" instead of "2 HOURS"
GREYLIST_INITIAL_DELAY = 4 MINUTE
GREYLIST_INITIAL_LIFETIME = 12 HOUR
GREYLIST_WHITE_LIFETIME = 36 DAY
GREYLIST_BOUNCE_LIFETIME = 0 HOUR
# you can change the table names
GREYLIST_TABLE=vm_greylisting
GREYLIST_RESENDERS_TABLE=vm_greylisting_resenders
.ifdef GREYLIST_ENABLED
# database macros
GREYLIST_TEST = SELECT CASE \
WHEN now() > block_expires THEN "accepted" \
ELSE "deferred" \
END AS result, id \
FROM GREYLIST_TABLE \
WHERE (now() < record_expires) \
AND (sender = '${quote_mysql:$sender_address}' \
OR (type='MANUAL' \
AND ( sender IS NULL \
OR sender = '${quote_mysql:*@$sender_address_domain}' \
) \
) \
) \
AND (recipient = '${quote_mysql:$local_part@$domain}' \
OR (type = 'MANUAL' \
AND ( recipient IS NULL \
OR recipient = '${quote_mysql:$local_part@*}' \
OR recipient = '${quote_mysql:*@$domain}' \
) \
) \
) \
AND (relay_hostname = '${quote_mysql:$acl_m_sender}' \
OR (type='MANUAL' \
AND relay_hostname IS NULL) \
) \
ORDER BY result DESC LIMIT 1
GREYLIST_ADD = INSERT INTO GREYLIST_TABLE \
(relay_hostname, relay_ip, sender, recipient, block_expires, \
record_expires, create_time, type) \
VALUES ( '${quote_mysql:$acl_m_sender}', \
'${quote_mysql:$sender_host_address}', \
'${quote_mysql:$sender_address}', \
'${quote_mysql:$local_part@$domain}', \
DATE_ADD(now(), INTERVAL GREYLIST_INITIAL_DELAY), \
DATE_ADD(now(), INTERVAL GREYLIST_INITIAL_LIFETIME), \
now(), \
'AUTO' \
)
GREYLIST_DEFER_HIT = UPDATE GREYLIST_TABLE \
SET blockcount=blockcount+1 \
WHERE id = $acl_m9
GREYLIST_OK_COUNT = UPDATE GREYLIST_TABLE \
SET passcount=passcount+1 \
WHERE id = $acl_m9
GREYLIST_OK_NEWTIME = UPDATE GREYLIST_TABLE \
SET record_expires = DATE_ADD(now(), INTERVAL GREYLIST_WHITE_LIFETIME) \
WHERE id = $acl_m9 AND type='AUTO'
GREYLIST_OK_BOUNCE = UPDATE GREYLIST_TABLE \
SET record_expires = DATE_ADD(now(), INTERVAL GREYLIST_BOUNCE_LIFETIME) \
WHERE id = $acl_m9 AND type='AUTO'
GREYLIST_RESENDERS_UPDATE = REPLACE INTO GREYLIST_RESENDERS_TABLE SET hostname = '${quote_mysql:$acl_m_sender}'
.endif
# No deliveries will ever be run under the uids of these users (a colon-
# separated list). An attempt to do so causes a panic error to be logged, and
# the delivery to be deferred. This is a paranoic safety catch. Note that the
# default setting means you cannot deliver mail addressed to root as if it
# were a normal user. This isn't usually a problem, as most sites have an alias
# for root that redirects such mail to a human administrator.
never_users = root
# The setting below causes Exim to do a reverse DNS lookup on all incoming
# IP calls, in order to get the true host name. If you feel this is too
# expensive, you can specify the networks for which a lookup is done, or
# remove the setting entirely.
host_lookup = *
# The settings below, which are actually the same as the defaults in the
# code, cause Exim to make RFC 1413 (ident) callbacks for all incoming SMTP
# calls. You can limit the hosts to which these calls are made, and/or change
# the timeout that is used. If you set the timeout to zero, all RFC 1413 calls
# are disabled. RFC 1413 calls are cheap and can provide useful information
# for tracing problem messages, but some hosts and firewalls have problems
# with them. This can result in a timeout instead of an immediate refused
# connection, leading to delays on starting up an SMTP session.
rfc1413_hosts = *
rfc1413_query_timeout = 0s
# This option unfreezes frozen bounce messages after two days, tries
# once more to deliver them, and ignores any delivery failures.
ignore_bounce_errors_after = 0s
# This option cancels (removes) frozen messages that are older than a day.
timeout_frozen_after = 1d
# Treat DNS failures (SERVFAIL) as lookup failures.
# This is so that we can later reject sender addresses
# within non-existing domains, or domains for which no
# nameserver exists.
dns_again_means_nonexist = !+local_domains
# Enable HELO verification in ACLs for all hosts
helo_try_verify_hosts = *
# Advertise ESMTP "PIPELINING" to all hosts
pipelining_advertise_hosts = *
acl_smtp_connect = acl_connect
acl_smtp_helo = acl_helo
acl_smtp_mail = acl_mail_from
acl_smtp_dkim = acl_check_dkim
acl_smtp_rcpt = acl_rcpt_to
acl_smtp_data = acl_data
# set the av scanner to clamav
##av_scanner = clamd:/run/clamav/clamd.ctl
begin acl
# this acl returns either deny or accept
# since we use it inside a defer with acl = greylist_acl,
# accepting here makes the condition TRUE thus deferring,
# denying here makes the condition FALSE thus not deferring
.ifdef GREYLIST_ENABLED
greylist_acl:
# For regular deliveries, check greylist.
# check greylist tuple, returning "accepted", "deferred" or "unknown"
# in acl_m8, and the record id in acl_m9
warn set acl_m8 = ${lookup mysql{GREYLIST_TEST}{$value}{result=unknown}}
# here acl_m8 = "result=x id=y"
set acl_m9 = ${extract{id}{$acl_m8}{$value}{-1}}
# now acl_m9 contains the record id (or -1)
set acl_m8 = ${extract{result}{$acl_m8}{$value}{unknown}}
# now acl_m8 contains unknown/deferred/accepted
# check if we know a certain triple, add and defer message if not
accept
# if above check returned unknown (no record yet)
condition = ${if eq{$acl_m8}{unknown}{1}}
# then also add a record
condition = ${lookup mysql{GREYLIST_ADD}{yes}{no}}
# check if the triple is still blocked
accept
# if above check returned deferred then defer
condition = ${if eq{$acl_m8}{deferred}{1}}
# and note it down
condition = ${lookup mysql{GREYLIST_DEFER_HIT}{yes}{yes}}
# use a warn verb to count records that were hit
warn condition = ${lookup mysql{GREYLIST_OK_COUNT}}
# use a warn verb to set a new expire time on automatic records,
# but only if the mail was not a bounce, otherwise set to now().
warn !senders = : postmaster@*
condition = ${lookup mysql{GREYLIST_OK_NEWTIME}}
warn senders = : postmaster@*
condition = ${lookup mysql{GREYLIST_OK_BOUNCE}}
deny
add_header = X-DNS-Greylist: mail from $sender_address to $local_part@$domain accepted by greylisting
condition = ${lookup mysql{GREYLIST_RESENDERS_UPDATE}}
.endif
# This access control list is used at the start of an incoming
# connection. The tests are run in order until the connection
# is either accepted or denied.
acl_connect:
# In this pass, we do not perform any checks here.
accept
# This access control list is used for the HELO or EHLO command in
# an incoming SMTP transaction. The tests are run in order until the
# greeting is either accepted or denied.
acl_helo:
# Early rejection of brute-force bots under the name 'ylmf-pc'
deny
condition = ${if eq{$sender_helo_name}{ylmf-pc}{yes}{no}}
# In this pass, we do not perform any checks here.
accept
# This access control list is used for the MAIL FROM: command in an
# incoming SMTP transaction. The tests are run in order until the
# sender address is either accepted or denied.
#
acl_mail_from:
# Accept the command.
accept
# This access control list is used for every RCPT command in an
# incoming SMTP message. The tests are run in order until the
# recipient address is either accepted or denied.
acl_check_dkim:
######################################################################
# DomainKeys
######################################################################
# DKIM fail
accept
dkim_status = fail
logwrite = DKIM test failed: $dkim_verify_reason
add_header = X-DKIM-Status: fail: $dkim_verify_reason
add_header = X-Junk-Flag: YES
# DKIM invalid
accept
dkim_status = invalid
logwrite = DKIM test invalid: $dkim_verify_reason
add_header = X-DKIM-Status: invalid: $dkim_verify_reason
add_header = X-Junk-Flag: YES
# DKIM none
accept
dkim_status = none
logwrite = DKIM test none
add_header = X-DKIM-Status: none
# DKIM pass
accept
dkim_status = pass
logwrite = DKIM test passed
add_header = X-DKIM-Status: passed: (address=$sender_address domain=$dkim_cur_signer), signature is good.
# Accept the message.
accept
acl_rcpt_to:
# Accept mail received over local SMTP (i.e. not over TCP/IP).
# We do this by testing for an empty sending host field.
# Also accept mails received from hosts for which we relay mail.
#
# Recipient verification is omitted here, because in many
# cases the clients are dumb MUAs that don't cope well with
# SMTP error responses.
#
accept
hosts = : +relay_from_hosts
# Accept if the message arrived over an authenticated connection,
# from any host. Again, these messages are usually from MUAs, so
# recipient verification is omitted.
#
accept
authenticated = *
######################################################################
# Hello checks
######################################################################
# If the remote host greets with an IP address, then reject the mail.
#
deny
message = Message was delivered by ratware
log_message = remote host used IP address in HELO/EHLO greeting
condition = ${if isip {$sender_helo_name}{true}{false}}
# Likewise if the peer greets with one of our own names
#
deny
message = Message was delivered by ratware
log_message = remote host used our name in HELO/EHLO greeting.
condition = ${if match_domain{$sender_helo_name}\
{$primary_hostname:+local_domains}\
{true}{false}}
deny
message = Message was delivered by ratware
log_message = remote host did not present HELO/EHLO greeting.
condition = ${if def:sender_helo_name {false}{true}}
# If HELO verification fails, we add a X-HELO-Warning: header in
# the message.
#
warn
!verify = helo
message = X-HELO-Warning: Remote host $sender_host_address \
${if def:sender_host_name {($sender_host_name) }}\
incorrectly presented itself as $sender_helo_name
log_message = remote host presented unverifiable HELO/EHLO greeting.
# if "!verify = helo" & $send_host_name = '' reject???
######################################################################
# Sender Address Checks
######################################################################
# If we cannot verify the sender address, deny the message.
#
# You may choose to remove the "callout" option. In particular,
# if you are sending outgoing mail through a smarthost, it will not
# give any useful information.
#
# Details regarding the failed callout verification attempt are
# included in the 550 response; to omit these, change
# "sender/callout" to "sender/callout,no_details".
#
# deny
# message = <$sender_address> does not appear to be a \
# valid sender address.
# !verify = sender/callout
######################################################################
# Recipent Address Checks
######################################################################
# Ensure only valid SRS prefixed bounce message get accepted
deny
senders = :
domains = +local_domains
local_parts = ${if match {$local_part} {(?i)\N^SRS[01][=+-]\N} {$local_part}}
control = caseful_local_part
condition = ${if match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{\n}}}{^ERROR: .* Invalid hash at .*}}
message = Invalid reverse path (SRS check failed on $local_part@$domain).
warn
senders = :
domains = +local_domains
local_parts = ${if match {$local_part} {\N^srs[01][=+-]\N} {$local_part}}
control = caseful_local_part
condition = ${if match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{ }}}{^SRS: Case insensitive hash match detected. Someone smashed case in the local-part. .*}}
log_message = SRS hash smashed on the way for $local_part@$domain by case insensitive MTA.
# this is for debugging only. can be safely removed any time
warn
senders = :
domains = +local_domains
local_parts = ${if match {$local_part} {(?i)\N^SRS[01][=+-]\N} {$local_part}}
control = caseful_local_part
condition = ${if !match{${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part@$domain}{5s}{\n}}}{^ERROR: .* Invalid hash at .*}}
log_message = Incoming SRS bounce to $local_part@$domain
# Deny if the local part contains @ or % or / or | or !. These are
# rarely found in genuine local parts, but are often tried by people
# looking to circumvent relaying restrictions.
#
# Also deny if the local part starts with a dot. Empty components
# aren't strictly legal in RFC 2822, but Exim allows them because
# this is common. However, actually starting with a dot may cause
# trouble if the local part is used as a file name (e.g. for a
# mailing list).
#
deny
local_parts = ^.*[@%!/|] : ^\\.
# Drop the connection if the envelope sender is empty, but there is
# more than one recipient address. Legitimate DSNs are never sent
# to more than one address.
#
drop
message = Legitimate bounces are never sent to more than one recipient.
log_message = Legitimate bounces are never sent to more than one recipient (count: $recipients_count).
senders = : postmaster@*
condition = $recipients_count
# Reject the recipient address if it is not in a domain for
# which we are handling mail.
#
deny
message = relay not permitted
!domains = +local_domains : +relay_to_domains
# Reject the recipient if it is not a valid mailbox.
# If the mailbox is not on our system (e.g. if we are a
# backup MX for the recipient domain), then perform a
# callout verification; but if the destination server is
# not responding, accept the recipient anyway.
#
deny
message = unknown user
domains = +local_domains
!domains = +relay_to_domains
!verify = recipient/callout=no_cache,10s,defer_ok
# skip any further checks if the address is whitelisted
accept
condition = WHITELISTED
logwrite = From: $sender_address To: $local_part@$domain is whitelisted in sa_userpref
add_header = X-Whitelist-Flag: YES
# skip any further checks if the address is already marked Junk (by DKIM)
accept
condition = ${if eq {$header_X-Junk-Flag:}{YES}}
logwrite = Skipping SPF/DNSBL/Greylist checks as message already failed DKIM
######################################################################
# DNS checks
######################################################################
#
# The results of these checks are cached, so multiple recipients
# does not translate into multiple DNS lookups.
#
# check whitelists
# a match will:
# add X-DNS-Whitelist header
# skip the rest of the checks (DNS blacklist/greylist, SPF)
## as of Aug 2019 swl.spamhaus.org appears to be offline pending redesign
## list.dnswl.org does not work with large nameresolvers (over 100k queries / 24 hours)
## accept
## domains = +local_domains
## dnslists = swl.spamhaus.org : list.dnswl.org&0.0.0.2
## logwrite = $sender_host_address is whitelisted in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}}, adding X-DNS-Whitelist header
## add_header = X-DNS-Whitelist: $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}}
# add SPF header
accept
condition = ${run{/usr/bin/spfquery --scope mfrom \
--identity ${quote:$sender_address} \
--ip-address ${quote:$sender_host_address}} \
{no}{${if eq {$runrc}{1}{yes}{no}}}}
add_header = Received-SPF: fail
add_header = X-Junk-Flag: YES
## log_message = SPF check failed.
warn
condition = ${if eq {$runrc}{0}{yes}{no}}
add_header = Received-SPF: pass
## log_message = SPF check: pass
warn
condition = ${if eq {$runrc}{2}{yes}{no}}
add_header = Received-SPF: softfail
## log_message = SPF check: softfail
warn
condition = ${if eq {$runrc}{3}{yes}{no}}
add_header = Received-SPF: neutral
## log_message = SPF check: neutral
warn
condition = ${if eq {$runrc}{4}{yes}{no}}
add_header = Received-SPF: permerror
## log_message = SPF check: permerror
warn
condition = ${if eq {$runrc}{5}{yes}{no}}
add_header = Received-SPF: temperror
log_message = Temporary DNS error while checking SPF record.
warn
condition = ${if eq {$runrc}{6}{yes}{no}}
add_header = Received-SPF: none
## log_message = SPF check: none
warn
condition = ${if >{$runrc}{6}{yes}{no}}
log_message = Unexpected error in SPF check. spfquery returned $runrc
# check DNSbl(s) and if found add header for filtering to Junk
accept
## condition = ${if and { \
## { ! def:header_X-DNS-Whitelist: } \
## { eq {$header_X-Whitelist-Flag:}{YES}} \
## } {yes}{no}}
condition = ${if !eq {$header_X-Whitelist-Flag:}{YES}}
dnslists = zen.spamhaus.org
log_message = $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}}
add_header = X-DNS-Blacklist: $sender_host_address is listed in $dnslist_domain ${if def:dnslist_text {($dnslist_text)}}
add_header = X-Junk-Flag: YES
# will never get here if X-Whitelist-Flag or X-Junk-Flag (gets set with X-DNS-Blacklist) or X-DNS-Whitelist headers are set above
.ifdef GREYLIST_ENABLED
# if $sender_host_name is set use that.
# run command strips of leftmost subdomain if this is a third or higher level domain
warn
condition = ${if def:sender_host_name}
set acl_m_sender = ${run{/bin/bash /etc/exim4/return-resender.sh $sender_host_name}{$value}{$sender_host_name}}
# if $sender_host_name is not set use $sender_helo_name
# if sender_helo_name not set email was already rejected (deny) earlier in this acl
warn
condition = ${if !def:sender_host_name}
set acl_m_sender = $sender_helo_name
# bypass greylisting if the sending host is a known resender
accept
condition = ${lookup mysql{SELECT id FROM GREYLIST_RESENDERS_TABLE WHERE hostname = '${quote_mysql:$acl_m_sender}'}{yes}{no}}
condition = ${lookup mysql{UPDATE GREYLIST_RESENDERS_TABLE SET count=count+1, timestamp = NOW() WHERE hostname = '${quote_mysql:$acl_m_sender}'}{yes}{yes}}
add_header = X-DNS-Greylist: known resender
logwrite = skipping greylisting for $acl_m_sender due to match in GREYLIST_RESENDERS_TABLE
accept
hosts = +skip_greylisting_hosts
log_message = skipping greylisting due to match in skip_greylisting_hosts
defer
!senders = : postmaster@*
!hosts = +skip_greylisting_hosts
acl = greylist_acl
message = greylisted - try again later
.endif
# Otherwise, the recipient address is OK.
#
accept
# This access control list is used for message data received via
# SMTP. The tests are run in order until the recipient address
# is either accepted or denied.
acl_data:
# Add Message-ID if missing in messages received from our own hosts.
warn
condition = ${if !def:h_Message-ID: {1}}
hosts = : +relay_from_hosts
message = Message-ID: <E$message_id@$primary_hostname>
# add domain keys status header
# warn
# message = DomainKey-Status: $dk_status
# !condition = ${if eq{$dk_status}{}{1}{0}}
# Accept mail received over local SMTP (i.e. not over TCP/IP).
# We do this by testing for an empty sending host field.
# Also accept mails received from hosts for which we relay mail.
#
accept
hosts = : +relay_from_hosts
# Accept if the message arrived over an authenticated connection, from
# any host.
#
accept
authenticated = *
# Enforce a message-size limit
#
#deny
# message = Message size $message_size is larger than limit of \
# MESSAGE_SIZE_LIMIT
# condition = ${if >{$message_size}{MESSAGE_SIZE_LIMIT}{true}{false}}
# skip any further checks if the address is already marked Junk
accept
condition = ${if eq {$header_X-Junk-Flag:}{YES}}
logwrite = Skipping header_syntax and header_sender checks as message already marked Junk
# Deny unless the address list header is syntactically correct.
# Note that some specialized MTAs, such as certain mailing list
# servers, do not automatically generate a Message-ID for bounces.
# Thus, we add the check for a non-empty sender.
# (email feedback reports from aol fail this check)
#deny
accept
message = X-RFC2822-Error: Your message does not conform to RFC2822 standard
log_message = message header failed RFC2822 syntax check
!hosts = +relay_from_hosts
!senders = : postmaster@*
!verify = header_syntax
add_header = X-RFC2822-Error: Your message does not conform to RFC2822 standard
add_header = X-Junk-Flag: YES
# Warn unless there is a verifiable sender address in at least
# one of the "Sender:", "Reply-To:", or "From:" header lines.
warn
!verify = header_sender
log_message = No valid sender in message header
add_header = X-Sender-Verify-Failed: No valid sender in message header
# Deny if the message contains a virus. Before enabling this check, you
# must install a virus scanner and set the av_scanner option above.
## accept
## condition = ${if ! def:header_X-Junk-Flag: }
## malware = */defer_ok
## log_message = This message contains a virus ($malware_name).
## add_header = X-Virus-Warning: This message contains a virus ($malware_name).
## add_header = X-Junk-Flag: YES
# Accept the message.
#
accept
######################################################################
# ROUTERS CONFIGURATION #
# Specifies how addresses are handled #
######################################################################
# THE ORDER IN WHICH THE ROUTERS ARE DEFINED IS IMPORTANT! #
# An address is passed to each router in turn until it is accepted. #
######################################################################
begin routers
autowhitelist_filter:
driver = redirect
domains = ! +local_domains
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain = "$sender_address_domain" AND vm_mboxes.mbox = "$sender_address_local_part" AND vm_domains.id = vm_mboxes.domain_id}}
check_local_user = false
user = Debian-exim
file = /etc/exim4/autowhitelist.filter
no_verify
unseen
allow_filter = true
srs_bounce:
senders = :
driver = redirect
domains = +local_domains
allow_fail
allow_defer
local_part_prefix = srs0+ : srs0- : srs0= : srs1+ : srs1- : srs1=
caseful_local_part
address_data = ${readsocket{/run/srsd/srsd.sock}{REVERSE $local_part_prefix$local_part@$domain}{5s}{ }{:defer: SRS daemon failure}}
data = ${sg {$address_data} {^SRS: Case insensitive hash match detected. Someone smashed case in the local-part\. .* ([^ ]+)@([^ ]+)\$} {\N$1@$2\N} }
headers_add = X-SRS: Decoded valid SRS return address to ${quote_local_part:${local_part:$address_data}}@${domain:$address_data} by $primary_hostname
srs_forward:
driver = redirect
senders = ! : ! *@+local_domains
domains = ! +local_domains : ! +relay_to_domains
condition = ${lookup mysql{SELECT vm_domains.id FROM vm_domains WHERE vm_domains.domain='${original_domain}' AND vm_domains.status = '1'}}
address_data = ${readsocket{/run/srsd/srsd.sock}\
{FORWARD $sender_address_local_part@$sender_address_domain $original_domain\n}\
{5s}{\n}{:defer: SRS daemon failure}}
errors_to = ${quote_local_part:${local_part:$address_data}}@${domain:$address_data}
data = ${quote_local_part:$local_part}@$domain
headers_add = X-SRS-Forward: from $sender_address to $original_local_part@$original_domain forwarded to $local_part@$domain by $primary_hostname
repeat_use = false
allow_defer
no_verify
# This router routes addresses that are not in local domains by doing a DNS
# lookup on the domain name. Any domain that resolves to 0.0.0.0 or to a
# loopback interface address (127.0.0.0/8) is treated as if it had no DNS
# entry. Note that 0.0.0.0 is the same as 0.0.0.0/32, which is commonly treated
# as the local host inside the network stack. It is not 0.0.0.0/0, the default
# route. If the DNS lookup fails, no further routers are tried because of
# the no_more setting, and consequently the address is unrouteable.
dnslookup:
driver = dnslookup
self = pass
transport = remote_smtp
ignore_target_hosts = 0.0.0.0 : 127.0.0.0/8
no_more
# The remaining routers handle addresses in the local domain(s).
virtual_alias:
driver = redirect
domains = +local_domains
local_part_suffix = +*
local_part_suffix_optional = true
condition = ${if !eq {$received_protocol}{spam-scanned}}
data = ${lookup mysql{SELECT CONCAT(vm_mboxes.mbox,'@${domain}') FROM vm_aliases, vm_mboxes, vm_domains WHERE vm_aliases.alias='${local_part}' AND vm_aliases.mbox_id = vm_mboxes.id AND vm_mboxes.domain_id = vm_domains.id AND vm_domains.domain='${domain}' AND vm_domains.status = '1' AND vm_mboxes.status = '1'}}
junk_filter:
driver = accept
domains = +local_domains
local_part_suffix = +*
local_part_suffix_optional = true
condition = ${if and { \
{ !eq {$received_protocol}{spam-scanned}} \
{ eq {$header_X-Junk-Flag:}{YES}} \
{ !eq {$header_X-Whitelist-Flag:}{YES}} \
} {yes}{no}}
condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_mboxes.filter > '0'}}
transport = junk_delivery
spam_filter:
driver = accept
domains = +local_domains
local_part_suffix = +*
local_part_suffix_optional = true
condition = ${if and { \
{ eq {$received_protocol}{spam-scanned}} \
{ eq {$header_X-Spam-Flag:}{YES}} \
{ !eq {$header_X-Whitelist-Flag:}{YES}} \
} {yes}{no}}
condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_mboxes.filter = '2'}}
transport = junk_delivery
virtual_vacation:
driver = accept
domains = +local_domains
# do not reply to errors or lists or spam-scanned messages, require vacation message in db
condition = ${if and { \
{ !match {$h_precedence:} {(?i)junk|bulk|list}} \
{ !eq {$received_protocol}{spam-scanned}} \
{ !eq {$sender_address} {}} \
{ eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Vacation}} \
} {yes} {no}}
# add options for start & end date fields
no_expn
# do not reply to errors and bounces or lists
senders = " ! ^.*-request@.*:\
! ^owner-.*@.*:\
! ^postmaster@.*:\
! ^listmaster@.*:\
! ^mailer-daemon@.*\
! ^root@.*"
transport = vacation_transport
unseen
no_verify
virtual_autoresponder:
driver = accept
domains = +local_domains
# currently configured to *not* autorespond to + aliases
#local_part_suffix = +*
#local_part_suffix_optional = true
# do not reply to errors or lists or spam-scanned messages, require autoresponder message in db
condition = ${if and { \
{ !match {$h_precedence:} {(?i)junk|bulk|list}} \
{ !eq {$received_protocol}{spam-scanned}} \
{ !eq {$sender_address} {}} \
{ eq {${lookup mysql{SELECT vm_autoresponders.mode FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_autoresponders.status = '1'}{$value}fail}}{Autoresponder} } \
} {yes} {no}}
# add options for start & end date fields
no_expn
# do not reply to errors and bounces or lists
senders = " ! ^.*-request@.*:\
! ^owner-.*@.*:\
! ^postmaster@.*:\
! ^listmaster@.*:\
! ^mailer-daemon@.*\
! ^root@.*"
transport = autoresponder_transport
unseen
no_verify
virtual_forward_and_drop:
driver = redirect
domains = +local_domains
condition = ${if !eq {$received_protocol}{spam-scanned}}
local_part_suffix = +*
local_part_suffix_optional = true
data = ${lookup mysql{SELECT vm_forwards.forward_to FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_forwards.save_local='0'}}
virtual_forward_and_keep:
driver = redirect
domains = +local_domains
condition = ${if !eq {$received_protocol}{spam-scanned}}
local_part_suffix = +*
local_part_suffix_optional = true
data = ${lookup mysql{SELECT CONCAT('${local_part}@${domain}\n', vm_forwards.forward_to) FROM vm_domains, vm_mboxes, vm_forwards WHERE vm_domains.domain='${domain}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.mbox='${local_part}' AND vm_mboxes.id=vm_forwards.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' AND vm_forwards.save_local='1'}}
spamcheck_router:
driver = accept
domains = +local_domains
local_part_suffix = +*
local_part_suffix_optional = true
condition = ${if and { \
{ !eq {$received_protocol}{spam-scanned}} \
{ !eq {$sender_address_domain}{$domain}} \
{ < {$message_size}{512k}} \
{ !eq {$header_X-Junk-Flag:}{YES}} \
{ !eq {$header_X-Whitelist-Flag:}{YES}} \
{ eq {${lookup mysql{SELECT vm_mboxes.status FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1'}{$value}fail}}{1} } \
} {yes} {no}}
# Check for other headers too? Blacklist, SPF, DKIM failers go directly to Spam folder without spam scan??? - actually they should go to spam folder before this router is hit?
headers_remove = X-Spam-Checker-Version:X-Spam-Flag:X-Spam-Level:X-Spam-Status:X-Spam-Score:X-Spam-Report
transport = spamcheck
user_filter:
driver = redirect
domains = +local_domains
local_part_suffix = +*
local_part_suffix_optional = true
data = ${lookup mysql{SELECT vm_filters.filter FROM vm_domains, vm_mboxes, vm_filters WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_mboxes.id = vm_filters.mbox_id AND vm_domains.status = '1' AND vm_mboxes.status = '1' }}
user = vmail
no_verify
no_expn
check_ancestor
allow_filter
file_transport = address_file
pipe_transport = address_pipe
reply_transport = address_reply
directory_transport = user_filter_maildir_delivery
allow_fail
lmtp_localuser:
driver = accept
domains = +local_domains
local_part_suffix = +*
local_part_suffix_optional = true
condition = ${lookup mysql{SELECT vm_mboxes.id FROM vm_domains, vm_mboxes WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_domains.status = '1' AND vm_mboxes.status = '1'}}
# add checks to query for domain & mbox being active
transport = dovecot_lmtp
cannot_route_message = Unknown user
# Support for catchall aliases. It is *not* recommended to use this.
virtual_alias_catchall:
driver = redirect
domains = +local_domains
condition = ${if !eq {$received_protocol}{spam-scanned}}
data = ${lookup mysql{SELECT CONCAT(vm_mboxes.mbox,'@${domain}') FROM vm_aliases, vm_mboxes, vm_domains WHERE vm_aliases.alias='catchall' AND vm_aliases.mbox_id = vm_mboxes.id AND vm_mboxes.domain_id = vm_domains.id AND vm_domains.domain='${domain}' AND vm_domains.status='1' AND vm_mboxes.status='1'}}
# This router handles aliasing using a linearly searched alias file with the
# name SYSTEM_ALIASES_FILE. When this configuration is installed automatically,
# the name gets inserted into this file from whatever is set in Exim's
# build-time configuration. The default path is the traditional /etc/aliases.
# If you install this configuration by hand, you need to specify the correct
# path in the "data" setting below.
#
##### NB You must ensure that the alias file exists. It used to be the case
##### NB that every Unix had that file, because it was the Sendmail default.
##### NB These days, there are systems that don't have it. Your aliases
##### NB file should at least contain an alias for "postmaster".
#
# If any of your aliases expand to pipes or files, you will need to set
# up a user and a group for these deliveries to run under. You can do
# this by uncommenting the "user" option below (changing the user name
# as appropriate) and adding a "group" option if necessary. Alternatively, you
# can specify "user" on the transports that are used. Note that the transports
# listed below are the same as are used for .forward files; you might want
# to set up different ones for pipe and file deliveries from aliases.
system_aliases:
driver = redirect
domains = +local_domains
allow_fail
allow_defer
data = ${lookup{$local_part}lsearch{/etc/aliases}}
# user = exim
# file_transport = address_file
# pipe_transport = address_pipe
# This router handles forwarding using traditional .forward files in users'
# home directories. If you want it also to allow mail filtering when a forward
# file starts with the string "# Exim filter", uncomment the "allow_filter"
# option.
# The no_verify setting means that this router is skipped when Exim is
# verifying addresses. Similarly, no_expn means that this router is skipped if
# Exim is processing an EXPN command.
# The check_ancestor option means that if the forward file generates an
# address that is an ancestor of the current one, the current one gets
# passed on instead. This covers the case where A is aliased to B and B
# has a .forward file pointing to A.
# The three transports specified at the end are those that are used when
# forwarding generates a direct delivery to a file, or to a pipe, or sets
# up an auto-reply, respectively.
#userforward:
# driver = redirect
# check_local_user
# file = $home/.forward
# no_verify
# no_expn
# check_ancestor
## allow_filter
# file_transport = address_file
# pipe_transport = address_pipe
# reply_transport = address_reply
# This router matches local user mailboxes.
#localuser:
# driver = accept
# check_local_user
# transport = local_delivery
######################################################################
# TRANSPORTS CONFIGURATION #
######################################################################
# ORDER DOES NOT MATTER #
# Only one appropriate transport is called for each delivery. #
######################################################################
# A transport is used only when referenced from a router that successfully
# handles an address.
begin transports
# This transport is used for delivering messages over SMTP connections.
remote_smtp:
driver = smtp
dkim_domain = ${lc:${domain:$h_from:}}
dkim_canon = relaxed
dkim_selector = ${if exists{/etc/ssl/dkim/${dkim_domain}.selector}{${readfile{/etc/ssl/dkim/${dkim_domain}.selector}{}}}{0}}
dkim_private_key = ${if exists{/etc/ssl/dkim/${dkim_domain}.pem}{/etc/ssl/dkim/${dkim_domain}.pem}{0}}
# This transport is used for local delivery to user mailboxes in traditional
# BSD mailbox format. By default it will be run under the uid and gid of the
# local user, and requires the sticky bit to be set on the /var/mail directory.
# Some systems use the alternative approach of running mail deliveries under a
# particular group instead of using the sticky bit. The commented options below
# show how this can be done.
local_delivery:
driver = appendfile
file = /var/mail/$local_part
delivery_date_add
envelope_to_add
return_path_add
group = vmail
mode = 0660
dovecot_lmtp:
driver = lmtp
socket = /run/dovecot/lmtp
#return_path_add
#maximum number of deliveries per batch, default 1
#batch_max = 200
#allow suffixes/prefixes (default unset)
#rcpt_include_affixes
#local_delivery:
# driver = appendfile
## file = /var/vmail/$local_part
# maildir_format = true
# directory = /home/$local_part/Maildir
# create_directory
# directory_mode = 770
# delivery_date_add
# envelope_to_add
# return_path_add
# user = $local_part
# group = $local_part
# mode = 0660
## for vacation mail
vacation_transport:
driver = autoreply
log = /var/vmail/${domain}/${local_part}/vacation_log
once = /var/vmail/${domain}/${local_part}/vacation_once_db
return_path = ${local_part}@${domain}
to = ${sender_address}
from = ${local_part}@${domain}
# subject = ${lookup mysql{SELECT subject FROM vm_autoresponder WHERE local_part='${local_part}' AND domain='${domain}'}{$value}{"Auto Reply"}}
# text = ${lookup mysql{SELECT message FROM vm_autoresponder WHERE local_part='${local_part}' AND domain='${domain}'}{$value}fail}
subject = ${lookup mysql{SELECT vm_autoresponders.subject FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}{"Auto Reply"}}
text = ${lookup mysql{SELECT vm_autoresponders.body FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}fail}
user = vmail
## for autoresponder
autoresponder_transport:
driver = autoreply
log = /var/vmail/${domain}/${local_part}/autoresponder_log
return_path = ${local_part}@${domain}
to = ${sender_address}
from = ${local_part}@${domain}
subject = ${lookup mysql{SELECT vm_autoresponders.subject FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}{"Auto Reply"}}
text = ${lookup mysql{SELECT vm_autoresponders.body FROM vm_domains, vm_mboxes, vm_autoresponders WHERE vm_domains.domain='${domain}' AND vm_mboxes.mbox='${local_part}' AND vm_domains.id = vm_mboxes.domain_id AND vm_autoresponders.mbox_id = vm_mboxes.id}{$value}fail}
user = vmail
maildir_delivery:
driver = appendfile
maildir_format
directory = /var/vmail/${domain}/${local_part}/Maildir
create_directory
directory_mode = 750
user = vmail
user_filter_maildir_delivery:
driver = appendfile
maildir_format
user = vmail
junk_delivery:
driver = appendfile
maildir_format
directory = /var/vmail/${domain}/${local_part}/Maildir/.Junk
create_directory
directory_mode = 750
user = vmail
# SpamAssassin
spamcheck:
driver = pipe
command = /usr/sbin/exim -oMr spam-scanned -bS
use_bsmtp = true
transport_filter = /usr/bin/spamc -f -u $local_part@$domain
home_directory = "/tmp"
current_directory = "/tmp"
# must use a privileged user to set $received_protocol on the way back in!
user = mail
group = mail
log_output = true
return_fail_output = true
return_path_add = false
message_prefix =
message_suffix =
# clamav
#clamav_scan
# driver = pipe
# command = /usr/bin/clamdscan
# user = clamav
# user_bsmtp = true
# This transport is used for handling pipe deliveries generated by alias or
# .forward files. If the pipe generates any standard output, it is returned
# to the sender of the message as a delivery error. Set return_fail_output
# instead of return_output if you want this to happen only when the pipe fails
# to complete normally. You can set different transports for aliases and
# forwards if you want to - see the references to address_pipe in the routers
# section above.
address_pipe:
driver = pipe
return_output
# This transport is used for handling deliveries directly to files that are
# generated by aliasing or forwarding.
address_file:
driver = appendfile
delivery_date_add
envelope_to_add
return_path_add
# This transport is used for handling autoreplies generated by the filtering
# option of the userforward router.
address_reply:
driver = autoreply
######################################################################
# RETRY CONFIGURATION #
######################################################################
begin retry
# This single retry rule applies to all domains and all errors. It specifies
# retries every 15 minutes for 2 hours, then every 2 hours until 1 full day
# has passed since the first delivery failed.
# Domain Error Retries
# ------ ----- -------
* * F,2h,15m; F,1d,2h
######################################################################
# REWRITE CONFIGURATION #
######################################################################
# There are no rewriting specifications in this default configuration file.
begin rewrite
######################################################################
# AUTHENTICATION CONFIGURATION #
######################################################################
# There are no authenticator specifications in this default configuration file.
begin authenticators
dovecot_plain:
driver = dovecot
public_name = PLAIN
server_socket = /run/dovecot/auth-client
server_set_id = $auth1
dovecot_login:
driver = dovecot
public_name = LOGIN
server_socket = /run/dovecot/auth-client
server_set_id = $auth1
# End of Exim configuration file