action "outbound" relay
# Rules
# Local
match from local for local action "local_mail"
match from local for domain "example.com" action "domain_mail"
match from local for any action "outbound"
# Remote
match from any for domain "example.com" action "domain_mail"
match from any for any action "outbound"
We'll flesh this out after dovecot and rspamd are set up.
2. Check configuration and restart smtpd:
doas smtpd -nf /etc/mail/smtpd.conf
doas rcctl restart smtpd
3. Check if it's working by sending messages from a local user to domain address:
mail -s "Test mail from local user" john@example.com
John,
This might work.
Pace
ctrl-d
Check if the mail is received:
doas ls -al /var/vmail/example.com/john/new/
F. Adjust pf.conf.
With this setup we will use imap and smtp.
1. Add to pf.conf on the firewall:
...
mailserver = "10.1.1.2"
email_ports = "{ smtp, imap, imap3, 465, submission, imaps }"
...
# Direct email traffic to smtp server
pass in on egress inet proto tcp from any to (egress) port $email_ports rdr-to $mailserver
....
2. Add to pf.conf on mailserver:
...
email_ports = "{ smtp, imap, imap3, 465, submission, imaps }"
...
# Pass in traffic to mailserver
pass in on egress inet proto tcp from any to ( egress ) port $email_ports
....
3. Now try to send an email to john@example.com from Gmail or other remote mail service. Check as above whether it is received.
You can't send mail yet, but at least 1/2 of the server is functional
G. Install, configure and test Dovecot
Here we basically follow https://www.vultr.com/docs/an-openbsd-e-mail-server-using-opensmtpd-dovecot-rspamd-and-rainloop#_Optional__Configure_RainLoop_Webmail with a few modifications.
1. define login class:
Define a login class for the Dovecot daemon. At the bottom of /etc/login.conf add the following lines.
dovecot:\
:openfiles-cur=1024:\
:openfiles-max=2048:\
:tc=daemon:
Note that cap_mkdb(1) must be run after each edit of /etc/login.conf to keep the database version in sync with the plain file:
cap_mkdb /etc/login.conf
2. Check /etc/dovecot/conf.d/10-ssl.conf to be sure the ssl_cert and ssl-key definitions are commented out as shown.
...
# PEM encoded X.509 SSL/TLS certificate and private key. They're opened before
# dropping root privileges, so keep the key file unreadable by anyone but
# root. Included doc/mkcert.sh can be used to easily generate self-signed
# certificate, just make sure to update the domains in dovecot-openssl.cnf
#ssl_cert = </etc/ssl/dovecotcert.pem
#ssl_key = </etc/ssl/private/dovecot.pem
...
3. Instead of editing individually each of the many config files of Dovecot, you can simply create a /etc/dovecot/local.conf file which will override any options. This example is from the vultr.com instructions and works if you install by those instructions. If you use another means of auth, this will need to be edited:
auth_mechanisms = plain
first_valid_uid = 2000
first_valid_gid = 2000
mail_location = maildir:/var/vmail/%d/%n
mail_plugin_dir = /usr/local/lib/dovecot
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext imapsieve vnd.dovecot.imapsieve
mbox_write_locks = fcntl
mmap_disable = yes
namespace inbox {
inbox = yes
location =
mailbox Archive {
auto = subscribe
special_use = \Archive
}
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
prefix =
}
passdb {
args = scheme=CRYPT username_format=%u /etc/mail/credentials
driver = passwd-file
name =
}
plugin {
imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_name = Junk
imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_name = *
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
sieve_plugins = sieve_imapsieve sieve_extprograms
}
protocols = imap sieve
service imap-login {
inet_listener imap {
port = 0
}
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
inet_listener sieve_deprecated {
port = 2000
}
}
ssl_cert = </etc/ssl/mail.crt
ssl_key = </etc/ssl/private/mail.key
userdb {
args = username_format=%u /etc/mail/credentials
driver = passwd-file
name =
}
protocol imap {
mail_plugins = " imap_sieve"
}
Be sure to edit the location of your TLS credentials in /etc/ssl. Then, enable and start dovecot.
doas rcctl enable dovecot
doas rcctl start dovecot
Check 'pa aux' to see if it's running and review /var/log/maillog for error messages.
H. Configure rspamd
1. DKIM signing: As noted above, you need to create /etc/rspamd/local.d/dkim_signing.conf:
doas nano -w /etc/rspamd/local.d/dkim_signing.conf
and add this:
domain {
example.com {
path = "/etc/mail/dkim/example.com.key";
selector = "default";
}
}
where "default" is the date of key creation of whatever string you used in the DNS record. Now, restart rspamd:
doas rcctl restart rspamd
2. Set up rspamd web interface:
rspamd has a web ui which displays statistics and allows adjustment of the configuration. This is configured with /etc/rspamd/worker-controller.inc which is not to be edited. Instead, create and edit /etc/rspamd/overrides.d/worker-controller.inc:
doas cp /etc/rspamd/worker-controller.inc /etc/rspamd/override.d/worker-controller.inc:
doas nano-w /etc/rspamd/override.d/worker-controller.inc
displays
count = 1;
password = "q1";
secure_ip = "127.0.0.1";
secure_ip = "::1";
static_dir = "${WWWDIR}";
Edit this as so:
password = "alphanumstring";
secure_ip = "127.0.0.1";
#secure_ip = "::1";
here. That configuration redirects the https request to 127.0.0.1 which bypasses password authentication. You can comment out the secure_ip line to force all connections to authenticate or you can forward to another ip address besides localhost.
3. More securely use the rspamd web interface:
The best and easiest way to do this is with an SSH tunnel. This obivates the need for changing the rspamd configuration, using relayd, or tls certificates. First block port 11334 at the firewall. Then, create a SSH tunnel:
ssh -N -L local_port:web_UI_address_of_rspamd:remote_port_of_rspamd_web_UI ip.add.of.server
or
ssh -N -L 11334:127.0.0.1:11334 IP.ADD.OF.SVR
Leave this running and point your browser at 127.0.0.1:11334 to see the web UI securely.
I. Set up Sieve:
I refer you to the instructions here:
https://www.vultr.com/docs/an-openbsd-e-mail-server-using-opensmtpd-dovecot-rspamd_and-rainloop
https://poolp.org/posts/2019-09-14/setting-up-a-mail-server-with-opensmtpd-dovecot-and-rspamd/
J. Further configuration of OpenSMTPD
Now, let's complete smtpd.conf:
# smtpd.conf
# PKI keys for TLS
pki mail cert "/etc/ssl/mail.example.com.fullchain.pem"
pki mail key "/etc/ssl/private/mail.example.com.key"
# Macros
filters = " { check_rdns check_fcrdns rspamd } "
# Filters
filter check_rdns phase connect match !rdns junk
filter check_fcrdns phase connect match !fcrdns junk
filter "rspamd" proc-exec "/usr/local/libexec/smtpd/filter-rspamd"
# Tables
table aliases file:/etc/mail/aliases
table credentials passwd:/etc/mail/credentials
table virtuals file:/etc/mail/virtuals
# Listeners
listen on all tls pki "mail" hostname "mail.example.com.example.com" mask-src filter $filters
#listen on all tls pki "mail" hostname "mail.example.com" mask-src filter "rspamd"
listen on egress port submission tls-require pki "mail" hostname "mail.example.com" \
auth filter "rspamd"
# Actions
action "local_mail" mbox alias
action "domain_mail" maildir "/var/vmail/example.com/%{dest.user:lowercase}" virtual
action "outbound" relay
# Rules
# Local
match from local for local action "local_mail"
match from local for domain "example.com" action "domain_mail"
match from local for any action "outbound"
# Remote
match from any for domain "example.com" action "domain_mail"
match auth from any for any action "outbound"
K. Install pf-badhost
For additional spam rejection and to protect your mail server, download and install pf-badhost (https://www.geoghegan.ca/pub/pf-badhost/">pf-badhost) as decribed on the authors page. (BTW, tip your local developer; both pf-badhost and unbound-adblock are wo
rthy projects.)
Edit pf-badhost.sh to include blocklists for email attackers:
....
##################################################################
# Block Lists
# Enter URL to any IP blocklist
....
### SMTP/E-Mail Attackers
https://lists.blocklist.de/lists/25.txt
https://lists.blocklist.de/lists/110.txt
https://lists.blocklist.de/lists/143.txt
https://lists.blocklist.de/lists/993.txt
https://lists.blocklist.de/lists/email.txt
https://lists.blocklist.de/lists/mail.txt
https://lists.blocklist.de/lists/imap.txt
https://lists.blocklist.de/lists/courierimap.txt
https://lists.blocklist.de/lists/courierpop3.txt
https://lists.blocklist.de/lists/pop3.txt
https://lists.blocklist.de/lists/postfix.txt
....
Then, restart pfbadhost to load new blocklists:
doas -u _pfbadhost pf-badhost -O openbsd
M. More testing
The acid test is now to send mail to one of your accounts at Google, Microsoft, Apple/iCloud, and others like ATT/Yahoo. If the message is received in your mailbox and not flagged as Spam/Junk, that's great. Now, reply to the message and look to see if the reply shows up in your mailbox of the newly commissioned mail server.
If not, go back to some of the tests detailed above. Instruction from https://prefetch.eu/blog/2020/email-server/#testing may be helpful. Display and read the headers and check DKIM both ways. Recheck the DNS configuration and reverse DNS. CheckTLS (https://www.checktls.com/howto.html#Deeper) and Hardenize (https://www.hardenize.com/) have online tools which can be helpful in pointing out problems. Make sure the mail is not getting trapped by rspamd (use the web ui to see the details).
Good luck!
N. Optional Configuration: TLSRPT and MTA-STS
SPF, DKIM and DMARC are widely used but spam volume has, if anything, increased. In 2018, the IETF released RFC 8460 and RFC 8461, which respectively define TLSRPT and MTA-STS. These are not widely adopted yet but email providers' spam filters may use presence or absense of TLSRPT and MTA-STS as part of their spam scoring system.
1. TLSRPT
TLS reporting, or TLSRPT for short, is very simple: all it does is provide a contact email address in case somebody has trouble with the TLS configuration of your SMTP server.
To enable it for your custom email domain example.com, simply create a DNS TXT record for the _smtp._tls subdomain:
_smtp._tls.example.com. TXT "v=TLSRPTv1; rua=mailto:"
without the angle braces, or,
_smtp._tls.example.com. TXT "v=TLSRPTv1; rua=mailto:postmaster@example.com"
where <contact> is an email address of your admin contact.
2. MTA-STS
MTA Strict Transport Security (MTA-STS) tells other servers that they should avoid sending you unencrypted email and should only accept certain certificates from your side. MTA-STS requires an HTTP web server but we already have one to manage our Let's Encrypt certificate renewals.
a. DNS: The DNS part is still pretty simple: create yet another DNS TXT record, this time for the subdomain _mta-sts:
_mta-sts.example.com. TXT "v=STSv1; id=<id%gt;"
The <id> identifies the policy; so you and remote servers can detect changes, I use the date and time of modification of mta-sts.txt. Every time you edit the mta-sts.txt, you need to update the <id> for remote servers can detect that the policy is changed and refresh their cache of your mta-sts.txt.
_mta-sts.example.com. TXT "v=STSv1; id=2108031905EDT"
Don't forget to create an A record which for subdomain mta-sts (without underscore):
mta-sts.example.com. IN A 1.2.3.4
And add CCA records to your new mta-sts subdomain:
mta-sts.example.com. CAA 0 issue "letsencrypt.org"
b. Create the mta-sts policy file:
First, create the web root folder for the file.
doas mkdir -p /var/www/mta-sts/
Now create the file mta-sts.txt. The contents are as follows, where mx1.example.com and mx2.example.com are the mail hosts defined in example.com’s DNS MX records.
version: STSv1
mode: enforce
mx: mx1.example.com
mx: mx2.example.com
max_age:
Note - weirdly, this file is said to apparently require CRLF Windows-style line endings ("\r\n") but appears to work fine with txt files created by nano. So, for our example:
doas nano -w /var/www/mta-sts/mta-sts.txt
version: STSv1
mode: testing
mx: mail.example.com
max_age: 86400
ctrl-O
ctrl-X
c. httpd: Set your web server to obtain certificates and serve the file. Note that the policy file must be served over HTTPS, so you need a yet another valid TLS certificate for the mta-sts subdomain.
# http server to obtain and renew Let's Encrypt certificate for mta-sts
server "mta-sts.example.com" {
listen on $ext_addr port $ext_HTTP_port
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location * {
block return 302 "https://$HTTP_HOST$REQUEST_URI"
}
}
# https server for serve mta-sts.txt
#server "mta-sts.example.com" {
# listen on $ext_addr port tls $ext_HTTPS_port
# tls {
# certificate "/etc/ssl/mta-sts.example.com.fullchain.pem"
# key "/etc/ssl/private/mta-sts.example.com.key"
# }
# location "/.well-known/mta-sts.txt" {
# root "/mta-sts"
# request strip 1
# }
# location "/.well-known/acme-challenge/*" {
# root "/acme"
# request strip 2
# }
#}
Check and restart httpd:
doas httpd -n
doas rcctl restart httpd
d. Now get the new certificates:
Configure acme-client: Add a stanza to acme-client.conf:
domain mta-sts.example.com {
domain key "/etc/ssl/private/mta-sts.example.com.key"
domain certificate "/etc/ssl/mta-sts.example.com.crt"
domain full chain certificate "/etc/ssl/mta-sts.example.com.fullchain.pem"
sign with letsencrypt
}
Now generate certificates:
doas acme-client -v mta-sts.example.com
Check if the new certificates are in /etc/ssl and keys in /etc/ssl/private.
Now uncomment the mta-sts https server stanza in /etc/httpd.conf and restart httpd.
e. Set up cron job to check and renew certificate:
doas crontab -e -u root
Add:
30 2 * * * acme-client mta-sts.example.com
f. Check your work by using various online MTA-STS validation tools:
https://esmtp.email/tools/mta-sts/">https://esmtp.email/tools/mta-sts/
https://aykevl.nl/apps/mta-sts/">https://aykevl.nl/apps/mta-sts/
https://www.checktls.com/howto.html#Deeper">https://www.checktls.com/howto.html#Deeper
https://www.hardenize.com/">https://www.hardenize.com/
Even if you did everything correctly, these tools will warn you that you’re not using DNSSEC/DANE. But, at this point, this is not widely adopted and can be a pain (see https://dane.sys4.de/common_mistakes). I'd pass on this at present.
Additional Reading/Sources:
https://prefetch.eu/blog/2020/email-server-extras/
version 9/3/21