User Tools

Site Tools


Setting up a mail server with Postfix, Postfixadmin, Dspam, Courier and MySQL


This is a howto on setting up a nice spam-virus fighting mailserver. It's based on a singlebox install but spreading out the components, isn't that difficult. I've tried building it so it could scale and still be easy to manage, with a lot of good webinterfaces. I think I have accomplished that - hope you can agree :-)

On a average day I get between 50-100 spammails (in my mailbox alone), so having a mailserver without a spamfighter would be utopia. Using Dspam has proven a very good choice, today (09-09-06) my spam catch rate is 99,680% (and that is without any kind of RBL/Blackhole services).

I hope that my time putting this together will help out others getting their mailservers up and running. Just through me a mail if you have questions or good ideas.

Please check the Reference section as it holds more information than I have written about here. Also they have helped me getting through this essay - thank you all for your help!

Good luck! — Thomas 26/07/2006 21:47

Components involved

  • Apache2
  • PHP4
  • Courier
  • Courier-authlibs
  • Postfix
  • Postfixadmin
  • MySQL
  • Dspam
  • Mod_auth_imap
  • Antivir

I'm always been a fan of SuSE so I'm going to use SuSE 9.3 Pro for this howto. I'm gonna use as much SuSE defaults as possible as it make my life easier. This setup is by no mean bound to SuSE, and a lot of similar implementations run on other distributions. A couple of these components are included in SuSE 9.3 but I'm only gonna use Apache, PHP4 and MySQL. The rest are handbuild and handinstalled. I like the idea that as much as possible is used from the distribution. That way I'm not gonna use time keeping the software up-to-date. Unfortunaly that is not possible when you use SuSE 9.3. Some of the components are not in the distribution and some are build without support for what we need.

I will not describe every little component involved (I don't know all of them) but when building the different software there may be dependencies. The good thing about SuSE is that its bundled with a gazillian packages of software so you hardly need anything other than the install DVD to satisfy the dependencies.

An other thing to mention is that this is a singlebox install. It's possible to distribute the varies pieces to make this scale but my needs (and wallet) are not compatible with such an implementation :-D


Start out by upgrading your Postfix as we need MySQL support build in (the one provided from SuSE hasn't got that support):

# wget
# wget
# rpm -Uhv postfix-*

You can also get the files here:postfix-2.2.3-1.1.mysql.i586.rpmpostfix-debuginfo-2.2.3-1.1.mysql.i586.rpm

Due to an error somewhere in the install/upgrade process of Postfix a line in is removed. So add the following to /etc/postfix/

scache    unix  -       -       n       -       1       scache

Create the directory where the mailboxes are to be placed:

# mkdir /usr/local/virtual
# chown -R postfix.postfix /usr/local/virtual
# chmod 751 /usr/local/virtual

The Postfix-user needs to be able to write in here as it is delivering the mail.

Now we need to change some of the Postfix configuration files, and create a couple of new ones. Append following inside

virtual_alias_maps = proxy:mysql:/etc/postfix/
virtual_gid_maps = static:51
virtual_mailbox_base = /usr/local/virtual
virtual_mailbox_domains = proxy:mysql:/etc/postfix/
virtual_mailbox_limit = 51200000
virtual_mailbox_maps = proxy:mysql:/etc/postfix/
virtual_minimum_uid = 51
virtual_transport = virtual
virtual_uid_maps = static:51

The virtual_gid_maps and virtual_uid_maps are the id for the postfix user taken from /etc/passwd. The virtual_mailbox_limit should be at least as big as message_size_limit.

Now create the lookup sql files. The username “postfix” is a MySQL user we create later on along with the database “postfix” for all the data. Obviously you should change the password for the MySQL user into something else than “postfix”. I assume we are dealing with Postfix 2.2.x.


user = postfix
password = postfix
hosts = localhost
dbname = postfix
query = SELECT goto FROM alias WHERE address='%s'


user = postfix
password = postfix
hosts = localhost
dbname = postfix
#query = SELECT description FROM domain WHERE domain='%s'
#optional query to use when relaying for backup MX
query = SELECT description FROM domain WHERE domain='%s' and backupmx = '0' and active = '1'


user = postfix
password = postfix
hosts = localhost
dbname = postfix
query = SELECT maildir FROM mailbox WHERE username='%s'


Now lets create the database for Postfix. Download Postfixadmin and untar it. Inside there is a textfile that we use to create and fixup the database portion for Postfix:

# mysql -u root -p < DATABASE_MYSQL.TXT

ét voila and we have created the database and all the tables, users and their rights - how easy can it get :-P. Inside the textfile you can see what is done. It's a good idea to check it out, as usernames and password my be different in the version you have downloaded. Also it might be a good idea to install mysql-administrator (or mysqlcc prior to 4.1). It's an easy-to-use GUI for MySQL. I may like cli but some colors and graphics are nice to have sometimes, especially when you're not a hardcore DBA.

Remember also to set the MySQL root password:

# mysqladmin -u root password 'SecretPassword'
# mysqladmin -u root -h password 'SecretPassword'


Courier-authlibs is the component which does the actual authentication. It can be used as a big swiss army knife, but we only utilize the interface to MySQL.

Create a user and group named “courier” with a uid/gui larger than 1000 and disable login and shell.

Unpack the tar-ball and configure it. Remember to have both zlib-devel and mysql-devel installed, it is required to make authmysql (took me two days and two nights to figure that out):

# cd courier-authlib-0.57
# ./configure

be patient configure will run for some time and look like it is looping, which it is not of course.

# su -
# make WITH_MYSQL=yes install
# make install-configure

Create /usr/local/etc/authlib/authmysqlrc and put the following into it:

MYSQL_DATABASE          postfix
MYSQL_GID_FIELD         '51'
MYSQL_HOME_FIELD        '/usr/local/virtual'
MYSQL_LOGIN_FIELD       username
MYSQL_NAME_FIELD        name
MYSQL_OPT               0
MYSQL_PASSWORD          postfix
#MYSQL_PORT             0
# Uncomment below if you want quota support.
MYSQL_SERVER            localhost
# Default FreeBSD Socket
#MYSQL_SOCKET           /var/mysql/mysql.sock
# Default RedHat Socket
MYSQL_SOCKET           /var/lib/mysql/mysql.sock
MYSQL_UID_FIELD         '51'
MYSQL_USERNAME          postfix
MYSQL_USER_TABLE        mailbox
#MYSQL_WHERE_CLAUSE     server=''

Add the a symbolic link to secure the daemon will start at boot:

# ln -s /usr/local/sbin/authdaemond /etc/init.d/authdaemond

Use Yast to enable the daemon in the different runlevels 1).


Unpack the courier-imap (not as root!):

# cd courier-imap-4.0.4
# ./configure --bindir=/usr/local/bin --mandir=/usr/local/man
# make
# su -
# make install-strip
# make install-configure

If you encounter problems compiling it with the openssl libraries in non-standard locations, try making a symbolic link:

# ln -s /usr/local/ssl/include/openssl /location-of-the-source/courier-imap-4.0.4/tcpd

(thanks to Kyle Johnson for this tip)

Add the a symbolic link to secure the daemon will start at boot:

# ln -s /usr/lib/courier-imap/libexec/imapd.rc /etc/init.d/imapd.rc

Use Yast to enable the daemon in the different runlevels2).


