OpenBSD - OpenSMTPD

Page content

Running a Mailserver on OpenBSD …

Source

Requirements

  • OpenBSD VM
  • Public IP & FQDN
  • no Portfilter from Hoster
  • root permission

Packages

pkg_add opensmtpd-extras opensmtpd-filter-rspamd dovecot dovecot-pigeonhole redis rspamd-- opensmtpd-filter-senderscore

FQDN

export host="hostname"
export domain="domain.tld"
export fqdn="${host}.${domain}"

httpd.conf

f="/etc/httpd.conf"; test -f ${f} && cp ${f} "${f}-$(date +'%s')"

cat << EOF > ${f}
# added $(date)
server "${fqdn}" {
  listen on * port 80
  location "/.well-known/acme-challenge/*" {
    root "/acme"
    request strip 2
  }
}
EOF
chown root:wheel ${f}; chmod 644 ${f}

pf.conf

allow Certain Ports for Any

cat << EOF >> /etc/pf.conf

# Full Access for Mail/Web, added $(date)
pass  in  log quick proto tcp   from any  to (self) port { 25 80 587 993 }
EOF

pfctl -nvf /etc/pf.conf && pfctl -f /etc/pf.conf

acme-client.conf

f="/etc/acme-client.conf"; test -f ${f} && cp ${f} "${f}-$(date +'%s')"

# Copy Example
cp /etc/examples/acme-client.conf /etc/

# Kill Last 10 Lines
sed -i -e :a -e '$d;N;2,6ba' -e 'P;D' /etc/acme-client.conf

# Append MyStuff
cat << EOF >> /etc/acme-client.conf
# added $(date)
domain ${fqdn} {
  domain key "/etc/ssl/private/${fqdn}.key"
  domain full chain certificate "/etc/ssl/${fqdn}.fullchain.pem"
  sign with letsencrypt
}
EOF
chown root:wheel ${f}; chmod 644 ${f}

# Start Web, get Service
rcctl -f start httpd
acme-client -v ${fqdn}
rcctl stop httpd

Credentials

touch /etc/mail/credentials
chmod 0440 /etc/mail/credentials
chown _smtpd:_dovecot /etc/mail/credentials

Virtual Home

mkdir /var/vmail
useradd -c "Virtual Users Mail Account" -d /var/vmail -s /sbin/nologin -u 2000 -g =uid -L staff vmail
chown vmail:vmail /var/vmail

Create User

export user="user"; export pass=$(smtpctl encrypt PASSWORD)
echo "${user}@${domain}:${pass}:vmail:2000:2000:\
/var/vmail/${domain}/${user}::userdb_mail=maildir:\
/var/vmail/${domain}/${user}" \
>> /etc/mail/credentials 

add Domains

f="/etc/mail/vdomains"; test -f ${f} && cp ${f} "${f}-$(date +'%s')"

cat << EOF > ${f}
${domain}
yourdomain.de
someotherdomains.de
EOF
chown _smtpd:wheel ${f}; chmod 640 ${f}

Virtual Users - Main Domain

f="/etc/mail/vusers"; test -f ${f} && cp ${f} "${f}-$(date +'%s')"
cat << EOF >> ${f}
#
# Domain: ${domain}
#
abuse@${domain}:      root@${domain}
hostmaster@${domain}: root@${domain}
postmaster@${domain}: root@${domain}
webmaster@${domain}:  root@${domain}
# vmails
root@${domain}:       vmail
catchall@${domain}:   vmail
# catchall
@${domain}:           catchall@${domain}

EOF
chown _smtpd:wheel ${f}; chmod 640 ${f}

Virtual Users - additional Domains

echo -n "Name of additional Domain? "; read domain
echo "adding Domain: ${domain}
cat << EOF >> vusers
#
# Domain: ${domain}, added $(date)
#
abuse@${domain}:      root@${domain}
hostmaster@${domain}: root@${domain}
postmaster@${domain}: root@${domain}
webmaster@${domain}:  root@${domain}
# vmails
root@${domain}:       vmail
catchall@${domain}:   vmail
# catchall
@${domain}:           catchall@${domain}

EOF
chown _smtpd:wheel ${f}; chmod 640 ${f}

secrets

if we wanna relay via some “relayhost” and have to authenticate there ..

echo "label username:password" >> secrets
chown root:_smtpd secrets
chmod 640 secrets

smtpd.conf

f="/etc/mail/smtpd.conf"; test -f ${f} && cp ${f} "${f}-$(date +'%s')"

cat << EOF > ${f}
# This is the smtpd server system-wide configuration file.
# installed: $(date)

pki "${fqdn}" cert "/etc/ssl/${fqdn}.fullchain.pem"
pki "${fqdn}" key  "/etc/ssl/private/${fqdn}.key"

table aliases     file:/etc/mail/aliases        # ...
table secrets     file:/etc/mail/secrets        # For Relaying
table credentials passwd:/etc/mail/credentials  # Users for Rainloop
table vdomains    file:/etc/mail/vdomains       # List all Domains
table vusers      file:/etc/mail/vusers         # User / Alias Mapping


