Doing things the clever way in Exim ACLs by exploiting ACL message variables

April 8, 2017

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:

   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:

  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:

  # exists just to set our ACL variable
  set acl_m1_astatus = ${run [...]}

  condition = ${if match{$acl_m1_astatus} {\N (zip|rar) exts:.* .(exe|js|wsf)\N} }
  message = ....

  condition = ${if match{$acl_m1_astatus} {\N MIME file ext: .(exe|js|bat|com)\N} }
  message = ....

  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.

Written on 08 April 2017.
« Wayland is now the future of Unix graphics and GUIs
A single .jar recognized as several types of malware at once »

Page tools: View Source, Add Comment.
Login: Password:
Atom Syndication: Recent Comments.

Last modified: Sat Apr 8 23:41:04 2017
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.