Debian mail server using OpenSMTPD, SpamAssassin and Dovecot


Now that I have a net.presence, I decided that I wanted to run my own mail server. This was a little more... interesting than I had anticipated (mostly because wrestling with DNS is terrible). But anyway, since a lot of the documentation available on OpenSMTPD on the web is only applicable to OpenBSD (as one would expect), I decided to write my own thoughts down on using OpenSMTPD on a different platform, namely Debian Stretch.

Why OpenSMTPD?

As far as I understand, the go-to MTAs for Debian are usually Exim or Postfix, so why did I bother pulling teeth in getting OpenSMTPD to work in a usable manner (and be warned, there is teeth pulling involved as seen here and below)? Well, first of all, I'm a bit of an OpenBSD fan, and I have read everybody else's blog posts about configuring OpenSMTPD on OpenBSD already; I had some knowledge of the software before I even started this endeavour, as opposed to my complete cluelessness with regards to Exim or Postfix. Second of all (and this is related to the first point), OpenSMTPD is really easy to configure (in my not-very-humble opinion) and, if you can perform the necessary mental gymnastics, it's possible to extend the configuration to cover some reasonably complex situations. (Since setting the mail server up I have seen someone else's production configuration for Postfix, and the number of moving parts and twiddly knobs was vaguely disconcerting.) TL;DR: I like OpenSMTPD.

How it all Just Works(tm)

I've configured my system for a full blown virtual users setup where all the mail users are mapped to a single system user. All of the virtual users' mail is stored in subdirectories of the system user's home directory. This might seem like a little overkill for a small mail site, but in my opinion the resulting configuration is more flexible and makes it really simple to add new users or new domains in the future (if you ever plan on upsizing).

As well as OpenSMTPD I'm using spampd (SpamAssassin) and Dovecot; spampd tags anything suspicious as spam and Dovecot handles the mail delivery function. It's possible to make OpenSMTPD do the mail delivery itself and ~~let Dovecot pick up the pieces~~ have Dovecot act more or less as just an IMAP server, but then you lose the ability to have Dovecot perform filtering on incoming email (e.g. move mails tagged as spam to a spam folder). My configuration handles the following three scenarios:

OpenSMTPD considers any mail user who has a mail account (virtual or otherwise) on the local system to be a local user. Any other user is considered a remote user.

Note that we aren't handling outgoing mail for remote users; we aren't a mail relay for other mail servers.

Packages

We need the following packages installed:

$ sudo apt-get install opensmtpd opensmtpd-extras spampd dovecot-core dovecot-lmtpd dovecot-imapd

I'm also assuming you're setting up this mail server on mail.example.com to handle mail for example.com and have a letsencrypt certificate for the mail.example.com domain.

Prerequisite configuration

Before we configure any of the mail-handling software, we need to create the user to which all of our virtual users are mapped, like so:

$ sudo useradd -m -d /var/local/mail -s -k /dev/null vmail