Prepare the Apache2 configuration for Postfixadmin. Start out by creating a new config file, lets call it postfixadmin.conf. I like to use SSL when we are dealing with password protected sites, no sniffing here. Create the certificates with “gensslcert” util coming with SuSE, without any parameters it will generate what we need and put the certificates in the right places.


Listen 555
<VirtualHost _default_:555>
DocumentRoot "/srv/www/htdocs/postfixadmin"
ServerName localhost:555
ErrorLog /var/log/apache2/postfixadmin_error_log
TransferLog /var/log/apache2/postfixadmin_access_log
SSLEngine on
SSLCertificateFile /etc/apache2/ssl.crt/server.crt
SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
<Directory "/srv/www/htdocs/postfixadmin">
    SSLOptions +StdEnvVars
SetEnvIf User-Agent ".*MSIE.*" \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0
CustomLog /var/log/apache2/postfixadmin_request_log \
          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<Directory /srv/www/htdocs/postfixadmin>
    AllowOverride AuthConfig
    Order deny,allow
    Allow from all

Unpack Postfixadmin into “/srv/www/htdocs/postfixadmin” as root. Lets make sure only root and the webserver can read the files:

# chgrp -R www /srv/www/htdocs/postfixadmin
# cd /srv/www/htdocs/postfixadmin
# chmod 640 *.php *.css
# cd /srv/www/htdocs/postfixadmin/admin/
# chmod 640 *.php .ht*
# cd /srv/www/htdocs/postfixadmin/images/
# chmod 640 *.gif *.png
# cd /srv/www/htdocs/postfixadmin/languages/
# chmod 640 *.lang
# cd /srv/www/htdocs/postfixadmin/templates/
# chmod 640 *.tpl
# cd /srv/www/htdocs/postfixadmin/users/
# chmod 640 *.php

Inside “/srv/www/htdocs/postfixadmin/admin/.htaccess” some settings needs to be changed. The references to the authentication file has to be corrected so it points to “/srv/www/htdocs/postfixadmin/admin/.htpasswd”. You could also consider to move it into /etc/apache2, your choice.

Next we need to configure the Postfixadmin's config file. The config file is named “”. Edit it and rename it to “”. The settings inside the file are fairly simple so I wouldn't use to much time on it here. Two option I how ever like to change:

$CONF['domain_path'] = 'YES';
$CONF['domain_in_mailbox'] = 'NO';

so that domains and usernames are separated in the directory structure, it gives a better overall view.


Download and unpack the SquirrelMail (SM) into “/srv/www/htdocs/squirrelmail”, or download the cvs version:

# cvs login
  (When it asks for password, just press ENTER)

# cvs co squirrelmail

Set the proper permissions:

#chown -R root.www /srv/www/htdocs/squirrelmail
#chmod 770 /srv/www/htdocs/squirrelmail/data

Run the config script “/srv/www/htdocs/squirrelmail/config/” and configure Squirrelmail, set default domain and such.

Put the following in “/etc/apache2/vhosts.d/squirrelmail.conf”:

Listen 443
<VirtualHost _default_:443>
DocumentRoot "/srv/www/htdocs/squirrelmail"
ServerName localhost:443
ErrorLog /var/log/apache2/squirrelmail_error_log
TransferLog /var/log/apache2/squirrelmail_access_log
SSLEngine on
SSLCertificateFile /etc/apache2/ssl.crt/server.crt
SSLCertificateKeyFile /etc/apache2/ssl.key/server.key
<Directory "/srv/www/htdocs/squirrelmail">
    SSLOptions +StdEnvVars
SetEnvIf User-Agent ".*MSIE.*" \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0
CustomLog /var/log/apache2/squirrelmail_request_log \
          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"
<Directory /srv/www/htdocs/squirrelmail>
    AllowOverride None
    Order deny,allow
    Allow from all

Inside the Postfixadmin “ADDITIONS” folder you find a Squirrelmail plugin I'll recommend you install. This plugin allows users to change their password by them self.


No mailserver with out a spamfilter. Dspam has showed its worth when installed and trained correctly, so far I have >99% catch rate. It can be a little tricky, and done in a million ways, but the philosophy “kiss” (keep it simple stupid) will get you through.

