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
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
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
master.cf I came up with the following extremely simple
solution: a custom wrapper that is integrated using
(...) mailbox_command = /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.