The Perfect SpamSnake - Ubuntu Jeos 10.10 Maverick Meerkat - Page 3

8. Baruwa

Download custom baruwa deb package:

cd /usr/src
wget baruwa_1.0.1-2sn_all.deb Updated 021811
wget baruwa-doc_1.0.1-2_all.deb
gdebi baruwa*.deb

You'll be prompted to setup the baruwa db for logging, including an access username and password, which will be used by the Baruwa scripts. You'll also be prompted to setup the admin user information for the Baruwa frontend.

vi /usr/share/pyshared/baruwa/

Change the Quarantine hosturl to your liking:

QUARANTINE_REPORT_HOSTURL = 'http://baruwa-alpha.local'


vi /opt/MailScanner/etc/MailScanner.conf

You need to make sure that the following options are set:

Always Looked Up Last = &BaruwaSQL
Is Definitely Not Spam = &BaruwaWhitelist
Is Definitely Spam = &BaruwaBlacklist
Required SpamAssassin Score = &BaruwaLowScore
High SpamAssassin Score = &BaruwaHighScore 


Upgrading Baruwa

*Note: This section is only for those running an older version of Baruwa. Skip this if you're doing a fresh install.

Download the latest baruwa deb package:

cd /usr/src
wget baruwa_1.0.2-4sn_all.deb Updated 052011
wget baruwa-doc_1.0.2-4_all.deb
gdebi baruwa*.deb

When prompted to setup the mysql db, select no. We'll reuse the current db and settings.

Make sure /usr/share/pyshared/baruwa/ is setup with the correct database information before you connect, otherwise your connection will fail.

vi /usr/share/pyshared/baruwa/
DATABASE_ENGINE = 'mysql'           # 'postgresql_psycopg2', 'postgresql', 'mysql', 'sqlite3' or 'oracle'.
DATABASE_NAME = 'baruwa'             # Or path to database file if using sqlite3.
DATABASE_USER = 'baruwa'             # Not used with sqlite3.
DATABASE_PASSWORD = 'password'         # Not used with sqlite3.
DATABASE_HOST = ''             # Set to empty string for localhost. Not used with sqlite3.
DATABASE_PORT = ''             # Set to empty string for default. Not used with sqlite3.


*Note: Due to a few minor upgrade conflicts, these steps might have to be taken to correctly upgrade baruwa.

If /opt/MailScanner was removed:

ln -s /opt/MailScanner-version /opt/MailScanner
rm -r /etc/MailScanner
ln -s /opt/MailScanner/etc /etc/MailScanner
ln -s /opt/MailScanner/lib/MailScanner/CustomFunctions /etc/MailScanner

