Exim's ACL Configuration
I have already explained a little about ACL's, the section begins with the heading "begin acl". You can have as many ACL's as you wish and the order that they appear does not matter, as they are called individually and are NOT processed each in turn. Once you are inside an ACL it is processed until it is the address is accepted, denied.
ACL section | ###################################################################### # ACL CONFIGURATION # # Specifies access control lists for incoming SMTP mail # ###################################################################### begin acl ##################################################### ### end acl/00_exim-config_header ##################################################### ##################################################### ### acl/20_exim-config_whitelist_local_deny ##################################################### ### acl/20_exim-config_whitelist_local_deny ################################# # This is used to determine whitelisted senders and hosts. # It checks for CONFDIR/local_host_whitelist and # CONFDIR/local_sender_whitelist. # # It is meant to be used from some other acl entry. # # For example, # deny # message = local blacklist example # !acl = acl_whitelist # dnslist = some.dns.list.example # will allow messages with envelope sender listed in local_sender_whitelist # or messages coming in from hosts listed in local_host_whitelist to be # accepted even if the delivering host is listed in the dns list. # # Whitelisting can also be configured by including negative items in the # black list. See /usr/share/doc/exim-config/default_acl for details. # # If the files do not exist, the white list never matches, which is # the desired behaviour. acl_whitelist_local_deny: accept hosts = ${if exists{CONFDIR/local_host_whitelist}\ {CONFDIR/local_host_whitelist}\ {}} accept senders = ${if exists{CONFDIR/local_sender_whitelist}\ {CONFDIR/local_sender_whitelist}\ {}} # This hook allows you to hook in your own ACLs without having to # modify this file. If you do it like we suggest, you'll end up with # a small performance penalty since there is an additional file being # accessed. This doesn't happen if you leave the macro unset. .ifdef WHITELIST_LOCAL_DENY_LOCAL_ACL_FILE .include WHITELIST_LOCAL_DENY_LOCAL_ACL_FILE .endif ##################################################### ### end acl/20_exim-config_whitelist_local_deny ##################################################### ##################################################### ### acl/30_exim-config_check_rcpt ##################################################### ### acl/30_exim-config_check_rcpt ################################# # This access control list is used for every RCPT command in an incoming # SMTP message. The tests are run in order until the address is either # accepted or denied. # acl_check_rcpt: # Accept if the source is local SMTP (i.e. not over TCP/IP). We do this by # testing for an empty sending host field. accept hosts = : # Add missing Date and Message-ID header for relayed messages warn hosts = +relay_from_hosts control = submission/sender_retain # The following section of the ACL is concerned with local parts that contain # certain non-alphanumeric characters. Dots in unusual places are # handled by this ACL as well. # # Non-alphanumeric characters other than dots are rarely found in genuine # local parts, but are often tried by people looking to circumvent # relaying restrictions. Therefore, although they are valid in local # parts, these rules disallow certain non-alphanumeric characters, as # a precaution. # # Empty components (two dots in a row) are not valid in RFC 2822, but Exim # allows them because they have been encountered. (Consider local parts # constructed as "firstinitial.secondinitial.familyname" when applied to # a name without a second initial.) However, a local part starting # with a dot or containing /../ can cause trouble if it is used as part of a # file name (e.g. for a mailing list). This is also true for local parts that # contain slashes. A pipe symbol can also be troublesome if the local part is # incorporated unthinkingly into a shell command line. # # Two different rules are used. The first one has a quite strict # default, and is applied to messages that are addressed to one of the # local domains handled by this host. # If you have local accounts that include strange characters, you can # use the macro provided to change the ACL range or to disable the # check completely. .ifdef CHECK_RCPT_LOCAL_LOCALPARTS deny domains = +local_domains local_parts = CHECK_RCPT_LOCAL_LOCALPARTS message = restricted characters in address .endif # The second rule applies to all other domains, and its default is # considerably less strict. .ifdef CHECK_RCPT_REMOTE_LOCALPARTS deny domains = !+local_domains local_parts = CHECK_RCPT_REMOTE_LOCALPARTS message = restricted characters in address .endif # Accept mail to postmaster in any local domain, regardless of the source, # and without verifying the sender. # accept .ifndef CHECK_RCPT_POSTMASTER local_parts = postmaster .else local_parts = CHECK_RCPT_POSTMASTER .endif domains = +local_domains : +relay_to_domains # deny bad senders (envelope sender) # CONFDIR/local_sender_blacklist holds a list of envelope senders that # should have their access denied to the local host. Incoming messages # with one of these senders are rejected at RCPT time. # # The explicit white lists are honored as well as negative items in # the black list. See /usr/share/doc/exim-config/default_acl for details. deny message = sender envelope address $sender_address is locally blacklisted here.\ |
ACL Definition
The ACL definition comes first
begin acl |
acl_whitelist_local_deny ACL
The ACL acl_whitelist_local_deny points the hosts to the local_host_whitelist file and senders to the local_sender_whitelist files, if they exists, otherwise they are blank
acl_whitelist_local_deny:
accept
hosts = ${if exists{CONFDIR/local_host_whitelist}\
{CONFDIR/local_host_whitelist}\
{}}
accept
senders = ${if exists{CONFDIR/local_sender_whitelist}\
{CONFDIR/local_sender_whitelist}\
{}}
# This hook allows you to hook in your own ACLs without having to
# modify this file. If you do it like we suggest, you'll end up with
# a small performance penalty since there is an additional file being
# accessed. This doesn't happen if you leave the macro unset.
.ifdef WHITELIST_LOCAL_DENY_LOCAL_ACL_FILE
.include WHITELIST_LOCAL_DENY_LOCAL_ACL_FILE
.endif
Note: the if statement is broken down as below (you can brush on you if skill here).
if the file local_host_whitelist exists the set hosts to local_sender_whitelist otherwise leave blank |
acl_check_rcpt ACL
Now comes a large ACL called acl_check_rcpt which I will break down
acl_check_rcpt: |
Accept if the source is local SMTP (i.e. not over TCP/IP). We do this by testing for an empty sending host field. I have a discussed this before in ACL.
accept hosts = : |
Add missing Date and Message-ID header for relayed messages, I have already talked about submission and control mode.
warn hosts = +relay_from_hosts control = submission/sender_retain |
Two different rules are used. The first one has a quite strict default, and is applied to messages that are addressed to one of the local domains handled by this host. If you have local accounts that include strange characters, you can use the macro provided to change the ACL range or to disable the check completely.
.ifdef CHECK_RCPT_LOCAL_LOCALPARTS deny domains = +local_domains local_parts = CHECK_RCPT_LOCAL_LOCALPARTS message = restricted characters in address .endif # The second rule applies to all other domains, and its default is considerably less strict. .ifdef CHECK_RCPT_REMOTE_LOCALPARTS deny domains = !+local_domains local_parts = CHECK_RCPT_REMOTE_LOCALPARTS message = restricted characters in address .endif Note: the marcos CHECK_RCPT_LOCAL_LOCALPARTS and CHECK_RCPT_REMOTE_LOCALPARTS were discussed here but for a recap they were set to the following CHECK_RCPT_LOCAL_LOCALPARTS = ^[.] : ^.*[@%!/|\#&?] |
Accept mail to postmaster in any local domain, regardless of the source, and without verifying the sender. We had nothing about postmaster in the main section but you can have.
accept .ifndef CHECK_RCPT_POSTMASTER local_parts = postmaster .else local_parts = CHECK_RCPT_POSTMASTER .endif domains = +local_domains : +relay_to_domains |
Deny bad senders (envelope sender) local_sender_blacklist holds a list of envelope senders that should have their access denied to the local host. Incoming messages with one of these senders are rejected at RCPT time. The explicit white lists are honored as well as negative items in the black list.
deny
message = sender envelope address $sender_address is locally blacklisted here.\ |
Deny unless the sender address can be verified. This is disabled by default so that DNSless systems don't break. If your system can do DNS lookups without delay or cost, you might want to enable this feature.
.ifdef CHECK_RCPT_VERIFY_SENDER
deny
message = Sender verification failed
!acl = acl_whitelist_local_deny
!verify = sender |
For some sender domains, we do callout to verify if a sender exists. I discussed the sender/callout option in address verification.
deny !acl = acl_whitelist_local_deny senders = ${if exists{CONFDIR/local_sender_callout} {CONFDIR/local_sender_callout} {}} !verify = sender/callout Note: notice we are calling another ACL acl_whitelist_local_deny |
For some recipient domains, we do callout to verify if a recipient exists. This is especially handy for customers that receive a lot of spam to non-existent addresses.
deny !acl = acl_whitelist_local_deny recipients = ${if exists{CONFDIR/local_rcpt_callout} {CONFDIR/local_rcpt_callout} {}} !verify = recipient/callout Note: notice we are calling another ACL acl_whitelist_local_deny |
Warn if the sender host does not have valid reverse DNS. If your system can do DNS lookups without delay or cost, you might want to enable this. If sender_host_address is defined, it's a remote call. If sender_host_name is not defined, then reverse lookup failed. Use this instead of !verify = reverse_host_lookup to catch deferrals as well as outright failures.
The condition statement is a custom condition check, you can read more about the condition statement here.
.ifdef CHECK_RCPT_REVERSE_DNS
warn
message = X-Host-Lookup-Failed: Reverse DNS lookup failed for $sender_host_address\
(${if eq{$host_lookup_failed}{1}{failed}{deferred}})
|
Check against classic DNS "black" lists (DNSBLs) which list sender IP addresses also check against DNSBLs which list sender domains, with an option to locally whitelist certain domains that might be blacklisted. If you want one blacklist per domain, you need to replicate the stanza for each DNSBL.
.ifdef CHECK_RCPT_IP_DNSBLS
deny
message = X-Warning: IP $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
log_message = IP $sender_host_address is listed at $dnslist_domain ($dnslist_value: $dnslist_text)
!senders = ${if exists{CONFDIR/local_domain_dnsbl_whitelist} {CONFDIR/local_domain_dnsbl_whitelist} {}}
dnslists = CHECK_RCPT_IP_DNSBLS
delay = 4m
.endif
.ifdef CHECK_RCPT_DOMAIN_DNSBLS
deny
message = X-Warning: Domain $sender_address_domain is listed\ |
This hook allows you to hook in your own ACLs without having to modify this file. If you do it like we suggest, you'll end up with a small performance penalty since there is an additional file being accessed. This doesn't happen if you leave the macro unset which is the case in my configuration
.ifdef CHECK_RCPT_LOCAL_ACL_FILE .include CHECK_RCPT_LOCAL_ACL_FILE .endif |
Accept if the address is in a local domain, but only if the recipient can be verified. Otherwise deny. The "endpass" line is the border between passing on to the next ACL statement (if tests above it fail) or denying access (if tests below it fail).
accept Note: remember endpass is like a if statement, in this instance if the address is not in the local_domains list then move on |
Accept if the address is in a domain for which we are relaying, but again, only if the recipient can be verified. If you want to use the more conservative "unknown user" error message in case of a non-existing local part, you might want to set CHECK_RCPT_GIVE_UNKNOWN_USER. However, this might reveal local information, which is the cause for it not being enabled by default.
accept domains = +relay_to_domains endpass .ifdef CHECK_RCPT_GIVE_UNKNOWN_USER message = ${if eq{$acl_verify_message}{Unrouteable address}{unknown user}{$acl_verify_message}} .else message = unrouteable address .endif verify = recipient |
Accept if the message comes from one of the hosts for which we are an outgoing relay. Recipient verification is omitted here, because in many cases the clients are dumb MUAs that don't cope well with SMTP error responses. If you are actually relaying out from MTAs, you should probably add recipient verification here.
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 = * |
Reaching the end of the ACL causes a "deny", but we might as well give an explicit message.
deny message = relay not permitted |
acl_check_data ACL
Deny unless the address list headers are syntactically correct. If you enable this, you might reject legitimate mail.
.ifdef CHECK_DATA_VERIFY_HEADER_SYNTAX deny message = Message headers fail syntax check !acl = acl_whitelist_local_deny !verify = header_syntax .endif |
Require that there is a verifiable sender address in at least one of the "Sender:", "Reply-To:", or "From:" header lines.
.ifdef CHECK_DATA_VERIFY_HEADER_SENDER deny message = No verifiable sender address in message headers !acl = acl_whitelist_local_deny !verify = header_sender .endif |
This hook allows you to hook in your own ACLs without having to modify this file. If you do it like we suggest, you'll end up with a small performance penalty since there is an additional file being accessed. This doesn't happen if you leave the macro unset.
.ifdef CHECK_DATA_LOCAL_ACL_FILE .include CHECK_DATA_LOCAL_ACL_FILE .endif |
Accept otherwise accept
accept |