Previous   Next   Contents       (Exim 4.10 Specification)

37. Access control lists

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.

37.1. ACL return codes

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.

37.2. Unset ACL options

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.

37.3. Data for message ACLs

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.

37.4. Data for non-message ACLs

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

37.5. Use of the ACL selection options

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:

37.6. Format of an ACL

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:

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.

37.7. Condition and modifier processing

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.

37.8. ACL modifiers

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.

37.9. ACL conditions

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.


authenticated = <string list>

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 = *

condition = <string>

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

domains = <domain list>

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.


encrypted = <string list>

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 = *

hosts = < host list>

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.


recipients = <address list>

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.


senders = <address 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 = :

verify = certificate

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

verify = header_syntax

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.


verify = helo

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.


verify = recipient/<options>

This condition is relevant only after a RCPT command. It verifies the current recipient. Details are given in the next section.


verify = reverse_host_lookup

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.


verify = sender/<options>

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.



37.10. Address verification

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.

37.11. Sender address verification reporting

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

37.12. Redirection while verifying

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:

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.

37.13. Using an ACL to control relaying

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.

Controlled relaying

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:

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.


Previous  Next  Contents       (Exim 4.10 Specification)