It took me days to figure this one out, so I'd like to share the knowledge.

This is about how to set up SpamAssassin between Postfix and Dovecot while having a separate system user per mailbox, using virtual_alias in Postfix.

I recently wanted to add virus and SPAM filtering to my mail server setup, consisting of Postfix and Dovecot (plus some other policy checking, SPF and DKIM stuff).

Googling this you will find tons of guides on how to set it up, most of them use Amavis to integrate SpamAssassin and ClamAV into the mail chain. I didn't like this approach because it requires a second Postfix instance for back-injection after scanning. I had already integrated ClamAV as milter (using clamav-milter) when coming to SpamAssassin, so I first tried to get along with spamass-milter.

The problem with this approach, as well as with the rest of the guides I found on the net, is that it assumes mail to be processed and stored with a single static UID/GID, which is not the case for my setup. I had chosen to create a separate system user account for each mailbox and use virtual_alias in Postfix to map addresses to it (maybe I'll explain my reasons in a separate post).

The setups described by the guides I found did not play well with this setup, making SpamAssassin always guess the user to run as from the first part of the recipient address and fail at setuid().

So on the LDA end of the chain I had Dovecot, hooked up to Postfix using

mailbox_command = /usr/bin/dovecot_deliver

in order to have Sieve on the server. SpamAssassin scanning needs to be run as the final system user in order to have user preference files and Bayes databases created in the right place, so I was looking for a way to put it right in between.

Long story short, after a couple of attempts with custom transport definitions in master.cf I came up with the following extremely simple solution: a custom wrapper that is integrated using mailbox_command:

/etc/postfix/main.cf:

(...)
mailbox_command = /etc/postfix/spamass-dovecot-pipe.sh

/etc/postfix/spamass-dovecot-pipe.sh:

#!/bin/sh

exec /usr/bin/spamc -u "$USER" -e /usr/lib/dovecot/deliver -f "$SENDER" -d "$USER"

The key here is that anything defined via mailbox_command is run by the local transport under the UID/GID of the final destination, in my case the system account the current message is delivered to. Luckily, Postfix exposes all required information via environment variables.