Opensmtpd

Here is how I set up my independent mail server. It is based on the old (now deleted) OpenSMTPd FAQ.

Install

Make sure to install opensmtpd-extras:

$ doas pkg_add opensmtpd-extras

Configuration

In my /etc/mail/smtpd.conf:

# PKI for TLS
pki mail.ircnow.org cert "/etc/ssl/ircnow.org.fullchain.pem"
pki mail.ircnow.org key "/etc/ssl/private/ircnow.org.key"

# tables setup
table aliases file:/etc/mail/aliases
table domains file:/etc/mail/domains
table passwd passwd:/etc/mail/passwd
table virtuals file:/etc/mail/virtuals
table hosts file:/etc/mail/hosts
table spammers file:/etc/mail/spammers

# Blocks junk mail
filter check_rdns phase connect match !rdns junk
filter check_fcrdns phase connect match !fcrdns junk
filter check_spammers phase connect match src <spammers> junk

# listeners
ipv4 = "192.168.1.1"
ipv6 = "2001:db8::"

# listeners
listen on lo0 mask-src
listen on lo0 port 10028 tag DKIM mask-src
listen on $ipv4 port 25 tls pki mail.ircnow.org mask-src filter { check_rdns check_fcrdns } hostname ircnow.org
listen on $ipv6 port 25 tls pki mail.ircnow.org mask-src filter { check_rdns check_fcrdns } hostname ircnow.org
listen on $ipv4 port 587 tls-require pki mail.ircnow.org auth <passwd> mask-src filter { check_rdns check_fcrdns } hostname ircnow.org
listen on $ipv6 port 587 tls-require pki mail.ircnow.org auth <passwd> mask-src filter { check_rdns check_fcrdns } hostname ircnow.org

action "lmtp" lmtp "/var/dovecot/lmtp" rcpt-to virtual <virtuals>
action "relay" relay
action "relay_dkim" relay host smtp://127.0.0.1:10027

# If mail is for any of our domains, pass it to dovecot
match from any for domain <domains> action "lmtp"

# If mail is tagged with DKIM, relay it out
match tag DKIM for any action "relay"

# If mail comes from known good hosts or has been authenticated, relay it to dkimproxy_out
match from src <hosts> for any action "relay_dkim"
match auth from any for any action "relay_dkim"

A single user vmail will receive mail for all virtual users:

$ doas useradd -m -g =uid -c "Virtual Mail" -d /var/vmail -s /sbin/nologin vmail

The /etc/passwd file will contain a line similar to this:

vmail:*:1000:1000:Virtual Mail:/var/vmail:/sbin/nologin

/var/vmail is used to store virtual users' maildir folders. It will be managed by dovecot, which receives mail via LMTP.

Adding users

At the bottom of /etc/mail/aliases, add these lines:

vmail:    /dev/null
root:   admin@ircnow.org
jrmu:   jrmu@ircnow.org
username:   username@ircnow.org

Add one line for each user.

Create a new file /etc/mail/virtuals and add these lines:

admin@ircnow.org        vmail
jrmu@ircnow.org      vmail
username@ircnow.org     vmail

A whitelist of known good senders goes into /etc/mail/hosts:

localhost
192.168.1.1
2001:db8::

For /etc/mail/spammers, create a blank file.

The mail sender's hostname goes in /etc/mail/mailname:

mail.ircnow.org

The list of domains you send mail for go in /etc/mail/domains:

ircnow.org
mail.ircnow.org

In /etc/mail/passwd, we have a list of colon-separated user credentials:

admin@ircnow.org:$2b$10$h5itbhzs73T4jsHAj9YX6Tf63yRatAquGBxoCX67wyekhCH4ZqioD6lKh::::::userdb_quota_rule=*:storage=1G
jrmu@ircnow.org:$2b$10$h5itbhzs73T4jsHAj9YX6Tf63yRatAquGBxoCX67wyekhCH4ZqioD6lKh::::::userdb_quota_rule=*:storage=1G
username@ircnow.org:$2b$10$h5itbhzs73T4jsHAj9YX6Tf63yRatAquGBxoCX67wyekhCH4ZqioD6lKh::::::userdb_quota_rule=*:storage=1G

WARNING: Some special characters like $, when used in passwords, will cause issue with opensmtpd. To be safe, you may want to use alphanumeric characters only for your password.

Make sure to set the proper permissions:

$ doas chown -R _smtpd:_dovecot /etc/mail/
$ doas chmod o-rx /etc/mail/

Spammers

In /etc/mail/spammers, we have IP addresses separated by newlines.

IMAP and POP3 via dovecot; mail signing via dkimproxy

Take a look at the sample dovecot setup for IMAP and POP3, and the sample dkimproxy setup for mail signing.

There are some additional steps for how to add a new user here:

Based on: https://cvsweb.openbsd.org/cgi-bin/cvsweb/~checkout~/www/opensmtpd/faq/Attic/example1.html?rev=1.14

To get hashes:

$ smtpctl encrypt

