Someone recently brought a problem to the Exim mailing list where, as we originally understood it, they wanted to reject messages at SMTP time if they had a certain sender, went to certain recipients, and had a specific message in their
Subject:
. This is actually a little bit difficult to do straightforwardly in Exim because of the recipients condition.
In order to check the
Subject:
header, your ACL condition must run in the
DATA
phase (which is the earliest that the message headers are available). If you don't need to check the recipients, this is straightforward and you get something like this:
deny
senders = <address list>
condition = ${if match{$h_subject:}{Commit}}
message = Prohibited commit message
The problem is in matching against the recipients. By the DATA phase there may be multiple recipients, so Exim doesn't offer any simple condition to match against them (the
recipients
ACL condition is valid only in the
RCPT TO
ACL, although Exim's current documentation doesn't make this clear). Exim exposes the entire accepted recipients list as
$recipients
, but you have to write a matching expression for this yourself and it's not completely trivial.
Fortunately there is a straightforward way around this: we can do our matching in stages and then accumulate and communicate our match results through ACL message variables. So if we want to match recipient addresses, we do that in the
RCPT TO
ACL in a
warn
ACL stanza whose only purpose is providing us a place to set an ACL variable:
warn recipients = <address list> set acl_m0_nocommit = 1
(After all, it's easy to match the recipient address against things in the
RCPT TO
ACL, because that's a large part of its purpose.)
Then in our DATA phase ACL we can easily match against
$acl_m0_nocommit
being set to 1. If we're being extra-careful we'll explicitly set
$acl_m0_nocommit
to 0 in our
MAIL FROM
ACL, although in practice you'll probably never run into a case where this matters.
Another example of communicating things from
RCPT TO
to
DATA
ACLs is in how we do milter-based spam rejection . Because
DATA
time rejection applies to all recipients and not all of our users have opted in to the same level of server side spam filtering, we accumulate a list of everyone's spam rejection level in the
RCPT TO
ACLs, then work out the minimum level in the
DATA
ACLs. This is discussed in somewhat more detail in the sidebar here .
In general ACL message variables can be used for all sorts of communication across ACL stanzas, both between different ACLs and even within the same ACL. As I sort of mentioned in how we do MIME attachment type logging with Exim , our rejection of certain sorts of attachments is done by recording the attachment type information into an ACL message variable and then reusing it repeatedly in later stanzas. So we have something like this:
warn
# exists just to set our ACL variable
[...]
set acl_m1_astatus = ${run [...]}
deny
condition = ${if match{$acl_m1_astatus} {\N (zip|rar) exts:.* .(exe|js|wsf)\N} }
message = ....
deny
condition = ${if match{$acl_m1_astatus} {\N MIME file ext: .(exe|js|bat|com)\N} }
message = ....
deny
condition = ${if match{$acl_m1_astatus} {\N; zip exts: .zip; inner zip exts: .doc\N} }
message = ....
[...]
(Note that these conditions are simplified and shortened from our real versions.)
None of this is surprising. Exim's ACL message variables are variables, and so you can use them for communicating between different chunks of code just as you do in any other programming language. You just have to think of Exim ACLs and ACL stanzas as being a programming language and thus being something that you can write code in. Admittedly it's a peculiar programming language, but then much of Exim is this way .