If you’re looking for a policy server that includes greylisting, PolicyD is a solid choice.

In addition to covering greylisting, it also offers per-user/domain rate limiting, quotas, and AV scanning before you accept the message (courtesy of Amavisd-new).

Configuring PolicyD (v2)

PolicyD v2 is a policy server for many popular MTAs (Postfix, Sendmail, etc). The goal is to implement as many spam combating and email compliance capabilities as possible while simultaneously maintaining the portability, stability and performance required for mission critical email hosting. Documentation for policyd here

We start by creating the database and SQL files:

CREATE ROLE policyd WITH ENCRYPTED PASSWORD 'password';
CREATE DATABASE policyd WITH OWNER = policyd;

I had a couple issues with the sql generation. It appears that they didn’t write the scripts using portable sed. Until the port gets patched, the easiest workaround is to install textproc/gsed. Then go in and patch the convert-tsql, parse-checkhelo-whitelist, and parse-greylisting-whitelist scripts to call gsed instead of sed.

You can use the following:

sed -i -e 's/sed/gsed/g' convert-tsql

and

sed -i -e 's/sed/gsed/g' parse-checkhelo-whitelist
sed -i -e 's/sed/gsed/g' parse-greylisting-whitelist

Additionally, I had to further patch the convert-tsql script, in the pgsql section so it would generate valid SQL (as interpreted by PostgreSQL 9.4). Adding the following line replaces all the lines starting with a ‘#’ with ‘–’ and removes empty lines.

-e 's/#/--/'
-e '/^\s*$/d'

The whole section looks like this:

  "pgsql")
    gsed \
      -e 's/@PRELOAD@/SET CONSTRAINTS ALL DEFERRED;/' \
      -e 's/@POSTLOAD@//' \
      -e 's/@CREATE_TABLE_SUFFIX@//' \
      -e 's/@SERIAL_TYPE@/SERIAL PRIMARY KEY/' \
      -e 's/@BIG_INTEGER_UNSIGNED@/INT8/' \
      -e 's/@TRACK_KEY_LEN@/512/' \
      -e 's/@SERIAL_REF_TYPE@/INT8/' \
      -e 's/#/--/' \
      -e '/^\s*$/d' < "$file"
  ;;

Now you can generate the SQL file:

cd /usr/local/share/policyd2/database/
for i in core.tsql access_control.tsql quotas.tsql amavis.tsql checkhelo.tsql checkspf.tsql greylisting.tsql ; do
bash convert-tsql pgsql ${i}
done > /tmp/policyd.sql

Now you can import your policyd.sql file

psql -U policyd -W policyd < /tmp/policyd.sql

Optionally, you can import the whitelist entries for ‘checkhelo’ and ‘greylisting’. These can be found in ‘/usr/local/share/policyd2/database/whitelist/’.

bash parse-checkhelo-whitelist > /tmp/policyd-checkhelo.sql
bash parse-greylisting-whitelist > /tmp/policyd-greylisting.sql
psql -U policyd -W policyd < /tmp/policyd-checkhelo.sql
psql -U policyd -W policyd < /tmp/policyd-greylisting.sql

Now that the database tables are populated, we can configure the /usr/local/etc/cluebringer.conf file.

Config file stripped of comments or empty lines:

# cluebringer.conf
[server]
protocols=<<EOT
Postfix
EOT
modules=<<EOT
Core
AccessControl
CheckHelo
CheckSPF
Greylisting
Quotas
EOT
pid_file=/var/run/cbpolicyd.pid
min_servers=2
min_spare_servers=2
max_spare_servers=4
max_servers=10
max_requests=1000
log_level=2
log_mail=mail@syslog:native
log_mail=maillog
host=*
port=10031
[database]
DSN=DBI:Pg:database=policyd;host=localhost
Username=policyd
Password=password
bypass_mode=tempfail
bypass_timeout=30
[AccessControl]
enable=1
[Greylisting]
enable=1
[CheckHelo]
enable=1
[CheckSPF]
enable=1
[Quotas]
enable=1

We also need to edit the config.php in /usr/local/www/policyd/includes and define the database connection parameters:

<?php
# config.php
#
# mysql:host=xx;dbname=yyy
# pgsql:host=xx;dbname=yyy
# sqlite:////full/unix/path/to/file.db?mode=0666
#
$DB_DSN="pgsql:host=localhost;dbname=policyd";
$DB_USER="policyd";
$DB_PASS="password";

# THE BELOW SECTION IS UNSUPPORTED AND MEANT FOR THE ORIGINAL SPONSOR OF V2
#
#$DB_POSTFIX_DSN="mysql:host=localhost;dbname=postfix";
#$DB_POSTFIX_USER="root";
#$DB_POSTFIX_PASS="";
?>

We can enable policyd with

sysrc policyd2_enable=YES

Now we can start policyd with

service policyd2 start

You should be able to see it in a ps(1) listing:

[louisk@mail louisk 1 ]$ ps ax | grep policyd
 1216  -  Ss       0:17.14 /usr/local/bin/perl /usr/local/bin/cbpolicyd
60241  -  I        0:00.03 /usr/local/bin/perl /usr/local/bin/cbpolicyd
69858  -  I        0:00.02 /usr/local/bin/perl /usr/local/bin/cbpolicyd
75186  0  S+       0:00.00 grep policyd
[louisk@mail louisk 2 ]$

and also with [sockstat(1)][sockstat(1)]:

[louisk@mail louisk 4 ]$ sockstat -46l | grep 10031
root     perl       75871 6  tcp4   127.0.0.1:10031       *:*
root     perl       69858 6  tcp4   127.0.0.1:10031       *:*
root     perl       60241 6  tcp4   127.0.0.1:10031       *:*
root     perl       1216  6  tcp4   127.0.0.1:10031       *:*
[louisk@mail louisk 5 ]$

You should now be able to point your web browser to https://my-ip/policyd/ and see something that looks like this:

Example policy

This policy will define a quota of 200 messages ever 3600s (1 hour):

Add a policy

Policies -> Main: disable Test policy (select policy and choose Action -> Change and switch Disabled to yes. Don’t forget to validate.)

Action -> Add: give it a name and a description

Activate your new policy: select policy and choose Action -> Change (change Disabled to no)

Add a new member to your policy: select it and choose Action -> Members. Next, Action -> Add. Specify any as both a source and a destination.

Now, go back to your policy. Choose Action -> Members, select your member, do Action -> Change, and activate the new member (change Disabled to no).

Add a quota

Under Quotas -> Configure: First, we disable Test quotas.

Add a new quota: Choose Action -> Add

  • Name : Something descriptive
  • Track : user@domain.tld
  • Period (seconds) : 3600
  • Link to policy : specify the policy you created previously
  • Verdict : Defer
  • Data : You can define a custom error message here
  • Comment : This is free-form

Activate your quota : change Disabled to no.

Add a limit to your quota: select your quota, choose Action -> Limits, then Action -> Add.

  • Type : MessageCount
  • Counter Limit : 200

Activate your limit : switch Disabled to no.

You now have a quota defined for user@domain.tld that will defer messages more than 200/hr.

Configuring SPF

Documentation for SPF.

If you would like an easy way to generate SPF records, look here

It should generate a DNS record that looks similar to this. I’ve specified IPs, and names of the mail server, in addition to “the machine sending mail”. The format is that of BIND.

cryptomonkeys.com.  IN TXT "v=spf1 mx a ip4:192.168.0.150 a:mx.cryptomonkeys.com include:mx.cryptomonkeys.com -all"

Footnotes and References