Postfix: ACCEPT if RBL and SPF checks pass, DUNNO/greylist otherwise. How to do it?

I would like to accept all clients that pass RBL and SPF checks (and possibly some checks, but these are minimum requirements for me), and greylist those who don't. When a client passes the SPF check (SPF record exists, no fail, no soft-fail), we can be pretty sure that it's not a botnet zombie, but an MTA that will retry delivery, so there's little point in greylisting such clients.

So far I have been using Whitelister, which can implement this rule, but it hasn't been maintained for the last 10 years or so, and is not available in modern distributions, so I'm looking for alternatives. As far as I understand, Postfix can only reject clients that are in RBLs, but cannot use RBLs as parts of more complex conditions, so I can't see any way to use reject_rbl_client here. Is there a policy daemon that can do such checks?

My recipient restrictions in main.cf are as follows. I don't know what I can put in place of ???:

smtpd_recipient_restrictions =
        check_sender_access regexp:/etc/postfix/sender_access_regexp,
        permit_mynetworks,
        permit_sasl_authenticated,
        reject_unknown_sender_domain,
        reject_unauth_destination,
        ???,
        check_policy_service unix:postgrey/postgrey.sock

I don't know what I can put in place of ???

A: check_policy_service


Long answer:

The postfix source implements reject_rbl_client in smtpd/smtpd_check.c
I expect it should work to add another function there which implements a copy of the reject_rbl_addr function with inverse logic - not replying with DUNNO but OK for a successful check.

--- a/postfix/src/smtpd/smtpd_check.c
+++ b/postfix/src/smtpd/smtpd_check.c
     rbl = find_dnsxl_addr(state, rbl_domain, addr);
     if (!SMTPD_DNSXL_STAT_OK(rbl)) {
-       return (SMTPD_CHECK_DUNNO);
+       return (SMTPD_CHECK_OK);
     } else {

Which you could then use in postfix' config instead of reject_rbl_client.

But this means additional, continual, maintenance to keep updated; patching your source as new releases occur. So I don't expect anyone to go that route.

In lieu of that, you will want to use an external policy check, as the whitelister program did for you. That means you can use check_policy_service here, you just need the right tool/program to interface with. (Btw, it's good to think of upgrading from that, since there was a reason for it being dropped. It fails checks on any IPv6 address.)

Checking for alternatives to that, there are (that I know of, in the debian repos)

  • tumgreyspf
  • postfix-policyd-spf-python and
  • bley,

all of them Python implementations - so while they may not do what you want out of the box (I haven't checked), they should be fairly easily adaptable.

Further ones are

  • postfix-policyd-spf-perl,
  • mtpolicyd (Perl),
  • a more complex and modular "PolicyD" (Perl)

and probably a ton more. You might even write your own, based on the many SPF libraries available.

The documentation about check_policy_service is a bit light, only the sample code blurb explains what values check_policy_service expects:

The result can be any action that is allowed in a Postfix access(5) map.

So access is the next stop, which (maybe) explains that changing DUNNO for OK will turn a "maybe-pass, run further checks first" (> greylist) logic into "mail passed, stop rule processing here".

So now you know how to turn any policy server into a whitelisting system, even if it doesn't do so out of the box.


Addendum: How to set up the policy server, from the policyd-spf README (though I expect you are aware of that, based on whitelister on postgrey).

Installing
----------

 1. Add the following to /etc/postfix/master.cf:

        policyd-spf  unix  -       n       n       -       0       spawn
            user=policyd-spf argv=/usr/bin/policyd-spf

 2. Configure the Postfix policy service in /etc/postfix/main.cf:

        smtpd_recipient_restrictions =
            ...
            reject_unauth_destination
            check_policy_service unix:private/policyd-spf
            ...

        policyd-spf_time_limit = 3600

    NOTE:  Specify check_policy_service AFTER reject_unauth_destination or
    else your system can become an open relay.

 3. Reload Postfix.