Let's set up a mail server with dkim signing and basic spam checks: ## Before we begin Read the the man pages for [opensmtpd](https://man.openbsd.org/smtpd), [smtpd.conf](/https://man.openbsd.org/smtpd.conf), and [[smtpctl](/https://man.openbsd.org/smtpctl). Read the the man pages for [opensmtpd](https://man.openbsd.org/smtpd), [smtpd.conf](/https://man.openbsd.org/smtpd.conf), and [[smtpctl](/https://man.openbsd.org/smtpctl). Read the [free OpenSMTPd book](/https://github.com/poolpOrg/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](dns/overview) and [[set up your name server](/nsd/configure). want to read up on [DNS](dns/overview) and [[set up your name server](/nsd/configure). You will need to [add proper DNS records](/DNS/Mail) 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 ## Configuration ### TLS You will want to use [acme-client](/acme-client/configure) to request a TLS public cert and private key in `/etc/ssl/`. Next, we'll create our smtpd configuration file in `/etc/mail/smtpd.conf`: # PKI for TLS pki example.com cert "/etc/ssl/example.com.crt" pki example.com key "/etc/ssl/private/example.com.key" This defines our public and private key pair for TLS encryption. ### Tables Next, we define 5 tables: # tables setup 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 users file:/etc/mail/users The domains table contains a list of domains that our mail server should receive mail on. The passwd table contains a colon-separated list of username/password/disk quota entries. The virtuals file shows which virtual user should handle whose mail. They are written as `key: value` pairs. See [aliases(5)](/https://man.openbsd.org/aliases) for more information. The hosts file contains a list of trusted sending hosts. The users file contains a list of valid sending users. All of these tables will be explained further in the following sections. ### Dealing with Spam # Blocks junk mail 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 _smtpd group _smtpd 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 third filter will sign any email with the DKIM private key. # -d specifies the domain name to sign for; you must replace example.com with your real domain. # -s specifies the selector (in this case mail). # -k specifies the path of the private key. # user and group both specify _smtpd, the user and group that does the signing ### Macros A macro defines a variable that will be replaced with a block of text: # macros ipv4 = "192.168.0.1" ipv6 = "2001:db8::" check = "pki example.com mask-src filter { check_rdns check_fcrdns } hostname example.com" authcheck = "pki example.com auth mask-src senders filter { dkimsign } hostname example.com" Lines 2 and 3 define the IPv4 and IPv6 addresses used for sending and receiving mail. Line 4 tells 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. Line 5 is identical to line 4 except 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. # listeners 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 smtps $authcheck listen on $ipv6 port 465 smtps $authcheck listen on $ipv4 port 587 tls-require $authcheck listen on $ipv6 port 587 tls-require $authcheck Line 2 tells smtpd to listen to the UNIX domain socket and to DKIM sign all emails. Line 3 tells us to listen to the loopback interface and also sign all emails. Lines 4-5 tells 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. Lines 6-7 tells 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](/opensmtpd/openrelay). Lines 8-9 is similar except it's for port 587, which is the SMTP submission port. ### Rules Next we define the actions that opensmtpd can take and how to decide which action to follow: # rules action "lmtp" lmtp "/var/dovecot/lmtp" rcpt-to virtual action "outbound" relay src $ipv4 match from any for domain action "lmtp" match from local for any action "outbound" match from src for any action "outbound" match auth from any for any action "outbound" In line 2, 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. In line 3, we define the action "outbound": we relay (send) the email out. Line 4 defines our first matching rule: any email headed for one of our domains should be handed over to lmtp (handed over to dovecot). Line 5 defines our second matching rule: any email from a local IP address or queue can relay (send) without authentication. Line 6 defines our third matching rule: any email from our trusted `/etc/mail/hosts` file will automatically be relayed (sent) without authentication. Line 7 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 example.com cert "/etc/ssl/example.com.fullchain.pem" pki example.com key "/etc/ssl/private/example.com.key" # tables setup 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 users file:/etc/mail/users # Blocks junk mail 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 _smtpd group _smtpd # macros ipv4 = "192.168.0.1" ipv6 = "2001:db8::" check = "pki example.com mask-src filter { check_rdns check_fcrdns } hostname example.com" authcheck = "pki example.com auth mask-src senders filter { dkimsign } hostname example.com" # listeners 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 smtps $authcheck listen on $ipv6 port 465 smtps $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 action "outbound" relay src $ipv4 match from any for domain action "lmtp" match from local for any action "outbound" match from src 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/virtuals` 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. This is recommended to do so, as daily(8) and other programs will send mails to root. check the mail account linked to root account often! **NOTE**: Make sure to check the mail account linked to root often# [daily(8)](/https://man.openbsd.org/daily) and 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`: localhost 127.0.0.1 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 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 _smtpd:_dovecot /etc/mail/ $ doas chmod -R o-rx /etc/mail/ ## IMAP and POP3 via dovecot To finish the setup, we need to [install and configure dovecot](/dovecot/install). ## DKIM signing We will need to set up [dkim](/DNS/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/* 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 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: ipv4 = "38.81.163.143" ipv6 = "2602:fccf:1:143::" check = "pki example.com filter { check_rdns check_fcrdns } hostname example.com" authcheck = "pki example.com auth filter { dkimsign } hostname example.com" # listeners 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 smtps $authcheck listen on $ipv6 port 465 smtps $authcheck listen on $ipv4 port 587 tls-require $authcheck listen on $ipv6 port 587 tls-require $authcheck ### 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= to= 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](/opensmtpd/openrelay)# Please follow the guide to fix it. ## [Troubleshooting OpenSMTPd](/opensmtpd/troubleshoot)