AdvancedMailServerSetup

Let's set up dovecot & smtpd to allow users to read mail with IMAP & lmtp with rspamd spam filter! you only have two configeration files one for dovecot.conf and one for smtpd.cond!

Installation

$ doas pkg_add dovecot opensmtpd-extras opensmtpd-filter-dkimsign-- opensmtpd-filter-dkimsign-0.5p2 opensmtpd-filter-rspamd-0.1.8p0 opensmtpd-filter-senderscore-0.1.2

Configuration

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

/var/vmail will be used to store virtual users' maildir folders. It will be managed by dovecot, which receives mail via LMTP. other files for smtpd files for /etc/mail/aliases, domains file:/etc/mail/domains, passwd file:/etc/mail/passwd, users file:/etc/mail/users, vusers file:/etc/mail/vusers, or hosts file:/etc/mail/hosts! all these's files are located in /etc/mail folder is part of smtpd.conf setup!

In order to secure our passwords, we need to rearrange file permissions and owners:

$ doas chown -R _dkimsign:_dkimsign /etc/mail/dkim/
$ doas chown _smtpd:_dovecot /etc/mail/passwd
$ doas chmod 770 /etc/mail/dkim/
$ doas chmod 440 /etc/mail/passwd
$ doas find /etc/mail ! -path /etc/mail -exec chmod o-rwx '{}' +

Here is Example /etc/dovecot/dovecot.conf to go by! change your hostname from example to your correct host and ip address's if specified!

protocols = imap lmtp
listen = 198.251.81.119, 2605:6400:10:5bf::, 127.0.0.1

service lmtp {
  user = vmail
}

service imap-login {
  inet_listener imap {
    address = *
    port = 143
  }
  inet_listener imaps {
    address = *
    port = 993
     ssl = yes
  }
  user = _dovecot
  group = _dovecot
  executable = /usr/local/libexec/dovecot/imap-login
}

service auth {
  user = _dovecot
  group = _dovecot
  executable = /usr/local/libexec/dovecot/auth
}

service quota-warning {
  executable = script /usr/local/libexec/dovecot/quota-warning.sh
  unix_listener quota-warning {
  mode = 0600
    user = vmail
    group = vmail
  }
}

quota_full_tempfail = yes
ssl = yes
ssl_cert = </etc/ssl/mail.examplee.com.crt
ssl_key = </etc/ssl/private/mail.example.com.key
ssl_dh=</etc/dovecot/dhparam.pem
ssl_cipher_list = ALL:!LOW:!SSLv2

mail_location = maildir:/var/vmail/%d/%n/Maildir
mail_uid = 1024
mail_gid = 1024

valid_chroot_dirs = /var/vmail
log_path = /var/log/dovecot.err
info_log_path = /var/log/dovecot.info
log_timestamp = "%Y-%m-%d %H:%M:%S "
login_greeting = Dovecot ready.
disable_plaintext_auth = no
auth_verbose = yes
auth_debug = yes
auth_debug_passwords = yes
mail_debug = yes
auth_verbose_passwords=sha1
verbose_ssl=yes


passdb {
  driver = passwd-file
  args = scheme=blf-crypt /etc/dovecot/users.txt
}

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

This tells dovecot to listen to the protocols IMAP, and LMTP. Note: We don't want to support pop3 or submission with dovecot.

It also tells dovecot the public IPs you want it to listen on. Finally, the last block tells dovecot to change to the username vmail to listen for LMTP.

We defines our password database to use blowfish (see blowfish(3) and encrypt(1)).

The second block says that the mail must be read by user ID and group ID vmail, and that all mail will be in the folders /var/vmail/<domain>/<username>.

This again indicates all mail will be in the folders /var/vmail/<domain>/<username>.

You will need to replace example.com with your real domain name.

Please read the instructions in the dovecot README in /usr/local/share/doc/pkg-readmes/dovecot. That file explains that you must add this login class to /etc/login.conf:

