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.james.Constants;
25 import org.apache.james.core.MailImpl;
26 import org.apache.james.dsn.DSNStatus;
27 import org.apache.mailet.base.mail.MimeMultipartReport;
28 import org.apache.mailet.Mail;
29 import org.apache.mailet.MailAddress;
30 import org.apache.mailet.base.RFC2822Headers;
31 import org.apache.mailet.base.RFC822DateFormat;
32 import org.apache.oro.text.regex.MalformedPatternException;
33 import org.apache.oro.text.regex.MatchResult;
34 import org.apache.oro.text.regex.Pattern;
35 import org.apache.oro.text.regex.Perl5Compiler;
36 import org.apache.oro.text.regex.Perl5Matcher;
37
38 import javax.mail.MessagingException;
39 import javax.mail.SendFailedException;
40 import javax.mail.Session;
41 import javax.mail.internet.InternetAddress;
42 import javax.mail.internet.MimeBodyPart;
43 import javax.mail.internet.MimeMessage;
44
45 import java.io.PrintWriter;
46 import java.io.StringWriter;
47 import java.net.ConnectException;
48 import java.net.InetAddress;
49 import java.net.SocketException;
50 import java.net.UnknownHostException;
51 import java.util.Collection;
52 import java.util.Date;
53 import java.util.HashSet;
54 import java.util.Iterator;
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 public class DSNBounce extends AbstractNotify {
92
93
94 private static final RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
95
96
97 private static Pattern statusPattern;
98
99 private static Pattern diagPattern;
100
101 private static final String MACHINE_PATTERN = "[machine]";
102
103 private String messageString = null;
104
105
106
107
108
109 static {
110 Perl5Compiler compiler = new Perl5Compiler();
111 String status_pattern_string = ".*\\s*([245]\\.\\d{1,3}\\.\\d{1,3}).*\\s*";
112 String diag_pattern_string = "^\\d{3}\\s.*$";
113 try {
114 statusPattern = compiler.
115 compile(status_pattern_string, Perl5Compiler.READ_ONLY_MASK);
116 } catch(MalformedPatternException mpe) {
117
118 System.err.println ("Malformed pattern: " + status_pattern_string);
119 mpe.printStackTrace (System.err);
120 }
121 try {
122 diagPattern = compiler.
123 compile(diag_pattern_string, Perl5Compiler.READ_ONLY_MASK);
124 } catch(MalformedPatternException mpe) {
125
126 System.err.println ("Malformed pattern: " + diag_pattern_string);
127 }
128 }
129
130
131
132
133 public void init() throws MessagingException {
134 super.init();
135 messageString = getInitParameter("messageString","Hi. This is the James mail server at [machine].\nI'm afraid I wasn't able to deliver your message to the following addresses.\nThis is a permanent error; I've given up. Sorry it didn't work out. Below\nI include the list of recipients and the reason why I was unable to deliver\nyour message.\n");
136 }
137
138
139
140
141
142
143
144
145
146 public void service(Mail originalMail) throws MessagingException {
147
148
149
150 MailImpl newMail = new MailImpl(originalMail);
151 try {
152
153
154
155 newMail.setRemoteAddr(getMailetContext().getAttribute(Constants.HOSTADDRESS).toString());
156 newMail.setRemoteHost(getMailetContext().getAttribute(Constants.HOSTNAME).toString());
157
158 if (originalMail.getSender() == null) {
159 if (isDebug)
160 log("Processing a bounce request for a message with an empty reverse-path. No bounce will be sent.");
161 if(!getPassThrough(originalMail)) {
162 originalMail.setState(Mail.GHOST);
163 }
164 return;
165 }
166
167 MailAddress reversePath = originalMail.getSender();
168 if (isDebug)
169 log("Processing a bounce request for a message with a reverse path. The bounce will be sent to " + reversePath);
170
171 Collection newRecipients = new HashSet();
172 newRecipients.add(reversePath);
173 newMail.setRecipients(newRecipients);
174
175 if (isDebug) {
176 log("New mail - sender: " + newMail.getSender()
177 + ", recipients: " +
178 arrayToString(newMail.getRecipients().toArray())
179 + ", name: " + newMail.getName()
180 + ", remoteHost: " + newMail.getRemoteHost()
181 + ", remoteAddr: " + newMail.getRemoteAddr()
182 + ", state: " + newMail.getState()
183 + ", lastUpdated: " + newMail.getLastUpdated()
184 + ", errorMessage: " + newMail.getErrorMessage());
185 }
186
187
188 MimeMessage newMessage =
189 new MimeMessage(Session.getDefaultInstance(System.getProperties(),
190 null));
191
192 MimeMultipartReport multipart = new MimeMultipartReport ();
193 multipart.setReportType ("delivery-status");
194
195
196 MimeBodyPart part1 = createTextMsg(originalMail);
197 multipart.addBodyPart(part1);
198
199
200 MimeBodyPart part2 = createDSN(originalMail);
201 multipart.addBodyPart(part2);
202
203
204
205 if (getAttachmentType() != NONE) {
206 MimeBodyPart part3 = createAttachedOriginal(originalMail,getAttachmentType());
207 multipart.addBodyPart(part3);
208 }
209
210
211
212 newMessage.setContent(multipart);
213 newMessage.setHeader(RFC2822Headers.CONTENT_TYPE, multipart.getContentType());
214 newMail.setMessage(newMessage);
215
216
217 setRecipients(newMail, getRecipients(originalMail), originalMail);
218 setTo(newMail, getTo(originalMail), originalMail);
219 setSubjectPrefix(newMail, getSubjectPrefix(originalMail), originalMail);
220 if(newMail.getMessage().getHeader(RFC2822Headers.DATE) == null) {
221 newMail.getMessage().setHeader(RFC2822Headers.DATE,rfc822DateFormat.format(new Date()));
222 }
223 setReplyTo(newMail, getReplyTo(originalMail), originalMail);
224 setReversePath(newMail, getReversePath(originalMail), originalMail);
225 setSender(newMail, getSender(originalMail), originalMail);
226 setIsReply(newMail, isReply(originalMail), originalMail);
227
228 newMail.getMessage().saveChanges();
229 getMailetContext().sendMail(newMail);
230 } finally {
231 newMail.dispose();
232 }
233
234
235 if(!getPassThrough(originalMail)) {
236 originalMail.setState(Mail.GHOST);
237 }
238 }
239
240
241
242
243
244
245
246
247 protected MimeBodyPart createTextMsg(Mail originalMail)
248 throws MessagingException {
249 MimeBodyPart part1 = new MimeBodyPart();
250 StringWriter sout = new StringWriter();
251 PrintWriter out = new PrintWriter(sout, true);
252 String machine = "[unknown]";
253 try {
254 InetAddress me = InetAddress.getLocalHost();
255 machine = me.getHostName();
256 } catch(Exception e){
257 machine = "[address unknown]";
258 }
259
260 StringBuffer bounceBuffer =
261 new StringBuffer(128).append (messageString);
262 int m_idx_begin = messageString.indexOf(MACHINE_PATTERN);
263 if (m_idx_begin != -1) {
264 bounceBuffer.replace (m_idx_begin,
265 m_idx_begin+MACHINE_PATTERN.length(),
266 machine);
267 }
268 out.println(bounceBuffer.toString());
269 out.println("Failed recipient(s):");
270 for (Iterator i = originalMail.getRecipients().iterator(); i.hasNext(); ) {
271 out.println(i.next());
272 }
273 MessagingException ex = (MessagingException)originalMail.getAttribute("delivery-error");
274 out.println();
275 out.println("Error message:");
276 out.println(getErrorMsg(ex));
277 out.println();
278
279 part1.setText(sout.toString());
280 return part1;
281 }
282
283
284
285
286
287
288
289
290 protected MimeBodyPart createDSN(Mail originalMail) throws MessagingException {
291 MimeBodyPart dsn = new MimeBodyPart();
292 StringWriter sout = new StringWriter();
293 PrintWriter out = new PrintWriter(sout, true);
294 String nameType = null;
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312 nameType = "dns";
313 try {
314 String myAddress =
315 (String)getMailetContext().getAttribute(Constants.HELLO_NAME);
316
317
318
319 out.println("Reporting-MTA: "+nameType+"; "+myAddress);
320 } catch(Exception e){
321
322 log("WARNING: sending DSN without required Reporting-MTA Address");
323 }
324
325
326
327
328 out.println("Received-From-MTA: "+nameType+"; "+originalMail.getRemoteHost());
329
330
331
332
333
334
335
336 Iterator recipients = originalMail.getRecipients().iterator();
337 while (recipients.hasNext())
338 {
339 MailAddress rec = (MailAddress)recipients.next();
340 String addressType = "rfc822";
341
342
343 out.println();
344
345
346
347
348
349 out.println("Final-Recipient: "+addressType+"; "+rec.toString());
350
351
352
353
354 out.println("Action: failed");
355
356
357
358
359 MessagingException ex =
360 (MessagingException) originalMail.getAttribute("delivery-error");
361 out.println("Status: "+getStatus(ex));
362
363
364
365
366
367 String diagnosticType = null;
368
369
370
371
372 String diagnosticCode = getErrorMsg(ex);
373
374
375 Perl5Matcher diagMatcher = new Perl5Matcher();
376 boolean smtpDiagCodeAvailable =
377 diagMatcher.matches(diagnosticCode, diagPattern);
378 if (smtpDiagCodeAvailable){
379 diagnosticType = "smtp";
380 } else {
381 diagnosticType = "X-James";
382 }
383 out.println("Diagnostic-Code: "+diagnosticType+"; "+diagnosticCode);
384
385
386 out.println("Last-Attempt-Date: "+
387 rfc822DateFormat.format(originalMail.getLastUpdated()));
388
389
390
391
392
393
394 }
395
396
397
398
399
400
401
402
403 dsn.setContent(sout.toString(), "text/plain");
404 dsn.setHeader("Content-Type","message/delivery-status");
405 dsn.setDescription("Delivery Status Notification");
406 dsn.setFileName("status.dat");
407 return dsn;
408 }
409
410
411
412
413
414
415
416
417 protected MimeBodyPart createAttachedOriginal(Mail originalMail, int attachmentType)
418 throws MessagingException {
419 MimeBodyPart part = new MimeBodyPart();
420 MimeMessage originalMessage = originalMail.getMessage();
421
422 if (attachmentType == HEADS) {
423 part.setContent(getMessageHeaders(originalMessage), "text/plain");
424 part.setHeader("Content-Type","text/rfc822-headers");
425 } else {
426 part.setContent(originalMessage, "message/rfc822");
427 }
428
429 if ((originalMessage.getSubject() != null) &&
430 (originalMessage.getSubject().trim().length() > 0)) {
431 part.setFileName(originalMessage.getSubject().trim());
432 } else {
433 part.setFileName("No Subject");
434 }
435 part.setDisposition("Attachment");
436 return part;
437 }
438
439
440
441
442
443
444
445
446
447 protected String getStatus(MessagingException me) {
448 if (me.getNextException() == null) {
449 String mess = me.getMessage();
450 Perl5Matcher m = new Perl5Matcher();
451 StringBuffer sb = new StringBuffer();
452 if (m.matches(mess, statusPattern)) {
453 MatchResult res = m.getMatch();
454 sb.append(res.group(1));
455 return sb.toString();
456 }
457
458 if (mess.startsWith("There are no DNS entries for the hostname"))
459 return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYSTEM);
460
461
462
463 if (mess.equals("No mail server(s) available at this time."))
464 return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_NO_ANSWER);
465
466
467 return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
468 } else {
469 Exception ex1 = me.getNextException();
470 Perl5Matcher m = new Perl5Matcher ();
471 StringBuffer sb = new StringBuffer();
472 if (m.matches(ex1.getMessage(), statusPattern)) {
473 MatchResult res = m.getMatch();
474 sb.append(res.group(1));
475 return sb.toString();
476 } else if (ex1 instanceof SendFailedException) {
477
478 int smtpCode = 0;
479 try {
480 smtpCode = Integer.parseInt(ex1.getMessage().substring(0,3));
481 } catch(NumberFormatException e) {
482 }
483
484 switch (smtpCode) {
485
486
487 case 450: return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.MAILBOX_OTHER);
488
489 case 451: return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.SYSTEM_OTHER);
490
491 case 452: return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.SYSTEM_FULL);
492
493 case 500: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_SYNTAX);
494
495 case 501: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_ARG);
496
497 case 502: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_CMD);
498
499 case 503: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_CMD);
500
501 case 504: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_INVALID_ARG);
502
503 case 550: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.MAILBOX_OTHER);
504
505
506 case 551: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.SECURITY_AUTH);
507
508 case 552: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.MAILBOX_FULL);
509
510 case 553: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYNTAX);
511
512 case 554: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
513
514 case 571: return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.SECURITY_AUTH);
515
516 default:
517
518
519 if (ex1.getMessage().startsWith("4")) {
520 return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.DELIVERY_OTHER);
521 } else return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_OTHER);
522 }
523
524 } else if (ex1 instanceof UnknownHostException) {
525
526 return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYSTEM);
527 } else if (ex1 instanceof ConnectException) {
528
529 return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_CONNECTION);
530 } else if (ex1 instanceof SocketException) {
531
532 return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_CONNECTION);
533 } else {
534
535 return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
536 }
537 }
538 }
539
540
541
542
543
544
545
546 protected String getErrorMsg(MessagingException me) {
547 if (me.getNextException() == null) {
548 return me.getMessage().trim();
549 } else {
550 Exception ex1 = me.getNextException();
551 return ex1.getMessage().trim();
552 }
553 }
554
555
556 public String getMailetInfo() {
557 return "DSNBounce Mailet";
558 }
559
560
561
562
563
564 protected String[] getAllowedInitParameters() {
565 String[] allowedArray = {
566 "debug",
567 "passThrough",
568 "messageString",
569 "attachment",
570 "sender",
571 "prefix"
572 };
573 return allowedArray;
574 }
575
576
577
578
579 protected int getAttachmentType() throws MessagingException {
580 return getTypeCode(getInitParameter("attachment","message"));
581 }
582
583
584
585
586
587 protected Collection getRecipients() {
588 Collection newRecipients = new HashSet();
589 newRecipients.add(SpecialAddress.REVERSE_PATH);
590 return newRecipients;
591 }
592
593
594
595
596 protected InternetAddress[] getTo() {
597 InternetAddress[] apparentlyTo = new InternetAddress[1];
598 apparentlyTo[0] = SpecialAddress.REVERSE_PATH.toInternetAddress();
599 return apparentlyTo;
600 }
601
602
603
604
605 protected MailAddress getReversePath(Mail originalMail) {
606 return SpecialAddress.NULL;
607 }
608
609
610
611
612
613 }