ClamAVScan - antivirus scan mailet using ClamAV's CLAMD daemon
ClamAVScan does an antivirus scan check using the ClamAV daemon CLAMD (see
http://www.clamav.net/).
It interacts directly with the daemon using the "stream" method, which should have the lowest possible overhead. I've done tests getting less than 2.5 seconds of CPU per megabyte scanned on a 1.5 GHz CPU.
The CLAMD daemon will typically reside on localhost, but could reside on a different host. It may also consist on a set of multiple daemons, each residing on a different server and on different IP number. In such case a DNS host name with multiple IP adresses (round-robin load sharing) is supported by the mailet (but on the same port number).
ClamAV runs on Linux, but there are ports to other OS too. I have ClamAVScan working on Windows XP and 2k using ClamAV For Windows (see
http://www.sosdg.org/clamav-win32)
Initialization parameters
The init parameters are as follows:
<debug>.
<host>: the host name of the server where CLAMD runs. It can either be a machine name, such as "java.sun.com", or a textual representation of its IP address. If a literal IP address is supplied, only the validity of the address format is checked. If the machine name resolves to multiple IP addresses, round-robin load sharing will be used. The default is localhost.
<port>: the port on which CLAMD listens. The default is 3310.
<maxPings>: the maximum number of connection retries during startup. If the value is 0 no startup test will be done. The default is 6.
<pingIntervalMilli>: the interval between each connection retry during startup. The default is 30000 (30 seconds).
<streamBufferSize>: the BufferedOutputStream buffer size to use when writing to the stream connection. The default is 8192.
Behaviour
The actions performed are as follows:
During initialization:
Gets all config.xml parameters, handling the defaults;
resolves the <host> parameter, creating the round-robin IP list;
connects to CLAMD at the first IP in the round-robin list, on the specified <port>;
if unsuccessful, retries every <pingIntervalMilli> milliseconds up to <maxPings> times;
sends a "PING" request;
waits for a "PONG" answer;
repeats steps 3-6 for every other IP resolved.
For every mail
connects to CLAMD at the "next" IP in the round-robin list, on the specified <port>, and increments the "next" index; if the connection request is not accepted, tries with the next one in the list unless all of them have failed;
sends a "STREAM" request;
parses the "PORT streamPort" answer obtaining the port number;
makes a second connection (the stream connection) to CLAMD at the same host (or IP) on the streamPort just obtained;
sends the MimeMessage to CLAMD (using MimeMessage#writeTo(OutputStream)) through the stream connection;
closes the stream connection;
gets the "OK" or "... FOUND" answer from the main connection;
closes the main connection;
sets the "org.apache.james.infected" mail attribute to either "true" or "false";
adds the "X-MessageIsInfected" header to either "true" or "false", depending on the results of the scan.
ClamAV configuration notes
The following parameters are required in clamav.conf:
LocalSocket must be commented out
TCPSocket must be set to a port# (typically 3310)
StreamMaxLength must be >= the James config.xml parameter <maxmessagesize> in SMTP <handler>
MaxThreads should? be >= the James config.xml parameter <threads> in <spoolmanager>
ScanMail must be uncommented
A James config.xml example
Here follows an example of config.xml definitions deploying CLAMD on localhost, and handling the infected messages:
...
<!-- Do an antivirus scan -->
<mailet match="All" class="ClamAVScan" onMailetException="ignore"/>
<!-- If infected go to virus processor -->
<mailet match="HasMailAttributeWithValue=org.apache.james.infected, true" class="ToProcessor">
<processor> virus </processor>
</mailet>
<!-- Check attachment extensions for possible viruses -->
<mailet match="AttachmentFileNameIs=-d -z *.exe *.com *.bat *.cmd *.pif *.scr *.vbs *.avi *.mp3 *.mpeg *.shs" class="ToProcessor" onMatchException="noMatch">
<processor> bad-extensions </processor>
</mailet>
...
<!-- Messages containing viruses -->
<processor name="virus">
<!-- To avoid a loop while bouncing -->
<mailet match="All" class="SetMailAttribute">
<org.apache.james.infected>true, bouncing</org.apache.james.infected>
</mailet>
<mailet match="SMTPAuthSuccessful" class="Bounce">
<sender>bounce-admin@xxx.com</sender>
<inline>heads</inline>
<attachment>none</attachment>
<notice>Warning: We were unable to deliver the message below because it was found infected by virus(es).</notice>
</mailet>
<!--
<mailet match="All" class="ToRepository">
<repositoryPath>file://var/mail/infected/</repositoryPath>
</mailet>
-->
<mailet match="All" class="Null"/>
</processor>
...
The reason for the onMailetException="ignore" entry in the ClamAVScan mailet "call" is to avoid losing the message if an Exception is unlikely thrown, but it is just my choice, as I have a "second line" defense blocking "bad extensions" with an "AttachmentFileNameIs=" call.