== How I do per-address blocklists with Exim Yesterday I wrote about [[the power of per-address blocklists ../spam/IndividualBlocklistsPower]]. 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. .pn prewrap on # 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 deny 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. deny 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 EximHostsListDanger]], 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 http://www.exim.org/exim-html-current/doc/html/spec_html/ch-domain_host_address_and_local_part_lists.html#SECTfilnamlis]] 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 _/_.