Create the user and group “dspam” and disable login and shell for it. Set home for the user to “/usr/local/var/dspam”. Download Dspam:

# wget

Configure and make Dspam:

# ./configure --enable-daemon \
    --with-storage-driver=mysql_drv \
    --with-mysql-includes=/usr/include/mysql \
    --enable-preferences-extension \
    --with-dspam-home-owner=dspam \
    --with-dspam-home-group=dspam \
    --with-dspam-home=/usr/local/var/dspam \
    --enable-long-usernames \
    --with-dspam-mode=dspam \
    --with-dspam-group=dspam \
    --enable-mysql4-initialization \
    --enable-domain-scale \
    --enable-virtual-users \
    --enable-debug \

# make && make install

I have compiled Dspam with debug enabled. To use these options look in “/usr/local/var/dspam/log” after you have enabled the debugging in “/usr/local/etc/dspam.conf” (there are some debug examples inside the file). When all is running as it should, recompile without debug (the last two –enables).

If you recieve mail through an inbound mailhop you should add an IgnoreHeader in your dspam.conf file, as this is not a trick fill-in from a spammer:

IgnoreHeader Received: from


Create dspam database:

# mysqladmin -u root -p create dspam

Import tables and set permissions:

# mysql -u root -p dspam < ./src/tools.mysql_drv/mysql_objects-4.1.sql
# mysql -u root -p dspam < ./src/tools.mysql_drv/neural.sql
# mysql -u root -p dspam < ./src/tools.mysql_drv/virtual_users.sql
# mysql -u root -p
# mysql> grant all on dspam.* to dspam@localhost identified by 'ThisIsMyPassword';

Web UI

Create the Web UI:

# mkdir /srv/www/htdocs/dspam
# cp -R ./cgi/* /srv/www/htdocs/dspam
# chown -R dspam.dspam /srv/www/htdocs/dspam
# cd /srv/www/htdocs/dspam
# chmod 444 *.*
# chmod 554 *.cgi
# chmod 555 templates
# chmod 444 templates/*

Put the users/email addresses that you want to be admins into this file:

# vi /srv/www/htdocs/dspam/admins 

Depending on how you would like to use the UI you have several options. The UI can be used in a way so that every user has his own interface and learning information. I learn and control all spam/ham through one account (my own) and therefor I use a password file to authenticate to UI - because it's simple:

# htpasswd2 -c /usr/local/etc/dspam.auth
# chown dspam.www /usr/local/etc/dspam.auth
# chmod 660 /usr/local/etc/dspam.auth

If you choose to give all users their own training facility you should use mod_auth_imap. More on that later.

Modify Apache2. Create “/etc/apache2/vhosts.d/dspam.conf”:

Listen 444
<VirtualHost _default_:444>

#   General setup for the virtual host
DocumentRoot "/srv/www/htdocs/dspam"
ServerName localhost:444
ErrorLog /var/log/apache2/dspam_error_log
TransferLog /var/log/apache2/dspam_access_log

SSLEngine on


SSLCertificateFile /etc/apache2/ssl.crt/server.crt
SSLCertificateKeyFile /etc/apache2/ssl.key/server.key

<Files ~ "\.(cgi|shtml|phtml|php3?)$">
    SSLOptions +StdEnvVars
<Directory "/srv/www/htdocs/dspam">
    SSLOptions +StdEnvVars

SetEnvIf User-Agent ".*MSIE.*" \
         nokeepalive ssl-unclean-shutdown \
         downgrade-1.0 force-response-1.0

CustomLog /var/log/apache2/dspam_request_log \
          "%t %h %{SSL_PROTOCOL}x %{SSL_CIPHER}x \"%r\" %b"

        RewriteEngine on
        RewriteRule ^/$ /dspam.cgi [R]

SuexecUserGroup dspam dspam

<Directory /srv/www/htdocs/dspam>

Options +ExecCGI FollowSymLinks
    AddHandler cgi-script cgi pl
#        AllowOverride None
#        Order deny,allow
#        Deny from all
#Turn on IMAP Authentication
        #Auth_IMAP_Enabled on

        #Give a name to the authentication domain, whatever you want:
        #AuthName ""

        #Only basic authentication is supported for now:
        #AuthType Basic

        #If you feel like it, restrict the users or allow all "valid-user"
        #Require user user1 user2

        #Make IMAP Authentication authoritative for this .htaccess file:
        #Auth_IMAP_Authoritative on

        #Set the IMAP Server to which you want to connect (default=localhost):

        #Set the port on which the imap server is running (default=143):
        #Auth_IMAP_Port 143

        #Turn on some extra logging (login attempts, etc.) in Apache's Error Log
        #Auth_IMAP_Log on

AuthType Basic
AuthName "Restricted Files"
AuthUserFile /usr/local/etc/dspam.auth
Require valid-user

We need to append the rewrite module in “/etc/sysconfig/apache2”. It should look like this:

APACHE_MODULES="access actions alias auth auth_dbm autoindex cgi dir env expires include log_config mime negotiation setenvif ssl suexec php4 mod_rewrite"

Please also pay attention to the certificate part, you may need to point the statements somewhere else. I've just used the same certificate files as with Postfixadmin, so when you get tired of all the browser warnings, and create a new set of certificates, you need to change the /etc/apache2/vhosts.d/dspam.conf file accordingly.

Due to SuExec you can't put the Web UI files anywhere else than where I have put them (/srv/www/htdocs/…), it's forced into this location at compile time by SuSE. SuExec is a dangerous tool to play with, security wise, so a lot of things needs to be right before it works (read the Apache2 docs if you would like to know more). Doing as I write will make it work the way it should.

