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