# Filter potential spam with rspamd
filter "rspamd" proc-exec "/usr/local/libexec/smtpd/filter-rspamd"

# DNS BL
filter dnsbl    proc-exec "filter-dnsbl -mv zen.spamhaus.org"

# Misc Filters
filter check_dyndns phase connect match rdns regex { '.*\.dyn\..*', '.*\.dsl\..*' } junk
filter check_rdns   phase connect match !rdns disconnect "550 no rDNS available"
filter check_fcrdns phase connect match !fcrdns disconnect "550 no FCrDNS available"
filter "senderscore" proc-exec "/usr/local/libexec/smtpd/filter-senderscore -blockBelow 10 -junkBelow 70 -slowFactor 1000"


# To accept external mail, replace with: listen on all
listen on lo0
listen on egress          tls         pki "${fqdn}" hostname "${fqdn}"                    filter { check_dyndns, check_rdns, check_fcrdns, dnsbl, rspamd, senderscore }
listen on egress port 587 tls-require pki "${fqdn}" hostname "${fqdn}" auth <credentials> filter { check_dyndns, check_rdns, check_fcrdns, dnsbl, rspamd, senderscore }


# Where to store incoming emails based on the target user.
action  "local_mail"      mbox alias <aliases>
action  "domain_vmail"    maildir "/var/vmail/%{dest.domain}/%{dest.user}"  virtual <vusers>

# Relay mails when they come from authorized clients.
action "outbound"         relay host smtp+tls://label@relay-host.net:587 auth <secrets>


# Block Bad Boys
match mail-from "@spammer.org"      for any                                       reject
match mail-from "@spammer.org"      for domain { "mydomain.net" "mydomain2.net" } reject
match mail-from "info@spammer.org"  for any                                       reject
match mail-from "user1@domain1.net" for rcpt-to regex "mydomain.*.net"            reject


# Next, we match incoming emails.
match       from any      for domain <vdomains>   action "domain_vmail"

# 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 self 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"
EOF
chown _smtpd:wheel ${f}; chmod 640 ${f}

Enable and Start Services

smtpd -n
rcctl enable smtpd rspamd redis
rcctl restart smtpd rspamd redis

Dovecot

cat << EOF >> /etc/login.conf

dovecot:\
  :openfiles-cur=1024:\
  :openfiles-max=2048:\
  :tc=daemon:
EOF

cap_mkdb /etc/login.conf
usermod -L dovecot _dovecot

may need a reboot?

local.conf

f="/etc/dovecot/local.conf"; test -f ${f} && cp ${f} "${f}-$(date +'%s')"

cat << EOF > ${f}
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/${fqdn}.fullchain.pem
ssl_key = </etc/ssl/private/${fqdn}.key
userdb {
  args = username_format=%u /etc/mail/credentials
  driver = passwd-file
  name =
}

protocol imap {
  mail_plugins = " imap_sieve"
}
EOF
chown root:wheel ${f}; chmod 640 ${f}

fix

sed -i.bak "s#^ssl_cert = </etc/ssl/dovecotcert.pem#ssl_cert = </etc/ssl/${fqdn}.fullchain.pem#" /etc/dovecot/conf.d/10-ssl.conf
sed -i.bak "s#^ssl_key = </etc/ssl/private/dovecot.pem#ssl_key = </etc/ssl/private/${fqdn}.key#" /etc/dovecot/conf.d/10-ssl.conf

sieve scripts

cd /usr/local/lib/dovecot/sieve
ftp https://blog.stoege.net/scripts/sieve-scripts.tar.gz
tar xzf sieve-scripts.tar.gz
rm sieve-scripts.tar.gz
sievec report-ham.sieve
sievec report-spam.sieve
chmod 0755 sa-learn-ham.sh
chmod 0755 sa-learn-spam.sh

Start Service

rcctl enable dovecot redis rspamd
rcctl restart dovecot redis rspamd

Check Login

doveadm user ${user}@${domain}
doveadm auth login ${user}@${domain}

rspamd

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

set SFP Record via DNS

your.domain. IN TXT "v=spf1 a ip4:your-public-ip mx ~all"

dkim

default._domainkey.your.domain. IN TXT "v=DKIM1;k=rsa;p=[…public key…]"

dmarc

_dmarc.example.com. IN TXT "v=DMARC1;p=none;pct=100;rua=mailto:postmaster@example.com"

xxx

f="/etc/rspamd/local.d/dkim_signing.conf"; test -f ${f} && cp ${f} "${f}-$(date +'%s')"

cat << EOF > ${f}
domain {
    example.com {
        path = "/etc/mail/dkim/private.key";
        selector = "default";
    }
}
EOF
chown root:wheel ${f}; chmod 640 ${f}

restart

rcctl enable redis rspamd
rcctl restart redis rspamd

Any Comments ?

sha256: 7060cbf5c4d145e055b7077f1b57fa5ad7f320fa040cdad189776da247ea523b