Cheap VPS & Xen Server

Residential Proxy Network - Hourly & Monthly Packages

The Perfect SpamSnake – Ubuntu Jeos 12.04 LTS Precise Pangolin

Postfix w/Bayesian Filtering, Postscreen, Postfix Recipient Callout (Relay Recipients via look-ahead Optional), Nginx/Uwsgi, Mysql, Dnsmasq, MailScanner (Spamassassin, ClamAV, Pyzor, Razor, DCC-Client), Baruwa, SPF Checks, FuzzyOcr, Sanesecurity Signatures, Greyfix, KAM, Scamnailer, FireHOL (Iptables Firewall), Relay Recipients Script (Optional), Webmin (Optional), Outgoing Disclaimer with alterMIME (Optional)

This tutorial shows how to set up an Ubuntu Jeos based server as a spamfilter in Gateway mode. In the end, you will have a SpamSnake Gateway which will relay clean emails to your MTA. You will also be able to view your incoming queue, train your SpamSnake and carry out a few more advanced operations via Baruwa.

I cannot offer any guarantees that this will work for you, the same way it’s working for me.

I will use the following software:
• Web Server: Nginx v1.1.19/Uwsgi v1.0.3
• Database Server: MySQL v5.5.28
• Mail Server: Postfix v2.9.3
• Caching DNS Server: Dnsmasq 2.59
• Filter: MailScanner v4.84.5-3
• Frontend: Baruwa v1.1.2-4sn

Credit goes to the guys at Kreationnext and the developers of MailScanner, Baruwa, Clamav, Nginx/Uwsgi, Mysql, Postfix, Spamassassin, Razor/Pyzor/DCC and Firehol.



1. Install minimum vm option
Set hostname to server1
2. Default guided partition method
3. Setup user:
u: administrator
p: password
No encryption
4. No auto-updates
5. Install OpenSSH



1. Get root Privileges

Enable the root login by running the following and giving root a password. You can then directly log in as root:

sudo passwd root


2. Configure The Network

