Doing things the clever way in Exim ACLs by exploiting ACL message variables
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.
|
|