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
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
Start out by upgrading your Postfix as we need MySQL support build in (the one provided from SuSE hasn't got that support):
# wget ftp://ftp.norrbring.com/pub/linux/suse_apps/9.3/postfix-2.2.3-1.1.MySQL.i586.rpm # wget ftp://ftp.norrbring.com/pub/linux/suse_apps/9.3/postfix-debuginfo-2.2.3-1.1.MySQL.i586.rpm # 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 master.cf is removed. So add the following to /etc/postfix/master.cf:
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 main.cf:
virtual_alias_maps = proxy:mysql:/etc/postfix/mysql_virtual_alias_maps.cf virtual_gid_maps = static:51 virtual_mailbox_base = /usr/local/virtual virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql_virtual_domains_maps.cf virtual_mailbox_limit = 51200000 virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf 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.
/etc/postfix/mysql_virtual_alias_maps.cf:
user = postfix password = postfix hosts = localhost dbname = postfix query = SELECT goto FROM alias WHERE address='%s'
/etc/postfix/mysql_virtual_domains_maps.cf:
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'
/etc/postfix/mysql_virtual_mailbox_maps.cf:
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 . 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 mailserver.domingo.dk 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_CRYPT_PWFIELD password MYSQL_DATABASE postfix MYSQL_GID_FIELD '51' MYSQL_HOME_FIELD '/usr/local/virtual' MYSQL_LOGIN_FIELD username MYSQL_MAILDIR_FIELD maildir MYSQL_NAME_FIELD name MYSQL_OPT 0 MYSQL_PASSWORD postfix #MYSQL_PORT 0 # Uncomment below if you want quota support. #MYSQL_QUOTA_FIELD quota 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='example.domain.com'
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.
/etc/apache2/vhosts.d/postfixadmin.conf:
Listen 555 <VirtualHost _default_:555> DocumentRoot "/srv/www/htdocs/postfixadmin" ServerName localhost:555 ServerAdmin you@example.com ErrorLog /var/log/apache2/postfixadmin_error_log TransferLog /var/log/apache2/postfixadmin_access_log SSLEngine on SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL SSLCertificateFile /etc/apache2/ssl.crt/server.crt SSLCertificateKeyFile /etc/apache2/ssl.key/server.key <Directory "/srv/www/htdocs/postfixadmin"> SSLOptions +StdEnvVars SSLRequireSSL </Directory> 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 </Directory> </VirtualHost>
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 “config.inc.php.sample”. Edit it and rename it to “config.inc.php”. 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 -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/squirrelmail login (When it asks for password, just press ENTER) # cvs -d:pserver:anonymous@cvs.sourceforge.net:/cvsroot/squirrelmail 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/conf.pl” 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 ServerAdmin you@example.com ErrorLog /var/log/apache2/squirrelmail_error_log TransferLog /var/log/apache2/squirrelmail_access_log SSLEngine on SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL SSLCertificateFile /etc/apache2/ssl.crt/server.crt SSLCertificateKeyFile /etc/apache2/ssl.key/server.key <Directory "/srv/www/htdocs/squirrelmail"> SSLOptions +StdEnvVars SSLRequireSSL </Directory> 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 </Directory> </VirtualHost>
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 http://www.nuclearelephant.com/projects/dspam/sources/dspam-3.4.9.tar.gz
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 \ --enable-verbose-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 backup-mx.post.tele.dk
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';
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 domingo@domingo.dk # 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 ServerAdmin you@example.com ErrorLog /var/log/apache2/dspam_error_log TransferLog /var/log/apache2/dspam_access_log SSLEngine on SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL SSLCertificateFile /etc/apache2/ssl.crt/server.crt SSLCertificateKeyFile /etc/apache2/ssl.key/server.key <Files ~ "\.(cgi|shtml|phtml|php3?)$"> SSLOptions +StdEnvVars </Files> <Directory "/srv/www/htdocs/dspam"> SSLOptions +StdEnvVars </Directory> 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 "something.com" #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): #Auth_IMAP_Server imap.something.com #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 </Directory> </VirtualHost>
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.
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/dspamit.sh:
#!/bin/sh PATH=/usr/local/bin:/usr/bin:/bin SENDMAIL="/usr/sbin/sendmail" DSPAM="/usr/local/bin/dspam" MYDOMAIN1="domingo.dk" MYDOMAIN2="pregel.dk" MYDOMAIN3="lnxgeek.org" # 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 fi if [ "$domain" = "$MYDOMAIN1" ] || [ "$domain" = "$MYDOMAIN2" ] || [ "$domain" = "$MYDOMAIN3" ]; then $DSPAM --deliver=innocent --user domingo@domingo.dk -i -f "$sender" -- "$recip" elif [ "$domain" = "spam.$MYDOMAIN1" ] || [ "$domain" = "spam.$MYDOMAIN2" ] || [ "$domain" = "spam.$MYDOMAIN3" ]; then $DSPAM --user domingo@domingo.dk --class=spam --source=error elif [ "$domain" = "ham.$MYDOMAIN1" ] || [ "$domain" = "ham.$MYDOMAIN2" ] || [ "$domain" = "ham.$MYDOMAIN3" ]; then $DSPAM --user domingo@domingo.dk --class=innocent --source=error else $SENDMAIL -i -f "$sender" -- "$recip" fi 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 domingo@domingo.dk” 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. gofigure@spam.domingo.dk will relearn a message as spam and somefix@ham.domingo.dk 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 . Remember to set the script's permissions:
# chown dspam.dspam /usr/local/bin/dspamit.sh # chmod 770 /usr/local/bin/dspamit.sh
Next append the following into master.cf:
dspamit unix - n n - 10 pipe flags=Rhqu user=dspam argv=/usr/local/bin/dspamit.sh ${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 main.cf:
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 (*@ham.domingo.dk and *@spam.domingo.dk), so a spammer can't poison my Dspam data from the Internet, I have made the following restriction file protect_ham_spam_accounts:
spam.domingo.dk 554 Domain not available ham.domingo.dk 554 Domain not available
Add this file as a smtpd_recipient_restrictions after permit_mynetworks in main.cf 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 reject_non_fqdn_sender reject_non_fqdn_recipient permit_mynetworks check_recipient_access hash:/etc/postfix/protect_ham_spam_accounts reject_unauth_destination ... ... ...
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 http://ben.brillat.net/projects/mod_auth_imap/. Make and install the module:
# apxs2 -i -a -c mod_auth_imap.c
Make sure the mod_auth_imap.so 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 #MySQLPort 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/configure.pl” 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";
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
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`;
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.
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
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.
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:
admin.cgi admingraph.cgi base.css* configure.pl configure.pl.in default.prefs dspam-logo-small.gif dspam.cgi graph.cgi rgb.txt
And the following into /srv/www/htdocs/dspam/templates:
nav_admin_preferences.html nav_alerts.html nav_fragment.html nav_preferences.html nav_admin_status.html nav_analysis.html nav_history.html nav_quarantine.html nav_admin_error.html nav_admin_user.html nav_error.html nav_performance.html nav_viewmessage.html
I then went into /srv/www/htdocs/dspam and ran:
chown dspam.dspam * chown root.root configure.pl chmod 555 *.cgi chmod 444 admins base.css configure.pl rgb.txt cd templates chmod 440 *
To reflect my setup I had to edit configure.pl 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 $CONFIG{'LOCAL_DOMAIN'} = "domingo.dk"; # Add customized settings below $CONFIG{'LOCAL_DOMAIN'} = "";
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 #MySQLPort MySQLUser dspam MySQLPass ******** MySQLDb dspam MySQLCompress true IgnoreHeader Received: from backup-mx.post.tele.dk 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 LocalMX 127.0.0.1 SystemLog on UserLog on Opt out ## EOF
#!/usr/bin/perl # DSPAM # COPYRIGHT (C) 2002-2006 JONATHAN A. ZDZIARSKI # # 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 # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the # 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_PER_PAGE'} = 100; $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 $CONFIG{'LOCAL_DOMAIN'} = "domingo.dk"; # Add customized settings below $CONFIG{'LOCAL_DOMAIN'} = ""; $ENV{'PATH'} = "$ENV{'PATH'}:$CONFIG{'DSPAM_BIN'}"; # Autodetect filesystem layout and preference options $CONFIG{'AUTODETECT'} = 1; # 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{'AUTODETECT'} = 0; #$CONFIG{'LARGE_SCALE'} = 0; #$CONFIG{'DOMAIN_SCALE'} = 0; #$CONFIG{'PREFERENCES_EXTENSION'} = 0; $CONFIG{'DSPAM_CGI'} = "dspam.cgi"; # Configuration was successful 1;
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/main.cf, /etc/postfix/master.cf 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: “127.0.0.1/8 192.168.0.0/16” (if 192.168.0.0/16 is your inside network). And for receiving mail domains on one line: “domingo.dk lnxgeek.org” 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/main.cf. Append a content filter statement:
content_filter = smtp:[127.0.0.1]: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/master.cf:
localhost:10025 inet n - n - - smtpd -o content_filter=
My final /etc/postfix/master.cf 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/dspamit.sh ${sender} ${recipient}
And my final /etc/postfix/main.cf 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 = PATH=/bin:/usr/bin:/usr/local/bin:/usr/X11R6/bin 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 = 192.168.1.0/24 , 127.0.0.0/8 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 = mail.domingo.dk 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 has_our_domain_as_sender = check_sender_access hash:/etc/postfix/our_domain_as_sender reject smtpd_data_restrictions = reject_multi_recipient_bounce permit smtpd_recipient_restrictions = check_client_access hash:/etc/postfix/internal_networks check_sender_access hash:/etc/postfix/not_our_domain_as_sender reject_non_fqdn_recipient reject_non_fqdn_sender #reject_unknown_sender_domain #reject_unknown_recipient_domain permit_mynetworks check_recipient_access hash:/etc/postfix/protect_ham_spam_accounts reject_unauth_destination check_recipient_access hash:/etc/postfix/roleaccount_exceptions check_helo_access pcre:/etc/postfix/helo_checks reject_non_fqdn_hostname reject_invalid_hostname #check_sender_mx_access cidr:/etc/postfix/bogus_mx #check_sender_access hash:/etc/postfix/rhsbl_sender_exceptions #reject_rhsbl_sender dsn.rfc-ignorant.org #reject_rhsbl_client relays.ordb.org #check_sender_access hash:/etc/postfix/common_spam_senderdomains #check_sender_access regexp:/etc/postfix/common_spam_senderdomains_keywords permit 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:127.0.0.1:10024 virtual_alias_maps = proxy:mysql:/etc/postfix/mysql_virtual_alias_maps.cf virtual_gid_maps = static:51 virtual_mailbox_base = /usr/local/virtual virtual_mailbox_domains = proxy:mysql:/etc/postfix/mysql_virtual_domains_maps.cf virtual_mailbox_limit = 512000000 virtual_mailbox_maps = proxy:mysql:/etc/postfix/mysql_virtual_mailbox_maps.cf virtual_minimum_uid = 51 virtual_transport = virtual virtual_uid_maps = static:51
Both master.cf and main.cf 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 used in main.cf:
#List of domains we relay/accept mails for domingo.dk OK pregel.dk OK lnxgeek.org OK testlokale.org 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 domingo.dk 554 Do not use my domain in your envelope sender pregel.dk 554 Do not use my domain in your envelope sender lnxgeek.org 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 spam.domingo.dk 554 Domain not available ham.domingo.dk 554 Domain not available
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:
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 ) 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 domingo@domingo.dk
I have found a lot of inspiration from these sites, and borrowed a lot of scripts and ideas. Check them out to get more ideas or further help.
http://www.nuclearelephant.com/projects/dspam/
http://high5.net/postfixadmin/
http://postfixwiki.org/index.php?title=Virtual_Users_and_Domains_with_Courier-IMAP_and_MySQL
http://ben.brillat.net/projects/mod_auth_imap/
http://www.wimble.info/articles/dspam-qmail-vpopmail.php
Chat on IRC in #dspam on irc.freenode.net
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)