Last Updated on Saturday, 05 December 2009 19:42 Written by Axel Sjøstedt
On my FreeBSD server, I use Exim as the Mail Transfer Agent, Vexim to deal with the virtual domains and account administration on an Apache webserver, courier-imap for POP and IMAP, SpamAsassin and ClamAV for filtering, and Roundcube for webmail. Exim (the "backend") is still actively developed, but the past few years Vexim (the frontend") hasn't been updated. Anyhow I still feel that this combo makes an effective mail solution, and I have no current plans of changing the setup. This page concerns the latest Vexim version at the time of writing, version 2.21.
I have made some enhancements to Vexim over the past years, and I'm sharing them with you on this page:
This howto requires a working Vexim setup, preferabely with Courier-IMAP, although most can be achieved with slight changes if you use another POP/IMAP handler together with Exim. I consider also writing a howto for the whole process of installing the system with the tweaks on this page included.
All feedback/questions/comments is appreciated, feel free to drop me an e-mail at This e-mail address is being protected from spambots. You need JavaScript enabled to view it . Or even better, as your input can be valuable for others as well, post a comment at the bottom of this page.
By default, the password in clear text is saved in the MySQL clear field, and a crypted version in the crypt field. Even if your server is secured properly, it is not a good thing to have lots of clear text passwords laying around in a database that needs to be accessed by the user running the web server. Both Exim and Courier can authenticate against crypted passwords, so the clear text password is not really neccessary. The biggest issue you should have in mind is that it can make migration to other systems more difficuelt in the future, especially to systems with different crypt() implementations.
begin authenticators
plain_login:
driver = plaintext
public_name = PLAIN
server_condition = "${if crypteq{$3}{${lookup mysql{ \
SELECT crypt FROM users \
WHERE username = '${quote_mysql:$2}' \
}}}{yes}{no}}"
server_set_id = $2
fixed_login:
driver = plaintext
public_name = LOGIN
server_prompts = "Username:: : Password::"
server_condition = "${if crypteq{$2}{${lookup mysql{ \
SELECT crypt FROM users \
WHERE username = '${quote_mysql:$1}' \
}}}{yes}{no}}"
server_set_id = $1
Support from cram-md5 authentication was not an issue for me, as I prefer to have the whole session encrypted via SSL anyways./usr/local/etc/rc.d/exim restart
MYSQL_CRYPT_PWFIELD crypt #MYSQL_CLEAR_PWFIELD clear
/usr/local/etc/rc.d/courier-authdaemond restart
mysql -p -u vexim -D veximIssue this SQL command:
UPDATE `users` SET `clear` = `crypt`;
By default, spam is only tagged in the X-Spam headers. Since users cannot tweak their own spam settings more than setting the score thresholds, and subject tagging still seems to be popular to identify spam and make client side filters, I added automatic tagging of "****SPAM*****" and spam score in subject. Here is how you do it:
warn message = X-Spam-Report: $spam_report
spam = nobody:true
warn message = X-New-Subject: ******SPAM****** ($spam_score) $h_subject:
spam = nobody
accept hosts = 127.0.0.1:+relay_from_hosts
#!/usr/bin/perl
use Mail::Internet;
$mail = new Mail::Internet \*STDIN, Modify => 0;
$header = $mail->head();
$subject = $header->get('X-New-Subject');
if ($subject ne "") {
$header->replace('Subject', $subject);
}
$mail->print();
chmod 755 /usr/local/etc/exim/rewrite_subject.pl
virtual_delivery:
driver = appendfile
envelope_to_add
return_path_add
mode = 0600
maildir_format = true
create_directory = true
transport_filter = '${if or { {eq {0}{${lookup mysql{select on_spamassassin from users,domains \
where localpart = "${quote_mysql:$local_part}" \
and domain = "${quote_mysql:$domain}" \
and users.domain_id=domains.domain_id}{$value}}}} \
{< {$spam_score_int}{${lookup mysql{select users.sa_tag * 10 from users,domains \
where localpart = "${quote_mysql:$local_part}" \
and domain = "${quote_mysql:$domain}" \
and users.domain_id=domains.domain_id \
and on_spamassassin = 1}{$value}}}} \
} {/bin/cat}{/usr/local/etc/exim/rewrite_subject.pl}}'
directory = ${lookup mysql{select smtp from users,domains \
where localpart = '${quote_mysql:$local_part}' \
and domain = '${quote_mysql:$domain}' \
and users.domain_id = domains.domain_id}}
/usr/local/etc/rc.d/exim restart
The mail infrastructure and standards (speaking of POP and SMTP) are much like how they were when Internet was a unknown phenomen several decades ago. But today, most modern mail clients at least support running the old mail protocols over a secured SSL connection. This will not make transferring of mail between the server and other servers secure, but it will make sure the user's password is not sent on an unsecure connection, and all traffic to/from the user and client will be encrypted. You can configure Apache (webmail), Exim (SMTP) and Courier-IMAP (POP and IMAP) to use the same certificates, and this is briefly covered in this chapter. I struggled to make Exim work with SSL, but after finding the perfect combo of configuration variables, it works perfectly.
All these systems support the use of several certificates on one server. Due to the nature of the regular SSL system, each certificate is tied to an uniquie IP address, so you will need several IP addressees on your server to make this working. The steps in the howto makes you able to use several certificates/host names, but you can follow them to only have one host.
daemon_smtp_ports = smtp : smtps
# SSL/TLS config
tls_advertise_hosts = *
# additionally listen on ssl/smtp
tls_on_connect_ports = 465
tls_certificate = /usr/local/certificates/$interface_address/server.crt
tls_privatekey = /usr/local/certificates/$interface_address/server.key
# We also want a little more detail in our logs, helps with debugging
log_selector = +tls_cipher +tls_peerdn
VIRTUAL_DOMAINS = SELECT DISTINCT domain FROM domains WHERE type = 'local' AND enabled = '1' AND domain = '${quote_mysql:$domain}'
RELAY_DOMAINS = SELECT DISTINCT domain FROM domains WHERE type = 'relay' AND domain = '${quote_mysql:$domain}'
ALIAS_DOMAINS = SELECT DISTINCT alias FROM domainalias WHERE alias = '${quote_mysql:$domain}'
TLS_CERTFILE=/usr/local/certificates/courier-imap.key-crtCourier-IMAP will add a dot and the IP address to the filename, and match the file courier-imap.key-crt.192.168.0.5 we made previously.
courier_imap_imapd_ssl_enable="YES" courier_imap_pop3d_ssl_enable="YES"
# Note: The following four lines must must be present to support starting without SSL on platforms with no /dev/random equivalent but a statically compiled-in mod_ssl.
<ifmodule>
SSLRandomSeed startup builtin
SSLRandomSeed connect builtin
</ifmodule>
Listen 443
AddType application/x-x509-ca-cert .crt
AddType application/x-pkcs7-crl .crl
SSLPassPhraseDialog builtin
SSLSessionCache "shmcb:/var/run/ssl_scache(512000)"
SSLSessionCacheTimeout 300
SSLMutex "file:/var/run/ssl_mutex"
<VirtualHost 192.168.0.50:443>
DocumentRoot /usr/local/www/apache22/data/mail.yourmailserver.com
ServerName mail.yourmailserver.com
ErrorLog mail.yourmailserver.com-error_log
CustomLog mail.yourmailserver.com-access-log combined
SSLEngine on
SSLCipherSuite ALL:!ADH:!EXPORT56:RC4+RSA:+HIGH:+MEDIUM:+LOW:+SSLv2:+EXP:+eNULL
SSLCertificateFile "/usr/local/certificates/192.168.0.50/server.crt"
SSLCertificateKeyFile "/usr/local/certificates/192.168.0.50/server.key"
<FilesMatch "\.(cgi|shtml|phtml|php)$">
SSLOptions +StdEnvVars
</FilesMatch>
<Directory "/usr/local/www/apache22/cgi-bin">
SSLOptions +StdEnvVars
</Directory>
BrowserMatch ".*MSIE.*" \
nokeepalive ssl-unclean-shutdown \
downgrade-1.0 force-response-1.0
Alias /admin "/usr/local/www/vexim/"
Alias /webmail "/usr/local/www/roundcube/"
<Directory /usr/local/www/apache22/data/mail.yourmailserver.com>
<IfModule mod_rewrite.c>
RewriteEngine On
RewriteRule ^$ /webmail
</IfModule>
</Directory>
<Directory "/usr/local/www/vexim/">
Options none
AllowOverride Limit
Order deny,allow
Allow from all
</Directory>
<Directory "/usr/local/www/roundcube">
Options Indexes FollowSymLinks
AllowOverride All
Order allow,deny
Allow from all
</Directory>
</VirtualHost>
NameVirtualHost 192.168.0.50:80
<VirtualHost 192.168.0.50:80>
ServerAlias mail.yourmailserver.com
Redirect / https://mail.yourmailserver.com/
</Virtualhost>
Include etc/apache22/extra/httpd-vhosts.conf Include etc/apache22/extra/httpd-ssl.conf
/usr/local/etc/rc.d/exim restart /usr/local/etc/rc.d/courier-imap-imapd-ssl restart /usr/local/etc/rc.d/courier-imap-pop3d-ssl restart apachectl restart
Since there is no support to administer server side filtering with Vexim, except for the limited header blocking, I added a feature that can automatically move spam from the inbox to subfolders. It is very convenient for IMAP users, especially those who use their mobile phone for reading mail and is irritated by both the traffic and precious screen space being occupied by spam.
mysql -p -u vexim -D veximIssue this MySQL command on the database:
ALTER TABLE users` ADD `axel_on_movespam` TINYINT( 1 ) NOT NULL DEFAULT '0';
axel_move_spam_to_folder_lowscore:
driver = redirect
allow_fail
file_transport = axel_move_folder_transport
data = ${lookup mysql{select concat(smtp,'/.Filter-Spam-Low') \
from users,domains \
where localpart = '${quote_mysql:$local_part}' \
and domain = '${quote_mysql:$domain}' \
and domains.enabled = '1' \
and users.enabled = '1' \
and users.domain_id = domains.domain_id}}
condition = ${if and \
{ \
{<{$spam_score_int}{90}} \
{> \
{$spam_score_int} \
{${lookup mysql{select users.sa_tag * 10 from users,domains \
where localpart = '${quote_mysql:$local_part}' \
and domain = '${quote_mysql:$domain}' \
and users.on_spamassassin = '1' \
and users.on_forward = '0' \
and users.axel_on_movespam = '1' \
and users.axel_on_filtermove != '1' \
and users.type = 'local' \
and users.domain_id=domains.domain_id }{$value}fail}} \
} \
} \
{yes}{no} \
}
local_part_suffix = -*
local_part_suffix_optional
retry_use_local_part
axel_move_spam_to_folder_highscore:
driver = redirect
allow_fail
file_transport = axel_move_folder_transport
data = ${lookup mysql{select concat(smtp,'/.Filter-Spam-High') \
from users,domains \
where localpart = '${quote_mysql:$local_part}' \
and domain = '${quote_mysql:$domain}' \
and domains.enabled = '1' \
and users.enabled = '1' \
and users.domain_id = domains.domain_id}}
condition = ${if and \
{ \
{>{$spam_score_int}{89}} \
{> \
{$spam_score_int} \
{${lookup mysql{select users.sa_tag * 10 from users,domains \
where localpart = '${quote_mysql:$local_part}' \
and domain = '${quote_mysql:$domain}' \
and users.on_spamassassin = '1' \
and users.on_forward = '0' \
and users.axel_on_movespam = '1' \
and users.axel_on_filtermove != '1' \
and users.type = 'local' \
and users.domain_id=domains.domain_id }{$value}fail}} \
} \
} \
{yes}{no} \
}
local_part_suffix = -*
local_part_suffix_optional
retry_use_local_part
These transporters will move spam between user's tag score and 9 to the folder Filter-Spam-Low (will be auto-generated), and spam between 9 and user's refuse score to folder Filter-Spam-High, but only if the user has turned on the setting via Vexim.axel_move_folder_transport:
driver = appendfile
envelope_to_add
return_path_add
mode = 0600
maildir_format = true
create_directory = true
transport_filter = '${if or { {eq {0}{${lookup mysql{select on_spamassassin from users,domains \
where localpart = "${quote_mysql:$local_part}" \
and domain = "${quote_mysql:$domain}" \
and users.domain_id=domains.domain_id}{$value}}}} \
{< {$spam_score_int}{${lookup mysql{select users.sa_tag * 10 from users,domains \
where localpart = "${quote_mysql:$local_part}" \
and domain = "${quote_mysql:$domain}" \
and users.domain_id=domains.domain_id \
and on_spamassassin = 1}{$value}}}} \
} {/bin/cat}{/usr/local/etc/exim/rewrite_subject.pl}}'
user = ${lookup mysql{select users.uid from users,domains \
where localpart = '${quote_mysql:$local_part}' \
and domain = '${quote_mysql:$domain}' \
and users.domain_id = domains.domain_id}}
group = ${lookup mysql{select users.gid from users,domains \
where localpart = '${quote_mysql:$local_part}' \
and domain = '${quote_mysql:$domain}' \
and users.domain_id = domains.domain_id}}
As you see, this transporter uses the perl script from chapter 2 to add "*****SPAM*****" to subject of tagged mail. If you don't want to use this script, just remove the transport_filter setting.This shell script will go through all mail folders and look for some special folders:
Setting up the script and automating it with a nightly cron is done like this:
#!/bin/sh # ---------- START ---------- sa-learn in standard folders ---------- learn="/usr/local/bin/sa-learn" SpamLearnDirs=`find /usr/local/mail/ -name "*LearnAsSpam" -type d` HamLearnDirs=`find /usr/local/mail/ -name "*LearnAsHam" -type d` for spamdir in $SpamLearnDirs; do $learn --spam $spamdir/cur $learn --spam $spamdir/new rm -f $spamdir/cur/* rm -f $spamdir/new/* done for hamdir in $HamLearnDirs; do $learn --ham $hamdir/cur $learn --ham $hamdir/new rm -f $hamdir/cur/* rm -f $hamdir/new/* done # ---------- END ---------- sa-learn in standard folders ---------- # ---------- START ---------- delete old mail in auto filter folders ---------- FilterSpamLowDirs=`find /usr/local/mail/ -name "*Filter-Spam-Low*" -type d` FilterSpamHighDirs=`find /usr/local/mail/ -name "*Filter-Spam-High*" -type d` for filterspamlowdir in $FilterSpamLowDirs; do find $filterspamlowdir/cur -type f -ctime +90 | xargs rm find $filterspamlowdir/new -type f -ctime +90 | xargs rm done for filterspamhighdir in $FilterSpamHighDirs; do find $filterspamhighdir/cur -type f -ctime +30 | xargs rm find $filterspamhighdir/new -type f -ctime +30 | xargs rm done # ---------- END ---------- delete old mail in auto filter folders ----------If you have your mail saved in another location than the default location, or the sa-learn binary is not placed in the directory in the code above, you will need to edit the code.
chmod 755 /usr/local/etc/exim/parse-spam.sh
/usr/local/etc/exim/parse-spam.sh
crontab -e
0 0 * * * /usr/local/etc/exim/spam-parse.sh >/var/log/spam-parse.log 2>&1
/var/log/spam-parse-delete-old.log 600 7 100 * J
I've been happy using Horde IMP as the webmail system for a long time, but recently changed to Roundcube as it provides a more sleek user experience thanks to the extensive use of AJAX technology.
I have made a plugin for Roundcube that moves all user configuration options in Vexim into the webmail. You will still need Vexim to administrate domains, but for regular users, there is no need to log in to Vexim. With this plugin they can change password, spam and virus settings, autoresponder, forwarding and header rules. The plugin can be used with a standard Vexim installation, and if you choose to add one or more of the customizations in chapter 1 to 5 on this page, you can enable support for these as well in the plugin configuration. The plugin is installed like this:
MYSQL_QUOTA_FIELD quota
$rcmail_config['plugins'] = array("veximaccountadmin", "otherplugin")
Since you are using Roundcube for webmail, you can change your Vexim login page to a Roundcube-like design (see screenshot on the right) for a cleaner user experience. The modification is only visual, and only regards the login page. The screenshot shows an installation with textbox as domain setting, but the modification also works with the dropdown or static setting.
Just download vexim-2.2.1-login_roundcube_style.tar.gz and uncompress it to your Vexim folder to freshen up the login page .
I hope you found this information useful. If you have any questions, don't hesitate to ask.
Please consider making a donation if this page made your life a little bit happier, that would be greatly appreciated and motivate a poor student to continue this project.
I appreciate all feedback/questions/comment/general rant - please leave a comment below! :)