DEPRECATED This is an old version of the OpenBSD mail server.
https://poolp.org/posts/2019-09-14/setting-up-a-mail-server-with-opensmtpd-dovecot-and-rspamd/
How to setup an OpenBSD 6.4 Mail Server with:
Note: This is mostly a copy/past guide. It is still recommended to read the man-pages to ‘understand’ why and how things work.
pf -> relay to spamd (tarpit)
pf -> relay to spamd (greylist handling)
pf -> relay to opensmtpd on lo0 ->
relay untagged mail to spampd (spamassassin) on port 10025 ->
return to opensmtpd on lo0 port 10026 and tag it SPAM_IN ->
deliver to mda (kopano-dagent)
kopano (kopano-spooler) -> opensmtpd on lo0 -> relay untagged mail to dkimproxy on port 10027 ->
return to opensmtpd on lo0 port 10028 and tag it DKIM_OUT ->
relay out -> pf
Install: pkg_add p5-Mail-SpamAssassin spampd
Boot startup:
rcctl enable spampd
rcctl enable spamassassin
rcctl set spampd flags "--port=10025 --relayhost=127.0.0.1:10026 --tagall --aw"
Change Rewrite: sed -i 's/^# \(rewrite_header.*\)/\1/' /etc/mail/spamassassin/local.cf
Permissions: chown -R _spampd /var/spool/spamassassin
Start the daemons:
rcctl start spampd
rcctl start spamassassin
Create the daily update cronjob:
echo "0 3 * * * /usr/local/bin/sa-update >/dev/null 2>&1" >> /var/cron/tabs/root
Install: pkg_add dkimproxy opendkim
Boot startup: rcctl enable dkimproxy_out
Note: replace example.com with your domain.
Edit config:
sed -i 's/^domain.*/domain example.com/' /etc/dkimproxy_out.conf
sed -i 's|^keyfile.*|keyfile /var/dkimproxy/default.private|' /etc/dkimproxy_out.conf
sed -i 's/^selector.*/selector default/' /etc/dkimproxy_out.conf
sed -i 's/^signature dkim.*/signature dkim(c=relaxed,a=rsa-sha256)/' /etc/dkimproxy_out.conf
Generate signing-key:
mkdir /var/dkimproxy
opendkim-genkey -d example.com -D /var/dkimproxy -b 4096
chgrp _dkimproxy /var/dkimproxy/*
chmod 640 /var/dkimproxy/*
Put the contents of /var/dkimproxy/default.txt in your DNS-Zone file.
Start dkimproxy: rcctl start dkimproxy_out
Boot startup:
rcctl enable smtpd
rcctl set smtpd flags -v
This file holds all virtual domains:
cat /etc/mail/vdomains
example.com
example.org
Create the virtual system user: useradd -d /var/empty -s /sbin/nologin vmail
This file holds the virtual user mappings:
# cat /etc/mail/vusers
john@example.com vmail
@example.com john@example.com
doe@example.org vmail
hostmaster@example.org doe@example.org
Replace example.com with your domain in /etc/mail/smtpd.conf.
smtp max-message-size 20MB
pki mail.example.com cert "/etc/ssl/example.com.fullchain.pem"
pki mail.example.com key "/etc/ssl/private/example.com.key"
smtp ciphers ECDHE-RSA-CHACHA20-POLY1305:CHACHA20:ECDHE+AESGCM:ECDHE+AES
listen on lo0
listen on lo0 port 10026 tag SPAM_IN
listen on lo0 port 10028 tag DKIM_OUT
listen on egress tls pki mail.example.com auth-optional
listen on egress smtps pki mail.example.com auth-optional
table aliases file:/etc/mail/aliases
table vdomains file:/etc/mail/vdomains
table vusers file:/etc/mail/vusers
action "local" mbox alias <aliases>
action "relay" relay
action "relay_spampd" relay host smtp://127.0.0.1:10025
action "relay_dkim" relay host smtp://127.0.0.1:10027
action "kopano" mda "/usr/local/sbin/kopano-dagent -s %{dest}" virtual <vusers> user "_kopano"
# tags
match tag SPAM_IN for domain <vdomains> action "kopano"
match tag DKIM_OUT for any action "relay"
# local
match for local action "local"
match from any for domain <vdomains> action "relay_spampd"
match for any action "relay_dkim"
match !for domain <vdomains> reject
Restart smtpd: rcctl restart smtpd
Create custom spam files:
touch /etc/mail/{nospamd,spamd.black,custom.black}
Config:
all:\
:nixspam:custom_black:
nixspam:\
:black:\
:msg="Your address %A is in the nixspam list\n\
See http://www.heise.de/ix/nixspam/dnsbl_en/ for details":\
:method=http:\
:file=www.openbsd.org/spamd/nixspam.gz
custom_black:\
:black:\
:msg="Your address %A has been manually blacklisted":\
:method=file:\
:file=/etc/mail/custom.black:
Boot startup:
Replace example.com with your domain.
rcctl enable spamd
rcctl set spamd flags "-v -G 4:4:864 -K /etc/ssl/private/example.com.key -C /etc/ssl/example.com.fullchain.pem"
rcctl enable spamlogd
Create spamd-setup cronjob: echo "*/5 * * * * /usr/libexec/spamd-setup -b >/dev/null 2>&1" >> /var/cron/tabs/root
cat <<EOF>> /etc/pf.conf
set limit table-entries 400000
smtp_ports = "{ smtp, 465 }"
table <spamd> persist
table <nospamd> persist file "/etc/mail/nospamd"
table <spamd-white> persist
pass in quick log on egress proto tcp \
from { <nospamd>, <spamd-white> } \
to any port $smtp_ports \
flags S/SA modulate state
pass in quick proto tcp from <spamd> \
to egress port $smtp_ports \
rdr-to 127.0.0.1 port spamd
pass in quick proto tcp from any \
to egress port $smtp_ports \
rdr-to 127.0.0.1 port spamd
pass out log on egress proto tcp to any port $smtp_ports
EOF
pfctl -f /etc/pf.conf
Start spamd and spamlogd:
rcctl start spamd
rcctl start spamlogd
Basic config at OpenBSD MariaDB
Create database:
mysql -u root -p
CREATE DATABASE kopano CHARACTER SET utf8mb4 COLLATE utf8mb4_unicode_ci;
GRANT ALL ON kopano.* TO 'kopano'@'localhost' IDENTIFIED BY '<password>';
FLUSH PRIVILEGES;
Install: pkg_add kopano-core kopano-mapi
Boot startup: rcctl enable kopano
Make sure mysqld starts before kopano: rcctl order mysqld
Change services to start:
perl -p -i -e 's/^(_pkg_scripts=.*)/#$1\n_pkg_scripts="kopano_server kopano_spooler kopano_monitor kopano_gateway"/' /etc/rc.d/kopano
Link kopano socket to chroot:
sed '/rc_cmd/d' /etc/rc.d/kopano_server
cat <<'EOF'>> /etc/rc.d/kopano_server
sock="/var/run/kopano/server.sock"
www_sock="/var/www/run/server.sock"
rc_start() {
${rcexec} "${daemon} ${daemon_flags} ${_bg}"
exc=$?
sleep 2
ln $sock $www_sock
return $exc
}
rc_post() {
rm -f $www_sock
}
rc_cmd $1
EOF
/etc/kopano/server.cfg
Set DB Credentials:
mysql_user = kopano
mysql_password = <password>
mysql_socket = /var/run/mysql/mysql.sock
/etc/kopano/gateway.cfg
pop3_listen =
pop3s_listen =
imap_listen =
imaps_listen = *:993
server_socket = file:///var/run/kopano/server.sock
disable_plaintext_auth = yes
ssl_private_key_file = /etc/ssl/private/example.com.key
ssl_certificate_file = /etc/ssl/example.com.fullchain.pem
log_file = /var/log/kopano/gateway.log
/etc/kopano/search.cfg
server_bind_name = file:///var/run/kopano/search.sock
Start kopano: rcctl start kopano
Create a user: kopano-admin -c "john@example.com" -f "John Doe" -e "john@example.com" -P
Install: pkg_add kopano-webapp
Boot startup: rcctl enable php70_fpm
echo "extension=mapi.so" > /etc/php-7.0/kopano.ini
Don’t expose php: sed -i 's/^expose_php.*/expose_php = off/' /etc/php-7.0.ini
Change Server Socket:
sed -i 's|^.define("DEFAULT_SERVER".*|define("DEFAULT_SERVER", "file:///run/server.sock");|' /var/www/kopano-webapp/config.php
Start php-fpm: rcctl start php70_fpm
Install: pkg_add z-push
Change Server Socket:
sed -i "s|^define('MAPI_SERVER'.*|define('MAPI_SERVER', 'file:///run/server.sock');|" /var/www/z-push/backend/kopano/config.php
Basic config at OpenBSD httpd
Kopano config:
connection {
max request body 20971520
}
root "/kopano-webapp"
directory index "index.php"
location "*.php" {
fastcgi socket "/run/php-fpm.sock"
}
location "/Microsoft-Server-ActiveSync" {
root "/z-push/index.php"
fastcgi socket "/run/php-fpm.sock"
}
If not yet installed: pkg_add unzip--
Install:
mkdir /var/www/kopano-webapp/plugins/passwd
ftp -o /var/www/kopano-webapp/plugins/passwd-1.3.zip https://github.com/silentsakky/zarafa-webapp-passwd/releases/download/1.3/passwd-1.3.zip
unzip -q /var/www/kopano-webapp/plugins/passwd-1.3.zip -d /var/www/kopano-webapp/plugins/passwd
Config:
sed -i "s|^define('PLUGIN_PASSWD_USER_DEFAULT_ENABLE'.*|define('PLUGIN_PASSWD_USER_DEFAULT_ENABLE', true);|" /var/www/kopano-webapp/plugins/passwd/config.php
sed -i "s|^define('PLUGIN_PASSWD_LOGIN_WITH_TENANT'.*|define('PLUGIN_PASSWD_LOGIN_WITH_TENANT', true);|" /var/www/kopano-webapp/plugins/passwd/config.php
Project: bgp-spamd
Boot startup: rcctl enable bgpd
OpenBGPD config:
cat <<'EOF'> /etc/bgpd.conf
spamdAS="65066"
AS 65001
fib-update no # Mandatory, to not update the local routing table
nexthop qualify via default # Make sure the nexthop is valid
group "spamd-bgp" {
remote-as $spamdAS
multihop 64
export none
# us.bgp-spamd.net
neighbor 64.142.121.62
# eu.bgp-spamd.net
neighbor 217.31.80.170
# IPv6 eu.bgp-spamd.net
#neighbor 2a00:15a8:0:100:0:d91f:50aa:1
}
deny to any
deny from any
allow from group "spamd-bgp"
# 'match' is required, to remove entries when routes are withdrawn
# This updates the <bgp-spamd-bypass> table in PF
match from group "spamd-bgp" community $spamdAS:42 set pftable "bgp_spamd_bypass"
match from group "spamd-bgp" community $spamdAS:666 set pftable "bgp_spamd"
EOF
chmod 600 /etc/bgpd.conf
Start OpenBGPD: rcctl start bgpd
Add these options to your /etc/pf.conf:
set limit table-entries 400000
table <bgp_spamd_bypass> persist
table <bgp_spamd> persist
bgp_addrs = "{ 64.142.121.62, 217.31.80.170, 2a00:15a8:0:100:0:d91f:50aa:1 }"
pass in on egress proto tcp from $bgp_addrs to any port bgp
Change your pf smtp rules to match these:
pass in quick log on egress proto tcp \
from { <bgp_spamd_bypass>, <nospamd>, <spamd-white> } \
to any port $smtp_ports \
flags S/SA modulate state
pass in quick proto tcp from { <bgp_spamd>, <spamd> } \
to egress port $smtp_ports \
rdr-to 127.0.0.1 port spamd
Reload pf: pfctl -f /etc/pf.conf
Edit the /etc/mail/spamd.conf:
# Add bgp-spamd to the all list:
all:\
:nixspam:custom_black:bgp-spamd:
# Add the bgp-spamd list:
bgp-spamd:\
:black:\
:msg="Your address %A has sent mail to a spamtrap\n\
within the last 24 hours":\
:method=file:\
:file=/etc/mail/spamd.black:
Create the spamd-reload script:
cat <<'EOF'> /usr/local/sbin/spamd-update
#!/bin/sh
black="/etc/mail/spamd.black"
custom="/etc/mail/custom.black"
bgpctl show rib community 65066:666|tail -n+5|
awk '{sub(/\/.*/, "", $1);print $1}'|uniq >${black}
for ip in $(cat ${custom}); do
/usr/sbin/spamdb -d ${ip} 2> /dev/null
done
/usr/libexec/spamd-setup -b
EOF
chmod 0744 /usr/local/sbin/spamd-update
Replace the spamd-setup cronjob from above with the new script and run it once manually: /usr/local/sbin/spamd-update
Add this to your /usr/local/sbin/spamd-update:
smtpctl spf walk <<EOF | sort -u | pfctl -t trusted_bypass -T replace -f- 2>/dev/null
_netblocks.google.com
_netblocks2.google.com
_netblocks3.google.com
steampowered.com
amazonses.com
outlook.com
_spf.apple.com
EOF
Add the trusted_bypass table to your pf.conf:
table <trusted_bypass> persist
pass in quick log on egress proto tcp \
from { <trusted_bypass>, <bgp_spamd_bypass>, <nospamd>, <spamd-white> } \
...
For the manual spam-reporting to work properly, you have to forward the desired SPAM-Mail as attachment to spamreport@example.com (replace example.com with your domain).
The spamreport fake MDA (Mail Delivery Agent) will inspect the Recieved: mail header to find the sender IP-address.
This IP-address will then be blacklisted in the custom blacklist.
Add the spamreport system user: useradd -d /var/empty -s /sbin/nologin spamreport
Create the spamreport MDA:
cat <<'EOF'> /usr/local/sbin/spamreport
#!/bin/sh
tmp=$(mktemp)
blacklist="/etc/mail/custom.black"
perl -ne 'print "$1\n" if/\[([12]?[0-9]{1,2}.*)\]/&&!/127/' >>${blacklist}
sort -u -o ${tmp} ${blacklist}
cp ${tmp} ${blacklist}
rm -f ${tmp}
EOF
chmod 755 /usr/local/sbin/spamreport
Change blacklist permissions:
chgrp spamreport /etc/mail/custom.black
chmod 664 /etc/mail/custom.black
Change your smtpd.conf to look like this (replace example.com with your domain):
action "spamreport" mda "/usr/local/sbin/spamreport" user "spamreport"
match from local rcpt-to "spamreport@example.com" action "spamreport"
Restart smtpd: rcctl restart smtpd
Optional
Install: pkg_add clamav clamsmtp
Boot startup:
rcctl enable clamd
/etc/clamd.conf
Comment out the 'Example' string:
#Example
Uncomment these options:
TCPSocket 3310
TCPAddr 127.0.0.1
/etc/freshclam.conf
Comment out the 'Example' string:
#Example
Change
#DatabaseMirror db.XY.clamav.net
to
DatabaseMirror db.de.clamav.net
Create the update cronjob:
echo "20 * * * * /usr/local/bin/freshclam >/dev/null 2>&1" >> /var/cron/tabs/root
Create two clamsmtpd configs:
cp /etc/clamsmtpd.conf /etc/clamsmtpd-in.conf
cp /etc/clamsmtpd.conf /etc/clamsmtpd-out.conf
/etc/clamsmtpd-in.conf
Change to match these values:
OutAddress: 10026
Listen: 127.0.0.1:10025
User: _clamav
Add this option:
ClamAddress: 127.0.0.1:3310
/etc/clamsmtpd-out.conf
Change to match these values:
OutAddress: 10028
Listen: 127.0.0.1:10027
User: _clamav
Add this option:
ClamAddress: 127.0.0.1:3310
Create the two init scripts:
cat <<'EOF'> /etc/rc.d/clamsmtpd_in
#!/bin/sh
daemon="/usr/local/sbin/clamsmtpd -f /etc/clamsmtpd-in.conf"
. /etc/rc.d/rc.subr
rc_cmd $1
EOF
cat <<'EOF'> /etc/rc.d/clamsmtpd_out
#!/bin/sh
daemon="/usr/local/sbin/clamsmtpd -f /etc/clamsmtpd-out.conf"
. /etc/rc.d/rc.subr
rc_cmd $1
EOF
chmod 0755 /etc/rc.d/clamsmtpd_{in,out}
Enable the services:
rcctl enable clamsmtpd_in
rcctl enable clamsmtpd_out
Run the initial freshclam: /usr/local/bin/freshclam
Start the daemons:
rcctl start clamd
rcctl start clamsmtpd_in
rcctl start clamsmtpd_out
#!/bin/sh
db="kopano"
db_user="kopano"
db_pw="<password>"
set -A paths \
/etc/kopano \
/etc/mail \
/etc/pf.conf \
/etc/httpd.conf \
/etc/clamsmtpd-in.conf \
/etc/clamsmtpd-out.conf \
/etc/clamd.conf \
/etc/freshclam.conf \
/etc/ssl \
/etc/acme-client.conf \
/etc/bgpd.conf \
/etc/rc.conf.local \
/etc/rc.d/clamsmtpd_in \
/etc/rc.d/clamsmtpd_out \
/etc/my.cnf \
/etc/rc.d/kopano \
/etc/php-7.0/kopano.ini \
/etc/php-7.0.ini \
/var/db/clamav \
/var/db/spamassassin \
/var/db/spamd \
/var/db/kopano \
/var/www/kopano-webapp/config.php \
/var/www/z-push/backend/kopano/config.php \
/usr/local/sbin/spamd-update \
/usr/local/sbin/cron-acme-update \
/usr/local/sbin/spamreport \
/usr/local/sbin/watchdog \
/etc/mail/spamassassin/local.cf \
/var/cron/tabs/root \
/tmp/*.pf \
/tmp/${db}.sql
for t in $(pfctl -s Tables); do
pfctl -t ${t} -T show > /tmp/${t}.pf
done
mysqldump -u ${db_user} -p${db_pw} ${db} >/tmp/${db}.sql
tar cfvz /root/mailserver_backup_$(date +%Y%m%d_%H%M%S).tgz ${paths[@]}
rm /tmp/${db}.sql /tmp/*.pf
Restore backup:
tar xfpvz mailserver_backup<date>.tgz -C /
Restore pf tables:
for t in $(ls /tmp/*.pf); do pfctl -t ${t%.pf} -T add -f ${t}; done
Restore database:
mysql -u root -p kopano < /tmp/kopano.sql
cat <<'EOF'>/usr/local/sbin/watchdog
#!/bin/sh
for p in $(rcctl ls failed|grep -v spampd); do
rcctl start ${p}
done
z=$(ps aux|grep -E "_spampd.*Z"|grep -v grep)
if [ "${z}" != "" ] || ! rcctl check spampd >/dev/null; then
pkill -9 -f spampd
rcctl start spampd
fi
EOF
chmod 755 /usr/local/sbin/watchdog
echo "*/5 * * * * /usr/local/sbin/watchdog >/dev/null 2>&1" >> /var/cron/tabs/root