(April 2018)
This is the day-to-day runbook for an OpenBSD mail smarthost. For installation and configuration, see https://paulgorman.org/technical/openbsd-smarthost.txt.html.
OpenBSD releases a new version every six months, along with upgrade instructions. OpenBSD provides updates for the current an previous version (i.e., for 12 months). The upgrade procedure is generally painless: boot from the install media, choose “upgrade”, and hit “y” a couple of times.
Between version upgrades, apply system updates like:
# syspatch
Update add-on packages with:
# pkg_add -Uu
Add or delete the address from the text file /etc/mail/relay-recipients
, then restart OpenSMTPD.
(Is the restart necessary?)
$ doas vim /etc/mail/relay-recipients
$ doas rcctl restart smtpd
OpenSMTPD logs to /var/log/maillog
.
By default, OpenBSD gzips the mail logs, which can still be accessed with tools like zgrep
and zcat
.
However, we have set log rotation to not compress the mail logs.
Older mail logs are available at /var/log/maillog.0
, /var/log/maillog.1
, etc.
systemctl
or service
?OpenBSD has its own init system (rc), with its own service management tool (rcctl).
$ rcctl help
usage: rcctl get|getdef|set service | daemon [variable [arguments]]
rcctl [-df] check|reload|restart|stop|start daemon ...
rcctl disable|enable|order [daemon ...]
rcctl ls all|failed|off|on|started|stopped
# rcctl ls all
# rcctl ls started
# rcctl check smtpd
# rcctl stop smtpd
# rcctl start smtpd
# rcctl restart smtpd
# rcctl reload smtpd
What if the service fails to start?! Try starting it with the debug flag:
# rcctl -d start amavisd
Edit /etc/mail/amavis-senders
.
Each line in the file adds to or subtracts from the final spam score.
bad.example.com 10
good.example.org -5
After saving /etc/mail/amavis-senders
:
$ doas rcctl restart spamassassin amavisd
(Is the service restart necessary? I’m not sure.)
Note: we specify the white/blacklist file in /etc/amavisd.conf
:
@score_sender_maps = ({
'.' => [
read_hash("/etc/mail/amavis-senders"),
…
$ spamdb | grep my.user
# doas pfctl -t spamd-white -T add 203.0.113.119
A root cronjob updates the lists every hour. Since spamd works through PF, we can check the current firewall rules.
# pfctl -s Tables
# pfctl -t spamd-white -T show
What about the greylist?
Spamd maintains its greylist in /var/db/spamdb
, which we can examine with the spamdb
tool.
# spamdb
(Is spamdb
only for greylisted addresses, or for everything? If so, spamdb
may be the better tool to examine and manipulate entries than pfctl
.)
Add the domain to /etc/mail/common_domains
, like:
amazonses.com
aol.com
att.net
charter.net
comcast.net
cox.net
email.com
facebook.com
facebookmail.com
gmail.com
googlemail.com
google.com
hotmail.com
icloud.com
mac.com
me.com
mail.com
msn.com
live.com
outlook.com
sbcglobal.net
verizon.net
yahoo.com
Use the spf_update_pf
utility script to grab the SPF records for those domains, and insert them into our whitelist.
$ doas /usr/local/bin/spf_update_pf
40 addresses added.
$ doas rcctl restart smtpd
https://github.com/akpoff/spf_fetch
Use shutdown -h -p +1
.
The -p
flag allows power-down after halting the system.
Note that it’s safe to virsh destroy myvm
a halted VM that was shut down without the -p
flag.
# smtpctl show queue
Amavis quarantines bad messages in /var/virusmails
.
When it quarantines a message, it leaves an entry in the mail log with the quarantine file name (e.g., quarantine: badh-zROkaXVT9p33
).
Release the message from quarantine, and deliver it to its recipient, like:
# amavisd-release badh-zROkaXVT9p33
RCVD_IN_IADB_DOPTIN
)?Find the best, though also terse, description in the SpamAssassin code:
$ grep -i describe /usr/local/share/spamassassin/* | grep RCVD_IN_IADB_DOPTIN
doas
? Where’s sudo
?Although the OpenBSD project originally developed sudo
, they’ve come to view it as too complex for how most people use it.
OpenBSD has developed doas
as simpler replacement for sudo
.
If typing doas
is bothersome, create a shell alias, like:
alias sudo='doas'
See doas(1).
There are still use cases for sudo
.
It’s available as a package, if needed.
OpenBSD defaults to ksh
.
It’s not too bad once you get used to it.
Check your current shell with:
$ echo $SHELL
The bash
shell is available as a package, along with zsh
and others.
Check the available shells with:
$ cat /etc/shells
If Bash is not listed, install it like:
# pkg_add bash
Once Bash is installed, change the shell for your user like:
$ chsh -s /usr/local/bin/bash
The adduser
and rmuser
prompt for details about the creation or removal of a user.
The adduser
command is generally what we want, rather than the lower-level useradd
command.
# adduser
Drop a user with the userdel
command.
We generally add IT staff to the “wheel” group, which allows them to use doas
.
Add an existing user to the wheel group:
# usermod -G wheel alice
Change passwords with the passwd
command.
gtimeout
(or another GNU utility)?The coreutils
package provides many GNU utilities.
Many of the binaries from this package are named like gtimeout
.
See a list of binaries in the package:
$ pkg_info -L coreutils | grep '/bin/'
Edit /etc/mail/spamassassin/local.cf
, and add a rule like:
header INVOICE_PHISH_SUBJECT Subject =~ /\binvoice/i
score INVOICE_PHISH_SUBJECT 2
Then:
$ doas rcctl restart spamassassin amavisd
spamassassin(ok)
spamassassin(ok)
amavisd(ok)
amavisd(ok)
$ smtpctl pause mta
$ smtpctl show queue
$ smtpctl envelope a9de9a39de27101b
$ smtpctl remove a9de9a39de27101b
$ smtpctl resume mta
score_sender_maps
) from Amavis working?!Matching messages get tagged.
Grep the mail logs for AM.WBL
.
spamdb
is hard to read!#!/usr/bin/env perl
# This script formats the output of `spamdb` on OpenBSD to make it more readable by humans.
# Paul Gorman, May 2018
use strict;
use warnings;
use POSIX qw(strftime);
sub hd {
return strftime("%H:%M:%S %b %d", localtime($_[0]));
}
my @spamdb = split(/\n/, `spamdb`);
foreach my $line (@spamdb) {
my @l = split(/\|/, $line);
if ($l[0] eq 'GREY') {
my $ip = $l[1];
my $helo = $l[2];
my $sadr = substr($l[3], 1, -1);
my $radr = substr($l[4], 1, -1);
my $fdt = strftime("%b %d %H:%M:%S", localtime($l[5]));
my $pdt = strftime("%b %d %H:%M:%S", localtime($l[6]));
my $edt = strftime("%b %d %H:%M:%S", localtime($l[7]));
my $bct = $l[8];
my $pct = $l[9];
printf("%s 1ST %s PASS %s EXP %s BLCT %-3s PSCT %-3s %-15s %-30s %s -> %s\n",
$l[0], $fdt, $pdt, $edt, $bct, $pct, $ip, $helo, $sadr, $radr);
} elsif ($l[0] eq 'WHITE') {
my $ip = $l[1];
my $fdt = strftime("%b %d %H:%M:%S", localtime($l[4]));
my $pdt = strftime("%b %d %H:%M:%S", localtime($l[5]));
my $edt = strftime("%b %d %H:%M:%S", localtime($l[6]));
my $bct = $l[7];
my $pct = $l[8];
printf("%s 1ST %s PASS %s EXP %s BLCT %-3s PSCT %-3s %s \n",
$l[0], $fdt, $pdt, $edt, $bct, $pct, $ip);
} else {
print join(' ', @l), "\n";
}
}
To sort output of the following script by score:
mlog | sort -n -k 10
Here’s the mlog
script:
#!/usr/bin/env perl
# This script get Passed lines from the mail log (Amavis+OpenSMTPD), and pretty prints them.
# Paul Gorman, May 2018
use strict;
use warnings;
# May 11 12:30:44 nostromo amavis[96961]: (96961-08) Passed CLEAN {RelayedInbound}, [127.0.0.1] [216.82.180.36] /ESMTP <prvs=6625f3126=john.smith@example.com> -> <Kristen.zozol@example.com>, (ESMTPS://216.82.180.36), Message-ID: <FF68A08914FEC846A95D6980E128406D0110B89EC9@s1flokydce2kx06.dm0001.info53.com>, mail_id: 9zW_7ljoGzGW, b: yTqus-1QJ, Hits: -5.11, size: 5213275, queued_as: 250 2.0.0: 7433ece1 Message accepted for delivery, Subject: "Mail float", From: <Aaron.Davidson@53.com> (dkim:AUTHOR), helo=, Tests: [DKIM_SIGNED=0.1,DKIM_VALID=-0.1,DKIM_VALID_AU=-0.1,HTML_MESSAGE=0.001,RCVD_IN_DNSWL_HI=-5,SPF_PASS=-0.001,T_RP_MATCHES_RCVD=-0.01], autolearn=ham autolearn_force=no, autolearnscore=-5.109, dkim_i=Aaron.Davidson@53.com,@53.com, dkim_sd=main:53.com, 7886 ms
# May 1 11:45:57 nostromo amavis[5854]: (05854-07-3) Passed CLEAN {RelayedInbound}, [127.0.0.1] [63.149.153.5] /ESMTP <BWells@example.com> -> <porman@example.com>, (SMTP://63.149.153.5), Message-ID: <C57EDD7078AFAB4D9932218DF902B664542C2929@tl-exch.saipeople.local>, mail_id: d4u1yXswW4yZ, b: BXbN6qlH2, Hits: -1.899, size: 32133, queued_as: 250 2.0.0: 2782a352 Message accepted for delivery, Subject: "Resumes Ready - Position?", From: <BWells@example.com>, helo=, Tests: [BAYES_00=-1.9,HTML_MESSAGE=0.001], autolearn=ham autolearn_force=no, autolearnscore=0.001, 762 ms
# Regex captures:
# 1 sender ip
# 2 from email
# 3 to email
# 4 hits
# 5 size
# 6 subject
my $r = '.+amavis.+Passed.+\[127.0.0.1\] \[(\d{1,3}\.\d{1,3}\.\d{1,3}\.\d{1,3})\].+/ESMTP <(.+@.+)> -> <(.+@.+)>, \(.?SMTP.+Hits:\s+([\-\d\.]+), size: (\d+),.+Subject: "(.+)", From:.+';
my @log = split(/\n/, `cat /var/log/maillog`);
foreach my $line (@log) {
if ($line =~ /$r/) {
my @l = split(/\s+/, $line);
my $s = reverse(substr(reverse($2), 0, 50));
printf("%s %2s %s %9s B %-15s %50.50s -> %-40.40s %7.3f hits \"%.65s\"\n",
$l[0], $l[1], $l[2], $5, $1, $s, $3, $4, $6);
}
}
I often filter output of the above through this:
#!/bin/sh
set -euf
mlog | tail -350 | sort -n -k 10 | vim -R -