How To Implement SPF In Postfix

Version 1.0
Author: Falko Timme

This tutorial shows how to implement SPF (Sender Policy Framework) in a Postfix 2.x installation. The Sender Policy Framework is an open standard specifying a technical method to prevent sender address forgery (see http://www.openspf.org/Introduction). There are lots of SPF extensions and patches available for Postfix, but most require that you recompile Postfix. Therefore we will install the postfix-policyd-spf-perl package from openspf.org which is a Perl package and can be implemented in existing Postfix installations (no Postfix compilation required).

I want to say first that this is not the only way of setting up such a system. There are many ways of achieving this goal but this is the way I take. I do not issue any guarantee that this will work for you!

 

1 Preliminary Note

I assume that you have already set up a working Postfix mail server.

The following procedure is distribution-independent, i.e., it should work on any Linux distribution (however, I tested this on Debian Etch).

 

2 Install Required Perl Modules

The postfix-policyd-spf-perl package depends on the Mail::SPF and the NetAddr::IP Perl modules. Therefore we are going to install them now using the Perl shell. Start the Perl shell like this:

perl -MCPAN -e shell

If you run the Perl shell for the first time, you will be asked a few questions. You can accept all default values. You will also be asked about the CPAN repositories to use. Select repositories that are close to you.

After the initial Perl shell configuration, we can start to install the needed modules. To install Mail::SPF, simply run

install Mail::SPF

In my case, it tried to install Module::Build (which is a dependency), but then it failed. If this happens to you, simply quit the Perl shell by typing

q

Then start the Perl shell again:

perl -MCPAN -e shell

and try to install Mail::SPF again:

install Mail::SPF

This time it should succeed, and you should see that it also installs the modules Net::DNS::Resolver::Programmable and NetAddr::IP on which Mail::SPF depends.

A successful installation of Mail:SPF should end like this:

Installing /usr/local/bin/spfquery
Writing /usr/local/lib/perl/5.8.8/auto/Mail/SPF/.packlist
  /usr/bin/make install  -- OK

Because NetAddr::IP has already been installed, we can now leave the Perl shell:

q

 

3 Install postfix-policyd-spf-perl

Next we download postfix-policyd-spf-perl from http://www.openspf.org/Software to the /usr/src/ directory and install it to the /usr/lib/postfix/ directory like this:

cd /usr/src
wget http://www.openspf.org/blobs/postfix-policyd-spf-perl-2.001.tar.gz
tar xvfz postfix-policyd-spf-perl-2.001.tar.gz
cd postfix-policyd-spf-perl-2.001
cp postfix-policyd-spf-perl /usr/lib/postfix/policyd-spf-perl

Then we edit /etc/postfix/master.cf and add the following stanza at the end:

vi /etc/postfix/master.cf
[...]
policy  unix  -       n       n       -       -       spawn
        user=nobody argv=/usr/bin/perl /usr/lib/postfix/policyd-spf-perl

(The leading spaces before user=nobody are important so that Postfix knows that this line belongs to the previous one!)

Then open /etc/postfix/main.cf and search for the smtpd_recipient_restrictions directive. You should have reject_unauth_destination in that directive, and right after reject_unauth_destination you add check_policy_service unix:private/policy like this:

vi /etc/postfix/main.cf
[...]
smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,check_policy_service unix:private/policy
[...]

or like this:

[...]
smtpd_recipient_restrictions =
            [...]
            reject_unauth_destination
            check_policy_service unix:private/policy
            [...]
[...]

It is important that you specify check_policy_service AFTER reject_unauth_destination or else your system can become an open relay!

Then restart Postfix:

/etc/init.d/postfix restart

That's it already. You should check the README file that comes with the postfix-policyd-spf-perl package, it contains some important details about how postfix-policyd-spf-perl processes emails, e.g. like this part from the postfix-policyd-spf-perl-2.0001 README:

This version of the policy server always checks HELO before Mail From (older
versions just checked HELO if Mail From was null). It will reject mail that
fails either Mail From or HELO SPF checks. It will defer mail if there is a
temporary SPF error and the message would othersise be permitted
(DEFER_IF_PERMIT). If the HELO check produces a REJECT/DEFER result, Mail From
will not be checked.

If the message is not rejected or deferred, the policy server will PREPEND the
appropriate SPF Received header. In the case of multi-recipient mail, multiple
headers will get appended. If Mail From is anything other than completely empty
(i.e. ) then the Mail From result will be used for SPF Received (e.g. Mail
From None even if HELO is Pass).

The policy server skips SPF checks for connections from the localhost (127.) and
instead prepends and logs 'SPF skipped - localhost is always allowed.'

 

4 Test policyd-spf-perl

We can test policyd-spf-perl by running

perl /usr/lib/postfix/policyd-spf-perl

The cursor will then wait on the policyd-spf-perl shell. We can now act as if we tried to send an email from a certain domain and a certain server to another email address. policyd-spf-perl will then check if that certain server is allowed to send emails for the sender domain and show us the result.

So let's see what happens if we try to send a mail from [email protected]****forge.com from the server h****.server*********.net (IP address 81.169.1**.**). The h****forge.com has an SPF record that allows 81.169.1**.** to send emails from h****forge.com.

So on the policyd-spf-perl shell we type:

request=smtpd_access_policy
protocol_state=RCPT
protocol_name=SMTP
helo_name=h****forge.com
queue_id=8045F2AB23
[email protected]****forge.com
[email protected]*******.de
client_address=81.169.1**.**
client_name=h****.server*********.net
[empty line]

The output should look like this:

action=PREPEND Received-SPF: pass (h****forge.com: 81.169.1**.** is authorized to use '[email protected]****forge.com' in 'mfrom' identity (mechanism 'ip4:81.169.1**.**' matched)) receiver=server1.example.com; identity=mfrom; envelope-from="[email protected]****forge.com"; helo=h****forge.com; client-ip=81.169.1**.**

which means we passed the test.

Let's run another test, this time we will send from the client 1.2.3.4 (www.example.com) which is not allowed to send emails from h****forge.com:

request=smtpd_access_policy
protocol_state=RCPT
protocol_name=SMTP
helo_name=h****forge.com
queue_id=8045F2AB23
[email protected]****forge.com
[email protected]*******.de
client_address=1.2.3.4
client_name=www.example.com
[empty line]

This is the output, the test failed as expected:

action=PREPEND Received-SPF: softfail (h****forge.com: Sender is not authorized by default to use '[email protected]****forge.com' in 'mfrom' identity, however domain is not currently prepared for false failures (mechanism '~all' matched)) receiver=server1.example.com; identity=mfrom; envelope-from="[email protected]****forge.com"; helo=h****forge.com; client-ip=1.2.3.4   

We can now even try to leave the sender field empty, as many spammers do. Still, policyd-spf-perl should be able to complete its tests:

request=smtpd_access_policy
protocol_state=RCPT
protocol_name=SMTP
helo_name=h****forge.com
queue_id=8045F2AB23
sender=
[email protected]*******.de
client_address=81.169.1**.**
client_name=h****.server*********.net
[empty line]

This is the output, we are still allowed to send from h****forge.com:

action=PREPEND Received-SPF: pass (h****forge.com: 81.169.1**.** is authorized to use 'h****forge.com' in 'helo' identity (mechanism 'ip4:81.169.1**.**' matched)) receiver=server1.example.com; identity=helo; helo=h****forge.com; client-ip=81.169.1**.**

Let's try the same test with an invalid client:

request=smtpd_access_policy
protocol_state=RCPT
protocol_name=SMTP
helo_name=h****forge.com
queue_id=8045F2AB23
sender=
[email protected]*******.de
client_address=1.2.3.4
client_name=www.example.com
[empty line]

As expected, this is the output:

action=PREPEND Received-SPF: softfail (h****forge.com: Sender is not authorized by default to use 'h****forge.com' in 'helo' identity, however domain is not currently prepared for false failures (mechanism '~all' matched)) receiver=server1.example.com; identity=helo; helo=h****forge.com; client-ip=1.2.3.4 

To leave the policyd-spf-perl shell, type

[CTRL+C]

 

Share this page:

Suggested articles

18 Comment(s)

Add comment

Comments

By:

Thank you for the excellent how-to. I've only recently come across SPF and after creating a short tutorial detailing how to set up SPF / Sender ID records with 123-reg for the domain itself, I was dreading having to actually implement it in Postfix.

I'll let you know how I get on.

By:

If you are using the current release of Ubuntu (7.04 - Feisty Fawn) mail::spf and the Postfix policy server are built into the packaging system.  All you have to do is:

sudo apt-get install libmail-spf-perl postfix-policyd-spf-perl

and all of the packages with dependencies will get pulled in.

The is also a Python policy server at the same site.  It is also in Ubuntu with the Python SPF library updated to the current release (2.0.3).  If you prefer Python all you have to do is:

sudo apt-get install python-spf python-policyd-spf

With the exception of the saying python instead of perl and the path to the executable (covered in the documentation), integration with Postfix is the same.

By:

One other small comment... The How-To says Postfix 2.x, but the policy service was introduced in Postfix 2.1, so it should say 2.1+ rather than 2.x.

By:

this is on my schedule to do tomorrow.

Just wondering though why other How Tos for this menution DNS

settings and this one does not?

Thanks

 

By: Panzer

This HOWTO describes the way to check if the incoming mail message is in accordance with the SPF that is set by the admins of the domain from which the message was sent.

DNS records for SPF serve the purpose of setting your own policy for others to check.

Therefore, you can check for SPF for incoming messages and not use SPF for your own domain (makes sense if you're not in control of your DNS records, or if it is complicated for you to create a sutable SPF policy), or you can set your own SPF policy for your domain and not use SPF for incoming messages (stupid), or you can use both SPF checking for incoming mail and set your own SPF policy for others to check (best).

 

By:

Hi there

Thanks for this HOWTO, it was very informative.  I picked up a problem, however.  In this section:

smtpd_recipient_restrictions = permit_sasl_authenticated,permit_mynetworks,reject_unauth_destination,check_policy_service unix:private/policy

The entry check_policy_service unix:private/policy causes Postfix to generate the following error:

postfix/smtpd[2815]: warning: problem talking to server private/policy: Connection timed out

This may be due to policyd-weight not being installed.  Deleting this entry from main.cf resolves the issue (or presumably installing policyd-weight will solve the issue too, however, since policyd-weight is no longer being maintained, I don't think I will go for this option.)

 Regards

By: Dauto

Hi,

I follow the steps and i´m getting the same error:

Jan 12 18:41:52 mta01 postfix/smtpd[9392]: warning: connect to private/policy: No such file or directory
Jan 12 18:41:53 mta01 postfix/smtpd[9392]: warning: connect to private/policy: No such file or directory
Jan 12 18:41:53 mta01 postfix/smtpd[9392]: warning: problem talking to server private/policy: No such file or directory

Any help.

Regards,

Dauto

By:

Thanks for a really nice tutorial, works like a charm!

 As for Fedora 9, all files are installed from standard repositories by :

yum install  perl-Mail-SPF-Query perl-Mail-SPF

 The postfix-policyd-spf script lives in the perl-Mail-SPF package as

/usr/share/doc/perl-Mail-SPF-Query-1.999.1/examples/postfix-policyd-spf

 I just copied this file to /usr/local/libexec, making corresponding changes to master.cf.

By: NicuAdrian

greylisting and SPF-policyd-perl work together?

By: Alv

If you get error like this:

 warning: problem talking to server private/policy-spf: No such file or directory

You should check name of policy in master.cf. In this howto is name  in master.cf

  policy unix - ........

but in main.cf is  policy-spf. So easily rename to  policy-spf in master.cf to

  policy-spf unix - ........

 

 

By: Bogdan

Hey there, is it me or between the updates for ISPConfig 3 (from 3.0.5.2 to 3.0.5.3 for example) the Postfix main.cf file is modified and in smtpd_recipient_restrictions the check_policy_service unix:private/policy option is missing after an update ?
 
I am using Ubuntu 12.04.3 LTS x64, with your guide for ISPConfig and I've noticed that twice.
 
Thanks !

By: Ethan

I also had to install the CPAN module Sys::Hostname::Long.

By the way, it makes sense to test the script BEFORE editing main.cf and restarting postfix, to make sure you don't lose any mails in case it doesnt work.

By: Clinton

Note that postfix-policyd-spf-perl is maintenance-only.  For current development, see python-postfix-policyd-spf, also at http://www.openspf.org/Software

By: Pat

useful info, thanks.

By: MrPete

CRUCIAL: postfix-policyd-spf-perl has serious BUGS. For example, it is rejecting softfail SPF (which should not be rejected.) And the current version no longer installs properly. It has not had a bug fix in many years. Thus, this tutorial is OUTDATED.

 

The current SPF tool is python-postfix-policyd-spf, also at http://www.openspf.org/Software

By: Lucian

Can you set a whitelist domain if someone set SPF wrong ?

By: Araz

Lucian, make sure your SPF record is correct and valid, you are welcome to use https://easydmarc.com/spf-record-check-tools 

By: Hifall

Do check if your SPF record is valid using a tool like: https://dmarcly.com/tools/spf-record-checker

Also, make sure your SPF record doesn't contain 10+ DNS lookups: https://dmarcly.com/blog/spf-permerror-too-many-dns-lookups-when-spf-record-exceeds-10-dns-lookup-limit