dovecot:\
         :openfiles-cur=4096:\
         :openfiles-max=8192:\
         :tc=daemon:

WARNING: You must use tabs and not spaces. If you use spaces in /etc/login.conf, the settings will not work.

NOTE: Allowing more open files than suggested in the README can help if you have many IP addresses.

WARNING: If login.conf.db exists, you will need to rebuild it:

# [ -f /etc/login.conf.db ] && cap_mkdb /etc/login.conf

But it is best to just remove /etc/login.conf.db since it is not required:

$ doas rm /etc/login.conf.db

Starting dovecot

To start dovecot via rcctl:

$ doas rcctl enable dovecot
$ doas rcctl start dovecot

Troubleshooting

Make sure to check /var/log/maillog: for errors too Make sure to check Errors in /var/log/dovecot.err for errors too Make sure to check Info in /var/log/dovecot.info for errors too Make sure to check rspamd in /var/log/rspamd/rspamd.log for errors too

$ openssl s_client -starttls imap -connect username.example.com:143

When starting dovecot, you may find it fails:

$ doas rcctl start dovecot

When this happens, run the rc.d script with debugging turned on:

$ doas /etc/rc.d/dovecot -d start
doing _rc_parse_conf
doing _rc_quirks
dovecot_flags empty, using default ><
doing rc_check
dovecot
doing rc_start
doing _rc_wait start
doing rc_check

doveconf: Fatal: Error in configuration file /etc/dovecot/conf: ssl_cert: Can't open file /etc/ssl/dovecotcert.pem: No such file or directory doing _rc_rm_runfile (failed)

In this case, you can see the error in the logs forgot to write the real path of the cert: /etc/ssl/example.com.crt (where example.com is replaced with my real domain).