Troubleshooting

Sometimes OpenSMTPD may end up in an inconsistent state. This can happen due to a misconfiguration. One symptom is you see this error:

smtpd[]: pony express: smtpd: socket: Too many open files

To fix this, you can delete all the temporary files inside OpenSMTPD. WARNING: this will delete any messages in the queue:

$ doas rcctl stop smtpd
$ doas rm -r /var/spool/smtpd/queue/*
$ doas rm -r /var/spool/smtpd/offline/*

At times, opensmtpd may be unable to connect because outgoing packets are being filtered. For example, suppose you are trying to send a letter to yahoo, but you get errors similar to following, showing a connection timeout:

smtpd[]: smtp-out: Enabling route [] <-> 67.195.204.77 (mtaproxy1.free.mail.vip.bf1.yahoo.com)
smtpd[]: smtp-out: Enabling route [] <-> 67.195.228.106 (mtaproxy2.free.mail.vip.gq1.yahoo.com)
smtpd[]: mta error reason=Connection timeout
smtpd[]: smtp-out: Disabling route [] <-> 104.47.55.33 (104.47.55.33) for 15s

An easy way to test if your packets are being filtered is:

$ dig -t mx yahoo.com
;; ANSWER SECTION:
yahoo.com.              395     IN      MX      1 mta6.am0.yahoodns.net.
yahoo.com.              395     IN      MX      1 mta5.am0.yahoodns.net.
yahoo.com.              395     IN      MX      1 mta7.am0.yahoodns.net.
$ nc mta5.am0.yahoodns.net 25

If you get no response, then outgoing packets to port 25 are being blocked (often due to firewalls by your VPS provider to block spam). If mail is working, you should see a 220 reply:

$ nc mta5.am0.yahoodns.net 25
220 mtaproxy511.free.mail.ne1.yahoo.com ESMTP ready

It is also possible that TLS is being dropped by the firewall. You can test using openssl:

$ openssl s_client -starttls smtp -connect mta5.am0.yahoodns.net:25
CONNECTED(00000003)
depth=2 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert High Assurance EV Root CA
verify return:1
depth=1 C = US, O = DigiCert Inc, OU = www.digicert.com, CN = DigiCert SHA2 High Assurance Server CA
verify return:1
depth=0 C = US, ST = California, L = Sunnyvale, O = Oath Inc, CN = *.am0.yahoodns.net
...
250 STARTTLS

You should see the entire SSL cert plus 250 STARTTLS reply. If you see the response hang at any point (eg, it returns CONNECTED(00000003) and nothing else), then TLS on port 25 is being filtered.

If you see this warning message in /var/log/maillog:

Dec  6 03:44:17 smtpd[]: info: OpenSMTPD 6.7.0 starting                                 
Dec  6 03:44:17 smtpd[]: pony express: smtpd: socket: Too many open files               
Dec  6 03:44:17 smtpd[]: warn: lost child: pony express exited abnormally               

This is due to having too many IP addresses that opensmtpd tries to bind to. This happens when you have a rule that says listen on egress:

listen on egress port 25 tls pki fruit.ircnow.org mask-src filter { check_rdns check_fcrdns } listen on egress port 587 tls-require pki fruit.ircnow.org auth <passwd> mask-src filter { check_rdns check_fcrdns }

These two lines mean that opensmtpd will listen to all available ip address, including the hundreds of IPv6 addresses you may have in /etc/hostname.vio0 and ifconfig vio0. To fix this, you must specify the IP addresses you want to listen to:

# listeners
ipv4 = "192.168.1.1"
ipv6 = "2001:db8::"

...

# listeners
listen on lo0 mask-src
listen on lo0 port 10028 tag DKIM mask-src
listen on $ipv4 port 25 tls pki mail.ircnow.org mask-src filter { check_rdns check_fcrdns } hostname ircnow.org
listen on $ipv6 port 25 tls pki mail.ircnow.org mask-src filter { check_rdns check_fcrdns } hostname ircnow.org
listen on $ipv4 port 587 tls-require pki mail.ircnow.org auth <passwd> mask-src filter { check_rdns check_fcrdns } hostname ircnow.org
listen on $ipv6 port 587 tls-require pki mail.ircnow.org auth <passwd> mask-src filter { check_rdns check_fcrdns } hostname ircnow.org

Open Mail Relay

If all your email is being marked as spam, check /var/log/maillog . If you see a message like the following:

Jan 8 11:00:29 smtpd[39035]: 83bd6b3b1669649f mta delivery evpid=a8d16cd2144222fa from=<spammer@example.com> to=<victim@example.com> rcpt=<-> source="192.168.0.1" relay="10.0.0.1 (10.0.0.1)" delay=16h2s result="TempFail" stat="451 4.7.650 The mail server [192.168.0.1] has been temporarily rate limited due to IP reputation. For e-mail delivery information, see https://postmaster.example.com (S843)"

Then your server is being exploited as an open mail relay! Please follow the guide to fix it.