Setup OpenSMTPD with Dovecot on OpenBSD
— 2687 words — 14 min
Table of Contents
I assume you already installed OpenBSD. If you haven’t take a look at the official installation guide. The process is well documented. When you have a running machine with OpenSSH (I wrote some notes about hardening OpenSSH), you’re good to go. You’ll also need access to the DNS settings of your domain.
Alright, with a running OpenBSD system let’s start with setting up the
correct DNS records for the domain I want to send mail. Let’s assume
it’s example.com
.
rDNS and FCrDNS🔗
You want a correct rDNS (reverse DNS) and FCrDNS (forward committed rDNS) record. This is an effective method to detect spam if one of these records is incorrect. For instance, compromised desktops behind residential connections will usually not have a “legitimate” looking rDNS and so it’s easy (for us and anybody else) to block mail from there. The same is true for compromised servers that were not intended to send email or have a different domain associated with them. Setting up these records is like the minimal amount of work you have to do as an operator to state that this machine is supposed to send email.
Let’s assume the IP address of your mailserver is 1.2.3.4
and you want
to serve mails for example.com
. Then you want meet the following
conditions.
Define which server is supposed to handle mail for example.com
with
the MX
record and create an A
record that resolves to the IP
address of that server. You should know where to do that, either on your
own nameserver or whereever you bought your domain if you use their
nameserver. When you did that, you can verify the records with dig
.
Now set the correct rDNS so that the domain name that handles email is returned if the IP address of that sever is looked up. You usually do that somewhere in the interface of your provider that hosts your server. Then verify that the IP address resolves to the correct domain.
When the mailserver accepts a connection from an IP address, it does a
reverse DNS lookup on that IP. In this case, the result is the domain
mx.example.com
. Then this domain gets resolved and returns an IP
address. If that resolved IP address and the connecting IP address are
the same, you’re passing the Forward Committed rDNS check.
Setup SPF🔗
SPF is the Sender Policy Framework. It lets you define which IP addresses and/or hostnames are allowed to send email for your domain.
The following TXT record specifies that only the hostname defined in the
MX record is allowed to send mail for the domain example.com
.
v=spf1 mx -all
You can use dig again to verify the record.
"v=spf1 mx -all"
If a mail server receives an email and the sending mail server is not a
hostname or IP address specified in the SPF record, then the -all
part
tells the receiving mail server to reject the email.
Setup DKIM🔗
DKIM stands for DomainKey Identified Mail. It helps the receiving mail server to detect forged email by looking at the DKIM signature. Every email that leaves the legitimate mail server is signed and the receiving mail server can verify the signature by using the public key that is published as a DNS TXT record. Let’s setup it up.
First, generate a 1024 bit RSA key and extract the public key that will
be published in a DNS TXT record. Also, adjust the permissions of the
private key so that it is not world readable and set the owner group to
_rspamd
so that rspamd can read it. If you wonder why rspamd
needs
to be able to read the file, it’s because in this setup rspamd
will
perform the signing operation as you will see in the setup of OpenSMTPD.
Now, create a DNS TXT record according to the scheme
<selector>._domainkey.example.com
. You can choose <selector>
arbitrarily, for example, the date you created the RSA key. The content
of the txt records looks like this with k=rsa
and p=<public key>
. One line; no spaces; no line breaks; values separated by a
semicolon.
v=DKIM1;k=rsa;p=MIGfMA0GCS[...]EudMVZQIDAQAB;
You can display the record with dig
. Replace <selector>
with the
value you chose.
Setup DMARC🔗
DMARC stands for Domain-based Message Authentication, Reporting
and Conformance. It lets you define how a receiving mail server should
handle emails that do not pass the SPF or DKIM check—provided that the
receiving mail server checks DMARC, SPF, and DKIM. Additionally, it may
also have other local policies that it follows. The DMARC record is
another TXT DNS entry consisting of a few properties. For all properties
see RFC 7489. Required tags are the version v=DMARC1
(must also be the first one) and the policy p
.
- policy
p={none,quarantine,reject}
- percentage
pct=100
how many mails should be subject to the policy (defaults to 100) - Reporting URI of Aggregated
rua=postmaster@example.com
(aggregated reports are sent to this email)
Example:
v=DMARC1;p=quarantine;pct=100;rua=mailto:postmaster@example.com;
My understanding of DMARC is still a bit fuzzy. For instance, I think
that a mail server receiving an email from a server that is not listed
in the SPF record (containing a -all
) will reject that email
instantly. I’m not sure whether a DMARC record can override this and
have the email quarantined, for example. I think it depends on how the
local service that does these checks is configured. In theory, it should
probably be
- check the DMARC record of the sending domain and act accordingly
- if there is no DMARC record, apply local policies.
However, I guess one could also configure the service in such as way
that the local policies are always used. I need to figure this out at
some point. Or if you know more, get in touch. As you’ll see
below the service that is responsible for these checks is rspamd with
the filter filter-rspamd
in the OpenSMTPD configuration.
Obtain a Lets Encrypt Certificate with ACME🔗
Setup httpd for the Lets Encrypt challenges.
cp /etc/examples/httpd.conf /etc/
For a minimal configuration, remove the SSL configurations so it looks like this.
"mx.example.com" {
on * port 80
location "/.well-known/acme-challenge/*" {
"/acme"
2
strip }
location * {
302 "https://$HTTP_HOST$REQUEST_URI"
return }
}
Copy the example ACME configuration.
Then edit the configuration slightly to match your domain name.
.example.com {
mx"/etc/ssl/private/mx.example.com.key"
key "/etc/ssl/mx.example.com.fullchain.pem"
full chain certificate with letsencrypt
}
Run the acme-client
.
You can add that command to cron
to be executed weekly. If the
certificate is valid for less than 30 days it will be renewed
automatically.
Setup rspamd🔗
Rspamd can do many things. Here it is reponsible for assigning a spam score and signing ougoing emails (DKIM) among other things.
Tell rspamd which selector to use for DKIM signing in
/etc/rspamd/local.d/dkim_signing.conf
. This is the DKIM selector you
could choose arbitrarily above.
true;
domain {
.com {
"/etc/mail/dkim/example.com.key";
"20230930";
}
}
As already mentioned when creating the key file, make sure that the key
is readable by the _rspamd
group.
Enable the services.
The rest of this rspamd section is optional. If you’re following along, you can probably skip this part and go straight to the Setup OpenSMTPD section. You can come back later and check if you need or want this.
The steps below add a new setting block called outgoing
to rspamd that
you can use in the OpenSMTPD configuration to adjust the behaviour of
rspamd.
This outgoing
setting block enables DKIM signing and sets the values
to mark an email as spam to 100
, so basically never. I use this
specific rspamd configuration only for my outgoing mail, so that rspamd
does not add its spam headers but still signs the emails (DKIM).
Why?
I did this because when user cron jobs send me mail (from @localhost to
@localhost) it got flagged as spam. Mail from the root
user seemed to
be fine, though.
Add the new setting block in /etc/rspamd/local.d/settings.conf
.
outgoing {
"outgoing";
apply {
"dkim"];
[ actions {
100.0;
100.0;
"add header" = 100.0;
}
}
}
Now I can use the -settings-id
flag to tell filter-rspamd
to use the
new configuration block for outgoing mail and use the default
configuration for incoming mail (notably, try to detect spam). Adjust
the following in /etc/mail/smtpd.conf
.
-in proc-exec "filter-rspamd"
rspamd-out proc-exec "filter-rspamd -settings-id outgoing"
rspamd
on all filter "rspamd-in"
on all port submission filter "rspamd-out"
See filter-rspamd
documentation on Github.
Finally Setup OpenSMTPD🔗
As OpenSMTPD is installed by default, you can just adjust its
configuration in /etc/mail/smtpd.conf
.
In English the configuration does roughly the following
- define the certificate to use for TLS
- define an alias table from an external file
- define some filters that reject senders under certain conditions
- listen on port 25 with optional STARTTLS
- listen on SMTPS port 465 and only allow authenticated connections
- specify when to do what (
match
on condition and doaction
)
.example.com cert "/etc/ssl/mx.example.com.fullchain.pem"
mx.example.com key "/etc/ssl/private/mx.example.com.key"
mx
:/etc/mail/aliases
aliases file
'.*\.dyn\..*', '.*\.dsl\..*' } \
check_dyndns phase connect match rdns regex { "550 no residential connections"
!rdns \
check_rdns phase connect match "550 no rDNS"
!fcrdns \
check_fcrdns phase connect match "550 no FCrDNS"
-exec "filter-rspamd"
rspamd proc
on all tls pki mx.example.com hostname mx.example.com \
filter { check_dyndns, check_rdns, check_fcrdns, rspamd }
on all smtps pki mx.example.com auth mask-src filter rspamd
"local_mail" maildir junk alias <aliases>
"outbound" relay helo mx.example.com
"example.com" action "local_mail"
from any for domain "local_mail"
for local action
"outbound"
from any auth for any action "outbound"
for any action
I would advice to not copy and paste this configuration. It will work though, if you adjust the domain names and so on. Read the manpage of smtpd.conf(5) and familiarize yourself the options—at least the options you find in the configuration above.
However, I do want you to note the mask-src
option on the listen line
for email submission. If you don’t specify it, the first Received
header in the email will contain a line such as the following:
Received: from [127.0.0.1] (dynamic-a-b-c-d.x.y.pool.isp.com
[a.b.c.d])
by localhost (OpenSMTPD) with ESMTPSA id d6b22206
(TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO)
As you can see, it recorded where that email orginated from: my
residential IP address (that I manually marked as a.b.c.d
) and the
reverse DNS name. If you do specify mark-src
this part is simply
omitted in the email headers.
Received:
by localhost (OpenSMTPD) with ESMTPSA id 9a19d954
(TLSv1.3:TLS_AES_256_GCM_SHA384:256:NO)
You can do a lot with OpenSMTPD. You could create another
table(5) referencing a file containing the regex expressions
matching the dynamic rDNS of residential IP addresses (similar to the
aliases table above), serve mails for multiple domains, create a users
table that’s used instead of the normal system users in /etc/passwd
and much more. Again, check out the manpage smtpd.conf(5)
🙂
Setup Dovecot🔗
Dovecot provides the imap service so that I can connect my phone or other mail clients that use imap to read and download mail. First, install the package.
Then I tweaked the following configuration settings.
Disable plain text authentication in /etc/dovecot/conf.d/10-auth.conf
.
yes
Require an SSL connection and tell dovecot which certificate and key file
to use in /etc/dovecot/conf.d/10-ssl.conf
.
required
</etc/ssl/mx.example.com.fullchain.pem
</etc/ssl/private/mx.example.com.key
Tell dovecot where mail is located in
/etc/dovecot/conf.d/10-mail.conf
.
:~/Maildir
maildir
Only provide imaps on port 993. I don’t need pop3 nor do I need imap on
port 143 via STARTTLS. This is configured in
/etc/dovecot/dovecot.conf
.
imaps
That’s it. Enable and start dovecot.
Now I can connect my email clients via imaps using a user and password
defined in /etc/passwd
. You might be used to use your email address as
user name but with this setup, you just use your local user on the
server.
Misc🔗
Some miscellaneous things 🙂
Manually Connect and Authenticate with SMTPS🔗
If you’re connecting to smtp on port 25 it’s pretty easy.
And you can start talking smtp to the server. It’s a bit harder when you try to go through SMTPS on port 465
- because TLS is enabled by default
- and you need to authenticate to the mail relay
Let’s start with the password encoding used by the authentication method
auth plain
in smtp. The order is a null byte, the user name, a null
byte, and the password encoded as base64.
|
Be aware of your shell history, if you do that 🙂.
Then I can use openssl
to start a tls connection and greet the mail
server. If I wanted to start sending mail, it tells me to authenticate
first. So I type auth plain
and send the base64 encoded combination of
the username and password.
Now I can submit my mail.
Email with a Wrong SPF Record🔗
Why? Because I wanted to see how the log entry looks like.
I set a wrong SPF record (meaning I chose a different IP address than the one from the real server) for the domain intentionally and then tried to send an email to another domain running postfix as smtp service. The error on the postfix side (I control that server, too) looks very similar to the error below which is logged by OpenSMTPD.
The email gets rejected with a PermFail
(I replaced occurences of the
other domain with somewhere.com
)
Sep 26 20:11:50 hostname smtpd[40801]: 7bceeb8376da5b52 mta delivery evpid=43ace175217a562a from=<user@example.com> to=<somewhere@somewhere.com> rcpt=<-> source="1.2.3.4" relay="a.b.c.d (mail.somewhere.com)" delay=0s result="PermFail" stat="550 5.7.23 <somewhere@somewhere.com>: Recipient address rejected: Message rejected due to: SPF fail - not authorized. Please see http://www.openspf.net/Why?s=helo;id=example.com;ip=1.2.3.4;r=somewhere.com"
Basically, as far as postfix was concerned, somebody tried to spoof an
email for the @example.com
domain because when postfix looked up the
SPF record it found a different IP than the one that was now trying to
hand over an email. So it got rejected.
Found a mistake or have a suggestion? Get in touch!
Articles from blogs I follow around the net
Status update, November 2024
Hi all! This month I’ve spent a lot of time triaging Sway and wlroots issues following the Sway 1.10 release. There are a few regressions, some of which are already fixed (thanks to all contributors for sending patches!). Kenny has added support for software-…
via emersion November 22, 2024Dismissed!
Dismissing gives me a quick little lift. “That guy is an idiot!” “That place sucks!” There. Now I feel superior. Now I don’t have to think about it. I don’t even need first-hand experience. I can just echo any complaint I’ve heard. I’ve done this with restaurants…
via Derek Sivers blog November 18, 2024Generated by openring