Postfix and PostFWD (Postfix Firewall Daemon) – Integrating the two

emailpostfix

I needed a way to be able to aggressively rate limit the number of mails that were being sent TO a specific domain (the whole domain), any mail that exceeded the rate limit needs to be rejected of dropped. Not delayed of deferred.

I cannot use Exim, so here we are, with Postfix and PostFWD, and a couple of issues.

Some versions..

Postfix v2.6.6
PostFWD v1.3.5
CentOS 6.8 x64

So to begin with, I verify that my rate limit rule exists in postfwd and is interpreted correctly from postfwd.cf in the postfix directory (it is):

[root@monitoringtest ~]# /usr/local/postfwd/sbin/postfwd -f /etc/postfix/postfwd.cf -C
Rule   0: id->"davelimit001"; action->"rate(recipient_domain/3/1800/421 4.7.1 - Sorry, exceeded 3 messages in 30 minutes.)"; recipient_domain->"==;dave-byrne.co.uk"
[root@monitoringtest ~]#

The above rate limits all outbound mail destined for anything @dave-byrne.co.uk to just 3emails within a 30minute window. The domain is my own for testing, but in production, this will rate limit messages bound for an external Email to SMS gateway.

A quick check to ensure Postfix and PostFWD are up and listening (they are):

[root@monitoringtest ~]# netstat -anpl | grep ':10040\|:25'
tcp        0      0 127.0.0.1:10040             0.0.0.0:*                   LISTEN      4093/postfwd.pid
tcp        0      0 0.0.0.0:25                  0.0.0.0:*                   LISTEN      4190/master
tcp        0      0 :::25                       :::*                        LISTEN      4190/master
[root@monitoringtest ~]#

I then fire sample requests at the PostFWD server listening internally on port 10040. you can see PostFWD passes (with its DUNNO action) 3 mails, before applying the rate limit to the 4th and rejecting it with a 421. Perfect. Now to just make Postfix use PostFWD!

[root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample
action=DUNNO
[root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample
action=DUNNO
[root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample
action=DUNNO
[root@monitoringtest ~]# nc 127.0.0.1 10040 </usr/local/postfwd/tools/request.sample
action=421 4.7.1 - Sorry, exceeded 3 messages in 30 minutes.
[root@monitoringtest ~]#

An excerpt from /var/log/maillog showing the rate limit applying to the 4th message above

Oct 19 17:15:47 monitoringtest postfwd[25933]: [RULES] rule=0, id=davelimit001, client=root@monitoringtest.co.uk[168.100.1.7], 
sender=<root@monitoringtest.co.uk>, recipient=<admin@dave-byrne.co.uk>, helo=<dave-byrne.co.uk>, proto=ESMTP, state=RCPT, rate=rate/4/21.67s, delay=0.00s, 
hits=davelimit001, action=421 4.7.1 - Sorry, exceeded 3 messages in 30 minutes.

So, to integrate with Postfix, I added the following to my postfix main.cf file:

[root@monitoringtest ~]# tail -n 3 /etc/postfix/main.cf
127.0.0.1:10040_time_limit   = 3600
smtpd_recipient_restrictions = permit_mynetworks,
        check_policy_service inet:127.0.0.1:10040
[root@monitoringtest ~]#

This is all as per the PostFWD documentation.

I then use telnet locally to connect to Postfix and send emails to admin[at]dave-byrne.co.uk. Like so:

[root@dedweb ~]# telnet <test-server-IP-here> smtp
Trying xx.xx.xx.xx...
Connected to xx.xx.xx.xx.
Escape character is '^]'.
220 monitoringtest.xxxxxxxxx.com ESMTP Postfix
HELO dave-byrne.co.uk
250 monitoringtest.xxxxxxxxx.com
MAIL FROM: root@monitoringtest.co.uk
250 2.1.0 Ok
RCPT TO: admin@dave-byrne.co.uk
250 2.1.5 Ok
DATA
354 End data with <CR><LF>.<CR><LF>
test1.
.
250 2.0.0 Ok: queued as 946B621C15

I do this 4, 5, 6 times, the 4th should have rate limited like it did when using netcat to directly fire them at PostFWD. But it doesn't, postfix just merrily goes about its business relaying the mails like it thinks it should. I can send a hundred and it wont even think about rate limiting. During this time, PostFWD prints NOTHING to the logs, it isn't hit at all, it doesn't pass anything, it doesn't block anything. Postfix isn't using PostFWD, even though its set in smtpd_recipient_restrictions as a check_policy_service.

And this is where I'm stuck. 3 days in and I'm none the wiser. Has anyone ever used PostFWD (Postfix Firewall Daemon) before successfully, with any type of rule set, regardless of rate limiting. I'm open to many suggestions, however I cannot change from postfix, I cannot change OS, and I cannot hand off to an external intermediate mail relay due to security concerns and workflow issues with what's actually being sent.

Thanks all,
Dave.

Best Answer

Immediately after asking this question, I realised that postfix recipient restrictions are executed in the order they appear in main.cf. So my

[root@monitoringtest ~]# tail -n 3 /etc/postfix/main.cf
127.0.0.1:10040_time_limit   = 3600
smtpd_recipient_restrictions = permit_mynetworks,
         check_policy_service inet:127.0.0.1:10040
[root@monitoringtest ~]#

Was returning a hard 'OK' at 'permit_mynetworks'. An OK will stop processing further restrictions.

I resolved my issue by placing the check_policy_service at the top of my smtp recipient restrictions. If PostFWD passes a mail, it replies with 'DUNNO' or rather 'DUNNO/OK', this passes, but continues to run further smtp restrictions.

With this in place, PostFWD was free to pass the mail that didnt trigger the rate limit, but once it did, it replied with a 421 rejection. Exactly what I wanted.

So keep in mind, the order in which you restrict, and what you are actually restricting, matters quite a lot.

Please note that this is a private internal mail server that serves one very specific purpose. Do not use this code for a production or shared mail server