Jun  9 01:37:35 jrmu dovecot: auth: Error: passwd-file(jrmu@jrmu.host.oddprotocol.org,125.231.25.80,<aiyNgk/EuHB95xlQ>): stat(/etc/mail/passwd) failed: Permission denied (euid=518(_dovecot) egid=518(_dovecot) missing +x perm: /etc/mail, we're not in group 1003(_mail), dir owned by 95:1003 mode=0750)
Jun  9 01:37:41 jrmu dovecot: auth: Error: passwd-file(jrmu@jrmu.host.oddprotocol.org,125.231.25.80,<aiyNgk/EuHB95xlQ>): stat(/etc/mail/passwd) failed: Permission denied (euid=518(_dovecot) egid=518(_dovecot) missing +x perm: /etc/mail, we're not in group 1003(_mail), dir owned by 95:1003 mode=0750)

Let's set up a mail server with dkim signing and basic rspamd checks:

Before we begin

Read the the man pages for opensmtpd, smtpd.conf, and smtpctl.

Read the free OpenSMTPd book by the author of OpenSMTPd

DNS

Running a mail server requires proper DNS records. If you have not already, you will want to read up on DNS and set up your name server.

You will need to add proper DNS records to your domain and make sure they work.

Install

Opensmtpd is part of OpenBSD base, but we will also want to install some opensmtpd-related packages and dovecot:

$ doas pkg_add opensmtpd-extras opensmtpd-filter-dkimsign-- dovecot-2.3.21.1v0 rspamd-3.9.1

If prompted to choose a version for dovecot, choose the one without gssapi gssapi.

Make sure to read the dkimsign filter README at /usr/local/share/doc/pkg-readmes/opensmtpd-filter-dkimsign.

Configuration

TLS

You will want to use acme-client to request a TLS public cert and private key in /etc/acme-client.conf.

#
# $OpenBSD: acme-client.conf,v 1.5 2023/05/10 07:34:57 tb Exp $
#
authority letsencrypt {
	api url "https://acme-v02.api.letsencrypt.org/directory"
	account key "/etc/acme/letsencrypt-privkey.pem"
}

authority letsencrypt-staging {
	api url "https://acme-staging-v02.api.letsencrypt.org/directory"
	account key "/etc/acme/letsencrypt-staging-privkey.pem"
}

authority buypass {
	api url "https://api.buypass.com/acme/directory"
	account key "/etc/acme/buypass-privkey.pem"
	contact "mailto:me@example.com"
}

authority buypass-test {
	api url "https://api.test4.buypass.no/acme/directory"
	account key "/etc/acme/buypass-test-privkey.pem"
	contact "mailto:me@example.com"
}

domain example.com {
#	alternative names { }
	domain key "/etc/ssl/private/example.com.key"
	domain full chain certificate "/etc/ssl/example.com.crt"
	sign with letsencrypt
}
domain bnc.example.com {
#	alternative names { }
	domain key "/etc/ssl/private/bnc.example.com.key"
	domain full chain certificate "/etc/ssl/bnc.example.com.crt"
	sign with letsencrypt
}

domain webirc.example.com {
#	alternative names { }
	domain key "/etc/ssl/private/webirc.example.com.key"
	domain full chain certificate "/etc/ssl/webirc.example.com.crt"
	sign with letsencrypt
}

domain webmail.example.com {
#	alternative names { }
	domain key "/etc/ssl/private/webmail.example.com.key"
	domain full chain certificate "/etc/ssl/webmail.example.com.crt"
	sign with letsencrypt
}

domain mail.example.com {
#	alternative names { }
	domain key "/etc/ssl/private/mail.example.com.key"
	domain full chain certificate "/etc/ssl/mail.example.com.crt"
	sign with letsencrypt
}
domain wiki.example.com {
#	alternative names { }
	domain key "/etc/ssl/private/wiki.example.com.key"
	domain full chain certificate "/etc/ssl/wiki.example.com.crt"
	sign with letsencrypt
}

Next, we'll create our smtpd configuration file in /etc/mail/smtpd.conf: Below is a example smtpd.conf file below! make sure to change the host and ip address's to your vm's ipv4 and ipv6 address!

# PKI for TLS
pki mail.example.com cert "/etc/ssl/mail.example.com.crt" # path to SSL certificate
pki mail.example.com key "/etc/ssl/private/mail.example.com.key" # path to private key

# tables setup
table aliases file:/etc/mail/aliases
table domains file:/etc/mail/domains
table passwd file:/etc/mail/passwd
table users file:/etc/mail/users
table vusers file:/etc/mail/vusers
table hosts file:/etc/mail/hosts

# Blocks junk mail
filter rspamd proc-exec "filter-rspamd"
filter check_rdns phase connect match !rdns junk
filter check_fcrdns phase connect match !fcrdns junk
filter "dkimsign" proc-exec "filter-dkimsign -d example.com -s mail -k /etc/mail/dkim/private.key" user _dkimsign group _dkimsign

# macros
ipv4 = "publicipv4"
ipv6 = "publicipv6"
check = "pki mail.example.com filter { check_rdns check_fcrdns rspamd } hostname example.com"
authcheck = "pki mail.example.com auth <passwd> mask-src senders <users> filter { check_rdns check_fcrdns dkimsign rspamd } hostname example.com"

# listeners
listen on socket filter { dkimsign rspamd }
listen on lo0 filter { dkimsign rspamd }
# listen on socket filter "dkimsign"
# listen on lo0 filter "dkimsign"
listen on $ipv4 port 25 tls $check
listen on $ipv6 port 25 tls $check 
listen on $ipv4 port 465 tls-require $authcheck 
listen on $ipv6 port 465 tls-require $authcheck 
listen on $ipv4 port 587 tls-require $authcheck 
listen on $ipv6 port 587 tls-require $authcheck

# rules
action "lmtp" lmtp "/var/dovecot/lmtp" rcpt-to virtual <vusers>
action "outbound" relay src $ipv4

match from any for domain <domains> action "lmtp"
match from src <hosts> for any action "outbound"
match from local for any action "outbound"
match auth from any for any action "outbound" 

The domains table contains a list of domains that our mail server should receive mail on. domains file:/etc/mail/domains

Note: Do not add domains that your mail server does not directly serve (for example, do not add domains you intend to forward mail to). If you add them by mistake, the mail server will not forward the mail properly.

The passwd table contains a colon-separated list of username/password/disk quota entries. passwd file for smptpd /etc/mail/passwd

The vusers file shows which virtual user should handle whose mail. They are written as key: value pairs. See aliases(5) for more information. vusers file:/etc/mail/vusers

The hosts file contains a list of trusted sending hosts. hosts file:/etc/mail/hosts

The users file contains a list of valid sending users. users file:/etc/mail/users

All of these tables will be explained further in the following sections.

Dealing with Spam

The first filter will check if the sender has an rdns entry. If not, the mail will be labeled as junk.

The second filter will check if the sender's forward and reverse dns entry match. If not, the mail will be labeled as junk.

The first filter will check if the sender has an rspam entry. If not, the mail will be labeled as junk.

The fourth filter will sign any email with the DKIM private key.

  1. -d specifies the domain name to sign for; you must replace example.com with your real domain.
  2. -s specifies the selector (in this case mail).
  3. -k specifies the path of the private key.
  4. user and group both specify _dkimsign, the user and group that does the signing

Macros

A macro defines a variable that will be replaced with a block of text:

define the IPv4 and IPv6 addresses used for sending and receiving mail.

opensmtpd to use the public/private keys we defined earlier for example.com. We mask the sender's source (the from part of the Received header). We also apply two filters to check for proper forward and reverse confirmed DNS entries. Finally, we indicate that the sending hostname must be example.com instead of the default server name.

it requires authentication with the password file and it checks if the sender is allowed.

Listeners

The listeners tell us what network interfaces, IP addresses, and ports to listen on.

smtpd to listen to the UNIX domain socket and to DKIM sign all emails. tells us to listen to the loopback interface and also sign all emails.

smtpd to listen on the IPv4 and IPv6 address on port 25, to provide TLS if supported but to offer plaintext as a fallback. Only basic checking is done.

smtpd to listen on the IPv4 and IPv6 address on port 465, for SMTPS. TLS encryption is required and authentication checking is forced because this socket can be used for sending mail to other servers. We want to avoid an open mail relay.

Rules

we define the actions that opensmtpd can take and how to decide which action to follow:

we define the action "lmtp": we pass the mail to dovecot to handle using the Local Mail Transfer Protocol (LMTP). The actual recipient will be translated using the virtuals table.

we define the action "outbound": we relay (send) the email out.

we defines matching rule: any email headed for one of our domains should be handed over to lmtp (handed over to dovecot).

we defines matching rule: any email from a local IP address or queue can relay (send) without authentication.

we defines matching rule: any email from our trusted /etc/mail/hosts file will automatically be relayed (sent) without authentication.

we defines our last matching rule: any email that has been properly authenticated will be relayed (sent).

Complete configuration file

Here is the entire configuration file in /etc/mail/smtpd.conf:

# PKI for TLS
pki mail.example.com cert "/etc/ssl/mail.example.com.crt" # path to SSL certificate
pki mail.example.com key "/etc/ssl/private/mail.example.com.key" # path to private key

# tables setup
table aliases file:/etc/mail/aliases
table domains file:/etc/mail/domains
table passwd file:/etc/mail/passwd
table users file:/etc/mail/users
table vusers file:/etc/mail/vusers
table hosts file:/etc/mail/hosts

# Blocks junk mail
filter rspamd proc-exec "filter-rspamd"
filter check_rdns phase connect match !rdns junk
filter check_fcrdns phase connect match !fcrdns junk
filter "dkimsign" proc-exec "filter-dkimsign -d example.com -s mail -k /etc/mail/dkim/private.key" user _dkimsign group _dkimsign

# macros
ipv4 = "publicipv4"
ipv6 = "publicipv6"
check = "pki mail.example.com filter { check_rdns check_fcrdns rspamd } hostname example.com"
authcheck = "pki mail.example.com auth <passwd> mask-src senders <users> filter { check_rdns check_fcrdns dkimsign rspamd } hostname example.com"

# listeners
listen on socket filter { dkimsign rspamd }
listen on lo0 filter { dkimsign rspamd }
# listen on socket filter "dkimsign"
# listen on lo0 filter "dkimsign"
listen on $ipv4 port 25 tls $check
listen on $ipv6 port 25 tls $check 
listen on $ipv4 port 465 tls-require $authcheck 
listen on $ipv6 port 465 tls-require $authcheck 
listen on $ipv4 port 587 tls-require $authcheck 
listen on $ipv6 port 587 tls-require $authcheck

# rules
action "lmtp" lmtp "/var/dovecot/lmtp" rcpt-to virtual <vusers>
action "outbound" relay src $ipv4

match from any for domain <domains> action "lmtp"
match from src <hosts> for any action "outbound"
match from local for any action "outbound"
match auth from any for any action "outbound"

Configuring Virtual Users

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

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

Adding users

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

root   admin@example.com
admin@example.com        vmail
username@example.com      vmail

Now, any mail sent to root will get forwarded to admin@example.com.

NOTE: Make sure to check the mail account linked to root often! other programs will send mails to root.

You can optionally add one line for each user to provide aliases.

For each new user account, you will want to create a new line.

You'll also need to create one line for each user in /etc/mail/users:

admin@example.com:	admin@example.com
username@example.com:	username@example.com

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

192.168.1.1
2001:db8::

Replace IP addresses 192.168.1.1 and 2001:db8:: with your server's real IP addresses.

In /etc/mail/mailname, put in the name you want to use for your mail server. This is very important for passing anti-spam checks:

example.com

The list of domains this mail server can receive emails for will go inside /etc/mail/domains:

example.com
mail.example.com

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

admin@example.com:$2b$10$h5itbhzs73T4jsHAj9YX6Tf63yRatAquGBxoCX67wyekhCH4ZqioD6lKh::::::userdb_quota_rule=*:storage=1G
username@example.com:$2b$10$h5itbhzs73T4jsHAj9YX6Tf63yRatAquGBxoCX67wyekhCH4ZqioD6lKh::::::userdb_quota_rule=*:storage=1G

Each field is separated with a colon.

The first field tells you the username. Note that usernames include a domain -- this is because you might host mail for multiple domains. So, when logging in to the mail server, your mail client must be of the format username@example.com.

The second field is the password hash. To generate a hash, you can run encrypt:

$ encrypt specificpassword

Type your password, then press enter. Type ctrl+d to quit.

smtpctl encrypt also does the same thing:

$ smtpctl encrypt

WARNING: Special characters like $, when used in passwords, may cause issues with your mail client or with opensmtpd. To be safe, you might want to use only alphanumeric characters for your password. You can increase the length of the password for more security.

The last field sets how much data storage each user is allowed. The default here is 1 gigabyte.

File Permissions

Make sure to set the proper permissions:

$ doas chown -R _dkimsign:_dkimsign /etc/mail/dkim/
$ doas chown _smtpd:_dovecot /etc/mail/passwd
$ doas chmod 770 /etc/mail/dkim/
$ doas chmod 440 /etc/mail/passwd
$ doas find /etc/mail ! -path /etc/mail -exec chmod o-rwx '{}' +

Note that you want to keep the ownership of any files that are listed in /etc/mtree/special the same, and the file permissions must be at least as strict as those. Otherwise the security(8) script run by daily(8) will flag those files and mail you about them.

IMAP via dovecot

To finish the setup, we need to install and configure dovecot.

DKIM signing

We will need to set up DKIM]to have the mail properly signed.

Troubleshooting

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/*

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 { dkimsign }

These two lines mean that opensmtpd will listen to all available ip addresses, 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:

Open Mail Relay

If all your email is being marked as spam, check /var/log/maillog, /var/log/dovecot.err, var/log/dovecot.info or /var/log/rspamd/rspamd.log . 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.

Troubleshooting OpenSMTPd