Postfix server setup

From Smith family
Jump to navigation Jump to search
Server setup
← Previous Next →
MDA (Dovecot) Desktop mail forwarding

This step of the howto assumes you've already set up Dovecot to provide authentication and local delivery services. This page describes the setup for Postfix, TLS, and Amavis + SpamAssassin + ClamAV. Dovecot, and Sieve setup are on the Dovecot server setup page. Squirrelmail setup is described in the Web server setup page, along with the HTTPS configuration.

The basic sources for setting up Postfix and Dovecot are the basic Postfix howto and the Postfix virtual user howto on the Ubuntu wiki.

Basic installation

  • Install postfix
root@server:~# aptitude install postfix mailx 
  • Update /etc/postfix/main.cf
compatibility_level = 2

mydomain = domain1.com   # Change this line for your domain
myorigin = $mydomain
myhostname = mail.$mydomain  # Change this for your mailserver's name

mydestination = $myhostname localhost.$mydomain localhost
relaydomains = $mydomain
relayhost =
mynetworks = 127.0.0.0/8 192.168.1.0/24
smtpd_recipient_restrictions = permit_mynetworks reject_unauth_destination

## Anti-spammer measures
# Don't talk to mail systems that don't know their own hostname.
smtpd_helo_restrictions = reject_unknown_helo_hostname

# Don't accept mail from domains that don't exist.
smtpd_sender_restrictions = reject_unknown_sender_domain

# Block clients that speak too early.
smtpd_data_restrictions = reject_unauth_pipelining

proxy_interfaces = 1.2.3.4  # My external IP
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

# appending .domain is the MUA's job.
append_dot_mydomain = no

# Uncomment the next line to generate "delayed mail" warnings
#delay_warning_time = 4h

alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases

mailbox_size_limit = 0
recipient_delimiter = .+  # Make sure you change this from a '+'
inet_interfaces = all

home_mailbox = Maildir/

virtual_mailbox_domains = /etc/postfix/vhosts
virtual_mailbox_base = /var/vmail
virtual_mailbox_maps = hash:/etc/postfix/vmaps
virtual_minimum_uid = 1000
virtual_uid_maps = static:5000
virtual_gid_maps = static:5000

virtual_alias_maps = hash:/etc/postfix/valiases
  • Tell Postfix which IPv6 address it should use when sending messages. Add the line -o smtp_bind_address6 below the smtp statement in master.cf
smtp      unix  -       -       y       -       -       smtp
  -o smtp_bind_address6=1:2:3::25
  • Tell Postfix about the domains it should service by creating /etc/postfix/vhosts
domain1.com
domain2.com
  • Tell Postfix about the users in those domains by creating /etc/postfix/vmaps
user1@domain1.com  domain1.com/user1/mail/
user2@domain1.com  domain1.com/user2/mail/
user1@domain2.com  domain2.com/user1/mail/
user3@domain2.com  domain2.com/user3/mail/
Then create the hash file:
root@server:~# postmap /etc/postfix/vmaps
(this command needs to be repeated every time /etc/postfix/vmaps is changed)
  • Create aliases for the users in /etc/postfix/valiases
root@domain1.com         user1@domain1.com
postmaster@domain1.com   user1@domain1.com
postmaster@domain2.com   user1@domain1.com
user2@domain1.com        user2@domain2.com

root@server.domain1.com  user1@domain1.com
root@desktop.domain1.com user1@domain1.com
Then create the hash file:
root@server:~# postmap /etc/postfix/valiases
(this command needs to be repeated every time /etc/postfix/valiases is changed)
  • Create /etc/aliases
root:   user1
clamav: root
amavis: root
postmaster:     root
and then create the database
root@server:~# newaliases
  • The virtual mailbox directory structure should be created for each user when they receive their first email.
  • Get Postfix to reload the updated configuration files
root@server:~#postfix reload
  • Test that Postfix is running properly. First, use telnet to connect to postfix
root@server:~# telnet localhost 25
Postfix will respond
Trying 127.0.0.1...
Connected to mail.server.org.
Escape character is '^]'.
220 localhost.localdomain ESMTP Postfix (Ubuntu)
then type the following
ehlo localhost
mail from: root@localhost
rcpt to: user1@domain1.com
data
Subject: My first mail on Postfix

Hi,
Are you there?
regards,
Admin
. (Type the .[dot] in a new Line and press Enter )
quit
then check that the message is delivered properly into the correct mailbox
root@server:~# ls /home/vmail/domain1/user1
  • Then test that all the mail transport is working, using the mail command line utility
root@server:~# mail user2@domain1.com
and again, check that the mail appears in the filesystem.

