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.