Adding a Mail Server to New VPS

With database replication working, the next thing I need is a mail server.

As with my move to bringing my e-mail in-house, configuration and testing of my mail server on vps3 before changing my DNS settings is going to be important—neglecting to thoroughly test may result in mail bouncing as undeliverable.

Fortunately the version of Ubuntu installed on vps3 is identical to that installed on vps2, so other than kernel differences (vps2 is a xen VPS so uses the same kernel as the host node) the configuration should be nearly identical.

Because of that, this article is going to borrow heavily from when I attempted to upgrade vps2 from Ubuntu Precise Pangolin to Trusty Tahr (starting from the "Postfix Installation" header.

Postfix Installation

sudo apt-get install postfix postfix-mysql postfix-policyd-spf-python opendkim opendmarc

For Postfix Configuration, I have chosen Internet Site as it is the default. The System mail name is likewise the default, vps3.thejc.me.uk.

After installation, Postfix has automatically started running.

sudo nano /etc/postfix/main.cf
myhostname = mail4.thejc.me.uk
mydestination = mail4.thejc.me.uk, mail3.thejc.me.uk, mail2.thejc.me.uk, vps3.thejc.me.uk, vps2.thejc.me.uk, localhost
relayhost = 
mynetworks = 127.0.0.1/8 [::ffff:127.0.0.1]/104 [::1]/128
inet_interfaces = all
relay_domains = proxy:mysql:/etc/postfix/mysql/relay_domains.conf
relay_recipient_maps = proxy:mysql:/etc/postfix/mysql/relay_recipient_maps.conf
relay_transport = smtp:[fdd7:5938:e2e6:1::25:1]
sudo mkdir /etc/postfix/mysql/
sudo nano /etc/postfix/mysql/relay_domains.conf
user = mail
password = mail
hosts = 127.0.0.1
dbname = mail
table = domain
select_field = domain
where_field = domain
additional_conditions = and active = '1'
#query = select domain from domain where domain='%s' and active='1'
sudo nano /etc/postfix/mysql/relay_recipient_maps.conf
user = mail
password = mail
hosts = 127.0.0.1
dbname = mail
table = alias
select_field = address
where_field = address
additional_conditions = and active = '1'
#query = select address from alias where address='%s' and active='1'
cd /etc/postfix/mysql/
sudo postmap alias_domains.conf
sudo postmap alias_recipient_maps.conf
sudo postconf
sudo service postfix restart

At this point, testing in telnet shows that valid addresses are accepted and invalid addresses result in a 550 error. There is still some work to do, but this is an ideal result - if 550 errors are returned for valid e-mail addresses mail would end up bouncing, whereas a 451 error for another configuration issue will result in mail being deferred.

sudo ip -6 addr add 2a03:ca80:8001:769d::25:4 dev eth0
sudo nano /etc/network/interfaces
…
iface eth0 inet6 static
…
	up /sbin/ip -6 addr add 2a03:ca80:8001:769d::25:4 dev eth0
sudo mkdir /etc/ssl/mail/
cd /etc/ssl/mail
sudo mkdir 201505
cd 201505
sudo su
openssl genrsa -out mail4_thejc_me_uk.key 4096
openssl req -new -key mail4_thejc_me_uk.key -out mail4_thejc_me_uk.csr -sha256

Login to StartSSL.com and make a request for a new certificate using the certificate signing request (CSR) from the last step.

While waiting, verify that both the IPv4 and IPv6 IP addresses for mail4.thejc.me.uk have PTR records pointing back to mail4.thejc.me.uk. Add the IPv6 IP address as a AAAA record for mail4.thejc.me.uk.

Once the certificate is ready, copy and paste it into /etc/ssl/mail/201505/mail4_thejc_me_uk.crt.

sudo nano /etc/postfix/main.cf
smtpd_banner = $myhostname ESMTP $mail_name (Ubuntu)
biff = no

readme_directory = no

smtpd_relay_restrictions = permit_my_networks permit_sasl_authenticated defer_unauth_destination
myhostname = mail4.thejc.me.uk
myorigin = /etc/mailname
smtp_bind_address = 149.255.108.141
smtp_bind_address6 = 2a03:ca80:8001:769d::25:4

mydestination = mail4.thejc.me.uk, mail3.thejc.me.uk, mail2.thejc.me.uk, vps3.thejc.me.uk, vps2.thejc.me.uk, localhost
alias_maps = hash:/etc/aliases
alias_database = hash:/etc/aliases

relayhost =

mynetworks = [2a03:ca80:8001:769d::]/64 127.0.0.0/8 [::ffff:127.0.0.0]/104 [::1]/128
mailbox_size_limit = 0
recipient_delimiter = +

inet_protocols = all
inet_interfaces = 149.255.108.141,[2a03:ca80:8001:769d::25:4]

relay_domains = mysql:/etc/postfix/mysql/relay_domains.cf
relay_recipient_maps = mysql:/etc/postfix/mysql/relay_recipient_maps.cf
relay_transport = smtp:[fdd7:5938:e2e6:1::25:1]
maximal_queue_lifetime = 100d
#// the maximum permitted is 100d

# SSL - Server
smtpd_tls_cert_file = /etc/ssl/mail/mail4-startssl-cert.pem
smtpd_tls_key_file = /etc/ssl/mail/mail4-startssl-key.pem
smtpd_tls_CAfile = /etc/ssl/StartCom/ca-sha2.pem
smtpd_tls_session_cache_database = btree:${data_directory}/smtpd_scache
smtpd_tls_security_level = may
smtpd_tls_received_header = yes
smtpd_tls_loglevel = 1

tls_random_source = dev:/dev/urandom

# SSL - Client

smtp_use_tls = yes
smtp_tls_security_level = may
smtp_tls_loglevel = 1
smtp_tls_CAfile = /etc/ssl/certs/ca-certificates.crt
smtp_tls_session_cache_database = btree:${data_directory}/smtp_scache

# Server - Restrictions

smtpd_helo_required = yes
smtpd_helo_restrictions = permit_mynetworks, permit
# TODO: helo check

smtpd_recipient_restrictions = reject_unknown_sender_domain, reject_unknown_recipient_domain, reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination

smtpd_sender_restrictions = reject_unknown_sender_domain

smtpd_client_restrictions = reject_rbl_client zen.spamhaus.org=127.0.0.[2..8]

This is enough to get my VPS working as an incoming mail relay (store and forward). This is not the most secure (or spam-resistant) configuration.

The Milters

SPF, DKIM, and DMARC checks are something that I have decided to add 24 hours later, after looking at RAM usage.

SPF Checks

I am going to use the same configuration I previously used on my VPS.

sudo nano /etc/postfix-policyd-spf-python/policyd-spf.conf
debugLevel = 1
defaultSeedOnly = 1

HELO_reject = SPF_Not_Pass
Mail_From_reject = Fail

PermError_reject = True
TempError_Defer = False

skip_addresses = 149.255.108.141,2a03:ca80:8001:769d::/64,2001:470:1f09:38d::/64,127.0.0.0/8,::ffff:127.0.0.0/104,::1/128

Domain_Whitelist = microsoft.com
sudo nano /etc/postfix/main.cf
smtpd_recipient_restrictions = reject_unknown_sender_domain, reject_unknown_recipient_domain, reject_unauth_pipelining, permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, check_policy_service unix:private/policy-spf

# SPF
policy-spf_time_limit = 3600s
sudo nano /etc/postfix/master.cf
policy-spf unix	-	n	n	-	-	 spawn
  user=nobody argv=/usr/bin/policyd-spf
postconf
sudo service postfix reload

DomainKeys Identified Mail (DKIM)

sudo nano /etc/opendkim.conf
AuthservID mail3.thejc.me.uk

Syslog			yes

UMask			002

ADSPAction		reject

OversignHeaders		From

AlwaysAddARHeader	1
AuthservIDWithJobID	1
BaseDirectory		/var/opendkim/
KeyTable		/var/opendkim/key-table.tbl
SigningTable		refile:/var/opendkim/signing-table.tbl
# InsecureKey = UnprotectedKey (DNSSEC related)
UnprotectedKey		none
# InsecurePolicy = UnprotectedPolicy (DNSSEC related)
UnprotectedPolicy	apply
LogWhy			1
Mode			sv
RemoveOldSignatures	0
Socket			inet6:8891@[fdd7:5938:e2e6:6c8d:7f00:1:c:8891]
SubDomains		0
cd ~/.ssh/
ssh-keygen -t rsa

Save the keypair as /home/thejc/.ssh/thejc.vps3, copy the content of /home/thejc/.ssh/thejc.vps3.pub to /home/thejc/.ssh/authorized_keys on vps2 and home.

sudo mkdir /var/opendkim/
sudo su
rsync -avrplogP --progress -e 'ssh -p 8043 -i /home/thejc/.ssh/thejc.vps3' thejc@home.thejc.me.uk:~/vps-backup-2015-01-24/var/opendkim/ /var/opendkim/
cd /var/opendkim/
chown -R opendkim:opendkim .
exit
sudo service opendkim restart
sudo nano /etc/postfix/main.cf
# DKIM
milter_default_action = accept
milter_protocol = 6
smtpd_milters = inet:[fdd7:5938:e2e6:6c8d:7f00:1:c:8891]:8891
non_smtpd_milters = inet:[fdd7:5938:e2e6:6c8d:7f00:1:c:8891]:8891
sudo nano /etc/init/ipv6-lo.conf
# ipv6-lo
#

description		"Set-up IPv6 loopback IPs

start on (filesystem and net-device-up IFACE=lo
# Stop script probably doesn't run
stop on (net-device-down IFACE=lo)

emits ipv6-lo-ips

pre-start script
/sbin/ip -6 addr add fdd7:5938:e2e6:6c8d:7f00:1:c:8891 dev lo
/sbin/ip -6 addr add fdd7:5938:e2e6:6c8d:7f00:1:c:8893 dev lo
initctl emit ipv6-lo-ips
end script

pre-stop script
/sbin/ip -6 addr del fdd7:5938:e2e6:6c8d:7f00:1:c::8891 dev lo
/sbin/ip -6 addr del fdd7:5938:e2e6:6c8d:7f00:1:c::8893 dev lo
stop script
sudo start ipv6-lo
sudo service opendkim start

DMARC

sudo nano /etc/opendmarc.conf
AuthservID mail3.thejc.me.uk

ForensicReports true

PidFile /var/run/opendmarc.pid

RejectFailures true

Socket inet6:8893@[fdd7:5938:e2e6:6c8d:7f00:1:c:8893]

UMask 0002

UserID opendmarc

AuthservIDWithJobID true

ForensicReportsSentBy dmarc@johncook.co.uk

PublicSuffixList /home/thejc/Scripts/output/effective_tld_names.dat

In order to use the PublicSuffixList option, we need a public suffix list. Mozilla update what is considered to be the globally recognised public suffix list at publicsuffix.org, but with such a large file potentially being requested by a billion machines per day (assuming each was running software that checks the list directly every 24 hours for updates) I do not want to waste their bandwidth.

For that reason, the following is more involved than just fetching the file using cron once a day (publicsuffix.org requests we don't check more often than that) but the result is that it will cut down bandwidth use considerably.

First we install curl, and then update the OS certificates list - when the certificates screen pops up, it should be fine to just leave things as default, tab to OK, and press enter.

Next we download the public suffix list, binding to my ethernet interface (--interface eth0:0) with my mail server IP address (so a reverse DNS lookup by Mozilla would show who downloaded it, rather than every request using a random IP on my server) and following any redirects (--location) using the OS certificate list (--cacert /etc/ssl/certs/ca-certificates.crt) because curl has trouble with the --capath option on my system, using the last modified time of a file (-z), creating directories if necessary (--create-dirs), requesting a compressed gzip copy of the file and decompressing it (--compressed) to the output file (-o), without any progress meter or error messages (--silent) but with verbose output so we can see the Last-Modified header among other things (--verbose).

The reason we need --verbose is because curl does not seem to set the modification time of the file to that in the Last-Modified header.

mkdir -p ~/Scripts/output
cd ~/Scripts
sudo apt-get install curl
sudo dpkg-reconfigure ca-certificates
curl --compressed --interface eth0:0 --location --cacert /etc/ssl/certs/ca-certificates.crt -z /home/thejc/Scripts/output/effective_tld_names.dat --create-dirs -o /home/thejc/Scripts/output/effective_tld_names.dat --verbose --silent https://publicsuffix.org/list/effective_tld_names.dat

The Last-Modified date is "Wed, 06 May 2015 20:56:04 GMT".

touch -t `date --date="Wed, 06 May 2015 20:56:04 GMT" +%Y%m%d%H%M.%S` /home/thejc/Scripts/output/effective_tld_names.dat

The Expires date is "Wed, 06 May 2015 20:56:04 GMT". I am not sure yet how I am going to programatically do things, but in a script [ `date -d $expires +%s` -lt `date +%s` ] will return 1 if the Expires date is in the future.

The reason we need to set the modification time of the file with touch after a successful (200) GET is because otherwise curl uses the current date, and some server software like Varnish does not compute If-Modified-Since < Last-Modified, rather it computes If-Modified-Since != Last-Modified.

sudo nano /etc/postfix/main.cf
smtpd_milters = inet:[fdd7:5938:e2e6:6c8d:7f00:1:c:8891]:8891 inet:[fdd7:5938:e2e6:6c8d:7f00:c:b:8893]:8893
non_smtpd_milters = inet:[fdd7:5938:e2e6:6c8d:7f00:1:c:8891]:8891 inet:[fdd7:5938:e2e6:6c8d:7f00:1:c:8893]:8893
postconf
sudo service opendmarc restart
sudo service postfix reload

At this point I ran into a rather curious bug. Despite everything opendmarc-related being identical on vps2 and vps3 (other than IP address and hostname settings) opendmarc refuses to start on vps3 when PublicSuffixList setting is enabled. The error opendmarc_policy_library_init() failed only seems to be avoidable by commenting out the PublicSuffixList configuration file line.

It turned out the problem is something permissions related because as soon as I moved the PSL to /etc/ opendmarc started without issue.

Another issue I ran into was after adding mail4.thejc.me.uk to the MX entries for thejc.me.uk at a higher priority than mail3, and mail getting stuck in the queue.

36 hours after Google Search insisted it was an MTU issue, I added verbosity to the mail logs, attempted manually connecting to my home server from vps3 using gnutls-cli (STARTTLS support), and then tried finding where the problem was.

My old (hyperboria) init scripts on home and vps2 are different than the new version—previously an MTU of 1400 was specifically set but now it was 1304. This meant ula-net was getting an MTU of 1280 on vps3, causing a timeout during DATA phase of SMTP connections between vps3 and home.

To resolve that, I added the line sudo ifconfig tun0 mtu 1400 to /etc/init.d/cjdns at the end of (within the) else block of start() (i.e. if cjdns is running, else start cjdns and set the MTU.

A reboot of vps3 later and tracepath6 was working from all 3 servers to each other's fdd7:5938:e2e6:3::… endpoint IP address. vps3 aka mail4 is now able to recieve mail and pass it on to home without error.

I installed dovecot-common and dovecot-sql and configured dovecot and postfix for SASL authentication, which will be used for authenticating users for outbound e-mail. I also installed mutt for testing local user mail.

I downloaded the source foe opendmarc 1.3.1 and libspf2, installed libopendmarc-dev, and then built and installed libspf2 and opendmarc (the latter with configuration command ./configure --prefix=/usr --with-spf) because opendmarc crashes (segfaults) if an e-mail has some headers missing. Note: the configuration file has changed—search for Forensic and replace with Failure.

At this point the only thing left to do is add mail4 MX records for my mail-enabled domains, and SPF records (and DMARC reporting delegation records if needed).