Having your own email server looks like a very challenging job for many, but don’t worry. We are here to show you how to build your own mail server on FreeBSD.
What do you need for your mail server?
A server with open port 25. You can get a Cloud server or a Dedicated server. You can get:
OpenSMTPd – the MTA (message transfer agent), which we will use for the tutorial. It will execute the communication process (via the SMTP protocol) with the other mail servers and deliver the incoming emails to the inboxes of the users in our FreeBSD.
Dovecot – This will be the MDA (mail delivery agent) for this guide. This one will be using IMAP or POP3 for delivering the emails to the users.
Spamd – A spam filter. You can use different criteria to filter the emails and organize them in lists.
The idea is:
Outside world ⇒ Firewall ⇒ spamd ⇒ OpenSMTPD ⇒ Mail boxes of users
Outside world ⇒ Firewall (spamd-allow list) ⇒ OpenSMTPD ⇒ Mail boxes of users
Outside world ⇒ Firewall (IMAP/POP3) ⇒ Dovecot
Outside world ⇒ Firewall (SMTPD submission)
Installing the software for the mail server
The first step will be installing all the necessary software. For that purpose, you need to be an admin (have sudo permissions). You will need to use the following command:
sudo pkg install opensmtpd dovecot spamd
We will need to edit the configuration by adding a few lines to the /etc/rc.conf:
pf_enable=”YES”
pf_rules=”/usr/local/etc/pf.conf”
pflog_enable=”YES”
pflog_logfile=”/var/log/pflog”
obspamd_enable=”YES”
obspamd_flags=”-v”
obspamlogd_enable=”YES”
dovecot_enable=”YES”
FreeBSD firewall configuration
We will be using the PF firewall, so our next step will be to create our configuration in /usr/local/etc/pf.conf. Use the following default configuration:
## Set public interface ##
ext_if=”vtnet0″
## set and drop IP ranges on the public interface ##
martians = “{ 127.0.0.0/8, 192.168.0.0/16, 172.16.0.0/12, \
10.0.0.0/8, 169.254.0.0/16, 192.0.2.0/24, \
0.0.0.0/8, 240.0.0.0/4 }”
table <spamd> persist
table <spamd-allow> persist
# Allowed webmail services
table <webmail> persist file “/usr/local/etc/pf.webmail.ip.conf”
## Skip loop back interface – Skip all PF processing on interface ##
set skip on lo
## Set the interface for which PF should gather statistics such as bytes in/out and packets passed/blocked ##
set loginterface $ext_if
# Deal with attacks based on incorrect handling of packet fragments
scrub in all
# Pass spamd allow list
pass quick log on $ext_if inet proto tcp from <spamd-allow> to $ext_if port smtp \
-> 127.0.0.1 port 25
# Pass webmail servers
rdr pass quick log on $ext_if inet proto tcp from <gmail> to $ext_if port smtp \
-> 127.0.0.1 port 25
# pass submission messages.
pass quick log on $ext_if inet proto tcp from any to $ext_if port submission modulate state
# Pass unknown mail to spamd
rdr pass log on $ext_if inet proto tcp from {!<spamd-allow> <spamd>} to $ext_if port smtp \
-> 127.0.0.1 port 8025
## Blocking spoofed packets
antispoof quick for $ext_if
## Set default policy ##
block return in log all
block out all
# Drop all Non-Routable Addresses
block drop in quick on $ext_if from $martians to any
block drop out quick on $ext_if from any to $martians
pass in inet proto tcp to $ext_if port ssh
# Allow Ping-Pong stuff. Be a good sysadmin
pass inet proto icmp icmp-type echoreq
# Open up imap/pop3 support
pass quick on $ext_if proto tcp from any to any port {imap, imaps, pop3, pop3s} modulate state
# Allow outgoing traffic
pass out on $ext_if proto tcp from any to any modulate state
pass out on $ext_if proto udp from any to any keep state
Here we define a $ext_if variable for the vtnet0 device to use later on. We also define invalid IP addresses that should not be allowed on the external interface.
There are two tables spamd and spamd-allow that appear as default from the spamd. We also have another table webmail. Here we will add big webmail providers.
To view a table, you can use the command:
pfctl -t tablename -T show
The idea behind it is to skip processing on the local interface, enable statistics on the external interface and scrub incoming packets.
The next step is to manage the outgoing traffic to spamd or OpenSMTPd.
The syntax of the redirect rules is the old PF syntax. You can find some things strange. All the traffic that we receive on SMTP from hosts in the spamd table or not in the spamd-allow table will be redirected through the spamd deamon which will manage it. There are a few pass-through rules for receiving emails. We allow messages from the IP addresses listed in the spam-allow and the webmail tables to the OpenSMTPd. And we will accept messages on port 587 (submission port).
The next steps are a few housekeeping rules, for establishing default policy, and accepting SSH and ICMP messages.
Then pass IMAP and POP3 on our external interface to access Dovecot.
The following step is to allow all outgoing traffic.
Start firewall PF:
sudo service pf start
Done with the firewall.
OpenSMTPd
Configuring the OpenSMTPd is easy. It is just 14 lines of configuration:
#This is the smtpd server system-wide configuration file.
# See smtpd.conf(5) for more information.
ext_if=vtnet0
# If you edit the file, you have to run “smtpctl update table aliases”
table aliases file:/etc/mail/aliases
table domains file:/etc/mail/domains
# Keys
pki mail.example.com key “/usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem”
pki mail.example.com certificate “/usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem”
# If you want to listen on multiple subdomains (e.g. mail.davidlenfesty) you have to add more lines
# of keys, and more lines of listeners
# Listen for local SMTP connections
listen on localhost hostname mail.example.com
# listen for filtered spamd connections
listen on lo0 port 10026
# Listen for submissions
listen on $ext_if port 587 tls-require auth pki mail.example.com tag SUBMITTED
# Accept mail from external sources.
accept from any for domain <domains> alias <aliases> deliver to maildir “~/mail”
accept for local alias <aliases> deliver to maildir “~/mail”
accept from local for any relay tls
accept tagged SUBMITTED for any relay tls
First, we define the external interface, tables, aliases, and domains. After that, we go to the SSL certificate for the domain.
We listen to the localhost for the domain in the example – mail.example.com. We then listen to the filtered spamd messages and these from the external interface.
Last, we listen to these on port 587 and check their authentication.
We then go to the accept setting where we are able to accept any messages from the domains in the domain table to be delivered to the maildir (home directory). We accept all local connections for the local mailboxes and relay the messages in order to send emails.
Aliases
FreeBSD ships with a default alias file /etc/mail/aliases in the following format:
vuser1: user1
vuser2: user1
vuser3: user1
vuser4: user2
These will be the different mailboxes. You can create local users who will have mailboxes on the server.
Domains
Creating a domain file on FreeBSD is not hard:
# Domains
example.com
mail.example.com
smtp.example.com
It is a simple plain text file that you want to listen to on a new line. The “#” symbol is for you to make a comment.
SSL Certificates
You can use self-signed or signed certificates. You can use Let’s Encrypt to get a free one.
Let’s install the certbot program.
sudo pkg install py-certbot
After that, go to your certificate, but make sure that you have port 80 opened. In your filtering rules add the following lines in /usr/local/etc/pf.conf:
pass quick on $ext_if from any to any port http
Then run
pfctl -f /usr/local/etc/pf.conf
to reload the set of rules.
Now run the command for any domain that you wish to get a certificate for:
certbot certonly --standalone -d mail.example.com
You can make crontab run certbot renew every 6 months so you always have a certificate.
Then for every relevant domain, you can modify the lines to point to the correct key file:
pki mail.example.com key “/usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem”
pki mail.example.com certificate “/usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem”
Edit the securities:
sudo chmod 700 /usr/local/etc/letsencrypt/archive/mail.example.com/*
You will have to do this for each original key file or else OpenSMTPd won’t open them.
After that, we can start the service:
sudo service smtpd start
Configuring spamd
We’ll use spamd to block spam from known sources. We will greylist certain connections. Spammers won’t try to resend the mail after an error while normal people will.
Run the following to mount fdescfs:
mount -t fdescfs null /dev/fd
Add this line to /etc/fstab:
fdescfs /dev/fd fdescfs rw 0 0
The default configuration file (/usr/local/etc/spamd/spamd.conf.sample) will work fine. You can edit it to add new sources:
sudo cp /usr/local/etc/spamd/spamd.conf.sample /usr/local/etc/spamd/spamd.conf
Now start the service with the following:
sudo service obspamd start
Spamd is now ready to work.
Enabling Webmail Services
One problem with greylisting is that big mail services (like Google) will send mails from different servers for different messages. We will need to allow the IP address ranges for these big webmail services. This is the part of the webmail able in the PF configuration.
To add an IP address range in the webmail table you can run the following command:
pfctl -t webmail -T add 192.0.2.0/24
Dovecot
If you want users to have a more comfortable experience you will need an MDA with IPAM/POP3 support. We have chosen Dovecot in this case because it is easy to use and configure. Copy the default configuration:
cd /usr/local/etc/dovecot
cp -R example-config/* ./
The configuration is made up of quite a few different files. To see the differences between your configuration and the dovecot defaults, run the command below:
sudo doveconf -n
Here’s an example of a working configuration:
# 2.3.2.1 (0719df592): /usr/local/etc/dovecot/dovecot.conf
# OS: FreeBSD 11.2-RELEASE amd64
# Hostname: mail.example.com
hostname = mail.example.com
mail_location = maildir:~/mail
namespace inbox {
inbox = yes
location =
mailbox Archive {
auto = create
special_use = \Archive
}
mailbox Archives {
auto = create
special_use = \Archive
}
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = create
autoexpunge = 60 days
special_use = \Junk
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
mailbox “Sent Mail” {
auto = no
special_use = \Sent
}
mailbox “Sent Messages” {
auto = no
special_use = \Sent
}
mailbox Spam {
auto = no
special_use = \Junk
}
mailbox Trash {
auto = no
autoexpunge = 90 days
special_use = \Trash
}
prefix =
separator = /
}
passdb {
args = imap
driver = pam
}
ssl = required
ssl_cert = </usr/local/etc/letsencrypt/live/mail.example.com/fullchain.pem
ssl_dh = </usr/local/etc/dovecot/dh.pem
ssl_key = </usr/local/etc/letsencrypt/live/mail.example.com/privkey.pem
userdb {
driver = passwd
}
Most config files will be in conf.d.
The important ones are 10-auth.conf, 10-mail.conf, and 10-ssl.conf.
You can configure the different mailboxes for different users in 15-mailboxes.conf. This configuration works fine for most people, but you can modify it for your particular needs.
Authentication
Most default settings will be correct. If you want the system users to authenticate, you will have to edit 10-auth.conf.
Remove the comment on the following line:
!include auth-system.conf.ext
Encryption
We have to generate Diffie-Hellman parameters:
sudo nohup openssl dhparam -out /usr/local/etc/dovecot/dh.pem
Note: This will take a long time to run. Much longer than you might expect.
Now that it is done, start Dovecot:
sudo service dovecot start
Conclusion
Now we have a fully functional, safe, and spam-protected email server on a FreeBSD OS. You can further reduce the spam with additional software, but this configuration will be enough for most organizations like this.