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.avalon.cornerstone.services.store.Store;
25 import org.apache.avalon.framework.configuration.DefaultConfiguration;
26 import org.apache.avalon.framework.container.ContainerUtil;
27 import org.apache.avalon.framework.service.ServiceException;
28 import org.apache.avalon.framework.service.ServiceManager;
29 import org.apache.james.Constants;
30 import org.apache.james.api.dnsservice.DNSService;
31 import org.apache.james.api.dnsservice.TemporaryResolutionException;
32 import org.apache.james.services.SpoolRepository;
33 import org.apache.james.util.TimeConverter;
34 import org.apache.mailet.base.GenericMailet;
35 import org.apache.mailet.HostAddress;
36 import org.apache.mailet.Mail;
37 import org.apache.mailet.MailAddress;
38 import org.apache.mailet.MailetContext;
39 import org.apache.oro.text.regex.MalformedPatternException;
40 import org.apache.oro.text.regex.MatchResult;
41 import org.apache.oro.text.regex.Pattern;
42 import org.apache.oro.text.regex.Perl5Compiler;
43 import org.apache.oro.text.regex.Perl5Matcher;
44
45 import javax.mail.Address;
46 import javax.mail.MessagingException;
47 import javax.mail.SendFailedException;
48 import javax.mail.Session;
49 import javax.mail.Transport;
50 import javax.mail.internet.InternetAddress;
51 import javax.mail.internet.MimeMessage;
52 import javax.mail.internet.MimeMultipart;
53 import javax.mail.internet.MimePart;
54 import javax.mail.internet.ParseException;
55
56 import java.io.IOException;
57 import java.io.PrintWriter;
58 import java.io.StringWriter;
59 import java.lang.reflect.InvocationTargetException;
60 import java.lang.reflect.Method;
61 import java.net.ConnectException;
62 import java.net.InetAddress;
63 import java.net.SocketException;
64 import java.net.UnknownHostException;
65 import java.util.ArrayList;
66 import java.util.Arrays;
67 import java.util.Collection;
68 import java.util.Date;
69 import java.util.Hashtable;
70 import java.util.Iterator;
71 import java.util.Locale;
72 import java.util.Properties;
73 import java.util.StringTokenizer;
74 import java.util.Vector;
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 public class RemoteDelivery extends GenericMailet implements Runnable {
102
103
104 private static final long DEFAULT_DELAY_TIME = 21600000;
105
106
107 private static final String PATTERN_STRING = "\\s*([0-9]*\\s*[\\*])?\\s*([0-9]+)\\s*([a-z,A-Z]*)\\s*";
108
109
110 private static Pattern PATTERN = null;
111
112
113 private DNSService dnsServer;
114
115
116
117
118
119
120 static {
121 try {
122 Perl5Compiler compiler = new Perl5Compiler();
123 PATTERN = compiler.compile(PATTERN_STRING, Perl5Compiler.READ_ONLY_MASK);
124 } catch(MalformedPatternException mpe) {
125
126 mpe.printStackTrace (System.err);
127 }
128 }
129
130
131
132
133
134
135 private class MultipleDelayFilter implements SpoolRepository.AcceptFilter {
136
137
138
139 long youngest = 0;
140
141
142
143
144
145
146
147
148
149 public boolean accept (String key, String state, long lastUpdated, String errorMessage) {
150 if (state.equals(Mail.ERROR)) {
151
152 int retries = 0;
153
154 try {
155 retries = Integer.parseInt(errorMessage);
156 } catch (NumberFormatException e) {
157
158
159 }
160
161
162 if (retries == 0) return true;
163
164 long delay = getNextDelay (retries);
165 long timeToProcess = delay + lastUpdated;
166
167
168 if (System.currentTimeMillis() > timeToProcess) {
169
170 return true;
171 } else {
172
173 if (youngest == 0 || youngest > timeToProcess) {
174
175 youngest = timeToProcess;
176 }
177 return false;
178 }
179 } else {
180
181 return true;
182 }
183 }
184
185
186
187
188
189 public long getWaitTime () {
190 if (youngest == 0) {
191 return 0;
192 } else {
193 long duration = youngest - System.currentTimeMillis();
194 youngest = 0;
195 return duration <= 0 ? 1 : duration;
196 }
197 }
198 }
199
200
201 private boolean isDebug = false;
202
203
204 private SpoolRepository workRepository;
205
206
207 private long[] delayTimes;
208
209
210 private int maxRetries = 5;
211
212
213 private long smtpTimeout = 180000;
214
215
216 private boolean sendPartial = false;
217
218
219 private int connectionTimeout = 60000;
220
221
222 private int workersThreadCount = 1;
223
224
225 private Collection gatewayServer = null;
226
227
228 private String authUser = null;
229
230
231 private String authPass = null;
232
233
234
235
236
237 private String bindAddress = null;
238
239
240
241
242
243 private boolean isBindUsed = false;
244
245
246 private Collection workersThreads = new Vector();
247
248
249 private volatile boolean destroyed = false;
250
251
252 private String bounceProcessor = null;
253
254
255
256
257
258 private Perl5Matcher delayTimeMatcher;
259
260
261
262
263 private MultipleDelayFilter delayFilter = new MultipleDelayFilter();
264
265
266 private Properties defprops = new Properties();
267
268
269 private int dnsProblemRetry = 0;
270
271
272
273
274
275
276
277
278 public void init() throws MessagingException {
279
280 isDebug = (getInitParameter("debug") == null) ? false : new Boolean(getInitParameter("debug")).booleanValue();
281
282
283 ArrayList delayTimesList = new ArrayList();
284 try {
285 if (getInitParameter("delayTime") != null) {
286 delayTimeMatcher = new Perl5Matcher();
287 String delayTimesParm = getInitParameter("delayTime");
288
289
290 StringTokenizer st = new StringTokenizer (delayTimesParm,",");
291 while (st.hasMoreTokens()) {
292 String delayTime = st.nextToken();
293 delayTimesList.add (new Delay(delayTime));
294 }
295 } else {
296
297 delayTimesList.add (new Delay());
298 }
299 } catch (Exception e) {
300 log("Invalid delayTime setting: " + getInitParameter("delayTime"));
301 }
302
303 try {
304
305 if (getInitParameter("maxRetries") != null) {
306 maxRetries = Integer.parseInt(getInitParameter("maxRetries"));
307 }
308
309
310 int totalAttempts = calcTotalAttempts(delayTimesList);
311
312
313 if (totalAttempts > maxRetries) {
314 log("Total number of delayTime attempts exceeds maxRetries specified. "
315 + " Increasing maxRetries from "
316 + maxRetries
317 + " to "
318 + totalAttempts);
319 maxRetries = totalAttempts;
320 } else {
321 int extra = maxRetries - totalAttempts;
322 if (extra != 0) {
323 log("maxRetries is larger than total number of attempts specified. "
324 + "Increasing last delayTime with "
325 + extra
326 + " attempts ");
327
328
329 if (delayTimesList.size() != 0) {
330
331 Delay delay = (Delay) delayTimesList.get(delayTimesList
332 .size() - 1);
333
334
335 delay.setAttempts(delay.getAttempts() + extra);
336 log("Delay of " + delay.getDelayTime()
337 + " msecs is now attempted: " + delay.getAttempts()
338 + " times");
339 } else {
340 throw new MessagingException(
341 "No delaytimes, cannot continue");
342 }
343 }
344 }
345 delayTimes = expandDelays(delayTimesList);
346
347 } catch (Exception e) {
348 log("Invalid maxRetries setting: " + getInitParameter("maxRetries"));
349 }
350
351 ServiceManager compMgr = (ServiceManager) getMailetContext()
352 .getAttribute(Constants.AVALON_COMPONENT_MANAGER);
353
354
355
356
357
358 String workRepositoryPath = getInitParameter("outgoing");
359 if (workRepositoryPath == null) {
360 workRepositoryPath = "file:///../var/mail/outgoing";
361 }
362
363 try {
364
365 Store mailstore = (Store) compMgr.lookup(Store.ROLE);
366
367 DefaultConfiguration spoolConf = new DefaultConfiguration(
368 "repository", "generated:RemoteDelivery");
369 spoolConf.setAttribute("destinationURL", workRepositoryPath);
370 spoolConf.setAttribute("type", "SPOOL");
371 workRepository = (SpoolRepository) mailstore.select(spoolConf);
372 } catch (ServiceException cnfe) {
373 log("Failed to retrieve Store component:" + cnfe.getMessage());
374 throw new MessagingException("Failed to retrieve Store component",
375 cnfe);
376 }
377
378
379 workersThreadCount = Integer.parseInt(getInitParameter("deliveryThreads"));
380 for (int i = 0; i < workersThreadCount; i++) {
381 String threadName = "Remote delivery thread (" + i + ")";
382 Thread t = new Thread(this, threadName);
383 t.start();
384 workersThreads.add(t);
385 }
386
387 try {
388 if (getInitParameter("timeout") != null) {
389 smtpTimeout = Integer.parseInt(getInitParameter("timeout"));
390 }
391 } catch (Exception e) {
392 log("Invalid timeout setting: " + getInitParameter("timeout"));
393 }
394
395 try {
396 if (getInitParameter("connectiontimeout") != null) {
397 connectionTimeout = Integer.parseInt(getInitParameter("connectiontimeout"));
398 }
399 } catch (Exception e) {
400 log("Invalid timeout setting: " + getInitParameter("timeout"));
401 }
402
403 sendPartial = (getInitParameter("sendpartial") == null) ? false : new Boolean(getInitParameter("sendpartial")).booleanValue();
404
405 bounceProcessor = getInitParameter("bounceProcessor");
406
407 String gateway = getInitParameter("gateway");
408 String gatewayPort = getInitParameter("gatewayPort");
409
410 if (gateway != null) {
411 gatewayServer = new ArrayList();
412 StringTokenizer st = new StringTokenizer(gateway, ",") ;
413 while (st.hasMoreTokens()) {
414 String server = st.nextToken().trim() ;
415 if (server.indexOf(':') < 0 && gatewayPort != null) {
416 server += ":";
417 server += gatewayPort;
418 }
419
420 if (isDebug) log("Adding SMTP gateway: " + server) ;
421 gatewayServer.add(server);
422 }
423 authUser = getInitParameter("gatewayUsername");
424
425 if (authUser == null) {
426 authUser = getInitParameter("gatewayusername");
427 }
428 authPass = getInitParameter("gatewayPassword");
429 }
430
431
432 try {
433 dnsServer = (DNSService) compMgr.lookup(DNSService.ROLE);
434 } catch (ServiceException e1) {
435 log("Failed to retrieve DNSService" + e1.getMessage());
436 }
437
438 bindAddress = getInitParameter("bind");
439 isBindUsed = bindAddress != null;
440 try {
441 if (isBindUsed) RemoteDeliverySocketFactory.setBindAdress(bindAddress);
442 } catch (UnknownHostException e) {
443 log("Invalid bind setting (" + bindAddress + "): " + e.toString());
444 }
445
446
447 Iterator i = getInitParameterNames();
448 while (i.hasNext()) {
449 String name = (String) i.next();
450 if (name.startsWith("mail.")) {
451 defprops.put(name,getInitParameter(name));
452 }
453
454 }
455
456 String dnsRetry = getInitParameter("maxDnsProblemRetries");
457 if (dnsRetry != null && !dnsRetry.equals("")) {
458 dnsProblemRetry = Integer.parseInt(dnsRetry);
459 }
460 }
461
462
463
464
465
466
467
468
469 private int calcTotalAttempts (ArrayList delayList) {
470 int sum = 0;
471 Iterator i = delayList.iterator();
472 while (i.hasNext()) {
473 Delay delay = (Delay)i.next();
474 sum += delay.getAttempts();
475 }
476 return sum;
477 }
478
479
480
481
482
483
484
485
486
487
488
489
490 private long[] expandDelays (ArrayList list) {
491 long[] delays = new long [calcTotalAttempts(list)];
492 Iterator i = list.iterator();
493 int idx = 0;
494 while (i.hasNext()) {
495 Delay delay = (Delay)i.next();
496 for (int j=0; j<delay.getAttempts(); j++) {
497 delays[idx++]= delay.getDelayTime();
498 }
499 }
500 return delays;
501 }
502
503
504
505
506
507
508 private long getNextDelay (int retry_count) {
509 if (retry_count > delayTimes.length) {
510 return DEFAULT_DELAY_TIME;
511 }
512 return delayTimes[retry_count-1];
513 }
514
515
516
517
518
519 private class Delay {
520 private int attempts = 1;
521
522 private long delayTime = DEFAULT_DELAY_TIME;
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538 public Delay(String initString) throws MessagingException {
539
540 String unit = "msec";
541
542 if (delayTimeMatcher.matches(initString, PATTERN)) {
543 MatchResult res = delayTimeMatcher.getMatch();
544
545
546
547
548
549 if (res.group(1) != null && !res.group(1).equals("")) {
550
551 String attemptMatch = res.group(1);
552
553
554 attemptMatch = attemptMatch.substring(0,
555 attemptMatch.length() - 1).trim();
556 attempts = Integer.parseInt(attemptMatch);
557 }
558
559 delayTime = Long.parseLong(res.group(2));
560
561 if (!res.group(3).equals("")) {
562
563 unit = res.group(3).toLowerCase(Locale.US);
564 }
565 } else {
566 throw new MessagingException(initString + " does not match "
567 + PATTERN_STRING);
568 }
569
570
571 try {
572 delayTime = TimeConverter.getMilliSeconds(delayTime, unit);
573 } catch (NumberFormatException e) {
574 throw new MessagingException(e.getMessage());
575 }
576 }
577
578
579
580
581
582 public Delay() {
583 }
584
585
586
587
588 public long getDelayTime() {
589 return delayTime;
590 }
591
592
593
594
595 public int getAttempts() {
596 return attempts;
597 }
598
599
600
601
602 public void setAttempts(int value) {
603 attempts = value;
604 }
605
606
607
608
609 public String toString() {
610 String message = getAttempts() + "*" + getDelayTime() + "msecs";
611 return message;
612 }
613 }
614
615 public String getMailetInfo() {
616 return "RemoteDelivery Mailet";
617 }
618
619
620
621
622
623
624
625
626
627 public void service(Mail mail) throws MessagingException{
628
629 if (isDebug) {
630 log("Remotely delivering mail " + mail.getName());
631 }
632 Collection recipients = mail.getRecipients();
633
634 if (gatewayServer == null) {
635
636 Hashtable targets = new Hashtable();
637 for (Iterator i = recipients.iterator(); i.hasNext();) {
638 MailAddress target = (MailAddress)i.next();
639 String targetServer = target.getHost().toLowerCase(Locale.US);
640 Collection temp = (Collection)targets.get(targetServer);
641 if (temp == null) {
642 temp = new ArrayList();
643 targets.put(targetServer, temp);
644 }
645 temp.add(target);
646 }
647
648
649
650
651
652 String name = mail.getName();
653 for (Iterator i = targets.keySet().iterator(); i.hasNext(); ) {
654 String host = (String) i.next();
655 Collection rec = (Collection) targets.get(host);
656 if (isDebug) {
657 StringBuffer logMessageBuffer =
658 new StringBuffer(128)
659 .append("Sending mail to ")
660 .append(rec)
661 .append(" on host ")
662 .append(host);
663 log(logMessageBuffer.toString());
664 }
665 mail.setRecipients(rec);
666 StringBuffer nameBuffer =
667 new StringBuffer(128)
668 .append(name)
669 .append("-to-")
670 .append(host);
671 mail.setName(nameBuffer.toString());
672 workRepository.store(mail);
673
674 }
675 } else {
676
677 if (isDebug) {
678 StringBuffer logMessageBuffer =
679 new StringBuffer(128)
680 .append("Sending mail to ")
681 .append(mail.getRecipients())
682 .append(" via ")
683 .append(gatewayServer);
684 log(logMessageBuffer.toString());
685 }
686
687
688 workRepository.store(mail);
689 }
690 mail.setState(Mail.GHOST);
691 }
692
693
694
695
696
697 public synchronized void destroy() {
698
699 destroyed = true;
700
701
702 for (Iterator i = workersThreads.iterator(); i.hasNext(); ) {
703 Thread t = (Thread)i.next();
704 t.interrupt();
705 }
706 notifyAll();
707 }
708
709
710
711
712
713
714 public void run() {
715
716
717
718
719
720
721
722
723
724
725 long stop = System.currentTimeMillis() + 60000;
726 while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null)
727 && stop > System.currentTimeMillis()) {
728 try {
729 Thread.sleep(1000);
730 } catch (Exception ignored) {}
731 }
732
733
734 Properties props = new Properties();
735
736 props.put("mail.debug", "false");
737
738
739 props.put("mail.smtp.ehlo", "true");
740
741
742
743
744 props.setProperty("mail.smtp.allow8bitmime", "true");
745
746 props.put("mail.smtp.timeout", smtpTimeout + "");
747
748 props.put("mail.smtp.connectiontimeout", connectionTimeout + "");
749 props.put("mail.smtp.sendpartial",String.valueOf(sendPartial));
750
751
752 if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) {
753 props.put("mail.smtp.localhost", getMailetContext().getAttribute(Constants.HELLO_NAME));
754 }
755 else {
756 String defaultDomain = (String) getMailetContext().getAttribute(Constants.DEFAULT_DOMAIN);
757 if (defaultDomain != null) {
758 props.put("mail.smtp.localhost", defaultDomain);
759 }
760 }
761
762 if (isBindUsed) {
763
764
765 props.put("mail.smtp.socketFactory.class", RemoteDeliverySocketFactory.class.getClass());
766
767 props.put("mail.smtp.socketFactory.fallback", "false");
768 }
769
770 if (authUser != null) {
771 props.put("mail.smtp.auth","true");
772 }
773
774 props.putAll(defprops);
775
776 Session session = obtainSession(props);
777 try {
778 while (!Thread.interrupted() && !destroyed) {
779 try {
780
781
782
783
784
785
786
787 Mail mail = workRepository.accept(delayFilter);
788 String key = mail.getName();
789 try {
790 if (isDebug) {
791 String message = Thread.currentThread().getName()
792 + " will process mail " + key;
793 log(message);
794 }
795
796
797 if (deliver(mail, session)) {
798
799
800 ContainerUtil.dispose(mail);
801 workRepository.remove(key);
802 } else {
803
804
805 workRepository.store(mail);
806 ContainerUtil.dispose(mail);
807
808
809
810 workRepository.unlock(key);
811
812
813
814
815
816
817 }
818
819
820
821 mail = null;
822 } catch (Exception e) {
823
824
825
826
827
828
829 ContainerUtil.dispose(mail);
830 workRepository.remove(key);
831 throw e;
832 }
833 } catch (Throwable e) {
834 if (!destroyed) {
835 log("Exception caught in RemoteDelivery.run()", e);
836 }
837 }
838 }
839 } finally {
840
841 Thread.interrupted();
842 }
843 }
844
845
846
847
848
849
850
851
852
853
854
855
856
857 private boolean deliver(Mail mail, Session session) {
858 try {
859 if (isDebug) {
860 log("Attempting to deliver " + mail.getName());
861 }
862 MimeMessage message = mail.getMessage();
863
864
865 Collection recipients = mail.getRecipients();
866 InternetAddress addr[] = new InternetAddress[recipients.size()];
867 int j = 0;
868 for (Iterator i = recipients.iterator(); i.hasNext(); j++) {
869 MailAddress rcpt = (MailAddress)i.next();
870 addr[j] = rcpt.toInternetAddress();
871 }
872
873 if (addr.length <= 0) {
874 log("No recipients specified... not sure how this could have happened.");
875 return true;
876 }
877
878
879
880 Iterator targetServers = null;
881 if (gatewayServer == null) {
882 MailAddress rcpt = (MailAddress) recipients.iterator().next();
883 String host = rcpt.getHost();
884
885
886 try {
887 targetServers = dnsServer.getSMTPHostAddresses(host);
888 } catch (TemporaryResolutionException e) {
889 log("Temporary problem looking up mail server for host: " + host);
890 StringBuffer exceptionBuffer =
891 new StringBuffer(128)
892 .append("Temporary problem looking up mail server for host: ")
893 .append(host)
894 .append(". I cannot determine where to send this message.");
895
896
897 return failMessage(mail, new MessagingException(exceptionBuffer.toString()), false);
898 }
899 if (!targetServers.hasNext()) {
900 log("No mail server found for: " + host);
901 StringBuffer exceptionBuffer =
902 new StringBuffer(128)
903 .append("There are no DNS entries for the hostname ")
904 .append(host)
905 .append(". I cannot determine where to send this message.");
906
907 int retry = 0;
908 try {
909 retry = Integer.parseInt(mail.getErrorMessage());
910 } catch (NumberFormatException e) {
911
912 }
913 if (retry == 0 || retry > dnsProblemRetry) {
914
915 return failMessage(mail, new MessagingException(exceptionBuffer.toString()), true);
916 } else {
917 return failMessage(mail, new MessagingException(exceptionBuffer.toString()), false);
918 }
919 }
920 } else {
921 targetServers = getGatewaySMTPHostAddresses(gatewayServer);
922 }
923
924 MessagingException lastError = null;
925
926 while ( targetServers.hasNext()) {
927 try {
928 HostAddress outgoingMailServer = (HostAddress) targetServers.next();
929 StringBuffer logMessageBuffer =
930 new StringBuffer(256)
931 .append("Attempting delivery of ")
932 .append(mail.getName())
933 .append(" to host ")
934 .append(outgoingMailServer.getHostName())
935 .append(" at ")
936 .append(outgoingMailServer.getHost())
937 .append(" for addresses ")
938 .append(Arrays.asList(addr));
939 log(logMessageBuffer.toString());
940
941 Properties props = session.getProperties();
942 if (mail.getSender() == null) {
943 props.put("mail.smtp.from", "<>");
944 } else {
945 String sender = mail.getSender().toString();
946 props.put("mail.smtp.from", sender);
947 }
948
949
950
951
952
953
954
955 Transport transport = null;
956 try {
957 transport = session.getTransport(outgoingMailServer);
958 try {
959 if (authUser != null) {
960 transport.connect(outgoingMailServer.getHostName(), authUser, authPass);
961 } else {
962 transport.connect();
963 }
964 } catch (MessagingException me) {
965
966
967
968
969 log(me.getMessage());
970 continue;
971 }
972
973
974 if (transport.getClass().getName().endsWith(".SMTPTransport")) {
975 boolean supports8bitmime = false;
976 try {
977 Method supportsExtension = transport.getClass().getMethod("supportsExtension", new Class[] {String.class});
978 supports8bitmime = ((Boolean) supportsExtension.invoke(transport, new Object[] {"8BITMIME"})).booleanValue();
979 } catch (NoSuchMethodException nsme) {
980
981 } catch (IllegalAccessException iae) {
982 } catch (IllegalArgumentException iae) {
983 } catch (InvocationTargetException ite) {
984
985 }
986
987
988
989
990
991
992
993
994 if (!supports8bitmime) {
995 try {
996 convertTo7Bit(message);
997 } catch (IOException e) {
998
999
1000
1001 log("Error during the conversion to 7 bit.", e);
1002 }
1003 }
1004 } else {
1005
1006
1007
1008
1009 try {
1010 convertTo7Bit(message);
1011 } catch (IOException e) {
1012 log("Error during the conversion to 7 bit.", e);
1013 }
1014 }
1015 transport.sendMessage(message, addr);
1016 } finally {
1017 if (transport != null) {
1018 try
1019 {
1020
1021
1022
1023
1024 transport.close();
1025 }
1026 catch (MessagingException e)
1027 {
1028 log("Warning: could not close the SMTP transport after sending mail (" + mail.getName()
1029 + ") to " + outgoingMailServer.getHostName() + " at " + outgoingMailServer.getHost()
1030 + " for " + mail.getRecipients() + "; probably the server has already closed the "
1031 + "connection. Message is considered to be delivered. Exception: " + e.getMessage());
1032 }
1033 transport = null;
1034 }
1035 }
1036 logMessageBuffer =
1037 new StringBuffer(256)
1038 .append("Mail (")
1039 .append(mail.getName())
1040 .append(") sent successfully to ")
1041 .append(outgoingMailServer.getHostName())
1042 .append(" at ")
1043 .append(outgoingMailServer.getHost())
1044 .append(" for ")
1045 .append(mail.getRecipients());
1046 log(logMessageBuffer.toString());
1047 return true;
1048 } catch (SendFailedException sfe) {
1049 logSendFailedException(sfe);
1050
1051 if (sfe.getValidSentAddresses() != null) {
1052 Address[] validSent = sfe.getValidSentAddresses();
1053 if (validSent.length > 0) {
1054 StringBuffer logMessageBuffer =
1055 new StringBuffer(256)
1056 .append("Mail (")
1057 .append(mail.getName())
1058 .append(") sent successfully for ")
1059 .append(Arrays.asList(validSent));
1060 log(logMessageBuffer.toString());
1061 }
1062 }
1063
1064
1065 if (sfe.getClass().getName().endsWith(".SMTPSendFailedException")) {
1066 try {
1067 int returnCode = ((Integer) invokeGetter(sfe, "getReturnCode")).intValue();
1068
1069 if (returnCode >= 500 && returnCode <= 599) throw sfe;
1070 } catch (ClassCastException cce) {
1071 } catch (IllegalArgumentException iae) {
1072 }
1073 }
1074
1075 if (sfe.getValidUnsentAddresses() != null
1076 && sfe.getValidUnsentAddresses().length > 0) {
1077 if (isDebug) log("Send failed, " + sfe.getValidUnsentAddresses().length + " valid addresses remain, continuing with any other servers");
1078 lastError = sfe;
1079 continue;
1080 } else {
1081
1082 throw sfe;
1083 }
1084 } catch (MessagingException me) {
1085
1086 StringBuffer exceptionBuffer =
1087 new StringBuffer(256)
1088 .append("Exception delivering message (")
1089 .append(mail.getName())
1090 .append(") - ")
1091 .append(me.getMessage());
1092 log(exceptionBuffer.toString());
1093 if ((me.getNextException() != null) &&
1094 (me.getNextException() instanceof java.io.IOException)) {
1095
1096
1097
1098
1099 lastError = me;
1100 continue;
1101 }
1102
1103
1104
1105
1106
1107
1108 throw me;
1109 }
1110 }
1111
1112
1113
1114
1115
1116
1117 if (lastError != null) {
1118 throw lastError;
1119 }
1120 } catch (SendFailedException sfe) {
1121 logSendFailedException(sfe);
1122
1123 Collection recipients = mail.getRecipients();
1124
1125 boolean deleteMessage = false;
1126
1127
1128
1129
1130
1131
1132
1133
1134
1135
1136
1137
1138
1139
1140
1141
1142
1143
1144
1145
1146
1147
1148 try {
1149 if (sfe.getClass().getName().endsWith(".SMTPSendFailedException")) {
1150 int returnCode = ((Integer) invokeGetter(sfe, "getReturnCode")).intValue();
1151
1152 deleteMessage = (returnCode >= 500 && returnCode <= 599);
1153 } else {
1154
1155 MessagingException me = sfe;
1156 Exception ne;
1157 while ((ne = me.getNextException()) != null && ne instanceof MessagingException) {
1158 me = (MessagingException)ne;
1159 if (me.getClass().getName().endsWith(".SMTPAddressFailedException")) {
1160 int returnCode = ((Integer) invokeGetter(me, "getReturnCode")).intValue();
1161 deleteMessage = (returnCode >= 500 && returnCode <= 599);
1162 }
1163 }
1164 }
1165 } catch (IllegalStateException ise) {
1166
1167 } catch (ClassCastException cce) {
1168
1169 }
1170
1171
1172 if (isDebug) log("Recipients: " + recipients);
1173
1174 if (sfe.getInvalidAddresses() != null) {
1175 Address[] address = sfe.getInvalidAddresses();
1176 if (address.length > 0) {
1177 recipients.clear();
1178 for (int i = 0; i < address.length; i++) {
1179 try {
1180 recipients.add(new MailAddress(address[i].toString()));
1181 } catch (ParseException pe) {
1182
1183
1184
1185 log("Can't parse invalid address: " + pe.getMessage());
1186 }
1187 }
1188 if (isDebug) log("Invalid recipients: " + recipients);
1189 deleteMessage = failMessage(mail, sfe, true);
1190 }
1191 }
1192
1193 if (sfe.getValidUnsentAddresses() != null) {
1194 Address[] address = sfe.getValidUnsentAddresses();
1195 if (address.length > 0) {
1196 recipients.clear();
1197 for (int i = 0; i < address.length; i++) {
1198 try {
1199 recipients.add(new MailAddress(address[i].toString()));
1200 } catch (ParseException pe) {
1201
1202
1203
1204 log("Can't parse unsent address: " + pe.getMessage());
1205 }
1206 }
1207 if (isDebug) log("Unsent recipients: " + recipients);
1208 if (sfe.getClass().getName().endsWith(".SMTPSendFailedException")) {
1209 int returnCode = ((Integer) invokeGetter(sfe, "getReturnCode")).intValue();
1210 deleteMessage = failMessage(mail, sfe, returnCode >= 500 && returnCode <= 599);
1211 } else {
1212 deleteMessage = failMessage(mail, sfe, false);
1213 }
1214 }
1215 }
1216
1217 return deleteMessage;
1218 } catch (MessagingException ex) {
1219
1220
1221
1222
1223
1224
1225
1226
1227
1228
1229
1230 return failMessage(mail, ex, ('5' == ex.getMessage().charAt(0)));
1231 } catch (Exception ex) {
1232
1233 return failMessage(mail, ex, true);
1234 }
1235
1236
1237
1238
1239
1240
1241
1242 return failMessage(mail, new MessagingException("No mail server(s) available at this time."), false);
1243 }
1244
1245
1246
1247
1248
1249
1250
1251
1252
1253
1254
1255
1256
1257 private String exceptionToLogString(Exception e) {
1258 if (e.getClass().getName().endsWith(".SMTPSendFailedException")) {
1259 return "RemoteHost said: " + e.getMessage();
1260 } else if (e instanceof SendFailedException) {
1261 SendFailedException exception = (SendFailedException) e;
1262
1263
1264 if ( exception.getInvalidAddresses().length == 0 &&
1265 exception.getValidUnsentAddresses().length == 0) return null;
1266
1267 Exception ex;
1268 StringBuffer sb = new StringBuffer();
1269 boolean smtpExFound = false;
1270 sb.append("RemoteHost said:");
1271
1272
1273 if (e instanceof MessagingException) while((ex = ((MessagingException) e).getNextException()) != null && ex instanceof MessagingException) {
1274 e = ex;
1275 if (ex.getClass().getName().endsWith(".SMTPAddressFailedException")) {
1276 try {
1277 InternetAddress ia = (InternetAddress) invokeGetter(ex, "getAddress");
1278 sb.append(" ( " + ia + " - [" + ex.getMessage().replaceAll("\\n", "") + "] )");
1279 smtpExFound = true;
1280 } catch (IllegalStateException ise) {
1281
1282 } catch (ClassCastException cce) {
1283
1284 }
1285 }
1286 }
1287 if (!smtpExFound) {
1288 boolean invalidAddr = false;
1289 sb.append(" ( ");
1290
1291 if (exception.getInvalidAddresses().length > 0) {
1292 sb.append(exception.getInvalidAddresses());
1293 invalidAddr = true;
1294 }
1295 if (exception.getValidUnsentAddresses().length > 0) {
1296 if (invalidAddr == true) sb.append(" " );
1297 sb.append(exception.getValidUnsentAddresses());
1298 }
1299 sb.append(" - [");
1300 sb.append(exception.getMessage().replaceAll("\\n", ""));
1301 sb.append("] )");
1302 }
1303 return sb.toString();
1304 }
1305 return null;
1306 }
1307
1308
1309
1310
1311
1312
1313
1314
1315
1316 private Object invokeGetter(Object target, String getter) {
1317 try {
1318 Method getAddress = target.getClass().getMethod(getter, null);
1319 return getAddress.invoke(target, null);
1320 } catch (NoSuchMethodException nsme) {
1321
1322 } catch (IllegalAccessException iae) {
1323 } catch (IllegalArgumentException iae) {
1324 } catch (InvocationTargetException ite) {
1325
1326 }
1327 return new IllegalStateException("Exception invoking "+getter+" on a "+target.getClass()+" object");
1328 }
1329
1330
1331
1332
1333 private void logSendFailedException(SendFailedException sfe) {
1334 if (isDebug) {
1335 MessagingException me = sfe;
1336 if (me.getClass().getName().endsWith(".SMTPSendFailedException")) {
1337 try {
1338 String command = (String) invokeGetter(sfe, "getCommand");
1339 Integer returnCode = (Integer) invokeGetter(sfe, "getReturnCode");
1340 log("SMTP SEND FAILED:");
1341 log(sfe.toString());
1342 log(" Command: " + command);
1343 log(" RetCode: " + returnCode);
1344 log(" Response: " + sfe.getMessage());
1345 } catch (IllegalStateException ise) {
1346
1347 log("Send failed: " + me.toString());
1348 } catch (ClassCastException cce) {
1349
1350 log("Send failed: " + me.toString());
1351 }
1352 } else {
1353 log("Send failed: " + me.toString());
1354 }
1355 Exception ne;
1356 while ((ne = me.getNextException()) != null && ne instanceof MessagingException) {
1357 me = (MessagingException)ne;
1358 if (me.getClass().getName().endsWith(".SMTPAddressFailedException") || me.getClass().getName().endsWith(".SMTPAddressSucceededException")) {
1359 try {
1360 String action = me.getClass().getName().endsWith(".SMTPAddressFailedException") ? "FAILED" : "SUCCEEDED";
1361 InternetAddress address = (InternetAddress) invokeGetter(me, "getAddress");
1362 String command = (String) invokeGetter(me, "getCommand");
1363 Integer returnCode = (Integer) invokeGetter(me, "getReturnCode");
1364 log("ADDRESS "+action+":");
1365 log(me.toString());
1366 log(" Address: " + address);
1367 log(" Command: " + command);
1368 log(" RetCode: " + returnCode);
1369 log(" Response: " + me.getMessage());
1370 } catch (IllegalStateException ise) {
1371
1372 } catch (ClassCastException cce) {
1373
1374 }
1375 }
1376 }
1377 }
1378 }
1379
1380
1381
1382
1383
1384
1385 private void convertTo7Bit(MimePart part) throws MessagingException, IOException {
1386 if (part.isMimeType("multipart/*")) {
1387 MimeMultipart parts = (MimeMultipart) part.getContent();
1388 int count = parts.getCount();
1389 for (int i = 0; i < count; i++) {
1390 convertTo7Bit((MimePart)parts.getBodyPart(i));
1391 }
1392 } else if ("8bit".equals(part.getEncoding())) {
1393
1394
1395
1396
1397
1398
1399
1400 String contentTransferEncoding = part.isMimeType("text/*") ? "quoted-printable" : "base64";
1401 part.setContent(part.getContent(), part.getContentType());
1402 part.setHeader("Content-Transfer-Encoding", contentTransferEncoding);
1403 part.addHeader("X-MIME-Autoconverted", "from 8bit to "+contentTransferEncoding+" by "+getMailetContext().getServerInfo());
1404 }
1405 }
1406
1407
1408
1409
1410
1411
1412
1413
1414
1415 private boolean failMessage(Mail mail, Exception ex, boolean permanent) {
1416 StringWriter sout = new StringWriter();
1417 PrintWriter out = new PrintWriter(sout, true);
1418 if (permanent) {
1419 out.print("Permanent");
1420 } else {
1421 out.print("Temporary");
1422 }
1423
1424 String exceptionLog = exceptionToLogString(ex);
1425
1426 StringBuffer logBuffer =
1427 new StringBuffer(64)
1428 .append(" exception delivering mail (")
1429 .append(mail.getName());
1430
1431 if (exceptionLog != null) {
1432 logBuffer.append(". ");
1433 logBuffer.append(exceptionLog);
1434 }
1435
1436 logBuffer.append(": ");
1437 out.print(logBuffer.toString());
1438 if (isDebug) ex.printStackTrace(out);
1439 log(sout.toString());
1440 if (!permanent) {
1441 if (!mail.getState().equals(Mail.ERROR)) {
1442 mail.setState(Mail.ERROR);
1443 mail.setErrorMessage("0");
1444 mail.setLastUpdated(new Date());
1445 }
1446
1447 int retries = 0;
1448 try {
1449 retries = Integer.parseInt(mail.getErrorMessage());
1450 } catch (NumberFormatException e) {
1451
1452 }
1453
1454 if (retries < maxRetries) {
1455 logBuffer =
1456 new StringBuffer(128)
1457 .append("Storing message ")
1458 .append(mail.getName())
1459 .append(" into outgoing after ")
1460 .append(retries)
1461 .append(" retries");
1462 log(logBuffer.toString());
1463 ++retries;
1464 mail.setErrorMessage(retries + "");
1465 mail.setLastUpdated(new Date());
1466 return false;
1467 } else {
1468 logBuffer =
1469 new StringBuffer(128)
1470 .append("Bouncing message ")
1471 .append(mail.getName())
1472 .append(" after ")
1473 .append(retries)
1474 .append(" retries");
1475 log(logBuffer.toString());
1476 }
1477 }
1478
1479 if (mail.getSender() == null) {
1480 log("Null Sender: no bounce will be generated for " + mail.getName());
1481 return true;
1482 }
1483
1484 if (bounceProcessor != null) {
1485
1486
1487 mail.setAttribute("delivery-error", ex);
1488 mail.setState(bounceProcessor);
1489
1490 MailetContext mc = getMailetContext();
1491 try {
1492 mc.sendMail(mail);
1493 } catch (MessagingException e) {
1494
1495 log("Exception re-inserting failed mail: ", e);
1496 }
1497 } else {
1498
1499 bounce(mail, ex);
1500 }
1501 return true;
1502 }
1503
1504 private void bounce(Mail mail, Exception ex) {
1505 StringWriter sout = new StringWriter();
1506 PrintWriter out = new PrintWriter(sout, true);
1507 String machine = "[unknown]";
1508 try {
1509 machine = getMailetContext().getAttribute(Constants.HOSTNAME).toString();
1510
1511 } catch(Exception e){
1512 machine = "[address unknown]";
1513 }
1514 StringBuffer bounceBuffer =
1515 new StringBuffer(128)
1516 .append("Hi. This is the James mail server at ")
1517 .append(machine)
1518 .append(".");
1519 out.println(bounceBuffer.toString());
1520 out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
1521 out.println("This is a permanent error; I've given up. Sorry it didn't work out. Below");
1522 out.println("I include the list of recipients and the reason why I was unable to deliver");
1523 out.println("your message.");
1524 out.println();
1525 for (Iterator i = mail.getRecipients().iterator(); i.hasNext(); ) {
1526 out.println(i.next());
1527 }
1528 if (ex instanceof MessagingException) {
1529 if (((MessagingException) ex).getNextException() == null) {
1530 out.println(ex.getMessage().trim());
1531 } else {
1532 Exception ex1 = ((MessagingException) ex).getNextException();
1533 if (ex1 instanceof SendFailedException) {
1534 out.println("Remote mail server told me: " + ex1.getMessage().trim());
1535 } else if (ex1 instanceof UnknownHostException) {
1536 out.println("Unknown host: " + ex1.getMessage().trim());
1537 out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
1538 } else if (ex1 instanceof ConnectException) {
1539
1540 out.println(ex1.getMessage().trim());
1541 } else if (ex1 instanceof SocketException) {
1542 out.println("Socket exception: " + ex1.getMessage().trim());
1543 } else {
1544 out.println(ex1.getMessage().trim());
1545 }
1546 }
1547 }
1548 out.println();
1549
1550 log("Sending failure message " + mail.getName());
1551 try {
1552 getMailetContext().bounce(mail, sout.toString());
1553 } catch (MessagingException me) {
1554 log("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
1555 } catch (Exception e) {
1556 log("Encountered unexpected exception while bouncing message: " + e.getMessage());
1557 }
1558 }
1559
1560
1561
1562
1563
1564
1565
1566 protected Session obtainSession(Properties props) {
1567 return Session.getInstance(props);
1568 }
1569
1570
1571
1572
1573
1574
1575
1576
1577
1578
1579
1580
1581
1582
1583
1584
1585 private Iterator getGatewaySMTPHostAddresses(final Collection gatewayServers) {
1586 return new Iterator() {
1587 private Iterator gateways = gatewayServers.iterator();
1588 private Iterator addresses = null;
1589
1590 public boolean hasNext() {
1591
1592
1593
1594
1595
1596
1597
1598 if (!hasNextAddress() && gateways.hasNext()) {
1599 do {
1600 String server = (String) gateways.next();
1601 String port = "25";
1602
1603 int idx = server.indexOf(':');
1604 if ( idx > 0) {
1605 port = server.substring(idx+1);
1606 server = server.substring(0,idx);
1607 }
1608
1609 final String nextGateway = server;
1610 final String nextGatewayPort = port;
1611 try {
1612 final InetAddress[] ips = dnsServer.getAllByName(nextGateway);
1613 addresses = new Iterator() {
1614 private InetAddress[] ipAddresses = ips;
1615 int i = 0;
1616
1617 public boolean hasNext() {
1618 return i < ipAddresses.length;
1619 }
1620
1621 public Object next() {
1622 return new org.apache.mailet.HostAddress(nextGateway, "smtp://" + (ipAddresses[i++]).getHostAddress() + ":" + nextGatewayPort);
1623 }
1624
1625 public void remove() {
1626 throw new UnsupportedOperationException ("remove not supported by this iterator");
1627 }
1628 };
1629 }
1630 catch (java.net.UnknownHostException uhe) {
1631 log("Unknown gateway host: " + uhe.getMessage().trim());
1632 log("This could be a DNS server error or configuration error.");
1633 }
1634 } while (!hasNextAddress() && gateways.hasNext());
1635 }
1636
1637 return hasNextAddress();
1638 }
1639
1640 private boolean hasNextAddress() {
1641 return addresses != null && addresses.hasNext();
1642 }
1643
1644 public Object next() {
1645 return (addresses != null) ? addresses.next() : null;
1646 }
1647
1648 public void remove() {
1649 throw new UnsupportedOperationException ("remove not supported by this iterator");
1650 }
1651 };
1652 }
1653
1654
1655
1656
1657
1658 protected synchronized void setDNSServer(DNSService dnsServer) {
1659 this.dnsServer = dnsServer;
1660 }
1661 }