001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james.transport.mailets;
019:
020: import org.apache.mailet.RFC2822Headers;
021: import org.apache.mailet.GenericMailet;
022: import org.apache.mailet.Mail;
023:
024: import javax.mail.MessagingException;
025: import javax.mail.internet.MimeMessage;
026:
027: import java.io.BufferedOutputStream;
028: import java.io.BufferedReader;
029: import java.io.BufferedWriter;
030: import java.io.IOException;
031: import java.io.InputStreamReader;
032: import java.io.OutputStreamWriter;
033: import java.io.PrintWriter;
034: import java.io.StringWriter;
035: import java.net.ConnectException;
036: import java.net.InetAddress;
037: import java.net.Socket;
038: import java.net.UnknownHostException;
039: import java.util.ArrayList;
040: import java.util.Collection;
041: import java.util.HashSet;
042: import java.util.Iterator;
043: import java.util.Set;
044:
045: /**
046: * <P>Does an antivirus scan check using a ClamAV daemon (CLAMD)</P>
047: *
048: * <P> Interacts directly with the daemon using the "stream" method,
049: * which should have the lowest possible overhead.</P>
050: * <P>The CLAMD daemon will typically reside on <I>localhost</I>, but could reside on a
051: * different host.
052: * It may also consist on a set of multiple daemons, each residing on a different
053: * server and on different IP number.
054: * In such case a DNS host name with multiple IP addresses (round-robin load sharing)
055: * is supported by the mailet (but on the same port number).</P>
056: *
057: * <P>Handles the following init parameters:</P>
058: * <UL>
059: * <LI><CODE><debug></CODE>.</LI>
060: * <LI><CODE><host></CODE>: the host name of the server where CLAMD runs. It can either be
061: * a machine name, such as
062: * "<code>java.sun.com</code>", or a textual representation of its
063: * IP address. If a literal IP address is supplied, only the
064: * validity of the address format is checked.
065: * If the machine name resolves to multiple IP addresses, <I>round-robin load sharing</I> will
066: * be used.
067: * The default is <CODE>localhost</CODE>.</LI>
068: * <LI><CODE><port></CODE>: the port on which CLAMD listens. The default is <I>3310</I>.</LI>
069: * <LI><CODE><maxPings></CODE>: the maximum number of connection retries during startup.
070: * If the value is <I>0</I> no startup test will be done.
071: * The default is <I>6</I>.</LI>
072: * <LI><CODE><pingIntervalMilli></CODE>: the interval (in milliseconds)
073: * between each connection retry during startup.
074: * The default is <I>30000</I> (30 seconds).</LI>
075: * <LI><CODE><streamBufferSize></CODE>: the BufferedOutputStream buffer size to use
076: * writing to the <I>stream connection</I>. The default is <I>8192</I>.</LI>
077: * </UL>
078: *
079: * <P>The actions performed are as follows:</P>
080: * <UL>
081: * <LI>During initialization:</LI>
082: * <OL>
083: * <LI>Gets all <CODE>config.xml</CODE> parameters, handling the defaults;</LI>
084: * <LI>resolves the <CODE><host></CODE> parameter, creating the round-robin IP list;</LI>
085: * <LI>connects to CLAMD at the first IP in the round-robin list, on
086: * the specified <CODE><port></CODE>;</LI>
087: * <LI>if unsuccessful, retries every <CODE><pingIntervalMilli></CODE> milliseconds up to
088: * <CODE><maxPings></CODE> times;</LI>
089: * <LI>sends a <CODE>PING</CODE> request;</LI>
090: * <LI>waits for a <CODE>PONG</CODE> answer;</LI>
091: * <LI>repeats steps 3-6 for every other IP resolved.
092: * </OL>
093: * <LI>For every mail</LI>
094: * <OL>
095: * <LI>connects to CLAMD at the "next" IP in the round-robin list, on
096: * the specified <CODE><port></CODE>, and increments the "next" index;
097: * if the connection request is not accepted tries with the next one
098: * in the list unless all of them have failed;</LI>
099: * <LI>sends a "<CODE>STREAM</CODE>" request;</LI>
100: * <LI>parses the "<CODE>PORT <I>streamPort</I></CODE>" answer obtaining the port number;</LI>
101: * <LI>makes a second connection (the <I>stream connection</I>) to CLAMD at the same host (or IP)
102: * on the <I>streamPort</I> just obtained;</LI>
103: * <LI>sends the mime message to CLAMD (using {@link MimeMessage#writeTo(OutputStream)})
104: * through the <I>stream connection</I>;</LI>
105: * <LI>closes the <I>stream connection</I>;</LI>
106: * <LI>gets the "<CODE>OK</CODE>" or "<CODE>... FOUND</CODE>" answer from the main connection;</LI>
107: * <LI>closes the main connection;</LI>
108: * <LI>sets the "<CODE>org.apache.james.infected</CODE>" <I>mail attribute</I> to either
109: * "<CODE>true</CODE>" or "<CODE>false</CODE>";</LI>
110: * <LI>adds the "<CODE>X-MessageIsInfected</CODE>" <I>header</I> to either
111: * "<CODE>true</CODE>" or "<CODE>false</CODE>";</LI>
112: * </OL>
113: * </UL>
114: *
115: * <P>Some notes regarding <a href="http://www.clamav.net/">clamav.conf</a>:</p>
116: * <UL>
117: * <LI><CODE>LocalSocket</CODE> must be commented out</LI>
118: * <LI><CODE>TCPSocket</CODE> must be set to a port# (typically 3310)</LI>
119: * <LI><CODE>StreamMaxLength</CODE> must be >= the James config.xml parameter
120: * <<CODE>maxmessagesize</CODE>> in SMTP <<CODE>handler</CODE>></LI>
121: * <LI><CODE>MaxThreads</CODE> should? be >= the James config.xml parameter
122: * <<CODE>threads</CODE>> in <<CODE>spoolmanager</CODE>></LI>
123: * <LI><CODE>ScanMail</CODE> must be uncommented</LI>
124: * </UL>
125: *
126: * <P>Here follows an example of config.xml definitions deploying CLAMD on localhost,
127: * and handling the infected messages:</P>
128: * <PRE><CODE>
129: *
130: * ...
131: *
132: * <!-- Do an antivirus scan -->
133: * <mailet match="All" class="ClamAVScan" onMailetException="ignore"/>
134: *
135: * <!-- If infected go to virus processor -->
136: * <mailet match="HasMailAttributeWithValue=org.apache.james.infected, true" class="ToProcessor">
137: * <processor> virus </processor>
138: * </mailet>
139: *
140: * <!-- Check attachment extensions for possible viruses -->
141: * <mailet match="AttachmentFileNameIs=-d -z *.exe *.com *.bat *.cmd *.pif *.scr *.vbs *.avi *.mp3 *.mpeg *.shs" class="ToProcessor">
142: * <processor> bad-extensions </processor>
143: * </mailet>
144: *
145: * ...
146: *
147: * <!-- Messages containing viruses -->
148: * <processor name="virus">
149: *
150: * <!-- To avoid a loop while bouncing -->
151: * <mailet match="All" class="SetMailAttribute">
152: * <org.apache.james.infected>true, bouncing</org.apache.james.infected>
153: * </mailet>
154: *
155: * <mailet match="SMTPAuthSuccessful" class="Bounce">
156: * <sender>bounce-admin@xxx.com</sender>
157: * <inline>heads</inline>
158: * <attachment>none</attachment>
159: * <notice> Warning: We were unable to deliver the message below because it was found infected by virus(es). </notice>
160: * </mailet>
161: *
162: * <!--
163: * <mailet match="All" class="ToRepository">
164: * <repositoryPath>file://var/mail/infected/</repositoryPath>
165: * </mailet>
166: * -->
167: *
168: * <mailet match="All" class="Null" />
169: * </processor>
170: * </CODE></PRE>
171: *
172: * @version 2.2.1
173: * @since 2.2.1
174: * @see <a href="http://www.clamav.net/">ClamAV Home Page</a>
175: * @see <a href="http://www.sosdg.org/clamav-win32/">ClamAV For Windows</a>
176: */
177: public class ClamAVScan extends GenericMailet {
178:
179: private static final int DEFAULT_PORT = 3310;
180:
181: private static final int DEFAULT_MAX_PINGS = 6;
182:
183: private static final int DEFAULT_PING_INTERVAL_MILLI = 30000;
184:
185: private static final int DEFAULT_STREAM_BUFFER_SIZE = 8192;
186:
187: private static final int DEFAULT_CONNECTION_TIMEOUT = 20000;
188:
189: private static final String STREAM_PORT_STRING = "PORT ";
190:
191: private static final String FOUND_STRING = "FOUND";
192:
193: private static final String MAIL_ATTRIBUTE_NAME = "org.apache.james.infected";
194:
195: private static final String HEADER_NAME = "X-MessageIsInfected";
196:
197: /**
198: * Holds value of property debug.
199: */
200: private boolean debug;
201:
202: /**
203: * Holds value of property host.
204: */
205: private String host;
206:
207: /**
208: * Holds value of property port.
209: */
210: private int port;
211:
212: /**
213: * Holds value of property maxPings.
214: */
215: private int maxPings;
216:
217: /**
218: * Holds value of property pingIntervalMilli.
219: */
220: private int pingIntervalMilli;
221:
222: /**
223: * Holds value of property streamBufferSize.
224: */
225: private int streamBufferSize;
226:
227: /**
228: * Holds value of property addresses.
229: */
230: private InetAddress[] addresses;
231:
232: /**
233: * Holds the index of the next address to connect to
234: */
235: private int nextAddressIndex;
236:
237: /**
238: * Return a string describing this mailet.
239: *
240: * @return a string describing this mailet
241: */
242: public String getMailetInfo() {
243: return "Antivirus Check using ClamAV (CLAMD)";
244: }
245:
246: /** Gets the expected init parameters. */
247: protected String[] getAllowedInitParameters() {
248: String[] allowedArray = {
249: // "static",
250: "debug", "host", "port", "maxPings",
251: "pingIntervalMilli", "streamBufferSize" };
252: return allowedArray;
253: }
254:
255: /**
256: * Initializer for property debug.
257: */
258: protected void initDebug() {
259: String debugParam = getInitParameter("debug");
260: setDebug((debugParam == null) ? false : new Boolean(debugParam)
261: .booleanValue());
262: }
263:
264: /**
265: * Getter for property debug.
266: * @return Value of property debug.
267: */
268: public boolean isDebug() {
269: return this .debug;
270: }
271:
272: /**
273: * Setter for property debug.
274: * @param debug New value of property debug.
275: */
276: public void setDebug(boolean debug) {
277: this .debug = debug;
278: }
279:
280: /**
281: * Initializer for property host.
282: * @throws UnknownHostException if unable to resolve the host name, or if invalid
283: */
284: protected void initHost() throws UnknownHostException {
285: setHost(getInitParameter("host"));
286: if (isDebug()) {
287: log("host: " + getHost());
288: }
289: }
290:
291: /**
292: * Getter for property host.
293: * @return Value of property host.
294: */
295: public String getHost() {
296:
297: return this .host;
298: }
299:
300: /**
301: * Setter for property host.
302: * Resolves also the host name into the corresponding IP addresses, issues
303: * a {@link #setAddresses} and resets the <CODE>nextAddressIndex</CODE>
304: * variable to <I>0</I> for dealing with <I>round-robin</I>.
305: * @param host New value of property host.
306: * @throws UnknownHostException if unable to resolve the host name, or if invalid
307: */
308: public void setHost(String host) throws UnknownHostException {
309:
310: this .host = host;
311:
312: setAddresses(InetAddress.getAllByName(host));
313:
314: nextAddressIndex = 0;
315: }
316:
317: /**
318: * Initializer for property port.
319: */
320: protected void initPort() {
321: String portParam = getInitParameter("port");
322: setPort((portParam == null) ? DEFAULT_PORT : Integer
323: .parseInt(portParam));
324: if (isDebug()) {
325: log("port: " + getPort());
326: }
327: }
328:
329: /**
330: * Getter for property port.
331: * @return Value of property port.
332: */
333: public int getPort() {
334:
335: return this .port;
336: }
337:
338: /**
339: * Setter for property port.
340: * @param port New value of property port.
341: */
342: public void setPort(int port) {
343:
344: this .port = port;
345: }
346:
347: /**
348: * Initializer for property maxPings.
349: */
350: protected void initMaxPings() {
351: String maxPingsParam = getInitParameter("maxPings");
352: setMaxPings((maxPingsParam == null) ? DEFAULT_MAX_PINGS
353: : Integer.parseInt(maxPingsParam));
354: if (isDebug()) {
355: log("maxPings: " + getMaxPings());
356: }
357: }
358:
359: /**
360: * Getter for property maxPings.
361: * @return Value of property maxPings.
362: */
363: public int getMaxPings() {
364:
365: return this .maxPings;
366: }
367:
368: /**
369: * Setter for property maxPings.
370: * @param maxPings New value of property maxPings.
371: */
372: public void setMaxPings(int maxPings) {
373:
374: this .maxPings = maxPings;
375: }
376:
377: /**
378: * Initializer for property pingIntervalMilli.
379: */
380: protected void initPingIntervalMilli() {
381: String pingIntervalMilliParam = getInitParameter("pingIntervalMilli");
382: setPingIntervalMilli((pingIntervalMilliParam == null) ? DEFAULT_PING_INTERVAL_MILLI
383: : Integer.parseInt(pingIntervalMilliParam));
384: if (isDebug()) {
385: log("pingIntervalMilli: " + getPingIntervalMilli());
386: }
387: }
388:
389: /**
390: * Getter for property pingIntervalMilli.
391: * @return Value of property pingIntervalMilli.
392: */
393: public int getPingIntervalMilli() {
394:
395: return this .pingIntervalMilli;
396: }
397:
398: /**
399: * Setter for property pingIntervalMilli.
400: * @param pingIntervalMilli New value of property pingIntervalMilli.
401: */
402: public void setPingIntervalMilli(int pingIntervalMilli) {
403:
404: this .pingIntervalMilli = pingIntervalMilli;
405: }
406:
407: /**
408: * Initializer for property streamBufferSize.
409: */
410: protected void initStreamBufferSize() {
411: String streamBufferSizeParam = getInitParameter("streamBufferSize");
412: setStreamBufferSize((streamBufferSizeParam == null) ? DEFAULT_STREAM_BUFFER_SIZE
413: : Integer.parseInt(streamBufferSizeParam));
414: if (isDebug()) {
415: log("streamBufferSize: " + getStreamBufferSize());
416: }
417: }
418:
419: /**
420: * Getter for property streamBufferSize.
421: * @return Value of property streamBufferSize.
422: */
423: public int getStreamBufferSize() {
424:
425: return this .streamBufferSize;
426: }
427:
428: /**
429: * Setter for property streamBufferSize.
430: * @param streamBufferSize New value of property streamBufferSize.
431: */
432: public void setStreamBufferSize(int streamBufferSize) {
433:
434: this .streamBufferSize = streamBufferSize;
435: }
436:
437: /**
438: * Indexed getter for property addresses.
439: * @param index Index of the property.
440: * @return Value of the property at <CODE>index</CODE>.
441: */
442: protected InetAddress getAddresses(int index) {
443:
444: return this .addresses[index];
445: }
446:
447: /**
448: * Getter for property addresses.
449: * @return Value of property addresses.
450: */
451: protected InetAddress[] getAddresses() {
452:
453: return this .addresses;
454: }
455:
456: /**
457: * Setter for property addresses.
458: * @param addresses New value of property addresses.
459: */
460: protected void setAddresses(InetAddress[] addresses) {
461:
462: this .addresses = addresses;
463: }
464:
465: /**
466: * Getter for property nextAddress.
467: *
468: * Gets the address of the next CLAMD server to connect to in this round, using round-robin.
469: * Increments the nextAddressIndex for the next round.
470: * @return Value of property address.
471: */
472: protected synchronized InetAddress getNextAddress() {
473:
474: InetAddress address = getAddresses(nextAddressIndex);
475:
476: nextAddressIndex++;
477: if (nextAddressIndex >= getAddressesCount()) {
478: nextAddressIndex = 0;
479: }
480:
481: return address;
482: }
483:
484: /**
485: * Getter for property addressesCount.
486: * @return Value of property addressesCount.
487: */
488: public int getAddressesCount() {
489: return getAddresses().length;
490: }
491:
492: /**
493: * Gets a Socket connected to CLAMD.
494: *
495: * Will loop though the round-robin address list until the first one accepts
496: * the connection.
497: * @return a socket connected to CLAMD
498: * @throws MessagingException if no CLAMD in the round-robin address list has accepted the connection
499: */
500: protected Socket getClamdSocket() throws MessagingException {
501:
502: InetAddress address = null;
503:
504: Set usedAddresses = new HashSet(getAddressesCount());
505: for (;;) {
506: // this do-while loop is needed because other threads could in the meantime
507: // calling getNextAddress(), and because of that the current thread may skip
508: // some working address
509: do {
510: if (usedAddresses.size() >= getAddressesCount()) {
511: String logText = "Unable to connect to CLAMD. All addresses failed.";
512: log(logText + " Giving up.");
513: throw new MessagingException(logText);
514: }
515: address = getNextAddress();
516: } while (!usedAddresses.add(address));
517: try {
518: // get the socket
519: return new Socket(address, getPort());
520: } catch (IOException ioe) {
521: log("Exception caught acquiring main socket to CLAMD on "
522: + address
523: + " on port "
524: + getPort()
525: + ": "
526: + ioe.getMessage());
527: address = getNextAddress();
528: // retry
529: continue;
530: }
531: }
532: }
533:
534: /**
535: * Mailet initialization routine.
536: */
537: public void init() throws MessagingException {
538:
539: // check that all init parameters have been declared in allowedInitParameters
540: checkInitParameters(getAllowedInitParameters());
541:
542: try {
543: initDebug();
544: if (isDebug()) {
545: log("Initializing");
546: }
547:
548: initHost();
549: initPort();
550: initMaxPings();
551: initPingIntervalMilli();
552: initStreamBufferSize();
553:
554: // If "maxPings is > ping the CLAMD server to check if it is up
555: if (getMaxPings() > 0) {
556: ping();
557: }
558:
559: } catch (Exception e) {
560: log("Exception thrown", e);
561: throw new MessagingException("Exception thrown", e);
562: }
563:
564: }
565:
566: /**
567: * Scans the mail.
568: *
569: * @param mail the mail to scan
570: * @throws MessagingException if a problem arises
571: */
572: public void service(Mail mail) throws MessagingException {
573:
574: // if already checked no action
575: if (mail.getAttribute(MAIL_ATTRIBUTE_NAME) != null) {
576: return;
577: }
578:
579: MimeMessage mimeMessage = mail.getMessage();
580:
581: if (mimeMessage == null) {
582: log("Null MimeMessage. Will send to ghost");
583: // write mail info to log
584: logMailInfo(mail);
585: mail.setState(Mail.GHOST);
586: return;
587: }
588:
589: // get the socket
590: Socket socket = getClamdSocket();
591: BufferedReader reader = null;
592: PrintWriter writer = null;
593: Socket streamSocket = null;
594: BufferedOutputStream bos = null;
595:
596: try {
597:
598: // prepare the reader and writer for the commands
599: reader = new BufferedReader(new InputStreamReader(socket
600: .getInputStream(), "ASCII"));
601: writer = new PrintWriter(new BufferedWriter(
602: new OutputStreamWriter(socket.getOutputStream())),
603: true);
604:
605: // write a request for a port to use for streaming out the data to scan
606: writer.println("STREAM");
607: writer.flush();
608:
609: // parse and get the "stream" port#
610: int streamPort = getStreamPortFromAnswer(reader.readLine());
611:
612: // get the "stream" socket and the related (buffered) output stream
613: streamSocket = new Socket(socket.getInetAddress(),
614: streamPort);
615: bos = new BufferedOutputStream(streamSocket
616: .getOutputStream(), getStreamBufferSize());
617:
618: // stream out the message to the scanner
619: mimeMessage.writeTo(bos);
620: bos.flush();
621: bos.close();
622: streamSocket.close();
623:
624: String answer = null;
625: boolean virusFound = false;
626: String logMessage = "";
627: for (;;) {
628: answer = reader.readLine();
629: if (answer != null) {
630: answer = answer.trim();
631:
632: // if a virus is found the answer will be '... FOUND'
633: if (answer.substring(
634: answer.length() - FOUND_STRING.length())
635: .equals(FOUND_STRING)) {
636: virusFound = true;
637: logMessage = answer + " (by CLAMD on "
638: + socket.getInetAddress() + ")";
639: log(logMessage);
640: }
641: } else {
642: break;
643: }
644: }
645:
646: reader.close();
647: writer.close();
648:
649: if (virusFound) {
650: String errorMessage = mail.getErrorMessage();
651: if (errorMessage == null) {
652: errorMessage = "";
653: } else {
654: errorMessage += "\r\n";
655: }
656: StringBuffer sb = new StringBuffer(errorMessage);
657: sb.append(logMessage + "\r\n");
658:
659: // write mail and message info to log
660: logMailInfo(mail);
661: logMessageInfo(mimeMessage);
662:
663: // mark the mail with a mail attribute to check later on by other matchers/mailets
664: mail.setAttribute(MAIL_ATTRIBUTE_NAME, "true");
665:
666: // sets the error message to be shown in any "notifyXxx" message
667: mail.setErrorMessage(sb.toString());
668:
669: // mark the message with a header string
670: mimeMessage.setHeader(HEADER_NAME, "true");
671:
672: } else {
673: if (isDebug()) {
674: log("OK (by CLAMD on " + socket.getInetAddress()
675: + ")");
676: }
677: mail.setAttribute(MAIL_ATTRIBUTE_NAME, "false");
678:
679: // mark the message with a header string
680: mimeMessage.setHeader(HEADER_NAME, "false");
681:
682: }
683:
684: try {
685: saveChanges(mimeMessage);
686: } catch (Exception ex) {
687: log(
688: "Exception caught while saving changes (header) to the MimeMessage. Ignoring ...",
689: ex);
690: }
691:
692: } catch (Exception ex) {
693: log("Exception caught calling CLAMD on "
694: + socket.getInetAddress() + ": " + ex.getMessage(),
695: ex);
696: throw new MessagingException("Exception caught", ex);
697: } finally {
698: try {
699: if (reader != null) {
700: reader.close();
701: }
702: } catch (Throwable t) {
703: }
704: try {
705: if (writer != null) {
706: writer.close();
707: }
708: } catch (Throwable t) {
709: }
710: try {
711: if (bos != null) {
712: bos.close();
713: }
714: } catch (Throwable t) {
715: }
716: try {
717: if (streamSocket != null) {
718: streamSocket.close();
719: }
720: } catch (Throwable t) {
721: }
722: try {
723: if (socket != null) {
724: socket.close();
725: }
726: } catch (Throwable t) {
727: }
728: }
729:
730: }
731:
732: /**
733: * Checks if there are unallowed init parameters specified in the configuration file
734: * against the String[] allowedInitParameters.
735: * @param allowedArray array of strings containing the allowed parameter names
736: * @throws MessagingException if an unknown parameter name is found
737: */
738: protected final void checkInitParameters(String[] allowedArray)
739: throws MessagingException {
740: // if null then no check is requested
741: if (allowedArray == null) {
742: return;
743: }
744:
745: Collection allowed = new HashSet();
746: Collection bad = new ArrayList();
747:
748: for (int i = 0; i < allowedArray.length; i++) {
749: allowed.add(allowedArray[i]);
750: }
751:
752: Iterator iterator = getInitParameterNames();
753: while (iterator.hasNext()) {
754: String parameter = (String) iterator.next();
755: if (!allowed.contains(parameter)) {
756: bad.add(parameter);
757: }
758: }
759:
760: if (bad.size() > 0) {
761: throw new MessagingException(
762: "Unexpected init parameters found: "
763: + arrayToString(bad.toArray()));
764: }
765: }
766:
767: /**
768: * Utility method for obtaining a string representation of an array of Objects.
769: */
770: private final String arrayToString(Object[] array) {
771: if (array == null) {
772: return "null";
773: }
774: StringBuffer sb = new StringBuffer(1024);
775: sb.append("[");
776: for (int i = 0; i < array.length; i++) {
777: if (i > 0) {
778: sb.append(",");
779: }
780: sb.append(array[i]);
781: }
782: sb.append("]");
783: return sb.toString();
784: }
785:
786: /**
787: * Tries to "ping" all the CLAMD daemons to
788: * check if they are up and accepting requests.
789: **/
790:
791: protected void ping() throws Exception {
792:
793: for (int i = 0; i < getAddressesCount(); i++) {
794: ping(getAddresses(i));
795: }
796: }
797:
798: /**
799: * Tries (and retries as specified up to 'getMaxPings()') to "ping" the specified CLAMD daemon to
800: * check if it is up and accepting requests.
801: * @param address the address to "ping"
802: */
803: protected void ping(InetAddress address) throws Exception {
804: Socket socket = null;
805:
806: int ping = 1;
807: for (;;) {
808: if (isDebug()) {
809: log("Trial #" + ping + "/" + getMaxPings()
810: + " - creating socket connected to " + address
811: + " on port " + getPort());
812: }
813: try {
814: socket = new Socket(address, getPort());
815: break;
816: } catch (ConnectException ce) {
817: log("Trial #" + ping + "/" + getMaxPings()
818: + " - exception caught: " + ce.toString()
819: + " while creating socket connected to "
820: + address + " on port " + getPort());
821: ping++;
822: if (ping <= getMaxPings()) {
823: log("Waiting " + getPingIntervalMilli()
824: + " milliseconds before retrying ...");
825: Thread.sleep(getPingIntervalMilli());
826: } else {
827: break;
828: }
829: }
830: }
831:
832: // if 'socket' is still null then 'maxPings' has been exceeded
833: if (socket == null) {
834: throw new ConnectException(
835: "maxPings exceeded: "
836: + getMaxPings()
837: + ". Giving up. The clamd daemon seems not to be running");
838: }
839:
840: try {
841: // get the reader and writer to ping and receive pong
842: BufferedReader reader = new BufferedReader(
843: new InputStreamReader(socket.getInputStream(),
844: "ASCII"));
845: PrintWriter writer = new PrintWriter(new BufferedWriter(
846: new OutputStreamWriter(socket.getOutputStream())),
847: true);
848:
849: log("Sending: \"PING\" to " + address + " ...");
850: writer.println("PING");
851: writer.flush();
852:
853: boolean pongReceived = false;
854: for (;;) {
855: String answer = reader.readLine();
856: if (answer != null) {
857: answer = answer.trim();
858: log("Received: \"" + answer + "\"");
859: answer = answer.trim();
860: if (answer.equals("PONG")) {
861: pongReceived = true;
862: }
863:
864: } else {
865: break;
866: }
867: }
868:
869: reader.close();
870: writer.close();
871:
872: if (!pongReceived) {
873: throw new ConnectException(
874: "Bad answer from \"PING\" probe: expecting \"PONG\"");
875: }
876: } finally {
877: socket.close();
878: }
879: }
880:
881: /**
882: * Parses the answer from a STREAM request and gets the port number.
883: *
884: * @param answer the answer from CLAMD containing the port number
885: * @return the port number for streaming out the data to scan
886: */
887: protected final int getStreamPortFromAnswer(String answer)
888: throws ConnectException {
889: int port = -1;
890: if (answer != null && answer.startsWith(STREAM_PORT_STRING)) {
891: try {
892: port = Integer.parseInt(answer
893: .substring(STREAM_PORT_STRING.length()));
894: } catch (NumberFormatException nfe) {
895:
896: }
897: }
898:
899: if (port <= 0) {
900: throw new ConnectException(
901: "\"PORT nn\" expected - unable to parse: " + "\""
902: + answer + "\"");
903: }
904:
905: return port;
906: }
907:
908: /**
909: * Saves changes resetting the original message id.
910: *
911: * @param message the message to save
912: */
913: protected final void saveChanges(MimeMessage message)
914: throws MessagingException {
915: String messageId = message.getMessageID();
916: message.saveChanges();
917: if (messageId != null) {
918: message.setHeader(RFC2822Headers.MESSAGE_ID, messageId);
919: }
920: }
921:
922: private void logMailInfo(Mail mail) {
923:
924: // writes the error message to the log
925: StringWriter sout = new StringWriter();
926: PrintWriter out = new PrintWriter(sout, true);
927:
928: out.print("Mail details:");
929: out.print(" MAIL FROM: " + mail.getSender());
930: Iterator rcptTo = mail.getRecipients().iterator();
931: out.print(", RCPT TO: " + rcptTo.next());
932: while (rcptTo.hasNext()) {
933: out.print(", " + rcptTo.next());
934: }
935:
936: log(sout.toString());
937: }
938:
939: private void logMessageInfo(MimeMessage mimeMessage) {
940:
941: // writes the error message to the log
942: StringWriter sout = new StringWriter();
943: PrintWriter out = new PrintWriter(sout, true);
944:
945: out.println("MimeMessage details:");
946:
947: try {
948: if (mimeMessage.getSubject() != null) {
949: out.println(" Subject: " + mimeMessage.getSubject());
950: }
951: if (mimeMessage.getSentDate() != null) {
952: out
953: .println(" Sent date: "
954: + mimeMessage.getSentDate());
955: }
956: String[] sender = null;
957: sender = mimeMessage.getHeader(RFC2822Headers.FROM);
958: if (sender != null) {
959: out.print(" From: ");
960: for (int i = 0; i < sender.length; i++) {
961: out.print(sender[i] + " ");
962: }
963: out.println();
964: }
965: String[] rcpts = null;
966: rcpts = mimeMessage.getHeader(RFC2822Headers.TO);
967: if (rcpts != null) {
968: out.print(" To: ");
969: for (int i = 0; i < rcpts.length; i++) {
970: out.print(rcpts[i] + " ");
971: }
972: out.println();
973: }
974: rcpts = mimeMessage.getHeader(RFC2822Headers.CC);
975: if (rcpts != null) {
976: out.print(" CC: ");
977: for (int i = 0; i < rcpts.length; i++) {
978: out.print(rcpts[i] + " ");
979: }
980: out.println();
981: }
982: out.print(" Size (in bytes): " + mimeMessage.getSize());
983: if (mimeMessage.getLineCount() >= 0) {
984: out.print(", Number of lines: "
985: + mimeMessage.getLineCount());
986: }
987: } catch (MessagingException me) {
988: log("Exception caught reporting message details", me);
989: }
990:
991: log(sout.toString());
992: }
993:
994: }
|