Hello again. I apologize in advance for a lot of text.
This question relates to a Postfix+Dovecot setup, but I'm posting it in
the Postfix list because I think this should be solved on the Postfix
side.
I have a small personal mail server with a MariaDB virtual users backend
hosting several accounts for me and some of my friends and relatives.
Initially, I didn't have any experience with software of this kind, so I
started with one of the many tutorials that can be found on the
internet, and continued to fine-tune my setup by studying the relevant
manuals and in-depth guides until I finally got (almost) exactly what I
was aiming for. Now, you might say that MariaDB is overkill for a small
server, but I already worked with it before, so at least this part was
mostly familiar. Anyway, while testing my setup, I ran into a certain
issue, for which I, surprisingly, couldn't find any ready-made solutions
online - so I had to come up with my own. I'm still wondering if I did
it right and if there might be a better way, so I would like to ask more
experienced users here if they have any thoughts on this.
On my server (and I guess on many others too), each email that arrives
from outside is handled first by Postfix smtpd, then passed on to
Dovecot LDA and saved in the recipient user's maildir. In case Dovecot
cannot accept the mail for some reason, Postfix will generate and send a
DSN back to the envelope-from address. This is generally not a good
thing, because it means that my server can become a source of
backscatter spam. Therefore, as long as everything is working normally,
we want each and every rejection to happen during the SMTP session. To
make sure Postfix does not bounce the mail back from Dovecot, the
following steps are taken:
1) Postfix performs virtual alias expansion to determine the final
recipient address.
2) Postfix looks up the recipient address in the database to make sure
it exists and is active.
3) Postfix queries the Dovecot quota-status service to make sure that
the user is not over quota.
Now, if you've already noticed a potential source of problems here, good
for you! I will however explain a bit more to make sure everyone
understands what's going on.
Since I don't want a user to fill up their mailbox with mail until
there's not enough disk space left, either accidentally or deliberately,
I'm using the Dovecot quota mechanism to limit the amount of free space
provided to each user. Naturally, if a user is over quota, we want to
know that in advance before we hand the mail over to Dovecot, to avoid
potential backscatter bounces. Dovecot has a solution to this - the
quota-status service, which provides a Postfix-compatible SMTP policy
server to enable Postfix to perform quota checks at connection stage.
And here's the issue: Dovecot does not know anything about aliased
addresses, and Postfix does not bother to disclose the final recipient
address to the quota-status service. This can lead to Postfix
erroneously handing the mail over to Dovecot even if the user is over
quota, then having to bounce it back.
Example: I have a primary address u...@mydomain.com and a secondary
address al...@mydomain.com, which also maps to u...@mydomain.com. If a
mail arrives destined for al...@mydomain.com, Postfix will expand this
alias and correctly tell Dovecot to deliver to u...@mydomain.com.
However, when talking to the quota-status service, Postfix will ask it
to check the quota status for al...@mydomain.com, which Dovecot knows
nothing about. This means that if u...@mydomain.com is over quota,
Postfix will suspect nothing and happily ask Dovecot to deliver the
mail, then suddenly realize that something's wrong (Dovecot doesn't
accept the mail), and proceed to generate a DSN to inform the sender -
which is exactly what we DON'T want to happen. Ideally, we want the
sender to know about this _before_ they finish talking to our Postfix,
not after; otherwise, if the sender address was forged and the mail is
spam (that somehow managed to pass our spam filter), we become a spammer
too.
Besides discarding the message entirely in this case, which is most
certainly NOT a good idea since it leaves the sender completely in the
dark, I can see at least 2 general approaches to address this problem:
1) Make sure quota-status (i.e. Dovecot) knows about aliased addresses.
2) Make sure Postfix somehow tells quota-status the final recipient
address instead of the aliased address.
NB: In case such a thing is even possible, I do not have any aliases
that map to multiple recipient addresses, nor do I plan to have any in
the future either. Each and every alias maps to only one recipient
address, so the quota is well-defined for any destination as long as
it's valid (i.e. maps to an active user in the database).
I tried the first approach initially, and found it not very reliable.
This is because Postfix can have multiple virtual_alias_maps with
different logical meanings and associated with different SQL queries
(e.g. single address alias vs whole domain alias vs catch-all alias);
Dovecot, however, has only one user_query setting that fetches a user
from the database by their login/email/domain. To make the first
approach work, I would have to combine all these queries into one (not
trivial because Dovecot needs more data in the result set than just an
address), make sure the resulting query has no errors, and also rewrite
it every time I make changes to the original queries so that the lookup
algorithm always stays the same on both sides. It's not that I make
changes often, but this just doesn't seem right to me, mainly because it
is Postfix's job to do alias expansion, and I don't see why Dovecot has
to do the same work again just to be able to check a user's quota
status.
The second approach is therefore the prefered one. However, it is also
not that simple. I couldn't find any Postfix configuration option so
that is passes the expanded recipient address instead of the original
recipient address to a policy service. I eventually resorted to writing
a shell script that acts as a proxy between Postfix and the quota-status
service (via "spawn"). It first uses "postconf" to find the value of
virtual_alias_maps, then calls "postmap -q ..." to perform the
expansion, and finally passes the result to quota-status. While testing
this script, I struggled with a rather tricky problem: since I'm using
MariaDB, I don't want to open a separate connection every time I read
from the database, because opening a new connection is an expensive
operation. This is already solved by Postfix providing the "proxymap"
service which keeps the connections open for me, but the actual problem
here is that Postfix's authors seem to have intentionally made it
difficult to allow access to this service from outside: the directory
"/var/spool/postfix/private" where it resides is only available to the
"postfix" user, and Postfix explicitly disallows running external
programs as this user (not sure why, but the manual mentions that, so it
seems intentional - and also somewhat annoying). I had to configure the
"sudo" program to allow the user my script runs as to perform "sudo -u
postfix postmap -q proxy:mysql:..." without a password, and then I had
to mess with PAM config files to make sure it doesn't produce audit logs
every time this happens.
Considering what I had to endure to make sure my script finally worked,
I'm left a bit puzzled: perhaps I was doing it all wrong from the
beginning? Could there be another, "cleaner" approach to solve this?
Maybe there actually is a setting to let Postfix pass the expanded
address to a policy service? If not, and if this isn't a serious enough
issue to warrant a feature request, then maybe there is a way to allow
external access to the proxymap service without _that_ much hassle? I
studied the manual entry for proxymap, but I couldn't find anything
relevant there. I can change permissions for the
"/var/spool/postfix/private" directory, but that seems to open up access
to everything inside, and I don't want that. As far as I can see,
considering that proxymap should be available to chrooted services, it
cannot be moved out of its original location, and the only way to access
it without changing permissions is to run the script as the "postfix"
user, which Postfix itself does not allow.
P.S. Another interesting quirk I've found during my effort to make my
server backscatter-proof: the command "RCPT TO: <Postmaster>" seems to
cause Postfix to bypass all recipient restrictions, including queries to
policy services. This is bad because there is no normal way to check the
quota status of the Dovecot user account associated with this
destination. I only managed to work around this by using
smtpd_command_filter to replace "RCPT TO: <Postmaster>" with "RCPT TO:
<postmas...@mydomain.com>". Not sure if a bug or feature, since the RFC
explicitly states that every SMTP server must accept mail to
"<Postmaster>", but this leaves a lot of room for abuse as this can
create backscatter if such mail is rejected at a later stage (e.g. by
Dovecot).
--
Kind regards,
Vladimir