PageRenderTime 57ms CodeModel.GetById 23ms RepoModel.GetById 0ms app.codeStats 1ms

/projects/james-2.2.0/src/java/org/apache/james/transport/mailets/DSNBounce.java

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 973 lines | 422 code | 162 blank | 389 comment | 59 complexity | feb0ab1e3f9a7c32c96eccf15ad0cb87 MD5 | raw file
  1. /***********************************************************************
  2. * Copyright (c) 2000-2004 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. package org.apache.james.transport.mailets;
  18. import java.io.PrintWriter;
  19. import java.io.StringWriter;
  20. import java.net.ConnectException;
  21. import java.net.InetAddress;
  22. import java.net.SocketException;
  23. import java.net.UnknownHostException;
  24. import java.util.Collection;
  25. import java.util.Date;
  26. import java.util.HashSet;
  27. import java.util.Iterator;
  28. import javax.mail.MessagingException;
  29. import javax.mail.SendFailedException;
  30. import javax.mail.Session;
  31. import javax.mail.internet.InternetAddress;
  32. import javax.mail.internet.MimeBodyPart;
  33. import javax.mail.internet.MimeMessage;
  34. import javax.mail.internet.MimeMultipart;
  35. import javax.activation.CommandMap;
  36. import javax.activation.MailcapCommandMap;
  37. import org.apache.james.core.MailImpl;
  38. import org.apache.mailet.Mail;
  39. import org.apache.mailet.MailAddress;
  40. import org.apache.james.util.RFC2822Headers;
  41. import org.apache.james.util.RFC822DateFormat;
  42. import org.apache.james.Constants;
  43. import org.apache.james.util.mail.MimeMultipartReport;
  44. import org.apache.oro.text.regex.MalformedPatternException;
  45. import org.apache.oro.text.regex.Pattern;
  46. import org.apache.oro.text.regex.Perl5Compiler;
  47. import org.apache.oro.text.regex.Perl5Matcher;
  48. import org.apache.oro.text.regex.MatchResult;
  49. /**
  50. *
  51. * <P>Generates a Delivery Status Notification (DSN)
  52. * Note that this is different than a mail-client's
  53. * reply, which would use the Reply-To or From header.</P>
  54. * <P>Bounced messages are attached in their entirety (headers and
  55. * content) and the resulting MIME part type is "message/rfc822".<BR>
  56. * The reverse-path and the Return-Path header of the response is set to "null" ("<>"),
  57. * meaning that no reply should be sent.</P>
  58. * <P>A sender of the notification message can optionally be specified.
  59. * If one is not specified, the postmaster's address will be used.<BR>
  60. * <P>Supports the <CODE>passThrough</CODE> init parameter (true if missing).</P>
  61. *
  62. * <P>Sample configuration:</P>
  63. * <PRE><CODE>
  64. * &lt;mailet match="All" class="DSNBounce">
  65. * &lt;sender&gt;<I>an address or postmaster or sender or unaltered,
  66. default=postmaster</I>&lt;/sender&gt;
  67. * &lt;prefix&gt;<I>optional subject prefix prepended to the original
  68. message</I>&lt;/prefix&gt;
  69. * &lt;attachment&gt;<I>message or none, default=message</I>&lt;/attachment&gt;
  70. * &lt;messageString&gt;<I>the message sent in the bounce, the first occurrence of the pattern [machine] is replaced with the name of the executing machine, default=Hi. This is the James mail server at [machine] ... </I>&lt;/messageString&gt;
  71. * &lt;passThrough&gt;<I>true or false, default=true</I>&lt;/passThrough&gt;
  72. * &lt;debug&gt;<I>true or false, default=false</I>&lt;/debug&gt;
  73. * &lt;/mailet&gt;
  74. * </CODE></PRE>
  75. *
  76. * @see org.apache.james.transport.mailets.AbstractNotify
  77. */
  78. public class DSNBounce extends AbstractNotify {
  79. /**
  80. * Constants and getters for RFC 3463 Enhanced Mail System Status Codes
  81. *
  82. * I suggest do extract this inner class for future use in the smtp-handler
  83. *
  84. */
  85. public static class DSNStatus {
  86. // status code classes
  87. /**
  88. * Success
  89. */
  90. public static final int SUCCESS = 2;
  91. /**
  92. * Persistent Transient Failure
  93. */
  94. public static final int TRANSIENT = 4;
  95. /**
  96. * Permanent Failure
  97. */
  98. public static final int PERMANENT = 5;
  99. // subjects and details
  100. /**
  101. * Other or Undefined Status
  102. */
  103. public static final int UNDEFINED = 0;
  104. /**
  105. * Other undefined status
  106. */
  107. public static final String UNDEFINED_STATUS = "0.0";
  108. /**
  109. * Addressing Status
  110. */
  111. public static final int ADDRESS = 1;
  112. /**
  113. * Other address status
  114. */
  115. public static final String ADDRESS_OTHER = "1.0";
  116. /**
  117. * Bad destination mailbox address
  118. */
  119. public static final String ADDRESS_MAILBOX = "1.1";
  120. /**
  121. * Bad destination system address
  122. */
  123. public static final String ADDRESS_SYSTEM = "1.2";
  124. /**
  125. * Bad destination mailbox address syntax
  126. */
  127. public static final String ADDRESS_SYNTAX = "1.3";
  128. /**
  129. * Destination mailbox address ambiguous
  130. */
  131. public static final String ADDRESS_AMBIGUOUS = "1.4";
  132. /**
  133. * Destination Address valid
  134. */
  135. public static final String ADDRESS_VALID = "1.5";
  136. /**
  137. * Destimation mailbox has moved, no forwarding address
  138. */
  139. public static final String ADDRESS_MOVED = "1.6";
  140. /**
  141. * Bad sender's mailbox address syntax
  142. */
  143. public static final String ADDRESS_SYNTAX_SENDER = "1.7";
  144. /**
  145. * Bad sender's system address
  146. */
  147. public static final String ADDRESS_SYSTEM_SENDER = "1.8";
  148. /**
  149. * Mailbox Status
  150. */
  151. public static final int MAILBOX = 2;
  152. /**
  153. * Other or Undefined Mailbox Status
  154. */
  155. public static final String MAILBOX_OTHER = "2.0";
  156. /**
  157. * Mailbox disabled, not accepting messages
  158. */
  159. public static final String MAILBOX_DISABLED = "2.1";
  160. /**
  161. * Mailbox full
  162. */
  163. public static final String MAILBOX_FULL = "2.2";
  164. /**
  165. * Message length exceeds administrative limit
  166. */
  167. public static final String MAILBOX_MSG_TOO_BIG = "2.3";
  168. /**
  169. * Mailing list expansion problem
  170. */
  171. public static final String MAILBOX_LIST_EXPANSION = "2.4";
  172. /**
  173. * Mail System Status
  174. */
  175. public static final int SYSTEM = 3;
  176. /**
  177. * Other or undefined mail system status
  178. */
  179. public static final String SYSTEM_OTHER = "3.0";
  180. /**
  181. * Mail system full
  182. */
  183. public static final String SYSTEM_FULL = "3.1";
  184. /**
  185. * System not accepting messages
  186. */
  187. public static final String SYSTEM_NOT_ACCEPTING = "3.2";
  188. /**
  189. * System not capable of selected features
  190. */
  191. public static final String SYSTEM_NOT_CAPABLE = "3.3";
  192. /**
  193. * Message too big for system
  194. */
  195. public static final String SYSTEM_MSG_TOO_BIG = "3.4";
  196. /**
  197. * System incorrectly configured
  198. */
  199. public static final String SYSTEM_CFG_ERROR = "3.5";
  200. /**
  201. * Network and Routing Status
  202. */
  203. public static final int NETWORK = 4;
  204. /**
  205. * Other or undefined network or routing status
  206. */
  207. public static final String NETWORK_OTHER = "4.0";
  208. /**
  209. * No answer form host
  210. */
  211. public static final String NETWORK_NO_ANSWER = "4.1";
  212. /**
  213. * Bad Connection
  214. */
  215. public static final String NETWORK_CONNECTION = "4.2";
  216. /**
  217. * Directory server failure
  218. */
  219. public static final String NETWORK_DIR_SERVER = "4.3";
  220. /**
  221. * Unable to route
  222. */
  223. public static final String NETWORK_ROUTE = "4.4";
  224. /**
  225. * Mail system congestion
  226. */
  227. public static final String NETWORK_CONGESTION = "4.5";
  228. /**
  229. * Routing loop detected
  230. */
  231. public static final String NETWORK_LOOP = "4.6";
  232. /**
  233. * Delivery time expired
  234. */
  235. public static final String NETWORK_EXPIRED = "4.7";
  236. /**
  237. * Mail Delivery Protocol Status
  238. */
  239. public static final int DELIVERY = 5;
  240. /**
  241. * Other or undefined (SMTP) protocol status
  242. */
  243. public static final String DELIVERY_OTHER = "5.0";
  244. /**
  245. * Invalid command
  246. */
  247. public static final String DELIVERY_INVALID_CMD = "5.1";
  248. /**
  249. * Syntax error
  250. */
  251. public static final String DELIVERY_SYNTAX = "5.2";
  252. /**
  253. * Too many recipients
  254. */
  255. public static final String DELIVERY_TOO_MANY_REC = "5.3";
  256. /**
  257. * Invalid command arguments
  258. */
  259. public static final String DELIVERY_INVALID_ARG = "5.4";
  260. /**
  261. * Wrong protocol version
  262. */
  263. public static final String DELIVERY_VERSION = "5.5";
  264. /**
  265. * Message Content or Media Status
  266. */
  267. public static final int CONTENT = 6;
  268. /**
  269. * Other or undefined media error
  270. */
  271. public static final String CONTENT_OTHER = "6.0";
  272. /**
  273. * Media not supported
  274. */
  275. public static final String CONTENT_UNSUPPORTED = "6.1";
  276. /**
  277. * Conversion required and prohibited
  278. */
  279. public static final String CONTENT_CONVERSION_NOT_ALLOWED = "6.2";
  280. /**
  281. * Conversion required, but not supported
  282. */
  283. public static final String CONTENT_CONVERSION_NOT_SUPPORTED = "6.3";
  284. /**
  285. * Conversion with loss performed
  286. */
  287. public static final String CONTENT_CONVERSION_LOSS = "6.4";
  288. /**
  289. * Conversion failed
  290. */
  291. public static final String CONTENT_CONVERSION_FAILED = "6.5";
  292. /**
  293. * Security or Policy Status
  294. */
  295. public static final int SECURITY = 7;
  296. /**
  297. * Other or undefined security status
  298. */
  299. public static final String SECURITY_OTHER = "7.0";
  300. /**
  301. * Delivery not authorized, message refused
  302. */
  303. public static final String SECURITY_AUTH = "7.1";
  304. /**
  305. * Mailing list expansion prohibited
  306. */
  307. public static final String SECURITY_LIST_EXP = "7.2";
  308. /**
  309. * Security conversion required, but not possible
  310. */
  311. public static final String SECURITY_CONVERSION = "7.3";
  312. /**
  313. * Security features not supported
  314. */
  315. public static final String SECURITY_UNSUPPORTED = "7.4";
  316. /**
  317. * Cryptographic failure
  318. */
  319. public static final String SECURITY_CRYPT_FAIL = "7.5";
  320. /**
  321. * Cryptographic algorithm not supported
  322. */
  323. public static final String SECURITY_CRYPT_ALGO = "7.6";
  324. /**
  325. * Message integrity failure
  326. */
  327. public static final String SECURITY_INTEGRITY = "7.7";
  328. // get methods
  329. public static String getStatus(int type, String detail) {
  330. return type + "." + detail;
  331. }
  332. public static String getStatus(int type, int subject, int detail) {
  333. return type + "." + subject + "." + detail;
  334. }
  335. }
  336. private static final RFC822DateFormat rfc822DateFormat = new RFC822DateFormat();
  337. // Used to generate new mail names
  338. private static final java.util.Random random = new java.util.Random();
  339. // regexp pattern for scaning status code from exception message
  340. private static Pattern statusPattern;
  341. private static Pattern diagPattern;
  342. private static final String MACHINE_PATTERN = "[machine]";
  343. private String messageString =
  344. "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";
  345. /*
  346. * Static initializer.<p>
  347. * Compiles patterns for processing exception messages.<p>
  348. */
  349. static {
  350. Perl5Compiler compiler = new Perl5Compiler();
  351. String status_pattern_string = ".*\\s*([245]\\.\\d{1,3}\\.\\d{1,3}).*\\s*";
  352. String diag_pattern_string = "^\\d{3}\\s.*$";
  353. try {
  354. statusPattern = compiler.
  355. compile(status_pattern_string, Perl5Compiler.READ_ONLY_MASK);
  356. } catch(MalformedPatternException mpe) {
  357. //this should not happen as the pattern string is hardcoded.
  358. System.err.println ("Malformed pattern: " + status_pattern_string);
  359. mpe.printStackTrace (System.err);
  360. }
  361. try {
  362. diagPattern = compiler.
  363. compile(diag_pattern_string, Perl5Compiler.READ_ONLY_MASK);
  364. } catch(MalformedPatternException mpe) {
  365. //this should not happen as the pattern string is hardcoded.
  366. System.err.println ("Malformed pattern: " + diag_pattern_string);
  367. }
  368. }
  369. /**
  370. * Initialize the mailet
  371. */
  372. public void init() throws MessagingException {
  373. super.init();
  374. if (getInitParameter("messageString") != null) {
  375. messageString = getInitParameter("messageString");
  376. }
  377. MailcapCommandMap mail_cap =
  378. (MailcapCommandMap) CommandMap.getDefaultCommandMap();
  379. mail_cap.addMailcap ("message/delivery-status;; x-java-content-handler=com.sun.mail.handlers.message_rfc822");
  380. CommandMap.setDefaultCommandMap (mail_cap);
  381. }
  382. /**
  383. * Service does the hard work and bounces the originalMail in the format specified by RFC3464.
  384. *
  385. * @param originalMail the mail to bounce
  386. * @throws MessagingException if a problem arises formulating the redirected mail
  387. *
  388. * @see org.apache.mailet.Mailet#service(org.apache.mailet.Mail)
  389. */
  390. public void service(Mail originalMail) throws MessagingException {
  391. // duplicates the Mail object, to be able to modify the new mail keeping the original untouched
  392. Mail newMail = ((MailImpl) originalMail).duplicate(newName((MailImpl) originalMail));
  393. // We don't need to use the original Remote Address and Host,
  394. // and doing so would likely cause a loop with spam detecting
  395. // matchers.
  396. try {
  397. ((MailImpl)newMail).setRemoteAddr(java.net.InetAddress.getLocalHost().getHostAddress());
  398. ((MailImpl)newMail).setRemoteHost(java.net.InetAddress.getLocalHost().getHostName());
  399. } catch (java.net.UnknownHostException _) {
  400. ((MailImpl) newMail).setRemoteAddr("127.0.0.1");
  401. ((MailImpl) newMail).setRemoteHost("localhost");
  402. }
  403. MailAddress returnAddress = getExistingReturnPath(originalMail);
  404. Collection newRecipients = new HashSet();
  405. if (returnAddress == SpecialAddress.NULL) {
  406. if (isDebug)
  407. log("Processing a bounce request for a message with an empty reverse-path. No bounce will be sent.");
  408. if(!getPassThrough(originalMail)) {
  409. originalMail.setState(Mail.GHOST);
  410. }
  411. return;
  412. } else if (returnAddress == null) {
  413. log("WARNING: Mail to be bounced does not contain a reverse-path.");
  414. } else {
  415. if (isDebug)
  416. log("Processing a bounce request for a message with a return path header. The bounce will be sent to " + returnAddress);
  417. }
  418. newRecipients.add(returnAddress);
  419. ((MailImpl)newMail).setRecipients(newRecipients);
  420. if (isDebug) {
  421. MailImpl newMailImpl = (MailImpl) newMail;
  422. log("New mail - sender: " + newMailImpl.getSender()
  423. + ", recipients: " +
  424. arrayToString(newMailImpl.getRecipients().toArray())
  425. + ", name: " + newMailImpl.getName()
  426. + ", remoteHost: " + newMailImpl.getRemoteHost()
  427. + ", remoteAddr: " + newMailImpl.getRemoteAddr()
  428. + ", state: " + newMailImpl.getState()
  429. + ", lastUpdated: " + newMailImpl.getLastUpdated()
  430. + ", errorMessage: " + newMailImpl.getErrorMessage());
  431. }
  432. // create the bounce message
  433. MimeMessage newMessage =
  434. new MimeMessage(Session.getDefaultInstance(System.getProperties(),
  435. null));
  436. MimeMultipartReport multipart = new MimeMultipartReport ();
  437. multipart.setReportType ("delivery-status");
  438. // part 1: descripive text message
  439. MimeBodyPart part1 = createTextMsg(originalMail);
  440. multipart.addBodyPart(part1);
  441. // part 2: DSN
  442. MimeBodyPart part2 = createDSN(originalMail);
  443. multipart.addBodyPart(part2);
  444. // part 3: original mail (optional)
  445. if (getAttachmentType() != NONE) {
  446. MimeBodyPart part3 = createAttachedOriginal(originalMail);
  447. multipart.addBodyPart(part3);
  448. }
  449. // stuffing all together
  450. newMessage.setContent(multipart);
  451. newMessage.setHeader(RFC2822Headers.CONTENT_TYPE, multipart.getContentType());
  452. newMail.setMessage(newMessage);
  453. //Set additional headers
  454. setRecipients(newMail, getRecipients(originalMail), originalMail);
  455. setTo(newMail, getTo(originalMail), originalMail);
  456. setSubjectPrefix(newMail, getSubjectPrefix(originalMail), originalMail);
  457. if(newMail.getMessage().getHeader(RFC2822Headers.DATE) == null) {
  458. newMail.getMessage().setHeader(RFC2822Headers.DATE,rfc822DateFormat.format(new Date()));
  459. }
  460. setReplyTo(newMail, getReplyTo(originalMail), originalMail);
  461. setReversePath(newMail, getReversePath(originalMail), originalMail);
  462. setSender(newMail, getSender(originalMail), originalMail);
  463. setIsReply(newMail, isReply(originalMail), originalMail);
  464. newMail.getMessage().saveChanges();
  465. getMailetContext().sendMail(newMail);
  466. // ghosting the original mail
  467. if(!getPassThrough(originalMail)) {
  468. originalMail.setState(Mail.GHOST);
  469. }
  470. }
  471. /**
  472. * Create a MimeBodyPart with a textual description for human readers.
  473. *
  474. * @param originalMail
  475. * @return MimeBodyPart
  476. * @throws MessagingException
  477. */
  478. protected MimeBodyPart createTextMsg(Mail originalMail)
  479. throws MessagingException {
  480. MimeBodyPart part1 = new MimeBodyPart();
  481. StringWriter sout = new StringWriter();
  482. PrintWriter out = new PrintWriter(sout, true);
  483. String machine = "[unknown]";
  484. try {
  485. InetAddress me = InetAddress.getLocalHost();
  486. machine = me.getHostName();
  487. } catch(Exception e){
  488. machine = "[address unknown]";
  489. }
  490. StringBuffer bounceBuffer =
  491. new StringBuffer(128).append (messageString);
  492. int m_idx_begin = messageString.indexOf(MACHINE_PATTERN);
  493. if (m_idx_begin != -1) {
  494. bounceBuffer.replace (m_idx_begin,
  495. m_idx_begin+MACHINE_PATTERN.length(),
  496. machine);
  497. }
  498. out.println(bounceBuffer.toString());
  499. out.println("Failed recipient(s):");
  500. for (Iterator i = originalMail.getRecipients().iterator(); i.hasNext(); ) {
  501. out.println(i.next());
  502. }
  503. MessagingException ex = (MessagingException)originalMail.getAttribute("delivery-error");
  504. out.println();
  505. out.println("Error message:");
  506. out.println(getErrorMsg(ex));
  507. out.println();
  508. part1.setText(sout.toString());
  509. return part1;
  510. }
  511. /**
  512. * creates the DSN-bodypart for automated processing
  513. *
  514. * @param originalMail
  515. * @return MimeBodyPart dsn-bodypart
  516. * @throws MessagingException
  517. */
  518. protected MimeBodyPart createDSN(Mail originalMail) throws MessagingException {
  519. MimeBodyPart dsn = new MimeBodyPart();
  520. MimeMessage dsnMessage =
  521. new MimeMessage(Session.getDefaultInstance(System.getProperties(), null));
  522. StringWriter sout = new StringWriter();
  523. PrintWriter out = new PrintWriter(sout, true);
  524. String errorMsg = null;
  525. String nameType = null;
  526. ////////////////////////
  527. // per message fields //
  528. ////////////////////////
  529. //optional: envelope-id
  530. // TODO: Envelope-Id
  531. // The Original-Envelope-Id is NOT the same as the Message-Id from the header.
  532. // The Message-Id identifies the content of the message, while the Original-Envelope-ID
  533. // identifies the transaction in which the message is sent. (see RFC3461)
  534. // so do NOT out.println("Original-Envelope-Id:"+originalMail.getMessage().getMessageID());
  535. //required: reporting MTA
  536. // this is always us, since we do not translate non-internet-mail
  537. // failure reports into DSNs
  538. nameType = "dns";
  539. try {
  540. String myAddress =
  541. (String)getMailetContext().getAttribute(Constants.HELLO_NAME);
  542. /*
  543. String myAddress = InetAddress.getLocalHost().getCanonicalHostName();
  544. */
  545. out.println("Reporting-MTA: "+nameType+"; "+myAddress);
  546. } catch(Exception e){
  547. // we should always know our address, so we shouldn't get here
  548. log("WARNING: sending DSN without required Reporting-MTA Address");
  549. }
  550. //only for gateways to non-internet mail systems: dsn-gateway
  551. //optional: received from
  552. out.println("Received-From-MTA: "+nameType+"; "+originalMail.getRemoteHost());
  553. //optional: Arrival-Date
  554. //////////////////////////
  555. // per recipient fields //
  556. //////////////////////////
  557. Iterator recipients = originalMail.getRecipients().iterator();
  558. while (recipients.hasNext())
  559. {
  560. MailAddress rec = (MailAddress)recipients.next();
  561. String addressType = "rfc822";
  562. //required: blank line
  563. out.println();
  564. //optional: original recipient (see RFC3461)
  565. //out.println("Original-Recipient: "+addressType+"; "+ ??? );
  566. //required: final recipient
  567. out.println("Final-Recipient: "+addressType+"; "+rec.toString());
  568. //required: action
  569. // alowed values: failed, delayed, delivered, relayed, expanded
  570. // TODO: until now, we do error-bounces only
  571. out.println("Action: failed");
  572. //required: status
  573. // get Exception for getting status information
  574. // TODO: it would be nice if the SMTP-handler would set a status attribute we can use here
  575. MessagingException ex =
  576. (MessagingException) originalMail.getAttribute("delivery-error");
  577. out.println("Status: "+getStatus(ex));
  578. //optional: remote MTA
  579. //to which MTA were we talking while the Error occured?
  580. //optional: diagnostic-code
  581. String diagnosticType = null;
  582. // this typically is the return value received during smtp
  583. // (or other transport) communication
  584. // and should be stored as attribute by the smtp handler
  585. // but until now we only have error-messages.
  586. String diagnosticCode = getErrorMsg(ex);
  587. // Sometimes this is the smtp diagnostic code,
  588. // but James often gives us other messages
  589. Perl5Matcher diagMatcher = new Perl5Matcher();
  590. boolean smtpDiagCodeAvailable =
  591. diagMatcher.matches(diagnosticCode, diagPattern);
  592. if (smtpDiagCodeAvailable){
  593. diagnosticType = "smtp";
  594. } else {
  595. diagnosticType = "X-James";
  596. }
  597. out.println("Diagnostic-Code: "+diagnosticType+"; "+diagnosticCode);
  598. //optional: last attempt
  599. out.println("Last-Attempt-Date: "+
  600. rfc822DateFormat.format(((MailImpl)originalMail).getLastUpdated()));
  601. //optional: retry until
  602. //only for 'delayed' reports .. but we don't report this (at least until now)
  603. //optional: extension fields
  604. }
  605. // setting content
  606. dsnMessage.setText(sout.toString());
  607. dsnMessage.saveChanges();
  608. //dsn.setContent(sout.toString(), "text/plain");
  609. dsn.setContent(dsnMessage, "message/delivery-status");
  610. dsn.setDescription("Delivery Status Notification");
  611. dsn.setFileName("status.dat");
  612. return dsn;
  613. }
  614. /**
  615. * Create a MimeBodyPart with the original Mail as Attachment
  616. *
  617. * @param originalMail
  618. * @return MimeBodyPart
  619. * @throws MessagingException
  620. */
  621. protected MimeBodyPart createAttachedOriginal(Mail originalMail)
  622. throws MessagingException {
  623. MimeBodyPart part = new MimeBodyPart();
  624. MimeMessage originalMessage = originalMail.getMessage();
  625. part.setContent(originalMessage, "message/rfc822");
  626. if ((originalMessage.getSubject() != null) &&
  627. (originalMessage.getSubject().trim().length() > 0)) {
  628. part.setFileName(originalMessage.getSubject().trim());
  629. } else {
  630. part.setFileName("No Subject");
  631. }
  632. part.setDisposition("Attachment");
  633. return part;
  634. }
  635. /**
  636. * Guessing status code by the exception provided.
  637. * This method should use the status attribute when the
  638. * SMTP-handler somewhen provides it
  639. *
  640. * @param MessagingException
  641. * @return status code
  642. */
  643. protected String getStatus(MessagingException me) {
  644. if (me.getNextException() == null) {
  645. String mess = me.getMessage();
  646. Perl5Matcher m = new Perl5Matcher();
  647. StringBuffer sb = new StringBuffer();
  648. if (m.matches(mess, statusPattern)) {
  649. MatchResult res = m.getMatch();
  650. sb.append(res.group(1));
  651. return sb.toString();
  652. }
  653. // bad destination system adress
  654. if (mess.startsWith("There are no DNS entries for the hostname"))
  655. return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYSTEM);
  656. // no answer from host (4.4.1) or
  657. // system not accepting network messages (4.3.2), lets guess ...
  658. if (mess.equals("No mail server(s) available at this time."))
  659. return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_NO_ANSWER);
  660. // other/unknown error
  661. return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
  662. } else {
  663. String retVal = null;
  664. Exception ex1 = me.getNextException();
  665. Perl5Matcher m = new Perl5Matcher ();
  666. StringBuffer sb = new StringBuffer();
  667. if (m.matches(ex1.getMessage(), statusPattern)) {
  668. MatchResult res = m.getMatch();
  669. sb.append(res.group(1));
  670. return sb.toString();
  671. } else if (ex1 instanceof SendFailedException) {
  672. // other/undefined protocol status
  673. // if we get an smtp returncode starting with 4
  674. // it is an persistent transient error, else permanent
  675. if (ex1.getMessage().startsWith("4")) {
  676. return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.DELIVERY_OTHER);
  677. } else return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.DELIVERY_OTHER);
  678. } else if (ex1 instanceof UnknownHostException) {
  679. // bad destination system address
  680. return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.ADDRESS_SYSTEM);
  681. } else if (ex1 instanceof ConnectException) {
  682. // bad connection
  683. return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_CONNECTION);
  684. } else if (ex1 instanceof SocketException) {
  685. // bad connection
  686. return DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_CONNECTION);
  687. } else {
  688. // other/undefined/unknown error
  689. return DSNStatus.getStatus(DSNStatus.PERMANENT, DSNStatus.UNDEFINED_STATUS);
  690. }
  691. }
  692. }
  693. /**
  694. * Utility method for getting the error message from the (nested) exception.
  695. * @param MessagingException
  696. * @return error message
  697. */
  698. protected String getErrorMsg(MessagingException me) {
  699. if (me.getNextException() == null) {
  700. return me.getMessage().trim();
  701. } else {
  702. Exception ex1 = me.getNextException();
  703. return ex1.getMessage().trim();
  704. }
  705. }
  706. /**
  707. * Utility method for obtaining a string representation of an array of Objects.
  708. */
  709. private String arrayToString(Object[] array) {
  710. if (array == null) {
  711. return "null";
  712. }
  713. StringBuffer sb = new StringBuffer(1024);
  714. sb.append("[");
  715. for (int i = 0; i < array.length; i++) {
  716. if (i > 0) {
  717. sb.append(",");
  718. }
  719. sb.append(array[i]);
  720. }
  721. sb.append("]");
  722. return sb.toString();
  723. }
  724. /**
  725. * Create a unique new primary key name.
  726. *
  727. * @param mail the mail to use as the basis for the new mail name
  728. * @return a new name
  729. */
  730. protected String newName(MailImpl mail) throws MessagingException {
  731. String oldName = mail.getName();
  732. // Checking if the original mail name is too long, perhaps because of a
  733. // loop caused by a configuration error.
  734. // it could cause a "null pointer exception" in AvalonMailRepository much
  735. // harder to understand.
  736. if (oldName.length() > 76) {
  737. int count = 0;
  738. int index = 0;
  739. while ((index = oldName.indexOf('!', index + 1)) >= 0) {
  740. count++;
  741. }
  742. // It looks like a configuration loop. It's better to stop.
  743. if (count > 7) {
  744. throw new MessagingException("Unable to create a new message name: too long."
  745. + " Possible loop in config.xml.");
  746. }
  747. else {
  748. oldName = oldName.substring(0, 76);
  749. }
  750. }
  751. StringBuffer nameBuffer =
  752. new StringBuffer(64)
  753. .append(oldName)
  754. .append("-!")
  755. .append(random.nextInt(1048576));
  756. return nameBuffer.toString();
  757. }
  758. public String getMailetInfo() {
  759. return "DSNBounce Mailet";
  760. }
  761. /* ******************************************************************** */
  762. /* ****************** Begin of getX and setX methods ****************** */
  763. /* ******************************************************************** */
  764. /** Gets the expected init parameters. */
  765. protected String[] getAllowedInitParameters() {
  766. String[] allowedArray = {
  767. "debug",
  768. "passThrough",
  769. "messageString",
  770. "attachment",
  771. "sender",
  772. "prefix"
  773. };
  774. return allowedArray;
  775. }
  776. /**
  777. * @return the <CODE>attachment</CODE> init parameter, or <CODE>MESSAGE</CODE> if missing
  778. */
  779. protected int getAttachmentType() throws MessagingException {
  780. if(getInitParameter("attachment") == null) {
  781. return MESSAGE;
  782. } else {
  783. return getTypeCode(getInitParameter("attachment"));
  784. }
  785. }
  786. /**
  787. * @return <CODE>SpecialAddress.REVERSE_PATH</CODE>
  788. */
  789. protected Collection getRecipients() {
  790. Collection newRecipients = new HashSet();
  791. newRecipients.add(SpecialAddress.REVERSE_PATH);
  792. return newRecipients;
  793. }
  794. /**
  795. * @return <CODE>SpecialAddress.REVERSE_PATH</CODE>
  796. */
  797. protected InternetAddress[] getTo() {
  798. InternetAddress[] apparentlyTo = new InternetAddress[1];
  799. apparentlyTo[0] = SpecialAddress.REVERSE_PATH.toInternetAddress();
  800. return apparentlyTo;
  801. }
  802. /**
  803. * @return <CODE>SpecialAddress.NULL</CODE> (the meaning of bounce)
  804. */
  805. protected MailAddress getReversePath(Mail originalMail) {
  806. return SpecialAddress.NULL;
  807. }
  808. /* ******************************************************************** */
  809. /* ******************* End of getX and setX methods ******************* */
  810. /* ******************************************************************** */
  811. }