CTT – Correios de Portugal check-digit

CTT - Correios de PortugalI was unable to find anywhere a Portuguese national postal service, package registry number check-digit validation. So, I politely asked them and soon after a PDF describing the algorithm was on my desk.

It’s pretty simple and standard stuff (only an awkward multiplier order of each digit), so the best way to describe it’s with a validation function in PHP (even for non PHP users/programmers should be simple to enough to understand at a glance):

function checkDigitCTT($ref) {
    if (! preg_match('/^[a-z]{2}[0-9]{9}[a-z]{2}$/i', $ref))
        return false;
    $digits      = substr($ref, 2, 9);
    $multipliers = array(8,6,4,2,3,5,9,7);
    $tmp = 0;
    foreach ($multipliers as $k => $v)
        $tmp = $tmp + (((int) $digits[$k]) * $v);

    $tmp = round($tmp % 11);

    if ($tmp == 0)
        $check_digit = 5;
    else if ($tmp == 1)
        $check_digit = 0;
        $check_digit = 11 - $tmp;

    if ($check_digit == ((int) $digits[8]))
        return true;

    return false;

Raspberry PI open hotspot for your company site(s) only

Raspberry HotspotThe problem is really simple, you want/need to give open Wifi to your customers (let’s say inside a shop), but to you own company website (or websites) only. And nothing else, no other resources in the (internal or external) network.

The solution is simple and it comes in a tiny format… you will just need a Raspberry PI with a Wifi USB dongle that supports AP mode. Your company website should have an exclusive IP address

Side note: as normal, I’m not liable for any kind of mess, data loss, massive meteorite smash or other apocalyptic event in your world due to this guide.

Have the PI installed with the latest Raspbian, booted and logged in as root (sudo -s or equivalent).

Update the software sources:

apt-get update

Install the required software

apt-get install hostapd dnsmasq

Configure the wireless interface with a static IP address,
edit /etc/network/interfaces

iface wlan0 inet static
# pre-up iptables-restore < /etc/iptables.rules

and restart the interface

ifdown wlan0
ifup wlan0

Here I choosed the address to isolate the Wifi guests from the 192.168.1.x internal network. You should adapt it according to your existing set-up.

edit /etc/default/hostapd

and replace


now edit (it’s a new file) /etc/hostapd/hostapd.conf

For a full list of switches and whistles please do refer to http://w1.fi/cgit/hostap/plain/hostapd/hostapd.conf, we go with a very minimalistic (but functional) configuration


Here we can start the service.

service hostapd start

and I got the dreadful failed in red font… a lsusb command quickly showed the infamous RTL8188CUS chip:
Bus 001 Device 004: ID 0bda:8176 Realtek Semiconductor Corp. RTL8188CUS 802.11n WLAN Adapter

Thanks to the good people of the Internets you get a quick fix (you are downloading an external binary… so cross your fingers before installation, and nothing bad will happen to your PI… well, it worked for me).

wget http://dl.dropbox.com/u/1663660/hostapd/hostapd
chmod 755 hostapd
mv /usr/sbin/hostapd /usr/sbin/hostapd.ori
mv hostapd /usr/sbin/

and change in /etc/hostapd/hostapd.conf

service hostapd start

service [….] Starting advanced IEEE 802.11 management: ok
hostapdioctl[RTL_IOCTL_HOSTAPD]: Invalid argument

Even with the warning output the service managed to start and work correctly.

By now there should be an open network called WIFI-FREE-AS-BEER available to log in, but the process will stall in the Obtaining IP Address stage. So it’s time to move to the DHCP and DNS server.

Edit /etc/dnsmasq.conf, and place at the end of the file the lines


adjust the aaa.bbb.ccc.ddd to the exclusive public IP address of your company website. Basically we are configuring Dnsmasq to answer all name resolution queries to your public IP address, and setting DCHP leases to the Hostspot clients from IP to valid for 12h periods.

From now on it should be possible to log in to the Hotspot, but no data flow, so let’s take care of this now. First activate the kernel IP forwarding

echo 1 > /proc/sys/net/ipv4/ip_forward

and then adjust iptables rules

iptables -F
iptables -t nat -A POSTROUTING -o eth0 -j MASQUERADE
iptables -A FORWARD -i eth0 -o wlan0 -m state --state RELATED,ESTABLISHED -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p tcp -d aaa.bbb.ccc.ddd --dport 80 -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p tcp -d aaa.bbb.ccc.ddd --dport 443 -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p udp -d --dport 53 -j ACCEPT
iptables -A FORWARD -i wlan0 -o eth0 -p udp -d --dport 67:68 -j ACCEPT
iptables -A FORWARD -i wlan0 -j DROP 

remember to replace aaa.bbb.ccc.ddd with your the exclusive public IP address like in dnsmasq. From this point there should be a fully functional system. You can login to the Hotspot, and any http/https request will be landing in your company website. All other network traffic (except for the DHCP and name resolution will be blocked).

Now, to wrap up just make all this stuff survive reboots:

echo "net.ipv4.ip_forward=1" >> /etc/sysctl.conf

update-rc.d hostapd defaults
update-rc.d dnsmasq defaults

iptables-save > /etc/iptables.rules

and uncomment in /etc/network/interfaces the line
# pre-up iptables-restore < /etc/iptables.rules

There is just one thing left, avoid the captive portal detection and the respective sign in to network message. If you are using some kind of URL mapping/decoupling system (really hope you do) it’s pretty easy.

For Android, test for http://clients3.google.com/generate_204 request and send a 204 header and 0 bytes:

if (isset($script_parts) && $script_parts[0] == 'generate_204') {
    header('HTTP/1.1 204 No Content');
    header('Content-Length: 0');

For iOS lalaland test for the user agent ‘CaptiveNetworkSupport’ and send a 200 response:

if (isset($_SERVER['HTTP_USER_AGENT']) && preg_match('/CaptiveNetworkSupport/i', $_SERVER['HTTP_USER_AGENT'])) {
    header("HTTP/1.1 200 OK");

That’s it folks, and I wonder what will be the next use for this tiny big computer?

After all been working well and good for a long time, maybe after a reboot a problem surfaced. Maybe a whim of the bits gods, the system was using the dnsmasq on internal lookups for all interfaces ignoring the interface directive.

So for example if one ssshed into the raspberry and tried to wget google.com one would get our company site…. not good.

Simple fix, manually edit /etc/resolv.conf, you can use Google public DNS (not censored) or your LAN Router IP (that normally uses the upstream DNS of your provider).

# Google IPv4 nameservers

and to not be automatic overwritten by dhcpclient updates set the immutable bit:

chattr +i /etc/resolv.conf

Noticed that the raspberry was missing /etc/network/interfaces (no file at all and I don’t recall to delete it). Maybe the problem was due to this and Maybe it’s time for a new SD card and fresh install.

Btrfs killed ZFS (on my desktops)

not-btrfsFollowing the latest post about Btrfs, until yesterday both ZFS and Btrfs were running for testing and evaluation purposes, in my primary desktop machine.

The outcome was pretty straight forward, i wiped all the ZFS stuff from the disks.  Now, my desktop is just running Btrfs (also root/boot).


Very simple reason, Btrfs is very well integrated with the OS, right from the system install, to the automatic apt-snapshots (that already saved my ass a couple of times), to the graphical tools graceful integration. On the other side, ZFS…. let’s just say that to use ZFS you actually have to add a repository and then install ZFS in the system, and just then you are ready to start and use it.

Actually both of the file systems are from Oracle, but Btrfs is GPL licensed and ZFS is CDDL license so don’t expect a kernel/OS integration anytime soon….

For my personal needs, Btrfs has all the whistles and bells, mainly sub-volumes, snapshots and compression. And it’s pretty damn fast and stable (zero problems to this day). The only thing i miss is native encryption, but probably it will roll out in an upcoming version. Sure ZFS can be more mature and feature rich, but it’s lack of integration it’s just a pita (at least for linux desktop)…

For more in depth information about Btrfs i totally recommend this presentation:


Linux snapshots – apt-btrfs-snapshot

Since a couple (half dozen) of years I have been using the beautiful KDE Desktop in the very well made and supported Kubuntu distribution. As technology progresses and the switch on desktop computers from fast spinning disks to solid state disks is being made, I sleep better at night with a SSD… and if even SSDs can eventually fail (as Linus Torvalds knows…), you should always backup or cloud your important data, so I was much more worried by a system messed up with some update/upgrade or my own incompetence than trough hard drive failure.

So it was time to try snapshots in Linux. As always in Unix land there’s more than one way to cook an egg. You can go with LVM + classical File System, ZFS or Btrfs (and surely many other options). I did a new Kubuntu installation with the installer defaults using Btrfs as the file system (wanted to test drive Btrfs anyway) and from here is very simple to implement snapshots.

First thing, make sure that you have the default Btrfs setup

# btrfs subvolume list /
ID 257 gen 97520 top level 5 path @
ID 258 gen 97520 top level 5 path @home

Install apt-btrfs-snapshot

# apt-get install apt-btrfs-snapshot

And check that snapshots are supported

# apt-btrfs-snapshot supported

Here actually I get an error the first time i ran it

# apt-btrfs-snapshot supported
Traceback (most recent call last):
File "/usr/bin/apt-btrfs-snapshot", line 92, in
apt_btrfs = AptBtrfsSnapshot()
File "/usr/lib/python3/dist-packages/apt_btrfs_snapshot.py", line 113, in __init__
self.fstab = Fstab(fstab)
File "/usr/lib/python3/dist-packages/apt_btrfs_snapshot.py", line 76, in __init__
entry = FstabEntry.from_line(line)
File "/usr/lib/python3/dist-packages/apt_btrfs_snapshot.py", line 49, in from_line
return FstabEntry(*args[0:6])
TypeError: __init__() missing 3 required positional arguments: 'mountpoint', 'fstype', and 'options'

this was caused by unsupported fuse syntax entries in /etc/fstab, just had to change
sshfs#user@host:/path/ /mountpoint fuse options 0 0
user@host:/path/ /mountpoint fuse.sshfs options 0 0

and it will work. From now on the system is pretty much autonomous, every time you apt-get upgrade a snapshot will be made for you. Take notice that each snapshot is relative to root only, this means that /home is excluded, we are taking snapshot of the system not user files and configs…

# apt-btrfs-snapshot list
Available snapshots:

or you can force a new snapshot

# apt-btrfs-snapshot snapshot

to rollback, just issue

# apt-btrfs-snapshot set-default @apt-snapshot-2014-12-18_19:57:02

and reboot, yeah… fuckin awesome!

Note, i noticed some problems in apt-btrfs-snapshot to delete and list some of own snashots. Probably because of updates in btrfs or apt-btrfs-snapshot itself (pretty common after a distribution upgrade). The delete command doesn’t works as expected and btrfs gives also error. The situation is like this:

You see the snapshot in the list:

#btrfs subvolume list /
ID 257 gen 361392 top level 5 path @
ID 258 gen 361392 top level 5 path @home
ID 505 gen 361392 top level 5 path @apt-snapshot-2015-11-12_13:01:23

But when you go to delete it, btrfs spits an awful ERROR: error accessing…

btrfs subvolume delete @apt-snapshot-2015-11-12_13:01:23
Transaction commit: none (default)
ERROR: error accessing '@apt-snapshot-2015-11-12_13:01:23'

But the solution it quite simple, just mount all the btrfs device and delete it by path:

#mount /dev/sda1 /mnt/
# ls /mnt/
drwxr-xr-x 1 root root 78 Nov 12 13:01 ./
drwxr-xr-x 1 root root 244 Ago 4 12:30 ../
drwxr-xr-x 1 root root 244 Ago 4 12:30 @/
drwxr-xr-x 1 root root 244 Ago 4 12:30 @apt-snapshot-2015-11-12_13:01:23/
drwxr-xr-x 1 root root 40 Fev 7 2015 @home/
# btrfs subvol delete /mnt/@apt-snapshot-2015-11-12_13\:01\:23/
Transaction commit: none (default)
Delete subvolume '/mnt/@apt-snapshot-2015-11-12_13:01:23'
# cd /
# umount /mnt

even so, sometimes when you try to manual delete like above you get an

ERROR: cannot delete ‘/mnt/@apt-snapshot-2015-11-12_13:01:23’: Directory not empty

in this situation just dig a bit deeper

#cd /mnt/@apt-snapshot-2015-11-12_13:01:23
# rm -rf *
rm: cannot remove 'var/lib/machines': Operation not permitted
# subvol delete /mnt/@apt-snapshot-2015-11-12_13:01:23/var/lib/machines/
# subvol delete /mnt/@apt-snapshot-2015-11-12_13:01:23/
# cd /
# umount /mnt

You can thank me later

My Qmail installation guide reloaded

Qmail ReloadedA couple of years ago I posted my Qmail installation guide, and has expected it served me good when was time to reform to the old mail server. But, i made some changes on this iteration and i think is more polished and shiny than ever.

Again, this is to my own reference, but i will be very glad if it also can help someone. On the other hand, if you follow it, and nukes your system or kills every life form on Earth please don’t blame me. You are warned.

The old picture:


2 Qmail instances, 1 published MX record that accepts emails from other MTAs, does the RBL checks and forwards the passed emails to the main Qmail instance via artificial smtproutes. The forwarded emails are then checked against virus (by Clamav) and spam (by SpamAssassin) trough qmail-scanner qmail-queue drop in replacement.

Users receive and send email trough the non published MX Qmail instance. They need to smtp-auth to relay email (send email to remote domains). Delivery to local domains doesn’t require smtp-auth.

Identified problems:

1 – One problem is that the main Qmail instance (that has no published MX records), that works with Vpopmail and holds all the accounts information, maildirs and email is somehow vulnerable:

The main weakness of this installation, is that if a clever spammer discovers that mail.domain.com accepts incoming emails for local domains, he can spam down your users bypassing the rbl tests.

also, one has to rememeber that has SPF and A records published, and it’s IP is printed on all outgoing email headers, so it’s not anonymous.

2 – The user debug is somewhat tricky, if there is a smtp-auth client configuration problem. The problem is that the user will be able to send emails to local domains, but will get the dreadful 553 sorry that domain isn’t in my list of allowed rcpthosts (#5.7.1) error.

3 – Qmail-scanner, is a very neat piece of software, but it is fundamentally flawed performance wise because for each and every email it must load the PERL interpreter.

4 – Restarting Qmail every 15m to recognize new or deleted domains is plain dumb.

The new picture:


All of the previous mentioned issues have been addressed and polished. The main Qmail instance (mail) will only accept outside authenticated connections for both local and remote deliveries. The external email comes trough the published mx record Qmail instance only, filtered by rbl, then routed to Amavis for virus and spam scans, and finally routed to the main Qmail instance (if virus and spam free). In this scenario you must trust your customers, because as they authenticate and send emails, these will bypass all the virus and spam checks.

Let’s put our hands to work, the first slice is on point 15 of the original guide “Clam Anti Virus, Spam Assassin and Qmail-scanner”, this version will move the virus and spam filter to the other Qmail instance. So follow the original guide until point 15, and then:

1 – Install qfilter

cd /usr/ports/mail/qmail-qfilter/
make install clean

2 – Make a shell script wrapper that will invoke the filters used by qfilter

mkdir -p /var/qmail/qfilter
edit /var/qmail/qfilter/qfilter-wrapper

and put these contents on the file

exec /usr/local/bin/qmail-qfilter /var/qmail/qfilter/smtp-auth-only

save and mark it executable

chmod +x /var/qmail/qfilter/smtp-auth-only

actually there is only one filter being invoked (smtp-auth-only), but qfilter supports several filters (exec /usr/local/bin/qmail-qfilter /path/to/filter-one –/path/to/filter-two –/path/to/filter-three)

3 – Install the smtp-auth-only filter

This is just a very simple perl script that will test the presence of the environment variable TCPREMOTEINFO, as this variable is only set upon successful smtp-auth. If the mail comes from an authenticated user the script returns 0, else if it’s from a non-authenticated user the script returns 31 signaling a permanent error.

edit /var/qmail/qfilter/smtp-auth-only

the script is very simple


if (defined $ENV{'TCPREMOTEINFO'} == false) {
        use Sys::Syslog qw(:DEFAULT :standard);
        openlog("qfilter", 'ndelay,pid', 'mail');
        syslog('info', "No SMTP-Auth - Rejecting Email");
        exit 31;

exit 0;

save it and mark it executable

chmod +x /var/qmail/qfilter/smtp-auth-only

4 – Adjust /etc/tcp.smtp to use qfilter

this is my last line now of /etc/tcp.smtp


it accepts connections from everywhere (if cpu load > 20 rejects connections) it bypasses SPF and RBL checks, and it uses qfilter-wrapper as qmailqueue. After

qmailctl cdb

to build the new smtp tcp rules cdb file and reload qmail, the main Qmail instance will only accept authenticated user email. Email routed from mx should match a previous /etc/tcp.smtp rule.

5 – Install Clam Anti Virus, Spam Assassin and Amavis

This step kind of mimics the step 15 on the original guide, with two main differences. We are installing all the filtering software on the MX instance of Qmail. And qmail-scanner as been replaced by Amavis.

So, log in to the MX console and install the software.

cd /usr/ports/security/clamav
make install clean


cd /usr/ports/mail/spamassassin
make install clean


cd /usr/ports/security/amavisd-new
make install clean


as always on FreeBSD the installation is easy and a breeze. Now the fun part, configuring and make all this work together…

6 – Configure Clam Anti Virus

First ClamAV and FreshClam (the anti-virus updater daemon). Here’s a comment striped out of /usr/local/etc/clamd.conf

LogSyslog yes
LogFacility LOG_MAIL
LogVerbose yes
ExtendedDetectionInfo yes
PidFile /var/run/clamav/clamd.pid
DatabaseDirectory /var/db/clamav
LocalSocket /var/run/clamav/clamd.sock
FixStaleSocket yes
ReadTimeout 300
CommandReadTimeout 5
User vscan
AllowSupplementaryGroups yes
ScanMail yes

and the comment stripped version of /usr/local/etc/freshclam.conf

DatabaseDirectory /var/db/clamav
LogVerbose yes
LogSyslog yes
LogFacility LOG_MAIL
PidFile /var/run/clamav/freshclam.pid
DatabaseOwner vscan
AllowSupplementaryGroups yes
DatabaseMirror database.clamav.net
NotifyClamd /usr/local/etc/clamd.conf

There are few modifications to the distribution configuration files, mainly 2 things, to run clamd/freshclam daemons as the user ‘vscan’, the same user that will run amavis, and to log via syslog mail facility.

It makes perfect sense to take advantage of syslog and newsyslog automatic maintenance and log rotation. Also, having most of stuff logging to /var/log/mail makes it easy to spot any error message outputted by any of the several components. The downsize, is that in a busy server the log can become a bit messy.

Adjust the ownership on ClamAV directories:

chown -R vscan:vscan /var/db/clamav
chown -R vscan:vscan /var/run/clamav

add the rcvars to /etc/rc.conf

and start both of the daemons

/usr/local/etc/rc.d/clamav-clamd start
/usr/local/etc/rc.d/clamav-freshclam start

7 – Configure Spamassassin

First, as Spamassassin uses the GeoIP database, you should have an updated database on /usr/local/share/GeoIP/GeoIP.dat, to do so automaticaly write this file on /usr/local/etc/periodic/daily/updategeoip


cd /usr/local/share/GeoIP
/usr/local/bin/wget -q http://geolite.maxmind.com/download/geoip/database/GeoLiteCountry/GeoIP.dat.gz
gzip -d -f GeoIP.dat.gz

exit 0

and mark it executable

chow +x /usr/local/etc/periodic/daily/updategeoip

and run it manually (you should have installed on your system /usr/ports/ftp/wget)


update and compile Spamassassin rules


and make this process automatic, edit /usr/local/etc/periodic/weekly/spamassassin

#! /bin/sh

/usr/local/bin/sa-update && /usr/local/bin/sa-compile

exit 0

mark it executable, and run by hand the first time

chmod +x /usr/local/etc/periodic/weekly/spamassassin

Spamassassin doesn’t need so much configuration, and it pretty much works out of the box, but i made some fine tuning to everything play happy, so there it is the commented striped version of /usr/local/etc/mail/spamassassin/local.cf

use_dcc 1
dcc_home /var/dcc
dcc_path /usr/local/bin/dccproc
dcc_timeout     10
add_header all  DCC _DCCB_: _DCCR_
use_pyzor 1
pyzor_path /usr/local/bin/pyzor
use_razor2 1
razor_config /var/amavis/.razor/razor-agent.conf
score RAZOR2_CHECK 2.500
score PYZOR_CHECK 2.500
score DCC_CHECK 4.000

create the /var/amavis/.razor directory, and set up razor

mkdir /var/amavis/.razor
razor-admin -home=/var/amavis/.razor -create
razor-admin -home=/var/amavis/.razor -discover

and change ownership to the vscan user

chown -R vscan:vscan /var/amavis/.razor

time to set up the rc vars at /etc/rc.conf and start Spamassassin (replace aaa.bbb.ccc.ddd for the allowed IP address to connect)


and start it

/usr/local/etc/rc.d/sa-spamd start

8 – Configure Amavis

Amavis will be the glue between Qmail and ClamAV and Spamassassin in a dual MTA setup. It will accept routed emails from Qmail (mx instance) on port 10024, fiter, and re-route to the main Qmail for local delivery (email instance).

Here it is the /usr/local/etc/amavisd.conf configuration file. Now some customizations required to amavis work properly:

  • set $mydomain and $myhostname to your host fqdn
  • configure $forward_method = ‘smtp:[aaa.bbb.ccc.ddd]:25’; Set aaa.bbb.ccc.ddd to the IP address of the main Qmail instance (email host) to where the filtered emails are forward. Remember that in the Qmail instance you need a corresponding entry in /etc/tcp.smtp that accepts the forward emails and skips SPF and RBL checks (replace aaa.bbb.ccc.ddd for the incoming IP of Amavis):
  • the $max_servers should, as commented, match the width of your MTA pipe /var/qmail/control/concurrencylocal
  • @local_domains_maps = [‘.’]; we accept every incoming email as a local domain email, because by configuration the mx Qmail instance will only accept and forward to Amavis emails to local domains
  • customize $inet_socket_bind, generally the loopback address IP should be fine, but if your are running inside a jail (and if you are following this guide you are) replace the loopback IP for the main jail IP
  • setup @inet_acl list (this is space delimited list of IPs that Amavis will accept email from). If you are running everything in the same jail (Qmail mx instance and Amavis) this is the main jail IP, if Qmail mx is running in other jail or host add the mx Qmail outgoing IP

These are some of the most important things that you should consider to setup Amavis to your own taste, and as pretty neat software everything (or just about everything) is customizable. The configuration file has extensive comments so it’s easy to understand each and every option:

  • In this setup Amavis is logging to syslog mail facility, $DO_SYSLOG = 1; and $SYSLOG_LEVEL = ‘mail.info’; scroll up to find out why. You can change this to a $LOGFILE. Also setup the $log_level
  • Virus, banned and spam (after $sa_kill_level_deflt threshold) emails are plain discarded, bounce only in case of bad headers.
    $final_virus_destiny = D_DISCARD; # (defaults to D_DISCARD)
    $final_banned_destiny = D_DISCARD; # (defaults to D_BOUNCE)
    $final_spam_destiny = D_DISCARD; # (defaults to D_BOUNCE)
    $final_bad_header_destiny = D_BOUNCE; # (defaults to D_PASS), D_BOUNCE suggested
  • you can customize $virus_admin and $spam_admin with a email address to receive reports when virus/spam email is detected, in this case you should also configure the from addresses in $mailfrom_notify_admin, $mailfrom_notify_recip, $mailfrom_notify_spamadmin
  • this configuration example does not notify me of positives, but i keep them in a quarantine dir, so i can do postmortem analysis and recovery,
    $QUARANTINEDIR = ‘/var/virusmails’;
    # Separate quarantine subdirectories virus, spam, banned and badh within
    # the directory $QUARANTINEDIR may be specified by the following settings
    # (the subdirectories need to exist – must be created manually):
    $virus_quarantine_method = ‘local:virus/virus-%i-%n’;
    $spam_quarantine_method = ‘local:spam/spam-%b-%i-%n’;
    $banned_files_quarantine_method = ‘local:banned/banned-%i-%n’;
    $bad_header_quarantine_method = ‘local:badh/badh-%i-%n’;
  • you can also customize the spam score required to each action
    $sa_tag_level_deflt = undef; # always add spam info headers
    $sa_tag2_level_deflt = 5.0; # subject will be re-written with $sa_spam_subject_tag value
    $sa_kill_level_deflt = 10; # email will not be delivered, and we keep a copy in quarantine
    $sa_dsn_cutoff_level = 15; # Since we are using D_DISCARD, this setting will serve no purpose, but if you were using D_BOUNCE, you can use this to set a level at which the sender will no longer be notified

and many more options that you can/should look into. If you are going to quarantine emails you should create the quarantine directories:

mkdir -p /var/virusmails/badh/
mkdir -p /var/virusmails/banned/
mkdir -p /var/virusmails/spam/
mkdir -p /var/virusmails/virus/

chown -R vscan:vscan /var/virusmails

also, it’s not a bad idea to put a line in root cron to delete older (30 days older) quarantined emails:

crontab -e

05 05 * * * /usr/bin/find /var/virusmails/* -type f -mtime +30 -exec /bin/rm -f {} \;

Finally! add the rc var at /etc/rc.conf


and start it

/usr/local/etc/rc.d/amavisd start

9 – Configure Qmail to use Amavis

Just a simple php script run every 10 minutes by cron will take care of this. As a bonus when you add, rename or delete a domain the Qmail mx instance will pick up the changes.

Edit /var/qmail/control/make_smtp_routes and adjust aaa.bbb.ccc.dd with the Amavis listening IP:port ($inet_socket_bind in amavisd.conf):

#! /usr/local/bin/php

$smtp_route = 'aaa.bbb.ccc.ddd:10024';

$rcpthosts     = file('/var/qmail/control/rcpthosts');
$morercpthosts = file('/var/qmail/control/morercpthosts');

$hosts = array_merge($rcpthosts, $morercpthosts);
$hosts = array_filter($hosts);

$fp = fopen("/var/qmail/control/smtproutes.tmp", "w");
foreach ($hosts as $host)
    fwrite($fp, trim($host).":".$smtp_route."\n");

if (md5_file('/var/qmail/control/smtproutes.tmp') == md5_file('/var/qmail/control/smtproutes')) {

syslog(LOG_INFO, "New /var/qmail/control/smtproutes");

rename("/var/qmail/control/smtproutes.tmp", "/var/qmail/control/smtproutes");

syslog(LOG_INFO, "Restarting Qmail");
exec('/root/bin/qmailctl restart');



mark it executable

chown +x /var/qmail/control/make_smtp_routes

and add it to cron

cron -e
*/10 * * * * /var/qmail/control/make_smtp_routes > /dev/null 2>&1

That’s it, this is the end. Now go grab a well deserved beer and behold your brand new system.

Final toughts

The system is cool, addressed the issues of the old system and is maintenance free. But, there is some space to improvements:
– develop an API (work in progress) that allows for administration, domain management and email management of the system. With this piece in place is then easy to integrate and develop admin and control panels that replace the outdated qmailadmin panel and administrative tasks on the command line.
– related with the API, to give domain managers the possibility to fine tune per domain anti-virus, spam, quarantine and notification settings. This also implies a deeper knowledge of Amavis configuration.
– to compile a complete and comprehensive guide that incorporates the original guide and the stuff on this one.