Postfix integration

Dspam is integrated into Postfix through a content filter. This makes a lot of things easy for us. How ever Dspam will be called by Postfix every time a mail runs through it. This can be okay but I only want incoming mail to be scanned as I don't send spam to the world (why should I?). This require some scripting. Start out by creating /usr/local/bin/



# Get arguments
sender="$1"; shift
recip="$1"; shift

# Parse out the username from the recipient
user=`echo $recip | sed -n 's/^\(.*\)@.*/\1/p'`

# Parse out domain from the recipient
domain=`echo $recip | sed -n 's/.*@\(.*\).*/\1/p'`

if [ -z "$user" ]; then
  echo "Can't determine user"
  exit 75 # EX_TEMPFAIL

if [ "$domain" = "$MYDOMAIN1" ] || [ "$domain" = "$MYDOMAIN2" ] || [ "$domain" = "$MYDOMAIN3" ]; then
  $DSPAM --deliver=innocent --user -i -f "$sender" -- "$recip"
elif [ "$domain" = "spam.$MYDOMAIN1" ] || [ "$domain" = "spam.$MYDOMAIN2" ] || [ "$domain" = "spam.$MYDOMAIN3" ]; then
  $DSPAM --user --class=spam --source=error
elif [ "$domain" = "ham.$MYDOMAIN1" ] || [ "$domain" = "ham.$MYDOMAIN2" ] || [ "$domain" = "ham.$MYDOMAIN3" ]; then
  $DSPAM --user --class=innocent --source=error
  $SENDMAIL -i -f "$sender" -- "$recip"

exit $?

The script require some configuration to fit your installation. First of all the three “MYDOMAIN” statements needs to reflect your domains. If you only have one or two domains, then just delete the statements accordingly and also remove them from all the “if” statements. As I only use one user for learning I have put in my own address as “–user” but it can be replaced by “–user “$user”” or “–user “$recip”” if you want the username to be including domain (preferable). What the script does, is to only scan for spam when the recipient is from one of the three domains. If the recipient is the subdomain ham.MYDOMAIN then the mail is used for training (correcting a false positive) and if it's for spam.MYDOMAIN then it is retrained as spam. It doesn't matter what you put in front of the @ (ex. will relearn a message as spam and will relearn it as innocent). I usually don't use the retraining facility because I use the Web UI for that. Also I have chosen to quarantine spam mails, so I will need to release a false positive anyway. How ever this gives me the opportunity to retrain a spam mail easily if I forget to do it in time through the Web UI (it only holds the last 200 mails). Mail sent from one of the MYDOMAIN to an other in MYDOMAIN will get trained. This could also be handled in the script but I have been lazy :-P. Remember to set the script's permissions:

# chown dspam.dspam /usr/local/bin/
# chmod 770 /usr/local/bin/

Next append the following into

dspamit     unix  -       n       n       -       10      pipe
        flags=Rhqu user=dspam argv=/usr/local/bin/ ${sender} ${recipient}

and change it to use the content filter:

#smtp      inet  n       -       n       -       -       smtpd
smtp      inet  n       -       n       -       -       smtpd
  -o content_filter=dspamit:dummy

