DIY mail server with OpenSMTPd and Rspamd

Setting up a mail server is surprisingly easy

A couple of months ago I came across a blog post on how to set up your own mail server using OpenSMTPd. The posts’ author – Gilles Chehade – is one of the main developers of OpenSMTPd, OpenBSDs default SMTP daemon. As I was busy with my final thesis at the time I put off trying it for later.

After finishing my final thesis, I followed his guide on how to set up the server and must say it was indeed not that difficult to set up, provided you’re a little bit familiar with general *nix system administration and adding some DNS records.

After succeeding, I immediately mailed a friend who is also running their own mail server to let them know that I finally managed to set one up as well. They confirmed that the config looked good, however they suggested that I remove some information from the mail headers.

Removing mail headers

The first piece of information to remove is the from field in the Received header. In my case the from field contained both the RFC1918 (private) address of my computer as well as the publicly routable IP address and host name of my router’s WAN interface provided by the ISP.

The Received header looked something like this (I have removed the sensitive information):

Received: from [192.168.69.20] (host name redacted [IP redacted])
	by mail.example.com (OpenSMTPD) with ESMTPSA id 0c5e35d7 (TLSv1.3:AEAD-AES256-GCM-SHA384:256:NO)
	for <recipient redacted>;
	Mon, 8 Mar 2021 13:09:41 +0100 (CET)

A quick check using man smtpd.conf reveals that OpenSMTPd can omit that field when adding the Received header using the mask-src option in a listen directive like so:

listen on all smtps pki "mail.example.com" auth mask-src filter rspamd

This shows another change I made to the configuration compared to the blog post. Instead of using SMTP with STARTTLS on port 143 I only allow SMTP over explicit TLS on port 465 for mail submission, which is what the smtps option does.

Another header that my friend suggested I remove is the User-Agent header, which in my case looked like this:

User-Agent: Mozilla/5.0 (X11; Linux x86_64; rv:78.0) Gecko/20100101 Thunderbird/78.8.0

Because I’m using Rspamd to perform DKIM signing on outgoing mail anyway, I can also use it to remove the User-Agent header. For this I am using the built-in Milter headers module. Simply add this to /etc/rspamd/local.d/milter_headers.conf:

skip_local = false;
skip_authenticated = false;
use = ["remove-headers"];
routines {
	remove-headers {
        headers {
            "User-Agent" = 0;
        }
	}
}

This configuration will remove all User-Agent headers from all mail that gets scanned by Rspamd. The options skip_local and skip_authenticated are important and must be set to false, otherwise mail arriving from authenticated users via SMTP won’t have that filter applied, which is the entire point of this exercise.

It took me quite a while to find this easy solution in a GitHub issue, even the helpful people in the official Rspamd IRC seemed to have forgotten about it. All in all Rspamd is easy to get going when you’re just using the default configuration. The lack of documentation however makes it quite difficult to figure out the correct configuration without other people’s blog posts and GitHub issues to help out.

Thunderbird autoconfiguration

Thunderbird and some other mail clients can automatically retrieve the mail server settings from a configuration server when setting up a new account. The configuration server is simply a HTTP server that serves an XML file with the account setup information. Thunderbird tries to download the XML file from the location https://autoconfig.example.com/mail/[email protected] or https://example.com/.well-known/autoconfig/mail/config-v1.1.xml, first using HTTPS and if that doesn’t work using HTTP. The configuration file format is described in detail here.

I’m using OpenBSD’s httpd as my HTTP server because it is part of the base system and very easy to configure. I placed my XML file at /var/www/autoconfig/config-v1.1.xml and use the following configuration for httpd:

server "autoconfig.example.com" {
        listen on egress port http
        location "/mail/*" {
                root "/autoconfig"
                request strip 1
        }
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
        location * {
                block return 302 "https://$HTTP_HOST$REQUEST_URI"
        }
}

server "autoconfig.example.com" {
        listen on egress tls port https
        tls {
                certificate "/etc/ssl/autoconfig.example.com.fullchain.pem"
                key "/etc/ssl/private/autoconfig.example.com.key"
        }
        location "/mail/*" {
                root "/autoconfig"
                request strip 1
        }
        location "/.well-known/acme-challenge/*" {
                root "/acme"
                request strip 2
        }
}

types {
        include "/usr/share/misc/mime.types"
}

The configuration serves both the XML file and handles the requests required for the acme-client TLS certificate renewal process. One important thing to note is that the types clause must be included, otherwise httpd will set the MIME-type of the requested XML file to application/octet-stream instead of text/xml which Thunderbird will refuse to use for autoconfiguration.