/****************************************************************
* Licensed to the Apache Software Foundation (ASF) under one *
* or more contributor license agreements. See the NOTICE file *
* distributed with this work for additional information *
* regarding copyright ownership. The ASF licenses this file *
* to you 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.mailet.RFC2822Headers;
import org.apache.mailet.GenericMailet;
import org.apache.mailet.Mail;
import javax.mail.MessagingException;
import javax.mail.internet.MimeMessage;
import java.io.BufferedOutputStream;
import java.io.BufferedReader;
import java.io.BufferedWriter;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.OutputStreamWriter;
import java.io.PrintWriter;
import java.io.StringWriter;
import java.net.ConnectException;
import java.net.InetAddress;
import java.net.Socket;
import java.net.UnknownHostException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashSet;
import java.util.Iterator;
import java.util.Set;
/**
* <P>Does an antivirus scan check using a ClamAV daemon (CLAMD)</P>
*
* <P> Interacts directly with the daemon using the "stream" 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 (in milliseconds)
* 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 {@link 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>
*
* ...
*
* <!-- 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">
* <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>
* </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 int DEFAULT_CONNECTION_TIMEOUT = 20000;
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 {@link #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 >= getAddressesCount()) {
nextAddressIndex = 0;
}
return address;
}
/**
* Getter for property addressesCount.
* @return Value of property addressesCount.
*/
public int getAddressesCount() {
return getAddresses().length;
}
/**
* 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 address = null;
Set usedAddresses = new HashSet(getAddressesCount());
for (;;) {
// this do-while loop is needed because other threads could in the meantime
// calling getNextAddress(), and because of that the current thread may skip
// some working address
do {
if (usedAddresses.size() >= getAddressesCount()) {
String logText = "Unable to connect to CLAMD. All addresses failed.";
log(logText + " Giving up.");
throw new MessagingException(logText);
}
address = getNextAddress();
} while (!usedAddresses.add(address));
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;
}
}
}
/**
* 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 < getAddressesCount(); i++) {
ping(getAddresses(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. The clamd daemon seems not to be running");
}
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());
}
}
|