== An interesting gotcha with Exim and _.forward_ processing Yesterday I described [[how Exim implements traditional .forward semantics EximForwardHow]] where putting your own address in your .forward means 'deliver it to me, bypassing my .forward'. Because Exim is [[a mailer construction kit EximMailerKit]], this isn't a specific feature for .forward handling, it's a generic general feature that happens to give you this result. So far, so good. Now, let's talk about [[our .forward-nonspam feature ../spam/OurSpamForwardingSolution]]. In the abstract, this is just another .forward-style router that reads a different file and only triggers under some conditions. In concrete, we need several routers in sequence, each of them doing one step of the processing logic: # if .forward-nonspam exists and the message is not spam, expand .forward-nonspam # if the message is spam, .forward-nonspam exists, and .forward does not exist, discard the message # if .forward exists, expand .forward If you have both a .forward-nonspam and a .forward, the third rule will only be triggered for spam messages because your .forward-nonspam skims off non-spam messages first. Well. Mostly. You see, although all three of these routers are conceptually a single block of .forward processing, Exim doesn't know this; as far as Exim is concerned, they are three separate and completely unrelated routers. Now suppose you put your own address into .forward-nonspam and also have a .forward, as you might do to create a simple 'put all non-spam email into my regular inbox and all spam mail into a file' system, and you get a non-spam message. Exim processes things until it reaches the first router, expands your .forward-nonspam, gets your address and restarts routing it, gets to the first router again, sees that the router has already handled this address, ~~and only skips that router~~, not all three .forward-processing routers. So your address falls through to the third router, which says 'sure, you have a .forward, I'll handle this' and dumps the non-spam message into the file for spam email. Oops. The fix for this is to split the third router into two routers, one for the case where you do have a .forward-nonspam (where it would only handle messages that are explicitly spam-tagged) and a second one for the case where you have no .forward-nonspam (where it would handle everything). However, this requires an annoying level of repetition in the Exim configuration file. (For technical reasons I think that you can't combine this together in a single condition on a single router that works quite exactly right.) === Sidebar: the technical reasons The condition you need is 'if .forward exists and either .forward-nonspam doesn't exist or the message is non-spam'. Exim has special support for securely and correctly checking for file existence over NFS, but this support is only available in the ((require_files)) router condition. However, we need to use a ((condition)) check with a '_${if ...}_' string expansion to check 'is non-spam'. You can't or together separate router conditions (they are all implicitly and'd together instead), and the does-file-exist check that's available in a _${if_ expansion doesn't work the right way over NFS. In theory you could get around this with various evil hacks involving [[Exim string expansion ../programming/EximObservation]], maybe. (Talking to myself: one could rephrase the condition as 'if .forward exists and, if the message is non-spam, .forward-nonspam doesn't exist' and then write this as a single ((require_files)) condition with a conditional string expansion in it.)