Because the Ubuntu installer has configured our system to get its network settings via DHCP, we have to change that now because a server should have a static IP address. Edit /etc/network/interfaces and adjust it to your needs (in this example setup I will use the IP address

vi /etc/network/interfaces

and make it look like the following:

# This file describes the network interfaces available on your system
# and how to activate them. For more information, see interfaces(5).
# The loopback network interface
auto lo
iface lo inet loopback
# The primary network interface
auto eth0
iface eth0 inet static

Then restart your network:

/etc/init.d/networking restart

vi /etc/hosts

and make it look like this:       localhost.localdomain   localhost     server1
# The following lines are desirable for IPv6 capable hosts
::1     localhost ip6-localhost ip6-loopback
fe00::0 ip6-localnet
ff00::0 ip6-mcastprefix
ff02::1 ip6-allnodes
ff02::2 ip6-allrouters
ff02::3 ip6-allhosts

Now run:

echo > /etc/hostname
reboot now

Afterwards, run:

hostname -f

Both should show now.


3. Change The Default Shell

/bin/sh is a symlink to /bin/dash, however we need /bin/bash, not /bin/dash. Therefore we do this:

dpkg-reconfigure dash

Install dash as /bin/sh? <– No

Install a few packages and requirements that are needed later on:

apt-get install binutils cpp fetchmail flex gcc libarchive-zip-perl libc6-dev libcompress-raw-zlib-perl libdb4.8-dev libpcre3 libpopt-dev lynx m4 make ncftp nmap openssl perl perl-modules unzip zip zlib1g-dev autoconf automake1.9 libtool bison autotools-dev g++ build-essential telnet wget gawk -y


4. Caching Dnsmasq

apt-get install dnsmasq -y

vi /etc/dnsmasq.conf

and make Dnsmasq listen on localhost:



5. Install Mysql

apt-get install mysql-client mysql-server libdbd-mysql-perl -y

You will be asked to provide a password for the MySQL root user – this password is valid for the user root@localhost as well as, so we don’t have to specify a MySQL root password manually later on:

New password for the MySQL “root” user: <– yourrootsqlpassword
Repeat password for the MySQL “root” user: <– yourrootsqlpassword

6. Install Postfix:

apt-get install postfix postfix-mysql postfix-doc procmail -y

You will be asked two questions. Answer as follows:

General type of mail configuration: –> Internet Site
System mail name: –>

Stop Postfix:

postfix stop

vi /etc/postfix/

and make it look like the following:

pickup    fifo  n       -       -       60      1       pickup
         -o content_filter=
         -o receive_override_options=no_header_body_checks


vi /usr/src/

with the following:

postconf -e "alias_maps = hash:/etc/aliases"
postconf -e "myorigin = domain.tld"
postconf -e "myhostname = server1.domain.tld"
postconf -e "mynetworks =,"
postconf -e "message_size_limit = 10485760"
postconf -e "local_transport = error:No local mail delivery"
postconf -e "mydestination = "
postconf -e "local_recipient_maps = "
postconf -e "relay_domains = mysql:/etc/postfix/"
postconf -e "relay_recipient_maps = mysql:/etc/postfix/"
postconf -e "transport_maps = mysql:/etc/postfix/"
postconf -e "virtual_alias_maps = hash:/etc/postfix/virtual"
postconf -e "disable_vrfy_command = yes"
postconf -e "strict_rfc821_envelopes = no"
postconf -e "smtpd_banner = $myhostname ESMTP SpamSnake"
postconf -e "smtpd_delay_reject = yes"
postconf -e "smtpd_recipient_limit = 100"
postconf -e "smtpd_helo_required = yes"
postconf -e "smtpd_client_restrictions = permit_sasl_authenticated, permit_mynetworks, permit"
postconf -e "smtpd_helo_restrictions = permit_sasl_authenticated, permit_mynetworks, permit"
postconf -e "smtpd_sender_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_non_fqdn_sender, reject_unknown_sender_domain, permit"
postconf -e "smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unknown_recipient_domain, reject_unauth_destination, whitelist_policy, grey_policy, rbl_policy, spf_policy, permit"
postconf -e "smtpd_data_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_pipelining"
postconf -e "smtpd_restriction_classes = spf_policy, grey_policy, whitelist_policy"
postconf -e "spf_policy = check_policy_service unix:private/policy-spf"
postconf –e "policy-spf_time_limit = 3600s"
postconf -e "rbl_policy = reject_rbl_client, reject_rbl_client"
postconf -e "grey_policy = check_policy_service unix:private/greyfix"
postconf -e "whitelist_policy = check_client_access mysql:/etc/postfix/, check_sender_access mysql:/etc/postfix/"
postconf -e "header_checks = regexp:/etc/postfix/header_checks"
touch /etc/postfix/virtual
echo "root" >> /etc/postfix/virtual && echo "abuse" >> /etc/postfix/virtual && echo "postmaster" >> /etc/postfix/virtual
postmap /etc/postfix/virtual
touch /etc/postfix/header_checks
echo "/^Received:/ HOLD" >> /etc/postfix/header_checks
postmap /etc/postfix/header_checks
cat > /etc/postfix/ <<EOF
user = baruwa
password = password
dbname = baruwa
query = select concat('PERMIT') 'action' from lists where from_address='%s' AND list_type='1';
hosts =
cat > /etc/postfix/ <<EOF
user = baruwa
password = password
dbname = baruwa
query = select concat(address, ' ', 'OK') 'domain' from user_addresses where user_addresses.address='%s' and user_addresses.enabled='1';
hosts =
cat > /etc/postfix/ <<EOF
user = baruwa
password = password
dbname = baruwa
query = select concat('@', address, 'OK') 'email' from user_addresses where user_addresses.address='%d';
hosts =
cat > /etc/postfix/ <<EOF
user = baruwa
password = password
dbname = baruwa
query = select concat('smtp:[', mail_hosts.address, ']', ':', port) 'transport' from mail_hosts, user_addresses where user_addresses.address = '%s' AND = mail_hosts.useraddress_id;
hosts =

Note: For this step, make sure to replace, and with real values that matches your setup.

Make it executable and run it:

chmod +x /usr/src/

*Note: The user/password for the cf files needs to be the same as the user/password you’ll use with your Baruwa DB setup later on.  Make sure to change everything in red before running the script.


Postfix Recipient Callout(Optional)

This feature queries the recipient server to see if the recipient exists. If not, it replies with a 550 error to the sending server and drops the connection. If the user does exist, the SpamSnake will continue processing the email. This is just another method to prevent backscatter, but comes at a price. Read up on it at You can skip this method and use the script method (later on in this guide) if you decide it will bog down your server.

vi /etc/postfix/ and add the following:

verify_recipient = reject_unknown_recipient_domain, reject_unverified_recipient
look_ahead = check_recipient_access hash:/etc/postfix/access
unverified_recipient_reject_code = 550
address_verify_map = btree:/var/lib/postfix/verify

Add this to your smtpd_restriction_classes:

verify_recipient, look_ahead

Add this to smptd_recipient_restrictions:

smtpd_recipient_restrictions = permit_mynetworks, permit_sasl_authenticated, reject_unauth_destination, look_ahead, whitelist_policy, grey_policy, rbl_policy, spf_policy, permit

Create the access file:

touch /etc/postfix/access

Add your domains:

cat > /etc/postfix/access <<EOF
#mysql-transports verify_recipient verify_recipient

*Note: Make sure to add valid domains you’re filtering for.

Postmap it:

postmap /etc/postfix/access

Final look at the Postfix install:

less /etc/postfix/

Check the contents of the file for errors and repair if needed. Fire up Postfix:

postfix start

Check that Postfix responds:

telnet 25

You should see:

220 [yourFQDNhere] ESMTP Postfix (Ubuntu)

7. Install MailScanner (Apparmor, Clamav, DCC, Pyzor, Razor and Spamassassin)

apt-get install razor pyzor clamav-daemon libclamav6 apparmor -y


Apparmor configuration for Clamav

Add clamav to the www-data group so that it can access the directory:

usermod -a -G www-data clamav

Now edit the profile for clamd:

vi /etc/apparmor.d/usr.sbin.clamd

and add the following to the list of folders:

   /var/spool/MailScanner/** rw,
   /var/spool/MailScanner/incoming/** rw,

Reload apparmor:

/etc/init.d/apparmor reload


DCC 32bit/64bit Configuration

Install DCC from .deb source:

cd /tmp
wget$(uname -m | sed -e ‘s/x86_64/amd64/’ -e ‘s/i686/i386/’).deb && dpkg -i dcc-common_1.3.130-0ubuntu1~ppa2~quantal1_$(uname -m | sed -e ‘s/x86_64/amd64/’ -e ‘s/i686/i386/’).deb
wget$(uname -m | sed -e ‘s/x86_64/amd64/’ -e ‘s/i686/i386/’).deb && dpkg -i dcc-client_1.3.130-0ubuntu1~ppa2~quantal1_$(uname -m | sed -e ‘s/x86_64/amd64/’ -e ‘s/i686/i386/’).deb

Test our installation with:

cdcc info

You should get ‘requests ok’ from the servers.


Pyzor Configuration

Because pyzor doesn’t work with python2.6 very well, the workaround is to append the following to the first line of /usr/bin/pyzor to make it look like:

#!/usr/bin/python -Wignore::DeprecationWarning

Here we supply the IP address of the Pyzor server to Pyzor. This will create the server’s IP address in a servers file therein. Then it will test the connection. If you are behind a firewall, open port 24441/udp in and out to your server. While you’re at it also open up 6277/udp for DCC, 2703/tcp for Razor and 783/tcp for SpamAssassin:

mkdir /var/lib/MailScanner
pyzor –homedir=/var/lib/MailScanner discover
pyzor ping


Razor Configuration

Create the .razor configuration:

cd && rm /etc/razor/razor-agent.conf
mkdir /var/lib/MailScanner/.razor
razor-admin -home=/var/lib/MailScanner/.razor -create
razor-admin -home=/var/lib/MailScanner/.razor -discover
razor-admin -home=/var/lib/MailScanner/.razor -register

vi /var/lib/MailScanner/.razor/razor-agent.conf

and add/update the follwing

debuglevel      = 0
razorhome 	= /var/lib/MailScanner/.razor/


Install dependencies:

apt-get install libconvert-tnef-perl libdbd-sqlite3-perl libfilesys-df-perl libmailtools-perl libmime-tools-perl libmime-perl libnet-cidr-perl libsys-syslog-perl libio-stringy-perl libfile-temp-perl libole-storage-lite-perl libarchive-zip-perl libsys-hostname-long-perl libnet-cidr-lite-perl libhtml-parser-perl libdb-file-lock-perl libnet-dns-perl libncurses5-dev libdigest-hmac-perl libnet-ip-perl liburi-perl libfile-spec-perl spamassassin libnet-ident-perl libmail-spf-perl libmail-dkim-perl dnsutils libio-socket-ssl-perl gdebi-core -y
wget http$(uname -m | sed -e ‘s/x86_64/amd64/’ -e ‘s/i686/i386/’).deb && dpkg -i libdigest-sha1-perl_2.13-2build2_$(uname -m | sed -e ‘s/x86_64/amd64/’ -e ‘s/i686/i386/’).deb


Download and install the latest MailScanner:

cd /usr/src && wget
tar xvfz MailScanner-install-4.84.5-3.tar.gz && cd MailScanner-install-4.84.5

Add the following to cron:

37      5 * * *  /opt/MailScanner/bin/update_phishing_sites &> /dev/null
07      * * * *  /opt/MailScanner/bin/update_bad_phishing_sites &> /dev/null
58     23 * * * /opt/MailScanner/bin/clean.quarantine &> /dev/null
42      * * * *  /opt/MailScanner/bin/update_virus_scanners &> /dev/null
3,23,43 * * * *  /opt/MailScanner/bin/check_mailscanner &> /dev/null


Fix Clamav autoupdate

vi /opt/MailScanner/etc/virus.scanners.conf

and update the following to:

clamav          /opt/MailScanner/lib/clamav-wrapper     /usr/


First we need to disable the default SpamAssassin configuration file:

mv /etc/spamassassin/ /etc/spamassassin/

Now let’s backup the SpamAssassin configuration file in MailScanner then edit:

cp /opt/MailScanner/etc/spam.assassin.prefs.conf /opt/MailScanner/etc/spam.assassin.prefs.conf.back


SpamAssassin SQL Bayes

Pre-requisities: You’ll need the perl-DBI and perl-DBD-MySQL modules installed.

Assumptions and Variables:

SpamAssassin Bayes Database Name: sa_bayes
SpamAssassin Bayes Database UserName: sa_user
SpamAssassin Bayes Database Password: sa_password

Create the MySQL database on the server where you intend on storing the bayesian information.

mysql -u root -p
mysql> create database sa_bayes;
mysql> GRANT ALL ON sa_bayes.* TO sa_user@localhost IDENTIFIED BY ‘sa_password’;
mysql> flush privileges;

Import database structure:

mysql -u sa_user -p sa_bayes < /usr/share/doc/spamassassin/sql/bayes_mysql.sql

*Note: If errors about MyISAM appears, edit bayes_mysql.sql and change TYPE to ENGINE.

vi /etc/spamassassin/v310.pre

and enable DCC:

loadplugin  Mail::SpamAssassin::Plugin::DCC

Create the following to prevent an error in a lint test:

mkdir /var/www/.spamassassin

vi /opt/MailScanner/etc/spam.assassin.prefs.conf

and add the following to the top:

use_pyzor 1
pyzor_options --homedir /var/lib/MailScanner/
use_razor2 1
razor_config /var/lib/MailScanner/.razor/razor-agent.conf

Fix DCC path:

dcc_path /usr/bin/dccproc

Update header string:

bayes_ignore_header X-YOURDOMAIN-COM-MailScanner
bayes_ignore_header X-YOURDOMAIN-COM-MailScanner-SpamCheck
bayes_ignore_header X-YOURDOMAIN-COM-MailScanner-SpamScore
bayes_ignore_header X-YOURDOMAIN-COM-MailScanner-Information
#use_auto_whitelist 0

“YOURDOMAIN-COM” should be replaced with whatever you used for “%org-name%” in the MailScanner.conf file. Leave the “X-” in place. This is the same orgname used in the MailScanner.conf above.

Add sql connection string to bottom:

bayes_store_module Mail::SpamAssassin::BayesStore::SQL
bayes_sql_dsn DBI:mysql:sa_bayes:localhost
bayes_sql_username sa_user
bayes_sql_password sa_password
bayes_sql_override_username root

Add it to cron:

30 01 * * * /usr/bin/sa-learn --force-expire --sync -p /opt/MailScanner/etc/spam.assassin.prefs.conf

Install missing perl packages:

perl -MCPAN -e shell
install IP::Country::Fast
install Encode::Detect
install Crypt::OpenSSL::RSA

Set permissions to bring it all together:

chown -R postfix:www-data /var/spool/postfix/hold
chmod -R ug+rwx /var/spool/postfix/hold

Test out the setup:

spamassassin -x -D -p /opt/MailScanner/etc/spam.assassin.prefs.conf –lint

Check for lines like:

debug: bayes: Database connection established
debug: bayes: found bayes db version 3
debug: bayes: Using userid: 2

You should see lines come up with DCC, Pyzor and Razor that say loading plugin and hopefully no errors.


MailScanner Configuration

We need to make a directory for SpamAssassin in the spool and give postfix permissions to it, if you run sa-learn –force as root, bayes databese that is stored in these directories will change to root:root and spamassassin will error looking at the db. Just keep an eye on the mail.log and you’ll remember to change the permissions back. Also disable the MailScanner default configs:

mkdir /var/spool/MailScanner/spamassassin

Backup your MailScanner.conf file:

cp /opt/MailScanner/etc/MailScanner.conf /opt/MailScanner/etc/MailScanner.conf.dist

vi /usr/src/

with the following content:

*Note: This step populates the MailScanner.conf file.

sed -i “/^%org-name% =/ c\%org-name% =orgname” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^%org-long-name% =/ c\%org-long-name% = longorgname” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^%web-site% =/ c\%web-site% = www.domain.tld” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Run As User =/ c\Run As User = postfix” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Run As Group =/ c\Run As Group = www-data” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Incoming Work Group =/ c\Incoming Work Group = clamav” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Incoming Work Permissions =/ c\Incoming Work Permissions = 0640” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Incoming Queue Dir =/ c\Incoming Queue Dir = /var/spool/postfix/hold” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Outgoing Queue Dir =/ c\Outgoing Queue Dir = /var/spool/postfix/incoming” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^MTA =/ c\MTA = postfix” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Quarantine User =/ c\Quarantine User = root” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Quarantine Group =/ c\Quarantine Group = www-data” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Quarantine Permissions =/ c\Quarantine Permissions = 0660” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Quarantine Whole Message =/ c\Quarantine Whole Message = yes” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Virus Scanners =/ c\Virus Scanners = clamd” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Monitors for ClamAV Updates =/ c\Monitors for ClamAV Updates = /var/lib/clamav/*.cld /var/lib/clamav/*.cvd” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Clamd Socket =/ c\Clamd Socket = /var/run/clamav/clamd.ctl” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Clamd Lock File =/ c\Clamd Lock File = /var/run/clamav/” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Spam Subject Text =/ c\Spam Subject Text = ***SPAM***” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Spam Actions =/ c\Spam Actions = deliver store” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^High Scoring Spam Actions =/ c\High Scoring Spam Actions = store delete” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^Non Spam Actions =/ c\Non Spam Actions = deliver store” /opt/MailScanner/etc/MailScanner.conf
sed -i “/^SpamAssassin User State Dir =/ c\SpamAssassin User State Dir = /var/spool/MailScanner/spamassassin” /opt/MailScanner/etc/MailScanner.conf

*Note: Make sure to change the items in red before running the script.

Make it executable and run it:

chmod +x


MailScanner Startup Script:

vi /etc/init.d/mailscanner

with the following content:

#! /bin/sh
   # Provides:          MailScanner daemon
   # Required-Start:    $local_fs $remote_fs
   # Required-Stop:     $local_fs $remote_fs
   # Default-Start:     2 3 4 5
   # Default-Stop:      0 1 6
   # Short-Description: Controls mailscanner instances
   # Description:       MailScanner is a queue-based spam/virus filter
   # Author: Simon Walter <>
   # PATH should only include /usr/* if it runs after the script
   DESC="mail spam/virus scanner"
   # Exit if the package is not installed
   [ -x "$DAEMON" ] || exit 0
   # Read configuration variable file if it is present
   [ -r /etc/default/$PNAME ] && . /etc/default/$PNAME
   # Load the VERBOSE setting and other rcS variables
   . /lib/init/
   # Define LSB log_* functions.
   # Depend on lsb-base (>= 3.0-6) to ensure that this file is present.
   . /lib/lsb/init-functions
   # sanity check for permissions
   echo >&2 "$0: $1"
   exit 1
   if [ ! -d $1 ]; then
   mkdir -p "$1" || \
   fail "directory $1: does not exist and cannot be created"
   actual="$(stat -c %U $1)"
   if [ "$actual" != "$2" ]; then
   chown -R "$2" "$1" || \
   fail "directory $1: wrong owner (expected $2 but is $actual)"
   actual="$(stat -c %G $1)"
   if [ "$actual" != "$3" ]; then
   chgrp -R "$3" "$1" || \
   fail "directory $1: wrong group (expected $3 but is $actual)"
   user=$(echo $(awk -F= '/^Run As User/ {print $2; exit}' $CONFFILE))
   group=$(echo $(awk -F= '/^Run As Group/ {print $2; exit}' $CONFFILE))
   check_dir /var/spool/MailScanner       ${user:-postfix} ${group:-www-data}
   check_dir /var/lib/MailScanner         ${user:-postfix} ${group:-www-data}
   check_dir /var/run/MailScanner         ${user:-postfix} ${group:-www-data}
   check_dir /var/lock/subsys	${user:-root}	${group:-root} #Required to Create Folder
   check_dir /var/lock/subsys/MailScanner ${user:-postfix} ${group:-www-data}
   # Function that starts the daemon/service
   # Return
   #   0 if daemon has been started
   #   1 if daemon was already running
   #   2 if daemon could not be started
   start-stop-daemon --start --quiet --startas $STARTAS --name $NAME --test > /dev/null \
   || return 1
   start-stop-daemon --start --quiet --nicelevel $run_nice --chuid postfix:www-data --exec $DAEMON --name $NAME -- $DAEMON_ARGS \
   || return 2
   # Add code here, if necessary, that waits for the process to be ready
   # to handle requests from services started subsequently which depend
   # on this one.  As a last resort, sleep for some time.
   # Set lockfile to inform cronjobs about the running daemon
   if [ $RETVAL -eq 0 ]; then
   touch /var/lock/subsys/mailscanner
   rm -f $stopped_lockfile
   if [ $RETVAL -eq 0 ]; then
   echo "MailScanner Started"
   # Function that stops the daemon/service
   # Return
   #   0 if daemon has been stopped
   #   1 if daemon was already stopped
   #   2 if daemon could not be stopped
   #   other if a failure occurred
   start-stop-daemon --stop --retry=TERM/30 --name $NAME
   [ "$RETVAL" = 2 ] && return 2
   # Remove lockfile for cronjobs
   if [ $RETVAL -eq 0 ]; then
   rm -f /var/lock/subsys/mailscanner
   touch $stopped_lockfile
   if [ $RETVAL -eq 0 ]; then
   echo "MailScanner Stopped"
   # Function that sends a SIGHUP to the daemon/service
   do_reload() {
   start-stop-daemon --stop --signal 1 --quiet --name $NAME
   return 0
   case "$1" in
   [ "$VERBOSE" != no ] && log_daemon_msg "Starting $DESC" "$NAME"
   case "$?" in
   0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
   2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
   [ "$VERBOSE" != no ] && log_daemon_msg "Stopping $DESC" "$NAME"
   case "$?" in
   0|1) [ "$VERBOSE" != no ] && log_end_msg 0 ;;
   2) [ "$VERBOSE" != no ] && log_end_msg 1 ;;
   # If the "reload" option is implemented then remove the
   # 'force-reload' alias
   log_daemon_msg "Restarting $DESC" "$NAME"
   case "$?" in
   case "$?" in
   0) log_end_msg 0 ;;
   1) log_end_msg 1 ;; # Old process is still running
   *) log_end_msg 1 ;; # Failed to start
   # Failed to stop
   log_end_msg 1
   echo "Usage: $SCRIPTNAME {start|stop|restart|force-reload}" >&2
   exit 3
 exit 0

Make it executable:

chmod +x /etc/init.d/mailscanner

Create Symlinks for mailscanner script to work:

chmod 755 /etc/init.d/mailscanner
update-rc.d mailscanner defaults
ln -s /opt/MailScanner/bin/Quick.Peek /usr/sbin/Quick.Peek

Start the system:

/etc/init.d/mailscanner start
/etc/init.d/postfix start

Check your logs for errors:

tail -f /var/log/mail.log

Check your mail.log (tail –f /var/log/mail.log) and you should see the following:

Jun 13 12:18:23 hoshi MailScanner[26388]: MailScanner E-Mail Virus Scanner version 4.84.5 starting…

Congratulations – you now have MailScanner logging to MySQL.

8. Baruwa

Make sure links are correct for MailScanner:

ln -s /opt/MailScanner/etc /etc/MailScanner
ln -s /opt/MailScanner/lib/MailScanner/CustomFunctions /etc/MailScanner

Install and configure Rabbitmq-Server:


and add the following:

deb testing main

Update sources and install key:

apt-key add rabbitmq-signing-key-public.asc
apt-get update

Install rabbitmq-server:

apt-get install rabbitmq-server -y

Add the database credentials:

rabbitmqctl add_user baruwa password
rabbitmqctl add_vhost baruwa
rabbitmqctl set_permissions -p baruwa baruwa “.*” “.*” “.*”
rabbitmqctl delete_user guest

Restart rabbitmq-server:

/etc/init.d/rabbitmq-server restart

Add sources for Baruwa 1.1.2-4 and install dependencies:

wget -O – | apt-key add –

vi /etc/apt/sources.list

and add the following:

 deb precise main

Install Dependencies:

apt-get update
apt-get install python-django-celery python-importlib -y

Download and install Baruwa1.1.2-4sn:

mkdir /usr/src/baruwa1124 && cd /usr/src/baruwa1124
wget && mv 0B9cN15Q3pKnwLW1WNG9rN0dQNzg baruwa_1.1.2-4sn_all.deb
wget && mv 0B9cN15Q3pKnwMHFUMFhWMW4ycU0 baruwa-doc_1.1.2-4sn_all.deb
gdebi baruwa_1.1.2-4sn_all.deb
gdebi baruwa-doc_1.1.2-4sn_all.deb

You’ll be prompted with the following, answer according to your setup:

Webserver to configure manually
Virtual Host name
Choose whether to configure mysql automatically
My Host
Database administrator name/password
Baruwa username/password/dbname
RabbitMQ host/username/password
Admin user/password/email for Baruwa Web

Fix the symlinks for

rm –r /usr/share/pyshared/baruwa/ && ln –s /etc/baruwa/ /usr/share/pyshared/baruwa/

vi /etc/baruwa/

and fix the baruwa database configuration:

    'default': {
        # Add 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3'
        # or 'oracle'.
        'ENGINE': 'django.db.backends.mysql',
        # Or path to database file if using sqlite3.
        'NAME': 'baruwa',
        # Not used with sqlite3.
        'USER': 'baruwa',
        # Not used with sqlite3.
        'PASSWORD': 'password',
        # Set to empty string for localhost. Not used with sqlite3.
        'HOST': '',
        # Set to empty string for default. Not used with sqlite3.
        'PORT': '',
QUARANTINE_REPORT_HOSTURL = 'http://baruwa-alpha.local'

Populate the database:

# baruwa-admin syncdb –noinput
# for name in $(echo “accounts messages lists reports status fixups config”); do
baruwa-admin migrate $name;

vi /etc/MailScanner/MailScanner.conf

change the following:

Run As Group = celeryd
Quarantine User = celeryd
Quarantine Group = celeryd
Always Looked Up Last = &BaruwaSQL
Is Definitely Not Spam = &BaruwaWhitelist
Is Definitely Spam = &BaruwaBlacklist
Required SpamAssassin Score = &BaruwaLowScore
High SpamAssassin Score = &BaruwaHighScore

vi /etc/MailScanner/conf.d/baruwa.conf

and update the following:

Quarantine User = celeryd #(Or what ever your `Run As User` is set to)
DB DSN = DBI:mysql:database=baruwa;host=localhost;port=3306 #set to valid DSN
DB Username = baruwa # your DB username
DB Password = password # your DB password

vi /etc/init.d/mailscanner

and update it changing the following:

check_dir /var/spool/MailScanner ${user:-postfix} ${group:-celeryd}
check_dir /var/lib/MailScanner ${user:-postfix} ${group:-celeryd}
check_dir /var/run/MailScanner ${user:-postfix} ${group:-celeryd}
check_dir /var/lock/subsys/MailScanner ${user:-postfix} ${group:-celeryd}
start-stop-daemon --start --quiet --startas $STARTAS  --pidfile "$PIDFILE" --test > /dev/null \
start-stop-daemon --start --quiet --nicelevel $run_nice --chuid postfix:celeryd --exec $DAEMON  --pidfile "$PIDFILE" -- $DAEMON_ARGS \
start-stop-daemon --stop --retry=TERM/30 --pidfile "$PIDFILE"
start-stop-daemon --stop --signal 1 --quiet --pidfile "$PIDFILE"

Add celeryd user to clamav group:

usermod -a -G celeryd clamav

Change the group ownership of the quarantine folder and content:

chgrp -R celeryd /var/spool/MailScanner/quarantine

If you’re going to use signatures, initialize it by running:

baruwa-admin initconfig

Otherwise, disable the options in /etc/MailScanner/conf.d/baruwa.conf:

#Inline HTML Signature = htmlsigs.customize
#Inline Text Signature = textsigs.customize
#Signature Image Filename = sigimgfiles.customize
#Signature Image Filename = sigimgs.customize

Finally, run to create the Baruwa super user:

baruwa-admin createsuperuser

Reboot your system and enjoy Baruwa 112-4sn.


9. Nginx with Uwsgi

Install nginx and uwsgi:

apt-get install nginx-full uwsgi uwsgi-plugin-python -y

vi /etc/uwsgi/apps-available/baruwa.ini

with the following content:

workers = 2
chdir = /usr/share/pyshared/baruwa
env = DJANGO_SETTINGS_MODULE=baruwa.settings
module = django.core.handlers.wsgi:WSGIHandler()

vi /etc/nginx/sites-available/baruwa.conf

with the following content:

server {
listen 80;
root /usr/share/pyshared/baruwa;
autoindex on;
access_log /var/log/nginx/access.log;
error_log /var/log/nginx/error.log;
location /static {
    root /usr/share/pyshared/baruwa/static/;
    # static resources
    location ~* ^.+\.(html|jpg|jpeg|gif|png|ico|css|zip|tgz|gz|rar|bz2|doc|xls|exe|pdf|ppt|txt|tar|mid|midi|wav|bmp|rtf|js)$
      expires 30d;
location / {
    uwsgi_pass unix:///var/run/uwsgi/app/baruwa/socket;
    include uwsgi_params;

*Note: Make sure to change to a FQDN that matches your setup.

Create symlinks:

ln -s /etc/nginx/sites-available/baruwa.conf /etc/nginx/sites-enabled/baruwa.conf
ln -s /etc/uwsgi/apps-available/baruwa.ini /etc/uwsgi/apps-enabled/baruwa.ini
rm -r /etc/nginx/sites-enabled/default
cp /usr/share/doc/uwsgi-extra/nginx/uwsgi_params /etc/nginx/uwsgi_params

Restart both nginx and uwsgi:

/etc/init.d/nginx restart && /etc/init.d/uwsgi restart

Remove the default virtual host and copy over uwsgi_params:

rm -r /etc/nginx/sites-enabled/default
cp /usr/share/doc/uwsgi-extra/nginx/uwsgi_params /etc/nginx/uwsgi_params
ln -s /etc/nginx/sites-available/baruwa.conf /etc/nginx/sites-enabled/baruwa.conf
ln -s /etc/uwsgi/apps-available/baruwa.ini /etc/uwsgi/apps-enabled/baruwa.ini

Restart services:

/etc/init.d/uwsgi restart && /etc/init.d/nginx restart

Create a symlink to

ln -s /usr/share/pyshared/baruwa/ /usr/bin/
chmod +x /usr/bin/

Add cron jobs:

@daily cleanquarantine &> /dev/null  #Clean quarantine
@daily sendquarantinereports &> /dev/null  #Send quarantine reports
@monthly  dbclean &> /dev/null  #Clean maillog
@weekly updatesarules &> /dev/null  #Update spamassassin rules
@daily sendpdfreports &> /dev/null #Send PDF Reports

Start up MailScanner:

/etc/init.d/mailscanner start

*Note: Point your browser to http://hostname used login with admin user and password and start working. You can now use the interface to add users and process messages, etc.

Setup Instructions for Baruwa

Log into Baruwa as admin –> Settings –> Accounts –> Create Account

Once you’ve created the user account, you’ll get two new tabs on that page, Profile Settings and Associated Addresses.

Fill out Profile Settings choosing Domain Admin and set a low score of 6 and a high score of 9 and check scan email.

Click the + sign under Associated Addresses and enter a domains for which the user is the admin of eg. will show up under Associated Addresses.

Click on and you’ll be taken into Domain Information, where you’ll be able to setup SMTP delivery information. Go ahead and add the receiving smtp server, or the ip of the receiving smtp server. Select enable and if you use a non-standard port, set it, otherwise use 25. Once you’ve done that, you can click on the test button next to the pencil, to see if your receiving server will accept the connection.

Now, you can log out as admin, and log in as the user you just setup and mails should start flowing.

The relay_recipients, relay_domains and transports settings in /etc/postfix/ will use the entries you’ve provided in Baruwa. Therefore, no hash file is required.  The associated queries in the mysql cf files will pull the result in the proper format and feed it to postfix for use.

However, if you’d like to use a hash for any of your config files, use the following as an example of how to setup /etc/postfix/

relay_recipient_maps = hash:/etc/postfix/relay_recipients

Of course, you would have to create the hash file(s), populate it and postmap it for postfix to use.

*Note: If you do end up using hash for relay_recipients for specific domains, you’ll have to remove that domain from /etc/postfix/access.  All other domain users can still be verified using look_ahead

Also, if you need to do mx lookups, you’ll have to edit /etc/postfix/ query to look like:

concat(‘smtp:’, mail_hosts.address, ‘:’, port) ‘transport’

The [ and ] were removed to allow MX lookups.

Enjoy Baruwa!


10. Install and Configure SPF


apt-get install postfix-policyd-spf-python -y

vi /etc/postfix/

and add the following stanza at the end:

policy-spf  unix  -       n       n       -       -       spawn      
	user=nobody argv=/usr/bin/policyd-spf

*Note:  (The leading spaces before user=nobody are important so that Postfix knows that this line belongs to the previous one!)

*Note: We already added the entry for using the postfix setup script.

Then restart Postfix:

/etc/init.d/postfix restart

That’s it already.


11. Install and Configure FuzzyOcr

FuzzyOCR has some prerequisites like ocrad and gocr that we can install like this:

apt-get install fuzzyocr netpbm gifsicle libungif-bin gocr ocrad libstring-approx-perl libmldbm-sync-perl libdigest-md5-perl libdbd-mysql-perl imagemagick tesseract-ocr -y
wget && tar xvfz fuzzyocr-3.6.0.tar.gz && cd FuzzyOcr-3.6.0/

vi /etc/spamassassin/

and uncomment the following lines:

focr_global_wordlist /etc/spamassassin/FuzzyOcr.words
focr_preprocessor_file /etc/spamassassin/FuzzyOcr.preps
focr_scanset_file /etc/spamassassin/FuzzyOcr.scansets
focr_enable_image_hashing 3
focr_digest_db /etc/spamassassin/FuzzyOcr.hashdb
focr_db_hash /etc/spamassassin/FuzzyOcr.db
focr_db_safe /etc/spamassassin/
focr_bin_helper convert, tesseract

Comment out the path:

#focr_path_bin /usr/local/netpbm/bin:/usr/local/bin:/usr/bin

Enable the following lines:

focr_mysql_db FuzzyOcr
focr_mysql_hash Hash
focr_mysql_safe Safe
focr_mysql_user fuzzyocr
focr_mysql_pass fuzzyocr
focr_mysql_host localhost
focr_mysql_port 3306
focr_mysql_socket /var/run/mysqld/mysqld.sock

We will be storing the image hashes in a mysql database to improve on performance such that images that we have already scanned do not get scanned again as OCR is a resource intense activity.

Create MySQL Database:

The sql script creates the database for fuzzyocr:

mysql -p <  FuzzyOcr.mysql

Set up FuzzyOcr Database Cleaner:

vi /usr/sbin/fuzzy-cleanmysql

with the following content:

#Script to clean out mysql tables of data. Default is to leave data in Safe for 1 day and Hash for 10 days.
use Getopt::Long;
use DBI;
use MLDBM qw(DB_File Storable);
my %Files = (
    db_hash => '/var/lib/fuzzyocr/FuzzyOcr.db',
    db_safe => '/var/lib/fuzzyocr/',
use DBI;
$database = "FuzzyOcr";
$hostname = "localhost";
$socket = "/var/run/mysqld/mysqld.sock";
$port = "3306";
$username = "fuzzyocr";
$password = 'password';
# defaults
my $cfgfile = "/etc/spamassassin/";
my %App;
my %age;
$age{'age'} = 10*24;  # 10 days
$age{'hash'} = $age{'age'};
$age{'safe'} = 0;
my $help = 0;
my $verbose = 0;
GetOptions( \%age,
    'config=s' => \$cfgfile,
    'help' => \$help,
    'verbose' => \$verbose,
if ($help) {
    print "Usage: fuzzy-cleanmysql [Options]\n";
    print "\n";
    print "Available options:\n";
    print "--age=i      Global age in hours to keep in db\n";
    print "--config=s   Specify location of\n";
    print "             Default: /etc/spamassassin/\n";
    print "--hash=i     Number of hours old to keep in Hash db\n";
    print "--safe=i     Number of hours old to keep in Safe db\n";
    print "--verbose    Show more informations\n";
    print "\n";
    exit 1;
# Convert hours to seconds
$age{'age'} *= 60 * 60;
$age{'hash'} *= 60 * 60;
$age{'safe'} *= 60 * 60;
$age{'safe'} = $age{'safe'} ? $age{'safe'} : $age{'age'};
# Read custom paths from
my $app_path = q(/usr/local/netpbm/bin:/usr/local/bin:/usr/bin);
open CONFIG, "< $cfgfile" or warn "Can't read configuration file, using defaults...\n";
while () {
    if ($_ =~ m/^focr_bin_(\w+) (.+)/) {
        $App{$1} = $2;
        printf "Found custom path \"$2\" for application \"$1\"\n" if $verbose;
    if ($_ =~ m/^focr_path_bin (.+)/) {
        $app_path = $1;
        printf "Found new path: \"$1\"\n" if $verbose;
    if ($_ =~ m/^focr_enable_image_hashing (\d)/) {
        $App{hashing_type} = $1;
        printf "Found DB Hashing\n" if ($verbose and $1 == 2);
        printf "Found MySQL Hashing\n" if ($verbose and $1 == 3);
    if ($_ =~ m/^focr_mysql_(\w+) (.+)/) {
        $MySQL{$1} = $2;
        printf "Found MySQL option $1 => '$2'\n" if $verbose;
    if ($_ =~ m/^focr_threshold_max_hash (.+)/) {
        $App{max_hash} = $1;
        printf "Updated Thresold{max_hash} = $1\n" if $verbose;
close CONFIG;
# make shure we have this threshold set
$App{max_hash} = 5 unless defined $App{max_hash};
# search path for bin_util unless already specified in configuration file
foreach my $app (@bin_utils) {
    next if defined $App{$app};
    foreach my $d (split(':',$app_path)) {
        if (-x "$d/$app") {
            $App{$app} = "$d/$app";
sub get_ddb {
    my %dopts = ( AutoCommit => 1 );
    my $dsn = "DBI:mysql:database=$database";
    if (defined $socket) {
        $dsn .= ";mysql_socket=$socket";
    } else {
        $dsn .= ";host=$hostname";
        $dns .= ";port=$port" unless $port == 3306;
    printf "Connecting to: $dsn\n" if $verbose;
    return DBI->connect($dsn, $username, $password,\%dopts) or die("Could not connect!");
if ($App{hashing_type} == 3) {
 my $ddb = get_ddb();
  if ($ddb) {
    my $sql;
    foreach my $ff (sort keys %Files) {
      $ff =~ s/db_//;
      $sqlbase = "FROM $MySQL{$ff} WHERE $MySQL{$ff}.\`check\` < ?";
      my $timestamp = time;
      $timestamp = $timestamp - $age{$ff};
      $sql = "DELETE $sqlbase";
      if ( $verbose ) {
        printf "Delete from Table $MySQL{$ff}\n";
        print "$sql,  $timestamp\n";
        print "Timestamp is ", scalar(localtime($timestamp)), "\n";
        print "That's $age{$ff} seconds earlier than now.\n";
        print "\n";

Make it executable:

chmod +x /usr/sbin/fuzzy-cleanmysql

Add it to cron:

@weekly /usr/sbin/fuzzy-cleanmysql &> /dev/null  #FuzzyOcr DB cleaner

We can feed each of these emails to SpamAssassin now to see if FuzzyOCR is linked correctly into SpamAssassin.

spamassassin –debug FuzzyOcr < /usr/src/FuzzyOcr-3.6.0/samples/ocr-gif.eml > /dev/null

You should now see a lot of output, the end should look like this:

[10025] dbg: FuzzyOcr:
[10025] dbg: FuzzyOcr: Friday Augurt 4, 4:01 pm ET
[10025] dbg: FuzzyOcr: LAS VEGAS, NEVADA–(MARKET WIRE)–Aug 4, 2006 — auantum Energy, lnc. (OTC
[10025] dbg: FuzzyOcr: BB:aEGY.oB-_-
[10025] dbg: FuzzyOcr: auantum Energy, lnc. is pleased to announce that it has applied to have its shares listed for
[10025] dbg: FuzzyOcr: trading on the Frankfurt Stock Exchange. The company has retained the services ofBaltic
[10025] dbg: FuzzyOcr: lnvestment Group of Hamburg, Germany to assist with the application.
[10025] dbg: FuzzyOcr:
[10025] dbg: FuzzyOcr: _ qEGY,OB “


12. Filtering PDF, XLS and Phishing Spam with ClamAV (Sanesecurity Signatures)

There is currently a lot of spam where the spam “information” is attached as .pdf or .xls files, sometimes also hidden inside a .zip file. While these spam mails are not easy to catch with e.g. SpamAssassin or a Bayes filter, the ClamAV virus scanner can catch them easily when it is fed with the correct signatures as ClamAV is built to scan mail attachments.

Create a folder for sanesecurity and download and give the script the proper permission:

apt-get install curl rsync -y
mkdir /usr/src/sanesecurity && cd /usr/src/sanesecurity
tar -zxf clamav-unofficial-sigs-3.7.1.tar.gz && cd clamav-unofficial-sigs-3.7.1
mv /usr/sbin
mv clamav-unofficial-sigs.conf /etc/
chmod +x /usr/sbin/

vi /etc/clamav-unofficial-sigs.conf

and change the following variables to match your installation:

reload_opt="kill -USR2 `cat $clamd_pid`" #Signals PID to reload dbs

Now we run the update script to check if the download works:

Add it to cron:

00 04 * * * /usr/sbin/ -c /etc/clamav-unofficial-sigs.conf &> /dev/null


13. Greylisting with Greyfix

Greyfix is a tiny greylisting daemon that works extremely well and is easy on resources.


cd /usr/src && wget
tar -xf greyfix-0.3.9.tar.gz && cd greyfix-0.3.9
./configure –localstatedir=/var
make install

vi /etc/postfix/

and add the following:

greyfix    unix  -        n       n       -        -       spawn
   user=nobody  argv=/usr/local/sbin/greyfix   --greylist-delay 60  -/ 24

*Note: We already added the entry for using the postfix setup script.

14. KAM

vi /etc/cron.daily/

with the following content:

 # Original version modified by Andrew MacLachlan (
 # Added additional MailScanner restarts on inital restart failure
 # Made script run silently for normal (successful) operation
 # Increased UPDATEMAXDELAY to 900 from 600
 # Insert a random delay up to this value, to spread virus updates round
 # the clock. 1800 seconds = 30 minutes.
 # Set this to 0 to disable it.
 if [ -f /opt/MailScanner/var/MailScanner ] ; then
 . /opt/MailScanner/var/MailScanner
 if [ "x$UPDATEMAXDELAY" = "x0" ]; then
 logger -p -t Delaying cron job up to $UPDATEMAXDELAY seconds
 perl -e "sleep int(rand($UPDATEMAXDELAY));"
 # JKF Fetch
 #echo Fetching
 cd /etc/mail/spamassassin
 rm -f
 wget -O > /dev/null 2>&1
 if [ "$?" = "0" ]; then
 #echo It completed and fetched something
 if ( tail -10 | grep -q '^#.*EOF' ); then
 # echo It succeeded so make a backup
 cp -f
 echo ERROR: Could not find EOF marker
 cp -f
 echo It failed to complete properly
 cp -f
 #echo Reloading MailScanner and SpamAssassin configuration rules
 /etc/init.d/mailscanner reload > /dev/null 2>&1
 if [ $? != 0 ] ; then
 echo "MailScanner reload failed - Retrying..."
 /etc/init.d/mailscanner force-reload
 if [ $? = 0 ] ; then
 echo "MailScanner reload succeeded."
 echo "Stopping MailScanner..."
 /etc/init.d/mailscanner stop
 echo "Waiting for a minute..."
 perl -e "sleep 60;"
 echo "Attemping to start MailScanner..."
 /etc/init.d/mailscanner start

Make it executable:

chmod +x /etc/cron.daily/


15. ScamNailer

vi /opt/MailScanner/bin/update_scamnailer

with the following content:

# (c) 2009 Julian Field ‹›
#          Version 2.05
# This file is the copyright of Julian Field ‹›,
# and is made freely available to the entire world. If you intend to
# make any money from my work, please contact me for permission first!
# If you just want to use this script to help protect your own site's
# users, then you can use it and change it freely, but please keep my
# name and email address at the top.
use strict;
use File::Temp;
use Net::DNS::Resolver;
use LWP::UserAgent;
use FileHandle;
use DirHandle;
# Filename of list of extra addresses you have added, 1 per line.
# Does not matter if this file does not exist.
my $local_extras = '/etc/MailScanner/ScamNailer.local.addresses';
# Output filename, goes into SpamAssassin. Can be over-ridden by just
# adding the output filename on the command-line when you run this script.
my $output_filename = '/etc/mail/spamassassin/';
# This is the location of the cache used by the DNS-based updates to the
# phishing database.
my $emailscurrent = '/var/cache/ScamNailer/';
# Set this next value to '' if ou are not using MailScanner.
# Or else change it to any command you need to run after updating the
# SpamAssassin rules, such as '/sbin/service spamd restart'.
my $mailscanner_restart = '/etc/init.d/mailscanner force-reload';
# The SpamAssassin score to assign to the final rule that fires if any of
# the addresses hit. Multiple hits don't increase the score.
# I use a score of 0.1 with this in MailScanner.conf:
# SpamAssassin Rule Actions = SCAMNAILER=>not-deliver,store,forward, header "X-Anti-Phish: Was to _TO_"
# If you don't understand that, read the section of MailScanner.conf about the
# "SpamAssassin Rule Actions" setting.
my $SA_score = 4.0;
# How complicated to make each rule. 20 works just fine, leave it alone.
my $addresses_per_rule = 20;
my $quiet = 1 if grep /quiet|silent/, @ARGV;
if (grep /help/, @ARGV) {
  print STDERR "Usage: $0 [ --quiet ]\n";
my($count, $rule_num, @quoted, @addresses, @metarules);
#local(*YPCAT, *SACF);
$output_filename = $ARGV[0] if $ARGV[0]; # Use filename if they gave one
# First do all the addresses we read from DNS and anycast and only do the
# rest if needed.
if (GetPhishingUpdate()) {
open(SACF, ">$output_filename") or die "Cannot write to $output_filename $!";
print SACF "# ScamNailer rules\n";
print SACF "# Generated by $0 at " . `date` . "\n";
# Now read all the addresses we generated from GetPhishingUpdate().
open(PHISHIN, $emailscurrent . 'phishing.emails.list')
  or die "Cannot read " . $emailscurrent . "phishing.emails.list, $!\n";
<phishin>) {
  next if /^\s*$/;
  next unless /^[^@]+\@[^@]+$/;
  push @addresses, $_; # This is for the report
  s/[^0-9a-z_-]/\\$&/ig; # Quote every non-alnum
  s/\\\*/[0-9a-z_.+-]*/g; # Unquote any '*' characters as they map to .*
  # Find all the numbers just before the @ and replace with them digit wildcards
  #push @quoted, '(' . $_ . ')';
  push @quoted, $_;
  if ($count % $addresses_per_rule == 0) {
    # Put them in 10 addresses at a time
    # Put a start-of-line/non-address character at the front,
    # and an end-of-line /non-address character at the end.
    print SACF "header __SCAMNAILER_H$rule_num ALL =~ /" .
               '(^|[;:\s])(?:' . join('|',@quoted) . ')($|[^0-9a-z_.+-])' .
    push @metarules, "__SCAMNAILER_H$rule_num";
    print SACF "uri __SCAMNAILER_B$rule_num /" .
               '^mailto:(?:' . join('|',@quoted) . ')$' .
    push @metarules, "__SCAMNAILER_B$rule_num";
    undef @quoted;
    undef @addresses;
close PHISHIN;
# Put in all the leftovers, if any
if (@quoted) {
    print SACF "header __SCAMNAILER_H$rule_num ALL =~ /" .
               '(^|[;:\s])(?:' . join('|',@quoted) . ')($|[^0-9a-z_.+-])' .
    push @metarules, "__SCAMNAILER_H$rule_num";
    print SACF "uri __SCAMNAILER_B$rule_num /" .
               '^mailto:(?:' . join('|',@quoted) . ')$' .
    push @metarules, "__SCAMNAILER_B$rule_num";
print SACF "\n# ScamNailer combination rule\n\n";
print SACF "meta     SCAMNAILER " . join(' || ',@metarules) . "\n";
print SACF "describe SCAMNAILER Mentions a spear-phishing address\n";
print SACF "score    SCAMNAILER $SA_score\n\n";
print SACF "# ScamNailer rules ($count) END\n";
close SACF;
# And finally restart MailScanner to use the new rules
$mailscanner_restart .= " >/dev/null 2>&1" if $quiet;
system($mailscanner_restart) if $mailscanner_restart;
exit 0;
sub GetPhishingUpdate {
  my $cache = $emailscurrent . 'cache/';
  my $status = $emailscurrent . 'status';
  my $urlbase = "";
  my $target= $emailscurrent . 'phishing.emails.list';
  my $query="";
  my $baseupdated = 0;
  if (! -d $emailscurrent) {
    print "Working directory is not present - making....." unless $quiet;
    mkdir ($emailscurrent) or die "failed";
    print " ok!\n" unless $quiet;
  if (! -d $cache) {
    print "Cache directory is not present - making....." unless $quiet;
    mkdir ($cache) or die "failed";
    print " ok!\n" unless $quiet;
  if (! -s $target) {
    open (FILE,">$target") or die
      "Failed to open target file so creating a blank file";
    print FILE "# Wibble";
    close FILE;
  } else {
    # So that clean quarantine doesn't delete it!
    utime(time(), time(), $emailscurrent);
  my ($status_base, $status_update);
  if (! -s $status) {
    print "This is the first run of this program.....\n" unless $quiet;
  } else {
    print "Reading status from $status\n" unless $quiet;
    open(STATUS_FILE, $status) or die "Unable to open status file\n";
    my $line=<status_file>;
    close (STATUS_FILE);
    # The status file is text.text
    if ($line =~ /^(.+)\.(.+)$/) {
  print "Checking that $cache$status_base exists..." unless $quiet;
  if ((! -s "$cache$status_base") && (!($status_base eq "-1"))) {
    print " no - resetting....." unless $quiet;
  print " ok\n" unless $quiet;
  print "Checking that $cache$status_base.$status_update exists..." unless $quiet;
  if ((! -s "$cache$status_base.$status_update") && ($status_update>0)) {
    print " no - resetting....." unless $quiet;
  print " ok\n" unless $quiet;
  my $currentbase = -1;
  my $currentupdate = -1;
  # Lets get the current version
  my $res = Net::DNS::Resolver->new();
  my $RR = $res->query($query, 'TXT');
  my @result;
  if ($RR) {
    foreach my $rr ($RR->answer) {
      my $text = $rr->rdatastr;
      if ($text =~ /^"emails\.(.+)\.(.+)"$/) {
  die "Failed to retrieve valid current details\n" if $currentbase eq "-1";
  print "I am working with: Current: $currentbase - $currentupdate and Status: $status_base - $status_update\n" unless $quiet;
  my $generate=0;
  # Create a user agent object
  my $ua = LWP::UserAgent->new;
  $ua->agent("UpdateBadPhishingSites/0.1 ");
  # Patch from
  if (!($currentbase eq $status_base)) {
    print "This is base update\n" unless $quiet;
    $status_update = -1;
    $baseupdated = 1;
    # Create a request
    #print "Getting $urlbase . $currentbase\n" unless $quiet;
    my $req = HTTP::Request->new(GET => $urlbase.$currentbase);
    # Pass request to the user agent and get a response back
    my $res = $ua->request($req);
    # Check the outcome of the response
    if ($res->is_success) {
      open (FILE, ">$cache/$currentbase") or die "Unable to write base file ($cache/$currentbase)\n";
      print FILE $res->content;
      close (FILE);
    } else {
      warn "Unable to retrieve $urlbase.$currentbase :".$res->status_line, "\n";
  } else {
    print "No base update required\n" unless $quiet;
  # Now see if the sub version is different
  if (!($status_update eq $currentupdate)) {
    my %updates=();
    print "Update required\n" unless $quiet;
    if ($currentupdate‹$status_update) {
      # In the unlikely event we roll back a patch - we have to go from the base
      print "Error!: $currentupdate<$status_update\n" unless $quiet;
      $generate = 1;
      $status_update = 0;
    # If there are updates avaliable and we haven't donloaded them
    # yet we need to reset the counter
    if ($currentupdate>0) {
      if ($status_update<1) {
      my $i;
      # Loop through each of the updates, retrieve it and then add
      # the information into the update array
      for ($i=$status_update+1; $i<=$currentupdate; $i++) {
        print "Retrieving $urlbase$currentbase.$i\n" unless $quiet;
        #print "Getting $urlbase . $currentbase.$i\n" unless $quiet;
        my $req = HTTP::Request->new(GET => $urlbase.$currentbase.".".$i);
        my $res = $ua->request($req);
        warn "Failed to retrieve $urlbase$currentbase.$i"
          unless $res->is_success;
        my $line;
        foreach $line (split("\n", $res->content)) {
          # Is it an addition?
          if ($line =~ /^\> (.+)$/) {
            if (defined $updates{$1}) {
              if ($updates{$1} eq "<") {
                delete $updates{$1};
            } else {
          # Is it an removal?
          if ($line =~ /^\< (.+)$/) {
            if (defined $updates{$1}) {
              if ($updates{$1} eq ">") {
                delete $updates{$1};
            } else {
      # OK do we have a previous version to work from?
      if ($status_update>0) {
        # Yes - we open the most recent version
        open (FILE, "$cache$currentbase.$status_update") or die
          "Unable to open base file ($cache/$currentbase.$status_update)\n";
      } else {                        # No - we open the the base file
        open (FILE, "$cache$currentbase") or die
          "Unable to open base file ($cache/$currentbase)\n";
      # Now open the new update file
      print "$cache$currentbase.$currentupdate\n" unless $quiet;
      open (FILEOUT, ">$cache$currentbase.$currentupdate") or die
        "Unable to open new base file ($cache$currentbase.$currentupdate)\n";
      # Loop through the base file (or most recent update)
      while (<file>) {
        my $line=$_;
        if (defined ($updates{$line})) {
          # Does the line need removing?
          if ($updates{$line} eq "<") {
          # Is it marked as an addition but already present?
          elsif ($updates{$line} eq ">") {
            delete $updates{$line};
        print FILEOUT $line."\n";
      close (FILE);
      my $line;
      # Are there any additions left
      foreach $line (keys %updates) {
        if ($updates{$line} eq ">") {
          print FILEOUT $line."\n" ;
      close (FILEOUT);
  # Changes have been made
  if ($generate) {
    print "Updating live file $target\n" unless $quiet;
    my $file="";
    if ($currentupdate>0) {
    } else {
    if ($file eq "") {
      die "Unable to work out file!\n";
    system ("mv -f $target $target.old");
    system ("cp $file $target");
    open(STATUS_FILE, ">$status") or die "Unable to open status file\n";
    print STATUS_FILE "$currentbase.$currentupdate\n";
    close (STATUS_FILE);
  my $queuedir = new DirHandle;
  my $file;
  my $match1 = "^" . $currentbase . "\$";
  my $match2 = "^" . $currentbase . "." . $currentupdate . "\$";
  $queuedir->open($cache) or die "Unable to do clean up\n";
  while(defined($file = $queuedir->read())) {
    next if $file eq '.' || $file eq '..';
    next if $file =~ /$match1/;
    next if $file =~ /$match2/;
    print "Deleting cached file: $file.... " unless $quiet;
    unlink($cache.$file) or die "failed";
    print "ok\n" unless $quiet;

Make it executable:

chmod +x /opt/MailScanner/bin/update_scamnailer

 Add it to cron:

@daily /usr/sbin/update_scamnailer &> /dev/null #Update Scamnailer


16. Firewalling the SpamSnake with Firehol

Firehol is a stateful iptables packet filtering firewall configurator. It is abstracted, extensible, easy and powerful. It can handle any kind of firewall, but most importantly, it gives you the means to configure it, the same way you think of it.

Install Firehol:

apt-get install firehol -y

vi /etc/default/firehol

and change the following:


vi /etc/firehol/firehol.conf

and add the following:

version 5
   # Accept all client traffic on any interface
   interface any internet
   protection strong
   server "icmp ping ICMP ssh http https telnet webmin dns dcc echo smtp" accept
 client all accept

This filters all incoming connections that are not related to the above services. If you want to be less polite, you can drop them by adding the following after ‘protection strong’: policy drop

vi /usr/sbin/get-iana

with the following content:

 # $Id:,v 1.13 2010/09/12 13:55:00 jcb Exp $
   # $Log:,v $
   # Revision 1.13 2010/09/12 13:55:00 jcb
   # Updated for latest IANA reservations format.
   # Revision 1.12 2008/03/17 22:08:43 ktsaou
   # Updated for latest IANA reservations format.
   # Revision 1.11 2007/06/13 14:40:04 ktsaou
   # *** empty log message ***
   # Revision 1.10 2007/05/05 23:38:31 ktsaou
   # Added support for external definitions of:
   # in files under the same name in /etc/firehol/.
   # Only RESERVED_IPS is mandatory (firehol will complain if it is not  there,
   # but it will still work without it), and is also the only file that  firehol
   # checks how old is it. If it is 90+ days old, firehol will complain  again.
   # Changed the supplied script to generate the RESERVED_IPS  file.
   # FireHOL also instructs the user to use this script if the file is  missing
   # or is too old.
   # Revision 1.9 2007/04/29 19:34:11 ktsaou
   # *** empty log message ***
   # Revision 1.8 2005/06/02 15:48:52 ktsaou
   # Allowed to be in RESERVED_IPS
   # Revision 1.7 2005/05/08 23:27:23 ktsaou
   # Updated RESERVED_IPS to current IANA reservations.
   # Revision 1.6 2004/01/10 18:44:39 ktsaou
   # Further optimized and reduced PRIVATE_IPS using:
   # The supplied uses .aggregate. if it finds it in the path.
   # (aggregate is the name of this program when installed on Gentoo)
   # Revision 1.5 2003/08/23 23:26:50 ktsaou
   # Bug #793889:
   # Change #!/bin/sh to #!/bin/bash to allow FireHOL run on systems that
   # bash is not linked to /bin/sh.
   # Revision 1.4 2002/10/27 12:44:42 ktsaou
   # CVS test
   # Program that downloads the IPv4 address space allocation by IANA
   # and creates a list with all reserved address spaces.
 # The program will match all rows in the file which start with a  number, have a slash,
   # followed by another number, for which the following pattern will also  match on the
   # same rows
 # which rows that are matched by the above, to ignore
   # (i.e. not include them in RESERVED_IPS)?
   #IANA_IGNORE="(Multicast|Private use|Loopback|Local  Identification)"
 AGGREGATE="`which aggregate 2>/dev/null`"
   if [ -z "${AGGREGATE}" ]
   AGGREGATE="`which aggregate 2>/dev/null`"
 if [ -z "${AGGREGATE}" ]
   echo >&2
   echo >&2
   echo >&2 "WARNING"
   echo >&2 "Please install 'aggregate' to shrink the list of  IPs."
   echo >&2
   echo >&2
 echo >&2
   echo >&2 "Fetching IANA IPv4 Address Space, from:"
   echo >&2 "${IPV4_ADDRESS_SPACE_URL}"
   echo >&2
 wget -O - -proxy=off "${IPV4_ADDRESS_SPACE_URL}" |\
   egrep " *[0-9]+/[0-9]+.*${IANA_RESERVED}" |\
   egrep -vi "${IANA_IGNORE}" |\
   sed -e 's:^ *\([0-9]*/[0-9]*\).*:\1:' |\
 while IFS="/" read range net
   if [ ! $net -eq 8 ]
   echo >&2 "Cannot handle network masks of $net bits  ($range/$net)"
 first=`echo $range | cut -d '-' -f 1`
   first=`expr $first + 0`
   last=`echo $range | cut -d '-' -f 2`
   last=`expr $last + 0`
   while [ ! $x -gt $last ]
   # test $x -ne 127 && echo "$x.0.0.0/$net"
   echo "$x.0.0.0/$net"
   x=$[x + 1]
   ) | \
   if [ ! -z "${AGGREGATE}" -a -x "${AGGREGATE}" ]
   ) >"${tempfile}"
 echo >&2
   echo >&2
   printf "RESERVED_IPS=\""
   for x in `cat ${tempfile}`
   i=$[i + 1]
   printf "${x} "
   printf "\"\n"
 if [ $i -eq 0 ]
   echo >&2
   echo >&2
   echo >&2 "Failed to find reserved IPs."
   echo >&2 "Possibly the file format has been changed, or I  cannot fetch the URL."
   echo >&2
 rm -f ${tempfile}
   exit 1
   echo >&2
   echo >&2
   echo >&2 "Differences between the fetched list and the list  installed in"
   echo >&2 "/etc/firehol/RESERVED_IPS:"
 echo >&2 "# diff /etc/firehol/RESERVED_IPS  ${tempfile}"
   diff /etc/firehol/RESERVED_IPS ${tempfile}
 if [ $? -eq 0 ]
   echo >&2
   echo >&2 "No  differences found."
   echo >&2
 rm -f ${tempfile}
   exit 0
 echo >&2
   echo >&2
   echo >&2 "Would you like to save this list to  /etc/firehol/RESERVED_IPS"
   echo >&2 "so that FireHOL will automatically use it from  now on?"
   echo >&2
   while [ 1 = 1 ]
   printf >&2 "yes or no > "
   read x
 case "${x}" in
   yes) cp -f /etc/firehol/RESERVED_IPS /etc/firehol/RESERVED_IPS.old  2>/dev/null
   cat "${tempfile}" >/etc/firehol/RESERVED_IPS || exit 1
   echo >&2 "New RESERVED_IPS written to  '/etc/firehol/RESERVED_IPS'."
   echo "Firehol will now be restart"
   sleep 3
   /etc/init.d/firehol restart
   echo >&2 "Saved nothing."
 *) echo >&2 "Cannot understand '${x}'."
 rm -f ${tempfile}

Make it executable:

chmod +x /usr/sbin/get-iana

vi /usr/sbin/update-iana

with the following content:

 /usr/sbin/get-iana  < /etc/firehol/get-iana-answerfile

Make it excutable:

chmod +x /usr/sbin/update-iana

vi /etc/firehol/get-iana-answerfile

with the following content:


Run the script to update RESERVED_IPS:


*Note: Now your server is set up to only accept connections for the services you allowed.

Add it to cron:

@monthly /usr/sbin/update-iana &> /dev/null #Update firehol reserved ips


17. Apply Relay Recipients (Optional)

The following directions are meant for people using Microsoft Exchange 2000 or Microsoft Exchange 2003.

This page describes how to configure your mail gateway to periodically get a list of valid recipient email addresses from your Exchange system. By doing this, you can configure your server to automatically reject any email addressed to invalid addresses. This will reduce the load on your exchange server, since it no longer has to process non-delivery reports, and it will reduce the load on your postfix server since it won’t have to perform spam and virus scanning on the message.

Install the perl module Net::LDAP:

perl -MCPAN -e shell
install Net::LDAP

vi /usr/bin/ with the following content:
#!/usr/bin/perl -T -w
   # This script will pull all users' SMTP addresses from your Active Directory
   # (including primary and secondary email addresses) and list them in the
   # format " OK" which Postfix uses with relay_recipient_maps.
   # Be sure to double-check the path to perl above.
   # This requires Net::LDAP to be installed.  To install Net::LDAP, at a shell
   # type "perl -MCPAN -e shell" and then "install Net::LDAP"
   use Net::LDAP;
   use Net::LDAP::Control::Paged;
   use Net::LDAP::Constant ( "LDAP_CONTROL_PAGED" );
   # Enter the path/file for the output
   $VALID = "/etc/postfix/relay_recipients";
   open VALID, ">$VALID" or die "CANNOT OPEN $VALID $!";
   # Enter the FQDN of your Active Directory domain controllers below
   # Enter the LDAP container for your userbase.
   # The syntax is CN=Users,dc=example,dc=com
   # This can be found by installing the Windows 2000 Support Tools
   # then running ADSI Edit.
   # In ADSI Edit, expand the "Domain NC []" &
   # you will see, for example, DC=example,DC=com (this is your base).
   # The Users Container will be specified in the right pane as
   # CN=Users depending on your schema (this is your container).
   # You can double-check this by clicking "Properties" of your user
   # folder in ADSI Edit and examining the "Path" value, such as:
   # LDAP://,DC=example,DC=com
   # which would be $hqbase="cn=Users,dc=example,dc=com"
   # Note:  You can also use just $hqbase="dc=example,dc=com"
   # Enter the username & password for a valid user in your Active Directory
   # with username in the form cn=username,cn=Users,dc=example,dc=com
   # Make sure the user's password does not expire.  Note that this user
   # does not require any special privileges.
   # You can double-check this by clicking "Properties" of your user in
   # ADSI Edit and examining the "Path" value, such as:
   # LDAP://,CN=Users,DC=example,DC=com
   # which would be $user="cn=user,cn=Users,dc=example,dc=com"
   # Note: You can also use the UPN login: "user\"
   # Connecting to Active Directory domain controllers
   $ldap = Net::LDAP->new($dc1) or
   if ($noldapserver == 1)  {
   $ldap = Net::LDAP->new($dc2) or
   die "Error connecting to specified domain controllers $@ \n";
   $mesg = $ldap->bind ( dn => $user,
   password =>$passwd);
   if ( $mesg->code()) {
   die ("error:", $mesg->error_text((),"\n"));
   # How many LDAP query results to grab for each paged round
   # Set to under 1000 for Active Directory
   $page = Net::LDAP::Control::Paged->new( size => 990 );
   @args = ( base     => $hqbase,
   # Play around with this to grab objects such as Contacts, Public Folders, etc.
   # A minimal filter for just users with email would be:
   # filter => "(&(sAMAccountName=*)(mail=*))"
   filter => "(& (mailnickname=*) (| (&(objectCategory=person)
   (objectCategory=group)(objectCategory=publicFolder) ))",
   control  => [ $page ],
   attrs  => "proxyAddresses",
   my $cookie;
   while(1) {
   # Perform search
   my $mesg = $ldap->search( @args );
   # Filtering results for proxyAddresses attributes
   foreach my $entry ( $mesg->entries ) {
   my $name = $entry->get_value( "cn" );
   # LDAP Attributes are multi-valued, so we have to print each one.
   foreach my $mail ( $entry->get_value( "proxyAddresses" ) ) {
   # Test if the Line starts with one of the following lines:
   # proxyAddresses: [smtp|SMTP]:
   # and also discard this starting string, so that $mail is only the
   # address without any other characters...
   if ( $mail =~ s/^(smtp|SMTP)://gs ) {
   print VALID $mail." OK\n";
   # Only continue on LDAP_SUCCESS
   $mesg->code and last;
   # Get cookie from paged control
   my($resp)  = $mesg->control( LDAP_CONTROL_PAGED ) or last;
   $cookie    = $resp->cookie or last;
   # Set cookie in paged control
   if ($cookie) {
   # We had an abnormal exit, so let the server know we do not want any more
   $ldap->search( @args );
   # Also would be a good idea to die unhappily and inform OP at this point
   die("LDAP query unsuccessful");
   # Add additional restrictions, users, etc. to the output file below.
   #print VALID "user\ OK\n";
   #print VALID "user\ 550 User unknown.\n";
   #print VALID " 550 User does not exist.\n";
 close VALID;;

Make it executable:

chmod +x /usr/bin/

Edit the file to customize it for your specific domain. Since the file is read only, you will need to use :w! to save the file in vi.

1. Set $dc1 and $dc2 to the fully qualified domain names or IP addresses of 2 of your domain controllers.
2. Set $hqbase equal to the LDAP path to the container or organizational unit which holds the email accounts for which you wish to get the email addresses.
3. Set $user and $passwd to indicate which user account should be used to access this information. This account only needs to be a member of the domain, so it would be a good idea to setup an account specifically for this.

Try running the script. If it works correctly, it will create /etc/postfix/relay_recipients.

*Note: If your postfix server is separated from your active directory controllers by a firewall, you will need to open TCP port 389 from the postfix server to the ADCs.

At this point, you may want to edit /etc/postfix/relay_recipients and edit out any unwanted email addresses as this script imports everything.

Postmap the file to create the hash db:

postmap /etc/postfix/relay_recipients
postfix reload

Finally, you may want to set up a cron job to periodically update and build the /etc/postfix/relay_recipients.db file. You can set up a script called /usr/bin/ (Optional)

vi /usr/bin/

with the following content:

postmap /etc/postfix/relay_recipients
postfix reload

Make it executable:

chmod +x /usr/bin/

Don’t forget to make sure the following is in your /etc/postfix/ file:

relay_recipient_maps = hash:/etc/postfix/relay_recipients

Add it to cron:

30 2 * * * /usr/bin/ #syncronize relay_recipients with Active Directory addresses

*Note: This cron job will run every day at 2:30 AM to update the database file. You may want to run yours more frequently or not depending on how often you add new email users to your system.
18. Install Webmin (Optional):

vi /etc/apt/sources.list

and add the following:

deb sarge contrib
deb sarge contrib

Install the GPG Key along with the package:

apt-key add jcameron-key.asc
apt-get update
apt-get install webmin -y

*Note: All dependencies should be resolved automatically.

Now to access webmin open your browser and enter: http://serverip:10000/
19. Automatically Add A Disclaimer To Outgoing Emails With alterMIME (Optional)

Install alterMIME:

apt-get install altermime -y

Next we create the user filter with the home directory /var/spool/filter – alterMIME will be run as that user:

useradd -r -c “Postfix Filters” -d /var/spool/filter filter
mkdir /var/spool/filter
chown filter:filter /var/spool/filter
chmod 750 /var/spool/filter

Afterwards we create the script /etc/postfix/disclaimer which executes alterMIME. Ubuntu’s alterMIME package comes with a sample script that we can simply copy to /etc/postfix/disclaimer:

cp /usr/share/doc/altermime/examples/ /etc/postfix/disclaimer
chgrp filter /etc/postfix/disclaimer
chmod 750 /etc/postfix/disclaimer

Now the problem with this script is that it doesn’t distinguish between incoming and outgoing emails – it simply adds a disclaimer to all mails. Typically you want disclaimers only for outgoing emails, and even then not for all sender addresses. Therefore I’ve modified the /etc/postfix/disclaimer script a little bit – we’ll come to that in a minute.

vi /etc/postfix/disclaimer_addresses

which holds all sender email addresses (one per line) for which alterMIME should add a disclaimer:


and modify it as follows (I have marked the parts that I’ve changed):

# Localize these.
####### Changed From Original Script #######
####### Changed From Original Script END #######
# Exit codes from <sysexits.h>
# Clean up when done or when aborting.
trap “rm -f in.$$” 0 1 2 3 15
# Start processing.
cd $INSPECT_DIR || { echo $INSPECT_DIR does not exist; exit
cat >in.$$ || { echo Cannot save mail to file; exit $EX_TEMPFAIL; }
####### Changed From Original Script #######
# obtain From address
from_address=`grep -m 1 “From:” in.$$ | cut -d “<” -f 2 | cut -d “>” -f 1`
if [ `grep -wi ^${from_address}$ ${DISCLAIMER_ADDRESSES}` ]; then
/usr/bin/altermime –input=in.$$ \
–disclaimer=/etc/postfix/disclaimer.txt \
–disclaimer-html=/etc/postfix/disclaimer.txt \
–xheader=”X-Copyrighted-Material: Please visit” || \
{ echo Message content rejected; exit $EX_UNAVAILABLE; }
####### Changed From Original Script END #######
$SENDMAIL “$@” <in.$$
exit $?

Next we need the text file /etc/postfix/disclaimer.txt which holds our disclaimer text. Ubuntu’s alterMIME package comes with a sample text that we can use for now (of course, you can modify it if you like):

cp /usr/share/doc/altermime/examples/disclaimer.txt /etc/postfix/disclaimer.txt

Finally we have to tell Postfix that it should use the /etc/postfix/disclaimer script to add disclaimers to outgoing emails.


and add -o content_filter=dfilt: to the smtp line:

# Postfix master process configuration file. For details on the format
# of the file, see the master(5) manual page (command: “man 5 master”).
# ==========================================================================
# service type private unpriv chroot wakeup maxproc command + args
# (yes) (yes) (yes) (never) (100)
# ==========================================================================
smtp inet n – – – – smtpd
-o content_filter=dfilt:

At the end of the same file, add the following two lines:

dfilt unix – n n – – pipe
flags=Rq user=filter argv=/etc/postfix/disclaimer -f ${sender} — ${recipient}

Restart Postfix afterwards:

/etc/init.d/postfix restart

That’s it! Now a disclaimer should be added to outgoing emails sent from the addresses listed in /etc/postfix/disclaimer_addresses.
20. Screenshots





You should now have a completely working SpamSnake.