Access Control Lists (ACLs) are defined in a separate section of the run time configuration file, headed by ``begin acl''. Each ACL definition starts with a name, terminated by a colon. Here is a complete ACL section which contains just one very small ACL:
begin acl small_acl: accept hosts = one.host.only
You can have as many lists as you like in the ACL section, and the order in which they appear does not matter. The lists are self-terminating.
ACLs are used to control Exim's behaviour when it receives certain SMTP commands. This applies both to incoming TCP/IP connections, and when a local process submits a message over a pipe (using the -bs option). The most common use is for controlling which recipients are accepted in incoming messages. The default configuration file contains an example of a realistic ACL. This is discussed in chapter 7. The -bh command line option provides a way of testing your ACL configuration by running a fake SMTP session.
In order to cause an ACL to be used, you have to name it in one of the relevant options in the main part of the configuration. These options are:
acl_smtp_auth | ACL for AUTH |
acl_smtp_data | ACL after DATA |
acl_smtp_etrn | ACL for ETRN |
acl_smtp_expn | ACL for EXPN |
acl_smtp_rcpt | ACL for RCPT |
acl_smtp_vrfy | ACL for VRFY |
For example, if you set
acl_smtp_rcpt = small_acl
the little ACL defined above is used whenever Exim receives a RCPT command in an SMTP dialogue. The majority of policy tests on incoming messages can be done when RCPT commands arrive. A rejection of RCPT should cause the sending MTA to give up on the recipient address contained in the RCPT command.
You cannot test the contents of the message, for example, to verify addresses in the headers, at RCPT time. Such tests have to appear in the ACL that is run after the message has been received, before the final response to the DATA command is sent. This is the ACL specified by acl_smtp_data. At this time, it is no longer possible to reject individual recipients. An error response should reject the entire message. Unfortunately, it is known that some MTAs do not treat hard (5xx) errors correctly at this point - they keep the message on their queues and try again later, but that is their problem, though it does waste some of your resources.
The result of running an ACL is either ``accept'' or ``deny'', or, if some test cannot be completed (for example, if a database is down), ``defer''. These results cause 2xx, 5xx, and 4xx return codes, respectively, to be used in the SMTP dialogue. A fourth return, ``error'', occurs when there is an error such as invalid syntax in the ACL. This also causes a 4xx return code.
The default actions when any of the acl_smtp_xxx options are unset are not all the same. If acl_smtp_auth is not defined, AUTH is always accepted (and an attempt is made to authenticate the connection). If acl_smtp_data is not defined, no checks are done after a message has been received (at the end of the DATA phase).
However, if any of the other options are not defined, the relevant SMTP command is rejected. In particular, this means that acl_smtp_rcpt must be defined in order to receive any messages over an SMTP connection. The default configuration file contains a suggested ACL which is set up for acl_smtp_rcpt.
When an ACL for RCPT or DATA is being run, the variables that contain information about the host and the message's sender (for example, $sender_host_address and $sender_address) are set, and can be used in ACL statements. In the case of RCPT (but not DATA), $domain and $local_part are set from the argument address.
The $message_size variable is set to the value of the SIZE parameter on the MAIL command at RCPT time, or -1 if that parameter was not given. Its value is updated to the true message size by the time the ACL after DATA is run.
The $rcpt_count variable increases by one for each RCPT command received. The $recipients_count variable increases by one each time a RCPT command is accepted, so while an ACL for RCPT is being processed, it contains the number of previously accepted recipients. At DATA time, $rcpt_count contains the total number of RCPT commands, and $recipients_count contains the total number of accepted recipients.
When an ACL for AUTH, ETRN, EXPN, or VRFY is being run, the remainder of the SMTP command line is placed in $smtp_command_argument. This can be tested using a condition condition. For example, here is an ACL for use with AUTH, which insists that either the session is encrypted, or the CRAM-MD5 authentication method is used. In other words, it does not permit authentication methods that use cleartext passwords on unencrypted connections.
acl_check_auth: accept encrypted = * accept condition = ${if eq{${uc:$smtp_command_argument}}\ {CRAM-MD5}{yes}{no}} deny message = TLS encryption or CRAM-MD5 required
The value of an acl_smtp_xxx option is expanded before use, so you can use different ACLs in different circumstances, and in fact the resulting string does not have to be the name of a configured list. Having expanded the string, Exim searches for an ACL as follows:
If the string begins with a slash, Exim attempts to open the file and read its contents as an ACL. If the file does not exist or cannot be read, an error occurs (typically causing a temporary failure of whatever caused the ACL to be run). If an ACL is successfully read from a file, it is retained in memory, so that it can be re-used without actually having to re-read the file. For example:
acl_smtp_data = /etc/acls/\ ${lookup{$sender_host_address}lsearch\ {/etc/acllist}{$value}{default}}
This looks up an ACL file to use on the basis of the host's IP address, falling back to a default if the lookup fails.
If the string does not start with a slash, and does not contain any spaces, Exim searches the ACL section of the configuration for a list whose name matches the string.
If no named ACL is found, or if the string contains spaces, Exim parses the string as an inline ACL. This can save typing in cases where you just want to have something like
acl_smtp_vrfy = accept
in order to allow free use of the VRFY command.
An individual ACL consists of a number of statements. Each statement starts with a verb, optionally followed by a number of conditions and other modifiers. If all the conditions are met, the verb is obeyed. If there are no conditions, the verb is always obeyed. What happens if any of the conditions are not met depends on the verb (and in one case, on a special modifier). Not all the conditions make sense at every testing point. For example, you cannot test a sender address in the ACL that is run for a VRFY command.
The verbs are as follows:
accept: If all the conditions are met, the ACL returns ``accept''. If any of the conditions are not met, what happens depends on whether endpass appears among the conditions (for syntax see below). If the failing condition precedes endpass, control is passed to the next ACL statement; if it follows endpass, the ACL returns ``deny''. Consider this statement, used to check a RCPT command:
accept domains = +local_domains endpass verify = recipient
If the recipient domain does not match the domains condition, control passes to the next statement. If it does match, the recipient is verified, and the command is accepted if verification succeeds. However, if verification fails, the ACL yields ``deny'', because the failing condition is after endpass.
deny: If all the conditions are met, the ACL returns ``deny''. If any of the conditions are not met, control is passed to the next ACL statement. For example,
deny dnslists = blackholes.mail-abuse.org
rejects commands from hosts that are on a DNS black list.
require: If all the conditions are met, control is passed to the next ACL statement. If any of the conditions are not met, the ACL returns ``deny''. For example, when checking a RCPT command,
require verify = sender
passes control to subsequent statements only if the message's sender can be verified. Otherwise, it rejects the command.
warn: If all the conditions are met, some warning action is taken. In all
cases, control passes to the next ACL statement. In the case of testing an
incoming message, the warning action consists of adding header lines to the
message,
and/or writing an entry in the main log. Additional header lines are specified
by the message modifier, as in this example:
warn message = X-blacklisted-at: $dnslist_domain
dnslists = blackholes.mail-abuse.org : \
dialup.mail-abuse.org
If the same header line is requested several times (provoked, for example, by multiple RCPT commands), only one copy is actually added to the message. Header lines that are added by an ACL at RCPT time are visible in string expansions in the ACL that is run after DATA.
Log lines are specified by the log_message modifier. For ACLs that are not concerned with incoming messages, only the logging action is available.
If any condition on a warn statement cannot be completed (that is, there is some sort of defer), the warning action does not take place. The incident is logged, but the ACL continues to be processed.
At the end of each ACL there is an implicit unconditional deny.
As you can see from the examples above, the conditions and modifiers are written one to a line, with the first one on the same line as the verb, and subsequent ones on following lines. If you have a very long condition, you can continue it onto several physical lines by the usual \ continuation mechanism. It is conventional to align the conditions vertically.
An exclamation mark preceding a condition negates its result. For example,
deny domains = *.dom.example !verify = recipient
causes the ACL to return ``deny'' if the recipient domain ends in dom.example, but the recipient address cannot be verified.
The arguments of conditions and modifiers are expanded. A forced failure of an expansion causes a condition to be ignored, that is, it behaves as if the condition is true. Consider these two statements:
accept senders = ${lookup{$host_name}lsearch\ {/some/file}{$value}fail} accept senders = ${lookup{$host_name}lsearch\ {/some/file}{$value}{}}
Each attempts to look up a list of acceptable senders. If the lookup succeeds, the returned list is searched, but if the lookup fails the behaviour is different in the two cases. The fail in the first statement causes the condition to be ignored, leaving no further conditions. The accept verb therefore succeeds. The second statement, however, generates an empty list when the lookup fails. No sender can match an empty list, so the condition fails and therefore the accept also fails.
The ACL modifiers are endpass, log_message, and message. They operate as follows:
endpass
This modifier, which has no argument, is recognized only in accept statements. It marks the boundary between the conditions whose failure causes control to pass to the next statement, and the conditions whose failure causes the ACL to return ``deny''. See the description of accept above.
log_message = <text>
This modifier sets up a message which is used as part of the log message if the ACL denies access because of a subsequent condition in the current statement. For example:
require log_message = wrong cipher suite $tls_cipher encrypted = DES-CBC3-SHA
log_message adds to any underlying error message that may exist because of the condition. For example, while verifying a recipient address, a :fail: redirection might have already set up a message. Although the message is defined before the conditions to which it applies, the expansion does not happen until after a condition has failed. This means that any variables that are set by the condition are available for inclusion in the message. For example, the $dnslist_<xxx> variables are set after a DNS black list lookup succeeds. If log_message is used with a warn statement, ``Warning:'' is added to the start of the message.
message = <text>
This modifier sets up a message which is used as an error message if a subsequent condition in the current statement causes the ACL to deny access. The message is returned as part of the SMTP response. For example:
deny message = Relaying denied domains = !+relay_domains
The text is literal; any quotes are taken as literals, but because it is expanded, backslash escapes are processed anyway. If the message contains newlines, this gives rise to a multi-line SMTP response. Like log_message, the contents of message are not expanded until after a condition has failed.
If message is used on a statement that verifies an address, the message specified overrides any message that is generated by the verification process. However, the original message is available in the variable $acl_verify_message, so you can incorporate it into your message if you wish. In particular, if you want the text from :fail: items in redirect routers to be passed back as part of the SMTP response, you should either not use a message modifier, or make use of $acl_verify_message.
If message is used with warn, it causes a header line to be added an incoming message; for non-message ACLs, it has no effect with warn.
If log_message is not present but message is present, the message text is used for logging. However, if it contains newlines, only the first line of the text is logged. In the absence of both log_message and message, a default built-in message is used. Neither log_message nor message are used if they are empty strings, or if their expansions fail.
Not all conditions are relevant in all circumstances. For example, testing senders and recipients does not make sense in an ACL that is being run as the result of the arrival of an ETRN command, and checks on message headers can be done only in the ACL specified by acl_smtp_data.
The conditions are:
acl = <name of acl or ACL string or file name >
The possible values of the argument are the same as for the acl_smtp_xxx options. The named or inline ACL is run. If it returns ``accept'' the condition is true; if it returns ``deny'' the condition is false; if it returns ``defer'', the current ACL returns ``defer''. ACLs may be nested up to 20 deep; the limit exists purely to catch runaway loops.
This condition allows you to use different ACLs in different conditions. For example, different ACLs can be used to handle RCPT commands for different local users or different local domains.
If the SMTP connection is not authenticated, the condition is false. Otherwise, the name of the authenticator is tested against the list. To test for authentication by any authenticator, you can set
authenticated = *
This feature allows you to make up custom conditions. If the result of expanding the string is an empty string, the number zero, or one of the strings ``no'' or ``false'', the condition is false. If the result is any non-zero number, or one of the strings ``yes'' or ``true'', the condition is true. For any other values, some error is assumed to have occured, and the ACL returns ``defer''.
dnslists = <list of domain names and other data>
This condition checks for entries in DNS black lists. These are also known as ``RBL lists'', after the original Realtime Blackhole List, but note that the use of the lists at mail-abuse.org now carries a charge. In its simplest form, the dnslists condition tests whether the calling host is on a DNS black list by looking up the inverted IP address in one or more DNS domains. For example, if the calling host's IP address is 192.168.62.43, and the ACL statement is
deny dnslists = blackholes.mail-abuse.org : \ dialups.mail-abuse.org
the following domains are looked up:
43.62.168.192.blackholes.mail-abuse.org 43.62.168.192.dialups.mail-abuse.org
If a DNS lookup times out or otherwise fails to give a decisive answer, Exim behaves as if the host is not on the relevant list. This is usually the required action when dnslists is used with deny (which is the most common usage), because it prevents a DNS failure from blocking mail. However, you can change this behaviour by putting one of the following special items in the list:
+include_unknown behave as if the item is on the list
+exclude_unknown behave as if the item is not on the list (default)
+defer_unknown give a temporary error
Each of these applies to any subsequent items on the list. For example:
deny dnslists = +defer_unknown : foo.bar.example
Testing the list of domains stops as soon as a match is found. If you want to warn for one list and block for another, you can use two different statements:
deny dnslists = blackholes.mail-abuse.org warn dnslists = dialups.mail-abuse.org
There are some lists which are keyed on domain names rather than inverted IP addresses (see for example the domain based zones link at http://www.rfc-ignorant.org/). You can change the name that is looked up by adding additional data to a dnslists item, introduced by a slash. For example,
deny message = Sender's domain is listed at $dnslist_domain dnslists = dsn.rfc-ignorant.org/$sender_address_domain
This particular example is useful only in ACLs that are obeyed after the RCPT or DATA commands, when a sender address is available. If (for example) the message's sender is user@tld.example the name that is looked up is
tld.example.dsn.rfc-ignorant.org
You can mix entries with and without additional data in the same dnslists condition.
When an entry is found in a DNS black list, the variable $dnslist_domain contains the name of the domain which matched, $dnslist_value contains the data from the entry, and $dnslist_text contains the contents of any associated TXT record. You can use these variables in message or log_message modifiers - although these appear before the condition in the ACL, they are not expanded until after it has failed. For example:
deny hosts = !+local_networks message = $sender_host_address is listed \ at $dnslist_domain dnslists = rbl-plus.mail-abuse.example
DNS black list lookups are cached by Exim for the duration of the SMTP session, so a lookup based on the IP address is done at most once for any incoming connection. Exim does not share information between multiple incoming connections (but your local name server cache should be active).
DNS black lists are constructed using address records in the DNS. The original RBL just used the address 127.0.0.1 on the right hand side of the records, but the RBL+ list and some other lists use a number of values with different meanings. The values used on the RBL+ list are:
127.1.0.1 | RBL |
127.1.0.2 | DUL |
127.1.0.3 | DUL and RBL |
127.1.0.4 | RSS |
127.1.0.5 | RSS and RBL |
127.1.0.6 | RSS and DUL |
127.1.0.7 | RSS and DUL and RBL |
If you add an equals sign and an IP address after a dnslists domain name, you can restrict its action to DNS records with a matching right hand side. For example,
deny dnslists = rblplus.mail-abuse.org=127.0.0.2
rejects only those hosts that yield 127.0.0.2. More than one address may be given, using a comma as a separator. These are alternatives - if any one of them matches, the RBL entry operates. If there are no addresses, any address record is considered to be a match.
If you want to specify a constraining address and also change the name that is looked up, the address list must be specified first. For example:
deny dnslists = dsn.rfc-ignorant.org\ =127.0.0.2/$sender_address_domain
This condition is relevant only after a RCPT command. It checks that the domain of the recipient address is in the domain list. If percent-hack processing is enabled, it is done before this test is done. If the check succeeds with a lookup, the result of the lookup is placed in $domain_data until the next domains test.
If the SMTP connection is not encrypted, the condition is false. Otherwise, the name of the cipher suite in use is tested against the list. To test for encryption without testing for any specific cipher suite(s), set
encrypted = *
This condition tests that the calling host matches the host list. If you have name lookups or wildcarded host names and IP addresses in the same host list, you should normally put the IP addresses first. For example, you could have:
accept hosts = 10.9.8.7 : dbm;/etc/friendly/hosts
The reason for this lies in the left-to-right way that Exim processes lists. It can test IP addresses without doing any DNS lookups, but when it reaches an item that requires a host name, it fails if it cannot find a host name to compare with the pattern. If the above list is given in the opposite order, the accept statement fails for a host whose name cannot be found, even if its IP address is 10.9.8.7.
If you really do want to do the name check first, and still recognize the IP address even if the name lookup fails, you can rewrite the ACL like this:
accept hosts = dbm;/etc/friendly/hosts accept hosts = 10.9.8.7
The default action on failing to find the host name is to assume that the host is not in the list, so the first accept statement fails. The second statement can then check the IP address.
local_parts = <local part list>
This condition is relevant only after a RCPT command. It checks that the local part of the recipient address is in the list. If percent-hack processing is enabled, it is done before this test. If the check succeeds with a lookup, the result of the lookup is placed in $local_part_data until the next local_parts test.
This condition is relevant only after a RCPT command. It checks the entire recipient address against a list of recipients.
sender_domains = <domain list>
This condition tests the domain of the sender of the message against the given domain list.
This condition tests the sender of the message against the given list. To test for a bounce message, which has an empty sender, set
senders = :
This condition is true if the SMTP session is encrypted, and a certificate was received from the client, and the certificate was verified. The server requests a certificate only if the client matches tls_verify_hosts or tls_try_verify_hosts (see chapter 36).
verify = header_sender/<options>
This condition is relevant only in an ACL that is run after a message has been received, that is, in an ACL specified by acl_smtp_data. It checks that there is a verifiable sender address in at least one of the Sender:, Reply-To:, or From: header lines. Details of address verification and the options are given in the next section. You can combine this condition with the senders condition to restrict it to bounce messages only:
deny senders = : message = A valid sender header is required for bounces !verify = header_sender
This condition is relevant only in an ACL that is run after a message has been received, that is, in an ACL specified by acl_smtp_data. It checks the syntax of all header lines that can contain lists of addresses (Sender:, From:, Reply-To:, To:, Cc:, and Bcc:). This is a syntax check only. A common spamming ploy is to send syntactically invalid headers such as
To: @
This condition can be used to reject such messages.
This condition is true if a HELO or EHLO command has been received from the client host, and its contents have been verified. Verification of these commands does not happen by default. See the description of the helo_verify_hosts and helo_try_verify_hosts options for details of how to request it.
This condition is relevant only after a RCPT command. It verifies the current recipient. Details are given in the next section.
This condition ensures that a verified host name has been looked up from the IP address of the client host. (This may have happened already if the host name was needed for checking a host list, or if the host matched host_lookup.) Verification ensures that the host name obtained from a reverse DNS lookup, or one of its aliases, does, when itself looked up in the DNS, yield the original IP address.
This condition is relevant only after a RCPT command, or after a message has been received. If the message's sender is empty (that is, this is a bounce message), the condition is true. Otherwise, the sender address is verified. Details of verification are given in the next section. Exim caches the result of sender verification, to avoid doing it more than once per message.
verify = sender=address/<options>
This is a variation of the previous option, in which a modified address is verified as a sender.
Several of the verify conditions described in the previous section cause addresses to be verified. These conditions can be followed by a number of options that modify the verification process. The options are separated from the keyword and from each other by slashes. For example:
verify = sender/callout verify = recipient/defer_ok/callout=10s/callout_defer_ok
The first stage of verification is to run the address through the routers, in ``verify mode''. Routers can detect the difference between verification and routing for delivery, and their actions can be varied by a number of generic options such as verify and verify_only (see chapter 14).
If there is a defer error while doing this verification routing, the ACL normally returns ``defer''. However, if you include defer_ok in the options, the condition is forced to be true instead.
For non-local addresses, routing verifies the domain, but is unable to do any checking of the local part. There are situations where some means of verifying the local part is desirable. One way this can be done is to make an SMTP callback to the sending host (for a sender address) or a callforward to a subsequent host (for a recipient address), to see if the host accepts the address. We use the term callout to cover both cases. This facility should be used with care, because it adds a lot of resource usage to the cost of verifying an address.
If the callout option is present on a condition that verifies an address, a second stage of verification occurs if the address is successfully routed to one or more remote hosts. Exim makes SMTP connections to the remote hosts, to test whether the address is acceptable. For a sender address, it behaves as if transmitting a bounce message and sends:
HELO <primary host name>
MAIL FROM:<>
RCPT TO:<the address to be tested>
QUIT
For a recipient address, the MAIL command contains the sender address of the message. If the response to the RCPT command is a 2xx code, the verification succeeds. If it is 5xx, the verification fails. For any other condition, Exim tries the next host, if any. If there is a problem with all the remote hosts, the ACL yields ``defer'', unless the callout_defer_ok option is given, in which case the condition is forced to succeed.
For SMTP callout connections, the port to connect to and the outgoing interface are taken from the transport to which address was routed, if it is a remote transport. Otherwise port 25 is used, and the interface is not specified.
The timeout that applies for the callout attempt to each host can be changed by specifying it on the callout option. For example:
verify = sender/callout=5s
The default is 30 seconds.
When sender verification fails in an ACL, the details of the failure are given as additional output lines before the 550 response to the relevant SMTP command (RCPT or DATA). For example, if sender callout is in use, you might see:
MAIL FROM:<xyz@abc.example> 250 OK RCPT TO:<pqr@def.example> 550-Verification failed for <xyz@abc.example> 550-Called: 192.168.34.43 550-Sent: RCPT TO:<xyz@abc.example> 550-Response: 550 Unknown local part xyz in <xyz@abc.example> 550 Sender verification failed
If more than one RCPT command fails in the same way, the details are given only for the first of them. However, some administrators do not want to send out this much information. You can suppress the details by adding ``/no_details'' to the ACL statement that requests sender verification. For example:
verify = sender/no_details
A dilemma arises when a local address is redirected by aliasing or forwarding during verification: should the generated addresses themselves be verified, or should the successful expansion of the original address be enough to verify it? Exim takes the following pragmatic approach:
When an incoming address is redirected to just one child address, verification continues with the child address, and if that fails to verify, the original verification also fails.
When an incoming address is redirected to more than one child address, verification does not continue. A success result is returned.
This seems the most reasonable behaviour for the common use of aliasing as a way of redirecting different local parts to the same mailbox. It means, for example, that a pair of alias entries of the form
A.Wol: aw123 aw123: :fail: Gone away, no forwarding address
work as expected, with both local parts causing verification failure. When a redirection generates more than one address, the behaviour is more like a mailing list, where the existence of the alias itself is sufficient for verification to succeed.
An MTA is said to relay a message if it receives it from some host and delivers it directly to another host as a result of a remote address contained within it. Redirecting a local address via an alias or forward file and then passing the message on to another host is not relaying, but a redirection as a result of the ``percent hack'' is.
Two kinds of relaying exist, which are termed ``incoming'' and ``outgoing''. A host which is acting as a gateway or an MX backup is concerned with incoming relaying from arbitrary hosts to a specific set of domains. On the other hand, a host which is acting as a smart host for a number of clients is concerned with outgoing relaying from those clients to the Internet at large. Often the same host is fulfilling both functions, as illustrated in the diagram below, but in principle these two kinds of relaying are entirely independent. What is not wanted is the transmission of mail from arbitrary remote hosts through your system to arbitrary domains.
You can implement relay control by means of suitable statements in the ACL that runs for each RCPT command. For convenience, it is often easiest to use Exim's named list facility to define the domains and hosts involved. For example, suppose you want to do the following:
Deliver a number of domains to mailboxes on the local host (or process them locally in some other way). Let's say these are my.dom1.example and my.dom2.example.
Relay mail for a number of other domains for which you are the secondary MX. These might be friend1.example and friend2.example.
Relay mail from the hosts on your local LAN, to whatever domains are involved. Suppose your LAN is 192.168.45.0/24.
In the main part of the configuration, you put the following definitions:
domainlist local_domains = my.dom1.example : my.dom2.example domainlist relay_domains = friend1.example : friend2.example hostlist relay_hosts = 192.168.45.0/24
Now you can use these definitions in the ACL that is run for every RCPT command:
acl_check_rcpt: accept domains = +local_domains : +relay_domains accept hosts = +relay_hosts
The first statement accepts any RCPT command that contains an address in the local or relay domains. For any other domain, control passes to the second statement, which accepts the command only if it comes from one of the relay hosts. In practice, you will probably want to make your ACL more sophisticated than this, for example, by including sender and recipient verification. The default configuration includes a more comprehensive example, which is described in chapter 7.