How I do per-address blocklists with Exim

January 4, 2016

Yesterday I wrote about the power of per-address blocklists. This is all well and good, but part of the challenge of this is actually implementing them. We use Exim, so my implementation is for that (and it's not entirely original with me; I believe I copied much of this approach from an ex-co-worker).

Since we want to reject at SMTP time, we need to do this in one of Exim's SMTP ACLs. Since we want to reject on a per-address basis, this has to go in the RCPT TO ACL. Exim has good general support for blocklists based on IP origin, MAIL FROM, and so on, so the important trick is to figure out how to easily support per-address ones. We use the simplest brute force approach: a directory hierarchy.

# The top level directory for per-local-address blocks.
BLOCKSDIR       = CFGDIR/blocks
# The per-user directory. Using $local_part is safe
# because we restrict this to valid addresses.
UBLOCKDIR       = BLOCKSDIR/${lc:$local_part}

Then in the actual RCPT TO ACL, we need some rules to match against this. Assuming that you have already rejected any non-local, non-valid addresses:

# Actual per-address block files for hosts and senders are in UBLOCKDIR:
#   UBLOCKDIR/hosts
#   UBLOCKDIR/senders
  hosts = ${if exists{UBLOCKDIR/hosts} {+ignore_unknown : +ignore_defer : UBLOCKDIR/hosts}}
  message = mail from host $sender_host_address not accepted by <$local_part@$domain>.
  log_message = blocked by personal hosts blacklist.

  senders = ${if exists {UBLOCKDIR/senders} {UBLOCKDIR/senders}}
  message = mail from <$sender_address> not accepted by <$local_part@$domain>.
  log_message = blocked by personal senders blacklist.

As I discovered the hard way, the +ignore_unknown and +ignore_defer are very important for making the host-based blocklist work the way you expect it to.

You could use another naming scheme for how to find the per-address host and blocklist files, but this one gives you a simple two-level namespace; you wind up with, say, /var/local/mail/blocks/cks/hosts. In theory it is easy enough to make subdirectories for particular users and then give them to the users to maintain on their own. Even without that, it keeps things nicely organized and lets you see at a glance (or at a ls) which local addresses even theoretically have some filtering.

Now, I have a confession: I don't know if this is actually secure enough to allow arbitrary users to directly manipulate their hosts and senders files (and our current setup doesn't directly expose these directories to users). Exim host and address list file expansion allows people to insert anything that can normally occur in host and address lists, and this is quite powerful. Probably you don't want to allow users to directly edit these files but instead force them to go through some interface or preprocessing step that limits what they can do. At the same time, giving people as much power here as is safe is nice, because you can do a lot of handy things with wildcards and even regular expressions.

(Locally, we have one persistent spammer that hits one of our administrative addresses using changing domains with a pattern that we wound up writing a regular expression to match. I was very happy to discover that we could actually do this, even in a host list read from a file; it was handy to make the spammer go away.)

PS: If you put this in the RCPT TO ACL before you verify that the local part is a valid username, you'll want to pre-filter things so that you block local parts with 'dangerous in filename' characters like .. and /.

Written on 04 January 2016.
« One anti-spam thing I like is per-person (or per-address) blocklists
Illumos's problem with its VCS commit messages »

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

Last modified: Mon Jan 4 01:24:45 2016
This dinky wiki is brought to you by the Insane Hackers Guild, Python sub-branch.