MailScanner MailScanner is incredibly powerful in that it can determine at run-time whether certain processing should be invoked on a per- message basis; it does this with rulesets, which define regular expressions that are applied to sender or recipient’s email addresses, domains or IP addresses, in order for the program to decide whether a certain part of MailScanner should be invoked or not. To further increase flexibility, MailScanner supports so-called custom functions, Perl routines supplied by the administrator, which replace the rulesets and further augment MailScanner’s flexibility. Having created two different custom functions for MailScanner which have had substantial productional use, I’m writing this mini-tutorial in the hope it will be of use to MailScanner administrators. I use MailScanner with the excellent Exim MTA, but the description below ought to be generic to whichever MTA you use MailScanner with, be it Exim, sendmail, Postfix or any other supported MTA. Implementing a custom function for MailScanner is not for the faint of heart. Before attempting to do so, I strongly recomend you consider whether a ruleset provides enough flexibility for you, as they are easier to implement, test and maintain than custom code is. A number of good sample rulesets can be found in the MailScanner manual, and they demonstrate the flexibility of rulesets very well. In any case, check the documentation before continuing, and the excellent MailScanner Configuration Index is also worth keeping at hand. A custom function for MailScanner is one or more Perl sub_s kept in a Perl module in the directory _/usr/lib/MailScanner/CustomFunctions or wherever the MailScanner directive Custom Functions Dir points to (check the manual). Custom functions may be used in MailScanner wherever a ruleset is allowed. So, for example, instead of checking whether messages must be signed with a ruleset

    Sign Clean Messages = /etc/MailScanner/rules/sign.rules

I can use a custom function to do that:

    Sign Clean Messages = &SigFunc;

where SigFunc is the custom function I must supply (note the leading ampersand [&] which denotes a function). My custom function is invoked with a copy of the message and MailScanner’s environment, so there is little left to desire with regards to what the custom function can do. Its return value must match whatever MailScanner expects for the ruleset: yes/no are returned as integers 1 and 0 respectively, and if MailScanner expects a string, the function must supply that string. Apart from the existing sample functions in the MailScanner distribution, there is little documentation on custom functions. The first thing I was interested in was what exactly does my custom function receive? Let us write a custom function to determine that. We have to create a Perl module and install it into the CustomFunctions/ directory, and we have to tell MailScanner where to invoke that function. The file I’ll be creating is called /usr/lib/MailScanner/CustomFunctions/ and I’m telling MailScanner that I want it invoked to check whether outgoing messages need to be signed. This setting guarantees that my function will be invoked for each and every message it receives.

    Sign Clean Messages = &Myfunc;

This module is loaded when MailScanner starts and a supplied Init function is executed, in which initialization of the custom function can take place. Just before unloading the module, MailScanner executes the supplied End function which can be used by the programmer to de-initialize the module. The basic structure of my module will therefore be the InitMyfunc and EndMyfunc functions (both of which are unused at this time, but they must be present) as well as the actual Myfunc code. Let me show you the code:

package MailScanner::CustomConfig;
use Data::Dumper;

use strict 'vars';
use strict 'refs';
no  strict 'subs'; # Allow bare words for parameter %'s

use vars qw($VERSION);

$VERSION = substr q$Revision: 1.1 $, 10;