The original smtp daemon is commented out with at hash (#) and replaced with one containing a content filter call. You can call Dspam in other places of the mail loop, fx after antivirus, this is up to you. Whether to filter spam before or after antivirus is a long and, some times religious debate, that I don't want to comment on here, I have chosen to do it this way, and it works.

Also put the following into your

dspamit_destination_recipient_limit = 1

Otherwise the script will not work properly as it only handles one recipient (Thanks to Ralf Hildebrandt for pointing that out to me)

To protect my two re-learning aliases (* and *, so a spammer can't poison my Dspam data from the Internet, I have made the following restriction file protect_ham_spam_accounts:         554 Domain not available          554 Domain not available

Add this file as a smtpd_recipient_restrictions after permit_mynetworks in thus only allowing internal hosts to use them:

smtpd_recipient_restrictions =
                check_client_access hash:/etc/postfix/internal_networks
                check_sender_access hash:/etc/postfix/not_our_domain_as_sender
                check_recipient_access hash:/etc/postfix/protect_ham_spam_accounts

This way external senders can't use the two aliases.


The easiest way of handling multiuser access to the Dspam Web UI, is to use mod_auth_imap. This way don't need to handle a separate username/password store, but just reuse the users login information. Download and unpack mod_auth_imap from Make and install the module:

# apxs2 -i -a -c mod_auth_imap.c

Make sure the is in “/usr/lib/apache2” and a symbolic link to this file is present in “/usr/lib/apache2-prefork”. To load the module append the module in “/etc/sysconfig/apache2” to the modulestring:

APACHE_MODULES="access actions alias auth auth_dbm autoindex cgi dir env expires include log_config mime negotiation setenvif ssl suexec php4 mod_rewrite mod_auth_imap"

In “/etc/apache2/vhosts/dspam.conf”, at the last part of the file, there are some commented lines concerning IMAP authentication. Uncomment these, and configure them to your setup, and you should be running.

To enter the Dspam Web UI open a browser and point it at https://yourserver:444 - login with one of your Imap users or the users you've created in dspam.auth.

If the graphics don't work you probably need to install perl-GD and perl-GD-Graph3d (which is at your disposal in the SuSE distribution).

A couple of places inside “/usr/local/etc/dspam.conf” also requires our attention. I have chosen to use sendmail as the MDA and therefor the following needs to be in the file:

TrustedDeliveryAgent "/usr/sbin/sendmail"

You also need this in the file:

Trust dspam

Dspam relies heavily on MySQL, in my configuration that is, so this also needs to be reflected in the /usr/local/etc/dspam.conf:

MySQLServer     /var/lib/mysql/mysql.sock
MySQLUser               dspam
MySQLPass               123456
MySQLDb                 dspam
MySQLCompress           true

I use the socket as everything runs on the same machine. If you prefer to use the tcp port instead, fx if you're distributing the installation, you just change it into the following:

MySQLServer     mysql-server-ip-or-name
MySQLPort        3306

This should get you going as a start with Dspam. There are millions of combinations and you may wish to use Dspam differently, or need to tweak it to catch more spam. The dspam.conf file is well documented so try to look at that as a beginning. If that fails don't hesitate (or start to cry, brake down mentally…) to throw in a line on the Dspam user maillinglist. Dspam has tons of options for a reason. To make a solution work for both a single user and giant corporations some tweaking possibilities are required. It takes time to get under the hood but when you get there you realize what powerful a tool Dspam really is.

Inside “/srv/www/htdocs/dspam/” you need to make sure that the right arguments are passed on to Dspam when you release mail from quarantine this is due to the fact that we are using sendmail (Postfix-edition) as our MDA:

$CONFIG{'DSPAM_ARGS'}   = "--deliver=innocent --class=innocent " .
                          "--source=error --user %CURRENT_USER% -i %u";

Training Dspam

Dspam is a statistical tool and hence needs a statistical foundation to be able to take the right decisions. So what we need here is a lot of spam mails and ham mails - both types are required and equally important. If either type is overrepresented you can be sure that you will get a lot of false positives and bad spam-catch-rate. Dspam comes with a tool, dspam_corpus, which is just what we need. Unfortunately this tool only reads mbox files, so if you only have maildir-stored mails, you will first need to convert them (mbox-to-mdir or mdir-to-mbox). I have a lot of spam mails but most are wrapped in SA. I don't want SA's reports as part of my statistical foundation, so first we start out by stripping the spam mails (we must have SA and procmail installed for this to work):

# formail -s spamassassin -d < Spam >> cleaned.spam.inbox

“Spam” is the mbox file where my spam is stored and “cleaned.spam.inbox” is where the unwrapped spam mails get stored.

Now we can start training:

# dspam_corpus --addspam user@domain.tld cleaned.spam.inbox

Change out “user@domain.tld” with whatever username you wish to train for.

Now don't forget the ham mails:

# dspam_corpus user@domain.tld mbox-files

Dspam database maintenance

In time the Dspam database will grow and fill out a lot of diskspace. This is of course not desirable so some housecleaning is required. Luckily Dspam comes with a sql cleanup file called “purge-4.1.sql”. This file should be run nightly, by cron, to make sure that only relevant data is left in the database. Run it like this:

# mysql -u root -p dspam < purge-4.1.sql

I don't need to run it every night, only once a week just before I take my backup. I then run it in combination with an analyze and optimization:

# mysql -u root -p dspam < analyze.sql

This is how my analyze.sql looks like:

ANALYZE TABLE `dspam_neural_data`, `dspam_neural_decisions`, `dspam_preferences`, `dspam_signature_data`, `dspam_stats`, `dspam_token_data`, `dspam_virtual_uids`;

OPTIMIZE TABLE `dspam_neural_data`, `dspam_neural_decisions`, `dspam_preferences`, `dspam_signature_data`, `dspam_stats`, `dspam_token_data`, `dspam_virtual_uids`;

Dspam training mode

After processing around 3500 mails I changed trainingmode from “teft” to “toe”. This seems to give me most effective catch rate. For the time beeing I have an overall accuracy of 98% and a spam catch rate of 93%. I hope by time I will get the catch rate higher but for now it is okay, only letting one or two spam mails through a day and just as important not producing any false positives! (Update: At present time, 09/09/2006 11:13, I've got a overall accuracy of 99,52% and a spam catch rate of 99,68%)

I also learned that training spam is a constant necessity. After my server was shutdown for almost a month, due to change of ISP, my catch rate dropped dramatically. This however quickly changed after a couple of days but it was an interesting experience and showes the constant change of spam.

Spam with pictures

Recent time has shown a different approach by the spam'ers where they only put a picture in the mail and nothing else - no text no nothing. How do you handle that with Dspam now there is nothing real to analyze on?? Well good news - have faith and do nothing but train 8-)

I've had a few spammails slipping through with the picture-only-design but after a couple of retrainings, they are put in the big bad bitbucket.

Upgrading from 3.4.9 to 3.6.6

Today (27/5-06) I upgraded my dspam installation to 3.6.6. I thought it was time to catch up, not that I realy needed it but eventually you need to upgrade not to fall too far behind.

Update (21-07-06): If you plan to use markov/CRM114 you will need to dump you database and start all over with the training. Otherwise you will get very poor results. I have in the meantime switched back to graham as I couldn't cope with having to restarting all my training again.

The process was fairly easy. I used the same configure flags as with 3.4.9. Ran make and make install and the files got compiled and put in the right places. Next I updated my dspam.conf file with a few new flags:

#3.6.6 upgrade addon stuff
TestConditionalTraining on
ProcessorBias off #default on but due to markov it is turned off

I wanted to try out the new CRM114 algorithm so a couple of other things needed to be changed in dspam.conf:

Feature sbph
#Feature noise
#Feature chained
Feature tb=5
Feature whitelist

To activate CRM114 algorithm I also changed:

#PValue robinson
#PValue graham
PValue markov

Next I upgraded the WebUI. This was done by copying the following files into /srv/www/htdocs/dspam:


And the following into /srv/www/htdocs/dspam/templates:


I then went into /srv/www/htdocs/dspam and ran:

chown dspam.dspam *
chown root.root
chmod 555 *.cgi
chmod 444 admins base.css rgb.txt
cd templates
chmod 440 *

To reflect my setup I had to edit and changed it to the following:

$CONFIG{'DSPAM_ARGS'}   = "--deliver=innocent --class=innocent " .
                          "--source=error --user %CURRENT_USER% -i %u";
$CONFIG{'DATE_FORMAT'}  = "%d.%m.%Y %H:%M"; # Date format in strftime style
# Add customized settings below

I few good things has happend to the WebUI so that part is worth upgrading. When it comes to the rest, only time can tell. So far things are functional and catching spam.


Home /usr/local/var/dspam
TrustedDeliveryAgent "/usr/sbin/sendmail"
OnFail error
Trust root
Trust mail
Trust mailnull 
Trust smmsp
Trust daemon
#Trust nobody
#Trust majordomo
Trust dspam
TrainingMode toe
#TrainingMode teft
#3.6.6 upgrade addon stuff
TestConditionalTraining on
ProcessorBias off #default on
Feature sbph
#Feature noise
#Feature chained
Feature tb=5
Feature whitelist
#Algorithm chi-square
Algorithm graham burton
#PValue robinson
#PValue graham
PValue markov
Preference "spamAction=quarantine"
Preference "signatureLocation=message"	# 'message' or 'headers'
Preference "showFactors=on"
#Preference "spamAction=tag"
#Preference "spamSubject=SPAM"
AllowOverride trainingMode
AllowOverride spamAction spamSubject
AllowOverride statisticalSedation
AllowOverride enableBNR
AllowOverride enableWhitelist
AllowOverride signatureLocation
AllowOverride showFactors
AllowOverride optIn optOut
AllowOverride whitelistThreshold
MySQLServer    	/var/lib/mysql/mysql.sock
MySQLUser 	     	dspam
MySQLPass    		********
MySQLDb      	  	dspam
MySQLCompress		true
IgnoreHeader Received: from
Notifications	off
PurgeSignatures 14          # Stale signatures
PurgeNeutral    90          # Tokens with neutralish probabilities
PurgeUnused     90          # Unused tokens
PurgeHapaxes    30          # Tokens with less than 5 hits (hapaxes)
PurgeHits1S	15          # Tokens with only 1 spam hit
PurgeHits1I	15          # Tokens with only 1 innocent hit
SystemLog on
UserLog   on
Opt out

## EOF


# This program is free software; you can redistribute it and/or
# modify it under the terms of the GNU General Public License
# as published by the Free Software Foundation; version 2
# of the License.
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# GNU General Public License for more details.
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.

# This configuration file is read by all the CGI scripts to configure both the
# environment that DSPAM is working in and the way it will display information
# to the web user.

# Default DSPAM enviroment
$CONFIG{'DSPAM_HOME'}   = "/usr/local/var/dspam";
$CONFIG{'DSPAM_BIN'}    = "/usr/local/bin";
$CONFIG{'DSPAM'}        = $CONFIG{'DSPAM_BIN'} . "/dspam";
$CONFIG{'DSPAM_STATS'}  = $CONFIG{'DSPAM_BIN'} . "/dspam_stats";
$CONFIG{'DSPAM_ARGS'}   = "--deliver=innocent --class=innocent " .
                          "--source=error --user %CURRENT_USER% -i %u";
$CONFIG{'TEMPLATES'}    = "./templates";        # Location of HTML templates
$CONFIG{'ALL_PROCS'}    = "ps auxw";            # use ps -deaf for Solaris
$CONFIG{'MAIL_QUEUE'}   = "mailq | grep '^[0-9,A-F]' | wc -l";

$CONFIG{'WEB_ROOT'}     = ""; # URL location of included htdocs/ files

# Default DSPAM display
$CONFIG{'DATE_FORMAT'}  = "%d.%m.%Y %H:%M"; # Date format in strftime style
                                             # if undefined use default DSPAM display format
$CONFIG{'HISTORY_SIZE'} = 799;          # Number of items in history
$CONFIG{'HISTORY_DUPLICATES'} = "yes";  # Wether to show duplicate entries in history "yes" or "no"
$CONFIG{'MAX_COL_LEN'}  = 50;           # Max chars in list columns
$CONFIG{'SORT_DEFAULT'} = "Rating";     # Show quarantine by "Date" or "Rating"
$CONFIG{'3D_GRAPHS'}    = 1;
$CONFIG{'OPTMODE'}      = "NONE";       # OUT=OptOut IN=OptIn NONE=not selectable

# Add customized settings below


# Autodetect filesystem layout and preference options

# Or, if you're running dspam.cgi as untrusted, it won't be able to auto-detect
# so you will need to specify some features manually:

$CONFIG{'DSPAM_CGI'} = "dspam.cgi";

# Configuration was successful

Antivir (to become Clam-AV)

Update 26-7-06: I just discovered today that my mailserver isn't catching any viruses - not even the eicar testvirus. That is bad!!! Unfortunatly there is nothing wrong with my setup - it looks like Antivir/Avira has pulled out the free option for personal users without telling me. That leaves me with no alternative but to change to Clam-AV. Not that I don't like Clam-AV, I have just experienced better results with commercial AV solutions. I will leave the description so that it may help out paying users of avmailgate

Now we have a good tool against spam, however we cannot setup a mailserver without catching the viruses as well. I have chosen to use AntiVir UNIX MailGate from H+BEDV. It is free when you're a private person and have what I need. It is, as Dspam, used as a plugin in Postfix so we need to configure /etc/postfix/, /etc/postfix/ and install and configure Antivir, to get this working (it is not as difficult as it sounds). Antivir is not Open Source, sorry to say.

As a personal user you will need a registration key (free of charge). Provide info here and you will receive a registration key by mail. When you've got the key continue with the actually installation. (Note - according to the Antivir homepage it is no langer nessesary to register for at key. I haven't tried it out so I don't know if it is right)

Download the latest AntiVir UNIX MailGate and unpack it somewhere. Inside the tarball you will find and install script called “install”. Run it and follow the instructions. I didn't deviate from default. Remember to apply all mail domains and all subnets for relaying. When asked for relay ip addresses type on one line: “” (if is your inside network). And for receiving mail domains on one line: “” Of course without the 's. IP's and domains are written to /etc/avmailgate.acl.

When the installation wizard has finished we move on to /etc/avmailgate.conf. As we use Antivir as a content filter we must tell Postfix to forward mail to Antivir and tell how Antivir should reinject mail back into Postfix. Inside avmailgate.conf look for: ”ForwardTo SMTP“. Change it to:

ForwardTo SMTP: localhost port 10025

This gets mail back into Postfix after scanning. Next, in the same file, look for: ”ListenAddress“ and set it to:

ListenAddress localhost port 10024

This will be the Antivir smtp-listen port where Postfix will inject mail for scanning.

Next step is to configure /etc/postfix/ Append a content filter statement:

content_filter = smtp:[]:10024

This will tell Postfix to forward all mail (incoming and outgoing) to Antivir.

The last step is to open Postfix for reinjection of scanned mail. Now insert the following into /etc/postfix/

localhost:10025 inet n - n - - smtpd
          -o content_filter=

My final /etc/postfix/ now looks like this:

# ==========================================================================
# service type  private unpriv  chroot  wakeup  maxproc command + args
#               (yes)   (yes)   (yes)   (never) (100)
# ==========================================================================
#smtp      inet  n       -       n       -       -       smtpd
smtp      inet  n       -       n       -       -       smtpd
  -o content_filter=dspamit:

localhost:10025 inet n - n - - smtpd
  -o content_filter=

#smtps    inet  n       -       n       -       -       smtpd
#  -o smtpd_tls_wrappermode=yes -o smtpd_sasl_auth_enable=yes
#submission   inet    n       -       n       -       -       smtpd
#  -o smtpd_enforce_tls=yes -o smtpd_sasl_auth_enable=yes -o smtpd_etrn_restrictions=reject
#628      inet  n       -       n       -       -       qmqpd
pickup    fifo  n       -       n       60      1       pickup
cleanup   unix  n       -       n       -       0       cleanup
qmgr      fifo  n       -       n       300     1       qmgr
#qmgr     fifo  n       -       n       300     1       oqmgr
#tlsmgr   fifo  -       -       n       300     1       tlsmgr
rewrite   unix  -       -       n       -       -       trivial-rewrite
bounce    unix  -       -       n       -       0       bounce
defer     unix  -       -       n       -       0       bounce
trace     unix  -       -       n       -       0       bounce
verify    unix  -       -       n       -       1       verify
flush     unix  n       -       n       1000?   0       flush
proxymap  unix  -       -       n       -       -       proxymap
smtp      unix  -       -       n       -       -       smtp
relay     unix  -       -       n       -       -       smtp
#       -o smtp_helo_timeout=5 -o smtp_connect_timeout=5
showq     unix  n       -       n       -       -       showq
error     unix  -       -       n       -       -       error
local     unix  -       n       n       -       -       local
virtual   unix  -       n       n       -       -       virtual
lmtp      unix  -       -       n       -       -       lmtp
anvil     unix  -       -       n       -       1       anvil
scache    unix  -       -       n       -       1       scache

#localhost:10025 inet   n       -       n       -       -       smtpd -o content_filter=
# Interfaces to non-Postfix software. Be sure to examine the manual
# pages of the non-Postfix software to find out what options it wants.
# maildrop. See the Postfix MAILDROP_README file for details.
maildrop  unix  -       n       n       -       -       pipe
  flags=DRhu user=vmail argv=/usr/local/bin/maildrop -d ${recipient}
cyrus     unix  -       n       n       -       -       pipe
  user=cyrus argv=/usr/lib/cyrus/bin/deliver -e -r ${sender} -m ${extension} ${user}
uucp      unix  -       n       n       -       -       pipe
  flags=Fqhu user=uucp argv=uux -r -n -z -a$sender - $nexthop!rmail ($recipient)
ifmail    unix  -       n       n       -       -       pipe
  flags=F user=ftn argv=/usr/lib/ifmail/ifmail -r $nexthop ($recipient)
bsmtp     unix  -       n       n       -       -       pipe
  flags=Fq. user=foo argv=/usr/local/sbin/bsmtp -f $sender $nexthop $recipient
procmail  unix  -       n       n       -       -       pipe
  flags=R user=nobody argv=/usr/bin/procmail -t -m /etc/procmailrc ${sender} ${recipient}

dspamit     unix  -       n       n       -       10      pipe
        flags=Rhqu user=dspam argv=/usr/local/bin/ ${sender} ${recipient}

And my final /etc/postfix/ looks like this:

queue_directory = /var/spool/postfix
command_directory = /usr/sbin
daemon_directory = /usr/lib/postfix
mail_owner = postfix
unknown_local_recipient_reject_code = 550
debug_peer_level = 2
debugger_command =
         xxgdb $daemon_directory/$process_name $process_id & sleep 5

sendmail_path = /usr/sbin/sendmail
newaliases_path = /usr/bin/newaliases
mailq_path = /usr/bin/mailq
setgid_group = maildrop
html_directory = /usr/share/doc/packages/postfix/html
manpage_directory = /usr/share/man
sample_directory = /usr/share/doc/packages/postfix/samples
readme_directory = /usr/share/doc/packages/postfix/README_FILES
mynetworks = ,
inet_protocols = all
biff = no
mail_spool_directory = /var/mail
canonical_maps = hash:/etc/postfix/canonical
virtual_maps = hash:/etc/postfix/virtual
relocated_maps = hash:/etc/postfix/relocated
transport_maps = hash:/etc/postfix/transport
sender_canonical_maps = hash:/etc/postfix/sender_canonical
masquerade_exceptions = root
masquerade_classes = envelope_sender, header_sender, header_recipient
myhostname =
program_directory = /usr/lib/postfix
inet_interfaces = all
masquerade_domains =
mydestination = $myhostname, localhost.$mydomain
defer_transports =
disable_dns_lookups = no
relayhost =
mailbox_command =
mailbox_transport =
smtpd_sender_restrictions = hash:/etc/postfix/access
smtpd_client_restrictions =
smtpd_helo_required = no
smtpd_helo_restrictions =
strict_rfc821_envelopes = no
dspamit_destination_recipient_limit = 1

smtpd_restriction_classes =

has_our_domain_as_sender =
                check_sender_access hash:/etc/postfix/our_domain_as_sender

smtpd_data_restrictions =

smtpd_recipient_restrictions =
                check_client_access hash:/etc/postfix/internal_networks
                check_sender_access hash:/etc/postfix/not_our_domain_as_sender
                check_recipient_access hash:/etc/postfix/protect_ham_spam_accounts
                check_recipient_access hash:/etc/postfix/roleaccount_exceptions
                check_helo_access pcre:/etc/postfix/helo_checks
                #check_sender_mx_access cidr:/etc/postfix/bogus_mx
                #check_sender_access hash:/etc/postfix/rhsbl_sender_exceptions
                #check_sender_access hash:/etc/postfix/common_spam_senderdomains
                #check_sender_access regexp:/etc/postfix/common_spam_senderdomains_keywords

smtp_sasl_auth_enable = no
smtpd_sasl_auth_enable = no
smtpd_use_tls = no
smtp_use_tls = no
alias_maps = hash:/etc/aliases
mailbox_size_limit = 0
message_size_limit = 102400000
content_filter = smtp:
virtual_alias_maps = proxy:mysql:/etc/postfix/
virtual_gid_maps = static:51
virtual_mailbox_base = /usr/local/virtual
virtual_mailbox_domains = proxy:mysql:/etc/postfix/
virtual_mailbox_limit = 512000000
virtual_mailbox_maps = proxy:mysql:/etc/postfix/
virtual_minimum_uid = 51
virtual_transport = virtual
virtual_uid_maps = static:51

Both and can be tweaked a lot more than this but this is working. I have commented out some of the recipient restrictions for performance reasons. The commented out restrictions are not bad as per say, but the time it takes to evaluate, if we should accept the mail or not, is to long and will slow down mail delivery to a non acceptable stage. Have a look in the Postfix Book if you want to know more about these settings.

Restriction maps

Restriction maps used in


#List of domains we relay/accept mails for      OK       OK     OK  OK
<>              OK


#When comming from these IP addresses the sending mail address must match our mail domains. 
#That way we prevent spoofed mail comming from our gateway.
127.0.0         has_our_domain_as_sender
192.168.1       has_our_domain_as_sender


#External mail with our domains as senders must be fake and are rejected      554 Do not use my domain in your envelope sender       554 Do not use my domain in your envelope sender     554 Do not use my domain in your envelope sender


#Addresses that you must always accept
postmaster@     OK
abuse@          OK
webmaster@      OK
ftpmaster@      OK


#We will not accept mail when the helo command contains parts of our hostname, IP address or non compliant values.
/^zool\.domingo\.dk$/   550 Don't use my hostname
/^83\.73\.4\.83$/       550 Don't use my IP address
/^\[83\.73\.4\.83\]$/   550 Don't use my IP address
/^[0-9.]+$/             550 Your client is not RFC 2821 compliant


#We don't want externals the ability to send to these domains, as they might by poisoned by spammers         554 Domain not available          554 Domain not available

Notes on firewalling

As I am only using one machine, my mailserver also works as gateway for my home lan. This requires that I'm using, at least, two network interfaces. From SuSE 9.3 and beyond the interface names are allocated dynamically, so the first loaded driver gets the first interface name (eg. eth0). The drivers are not loaded in the same order at each boot, so the internal sometimes suddenly gets external and verse versa. This behavior seriously messes up the iptables rules and you end up with a completely isolated gateway (iptables dropping all traffic). To handle this behavior you can do two things:

  1. Inside /etc/sysconfig/network/ifcfg-eth-xxxx-mac-addr-xxx append “PERSISTANT_NAME=name-of-interface”. This way you can give your interfaces any name you like, except ethX names. This is the way I have done it. I've given them the names “internal” and “external”, easy to understand.
  2. Or you can force the ethX names through mac mapping in a file called /etc/iftab:
      eth0    mac 00:01:80:60:57:8B
      eth1    mac 00:0E:0D:81:23:02

Some applications3) don't like interface names like ”internal“ and ”external“, so you may be forced to use options 2.


With a combination of the right tools and enough investigation time, it is possible to make a powerful mail server that does the job, for a single geek (like me :-D ) or a larger corporation, with the same set of tools.

Utilizing Open-Source software can really make you wonder – why use anything else…

Please feel free to comment or correct this howto. I can be reached at



There are no warrantys here, either expressed or implied. By using the advice provided herein, you agree to hold the author, Thomas D Dahlmann, harmless from any and all losses, including loss of data, loss of profits, loss of girlfriend or wifes, time wasted, and/or any drugs you may have done in the 1960's. Your mileage may vary!4)

1) Remember to verify that Yast has put the authdaemond daemon after networking - if that's not the case the authdaemond will not start
2) Again check when the daemon is started
3) Like VMware Workstation
howtos/mailserver.txt · Last modified: d/m/Y H:i by domingo