Adding TLS

Now to enable TLS, which encrypts communication between MTAs, preventing eavesdropping. We'll use the certificates generated previously. All that's needed is to modify the /etc/postfix/main.cf file to point to the certificates.

# TLS parameters
smtpd_tls_cert_file=/etc/letsencrypt/live/webmail.domain.tld/fullchain.pem
smtpd_tls_key_file=/etc/letsencrypt/live/webmail.domain.tld/privkey.pem

# smtpd_use_tls=yes
smtpd_tls_security_level=may
smtpd_tls_session_cache_database = btree:${queue_directory}/smtpd_scache
smtp_tls_session_cache_database = btree:${queue_directory}/smtp_scache

and then reload Postfix

root@server:~# postfix reload

Check it's working by telnetting into Postfix

root@server:~# telnet localhost 25
Trying 127.0.0.1...
Connected to localhost.
Escape character is '^]'.
220 mail.domain.tld ESMTP Postfix (Ubuntu)
ehlo localhost

and check that the line

250-STARTTLS

appears in the output. Use quit to end the session.

Clients should connect on port 25, using TLS security.

Allowing off-site users with SASL

As it stands, the server will only forward mail sent from the LAN. That's fine, but if you want to send mail from other devices via this SMTP server, you need to put some authentication on it. SASL is the general term for authentication on mail servers.

