Access Control Lists

Access Control Lists (ACL) allows Exim to check incoming messages, most of them apply to SMTP input but to can check non-SMTP messages as well. ACLs in the default configuration forbid relaying and impose some standard checks on messages from other hosts.

ACLs have there own section in the configuration file, each ACL starts with its name, terminated by a colon

ACL configuration begin acl                                  ## beginning of the ACL section

small_acl:                                 ## ACL definition called "small_acl"
   accept   hosts = one.datadisk.co.uk

You can have as many ACLs as you wish, and the order in which they appear does not matter. The two most common uses for ACLs are

Default ACL Configuration

I will now go through the default configuration that is supplied when you first configure Exim.

Default ACL Configuration
Default Configuration
acl_check_rcpt:


  accept
    hosts = :


deny
    message = Restricted characters in address
    domains = +local_domains
    local_parts = ^[.] : ^.*[@%!/|\#&?]

deny
    message = Restricted characters in address
    domains = !+local_domains
    local_parts = ^[./|] : ^.*[@%!\'`#&?] : ^.*/\\.\\./

accept
    local_parts = postmaster
    domains = +local_domains

require
   verify = sender

accept
   hosts = +relay_from_hosts
   control = submission

accept
   authenticated = *
   control = submission

require
   message = relay not permitted
   domains = +local_domains : +relay_to_domains

require
   verify = recipient

accept

Now to explain what everything means rule by rule

Breakdown of default configuration
acl_check_rcpt: ACL Name

The start of the ACL and names it
accept
    hosts = :

Messages from local Processes

accepts unconditionally all recipients in messages that are submitted by SMTP from local processes using the standard input and output (that is not using TCP/IP)

Basically Exim matches incoming messages that did not arrive over a TCP/IP connection.

deny
    message = Restricted characters in address
    domains = +local_domains
    local_parts = ^[.] : ^.*[@%!/|\#&?]

Guarding against incoming rogue local parts

If both conditions are true (domains and local_parts) access is denied and the value of message is sent.

domains means that the domain in the recipients address must match one domain in the list (notice the + this means a list)

local_parts has two expressions, which match any address that start with a dot or contain any special characters.

The expressions are used to filter out spam email before we do anything else.

deny
    message = Restricted characters in address
    domains = !+local_domains
    local_parts = ^[./|] : ^.*[@%!\'`#&?] : ^.*/\\.\\./

Guarding against outgoing rogue local parts

This is the same as above but less restrictive

accept
    local_parts = postmaster
    domains = +local_domains

Mail for postmaster

This statement accepts any messages if the local part is postmaster and the domain is listed in the local_domains list, this makes sure that all mail to postmaster is accepted.

require
    verify = sender

Verifying the senders address

This statement requires that the senders address to be verified before any subsequent ACL statement can be used. Verification consists of trying to route the address, to see if the message could be delivered to it.

accept
    hosts = +relay_from_hosts
    control = submission

Allow relaying from identified hosts

hosts states that the messages must come from a specific host, in the case from the relay_from_hosts list

The control statement (control modifier) means that the message is to be processed as a submission from an MUA. submission mode is used when receiving messages from a MUA client, is applies extra checks and transformations that are not used for messages that arrive from other MTA's

accept
    authenticated = *
    control = submission

Allow relaying from authenticated hosts

This statement accepts the address only if the client host has authenticated itself, again assuming submission mode when it does so. By default Exim does not define any authenticators so this would fail normally

require
    message = relay not permitted
    domains = +local_domains : +relay_to_domains

Reject unrecognized domains

This statement checks that the domain of the incoming recipient address is either in the list of local domains, or in the list of domains for which incoming relaying is permitted. Any other domains is rejected as an invalid relay attempt.

require
    verify = recipient

Verify the recipient address

This statement requires the recipient address to be verified, in the same way that the sender address was verified earlier

accept

Accept a valid address

If the messages passed all the checks before then accept it

Using ACLs

You have to tell Exim when to use a ACL by specifying it in the configuration file, you can specify different ACLs for different SMTP commands

Calling a ACL

# ACL external file
acl_smtp_rcpt = /etc/acls/${lookup{$sender_host_address}lsearch{/etc/aclist}{$value}{default}}

# ACL defined in Exim's configuration file
acl_smtp_rcpt = acl_check_rcpt    # acl_smtp_rcpt is the ACL option, acl_check_rcpt is the ACL code                                   # block that will be run

# inline ACL
acl_smtp_rcpt = accept

There are many ACL options

acl_not_smtp ACL for non-SMTP messages
acl_not_smtp_mime ACL for each non-SMTP MME part
acl_not_smtp_start ACL for the start of a non-SMTP message
acl_smtp_auth ACL for AUTH commands, the code block will run but no data has been sent yet, thus you can reject the message before getting the data saving resources getting the data.
acl_smtp_connect ACL for the start of a SMTP connection, runs at the start of a SMTP connection
acl_smtp_data ACL for the end of DATA commands
acl_smtp_etrn ACL for ETRN commands
acl_smtp_expn ACL for EXPN commands
acl_smtp_helo ACL for HLO or EHLO commands
acl_smtp_mail ACL for MAIL commands
acl_smtp_mailauth ACL for AUTH parameters of MAIL commands
acl_smtp_mime ACL for each STP MIME part , is run before acl_smtp_data
acl_smtp_predata ACL for the start of DATA commands, the code block will run but no data has been sent yet, thus you can reject the message before getting the data saving resources getting the data.
acl_smtp_quit ACL for QUIT commands
acl_smtp_rcpt ACL for RCPT commands
acl_smtp_starttls ACL for STARTTLS commands
acl_smtp_vrfy ACL for VRFY commands

The result of an ACL is either accept or deny, or if a test cannot complete defer. These results cause 2xx, 5xx, and 4xx return codes to be used in the SMTP dialogue.

ACLs Variables and Verbs

When an ACL is run for an incoming SMTP command, the contents of the command are placed in $smtp_command. The setting of other variables depends on the type of SMTP command, here are a list of the most commonly used ones

$smtp_command contains the contents of the SMTP command
$smtp_command_argument contains the argument that follows the SMTP command
$sender_host_address contains information about the senders host
$sender_address contains information about message's sender
$domain contains information about domain part of the address
$local_part contains information about local part of the address
$authenicated_sender contains the value obtained from the AUTH parameter of the MAIL command
$message_size set to the value of the SIZE parameter on the MAIL command
$rcpt_count increases by one everytime a RCPT command is received before the ACL is run
$recipients_count increases by one each time a RCPT command is accepted, so while an ACL is being processed, contains the number previously accepted recipients

An individual ACL consists of a number of statements, each statements starts with a verb followed by a number of conditions and other modifiers, these are processed in order, if all the conditions are meet then the action of the verb is taken.

ACL statements are processed in order, depending on the verb some conditions may not be tested if other conditions have already failed, for instance the require verb will not test any of the other conditions below one that has failed, where deny will test all conditions regardless if any have failed.

statement checks require
   domains = +local_domains
   verify = recipient
   message = unrecognized address         ## will never be reached if rejection occurs before

The verbs are listed as follows

accept if all conditions are true the ACL returns accept, if any of the conditions are false what happens depends on whether endpass appears among the conditions. If the failing condition precedes endpass, control is passed to the next ACL statement, if it follows endpass the ACL returns deny.
defer if all the conditions are true the ACL returns deny, if any are false then it is passed to the next ACL
deny if all the conditions are true the ACL returns deny, if any are false then it is passed to the next ACL
discard It acts like accept, except that it causes recipients to be discarded
drop if all the conditions are true the action taken is the same as deny, except that after a permanent errors the SMTP connection is dropped
require if all the conditions are true it is passed to the next ACL, if any are false then the ACL returns deny.
warn Always pass to the next ACL, it is used for its side effects, if all conditions are true and a log_message modifier is present its data is written to Exim's main log. You can use the logwrite modifier as well

You can negate conditions

negate a condition

deny
   domains = datadisk.co.uk
   !verify = recipient

Note: causes the ACL to return deny if the recipient domains ends in datadisk.co.uk and the recipient address cannot be verified.

ACL Modifiers

There are a number of ACL modifiers

add_header adds a header to an incoming message
control has a number of different arguments that vary the way in which an incoming message is processed. It normally is used with the warn verb so that other ACLs can make the decision to accept or deny the message
delay will wait until the interval time supplied has passed before processing
endpass is only used in accept or discard statements, it is used between statements to supply a if like statement.
log_message Always writes to Exim's main log file and adds the text "Warning:" to the start of the message
logwrite writes a message to Exim's main log file, you can specify a different log file to write to.
message sets a up message that is used if the current statement causes the ACL to terminate with an accept or deny or defer status.
set is used to put values into ACL variables

Arguments for the Control Modifier

There are a number of arguments for the control modifier, some of which are used in special circumstances only.

caseful_local_part
caselower_local_part
using caseful_local_part any uppercase letters in the original local part are restored for the rest of the ACL or until caselower_local_part is encountered
queue_only
freeze
when using queue_only the message is queued and not delivered immediately, you can freeze a message by using the freeze option. This is only on a per message bases and not apply to the next message useless it uses this ACL.
submission If a message is received by the MTA via a MUA (client), the message is modified to look like a normal message for example if the Date: header line is missing it is added.

ACL Conditions

There are many ACL conditions, some have been used already here is a list of the rest

acl Allows one ACL to call another
authenicated Tests for SMTP authentication
condition A general customizable condition, this should result in yes or no
decode decode a MIME part
dnslist Checks DNS black lists
domains Tests the domain of the recipient
encrypted Tests for encrypted SMTP session
hosts Tests for specific sending hosts
local_parts Tests for local part of a recipient address
malware Call external virus scanner
mime_regex Scan MIME part with regex
ratelimit Check rate of message submission
recipients Tests a recipient address
regex Scan the message with a regex
sender_domains Tests the domain of a sender address
senders Tests the sender address
spam Call spamAssassin
verify Verifies sender and recipient addresses and a number of other message characteristics

I am not going into too much details but i will supply a few examples

Checking the identify of the client host accept
   hosts = +relay_from_hosts
Using DNS black lists

deny
   log_message = Domain $sender_address_domain is listed at $dnslist_domain ($dnslist_value:$dnslist_text)
   dnslist = smtp.dnsbl.sorbs.net : spam.dnsbl.sorbds.net

Note: the message is only logged if the condition fails

Checking senders address

deny
   senders = spammer@coldmail.exmple : *@spamsource.example

Note: blocking specific addresses and domains

Checking recipients address

deny
   senders = spammer@example.com
   recipients = lsearch;/etc/blockspammers

Note: check the recipients address

Check message header lines

require
   verify = header_syntax

Note: reject messages with an incorrect header, ideal to capture @ or <> header lines

Custom ACL check

accept
   condition = ${if and{{def:sender_host_address}{!def:sender_host_name}} {yes}{no}}
   

Ratelimit

defer
   message = Sorry, very busy, try again later
   ratelimit = 10 / 1s / $primary_hostname

Note:
10 = average client sending rate is less than 10 per time period (see below)
1s = time period
$primary_hostname = particular server

you can also use $sender_rate, $sender_rate_limit and $sender_rate_period in log_message

Calling another ACL acl_check_rcpt:

accept
   domains = +local_domains
   endpass
   acl = acl_local

acl_local:

accept
   local_parts = postmaster
   domains = +relay_to_domains
logging to a different file

warn
   senders = *@datadisk.co.uk
   recipients = *@datadisk.co.uk
   !hosts = mailhost
   logwrite = :reject: Possible spoofed email sender: $sender_address($sender_host_address)\
                       recipient: $local_part_$domain

Note: the :reject: means that this will be logged to the reject log instead of mainlog, the rule here basically states that i should only receive emails to the local domain from my mailhost only.

see message filtering for more information on logfiles