acme-client
and the httpd
server on OpenBSD)opensmtpd
and dovecot
)
example.com
and local usersrspamd
+ Sieve rules to train the antispam)No fancy webmail.
This tutorial is heavily inspired by the vultr tutorial and an old article about let’s encrypt on openbsd.
This is very straightforward.
httpd
has to be configured to point to the right directory for acme challenges for your domainsacme-client
has to know your domainsThat’s it.
httpd.conf
# $OpenBSD: httpd.conf,v 1.20 2018/06/13 15:08:24 reyk Exp $
server "example.com" {
alias "www.example.com"
alias "mail.example.com"
alias "team.example.com"
listen on * port 80
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
location "/special/*" {
root "/htdocs/"
}
location * {
block return 302 "https://$HTTP_HOST$REQUEST_URI"
}
}
server "example.com" {
alias "www.example.com"
alias "mail.example.com"
alias "team.example.com"
listen on * tls port 443
root "/htdocs/example.com"
tls {
certificate "/etc/ssl/example.com.fullchain.pem"
key "/etc/ssl/private/example.com.key"
}
location "/pub/*" {
directory auto index
}
location "/.well-known/acme-challenge/*" {
root "/acme"
request strip 2
}
}
acme-client.conf
#
# $OpenBSD: acme-client.conf,v 1.2 2019/06/07 08:08:30 florian 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"
}
domain example.com {
alternative names { www.example.com mail.example.com team.example.com }
domain key "/etc/ssl/private/example.com.key"
domain full chain certificate "/etc/ssl/example.com.fullchain.pem"
sign with letsencrypt
}
In this example, I want example.com
and three aliases. Here are the DNS records:
example.com IN 1200 A 10.0.0.1
www.example.com IN 1200 A 10.0.0.1
team.example.com IN 1200 A 10.0.0.1
mail.example.com IN 1200 A 10.0.0.1
example.com IN 1200 MX 10 mail
You can also put CNAME
instead of A
records for team
and www
.
You can have free domain names on netlib.re!
Lastly, we want to create acme
and tls
folders:
mkdir /var/www/acme
mkdir -p /etc/ssl/acme/private /etc/acme
chmod 0700 /etc/ssl/acme/private /etc/acme
Then we can restart httpd
and launch acme-client
:
rcctl restart httpd
acme-client example.com
# Second restart, now the web server has the right certificate.
rcctl restart httpd
pkg_add opensmtpd-extras opensmtpd-filter-rspamd dovecot dovecot-pigeonhole rspamd redis
This tutorial uses an user vmail
to handle all virtual users mails. vmail
has the uid 2000
, the gid 2000
, and its home directory will be the root for virtual users maildirs: /var/vmail/
.
touch /etc/mail/credentials
chmod 0440 /etc/mail/credentials
chown _smtpd:_dovecot /etc/mail/credentials
useradd -c "Virtual Users Mail Account" -d /var/vmail -s /sbin/nologin -u 2000 -g =uid -L staff vmail
mkdir /var/vmail
chown vmail:vmail /var/vmail
In our setup, mail users will be either system users or virtual ones. The list of virtual users and their credentials need to be configured: this tutorial use a simple file to that end.
Each client needs to be authenticated before sending emails. Passwords need to be processed before being put in a simple text file.
smtpctl encrypt PASSWORD
# Example: smtpctl encrypt chocolate
# $2b$09$/aUucPECRPTasFpNZcgpEe.0TTSiJ9UXqhF4uIzFvlLr220nBxuMq
This way, passwords can be put in the credentials
file.
The /etc/mail/credentials
file needs to be formatted, almost as /etc/passwd
:
<email>:<password>:<user>:<uid>:<gid>:<maildir-path>::userdb_mail=maildir:<maildir-path>
Parameters user
, uid
and gid
are related to the system user that will handle all virtual users mails: vmail. Also, maildir-path
will be into the vmail
home directory.
Here is an example for the user joe.satriani
for the domain example.com
.
joe.satriani@example.com:$2b$09$/aUucPECRPTasFpNZcgpEe.0TTSiJ9UXqhF4uIzFvlLr220nBxuMq:vmail:2000:2000:/var/vmail/example.com/joe.satriani::userdb_mail=maildir:/var/vmail/example.com/joe.satriani
/etc/mail/virtuals
defines the valid email addresses (and aliases) for our default domain example.com
.
abuse@example.com: joe.satriani@example.com
hostmaster@example.com: joe.satriani@example.com
postmaster@example.com: joe.satriani@example.com
webmaster@example.com: joe.satriani@example.com
joe.satriani@example.com: vmail
steve.vai@example.com: vmail
john.petrucci@example.com: vmail
Before presenting the configuration file, here is a quick overview of the smtpd
configuration parameters.
pki
indicates certificate and key paths for a domain.table
references lists:
listen
directive indicates:
pki
to usefilter
executes an application on a mail
action
describes what to do with a message
match
uses an action based on the processed message (origin, destination)
domain_mail
action for emails for the domain example.com
local_mail
action for emails coming from local addressNext step: the configuration file. I allow a machine in my domain (192.168.0.200
) to send emails with this mail server as a relay.
The configuration file I use.
# $OpenBSD: smtpd.conf,v 1.14 2019/11/26 20:14:38 gilles Exp $
# This is the smtpd server system-wide configuration file.
# See smtpd.conf(5) for more information.
pki "mail.example.com" cert "/etc/ssl/example.com.fullchain.pem"
pki "mail.example.com" key "/etc/ssl/private/example.com.key"
table aliases file:/etc/mail/aliases
table credentials passwd:/etc/mail/credentials
table virtuals file:/etc/mail/virtuals
# Filter potential spam with rspamd
filter "rspamd" proc-exec "/usr/local/libexec/smtpd/filter-rspamd"
# To accept external mail, replace with: listen on all
#
#listen on socket
listen on lo0
listen on "192.168.0.100" \
tls pki "mail.example.com" \
hostname "mail.example.com" filter "rspamd"
# Authorize people to send messages from our server
listen on "192.168.0.100" port submission \
tls-require pki "mail.example.com" \
hostname "mail.example.com" \
auth <credentials> filter "rspamd"
# Where to store incoming emails based on the target user.
action "local_mail" mbox alias <aliases>
action "domain_mail" \
maildir "/var/vmail/example.com/%{dest.user}" \
virtual <virtuals>
# Relay mails when they come from authorized clients.
action "outbound" relay
# Next, we match incoming emails.
# When the mail comes from any place for our domain, it triggers the "domain_mail" action.
match from any for domain "example.com" action "domain_mail"
# When the mail comes from and for a local user it triggers the "local_mail" action.
match from local for local action "local_mail"
# HEADS UP: Authorize forwarding emails for a local machine
match from src "192.168.0.200" for any action "outbound"
# HEADS UP: Authorize forwarding emails for a local machine
match from local for any action "outbound"
match auth from any for any action "outbound"
The setup is simple: smtpd
listens to local connections (on lo0
) and on another interface (with the IP address 192.168.0.100
). TLS is used along with the mail.example.com
PKI for incoming connections (either for submissions or not). Incoming connections are filtered with rspamd
and have mail.example.com
as the provided host name. Submissions need to pass authentication based on the /etc/mail/credentials
file set earlier.
This configuration allows any local or authenticated user to send mails to any domain. Besides the generic configuration in my setup, I authorized the forwarding of mails coming from a specific IP address in my local network (192.168.0.200
).
# First, try the syntax.
doas smtpd -n
# Then run the service.
doas rcctl restart smtpd
Mails will be transfered in the /var/vmail/example.com/<username>/new
directory.
Since dovecot
opens a lot of files, it is preferable to create a login class for the service.
dovecot:\
:openfiles-cur=1024:\
:openfiles-max=2048:\
:tc=daemon:
# Ensure new login class is taken into account.
cap_mkdb /etc/login.conf
# Change the dovecot login class.
usermod -L dovecot _dovecot
File /etc/dovecot/local.conf
auth_mechanisms = plain
first_valid_uid = 2000
first_valid_gid = 2000
mail_location = maildir:/var/vmail/%d/%n
mail_plugin_dir = /usr/local/lib/dovecot
managesieve_notify_capability = mailto
managesieve_sieve_capability = fileinto reject envelope encoded-character vacation subaddress comparator-i;ascii-numeric relational regex imap4flags copy include variables body enotify environment mailbox date index ihave duplicate mime foreverypart extracttext imapsieve vnd.dovecot.imapsieve
mbox_write_locks = fcntl
mmap_disable = yes
namespace inbox {
inbox = yes
location =
mailbox Archive {
auto = subscribe
special_use = \Archive
}
mailbox Drafts {
auto = subscribe
special_use = \Drafts
}
mailbox Junk {
auto = subscribe
special_use = \Junk
}
mailbox Sent {
auto = subscribe
special_use = \Sent
}
mailbox Trash {
auto = subscribe
special_use = \Trash
}
prefix =
}
passdb {
args = scheme=CRYPT username_format=%u /etc/mail/credentials
driver = passwd-file
name =
}
plugin {
imapsieve_mailbox1_before = file:/usr/local/lib/dovecot/sieve/report-spam.sieve
imapsieve_mailbox1_causes = COPY
imapsieve_mailbox1_name = Junk
imapsieve_mailbox2_before = file:/usr/local/lib/dovecot/sieve/report-ham.sieve
imapsieve_mailbox2_causes = COPY
imapsieve_mailbox2_from = Junk
imapsieve_mailbox2_name = *
sieve = file:~/sieve;active=~/.dovecot.sieve
sieve_global_extensions = +vnd.dovecot.pipe +vnd.dovecot.environment
sieve_pipe_bin_dir = /usr/local/lib/dovecot/sieve
sieve_plugins = sieve_imapsieve sieve_extprograms
}
protocols = imap sieve
service imap-login {
inet_listener imaps {
port = 993
}
}
service managesieve-login {
inet_listener sieve {
port = 4190
}
inet_listener sieve_deprecated {
port = 2000
}
}
ssl_cert = </etc/ssl/example.com.fullchain.pem
ssl_key = </etc/ssl/private/example.com.key
userdb {
args = username_format=%u /etc/mail/credentials
driver = passwd-file
name =
}
protocol imap {
mail_plugins = " imap_sieve"
}
As of today (september 2020), there still is a bug in Dovecot: the ssl_cert
and ssl_key
settings do not get overridden in the local.conf
file. We need to so we have to comment these parameters in the file /etc/dovecot/conf.d/10-ssl.conf
.
To train rspamd
, we need to provide sieve scripts. Training is done by moving emails into and out the junk folder. These files are located at /usr/local/lib/dovecot/sieve
.
This archive contains the 4 files you need:
report-ham.sieve
and report-spam.sieve
sa-learn-ham.sh
and sa-learn-spam.sh
Once untar in /usr/local/lib/dovecot/sieve
:
sievec report-ham.sieve
sievec report-spam.sieve
chmod 0755 sa-learn-ham.sh
chmod 0755 sa-learn-spam.sh
Then we can start the dovecot
daemon.
rcctl enable dovecot
rcctl start dovecot
Finally, we can verify the setup by requesting informations about a client.
doveadm user joe.satriani@example.com
And verify that the user can log in.
doveadm auth login joe.satriani@example.com
For rspamd
, we need:
doas su
mkdir /etc/mail/dkim
cd /etc/mail/dkim
openssl genrsa -out private.key 1024
openssl rsa -in private.key -pubout -out public.key
chmod 0440 private.key
chown root:_rspamd private.key
Of course, you have to put your own public IPv4 (or IPv6) address.
example.com. IN TXT "v=spf1 a ip4:192.168.0.100 mx ~all"
default._domainkey.example.com. IN TXT "v=DKIM1;k=rsa;p=[…public key…]"
_dmarc.example.com. IN TXT "v=DMARC1;p=none;pct=100;rua=mailto:postmaster@example.com"
Then we need to create the /etc/rspamd/local.d/dkim_signing.conf
file.
domain {
example.com {
path = "/etc/mail/dkim/private.key";
selector = "default";
}
}
Now, we have all the scripts, configuration files and services up and running except redis
and rspamd
.
doas rcctl enable redis rspamd
doas rcctl start redis rspamd