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

Reply via email to