1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22 package org.apache.james.transport.mailets;
23
24 import org.apache.mailet.base.RFC2822Headers;
25 import org.apache.mailet.base.GenericMailet;
26 import org.apache.mailet.Mail;
27
28 import javax.mail.MessagingException;
29 import javax.mail.internet.MimeMessage;
30
31 import java.io.BufferedOutputStream;
32 import java.io.BufferedReader;
33 import java.io.BufferedWriter;
34 import java.io.IOException;
35 import java.io.InputStreamReader;
36 import java.io.OutputStream;
37 import java.io.OutputStreamWriter;
38 import java.io.PrintWriter;
39 import java.io.Reader;
40 import java.io.StringWriter;
41 import java.io.Writer;
42 import java.net.ConnectException;
43 import java.net.InetAddress;
44 import java.net.Socket;
45 import java.net.UnknownHostException;
46 import java.util.HashSet;
47 import java.util.Iterator;
48 import java.util.Set;
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183 public class ClamAVScan extends GenericMailet {
184
185 private static final int DEFAULT_PORT = 3310;
186
187 private static final int DEFAULT_MAX_PINGS = 6;
188
189 private static final int DEFAULT_PING_INTERVAL_MILLI = 30000;
190
191 private static final int DEFAULT_STREAM_BUFFER_SIZE = 8192;
192
193
194
195 private static final String STREAM_PORT_STRING = "PORT ";
196
197 private static final String FOUND_STRING = "FOUND";
198
199 private static final String MAIL_ATTRIBUTE_NAME = "org.apache.james.infected";
200
201 private static final String HEADER_NAME = "X-MessageIsInfected";
202
203
204
205
206 private boolean debug;
207
208
209
210
211 private String host;
212
213
214
215
216 private int port;
217
218
219
220
221 private int maxPings;
222
223
224
225
226 private int pingIntervalMilli;
227
228
229
230
231 private int streamBufferSize;
232
233
234
235
236 private InetAddress[] addresses;
237
238
239
240
241 private int nextAddressIndex;
242
243
244
245
246
247
248 public String getMailetInfo() {
249 return "Antivirus Check using ClamAV (CLAMD)";
250 }
251
252
253 protected String[] getAllowedInitParameters() {
254 String[] allowedArray = {
255
256 "debug",
257 "host",
258 "port",
259 "maxPings",
260 "pingIntervalMilli",
261 "streamBufferSize"
262 };
263 return allowedArray;
264 }
265
266
267
268
269 protected void initDebug() {
270 String debugParam = getInitParameter("debug");
271 setDebug((debugParam == null) ? false : new Boolean(debugParam).booleanValue());
272 }
273
274
275
276
277
278 public boolean isDebug() {
279 return this.debug;
280 }
281
282
283
284
285
286 public void setDebug(boolean debug) {
287 this.debug = debug;
288 }
289
290
291
292
293
294 protected void initHost() throws UnknownHostException {
295 setHost(getInitParameter("host"));
296 if (isDebug()) {
297 log("host: " + getHost());
298 }
299 }
300
301
302
303
304
305 public String getHost() {
306
307 return this.host;
308 }
309
310
311
312
313
314
315
316
317
318 public void setHost(String host) throws UnknownHostException {
319
320 this.host = host;
321
322 setAddresses(InetAddress.getAllByName(host));
323
324 nextAddressIndex = 0;
325 }
326
327
328
329
330 protected void initPort() {
331 String portParam = getInitParameter("port");
332 setPort((portParam == null) ? DEFAULT_PORT : Integer.parseInt(portParam));
333 if (isDebug()) {
334 log("port: " + getPort());
335 }
336 }
337
338
339
340
341
342 public int getPort() {
343
344 return this.port;
345 }
346
347
348
349
350
351 public void setPort(int port) {
352
353 this.port = port;
354 }
355
356
357
358
359 protected void initMaxPings() {
360 String maxPingsParam = getInitParameter("maxPings");
361 setMaxPings((maxPingsParam == null) ? DEFAULT_MAX_PINGS : Integer.parseInt(maxPingsParam));
362 if (isDebug()) {
363 log("maxPings: " + getMaxPings());
364 }
365 }
366
367
368
369
370
371 public int getMaxPings() {
372
373 return this.maxPings;
374 }
375
376
377
378
379
380 public void setMaxPings(int maxPings) {
381
382 this.maxPings = maxPings;
383 }
384
385
386
387
388 protected void initPingIntervalMilli() {
389 String pingIntervalMilliParam = getInitParameter("pingIntervalMilli");
390 setPingIntervalMilli((pingIntervalMilliParam == null) ? DEFAULT_PING_INTERVAL_MILLI : Integer.parseInt(pingIntervalMilliParam));
391 if (isDebug()) {
392 log("pingIntervalMilli: " + getPingIntervalMilli());
393 }
394 }
395
396
397
398
399
400 public int getPingIntervalMilli() {
401
402 return this.pingIntervalMilli;
403 }
404
405
406
407
408
409 public void setPingIntervalMilli(int pingIntervalMilli) {
410
411 this.pingIntervalMilli = pingIntervalMilli;
412 }
413
414
415
416
417 protected void initStreamBufferSize() {
418 String streamBufferSizeParam = getInitParameter("streamBufferSize");
419 setStreamBufferSize((streamBufferSizeParam == null) ? DEFAULT_STREAM_BUFFER_SIZE : Integer.parseInt(streamBufferSizeParam));
420 if (isDebug()) {
421 log("streamBufferSize: " + getStreamBufferSize());
422 }
423 }
424
425
426
427
428
429 public int getStreamBufferSize() {
430
431 return this.streamBufferSize;
432 }
433
434
435
436
437
438 public void setStreamBufferSize(int streamBufferSize) {
439
440 this.streamBufferSize = streamBufferSize;
441 }
442
443
444
445
446
447
448 protected InetAddress getAddresses(int index) {
449
450 return this.addresses[index];
451 }
452
453
454
455
456
457 protected InetAddress[] getAddresses() {
458
459 return this.addresses;
460 }
461
462
463
464
465
466 protected void setAddresses(InetAddress[] addresses) {
467
468 this.addresses = addresses;
469 }
470
471
472
473
474
475
476
477
478 protected synchronized InetAddress getNextAddress() {
479
480 InetAddress address = getAddresses(nextAddressIndex);
481
482 nextAddressIndex++;
483 if (nextAddressIndex >= getAddressesCount()) {
484 nextAddressIndex = 0;
485 }
486
487 return address;
488 }
489
490
491
492
493
494 public int getAddressesCount() {
495 return getAddresses().length;
496 }
497
498
499
500
501
502
503
504
505
506 protected Socket getClamdSocket() throws MessagingException {
507
508 InetAddress address = null;
509
510 Set usedAddresses = new HashSet(getAddressesCount());
511 for (;;) {
512
513
514
515 do {
516 if (usedAddresses.size() >= getAddressesCount()) {
517 String logText = "Unable to connect to CLAMD. All addresses failed.";
518 log(logText + " Giving up.");
519 throw new MessagingException(logText);
520 }
521 address = getNextAddress();
522 } while (!usedAddresses.add(address));
523 try {
524
525 return new Socket(address, getPort());
526 } catch (IOException ioe) {
527 log("Exception caught acquiring main socket to CLAMD on "
528 + address + " on port " + getPort() + ": " + ioe.getMessage());
529 address = getNextAddress();
530
531 continue;
532 }
533 }
534 }
535
536
537
538
539 public void init() throws MessagingException {
540
541
542 checkInitParameters(getAllowedInitParameters());
543
544 try {
545 initDebug();
546 if (isDebug()) {
547 log("Initializing");
548 }
549
550 initHost();
551 initPort();
552 initMaxPings();
553 initPingIntervalMilli();
554 initStreamBufferSize();
555
556
557 if (getMaxPings() > 0) {
558 ping();
559 }
560
561 } catch (Exception e) {
562 log("Exception thrown", e);
563 throw new MessagingException("Exception thrown", e);
564 }
565
566 }
567
568
569
570
571
572
573
574 public void service(Mail mail) throws MessagingException {
575
576
577 if (mail.getAttribute(MAIL_ATTRIBUTE_NAME) != null) {
578 return;
579 }
580
581 MimeMessage mimeMessage = mail.getMessage();
582
583 if (mimeMessage == null) {
584 log("Null MimeMessage. Will send to ghost");
585
586 logMailInfo(mail);
587 mail.setState(Mail.GHOST);
588 return;
589 }
590
591
592 Socket socket = getClamdSocket();
593 BufferedReader reader = null;
594 PrintWriter writer = null;
595 Socket streamSocket = null;
596 BufferedOutputStream bos = null;
597
598 try {
599
600
601 reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ASCII"));
602 writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
603
604
605 writer.println("STREAM");
606 writer.flush();
607
608
609 int streamPort = getStreamPortFromAnswer(reader.readLine());
610
611
612 streamSocket = new Socket(socket.getInetAddress(), streamPort);
613 bos = new BufferedOutputStream(streamSocket.getOutputStream(), getStreamBufferSize());
614
615
616 mimeMessage.writeTo(bos);
617 bos.flush();
618 bos.close();
619 streamSocket.close();
620
621 String answer = null;
622 boolean virusFound = false;
623 String logMessage = "";
624 for (;;) {
625 answer = reader.readLine();
626 if (answer != null) {
627 answer = answer.trim();
628
629
630 if (answer.substring(answer.length() - FOUND_STRING.length()).equals(FOUND_STRING)) {
631 virusFound = true;
632 logMessage = answer + " (by CLAMD on " + socket.getInetAddress() + ")";
633 log(logMessage);
634 }
635 } else {
636 break;
637 }
638 }
639
640 reader.close();
641 writer.close();
642
643 if (virusFound) {
644 String errorMessage = mail.getErrorMessage();
645 if (errorMessage == null) {
646 errorMessage = "";
647 } else {
648 errorMessage += "\r\n";
649 }
650 StringBuffer sb = new StringBuffer(errorMessage);
651 sb.append(logMessage + "\r\n");
652
653
654 logMailInfo(mail);
655 logMessageInfo(mimeMessage);
656
657
658 mail.setAttribute(MAIL_ATTRIBUTE_NAME, "true");
659
660
661 mail.setErrorMessage(sb.toString());
662
663
664 mimeMessage.setHeader(HEADER_NAME, "true");
665
666 } else {
667 if (isDebug()) {
668 log("OK (by CLAMD on " + socket.getInetAddress() + ")");
669 }
670 mail.setAttribute(MAIL_ATTRIBUTE_NAME, "false");
671
672
673 mimeMessage.setHeader(HEADER_NAME, "false");
674
675 }
676
677 try {
678 saveChanges(mimeMessage);
679 } catch (Exception ex) {
680 log("Exception caught while saving changes (header) to the MimeMessage. Ignoring ...", ex);
681 }
682
683 } catch (Exception ex) {
684 log("Exception caught calling CLAMD on " + socket.getInetAddress() + ": " + ex.getMessage(), ex);
685 throw new MessagingException("Exception caught", ex);
686 } finally {
687 shutdownReader(reader);
688 shutdownWriter(writer);
689 shutdownStream(bos);
690 shutdownSocket(streamSocket);
691 shutdownSocket(socket);
692 }
693
694 }
695
696
697
698
699
700
701 private static void shutdownStream( final OutputStream output )
702 {
703 if( null == output )
704 {
705 return;
706 }
707
708 try
709 {
710 output.close();
711 }
712 catch( final IOException ioe )
713 {
714 }
715 }
716
717
718
719
720
721
722 private static void shutdownSocket( final Socket socket ) {
723 if( null == socket ) {
724 return;
725 }
726
727 try {
728 socket.close();
729 } catch( final IOException ioe ){
730 }
731 }
732
733
734
735
736
737
738
739 private void shutdownWriter( final Writer output )
740 {
741 if( null == output )
742 {
743 return;
744 }
745
746 try
747 {
748 output.close();
749 }
750 catch( final IOException ioe )
751 {
752 }
753 }
754
755
756
757
758
759
760
761
762 private void shutdownReader( final Reader input )
763 {
764 if( null == input )
765 {
766 return;
767 }
768
769 try
770 {
771 input.close();
772 }
773 catch( final IOException ioe )
774 {
775 }
776 }
777
778
779
780
781
782
783 protected void ping() throws Exception {
784
785 for (int i = 0; i < getAddressesCount(); i++) {
786 ping(getAddresses(i));
787 }
788 }
789
790
791
792
793
794
795 protected void ping(InetAddress address) throws Exception {
796 Socket socket = null;
797
798 int ping = 1;
799 for (; ; ) {
800 if (isDebug()) {
801 log("Trial #" + ping + "/" + getMaxPings() + " - creating socket connected to " + address + " on port " + getPort());
802 }
803 try {
804 socket = new Socket(address, getPort());
805 break;
806 } catch (ConnectException ce) {
807 log("Trial #" + ping + "/" + getMaxPings() + " - exception caught: " + ce.toString() + " while creating socket connected to " + address + " on port " + getPort());
808 ping++;
809 if (ping <= getMaxPings()) {
810 log("Waiting " + getPingIntervalMilli() + " milliseconds before retrying ...");
811 Thread.sleep(getPingIntervalMilli());
812 } else {
813 break;
814 }
815 }
816 }
817
818
819 if (socket == null) {
820 throw new ConnectException("maxPings exceeded: " + getMaxPings() + ". Giving up. The clamd daemon seems not to be running");
821 }
822
823 try {
824
825 BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), "ASCII"));
826 PrintWriter writer = new PrintWriter(new BufferedWriter(new OutputStreamWriter(socket.getOutputStream())), true);
827
828 log("Sending: \"PING\" to " + address + " ...");
829 writer.println("PING");
830 writer.flush();
831
832 boolean pongReceived = false;
833 for (;;) {
834 String answer = reader.readLine();
835 if (answer != null) {
836 answer = answer.trim();
837 log("Received: \"" + answer + "\"");
838 answer = answer.trim();
839 if (answer.equals("PONG")) {
840 pongReceived = true;
841 }
842
843 } else {
844 break;
845 }
846 }
847
848 reader.close();
849 writer.close();
850
851 if (!pongReceived) {
852 throw new ConnectException("Bad answer from \"PING\" probe: expecting \"PONG\"");
853 }
854 } finally {
855 socket.close();
856 }
857 }
858
859
860
861
862
863
864
865 protected final int getStreamPortFromAnswer(String answer) throws ConnectException {
866 int port = -1;
867 if (answer != null && answer.startsWith(STREAM_PORT_STRING)) {
868 try {
869 port = Integer.parseInt(answer.substring(STREAM_PORT_STRING.length()));
870 } catch (NumberFormatException nfe) {
871
872 }
873 }
874
875 if (port <= 0) {
876 throw new ConnectException("\"PORT nn\" expected - unable to parse: " + "\"" + answer + "\"");
877 }
878
879 return port;
880 }
881
882
883
884
885
886
887 protected final void saveChanges(MimeMessage message) throws MessagingException {
888 String messageId = message.getMessageID();
889 message.saveChanges();
890 if (messageId != null) {
891 message.setHeader(RFC2822Headers.MESSAGE_ID, messageId);
892 }
893 }
894
895 private void logMailInfo(Mail mail) {
896
897
898 StringWriter sout = new StringWriter();
899 PrintWriter out = new PrintWriter(sout, true);
900
901 out.print("Mail details:");
902 out.print(" MAIL FROM: " + mail.getSender());
903 Iterator rcptTo = mail.getRecipients().iterator();
904 out.print(", RCPT TO: " + rcptTo.next());
905 while (rcptTo.hasNext()) {
906 out.print(", " + rcptTo.next());
907 }
908
909 log(sout.toString());
910 }
911
912 private void logMessageInfo(MimeMessage mimeMessage) {
913
914
915 StringWriter sout = new StringWriter();
916 PrintWriter out = new PrintWriter(sout, true);
917
918 out.println("MimeMessage details:");
919
920 try {
921 if (mimeMessage.getSubject() != null) {
922 out.println(" Subject: " + mimeMessage.getSubject());
923 }
924 if (mimeMessage.getSentDate() != null) {
925 out.println(" Sent date: " + mimeMessage.getSentDate());
926 }
927 String[] sender = null;
928 sender = mimeMessage.getHeader(RFC2822Headers.FROM);
929 if (sender != null) {
930 out.print(" From: ");
931 for (int i = 0; i < sender.length; i++) {
932 out.print(sender[i] + " ");
933 }
934 out.println();
935 }
936 String[] rcpts = null;
937 rcpts = mimeMessage.getHeader(RFC2822Headers.TO);
938 if (rcpts != null) {
939 out.print(" To: ");
940 for (int i = 0; i < rcpts.length; i++) {
941 out.print(rcpts[i] + " ");
942 }
943 out.println();
944 }
945 rcpts = mimeMessage.getHeader(RFC2822Headers.CC);
946 if (rcpts != null) {
947 out.print(" CC: ");
948 for (int i = 0; i < rcpts.length; i++) {
949 out.print(rcpts[i] + " ");
950 }
951 out.println();
952 }
953 out.print(" Size (in bytes): " + mimeMessage.getSize());
954 if (mimeMessage.getLineCount() >= 0) {
955 out.print(", Number of lines: " + mimeMessage.getLineCount());
956 }
957 } catch (MessagingException me) {
958 log("Exception caught reporting message details", me);
959 }
960
961 log(sout.toString());
962 }
963
964 }
965