Make sure to update your database connection string in /etc/MailScanner/CustomFunctions/*.pm files.

Finally, run syncdb

Restart Uwsgi and Nginx

/etc/init.d/uwsgi-python2.6 restart && /etc/init.d/nginx restart

Baruwa should be upgraded to 102-4sn.


Upgrading Baruwa 102-4 to 111-3sn

Install Rabbitmq-Server 2.2.0 and setup db/user/password:

cd /usr/src
gdebi rabbit*

vi /etc/rabbitmq/rabbitmq.conf to bind to localhost:

export RABBITMQ_NODENAME=rabbit@localhost

Add the database credentials:

rabbitmqctl add_user baruwa password
rabbitmqctl add_vhost baruwa
rabbitmqctl set_permissions -p baruwa baruwa ".*" ".*" ".*"

Restart rabbitmq-server:

/etc/init.d/rabbitmq-server restart

Add sources for Baruwa 1.1.1-3 and install dependencies:

wget -O - | apt-key add -

vi /etc/apt/sources.list

deb maverick main

Install Dependencies:

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

*Note: The above command should install all dependencies for Baruwa.

Download and install Baruwa1.1.1-3sn:

mkdir /usr/src/baruwa1113 && cd /usr/src/baruwa1113
mv open* baruwa_1.1.1-3sn_all.deb
mv open* baruwa-doc_1.1.1-3sn_all.deb
gdebi baruwa_1.1.1-3sn_all.deb
gdebi baruwa-doc_1.1.1-3sn_all.deb

*Note: Install package maintainer’s version of files when asked. Select no to setup mysql as we’re upgrading from a previous version.

Fix the symlink 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': '',


Update Baruwa database structure:

baruwa-admin syncdb --noinput

Run fake migration to initiate an upgrade:

for name in $(echo "accounts messages lists reports status config"); do
baruwa-admin migrate $name 0001 --fake;

Run the normal migration:

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

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

Quarantine User = postfix #(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 change group entries from www-data to celeryd:

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 ${user:-root} ${group:-root} #Required to Create Folder
check_dir /var/lock/subsys/MailScanner ${user:-postfix} ${group:-celeryd}
start-stop-daemon --start --quiet --nicelevel $run_nice --chuid postfix:celeryd --exec $DAEMON --name $NAME -- $DAEMON_ARGS \

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 is /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

Reboot your system and enjoy Baruwa 111-3sn.


9. Nginx with Uwsgi

Download and install both Nginx and Uwsgi from Chris Lea’s Launchpad:

wget$(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
wget$(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
apt-get install libsctp1
dpkg -i nginx*
dpkg -i uwsgi*

Use the following config files for uwsgi and nginx:

vi /etc/uwsgi/uwsgi-python2.6/baruwa.ini
   socket = /var/run/uwsgi/uwsgi-python2.6/baruwa/baruwa.sock
   pythonpath = /usr/share/pyshared/baruwa/
   master = true
   processes = 2
   env = DJANGO_SETTINGS_MODULE=baruwa.settings
   module = django.core.handlers.wsgi:WSGIHandler()
vi /etc/nginx/sites-available/baruwa.conf
   server {
      listen 80;
      root /usr/share/pyshared/baruwa;
      #main access log
      access_log  /var/log/nginx/access.log;
      #main error 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/uwsgi-python2.6/baruwa/baruwa.sock;
      include uwsgi_params;

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

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

Restart services:

/etc/init.d/uwsgi-python2.6 restart && /etc/init.d/nginx restart

You should be all set.

Set up Cron jobs

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

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

The postfix-policyd-spf-perl package depends on the Mail::SPF and the NetAddr::IP Perl modules.

We need to download postfix-policyd-spf-perl from to the /usr/src/ directory and install it to the /usr/lib/postfix/ directory like this:

cd /usr/src
tar xvfz postfix-policyd-spf-perl-2.007.tar.gz
cd postfix-policyd-spf-perl-2.007
cp postfix-policyd-spf-perl /usr/lib/postfix/policyd-spf-perl

Then we edit /etc/postfix/ and add the following stanza at the end:

vi /etc/postfix/
policy unix - n n - - spawn
  user=nobody argv=/usr/bin/perl /usr/lib/postfix/policyd-spf-perl

(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
tar xvfz fuzzyocr-3.6.0.tar.gz
cd FuzzyOcr-3.6.0/

So FuzzyOCR is now installed, now we need to configure it. FuzzyOCR's configuration file is /etc/spamassassin/ In that file almost everything is commented out. We open that file now and make some modifications:

vi /etc/spamassassin/

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


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

Grant privileges:

mysql –u root –p
GRANT ALL ON FuzzyOcr.* TO fuzzyocr@localhost IDENTIFIED BY ‘password’;


vi /etc/spamassassin/

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

*Note: Make sure you change the lines in red.


Setup FuzzyOcr Database Cleaner

vi /usr/sbin/fuzzy-cleanmysql
#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";


chmod +x /usr/sbin/fuzzy-cleanmysql


Add it to cron

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


That's it already for the FuzzyOCR configuration. Now let's see if it works as expected.

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
mkdir /usr/src/sanesecurity && cd /usr/src/sanesecurity
tar -zxf clamav-unofficial-sigs.tar.gz && cd clamav-unofficial-sigs-3.7.1
mv /usr/sbin
mv clamav-unofficial-sigs.conf /etc/
chmod +x /usr/sbin/

vi clamav-unofficial-sigs.conf

and change the following variables to match your installation:


The variable clamav_dbs contains the path to the directory where your ClamAV signatures are stored.

Path to


Reload after update:

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

Work Directory:


And once you're done with the configuration, set the following to yes:


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 

Please edit /etc/clamav-unofficial-sigs.conf and comment out the MalwarePortal database section. There has been tons of false positives when using it's defs.


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

We already added the entry for using the postfix setup script.

Share this page:

14 Comment(s)