Running a mail server on NetBSD for fun
NetBSD email installationThis is the way I set up a mail server in NetBSD. It uses base Postfix, Dovecot and other packages from pkgsrc. Beware that I’m not an expert on this topic, and security issues could be there. Also, this is suited for personal use. I wouldn’t use this for enterprise.
In the enterprise, usually there’s no “mail server” but a group of servers with services like MTA (message transfer agent), MSA (message submission agent) and MDA (message delivery agent). So, there’s a “mail system” instead of just a single server.
Personal use
I’m running a personal e-mail server. It’s fun and have some advantages, like creating whatever aliases I want, and having control over my data. And, of course, learning a thing or two in the process.
Running e-mail is not that complex, but require a solid knowledge in different areas of sysadmin stuff: DNS, TLS, networking, and of course SMTP and mail stuff. There’s lot of moving parts and it’s easy to misconfigure something. For that reason, I’m probably forgetting something here.
I’m using an external smart-host to send to Big E-Mail providers, so I’m not flagged as spam. But that part is optional, and I can stop using this third-party service in any moment without losing data or control.
In this simple setup, I’m using Postfix, Dovecot, Spamassasin and OpenDKIM.
Dovecot authenticates users using local /etc/passwd file.
No SQL database is needed, but this only supports one domain.
Also, e-mail users are also UNIX shell users, which can be a huge security issue. Just not too concerning in my homelab.
Why?
This is part of my quest to learn NetBSD. I could easily deploy some docker-based solution. Or follow yet another Linux-based tutorial on the topic.
By manually deploying some services, like e-mail, I feel that I understand the tools I’m working with. Then I can work on automation of the task. That’s why this is not an Ansible playbook or a shell script.
In this post I’m not explaining Postfix or Dovecot configuration. I recommend reading the documentation of those projects. The goal of this post is follow my way to manually deploy an e-mail server, which helps to understand how to do stuff on NetBSD.
Installation of packages
Postfix comes with base NetBSD. The rest needs to be installed via pkgin.
pkgin install spamassassin spamass-milter dovecot dovecot-pigeonhole opendkim
Postfix configuration
The content of /etc/postfix/master.cf looks like this:
pickup unix n - n 60 1 pickup
cleanup unix n - n - 0 cleanup
qmgr unix n - n 300 1 qmgr
tlsmgr unix - - n 1000? 1 tlsmgr
rewrite unix - - n - - trivial-rewrite
bounce unix - - n - 0 bounce
defer unix - - n - 0 bounce
trace unix - - n - 0 bounce
verify unix - - n - 1 verify
flush unix n - n 1000? 0 flush
proxymap unix - - n - - proxymap
proxywrite unix - - n - 1 proxymap
smtp unix - - n - - smtp
relay unix - - n - - smtp
-o syslog_name=postfix/$service_name
showq unix n - n - - showq
error unix - - n - - error
retry unix - - n - - error
discard unix - - n - - discard
local unix - n n - - local
virtual unix - n n - - virtual
lmtp unix - - n - - lmtp
anvil unix - - n - 1 anvil
scache unix - - n - 1 scache
postlog unix-dgram n - n - 1 postlogd
smtp inet n - y - - smtpd
-o content_filter=spamassassin
submission inet n - y - - smtpd
-o syslog_name=postfix/submission
-o smtpd_tls_security_level=encrypt
-o smtpd_tls_auth_only=yes
-o smtpd_enforce_tls=yes
-o smtpd_client_restrictions=permit_sasl_authenticated,reject
-o smtpd_sender_restrictions=reject_sender_login_mismatch
-o smtpd_recipient_restrictions=permit_sasl_authenticated,reject_unauth_destination
smtps inet n - y - - smtpd
-o syslog_name=postfix/smtps
-o smtpd_tls_wrappermode=yes
-o smtpd_sasl_auth_enable=yes
spamassassin unix - n n - - pipe
user=spamd argv=/usr/pkg/bin/spamc -f -e /usr/sbin/sendmail -oi -f ${sender} ${recipient}
The important part is that submission service requires TLS connections from client and reject non-authenticated e-mail, so we don’t become an open relay.
Domain Configuration
We can either edit /etc/postfix/main.cf, or set config values with postconf -e.
I’d rather edit the file and reload the service, but for scripting postconf can be useful.
Here we define the domain name.
Replace example.com with a proper name.
postconf -e "myhostname = example.com"
postconf -e "mail_name = example.com"
postconf -e "mydomain = example.com"
postconf -e 'mydestination = $mydomain, mail, localhost.localdomain, localhost, localhost.$mydomain'
In most places a host named like mail.example.com is used, even if the mail domain is example.com
In those cases myhostname should be mail.example.com, but mail_name and mydomain should be the shorter example.com.
TLS Configuration
I’m using Let’s Encrypt to get my TLS certificates.
That setup is not covered here.
Let’s assume that you already have a TLS certificate for example.com.
postconf -e "smtpd_tls_key_file=/usr/pkg/etc/letsencrypt/live/example.com/privkey.pem"
postconf -e "smtpd_tls_cert_file=/usr/pkg/etc/letsencrypt/live/example.com/fullchain.pem"
postconf -e 'smtpd_tls_security_level = may'
postconf -e 'smtp_tls_security_level = may'
postconf -e 'smtpd_tls_auth_only = yes'
postconf -e 'smtpd_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1'
postconf -e 'smtp_tls_mandatory_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1'
postconf -e 'smtpd_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1'
postconf -e 'smtp_tls_protocols = !SSLv2, !SSLv3, !TLSv1, !TLSv1.1'
postconf -e 'inet_interfaces = all'
Variables named smtp_ are meant to define the behavior of Postfix as an SMTP client.
While the ones like smtpd_ the behavior of the SMTP server.
With this config, TLS is optional for everyone, except for authenticating. There it’s mandatory.
HELO, sender, relay and recipient restrictions
More restrictions to avoid unauthorized e-mail. We allow our authenticated users and servers with FQDN and reverse DNS sending to us.
postconf -e "smtpd_sender_login_maps = regexp:/etc/postfix/login_maps"
postconf -e 'smtpd_sender_restrictions = reject_sender_login_mismatch, permit_sasl_authenticated, permit_mynetworks, reject_unknown_reverse_client_hostname, reject_unknown_sender_domain'
postconf -e 'smtpd_recipient_restrictions = permit_sasl_authenticated, permit_mynetworks, reject_unauth_destination, reject_unknown_recipient_domain'
postconf -e 'smtpd_relay_restrictions = permit_sasl_authenticated, reject_unauth_destination'
postconf -e 'smtpd_helo_required = yes'
postconf -e 'smtpd_helo_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_invalid_helo_hostname, reject_non_fqdn_helo_hostname, reject_unknown_helo_hostname'
The content for /etc/postfix/login_maps should look like this.
/^(.*)@example\.com$/ ${1}
We are gonna configure Dovecot to use our Unix users and passwords.
But those don’t have the @example.com part in their names.
Here we are defining a map where any user@example.com is mapped as user, so we can use local authentication.
Privacy
Some headers may give away sender IP address.
Let’s define /etc/postfix/header_checks to ignore them.
postconf -e "header_checks = regexp:/etc/postfix/header_checks"
if [ ! -f /etc/postfix/headers_cheks ]; then
echo """
/^Received:.*/ IGNORE
/^X-Originating-IP:/ IGNORE
""" > /etc/postfix/headers_cheks
fi
Smart-host configuration
This part is optional. We can use a smart-host to relay our messages if our IP address have low reputation.
postconf -e 'smtp_use_tls = yes'
postconf -e 'smtp_sender_dependent_authentication = yes'
postconf -e 'smtp_sasl_auth_enable = yes'
postconf -e 'smtp_sasl_security_options = noanonymous'
postconf -e 'smtp_sasl_password_maps = hash:/etc/postfix/sasl_passwd'
postconf -e 'smtp_always_send_ehlo = yes'
postconf -e 'transport_maps = hash:/etc/postfix/transport'
postconf -e 'smtp_tls_CAfile = /etc/openssl/certs/ca-certificates.crt'
The file /etc/postfix/sasl_passwd have the credentials of your account in the smart-host.
Something like this.
[smarthost.example]:2525 user:p4ssw0rd
The brackets in [smarthost.example] means that Postfix should not look for the MX DNS registry.
The A registry is needed for that name.
The :2525 is the TCP port where we should connect.
The /etc/postfix/transport file should look like this.
example.com local
some_user@somehost.example relay:[smarthost.example]:2525
gmail.com relay:[smarthost.example]:2525
hotmail.com relay:[smarthost.example]:2525
* smtp
This file is used to determine how to send messages to different domains.
We can either use a relay, our smart-host, or smtp which means that we send the message directly.
The first column indicates the destination.
It can be a domain or a specific e-mail address.
The wildcard * means any address and any domain.
The second column indicates the method. Local delivery, a relay with the address of the smart-host or send directly via SMTP.
If I had to send a very important message to some_user@somehost.example I can use the smart-host, to avoid get flagged as spam.
You should note that the privacy of the message is lost: the smart-host needs to read the whole mail to relay it.
Once those maps are ready, we should run postmap to have them in binary (fast) format.
postmap /etc/postfix/transport
postmap /etc/postfix/sasl_passwd
Dovecot configuration
Here we configure Sieve and Dovecot.
The first step is to have a spam user.
We are gonna run our spam filter with that.
Then we compile the Sieve rules.
grep -q '^spamd:' /etc/passwd || useradd -g =uid -m -s /sbin/nologin spamd
mkdir -p /var/sieve
sievec /var/sieve/default.sieve
chown -R spamd:spamd /var/sieve
The contents of /var/sieve/default.sieve are something like this.
require ["fileinto", "mailbox"];
if header :contains "X-Spam-Flag" "YES"
{
fileinto "Junk";
}
The rule says: if the header X-Spam-Flag is set to YES, mail goes to the Junk folder.
Spamassasin is gonna set that header if it believes the message is spam.
The content of /usr/pkg/etc/dovecot/dovecot.conf is something like this.
ssl = required
ssl_cert = </usr/pkg/etc/letsencrypt/live/example.com/fullchain.pem
ssl_key = </usr/pkg/etc/letsencrypt/live/example.com/privkey.pem
ssl_min_protocol = TLSv1.2
ssl_prefer_server_ciphers = yes
auth_mechanisms = plain login
auth_username_format = %n
protocols = imap
# Search for valid users in /etc/passwd
userdb {
driver = passwd
}
#Fallback: Use plain old PAM to find user passwords
passdb {
driver = pam
}
# Our mail for each user will be in ~/Mail, and the inbox will be ~/Mail/Inbox
# The LAYOUT option is also important because otherwise, the boxes will be \`.Sent\` instead of \`Sent\`.
mail_location = maildir:~/Mail:INBOX=~/Mail/Inbox:LAYOUT=fs
namespace inbox {
inbox = yes
mailbox Drafts {
special_use = \Drafts
auto = subscribe
}
mailbox Junk {
special_use = \Junk
auto = subscribe
autoexpunge = 30d
}
mailbox Sent {
special_use = \Sent
auto = subscribe
}
mailbox Trash {
special_use = \Trash
}
mailbox Archive {
special_use = \Archive
}
}
# Here we let Postfix use Dovecot's authentication system.
service auth {
unix_listener /var/spool/postfix/private/auth {
mode = 0660
user = postfix
group = postfix
}
}
protocol lda {
mail_plugins = $mail_plugins sieve
}
protocol lmtp {
mail_plugins = $mail_plugins sieve
}
protocol pop3 {
pop3_uidl_format = %08Xu%08Xv
pop3_no_flag_updates = yes
}
plugin {
sieve = ~/.dovecot.sieve
sieve_default = /var/sieve/default.sieve
sieve_dir = ~/.sieve
sieve_global_dir = /var/sieve/
}
This configuration is for Dovecot 2.3. In 2.4 Dovecot has breaking changes that makes old configuration files obsolete. If you are running Dovecot 2.4, you need to fix this file accordingly.
Let’s configure Postfix to use Dovecot for authentication and delivery, and Spamassasin as filter.
cp -v /etc/pam.d/imap /etc/pam.d/dovecot
postconf -e 'smtpd_sasl_security_options = noanonymous, noplaintext'
postconf -e 'smtpd_sasl_tls_security_options = noanonymous'
postconf -e 'milter_default_action = accept'
postconf -e 'milter_protocol = 6'
postconf -e 'smtpd_milters = inet:localhost:12301'
postconf -e 'non_smtpd_milters = inet:localhost:12301'
postconf -e 'mailbox_command = /usr/pkg/libexec/dovecot/deliver'
postconf -e 'smtpd_sasl_auth_enable = yes'
postconf -e 'smtpd_sasl_type = dovecot'
postconf -e 'smtpd_sasl_path = private/auth'
OpenDKIM configuration
DKIM is used to sign parts of the mail with a key pair, whose public part is in a TXT DNS record. So other servers can verify the signature to make sure the message comes from your server and have not been forged.
This is the content of /usr/pkg/etc/opendkim.conf
Canonicalization relaxed/simple
Domain example.com
InternalHosts refile:/etc/postfix/dkim/trustedhosts
KeyTable file:/etc/postfix/dkim/keytable
Selector default
SigningTable refile:/etc/postfix/dkim/signingtable
Socket inet:12301@localhost
Syslog Yes
Let’s generate a key pair if we don’t have one.
mkdir -p /etc/postfix/dkim/example.com
if [ ! -f /etc/postfix/dkim/example.com/default.private ]; then
opendkim-genkey -D "/etc/postfix/dkim/example.com" -d example.com -s default
chown -R root:opendkim /etc/postfix/dkim/
chmod -R g+r /etc/postfix/dkim/example.com/
fi
The contents of /etc/postfix/dkim/trustedhosts.
We only trust localhost, basically.
127.0.0.1
::1
The contents of /etc/postfix/dkim/keytable are our DNS record, the domain, selector name and path of the secret key.
default._domainkey.example.com example.com:default:/etc/postfix/dkim/example.com/default.private
It is possible to have multiple key pairs per domain.
Hence the selectors.
Those selector indicates what DNS record have the public key to verify the signature.
Because our setup is very simple, we only have one default selector.
The contents of /etc/postfix/dkim/signingtable.
We tell OpenDKIM what pair of address/selector we should use.
In this case, all messages are signed with default selector.
*@example.com default._domainkey.example.com
RC configuration
The /etc/rc.conf file should have the following services.
# email
postfix=YES
dovecot=YES
spamass_milter=YES
opendkim=YES
spamd=YES
DNS registries
Now we can update the DNS registries needed to send and receive messages.
default._domainkey.example.com TXT v=DKIM1; k=rsa; ${PUBLIC_KEY}"
dmarc.example.com TXT v=DMARC1; p=reject; rua=mailto:postmaster@example.com; fo=1"
example.com TXT v=spf1 mx a -all"
example.com MX example.com
example.com A ${IP_ADDR}
example.com AAAA ${IP6_ADDR}
You can find the DKIM public key in /etc/postfix/dkim/example.com/default.txt
In Summary
I didn’t dig too much on what those configurations are. I recommend you to read the actual documentation of those projects. Here I just put in place the manual steps to deploy a mail server on NetBSD. Which is a very fun way to learn this OS.
There’s more stuff needed to have a functional mail server. Monitoring, backups, etc. None of that is covered here.
See also
- Postfix documentation: https://cdn.netbsd.org/pub/NetBSD/NetBSD-current/src/external/ibm-public/postfix/dist/html/
- Dovecot documentation: https://doc.dovecot.org/2.3/
- OpenDKIM documentation: https://www.opendkim.org/docs.html