sub MyfuncLOG {
    my ($text) = @_;

    MailScanner::Log::InfoLog("Myfunc: $text");

sub InitMyfunc {
    MyfuncLOG("Starting Myfunc");

sub EndMyfunc {
    MyfuncLOG("Ending Myfunc");

sub Myfunc {
    my($message) = @_;

    return 0 unless $message;        # Sanity

    my $msgid = $message->{id};

    if (open(MESSAGE, "> /tmp/msg-$msgid")) {
        print MESSAGE Dumper($message);


The helper routine MyfuncLog utilizes MailScanner’s logging module to send a message to the same log to which MailScanner usually logs. On my system those messages are passed to syslog which stores them in /var/log/maillog. Note the solitary true value at the end of the module file (1;). This ensures that Perl can load the module. Without this, my module won’t be loaded. When testing the custom function keep a very close look at the MailScanner logs (probably /var/log/maillog) for errors. Runtime errors are hard to detect and usually cause MailScanner to croak; you’ll recognize that by MailScanner restarting itself constantly. These errors are very hard to find, and, apart from copious logging, I haven’t found a good way to debug them. For me, a tail -f /var/log/maillog is my friend. I’ll show you what I mean:

    Mar 28 22:59:18 dad MailScanner[2642]: MailScanner E-Mail Virus Scanner version 4.58.9 starting... 
    Mar 28 22:59:18 dad MailScanner[2642]: Read 764 hostnames from the phishing whitelist 
    Mar 28 22:59:18 dad MailScanner[2642]: Config: calling custom init function Myfunc 
    Mar 28 22:59:18 dad MailScanner[2642]: Myfunc: Starting Myfunc 
    Mar 28 22:59:30 dad MailScanner[2642]: New Batch: Scanning 1 messages, 672 bytes 
    Mar 28 22:59:30 dad MailScanner[2642]: Virus and Content Scanning: Starting 
    Mar 28 22:59:31 dad MailScanner[2642]: Myfunc: Invoking Myfunc for 1HWfEv-0000gh-C4 
    Mar 28 22:59:31 dad MailScanner[2642]: Uninfected: Delivered 1 messages 

When MailScanner starts, I see my init function being invoked. The fourth line is the log message in the init function. I then submitted a message, and MailScanner starts the new batch, during which it calls Myfunc to handle our message. Although not shown here, when stopping MailScanner, I see the end function being called. Once again, these two can be used to create and later drop a connection to a database or to bind and later unbind from an LDAP directory server. Generally, the Init function sets up the environment, and the End function cleans that up. Of course I’m specially curious as to whether our file /tmp/msg-_msgid_ was created. ;-) Here is an excerpt of it, as it contains the whole message structure as supplied by MailScanner. The use of Data::Dumper is invaluable in decoding that. So, without further ado, heeeere is the message!

$VAR1 = bless( {
       'datestring' => 'Wed Mar 28 22:59:30 2007',
       'sascore' => 0,
       'virusinfected' => 0,
       'metadata' => {
             'headers' => [
               'body' => ' postmaster@localhost',
               'name' => 'To:',
               'flag' => 'T'
               'body' => '',
               'name' => 'From:',
               'flag' => 'F'
             'dv_interface_address' => '',
             'numrcpts' => 1,
             'dv_body_linecount' => '2',
             'user' => 'root',
             'id' => '1HWfEv-0000gh-C4-H',
             'dv_received_protocol' => 'esmtp',
             'rcpts' => [
             'sender' => '',
       'id' => '1HWfEv-0000gh-C4',
       'otherreports' => {},
       'safesubject' => ' test Wed, 28 Mar 2007 22:59:29 +0200',
       'fromuser' => 'jpm',
       'spamreport' => 'not spam',
       'subject' => ' test Wed, 28 Mar 2007 22:59:29 +0200',
       'fromdomain' => '',
       'spamwhitelisted' => 0,
       'scanmail' => 1,
       'todomain' => [
       'size' => 672,
       'touser' => [
       'from' => '',
       'numberparts' => 1,
       'clientip' => '',
       'headerspath' => '/var/spool/MailScanner/incoming/2642/1HWfEv-0000gh-C4.header'
     }, 'MailScanner::Message' );

Believe it or not, I’ve shortened the output a bit (have a look at one of your own messages), but even so, there is a wealth of interesting information that was passed to our function: I’m sure you’ll agree. To begin with, sender and recipient details of course, as well as information on the message content: its parts, lines, size, date, sending client IP address, etc. Some of these values are MTA dependent, and most of them are undocumented at the best of times. Getting at the values you want to check ought to be quite easy: look at how I retrieved the Message-ID above. As already mentioned, Custom MailScanner functions can be used wherever a ruleset is expected. I myself have used such functions successfully for whitelisting incoming messages as well as for determining if an outgoing message needs a footer (signature) attached to it. Some more ideas might be:

  • Enable individual users to receive password-protected Zip files (Allow Password-Protected Archives)
  • Use a directory service to enable larger messages on a per-user basis; do remember though, that Maximum Message Size must match whatever your MTA uses
  • Decide on a per-user or per-domain basis whether Virus Scanning should be enabled (probably not a good idea)
  • Determine from message content whether Notifiy Senders should be applied or not

There are undoubtedly many other uses for custom functions, but once again: do first check if a normal ruleset will work for you. If not, I’d be pleased to hear where you are using custom functions for; just leave a comment.

LDAP, Mail, DomiNotes, MySQL, Exim, Linux, Security, Database, and CLI :: 29 Mar 2007 :: e-mail