(The use of /var/local/vmail is just my personal taste, as I have other things running on my mail server which aren't quite default either.)

Configuring spampd

This part's easy; spampd's default configuration does exactly what we need it to. spampd listens on port 10025 on localhost, scans any mails it is sent, and then forwards it to localhost port 10026, where OpenSMTPD will be listening.

Configuring OpenSMTPD

Configuration file

OpenSMTPD's configuration file is pretty friendly. The annotated example below is based on my production /etc/smtpd.conf config file. In saying that, don't just blindly copy it; look at some other people's examples and the smtpd.conf manual page.

# This is the smtpd server system-wide configuration file.
# See smtpd.conf(5) for more information.

# pki setup - this is where we tell smtpd where to find our TLS certificate and private key
pki mail.example.com key "/etc/letsencrypt/live/mail.example.com/privkey.pem"
pki mail.example.com certificate "/etc/letsencrypt/live/mail.example.com/fullchain.pem"

# tables setup - this is where we specify other files which contain information like alias mapping or virtual users
table aliases file:/etc/mail/aliases      # mail aliases for local users
table domains file:/etc/mail/domains      # domains we accept mail for
table passwd passwd:/etc/mail/passwd      # a passwd(5)-style file containing users' encrypted passwords
table virtuals file:/etc/mail/virtuals    # mapping of virtual users to local users

# listen ports setup

## listen on the loopback interface for mails we've previously sent to spampd and tag them so we know we've already
## processed them.
listen on lo port 10026 tag SPAMPD_IN 

## listen on interface eth0 on port 25 for mail from other servers for our users; use the TLS certificate for 
## mail.example.com for encrypted connections. Only accept mail which is delivered over an encrypted connection though
listen on eth0 port 25 tls pki mail.example.com 

## listen on interface eth0 on port 587 for mail submitted by our local users for other servers; use the TLS certificate
## for mail.example.com, *require* the connection to be encrypted and require remote users to authenticate against the
## data in the "passwd" table, configured above.
listen on eth0 port 587 smtps pki mail.example.com auth <passwd> 

# the following line restricts the mail transfer agent component of smtpd to connecting to remote hosts using only IPv4.
# you should enable this if you don't have any PTR records for you public IPv6 address (if you have one, which you really
# should), as some mail servers won't accept mail from a remote system with no accurate PTR records (look for "Forward-
# confirmed reverse DNS" on wikipedia).
limit mta inet4

# accept mails from local users destined for other local users.
accept from local for local deliver to lmtp "/var/run/dovecot/lmtp" rctp-to

# handle incoming mail from remote systems
## accept mails which we have already accepted and sent to spampd which are also destined for one of our virtual users.
accept from local tagged SPAMPD_IN for domains <domains> virtual <virtuals> deliver to lmtp "/var/run/dovecot/lmtp" rctp-to
## accept mails for any of the domains for which we handle mail and relay it to spampd
accept from any for domain <domains> relay via "smtp://127.0.0.1:10025"

# accept mail from our local users which is for another system and relay it out
accept from local for any relay

Other files

As referenced in the configuration file above, there are some other files that need to be created and populated. This following section is very similar to the example given on opensmtpd.org (save for one caveat), but I have reproduced the instructions here along with some commentary.

Firstly, we need to configure a list of aliases mapping users required by the RFCs to users on our system (as well as any other required aliases). Your /etc/mail/aliases file should look similar to the following:

# /etc/mail/aliases
mailer-daemon: postmaster
postmaster: root
nobody: root
hostmaster: root
usenet: root
news: root
webmaster: root
www: root
ftp: root
abuse: root
noc: root
security: root
root: admin
admin: admin@example.com
vmail: /dev/null

Note that we map all of the RFC-required mail accounts to root and then alias root to our 'admin' user; replace this with a suitable system account on your system. We then alias this admin account to a virtual user (which will be configured later). We also indicate that the vmail system user should not be receiving any mail directly.

Next, we configure the list of domains for we will accept mail in /etc/mail/domains. This is just a newline-separated file of domain names:

# /etc/mail/domains
example.com

We also need to configure the mapping of virtual users to either other virtual users (like the aliases above) or to system users; this is done in /etc/mail/virtuals. Note that any virtual user mapping chains must terminate at the system vmail user.

# /etc/mail/virtuals
admin@example.com   fred@example.com
fred@example.com    vmail

Next we need the file, /etc/mail/passwd, which stores encrypted passwords so we can authenticate users. This is similar to a passwd(5)-style file.

CAVEAT WARNING

The version of the passwd file table lookup program (from the opensmtpd-extras package) that ships with Debian is an older version which requires a fully populated passwd(5) file including uid, gid and home directory. Most of this information is completely superfluous for our purposes, all we need is a mapping of usernames to encrypted passwords. More recent upstream versions of opensmtpd-extras have changed the table-passwd program so that it only requries the username and password fields to be populated, however the table processing API version was also increased at some point as well. You can either proceed with the default table-passwd program and fill in all the extra fields as required, or you can download the 12th of July 2016 snapshot of opensmtpd-extras from here, configure and compile it with the same options as the Debian package and then copy the resulting table-passwd binary to /usr/lib/$ARCH-linux-gnu/opensmtpd/table-newpasswd or similar and change the configuration file above to point to newpasswd:/etc/mail/passwd. The following instructions assume that you are using a custom-compiled version of table-passwd, so you should populate the extra fields as necessary if you are using the version that ships with Debian.

You can generate an encrypted password using smtpctl encrypt or using the encrypt program found under /usr/lib/$ARCH-linux-gnu/opensmtpd/. For example:

$ sudo smtpctl encrypt
fred foobar
$6$w8NCJkS4KR/e2uhX$Q5laV4OropKvVRoFnNKWFuZTQsxg6rJXjlB9GVZ2DE03tUkENCFPNimgohoJwi.EvEMh.s2FOdy9wQ9WqhFkf0

You can then enter the username and encrypted password into /etc/mail/passwd:

# /etc/mail/passwd
fred@example.com:$6$w8NCJkS4KR/e2uhX$Q5laV4OropKvVRoFnNKWFuZTQsxg6rJXjlB9GVZ2DE03tUkENCFPNimgohoJwi.EvEMh.s2FOdy9wQ9WqhFkf0::::::

Note that you can append options for Dovecot at the end of the line, if desired; Dovecot will share this password file.

Configuring Dovecot

Dovecot has a lot of knobs that can be twiddled and handles that can be pulled, however the default files that ship with Debian are very well commented, so you should just dive in and adjust them to suit your own installation. There are some important changes that should be made as well as site-specific tuning. Firstly, you should edit or copy and edit /etc/dovecot/conf.d/auth-passwdfile.conf.ext and ensure that it contains at least the following configuration stanzas:

passdb {
  driver = passwd-file
  args = /etc/mail/passwd
}

userdb {
  driver = static
  args = uid=vmail gid=vmail home=/var/local/vmail/%d/%n
}

This tells dovecot to look for user authetication data in the file /etc/mail/passwd, which is (surprise, surprise) a passwd(5)-style file, and that all users map to the vmail user and that their home directories are subdirectories under /var/local/vmail. Next, you should edit /etc/dovecot/conf.d/10-auth.conf, go to the bottom of the file and uncomment the line which includes the passwd file authentication driver (or modify the existing one if you copied the file). You may wish to modify /etc/dovecot/conf.d/10-mail.conf and configure whether Dovecot uses Maildir or mbox format mailboxes and where it should find them. (Unless you have existing reasons to use mbox, it is highly recommended that you use Maildir.) The rest of Dovecot's configuration files may be edited as necessary and according to the directions given in the comments.

It's a wrap!

That should be enough to get you started with an OpenSMTPD and Debian powered mail server. Turn everything on, send some test emails (or get someone to do it for you) and make sure you can see them being processed in /var/log/mail.info. If you point a mail client at the correct ports, you should be able to send and receive emails from the mail server.

Addendum

Note to self: writing blog posts in markdown is all well and good until you have triple backtick-delimited code blocks which start with a hash on a line. markdown will render this as an h1 tag and make your life sad. Edit: werc also doesn't like POST requests with large request bodies, meaning that updating long ~~blog~~ blag posts through the web UI can leave them truncated. Joy.



home