Weblog Navigation

First Previous Index Next Last

Adventures With Procmail

Tuesday, July 28th, 2009

I was working on converting mailboxes from traditional UNIX format to the more efficient MBX format, and I needed to update some procmail rules to make sure things don't break. UW imapd ships with a utility called dmail which is designed for this purpose. So I figured out how to make it work, then started testing to find problems.

The first issue I ran into is that if dmail can't write to a mailbox for some reason (for example, if the user has mistakenly deleted their spam quarantine folder) it will deliver the mail to their inbox instead. That's a sensible action to take, but in this case, I want an empty mailbox to be created (in MBX format) and then the message to be delivered there. Since dmail has no configuration option to enable this behavior, I decided to just test for the existence of the mailbox myself, and create it if it doesn't exist. My first thought was to simply use touch for this purpose, but an empty file will make imapd treat it as a UNIX-format mailbox instead of MBX, thus defeating the purpose of this whole exercise. So, I wrote my own create-mbx script which creates an empty mailbox only if it doesn't already exist.

Then it was time to test it. The dmail man page says it will return exit codes to let procmail know whether delivery was successful or not, so I assumed procmail should notice a failure and let sendmail know that the message should be bounced to the sender. That turned out not to be the case. If dmail fails to deliver the message, procmail ignores the error and sendmail thinks the message was delivered successfully, so the message is simply lost. Obviously, this is completely unacceptable.

Trapping for errors can't be something that nobody else has ever thought of, so I'm a bit confused as to why it took so long to come up with a solution. I couldn't find anything suggested in the documentation, and I'm sure there must be a better solution, but this is what I managed to hack together:

DEFAULT=/var/spool/mail/$LOGNAME
MAILDIR=$HOME/.imap
DMAIL=/usr/local/sbin/dmail
CREATE=/usr/local/bin/create-mbx
DROPPRIVS=yes
:0      
* ^X-Spam-Score:.* \(\*\*\*\*\*
{
    :0w
    | $CREATE Quarantine && $DMAIL +Quarantine
    EXITCODE=73
    :0
    /dev/null
}

First we create a rule that matches the header we're looking for. There can only be one action per rule, so the action here is a block. Then we need another rule inside the block, and we use the w flag to tell procmail to check for errors, and if an error does occur, skip this rule and go on to the next thing. The action for this rule uses my script to create the destination mailbox if it doesn't exist, then delivers the message. If this is successful, processing stops here (because this is a delivery action). Otherwise (because of the w flag, processing continues. We set an exit code manually, which will indicate a write failure to sendmail, but not until procmail exits! At this point procmail still wants to deliver the message, so we have to use a new rule to force it to “deliver” the message to /dev/null; otherwise it would deliver to the inbox and return an error to sendmail.

This shouldn't have to be this complicated.


Weblog Navigation

First Previous Index Next Last