Assuming you have Dovecot set up with some users (up to the 'Testing Dovecot' stage, you can use Dovecot's authentication engine for authenticating users of the SMTP server.

First, extend the Dovecot configuration file /etc/dovecot/conf.d/10-master.conf with the unix_listener section below so that it accepts authentication requests from Postfix.

service auth {
  unix_listener auth-userdb {
    mode = 0660
    user = vmail
    group = vmail
  }

  # Postfix smtp-auth                                                                                                 
  unix_listener /var/spool/postfix/private/auth {
    group = postfix
    user = postfix
    mode = 0666
  }

  # Auth process is run as this user.                                                                                 
  #user = $default_internal_user                                                                                      
}

service auth-worker {
  # Auth worker process is run as root by default, so that it can access                                              
  # /etc/shadow. If this isn't necessary, the user should be changed to                                               
  # $default_internal_user.                                                                                           
  #user = root                                                                                                        
  user = $default_internal_user
}

Next, make Postfix authenticate users. Modify /etc/postfix/main.cf to include the lines

smtpd_sasl_auth_enable = yes
smtpd_sasl_type = dovecot
smtpd_sasl_path = private/auth
smtpd_recipient_restrictions = permit_sasl_authenticated permit_mynetworks reject_unauth_destination

smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache

Restart both Dovecot and Postfix:

root@server:~# service dovecot reload
root@server:~# postfix reload

Mail clients can now post messages through the SMTP server, so long as they give a valid username/password combination that's recognised by Dovecot.

Amavis + ClamAV + SpamAssassin

Both ClamAV and SpamAssassin need to be introduced into Postfix's message-handlng queue to do their things with messages. The idea is that Postfix will take the message out of it's queue and hand it over the filter program (listening on the loopback interface), who will return it the same way after doing its thing. The trouble is, Postfix can only have one such filter, so Amavis is needed to act as a wrapper around them both. Here is how to do it. For instructions, I basically followed the ones on the Ubuntu wiki.

  • Create the file /etc/mailname containing
mydomain.com
  • Install the packages:
root@server:~# aptitude install amavisd-new spamassassin clamav-daemon pyzor razor libnet-dns-perl libmail-spf-perl
root@server:~# aptitude install arj bzip2 cabextract cpio file gzip lhasa nomarch pax rar unrar unzip zip zoo
root@server:~# aptitude install lrzip liblz4-tool rpm2cpio unrar-free ripole p7zip-full p7zip-rar lzop

ClamAV

  • Add the ClamAV user to the Amavis group, and vice versa
root@server:~# adduser clamav amavis
root@server:~# adduser amavis clamav
  • Change /etc/clamav/clamd.conf to contain
# AllowSupplementaryGroups false
AllowSupplementaryGroups true

SpamAssassin

  • Edit /etc/default/spamassassin to activate the Spamassassin daemon. Change ENABLED=0 to:
ENABLED=1
and, to allow automatic rule updates, change CRON=1
CRON=1
  • Tell SystemD to keep Spamassassin running. Add these lines to the [Service] section of /lib/systemd/system/spamassassin.service:
Restart=on-failure
RestartSec=3

Then start Spamassassin:

root@server:~# systemctl restart spamassassin.service

Amavis

  • Edit /etc/amavis/conf.d/15-content_filter_mode to enable spam filtering
@bypass_virus_checks_maps = (
   \%bypass_virus_checks, \@bypass_virus_checks_acl, \$bypass_virus_checks_re);

@bypass_spam_checks_maps = (
   \%bypass_spam_checks, \@bypass_spam_checks_acl, \$bypass_spam_checks_re);
  • Edit both /etc/amavis/conf.d/50-user to determine the fate of spam and virus-laden mails
$final_virus_destiny      = D_PASS;
$final_banned_destiny     = D_PASS;
$final_spam_destiny       = D_PASS;
$final_bad_header_destiny = D_PASS;
(in other words, don't discard any mail for the moment)
  • Restart Amavis
root@server:~# systemctl restart amavis.service
  • Once Amavis is working and correctly identifying spam and malware, you can adjust the settings in /etc/amavis/conf.d/50-user to classify more spam, and reject the most spammy. (Rejected messages are quarantined, with a notification going to postmaster@domain.tld.)
$enable_dkim_verification = 1; # was disabled to prevent warning
$sa_tag_level_deflt  = 0.0;  # add spam info headers if at, or above that level
$sa_tag2_level_deflt = 1.5; # add 'spam detected' headers at that level
$sa_kill_level_deflt = 2.5; # triggers spam evasive actions
$sa_dsn_cutoff_level = 6.0;   # spam level beyond which a DSN is not sent, was 10

$final_bad_header_destiny = D_PASS;     # False-positive prone (for spam)
$final_virus_destiny      = D_DISCARD;  # (data not lost, see virus quarantine)
$final_banned_destiny     = D_REJECT;   # D_REJECT when front-end MTA
$final_spam_destiny       = D_DISCARD;

# Where quarantine notifications go
$virus_admin = "postmaster\@$mydomain"; # due to D_DISCARD default
$spam_admin  = "postmaster\@$mydomain"; # due to D_DISCARD default

# Where quarantine notifications appear to come from
$mailfrom_notify_admin     = "contentfilter\@$mydomain";
$mailfrom_notify_recip     = $mailfrom_notify_admin;
$mailfrom_notify_spamadmin = $mailfrom_notify_admin;

$hdrfrom_notify_sender = $mailfrom_notify_admin;
$hdrfrom_notify_admin = $mailfrom_notify_admin;

Spam will be quarantined and notifications sent to postmaster@domain.tld. The notification will contain lines like:

Content type: Spam
Internal reference code for the message is 26829-01/ZKBMjq6S2Dsi

...
The message has been quarantined as: Z/spam-ZKBMjq6S2Dsi.gz

The message WAS NOT relayed to:
<someone@domain.tld>:
  250 2.7.0 Ok, discarded, id=26829-01 - spam

You can release the quarantined message with amavisd-release, like so:

root@server:~# amavisd-release Z/spam-ZKBMjq6S2Dsi.gz

or send it message to another user by giving their address:

root@server:~# amavisd-release Z/spam-ZKBMjq6S2Dsi.gz admin@domain.tld

Delete old spam by creating the file /etc/cron.daily/delete_old_spam containing

#!/bin/sh

find /var/lib/amavis/virusmails/ -type f -mtime +31 -delete

and make it executable

root@server:~# chmod u+x /etc/cron.daily/delete_old_spam

Postfix

  • Now, include Amavis in Postfix's message queue. Modify /etc/postfix/main.cf to use Amavis
# Spam and virus filtering
content_filter=smtp-amavis:[127.0.0.1]:10024
  • Modify /etc/postfix/master.cf to handle the return from Amavis
# 
# Amavis spam and virus filtering
#

# Sending to Amavis
# smtp-amavis unix        -       -       -       -       2       smtp
smtp-amavis unix        -       -       n       -       2       smtp
  -o smtp_data_done_timeout=1200
  -o smtp_send_xforward_command=yes
  -o disable_dns_lookups=yes
  -o max_use=20

# Returning from Amavis
# 127.0.0.1:10025 inet    n       -       -       -       -       smtpd
127.0.0.1:10025 inet    n       -       n       -       -       smtpd
  -o content_filter=
  -o local_recipient_maps=
  -o relay_recipient_maps=
  -o smtpd_restriction_classes=
  -o smtpd_delay_reject=no
  -o smtpd_client_restrictions=permit_mynetworks,reject
  -o smtpd_helo_restrictions=
  -o smtpd_sender_restrictions=
  -o smtpd_recipient_restrictions=permit_mynetworks,reject
  -o smtpd_data_restrictions=reject_unauth_pipelining
  -o smtpd_end_of_data_restrictions=
  -o mynetworks=127.0.0.0/8
  -o smtpd_error_sleep_time=0
  -o smtpd_soft_error_limit=1001
  -o smtpd_hard_error_limit=1000
  -o smtpd_client_connection_count_limit=0
  -o smtpd_client_connection_rate_limit=0
  -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters,no_address_mappings
  • Add the two -o … lines to master.cf, to prevent Spamassassin's reports being checked for being spam.
pickup    unix  n       -       y       60      1       pickup
        -o content_filter=
        -o receive_override_options=no_header_body_checks
  • Reload the Postfix configuration:
root@server:~# postfix reload

Enable Dovecot LDA

We now want Postfix to use the Dovecot LDA for local mail delivery and filtering.

  • Modify /etc/postfix/master.cf to include the line
#
# dovecot as a local delivery agent 
#
dovecot   unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail:vmail argv=/usr/lib/dovecot/dovecot-lda -f ${sender} -a ${recipient} -d ${user}@${nexthop}
  • Modify /etc/postfix/main.cf to include the line
## Local Delivery

dovecot_destination_recipient_limit = 1
mailbox_transport = dovecot
virtual_transport = dovecot
  • Reload the Postfix configuration:
root@server:~# postfix reload

Block addresses that are known to spammers

The recipient_delimiter option on Postfix is rather neat. Given a recipient_delimiter of '.', messages to user.ext1@domain1.com are handled in an interesting way. If user.ext1@domain1.com is a valid user, the message is put in their mailbox. If user.ext1@domain1.com isn't a valid user, Postfix will drop everything in the username after the delimiter and retry the delivery to user@domain1.com. This means that you can create specialised-use email addresses and have them all come to the same mailbox. It also means you can create 'disposable' email addresses. That also requires some method of disposing of those addresses when the time comes.

The disposal is done by checking both the headers of incoming emails the RCPT TO addresses, and discarding or rejecting those that are to a disposable address.

  • Add the following to /etc/postfix/main.cf
# block addresses that are known to spammers
header_checks = regexp:/etc/postfix/header_checks

smtpd_recipient_restrictions =
                            check_recipient_access hash:/etc/postfix/recipient_checks # Add this directive
                            permit_sasl_authenticated
                            permit_mynetworks
                            reject_unauth_destination
  • Create the file /etc/postfix/header_checks
if /^To:/
/^To:.*(user1\.ext1@domain1\.com)/
  reject Recipient address rejected: User unknown in virtual mailbox table
/^To:.*(user1\.ext2@domain1\.com)/
  reject Recipient address rejected: User unknown in virtual mailbox table
endif
  • Create the file /etc/postfix/recipient_checks
# Reject messages with these recipient addresses
user.banned2@example.com REJECT
user.banned@example.com REJECT
user.banned3@example.com REJECT
user.banned4@example.com REJECT
person.banned@example.com REJECT
  • Reload the configuration
root@server:~# postmap /etc/postfix/recipient_checks
root@server:~# postfix reload

Configure Postfix as a backup MX server for other domains

Having backup mail servers is always good, so it's only fair to offer the same service to others in exchange. This is how you do it.

  • In /etc/postfix/main.cf, modify smtpd_recipient_restrictions and add the relay_domains and smtpd_relay_restrictions
smtpd_recipient_restrictions =
                            check_recipient_access hash:/etc/postfix/recipient_checks

# $mydomain included as a virtual domain below                                                                                                                                                                                                             
relay_domains = otherdomain1.com otherdomain2.com

# Include the relay_recipients to only relay certain addresses at the relayed domains
# relay_recipient_maps = hash:/etc/postfix/relay_recipients                                                                                                                                                                                                
smtpd_relay_restrictions =  permit_sasl_authenticated
                            permit_mynetworks
                            reject_unauth_destination
  • Reload the configuration
root@server:~# postfix reload

However, there is a problem with queue lifetimes. With this setup, messages held in the backup role will only be stored on this server for the standard three days. If you're acting as backup for a privately-run mail server, this may not be long enough (the other site's owner could be away on holiday, for example, and may not get back within three days). Simply increasing the queue lifetime means that undeliverable messages to third parties are not reported back for a long time, which isn't that great.

The way to fix is, apparently, is to have two Postfix instances. One instance handles the normal traffic, with a short queue time, while the other handles traffic for the backed-up mail host; the latter Postfix instance has a long queue time and so can keep the mail for a while. I've not attempted to do this yet, as the above settings work OK for me.

Open submission port (587)

Some ISPs prevent access to port 25, the normal mail transfer port. To allow clients on these networks to still connext to this server, we open port 587 for SMTP mail submission.

Modify /etc/postfix/master.cf to include:

submission inet n       -       -       -       -       smtpd
  -o syslog_name=postfix/submission
  -o smtpd_tls_security_level=encrypt
  -o smtpd_sasl_auth_enable=yes
  # -o smtpd_client_restrictions=permit_sasl_authenticated,permit_mx_backup,reject
  -o milter_macro_daemon_name=ORIGINATING

You may also need to modify /etc/postfix/main.cf to include

data_directory = /var/lib/postfix
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
tls_random_exchange_name = ${data_directory}/prng_exch

And don't forget to open the port in the firewall and on the router.

Authenication and verification

There are a number of ways to authenticate your email servers with others. This should prevent spam messages.

SPF

This is a mechanism for the receiving MTA to check that messages it receives came from an MTA that was authorised to send them. It uses DNS records to list the allowed domains. You simply set a DNS record to allow other MTAs to check mail that purports to come from you. To check mail that comes from other domains to your own MTA, you need to install an SPF checking tool and integrate it into Postfix.

  • Add the following as a TXT record to the DNS entry for each domain you send mail.
v=spf1 mx a mx:backup.com ~all
This tells other mail servers that mail servers listed the MX portion of your DNS entry can send email for your domain. It also tells those other servers to accept email sent from your IP number and servers listed in the MX records of backup.com (where backup.com is a domain that acts as a backup MX server for your domain).
  • Install the SPF checking tool in Postfix
root@server:~# aptitude install postfix-policyd-spf-python
  • Add the SPF checker to /etc/postfix/master.cf
policy-spf  unix  -       n       n       -       -       spawn
    user=nobody argv=/usr/bin/policyd-spf
  • Add the SPF timeout line, and modify smtpd_relay_restrictions in /etc/postfix/main.cf
smtpd_relay_restrictions =  permit_sasl_authenticated
                            permit_mynetworks
                            reject_unauth_destination
                            check_policy_service unix:private/policy-spf

policy-spf_time_limit = 3600s
  • Modify /etc/postfix-policyd-spf-python/policyd-spf.conf to whitelist any mail servers that act as secondary for this domain. Add this line:
Domain_Whitelist = mybackup.domain.com
  • Test the setup by sending a message from a domain that uses SPF to the server you control. The mail log for your MTA should contain a line like
Nov 20 10:26:05 server policyd-spf[18300]: Pass; identity=helo; client-ip=157.56.112.83; helo=blah.senderdomain.com; envelope-from=user@senderdomain.com; receiver=user@domain.com 
and the headers of the message should contain a line like
Received-SPF: Pass (sender SPF authorized) identity=helo; client-ip=x.x.x.x; helo=blah.senderdomain.com; envelope-from=user@senderdomain.com; receiver=user@domain.com
  • Test the setup by sending a message to a domain that uses SPF from your own server. The headers of that message should contain a line like
Received-SPF: Pass 
followed by a bunch of stuff that shows how the receiving MTA checks SPF records.

(Taken from Ubuntu community docs for Postfix/SPF).

DKIM

With DKIM, the originating MTA takes a hash of some message headers and the message body, signs it with the server's private key, and embeds the signed hash as an additional header in the message. The public key is published as a DNS TXT record. The receiving MTA recalculates the hash and compares it to the signed version in the message. If they match, the message passes the DKIM authentication.

In this example, I have several domains that can send mail. Each has a separate DKIM key. The details are stored in the /etc/opendkim/key_table and /etc/opendkim/signing_table files, rather than in the /etc/opendkim.conf file (the latter is fine if you only have one domain).

  • Install the packages
root@server:~# aptitude install opendkim opendkim-tools
  • Edit /etc/opendkim.conf
# Log to syslog
Syslog                  yes
# Required to use local socket with MTAs that access the socket as a non-
# privileged user (e.g. Postfix)
UMask                   002

Socket          inet:8891@localhost
PiDFile         /var/run/opendkim/opendkim.pid

# Sign for example.com with key in /etc/mail/dkim.key using
# selector '2007' (e.g. 2007._domainkey.example.com)
#Domain                  example.com
#KeyFile                 /etc/mail/dkim.key
#Selector                mail
KeyTable                /etc/opendkim/key_table
SigningTable            /etc/opendkim/signing_table
ExternalIgnoreList      /etc/opendkim/trusted_hosts
InternalHosts           /etc/opendkim/trusted_hosts

# Commonly-used options; the commented-out versions show the defaults.
#Canonicalization       simple
#Mode                   sv
#SubDomains             no
#ADSPAction            continue

AutoRestart             yes
AutoRestartRate         10/1h
Background              yes
UserID                  opendkim:opendkim

Canonicalization        relaxed/relaxed
DNSTimeout              5
Mode                    sv
SignatureAlgorithm      rsa-sha256
SubDomains              no
#UseASPDiscard          no
#Version                rfc4871
X-Header                no

  • Modify /etc/default/opendkim to include only these non-comment lines.
RUNDIR=/var/run/opendkim
USER=opendkim           
GROUP=opendkim
PIDFILE=$RUNDIR/$NAME.pid
EXTRAAFTER=
  • Create the directories for the keys (one for each domain)
$ root@server:~# mkdir /etc/opendkim
$ root@server:~# mkdir /etc/opendkim/example.com
$ root@server:~# mkdir /etc/opendkim/example2.com 
  • Create /etc/opendkim/trusted_hosts to show which hosts don't need DKIM authentication. In this case, it's just the hosts my LAN.
# local host
127.0.0.1
# local subnets that are trusted and do not need to be verified
192.168.1.0/24
aaaa:bbbb:cccc:dddd/64
  • Create the keys for each domain
cd /etc/opendkim/example.com
opendkim-genkey -s mail -d example.com
chown opendkim:opendkim mail.private
  • Create /etc/opendkim/signing_table
example.com      mail._domainkey.example.com
example1.com     mail._domainkey.example1.com
example2.com     mail._domainkey.example2.com
  • Place the keys in your domains' DNS records. Create a new subdomain mail._domainkey for each domain (e.g. mail._domainkey.example.com). Create a new TXT record for that new subdomain that contains the core of the relevant part of /etc/opendkim/example.com/mail.txt. If the file contains
mail._domainkey IN      TXT     ( "v=DKIM1; k=rsa; "  "p=MIGfMA0GCSqGSIb3DQEBAQU...blah...blah...QIDAQAB" )  ; ----- DKIM key mail for example.com

the DNS TXT record for mail._domainkey.example.com should contain

v=DKIM1; k=rsa; p=MIGfMA0GCSqGSIb3DQEBAQU...blah...blah...QIDAQAB
  • Check the entry is in the domain
user@desktop:~$ host -t TXT mail._domainkey.example.com ns1.isp.net
Using domain server:
Name: ns1.isp.net
Address: 100.100.100.1006#53
Aliases: 

mail._domainkey.example.com descriptive text "v=DKIM1\; k=rsa\; p=MIGfMA0GCSqGSIb3DQEB...blah...blah...uY/wIDAQAB"
  • If needed, add the entry to /etc/opendkim/key_table:
mail._domainkey.domain.tld domain.tld:mail:/etc/opendkim/domain.tld/mail.private
  • Modify /etc/postfix/main.cf to include
# DKIM Milter
milter_protocol = 6
milter_default_action = accept
smtpd_milters = inet:localhost:8891
non_smtpd_milters = inet:localhost:8891
(If you already have milters operating, add the DKIM milter settings to the existing ones
milter_default_action = accept
milter_protocol = 2
smtpd_milters = inet:localhost:8891,inet:localhost:8892
non_smtpd_milters = inet:localhost:8891,inet:localhost:8892
  • Modify /etc/postfix/master.cf to prevent messages being signed when going both to and from Amavis. In the section for Amavis, extend the receive_override_options with no_milters:
# Returning from Amavis
127.0.0.1:10025 inet    n       -       -       -       -       smtpd
 -o content_filter=
...
 -o receive_override_options=no_header_body_checks,no_unknown_recipient_checks,no_milters
  • Restart everything, checking /var/log/mail.log for errors
root@server:~# systemctl restart opendkim
root@server:~# postfix reload
  • Test it all works by sending some messages to and from domains with working DKIM services. Outgoing messages should contain a header like
DKIM-Signature: v=1; a=rsa-sha256; c=relaxed/relaxed; d=example.com; s=mail;
       t=1448108655; bh=Hciu/taJLWKuURd9G8XyD6VK2YpDwXAHXAfUrZpPfRo=;
       h=Date:Subject:From:To:From;
       b=IFbjcnrZcD0SQsL9N2zGaOjhR/j2opmjAmxBgp8+3QejtqPgbNqFjdUOeXkQlh0xo
        M1nX2VK73v+2D7Og41jZuXQgb8jYn2yUeKLkvJDWNnEOdpmXvRpspuhu2+uqwEk8cj
        uqkY5jTXUBmn9FKVRRn0Xg8Nzl0jG7HY9pYNPXYY=
If it works, the receiving MTA should also include a header like
Authentication-Results: spf=pass (sender IP is 100.100.100.100)
 smtp.mailfrom=example.com; desitination.com; dkim=pass (signature was verified)
 header.d=example.com;destination.com; dmarc=bestguesspass action=none
 header.from=example.com;
Check that the DKIM process works in both directions and for each domain you have.
  • Once you're confident that DKIM is working, tell the world to reject messages that don't have the DKIM keys. Create a new domain adsp._domainkey.example.com and give it a DNS TXT record containing
dkim=all

Most of these instructions were taken from How to eliminate spam and protect your name with DMARC, with some from Postfix/DKIM Ubuntu community documentation.

DMARC

See also this page on fixing OpenDmarc configuration.


DMARC is a method for MTAs to publish a policy for how their messages should be processed, and allows them to report how well they're handling each others' mail according to those policies.

DMARC policy for outgoing mail

You create a DNS TXT record that tells other MTAs about how you want your outgoing messages to be processed. You set up a DMARC milter on your MTA to process incoming messages according to the published DMARC policy. The DMARC milter also logs how you're handling other MTAs' messages and can send reports to them as requested..

  • Create a subdomain _dmarc.example.com and a TXT record specifying the DMARC reports you want.
v=DMARC1; p=none; rua=mailto:dmarc@example.com; fo=1; adkim=r; aspf=r
(The KTS DMARC Record Assistant will create the records for you. Remove the fo=1; for fewer reports once you're confident things are working.)
  • Create the dmarc user by modifying /etc/postfix/valiases to include lines
dmarc@example.com               user@example1.com
dmarc@example1.com              user@example1.com
dmarc@example1.com              user@example1.com
  • Refresh the Postfix config.
root@server:~# postmap /etc/postfix/valiases
root@server:~# postfix reload

You should get messages arriving from other MTAs to say how well you've been doing.

Testing and logging incoming mail

Next is to log the details from incoming messages and tell those MTAs how well they've been doing.

  • Install OpenDMARC
root@server:~# aptitude install opendmarc libauthen-sasl-perl python3-psycopg2
(I need the libauthen package to allow the Opendmarc scripts to connect to my mailserver via SASL, and the psycopg2 package to allow the importer to connect to Postgres.)
  • Update /etc/opendmarc.conf
AuthservID mail.domain1.com
PidFile /var/run/opendmarc/opendmarc.pid # Change this line!
Socket inet:8893@localhost
RejectFailures false
Syslog true
TrustedAuthservIDs mail.domain1.com,mail.domain2.com,mail2.example.com
UMask 0002
UserID opendmarc:opendmarc
IgnoreHosts /etc/opendmarc/ignore.hosts
HistoryFile /var/run/opendmarc/opendmarc.dat
#for testing:
SoftwareHeader true
where mail.domain1.com,mail.domain2.com,mail2.example.com are the names of mail servers you trust, including your own. I included the names of the other domains this MTA serves.
  • Create a directory for the OpenDMARC Pid file:
root@server:~# mkdir /var/run/opendmarc
root@server:~# chown -R opendmarc:opendmarc /var/run/opendmarc/
  • Create the directory /etc/opendmarc/ and the file /etc/opendmarc/ignore.hosts, containing
localhost
192.168.1.0/24
aaaa:bbbb:cccc:dddd::/64
  • Update /etc/default/opendmarc to include the line
SOCKET="inet:8893@localhost"
  • Exit /etc/postfix/main.cf to include the extra milter. Add the port specification to these lines:
smtpd_milters = inet:localhost:8891,inet:localhost:8893
non_smtpd_milters = inet:localhost:8891,inet:localhost:8893
  • Start OpenDMARC and refresh Postfix
root@server:~# systemctl restart opendmarc.service
root@server:~# postfix reload

Send a mail to yourself from a service that uses DMARC, such as GMail. Messages you receive should include headers like:

DMARC-Filter: OpenDMARC Filter v1.2.0 mail.domain.org 821E7A4
Authentication-Results: mail.domain.org; dmarc=pass header.from=gmail.com
The first line is the flag from the SoftwareHeader line, and shows that OpenDMARC handled the message. The second line is the DMARC authentication result. If it's all working, you can remove the SoftwareHeader line from /etc/opendmarc.conf and restart OpenDMARC.

Reporting on incoming mail

See Recording Dmarc feedback.

Important note on versions

The instructions below won't generate any messages with Ubuntu 14.04 LTS. The supplied version of OpenDMARC in that version of Ubuntu (1.2.0) has a bug where it fails to record the DMARC rua address, which specifies where the DMARC reports should go. This is fixed in OpenDMARC 1.3.1, which is bundled in Ubuntu 15.04 and later.

The final part is to log the results of DMARC testing incoming mail, and sending the results back to the originating MTAs.

  • Edit /usr/share/doc/opendmarc/schema.mysql. Update the default timestamp for lastsent on about line 31 to be
CREATE TABLE IF NOT EXISTS requests (
       …
       lastsent TIMESTAMP NOT NULL DEFAULT '1970-01-01 01:01:01',
and uncomment the lines
CREATE USER 'opendmarc'@'localhost' IDENTIFIED BY 'changeme';
GRANT ALL ON opendmarc.* to 'opendmarc'@'localhost';
create a stong password to replace 'changeme'.
  • Run the script to create the MySQL database to store the reports.
root@server:~# mysql -u root -p < /usr/share/doc/opendmarc/schema.sql
  • Fix the missing database backend selectors in the OpenDMARC scripts. In /usr/sbin/opendmarc-import , /usr/sbin/opendmarc-params , and /usr/sbin/opendmarc-reports , change the DBD lines to use "mysql" not "yes" (you may have to hunt, and it seems already fixed in /usr/sbin/opendmarc-expire),
# require DBD::yes;
require DBD::mysql;

# my $dbscheme     = "yes";
my $dbscheme     = "mysql";
  • Create /etc/cron.daily/opendmarc-report to generate the reports once a day.
#!/bin/bash
 
DB_SERVER='localhost'
DB_USER='opendmarc'
DB_PASS='changeme'
DB_NAME='opendmarc'
WORK_DIR='/var/run/opendmarc'
REPORT_EMAIL='dmarc@domain.com'
REPORT_ORG='domain.com'

mv ${WORK_DIR}/opendmarc.dat ${WORK_DIR}/opendmarc_import.dat -f
touch ${WORK_DIR}/opendmarc.dat
chown opendmarc:opendmarc ${WORK_DIR}/opendmarc.dat

/usr/sbin/opendmarc-import --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose < ${WORK_DIR}/opendmarc_import.dat
/usr/sbin/opendmarc-reports --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose \
    --interval=86400 --report-email=${REPORT_EMAIL} --report-org=${REPORT_ORG}
/usr/sbin/opendmarc-expire --dbhost=${DB_SERVER} --dbuser=${DB_USER} --dbpasswd=${DB_PASS} --dbname=${DB_NAME} --verbose
where you use the DB_PASS password you generated earlier.
  • Make the cron script executable:
root@server:~# chmod +x /etc/cron.daily/opendmarc-report
  • Ensure that, for testing at least, you get a copy of every outgoing DMARC report. Modify /etc/postfix/main.cf to include
sender_bcc_maps = hash:/etc/postfix/sender_bcc
then create /etc/postfix/sender_bcc to contain the one line
/dmarc@domain1.com/ user@domain1.com
and refresh the Postfix configuration
root@server:~# postmap /etc/postfix/sender_bcc
root@server:~# postfix reload

Most of these instructions were taken from How to eliminate spam and protect your name with DMARC, with some from Christian Laußat's Linux Blog.

DANE

To add: see documents at http://www.postfix.org/TLS_README.html#client_tls_dane . Will require DNSSEC enabled on the sending domain.

Spamhaus block lists

Spamhaus maintains lists of known spammer address and allows people to use them to block spam. They provide a plugin for SpamAssassin that handles the lookups.

root@server:~/spamassassin-dqs/3.4.1+# sed -i -e 's/your_DQS_key/aip7yig6sahg6ehsohn5shco3z/g' sh.cf
root@server:~/spamassassin-dqs/3.4.1+# sed -i -e 's/your_DQS_key/aip7yig6sahg6ehsohn5shco3z/g' sh_hbl.cf
  • Modify sh.pre with the location of SpamAssassin
loadplugin       Mail::SpamAssassin::Plugin::SH /etc/mail/spamassassin/SH.pm
  • Copy the plugin into the SpamAssassin directory
root@server:~/spamassassin-dqs/3.4.1+# cp SH.pm /etc/mail/spamassassin
root@server:~/spamassassin-dqs/3.4.1+# cp sh.cf /etc/mail/spamassassin
root@server:~/spamassassin-dqs/3.4.1+# cp sh_scores.cf /etc/mail/spamassassin
root@server:~/spamassassin-dqs/3.4.1+# cp sh.pre /etc/mail/spamassassin
  • Restart SpamAssassin, Amavis, and Postfix
root@server:~# systemctl restart spamassassin
root@server:~# systemctl restart amavis
root@server:~# systemctl restart postfix

See also

Here are a few pages that are useful guides or provide background and context.