Author: vincenzo
Date: Thu Feb 24 08:00:26 2005
New Revision: 155194
URL: http://svn.apache.org/viewcvs?view=rev&rev=155194
Log:
ClamAV antivirus scanner mailet.
Added:
james/server/branches/branch_2_1_fcs/src/java/org/apache/james/transport/mailets/ClamAVScan.java
Added:
james/server/branches/branch_2_1_fcs/src/java/org/apache/james/transport/mailets/ClamAVScan.java
URL:
http://svn.apache.org/viewcvs/james/server/branches/branch_2_1_fcs/src/java/org/apache/james/transport/mailets/ClamAVScan.java?view=auto&rev=155194
==============================================================================
---
james/server/branches/branch_2_1_fcs/src/java/org/apache/james/transport/mailets/ClamAVScan.java
(added)
+++
james/server/branches/branch_2_1_fcs/src/java/org/apache/james/transport/mailets/ClamAVScan.java
Thu Feb 24 08:00:26 2005
@@ -0,0 +1,927 @@
+/***********************************************************************
+ * Copyright (c) 2000-2004 The Apache Software Foundation. *
+ * All rights reserved. *
+ * ------------------------------------------------------------------- *
+ * Licensed under the Apache License, Version 2.0 (the "License"); you *
+ * may not use this file except in compliance with the License. You *
+ * may obtain a copy of the License at: *
+ * *
+ * http://www.apache.org/licenses/LICENSE-2.0 *
+ * *
+ * Unless required by applicable law or agreed to in writing, software *
+ * distributed under the License is distributed on an "AS IS" BASIS, *
+ * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
+ * implied. See the License for the specific language governing *
+ * permissions and limitations under the License. *
+ ***********************************************************************/
+
+package org.apache.james.transport.mailets;
+
+import org.apache.james.util.RFC2822Headers;
+import org.apache.mailet.GenericMailet;
+import org.apache.mailet.Mail;
+import org.apache.mailet.MailAddress;
+import org.apache.mailet.MailetException;
+
+import javax.mail.Address;
+import javax.mail.Message;
+import javax.mail.MessagingException;
+import javax.mail.Session;
+import javax.mail.internet.InternetAddress;
+import javax.mail.internet.MimeBodyPart;
+import javax.mail.internet.MimeMessage;
+import javax.mail.internet.MimeMultipart;
+import java.io.IOException;
+import java.io.PrintWriter;
+import java.io.StringWriter;
+import java.util.Date;
+import java.util.HashSet;
+import java.util.Set;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.ArrayList;
+import java.net.*;
+import java.io.*;
+
+
+/**
+ * <P>Does an antivirus scan check using a ClamAV daemon (CLAMD)</P>
+ *
+ * <P> Interacts directly with the daemon using the <I>stream</I> method,
+ * which should have the lowest possible overhead.</P>
+ * <P>The CLAMD daemon will typically reside on <I>localhost</I>, 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 addresses (round-robin load
sharing)
+ * is supported by the mailet (but on the same port number).</P>
+ *
+ * <P>Handles the following init parameters:</P>
+ * <UL>
+ * <LI><<CODE>debug</CODE>>.</LI>
+ * <LI><<CODE>host</CODE>>: the host name of the server where CLAMD
runs. It can either be
+ * a machine name, such as
+ * "<code>java.sun.com</code>", 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,
<I>round-robin load sharing</I> will
+ * be used.
+ * The default is <CODE>localhost</CODE>.</LI>
+ * <LI><<CODE>port</CODE>>: the port on which CLAMD listens. The
default is <I>3310</I>.</LI>
+ * <LI><<CODE>maxPings</CODE>>: the maximum number of connection
retries during startup.
+ * If the value is <I>0</I> no startup test will be done.
+ * The default is <I>6</I>.</LI>
+ * <LI><<CODE>pingIntervalMilli</CODE>>: the interval between each
connection retry during startup.
+ * The default is <I>30000</I> (30 seconds).</LI>
+ * <LI><<CODE>streamBufferSize</CODE>>: the BufferedOutputStream
buffer size to use
+ * writing to the <I>stream connection</I>. The default is
<I>8192</I>.</LI>
+ * </UL>
+ *
+ * <P>The actions performed are as follows:</P>
+ * <UL>
+ * <LI>During initialization:</LI>
+ * <OL>
+ * <LI>Gets all <CODE>config.xml</CODE> parameters, handling the
defaults;</LI>
+ * <LI>resolves the <CODE>host</CODE> parameter, creating the
round-robin IP list;</LI>
+ * <LI>connects to CLAMD at the first IP in the round-robin list, on
+ * the specified <CODE>port</CODE>;</LI>
+ * <LI>if unsuccessful, retries every <CODE>pingIntervalMilli</CODE>
milliseconds up to
+ * <CODE>maxPings</CODE> times;</LI>
+ * <LI>sends a <CODE>PING</CODE> request;</LI>
+ * <LI>waits for a <CODE>PONG</CODE> answer;</LI>
+ * <LI>repeats steps 3-6 for every other IP resolved.
+ * </OL>
+ * <LI>For every mail</LI>
+ * <OL>
+ * <LI>connects to CLAMD at the "next" IP in the round-robin list, on
+ * the specified <CODE>port</CODE>, 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;</LI>
+ * <LI>sends a <CODE>STREAM</CODE> request;</LI>
+ * <LI>parses the <CODE>PORT <I>streamPort</I></CODE> answer obtaining
the port number;</LI>
+ * <LI>makes a second connection (the <I>stream connection</I>) to
CLAMD at the same host (or IP)
+ * on the <I>streamPort</I> just obtained;</LI>
+ * <LI>sends the mime message to CLAMD (using [EMAIL PROTECTED]
MimeMessage#writeTo(OutputStream)})
+ * through the <I>stream connection</I>;</LI>
+ * <LI>closes the <I>stream connection</I>;</LI>
+ * <LI>gets the <CODE>OK</CODE> or <CODE>FOUND</CODE> answer from the
main connection;</LI>
+ * <LI>closes the main connection;</LI>
+ * <LI>sets the <CODE>org.apache.james.infected</CODE> <I>mail
attribute</I> to either
+ * <CODE>true</CODE> or <CODE>false</CODE>;</LI>
+ * <LI>adds the <CODE>X-MessageIsInfected</CODE> <I>header</I> to either
+ * <CODE>true</CODE> or <CODE>false</CODE>;</LI>
+ * </OL>
+ * </UL>
+ *
+ * <P>Some notes regarding <a
href="http://www.clamav.net/">clamav.conf</a>:</p>
+ * <UL>
+ * <LI><CODE>LocalSocket</CODE> must be commented out</LI>
+ * <LI><CODE>TCPSocket</CODE> must be set to a port# (typically 3310)</LI>
+ * <LI><CODE>StreamMaxLength</CODE> must be >= the James config.xml
parameter
+ * <<CODE>maxmessagesize</CODE>> in SMTP
<<CODE>handler</CODE>></LI>
+ * <LI><CODE>MaxThreads</CODE> should? be >= the James config.xml
parameter
+ * <<CODE>threads</CODE>> in <<CODE>spoolmanager</CODE>></LI>
+ * <LI><CODE>ScanMail</CODE> must be uncommented</LI>
+ * </UL>
+ *
+ * <P>Here follows an example of config.xml definitions deploying CLAMD on
localhost,
+ * and handling the infected messages:</P>
+ * <PRE><CODE>
+ *
+ * ...
+ *
+ * <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>
+ *
+ * ...
+ *
+ * <!-- Messages containing viruses -->
+ *
+ * <!-- To avoid a loop while bouncing -->
+ * <processor name="virus">
+ * <mailet match="All" class="SetMailAttribute">
+ * <org.apache.james.infected>true,
bouncing</org.apache.james.infected>
+ * </mailet>
+ *
+ * <mailet match="SMTPAuthSuccessful" class="Bounce">
+ * <sender>[EMAIL PROTECTED]</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>
+ * </CODE></PRE>
+ *
+ * @version 2.2.1
+ * @since 2.2.1
+ * @see <a href="http://www.clamav.net/">ClamAV Home Page</a>
+ * @see <a href="http://www.sosdg.org/clamav-win32/">ClamAV For Windows</a>
+ */
+public class ClamAVScan extends GenericMailet {
+
+ private static final int DEFAULT_PORT = 3310;
+
+ private static final int DEFAULT_MAX_PINGS = 6;
+
+ private static final int DEFAULT_PING_INTERVAL_MILLI = 30000;
+
+ private static final int DEFAULT_STREAM_BUFFER_SIZE = 8192;
+
+ private static final String STREAM_PORT_STRING = "PORT ";
+
+ private static final String FOUND_STRING = "FOUND";
+
+ private static final String MAIL_ATTRIBUTE_NAME =
"org.apache.james.infected";
+
+ private static final String HEADER_NAME = "X-MessageIsInfected";
+
+ /**
+ * Holds value of property debug.
+ */
+ private boolean debug;
+
+ /**
+ * Holds value of property host.
+ */
+ private String host;
+
+ /**
+ * Holds value of property port.
+ */
+ private int port;
+
+ /**
+ * Holds value of property maxPings.
+ */
+ private int maxPings;
+
+ /**
+ * Holds value of property pingIntervalMilli.
+ */
+ private int pingIntervalMilli;
+
+ /**
+ * Holds value of property streamBufferSize.
+ */
+ private int streamBufferSize;
+
+ /**
+ * Holds value of property addresses.
+ */
+ private InetAddress[] addresses;
+
+ /**
+ * Holds the index of the next address to connect to
+ */
+ private int nextAddressIndex;
+
+ /**
+ * Return a string describing this mailet.
+ *
+ * @return a string describing this mailet
+ */
+ public String getMailetInfo() {
+ return "Antivirus Check using ClamAV (CLAMD)";
+ }
+
+ /** Gets the expected init parameters. */
+ protected String[] getAllowedInitParameters() {
+ String[] allowedArray = {
+ // "static",
+ "debug",
+ "host",
+ "port",
+ "maxPings",
+ "pingIntervalMilli",
+ "streamBufferSize"
+ };
+ return allowedArray;
+ }
+
+ /**
+ * Initializer for property debug.
+ */
+ protected void initDebug() {
+ String debugParam = getInitParameter("debug");
+ setDebug((debugParam == null) ? false : new
Boolean(debugParam).booleanValue());
+ }
+
+ /**
+ * Getter for property debug.
+ * @return Value of property debug.
+ */
+ public boolean isDebug() {
+ return this.debug;
+ }
+
+ /**
+ * Setter for property debug.
+ * @param debug New value of property debug.
+ */
+ public void setDebug(boolean debug) {
+ this.debug = debug;
+ }
+
+ /**
+ * Initializer for property host.
+ * @throws UnknownHostException if unable to resolve the host name, or if
invalid
+ */
+ protected void initHost() throws UnknownHostException {
+ setHost(getInitParameter("host"));
+ if (isDebug()) {
+ log("host: " + getHost());
+ }
+ }
+
+ /**
+ * Getter for property host.
+ * @return Value of property host.
+ */
+ public String getHost() {
+
+ return this.host;
+ }
+
+ /**
+ * Setter for property host.
+ * Resolves also the host name into the corresponding IP addresses, issues
+ * a [EMAIL PROTECTED] #setAddresses} and resets the
<CODE>nextAddressIndex</CODE>
+ * variable to <I>0</I> for dealing with <I>round-robin</I>.
+ * @param host New value of property host.
+ * @throws UnknownHostException if unable to resolve the host name, or if
invalid
+ */
+ public void setHost(String host) throws UnknownHostException {
+
+ this.host = host;
+
+ setAddresses(InetAddress.getAllByName(host));
+
+ nextAddressIndex = 0;
+ }
+
+ /**
+ * Initializer for property port.
+ */
+ protected void initPort() {
+ String portParam = getInitParameter("port");
+ setPort((portParam == null) ? DEFAULT_PORT :
Integer.parseInt(portParam));
+ if (isDebug()) {
+ log("port: " + getPort());
+ }
+ }
+
+ /**
+ * Getter for property port.
+ * @return Value of property port.
+ */
+ public int getPort() {
+
+ return this.port;
+ }
+
+ /**
+ * Setter for property port.
+ * @param port New value of property port.
+ */
+ public void setPort(int port) {
+
+ this.port = port;
+ }
+
+ /**
+ * Initializer for property maxPings.
+ */
+ protected void initMaxPings() {
+ String maxPingsParam = getInitParameter("maxPings");
+ setMaxPings((maxPingsParam == null) ? DEFAULT_MAX_PINGS :
Integer.parseInt(maxPingsParam));
+ if (isDebug()) {
+ log("maxPings: " + getMaxPings());
+ }
+ }
+
+ /**
+ * Getter for property maxPings.
+ * @return Value of property maxPings.
+ */
+ public int getMaxPings() {
+
+ return this.maxPings;
+ }
+
+ /**
+ * Setter for property maxPings.
+ * @param maxPings New value of property maxPings.
+ */
+ public void setMaxPings(int maxPings) {
+
+ this.maxPings = maxPings;
+ }
+
+ /**
+ * Initializer for property pingIntervalMilli.
+ */
+ protected void initPingIntervalMilli() {
+ String pingIntervalMilliParam = getInitParameter("pingIntervalMilli");
+ setPingIntervalMilli((pingIntervalMilliParam == null) ?
DEFAULT_PING_INTERVAL_MILLI : Integer.parseInt(pingIntervalMilliParam));
+ if (isDebug()) {
+ log("pingIntervalMilli: " + getPingIntervalMilli());
+ }
+ }
+
+ /**
+ * Getter for property pingIntervalMilli.
+ * @return Value of property pingIntervalMilli.
+ */
+ public int getPingIntervalMilli() {
+
+ return this.pingIntervalMilli;
+ }
+
+ /**
+ * Setter for property pingIntervalMilli.
+ * @param pingIntervalMilli New value of property pingIntervalMilli.
+ */
+ public void setPingIntervalMilli(int pingIntervalMilli) {
+
+ this.pingIntervalMilli = pingIntervalMilli;
+ }
+
+ /**
+ * Initializer for property streamBufferSize.
+ */
+ protected void initStreamBufferSize() {
+ String streamBufferSizeParam = getInitParameter("streamBufferSize");
+ setStreamBufferSize((streamBufferSizeParam == null) ?
DEFAULT_STREAM_BUFFER_SIZE : Integer.parseInt(streamBufferSizeParam));
+ if (isDebug()) {
+ log("streamBufferSize: " + getStreamBufferSize());
+ }
+ }
+
+ /**
+ * Getter for property streamBufferSize.
+ * @return Value of property streamBufferSize.
+ */
+ public int getStreamBufferSize() {
+
+ return this.streamBufferSize;
+ }
+
+ /**
+ * Setter for property streamBufferSize.
+ * @param streamBufferSize New value of property streamBufferSize.
+ */
+ public void setStreamBufferSize(int streamBufferSize) {
+
+ this.streamBufferSize = streamBufferSize;
+ }
+
+ /**
+ * Indexed getter for property addresses.
+ * @param index Index of the property.
+ * @return Value of the property at <CODE>index</CODE>.
+ */
+ protected InetAddress getAddresses(int index) {
+
+ return this.addresses[index];
+ }
+
+ /**
+ * Getter for property addresses.
+ * @return Value of property addresses.
+ */
+ protected InetAddress[] getAddresses() {
+
+ return this.addresses;
+ }
+
+ /**
+ * Setter for property addresses.
+ * @param addresses New value of property addresses.
+ */
+ protected void setAddresses(InetAddress[] addresses) {
+
+ this.addresses = addresses;
+ }
+
+ /**
+ * Getter for property nextAddress.
+ *
+ * Gets the address of the next CLAMD server to connect to in this round,
using round-robin.
+ * Increments the nextAddressIndex for the next round.
+ * @return Value of property address.
+ */
+ protected synchronized InetAddress getNextAddress() {
+
+ InetAddress address = getAddresses(nextAddressIndex);
+
+ nextAddressIndex++;
+ if (nextAddressIndex >= getAddresses().length) {
+ nextAddressIndex = 0;
+ }
+
+ return address;
+ }
+
+ /**
+ * Gets a Socket connected to CLAMD.
+ *
+ * Will loop though the round-robin address list until the first one
accepts
+ * the connection.
+ * @return a socket connected to CLAMD
+ * @throws MessagingException if no CLAMD in the round-robin address list
has accepted the connection
+ */
+ protected Socket getClamdSocket() throws MessagingException {
+
+ InetAddress firstAddress = getNextAddress();
+
+ InetAddress address = firstAddress;
+ do {
+ try {
+ // get the socket
+ return new Socket(address, getPort());
+ } catch (IOException ioe) {
+ log("Exception caught acquiring main socket to CLAMD on " +
address + " on port " + getPort() + ": " + ioe.getMessage());
+ address = getNextAddress();
+ // retry
+ continue;
+ }
+
+ } while (address != firstAddress);
+
+ String logText = "Unable to connect to CLAMD. All addresses failed.";
+ log(logText + " Giving up.");
+ throw new MessagingException(logText);
+ }
+
+ /**
+ * Mailet initialization routine.
+ */
+ public void init() throws MessagingException {
+
+ // check that all init parameters have been declared in
allowedInitParameters
+ checkInitParameters(getAllowedInitParameters());
+
+ try {
+ initDebug();
+ if (isDebug()) {
+ log("Initializing");
+ }
+
+ initHost();
+ initPort();
+ initMaxPings();
+ initPingIntervalMilli();
+ initStreamBufferSize();
+
+ // If "maxPings is > ping the CLAMD server to check if it is up
+ if (getMaxPings() > 0) {
+ ping();
+ }
+
+ } catch (Exception e) {
+ log("Exception thrown", e);
+ throw new MessagingException("Exception thrown", e);
+ }
+
+ }
+
+ /**
+ * Scans the mail.
+ *
+ * @param mail the mail to scan
+ * @throws MessagingException if a problem arises
+ */
+ public void service(Mail mail) throws MessagingException {
+
+ // if already checked no action
+ if (mail.getAttribute(MAIL_ATTRIBUTE_NAME) != null) {
+ return;
+ }
+
+ MimeMessage mimeMessage = mail.getMessage();
+
+ if (mimeMessage == null) {
+ log("Null MimeMessage. Will send to ghost");
+ // write mail info to log
+ logMailInfo(mail);
+ mail.setState(Mail.GHOST);
+ return;
+ }
+
+ // get the socket
+ Socket socket = getClamdSocket();
+ BufferedReader reader = null;
+ PrintWriter writer = null;
+ Socket streamSocket = null;
+ BufferedOutputStream bos = null;
+
+ try {
+
+ // prepare the reader and writer for the commands
+ reader = new BufferedReader(new
InputStreamReader(socket.getInputStream(), "ASCII"));
+ writer = new PrintWriter(new BufferedWriter(new
OutputStreamWriter(socket.getOutputStream())), true);
+
+ // write a request for a port to use for streaming out the data to
scan
+ writer.println("STREAM");
+ writer.flush();
+
+ // parse and get the "stream" port#
+ int streamPort = getStreamPortFromAnswer(reader.readLine());
+
+ // get the "stream" socket and the related (buffered) output stream
+ streamSocket = new Socket(socket.getInetAddress(), streamPort);
+ bos = new BufferedOutputStream(streamSocket.getOutputStream(),
getStreamBufferSize());
+
+ // stream out the message to the scanner
+ mimeMessage.writeTo(bos);
+ bos.flush();
+ bos.close();
+ streamSocket.close();
+
+ String answer = null;
+ boolean virusFound = false;
+ String logMessage = "";
+ for (;;) {
+ answer = reader.readLine();
+ if (answer != null) {
+ answer = answer.trim();
+
+ // if a virus is found the answer will be '... FOUND'
+ if (answer.substring(answer.length() -
FOUND_STRING.length()).equals(FOUND_STRING)) {
+ virusFound = true;
+ logMessage = answer + " (by CLAMD on " +
socket.getInetAddress() + ")";
+ log(logMessage);
+ }
+ } else {
+ break;
+ }
+ }
+
+ reader.close();
+ writer.close();
+
+ if (virusFound) {
+ String errorMessage = mail.getErrorMessage();
+ if (errorMessage == null) {
+ errorMessage = "";
+ } else {
+ errorMessage += "\r\n";
+ }
+ StringBuffer sb = new StringBuffer(errorMessage);
+ sb.append(logMessage + "\r\n");
+
+ // write mail and message info to log
+ logMailInfo(mail);
+ logMessageInfo(mimeMessage);
+
+ // mark the mail with a mail attribute to check later on by
other matchers/mailets
+ mail.setAttribute(MAIL_ATTRIBUTE_NAME, "true");
+
+ // sets the error message to be shown in any "notifyXxx"
message
+ mail.setErrorMessage(sb.toString());
+
+ // mark the message with a header string
+ mimeMessage.setHeader(HEADER_NAME, "true");
+
+ } else {
+ if (isDebug()) {
+ log("OK (by CLAMD on " + socket.getInetAddress() + ")");
+ }
+ mail.setAttribute(MAIL_ATTRIBUTE_NAME, "false");
+
+ // mark the message with a header string
+ mimeMessage.setHeader(HEADER_NAME, "false");
+
+ }
+
+ try {
+ saveChanges(mimeMessage);
+ } catch (Exception ex) {
+ log("Exception caught while saving changes (header) to the
MimeMessage. Ignoring ...", ex);
+ }
+
+ } catch (Exception ex) {
+ log("Exception caught calling CLAMD on " + socket.getInetAddress()
+ ": " + ex.getMessage(), ex);
+ throw new MessagingException("Exception caught", ex);
+ } finally {
+ try {
+ if (reader != null) {
+ reader.close();
+ }
+ } catch (Throwable t) {}
+ try {
+ if (writer != null) {
+ writer.close();
+ }
+ } catch (Throwable t) {}
+ try {
+ if (bos != null) {
+ bos.close();
+ }
+ } catch (Throwable t) {}
+ try {
+ if (streamSocket != null) {
+ streamSocket.close();
+ }
+ } catch (Throwable t) {}
+ try {
+ if (socket != null) {
+ socket.close();
+ }
+ } catch (Throwable t) {}
+ }
+
+ }
+
+ /**
+ * Checks if there are unallowed init parameters specified in the
configuration file
+ * against the String[] allowedInitParameters.
+ * @param allowedArray array of strings containing the allowed parameter
names
+ * @throws MessagingException if an unknown parameter name is found
+ */
+ protected final void checkInitParameters(String[] allowedArray) throws
MessagingException {
+ // if null then no check is requested
+ if (allowedArray == null) {
+ return;
+ }
+
+ Collection allowed = new HashSet();
+ Collection bad = new ArrayList();
+
+ for (int i = 0; i < allowedArray.length; i++) {
+ allowed.add(allowedArray[i]);
+ }
+
+ Iterator iterator = getInitParameterNames();
+ while (iterator.hasNext()) {
+ String parameter = (String) iterator.next();
+ if (!allowed.contains(parameter)) {
+ bad.add(parameter);
+ }
+ }
+
+ if (bad.size() > 0) {
+ throw new MessagingException("Unexpected init parameters found: "
+ + arrayToString(bad.toArray()));
+ }
+ }
+
+ /**
+ * Utility method for obtaining a string representation of an array of
Objects.
+ */
+ private final String arrayToString(Object[] array) {
+ if (array == null) {
+ return "null";
+ }
+ StringBuffer sb = new StringBuffer(1024);
+ sb.append("[");
+ for (int i = 0; i < array.length; i++) {
+ if (i > 0) {
+ sb.append(",");
+ }
+ sb.append(array[i]);
+ }
+ sb.append("]");
+ return sb.toString();
+ }
+
+ /**
+ * Tries to "ping" all the CLAMD daemons to
+ * check if they are up and accepting requests.
+ **/
+
+ protected void ping() throws Exception {
+
+ for (int i = 0; i < addresses.length; i++) {
+ ping(addresses[i]);
+ }
+ }
+
+ /**
+ * Tries (and retries as specified up to 'getMaxPings()') to "ping" the
specified CLAMD daemon to
+ * check if it is up and accepting requests.
+ * @param address the address to "ping"
+ */
+ protected void ping(InetAddress address) throws Exception {
+ Socket socket = null;
+
+ int ping = 1;
+ for (; ; ) {
+ if (isDebug()) {
+ log("Trial #" + ping + "/" + getMaxPings() + " - creating
socket connected to " + address + " on port " + getPort());
+ }
+ try {
+ socket = new Socket(address, getPort());
+ break;
+ } catch (ConnectException ce) {
+ log("Trial #" + ping + "/" + getMaxPings() + " - exception
caught: " + ce.toString() + " while creating socket connected to " + address +
" on port " + getPort());
+ ping++;
+ if (ping <= getMaxPings()) {
+ log("Waiting " + getPingIntervalMilli() + " milliseconds
before retrying ...");
+ Thread.sleep(getPingIntervalMilli());
+ } else {
+ break;
+ }
+ }
+ }
+
+ // if 'socket' is still null then 'maxPings' has been exceeded
+ if (socket == null) {
+ throw new ConnectException("maxPings exceeded: " + getMaxPings() +
". Giving up.");
+ }
+
+ try {
+ // get the reader and writer to ping and receive pong
+ BufferedReader reader = new BufferedReader(new
InputStreamReader(socket.getInputStream(), "ASCII"));
+ PrintWriter writer = new PrintWriter(new BufferedWriter(new
OutputStreamWriter(socket.getOutputStream())), true);
+
+ log("Sending: \"PING\" to " + address + " ...");
+ writer.println("PING");
+ writer.flush();
+
+ boolean pongReceived = false;
+ for (;;) {
+ String answer = reader.readLine();
+ if (answer != null) {
+ answer = answer.trim();
+ log("Received: \"" + answer + "\"");
+ answer = answer.trim();
+ if (answer.equals("PONG")) {
+ pongReceived = true;
+ }
+
+ } else {
+ break;
+ }
+ }
+
+ reader.close();
+ writer.close();
+
+ if (!pongReceived) {
+ throw new ConnectException("Bad answer from \"PING\" probe:
expecting \"PONG\"");
+ }
+ } finally {
+ socket.close();
+ }
+ }
+
+ /**
+ * Parses the answer from a STREAM request and gets the port number.
+ *
+ * @param answer the answer from CLAMD containing the port number
+ * @return the port number for streaming out the data to scan
+ */
+ protected final int getStreamPortFromAnswer(String answer) throws
ConnectException {
+ int port = -1;
+ if (answer != null && answer.startsWith(STREAM_PORT_STRING)) {
+ try {
+ port =
Integer.parseInt(answer.substring(STREAM_PORT_STRING.length()));
+ } catch (NumberFormatException nfe) {
+
+ }
+ }
+
+ if (port <= 0) {
+ throw new ConnectException("\"PORT nn\" expected - unable to
parse: " + "\"" + answer + "\"");
+ }
+
+ return port;
+ }
+
+ /**
+ * Saves changes resetting the original message id.
+ *
+ * @param message the message to save
+ */
+ protected final void saveChanges(MimeMessage message) throws
MessagingException {
+ String messageId = message.getMessageID();
+ message.saveChanges();
+ if (messageId != null) {
+ message.setHeader(RFC2822Headers.MESSAGE_ID, messageId);
+ }
+ }
+
+ private void logMailInfo(Mail mail) {
+
+ // writes the error message to the log
+ StringWriter sout = new StringWriter();
+ PrintWriter out = new PrintWriter(sout, true);
+
+ out.print("Mail details:");
+ out.print(" MAIL FROM: " + mail.getSender());
+ Iterator rcptTo = mail.getRecipients().iterator();
+ out.print(", RCPT TO: " + rcptTo.next());
+ while (rcptTo.hasNext()) {
+ out.print(", " + rcptTo.next());
+ }
+
+ log(sout.toString());
+ }
+
+ private void logMessageInfo(MimeMessage mimeMessage) {
+
+ // writes the error message to the log
+ StringWriter sout = new StringWriter();
+ PrintWriter out = new PrintWriter(sout, true);
+
+ out.println("MimeMessage details:");
+
+ try {
+ if (mimeMessage.getSubject() != null) {
+ out.println(" Subject: " + mimeMessage.getSubject());
+ }
+ if (mimeMessage.getSentDate() != null) {
+ out.println(" Sent date: " + mimeMessage.getSentDate());
+ }
+ String[] sender = null;
+ sender = mimeMessage.getHeader(RFC2822Headers.FROM);
+ if (sender != null) {
+ out.print(" From: ");
+ for (int i = 0; i < sender.length; i++) {
+ out.print(sender[i] + " ");
+ }
+ out.println();
+ }
+ String[] rcpts = null;
+ rcpts = mimeMessage.getHeader(RFC2822Headers.TO);
+ if (rcpts != null) {
+ out.print(" To: ");
+ for (int i = 0; i < rcpts.length; i++) {
+ out.print(rcpts[i] + " ");
+ }
+ out.println();
+ }
+ rcpts = mimeMessage.getHeader(RFC2822Headers.CC);
+ if (rcpts != null) {
+ out.print(" CC: ");
+ for (int i = 0; i < rcpts.length; i++) {
+ out.print(rcpts[i] + " ");
+ }
+ out.println();
+ }
+ out.print(" Size (in bytes): " + mimeMessage.getSize());
+ if (mimeMessage.getLineCount() >= 0) {
+ out.print(", Number of lines: " + mimeMessage.getLineCount());
+ }
+ } catch (MessagingException me) {
+ log("Exception caught reporting message details", me);
+ }
+
+ log(sout.toString());
+ }
+
+}
+
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]