PageRenderTime 56ms CodeModel.GetById 22ms RepoModel.GetById 1ms app.codeStats 0ms

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

https://gitlab.com/essere.lab.public/qualitas.class-corpus
Java | 1116 lines | 712 code | 89 blank | 315 comment | 138 complexity | c0adf65921e74af54c4955cc01c3a1af 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.Arrays;
  25. import java.util.Collection;
  26. import java.util.Date;
  27. import java.util.Hashtable;
  28. import java.util.HashMap;
  29. import java.util.Iterator;
  30. import java.util.Locale;
  31. import java.util.Properties;
  32. import java.util.StringTokenizer;
  33. import java.util.Vector;
  34. import java.util.ArrayList;
  35. import javax.mail.Address;
  36. import javax.mail.MessagingException;
  37. import javax.mail.SendFailedException;
  38. import javax.mail.Session;
  39. import javax.mail.Transport;
  40. import javax.mail.URLName;
  41. import javax.mail.internet.AddressException;
  42. import javax.mail.internet.InternetAddress;
  43. import javax.mail.internet.MimeMessage;
  44. import javax.mail.internet.ParseException;
  45. import org.apache.avalon.framework.component.ComponentException;
  46. import org.apache.avalon.framework.component.ComponentManager;
  47. import org.apache.avalon.framework.configuration.DefaultConfiguration;
  48. import org.apache.james.Constants;
  49. import org.apache.james.core.MailImpl;
  50. import org.apache.james.services.MailServer;
  51. import org.apache.james.services.MailStore;
  52. import org.apache.james.services.SpoolRepository;
  53. import org.apache.mailet.MailetContext;
  54. import org.apache.mailet.GenericMailet;
  55. import org.apache.mailet.HostAddress;
  56. import org.apache.mailet.Mail;
  57. import org.apache.mailet.MailAddress;
  58. import org.apache.oro.text.regex.MalformedPatternException;
  59. import org.apache.oro.text.regex.Pattern;
  60. import org.apache.oro.text.regex.Perl5Compiler;
  61. import org.apache.oro.text.regex.Perl5Matcher;
  62. import org.apache.oro.text.regex.MatchResult;
  63. /**
  64. * Receives a MessageContainer from JamesSpoolManager and takes care of delivery
  65. * the message to remote hosts. If for some reason mail can't be delivered
  66. * store it in the "outgoing" Repository and set an Alarm. After the next "delayTime" the
  67. * Alarm will wake the servlet that will try to send it again. After "maxRetries"
  68. * the mail will be considered undeliverable and will be returned to sender.
  69. *
  70. * TO DO (in priority):
  71. * 1. Support a gateway (a single server where all mail will be delivered) (DONE)
  72. * 2. Provide better failure messages (DONE)
  73. * 3. More efficiently handle numerous recipients
  74. * 4. Migrate to use Phoenix for the delivery threads
  75. *
  76. * You really want to read the JavaMail documentation if you are
  77. * working in here, and you will want to view the list of JavaMail
  78. * attributes, which are documented here:
  79. *
  80. * http://java.sun.com/products/javamail/1.3/docs/javadocs/com/sun/mail/smtp/package-summary.html
  81. *
  82. * as well as other places.
  83. *
  84. * @version CVS $Revision: 1.33.4.21 $ $Date: 2004/05/02 06:08:37 $
  85. */
  86. public class RemoteDelivery extends GenericMailet implements Runnable {
  87. private static final long DEFAULT_DELAY_TIME = 21600000; // default is 6*60*60*1000 millis (6 hours)
  88. private static final String PATTERN_STRING =
  89. "\\s*([0-9]*\\s*[\\*])?\\s*([0-9]+)\\s*([a-z,A-Z]*)\\s*";//pattern to match
  90. //[attempts*]delay[units]
  91. private static Pattern PATTERN = null; //the compiled pattern of the above String
  92. private static final HashMap MULTIPLIERS = new HashMap (10); //holds allowed units for delaytime together with
  93. //the factor to turn it into the equivalent time in msec
  94. /*
  95. * Static initializer.<p>
  96. * Compiles pattern for processing delaytime entries.<p>
  97. * Initializes MULTIPLIERS with the supported unit quantifiers
  98. */
  99. static {
  100. try {
  101. Perl5Compiler compiler = new Perl5Compiler();
  102. PATTERN = compiler.compile(PATTERN_STRING, Perl5Compiler.READ_ONLY_MASK);
  103. } catch(MalformedPatternException mpe) {
  104. //this should not happen as the pattern string is hardcoded.
  105. System.err.println ("Malformed pattern: " + PATTERN_STRING);
  106. mpe.printStackTrace (System.err);
  107. }
  108. //add allowed units and their respective multiplier
  109. MULTIPLIERS.put ("msec", new Integer (1));
  110. MULTIPLIERS.put ("msecs", new Integer (1));
  111. MULTIPLIERS.put ("sec", new Integer (1000));
  112. MULTIPLIERS.put ("secs", new Integer (1000));
  113. MULTIPLIERS.put ("minute", new Integer (1000*60));
  114. MULTIPLIERS.put ("minutes", new Integer (1000*60));
  115. MULTIPLIERS.put ("hour", new Integer (1000*60*60));
  116. MULTIPLIERS.put ("hours", new Integer (1000*60*60));
  117. MULTIPLIERS.put ("day", new Integer (1000*60*60*24));
  118. MULTIPLIERS.put ("days", new Integer (1000*60*60*24));
  119. }
  120. /**
  121. * This filter is used in the accept call to the spool.
  122. * It will select the next mail ready for processing according to the mails
  123. * retrycount and lastUpdated time
  124. **/
  125. private class MultipleDelayFilter implements SpoolRepository.AcceptFilter
  126. {
  127. /**
  128. * holds the time to wait for the youngest mail to get ready for processing
  129. **/
  130. long youngest = 0;
  131. /**
  132. * Uses the getNextDelay to determine if a mail is ready for processing based on the delivered parameters
  133. * errorMessage (which holds the retrycount), lastUpdated and state
  134. * @param key the name/key of the message
  135. * @param state the mails state
  136. * @param lastUpdated the mail was last written to the spool at this time.
  137. * @param errorMessage actually holds the retrycount as a string (see failMessage below)
  138. **/
  139. public boolean accept (String key, String state, long lastUpdated, String errorMessage) {
  140. if (state.equals(Mail.ERROR)) {
  141. //Test the time...
  142. int retries = Integer.parseInt(errorMessage);
  143. long delay = getNextDelay (retries);
  144. long timeToProcess = delay + lastUpdated;
  145. if (System.currentTimeMillis() > timeToProcess) {
  146. //We're ready to process this again
  147. return true;
  148. } else {
  149. //We're not ready to process this.
  150. if (youngest == 0 || youngest > timeToProcess) {
  151. //Mark this as the next most likely possible mail to process
  152. youngest = timeToProcess;
  153. }
  154. return false;
  155. }
  156. } else {
  157. //This mail is good to go... return the key
  158. return true;
  159. }
  160. }
  161. /**
  162. * @return the optimal time the SpoolRepository.accept(AcceptFilter) method should wait before
  163. * trying to find a mail ready for processing again.
  164. **/
  165. public long getWaitTime () {
  166. if (youngest == 0) {
  167. return 0;
  168. } else {
  169. long duration = youngest - System.currentTimeMillis();
  170. youngest = 0; //get ready for next run
  171. return duration <= 0 ? 1 : duration;
  172. }
  173. }
  174. }
  175. /**
  176. * Controls certain log messages
  177. */
  178. private boolean isDebug = false;
  179. private SpoolRepository outgoing; // The spool of outgoing mail
  180. private long[] delayTimes; //holds expanded delayTimes
  181. private int maxRetries = 5; // default number of retries
  182. private long smtpTimeout = 600000; //default number of ms to timeout on smtp delivery
  183. private boolean sendPartial = false; // If false then ANY address errors will cause the transmission to fail
  184. private int connectionTimeout = 60000; // The amount of time JavaMail will wait before giving up on a socket connect()
  185. private int deliveryThreadCount = 1; // default number of delivery threads
  186. private Collection gatewayServer = null; // the server(s) to send all email to
  187. private String bindAddress = null; // JavaMail delivery socket binds to this local address. If null the JavaMail default will be used.
  188. private boolean isBindUsed = false; // true, if the bind configuration
  189. // parameter is supplied, RemoteDeliverySocketFactory
  190. // will be used in this case
  191. private Collection deliveryThreads = new Vector();
  192. private MailServer mailServer;
  193. private volatile boolean destroyed = false; //Flag that the run method will check and end itself if set to true
  194. private String bounceProcessor = null; // the processor for creating Bounces
  195. private Perl5Matcher delayTimeMatcher; //matcher use at init time to parse delaytime parameters
  196. private MultipleDelayFilter delayFilter = new MultipleDelayFilter ();//used by accept to selcet the next mail ready for processing
  197. /**
  198. * Initialize the mailet
  199. */
  200. public void init() throws MessagingException {
  201. isDebug = (getInitParameter("debug") == null) ? false : new Boolean(getInitParameter("debug")).booleanValue();
  202. ArrayList delay_times_list = new ArrayList();
  203. try {
  204. if (getInitParameter("delayTime") != null) {
  205. delayTimeMatcher = new Perl5Matcher();
  206. String delay_times = getInitParameter("delayTime");
  207. //split on comma's
  208. StringTokenizer st = new StringTokenizer (delay_times,",");
  209. while (st.hasMoreTokens()) {
  210. String delay_time = st.nextToken();
  211. delay_times_list.add (new Delay(delay_time));
  212. }
  213. } else {
  214. //use default delayTime.
  215. delay_times_list.add (new Delay());
  216. }
  217. } catch (Exception e) {
  218. log("Invalid delayTime setting: " + getInitParameter("delayTime"));
  219. }
  220. try {
  221. if (getInitParameter("maxRetries") != null) {
  222. maxRetries = Integer.parseInt(getInitParameter("maxRetries"));
  223. }
  224. //check consistency with delay_times_list attempts
  225. int total_attempts = calcTotalAttempts (delay_times_list);
  226. if (total_attempts > maxRetries) {
  227. log("Total number of delayTime attempts exceeds maxRetries specified. Increasing maxRetries from "+maxRetries+" to "+total_attempts);
  228. maxRetries = total_attempts;
  229. } else {
  230. int extra = maxRetries - total_attempts;
  231. if (extra != 0) {
  232. log("maxRetries is larger than total number of attempts specified. Increasing last delayTime with "+extra+" attempts ");
  233. if (delay_times_list.size() != 0) {
  234. Delay delay = (Delay)delay_times_list.get (delay_times_list.size()-1); //last Delay
  235. delay.setAttempts (delay.getAttempts()+extra);
  236. log("Delay of "+delay.getDelayTime()+" msecs is now attempted: "+delay.getAttempts()+" times");
  237. } else {
  238. log ("NO, delaytimes cannot continue");
  239. }
  240. }
  241. }
  242. delayTimes = expandDelays (delay_times_list);
  243. } catch (Exception e) {
  244. log("Invalid maxRetries setting: " + getInitParameter("maxRetries"));
  245. }
  246. try {
  247. if (getInitParameter("timeout") != null) {
  248. smtpTimeout = Integer.parseInt(getInitParameter("timeout"));
  249. }
  250. } catch (Exception e) {
  251. log("Invalid timeout setting: " + getInitParameter("timeout"));
  252. }
  253. try {
  254. if (getInitParameter("connectiontimeout") != null) {
  255. connectionTimeout = Integer.parseInt(getInitParameter("connectiontimeout"));
  256. }
  257. } catch (Exception e) {
  258. log("Invalid timeout setting: " + getInitParameter("timeout"));
  259. }
  260. sendPartial = (getInitParameter("sendpartial") == null) ? false : new Boolean(getInitParameter("sendpartial")).booleanValue();
  261. bounceProcessor = getInitParameter("bounceProcessor");
  262. String gateway = getInitParameter("gateway");
  263. String gatewayPort = getInitParameter("gatewayPort");
  264. if (gateway != null) {
  265. gatewayServer = new ArrayList();
  266. StringTokenizer st = new StringTokenizer(gateway, ",") ;
  267. while (st.hasMoreTokens()) {
  268. String server = st.nextToken().trim() ;
  269. if (server.indexOf(':') < 0 && gatewayPort != null) {
  270. server += ":";
  271. server += gatewayPort;
  272. }
  273. if (isDebug) log("Adding SMTP gateway: " + server) ;
  274. gatewayServer.add(server);
  275. }
  276. }
  277. ComponentManager compMgr = (ComponentManager)getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER);
  278. String outgoingPath = getInitParameter("outgoing");
  279. if (outgoingPath == null) {
  280. outgoingPath = "file:///../var/mail/outgoing";
  281. }
  282. try {
  283. // Instantiate the a MailRepository for outgoing mails
  284. MailStore mailstore = (MailStore) compMgr.lookup("org.apache.james.services.MailStore");
  285. DefaultConfiguration spoolConf
  286. = new DefaultConfiguration("repository", "generated:RemoteDelivery.java");
  287. spoolConf.setAttribute("destinationURL", outgoingPath);
  288. spoolConf.setAttribute("type", "SPOOL");
  289. outgoing = (SpoolRepository) mailstore.select(spoolConf);
  290. } catch (ComponentException cnfe) {
  291. log("Failed to retrieve Store component:" + cnfe.getMessage());
  292. } catch (Exception e) {
  293. log("Failed to retrieve Store component:" + e.getMessage());
  294. }
  295. //Start up a number of threads
  296. try {
  297. deliveryThreadCount = Integer.parseInt(getInitParameter("deliveryThreads"));
  298. } catch (Exception e) {
  299. }
  300. for (int i = 0; i < deliveryThreadCount; i++) {
  301. StringBuffer nameBuffer =
  302. new StringBuffer(32)
  303. .append("Remote delivery thread (")
  304. .append(i)
  305. .append(")");
  306. Thread t = new Thread(this, nameBuffer.toString());
  307. t.start();
  308. deliveryThreads.add(t);
  309. }
  310. bindAddress = getInitParameter("bind");
  311. isBindUsed = bindAddress != null;
  312. try {
  313. if (isBindUsed) RemoteDeliverySocketFactory.setBindAdress(bindAddress);
  314. } catch (UnknownHostException e) {
  315. log("Invalid bind setting (" + bindAddress + "): " + e.toString());
  316. }
  317. }
  318. /**
  319. * We can assume that the recipients of this message are all going to the same
  320. * mail server. We will now rely on the DNS server to do DNS MX record lookup
  321. * and try to deliver to the multiple mail servers. If it fails, it should
  322. * throw an exception.
  323. *
  324. * Creation date: (2/24/00 11:25:00 PM)
  325. * @param mail org.apache.james.core.MailImpl
  326. * @param session javax.mail.Session
  327. * @return boolean Whether the delivery was successful and the message can be deleted
  328. */
  329. private boolean deliver(MailImpl mail, Session session) {
  330. try {
  331. if (isDebug) {
  332. log("Attempting to deliver " + mail.getName());
  333. }
  334. MimeMessage message = mail.getMessage();
  335. //Create an array of the recipients as InternetAddress objects
  336. Collection recipients = mail.getRecipients();
  337. InternetAddress addr[] = new InternetAddress[recipients.size()];
  338. int j = 0;
  339. for (Iterator i = recipients.iterator(); i.hasNext(); j++) {
  340. MailAddress rcpt = (MailAddress)i.next();
  341. addr[j] = rcpt.toInternetAddress();
  342. }
  343. if (addr.length <= 0) {
  344. log("No recipients specified... not sure how this could have happened.");
  345. return true;
  346. }
  347. //Figure out which servers to try to send to. This collection
  348. // will hold all the possible target servers
  349. Iterator targetServers = null;
  350. if (gatewayServer == null) {
  351. MailAddress rcpt = (MailAddress) recipients.iterator().next();
  352. String host = rcpt.getHost();
  353. //Lookup the possible targets
  354. targetServers = getMailetContext().getSMTPHostAddresses(host);
  355. if (!targetServers.hasNext()) {
  356. log("No mail server found for: " + host);
  357. StringBuffer exceptionBuffer =
  358. new StringBuffer(128)
  359. .append("There are no DNS entries for the hostname ")
  360. .append(host)
  361. .append(". I cannot determine where to send this message.");
  362. return failMessage(mail, new MessagingException(exceptionBuffer.toString()), false);
  363. }
  364. } else {
  365. targetServers = getGatewaySMTPHostAddresses(gatewayServer);
  366. }
  367. MessagingException lastError = null;
  368. while ( targetServers.hasNext()) {
  369. try {
  370. HostAddress outgoingMailServer = (HostAddress) targetServers.next();
  371. StringBuffer logMessageBuffer =
  372. new StringBuffer(256)
  373. .append("Attempting delivery of ")
  374. .append(mail.getName())
  375. .append(" to host ")
  376. .append(outgoingMailServer.getHostName())
  377. .append(" at ")
  378. .append(outgoingMailServer.getHost())
  379. .append(" to addresses ")
  380. .append(Arrays.asList(addr));
  381. log(logMessageBuffer.toString());
  382. Properties props = session.getProperties();
  383. if (mail.getSender() == null) {
  384. props.put("mail.smtp.from", "<>");
  385. } else {
  386. String sender = mail.getSender().toString();
  387. props.put("mail.smtp.from", sender);
  388. }
  389. //Many of these properties are only in later JavaMail versions
  390. //"mail.smtp.ehlo" //default true
  391. //"mail.smtp.auth" //default false
  392. //"mail.smtp.dsn.ret" //default to nothing... appended as RET= after MAIL FROM line.
  393. //"mail.smtp.dsn.notify" //default to nothing...appended as NOTIFY= after RCPT TO line.
  394. Transport transport = null;
  395. try {
  396. transport = session.getTransport(outgoingMailServer);
  397. try {
  398. transport.connect();
  399. } catch (MessagingException me) {
  400. // Any error on connect should cause the mailet to attempt
  401. // to connect to the next SMTP server associated with this
  402. // MX record. Just log the exception. We'll worry about
  403. // failing the message at the end of the loop.
  404. log(me.getMessage());
  405. continue;
  406. }
  407. transport.sendMessage(message, addr);
  408. } finally {
  409. if (transport != null) {
  410. transport.close();
  411. transport = null;
  412. }
  413. }
  414. logMessageBuffer =
  415. new StringBuffer(256)
  416. .append("Mail (")
  417. .append(mail.getName())
  418. .append(") sent successfully to ")
  419. .append(outgoingMailServer.getHostName())
  420. .append(" at ")
  421. .append(outgoingMailServer.getHost());
  422. log(logMessageBuffer.toString());
  423. return true;
  424. } catch (SendFailedException sfe) {
  425. if (sfe.getValidSentAddresses() == null
  426. || sfe.getValidSentAddresses().length < 1) {
  427. if (isDebug) log("Send failed, continuing with any other servers");
  428. lastError = sfe;
  429. continue;
  430. } else {
  431. // If any mail was sent then the outgoing
  432. // server config must be ok, therefore rethrow
  433. throw sfe;
  434. }
  435. } catch (MessagingException me) {
  436. //MessagingException are horribly difficult to figure out what actually happened.
  437. StringBuffer exceptionBuffer =
  438. new StringBuffer(256)
  439. .append("Exception delivering message (")
  440. .append(mail.getName())
  441. .append(") - ")
  442. .append(me.getMessage());
  443. log(exceptionBuffer.toString());
  444. if ((me.getNextException() != null) &&
  445. (me.getNextException() instanceof java.io.IOException)) {
  446. //This is more than likely a temporary failure
  447. // If it's an IO exception with no nested exception, it's probably
  448. // some socket or weird I/O related problem.
  449. lastError = me;
  450. continue;
  451. }
  452. // This was not a connection or I/O error particular to one
  453. // SMTP server of an MX set. Instead, it is almost certainly
  454. // a protocol level error. In this case we assume that this
  455. // is an error we'd encounter with any of the SMTP servers
  456. // associated with this MX record, and we pass the exception
  457. // to the code in the outer block that determines its severity.
  458. throw me;
  459. }
  460. } // end while
  461. //If we encountered an exception while looping through,
  462. //throw the last MessagingException we caught. We only
  463. //do this if we were unable to send the message to any
  464. //server. If sending eventually succeeded, we exit
  465. //deliver() though the return at the end of the try
  466. //block.
  467. if (lastError != null) {
  468. throw lastError;
  469. }
  470. } catch (SendFailedException sfe) {
  471. boolean deleteMessage = false;
  472. Collection recipients = mail.getRecipients();
  473. //Would like to log all the types of email addresses
  474. if (isDebug) log("Recipients: " + recipients);
  475. /*
  476. if (sfe.getValidSentAddresses() != null) {
  477. Address[] validSent = sfe.getValidSentAddresses();
  478. Collection recipients = mail.getRecipients();
  479. //Remove these addresses for the recipients
  480. for (int i = 0; i < validSent.length; i++) {
  481. try {
  482. MailAddress addr = new MailAddress(validSent[i].toString());
  483. recipients.remove(addr);
  484. } catch (ParseException pe) {
  485. //ignore once debugging done
  486. pe.printStackTrace();
  487. }
  488. }
  489. }
  490. */
  491. /*
  492. * The rest of the recipients failed for one reason or
  493. * another.
  494. *
  495. * SendFailedException actually handles this for us. For
  496. * example, if you send a message that has multiple invalid
  497. * addresses, you'll get a top-level SendFailedException
  498. * that that has the valid, valid-unsent, and invalid
  499. * address lists, with all of the server response messages
  500. * will be contained within the nested exceptions. [Note:
  501. * the content of the nested exceptions is implementation
  502. * dependent.]
  503. *
  504. * sfe.getInvalidAddresses() should be considered permanent.
  505. * sfe.getValidUnsentAddresses() should be considered temporary.
  506. *
  507. * JavaMail v1.3 properly populates those collections based
  508. * upon the 4xx and 5xx response codes.
  509. *
  510. */
  511. if (sfe.getInvalidAddresses() != null) {
  512. Address[] address = sfe.getInvalidAddresses();
  513. if (address.length > 0) {
  514. recipients.clear();
  515. for (int i = 0; i < address.length; i++) {
  516. try {
  517. recipients.add(new MailAddress(address[i].toString()));
  518. } catch (ParseException pe) {
  519. // this should never happen ... we should have
  520. // caught malformed addresses long before we
  521. // got to this code.
  522. log("Can't parse invalid address: " + pe.getMessage());
  523. }
  524. }
  525. if (isDebug) log("Invalid recipients: " + recipients);
  526. deleteMessage = failMessage(mail, sfe, true);
  527. }
  528. }
  529. if (sfe.getValidUnsentAddresses() != null) {
  530. Address[] address = sfe.getValidUnsentAddresses();
  531. if (address.length > 0) {
  532. recipients.clear();
  533. for (int i = 0; i < address.length; i++) {
  534. try {
  535. recipients.add(new MailAddress(address[i].toString()));
  536. } catch (ParseException pe) {
  537. // this should never happen ... we should have
  538. // caught malformed addresses long before we
  539. // got to this code.
  540. log("Can't parse unsent address: " + pe.getMessage());
  541. }
  542. }
  543. if (isDebug) log("Unsent recipients: " + recipients);
  544. deleteMessage = failMessage(mail, sfe, false);
  545. }
  546. }
  547. return deleteMessage;
  548. } catch (MessagingException ex) {
  549. // We should do a better job checking this... if the failure is a general
  550. // connect exception, this is less descriptive than more specific SMTP command
  551. // failure... have to lookup and see what are the various Exception
  552. // possibilities
  553. // Unable to deliver message after numerous tries... fail accordingly
  554. // We check whether this is a 5xx error message, which
  555. // indicates a permanent failure (like account doesn't exist
  556. // or mailbox is full or domain is setup wrong).
  557. // We fail permanently if this was a 5xx error
  558. return failMessage(mail, ex, ('5' == ex.getMessage().charAt(0)));
  559. }
  560. /* If we get here, we've exhausted the loop of servers without
  561. * sending the message or throwing an exception. One case
  562. * where this might happen is if we get a MessagingException on
  563. * each transport.connect(), e.g., if there is only one server
  564. * and we get a connect exception.
  565. */
  566. return failMessage(mail, new MessagingException("No mail server(s) available at this time."), false);
  567. }
  568. /**
  569. * Insert the method's description here.
  570. * Creation date: (2/25/00 1:14:18 AM)
  571. * @param mail org.apache.james.core.MailImpl
  572. * @param exception javax.mail.MessagingException
  573. * @param boolean permanent
  574. * @return boolean Whether the message failed fully and can be deleted
  575. */
  576. private boolean failMessage(MailImpl mail, MessagingException ex, boolean permanent) {
  577. StringWriter sout = new StringWriter();
  578. PrintWriter out = new PrintWriter(sout, true);
  579. if (permanent) {
  580. out.print("Permanent");
  581. } else {
  582. out.print("Temporary");
  583. }
  584. StringBuffer logBuffer =
  585. new StringBuffer(64)
  586. .append(" exception delivering mail (")
  587. .append(mail.getName())
  588. .append(": ");
  589. out.print(logBuffer.toString());
  590. ex.printStackTrace(out);
  591. log(sout.toString());
  592. if (!permanent) {
  593. if (!mail.getState().equals(Mail.ERROR)) {
  594. mail.setState(Mail.ERROR);
  595. mail.setErrorMessage("0");
  596. mail.setLastUpdated(new Date());
  597. }
  598. int retries = Integer.parseInt(mail.getErrorMessage());
  599. if (retries < maxRetries) {
  600. logBuffer =
  601. new StringBuffer(128)
  602. .append("Storing message ")
  603. .append(mail.getName())
  604. .append(" into outgoing after ")
  605. .append(retries)
  606. .append(" retries");
  607. log(logBuffer.toString());
  608. ++retries;
  609. mail.setErrorMessage(retries + "");
  610. mail.setLastUpdated(new Date());
  611. return false;
  612. } else {
  613. logBuffer =
  614. new StringBuffer(128)
  615. .append("Bouncing message ")
  616. .append(mail.getName())
  617. .append(" after ")
  618. .append(retries)
  619. .append(" retries");
  620. log(logBuffer.toString());
  621. }
  622. }
  623. if (bounceProcessor != null) {
  624. // do the new DSN bounce
  625. // setting attributes for DSN mailet
  626. mail.setAttribute("delivery-error", ex);
  627. mail.setState(bounceProcessor);
  628. // re-insert the mail into the spool for getting it passed to the dsn-processor
  629. MailetContext mc = getMailetContext();
  630. try {
  631. mc.sendMail(mail);
  632. } catch (MessagingException e) {
  633. // we shouldn't get an exception, because the mail was already processed
  634. log("Exception re-inserting failed mail: ", e);
  635. }
  636. } else {
  637. // do an old style bounce
  638. bounce(mail, ex);
  639. }
  640. return true;
  641. }
  642. private void bounce(MailImpl mail, MessagingException ex) {
  643. StringWriter sout = new StringWriter();
  644. PrintWriter out = new PrintWriter(sout, true);
  645. String machine = "[unknown]";
  646. try {
  647. InetAddress me = InetAddress.getLocalHost();
  648. machine = me.getHostName();
  649. } catch(Exception e){
  650. machine = "[address unknown]";
  651. }
  652. StringBuffer bounceBuffer =
  653. new StringBuffer(128)
  654. .append("Hi. This is the James mail server at ")
  655. .append(machine)
  656. .append(".");
  657. out.println(bounceBuffer.toString());
  658. out.println("I'm afraid I wasn't able to deliver your message to the following addresses.");
  659. out.println("This is a permanent error; I've given up. Sorry it didn't work out. Below");
  660. out.println("I include the list of recipients and the reason why I was unable to deliver");
  661. out.println("your message.");
  662. out.println();
  663. for (Iterator i = mail.getRecipients().iterator(); i.hasNext(); ) {
  664. out.println(i.next());
  665. }
  666. if (ex.getNextException() == null) {
  667. out.println(ex.getMessage().trim());
  668. } else {
  669. Exception ex1 = ex.getNextException();
  670. if (ex1 instanceof SendFailedException) {
  671. out.println("Remote mail server told me: " + ex1.getMessage().trim());
  672. } else if (ex1 instanceof UnknownHostException) {
  673. out.println("Unknown host: " + ex1.getMessage().trim());
  674. out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server.");
  675. } else if (ex1 instanceof ConnectException) {
  676. //Already formatted as "Connection timed out: connect"
  677. out.println(ex1.getMessage().trim());
  678. } else if (ex1 instanceof SocketException) {
  679. out.println("Socket exception: " + ex1.getMessage().trim());
  680. } else {
  681. out.println(ex1.getMessage().trim());
  682. }
  683. }
  684. out.println();
  685. out.println("The original message is attached.");
  686. log("Sending failure message " + mail.getName());
  687. try {
  688. getMailetContext().bounce(mail, sout.toString());
  689. } catch (MessagingException me) {
  690. log("Encountered unexpected messaging exception while bouncing message: " + me.getMessage());
  691. } catch (Exception e) {
  692. log("Encountered unexpected exception while bouncing message: " + e.getMessage());
  693. }
  694. }
  695. public String getMailetInfo() {
  696. return "RemoteDelivery Mailet";
  697. }
  698. /**
  699. * For this message, we take the list of recipients, organize these into distinct
  700. * servers, and duplicate the message for each of these servers, and then call
  701. * the deliver (messagecontainer) method for each server-specific
  702. * messagecontainer ... that will handle storing it in the outgoing queue if needed.
  703. *
  704. * @param mail org.apache.mailet.Mail
  705. */
  706. public void service(Mail genericmail) throws MessagingException{
  707. MailImpl mail = (MailImpl)genericmail;
  708. // Do I want to give the internal key, or the message's Message ID
  709. if (isDebug) {
  710. log("Remotely delivering mail " + mail.getName());
  711. }
  712. Collection recipients = mail.getRecipients();
  713. if (gatewayServer == null) {
  714. // Must first organize the recipients into distinct servers (name made case insensitive)
  715. Hashtable targets = new Hashtable();
  716. for (Iterator i = recipients.iterator(); i.hasNext();) {
  717. MailAddress target = (MailAddress)i.next();
  718. String targetServer = target.getHost().toLowerCase(Locale.US);
  719. Collection temp = (Collection)targets.get(targetServer);
  720. if (temp == null) {
  721. temp = new ArrayList();
  722. targets.put(targetServer, temp);
  723. }
  724. temp.add(target);
  725. }
  726. //We have the recipients organized into distinct servers... put them into the
  727. //delivery store organized like this... this is ultra inefficient I think...
  728. // Store the new message containers, organized by server, in the outgoing mail repository
  729. String name = mail.getName();
  730. for (Iterator i = targets.keySet().iterator(); i.hasNext(); ) {
  731. String host = (String) i.next();
  732. Collection rec = (Collection) targets.get(host);
  733. if (isDebug) {
  734. StringBuffer logMessageBuffer =
  735. new StringBuffer(128)
  736. .append("Sending mail to ")
  737. .append(rec)
  738. .append(" on host ")
  739. .append(host);
  740. log(logMessageBuffer.toString());
  741. }
  742. mail.setRecipients(rec);
  743. StringBuffer nameBuffer =
  744. new StringBuffer(128)
  745. .append(name)
  746. .append("-to-")
  747. .append(host);
  748. mail.setName(nameBuffer.toString());
  749. outgoing.store(mail);
  750. //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
  751. }
  752. } else {
  753. // Store the mail unaltered for processing by the gateway server(s)
  754. if (isDebug) {
  755. StringBuffer logMessageBuffer =
  756. new StringBuffer(128)
  757. .append("Sending mail to ")
  758. .append(mail.getRecipients())
  759. .append(" via ")
  760. .append(gatewayServer);
  761. log(logMessageBuffer.toString());
  762. }
  763. //Set it to try to deliver (in a separate thread) immediately (triggered by storage)
  764. outgoing.store(mail);
  765. }
  766. mail.setState(Mail.GHOST);
  767. }
  768. // Need to synchronize to get object monitor for notifyAll()
  769. public synchronized void destroy() {
  770. //Mark flag so threads from this mailet stop themselves
  771. destroyed = true;
  772. //Wake up all threads from waiting for an accept
  773. for (Iterator i = deliveryThreads.iterator(); i.hasNext(); ) {
  774. Thread t = (Thread)i.next();
  775. t.interrupt();
  776. }
  777. notifyAll();
  778. }
  779. /**
  780. * Handles checking the outgoing spool for new mail and delivering them if
  781. * there are any
  782. */
  783. public void run() {
  784. /* TODO: CHANGE ME!!! The problem is that we need to wait for James to
  785. * finish initializing. We expect the HELLO_NAME to be put into
  786. * the MailetContext, but in the current configuration we get
  787. * started before the SMTP Server, which establishes the value.
  788. * Since there is no contractual guarantee that there will be a
  789. * HELLO_NAME value, we can't just wait for it. As a temporary
  790. * measure, I'm inserting this philosophically unsatisfactory
  791. * fix.
  792. */
  793. long stop = System.currentTimeMillis() + 60000;
  794. while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null)
  795. && stop > System.currentTimeMillis()) {
  796. try {
  797. Thread.sleep(1000);
  798. } catch (Exception ignored) {} // wait for James to finish initializing
  799. }
  800. //Checks the pool and delivers a mail message
  801. Properties props = new Properties();
  802. //Not needed for production environment
  803. props.put("mail.debug", "false");
  804. //Prevents problems encountered with 250 OK Messages
  805. props.put("mail.smtp.ehlo", "false");
  806. //Sets timeout on going connections
  807. props.put("mail.smtp.timeout", smtpTimeout + "");
  808. props.put("mail.smtp.connectiontimeout", connectionTimeout + "");
  809. props.put("mail.smtp.sendpartial",String.valueOf(sendPartial));
  810. //Set the hostname we'll use as this server
  811. if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) {
  812. props.put("mail.smtp.localhost", (String) getMailetContext().getAttribute(Constants.HELLO_NAME));
  813. }
  814. else {
  815. Collection servernames = (Collection) getMailetContext().getAttribute(Constants.SERVER_NAMES);
  816. if ((servernames != null) && (servernames.size() > 0)) {
  817. props.put("mail.smtp.localhost", (String) servernames.iterator().next());
  818. }
  819. }
  820. if (isBindUsed) {
  821. // undocumented JavaMail 1.2 feature, smtp transport will use
  822. // our socket factory, which will also set the local address
  823. props.put("mail.smtp.socketFactory.class",
  824. "org.apache.james.transport.mailets.RemoteDeliverySocketFactory");
  825. // Don't fallback to the standard socket factory on error, do throw an exception
  826. props.put("mail.smtp.socketFactory.fallback", "false");
  827. }
  828. Session session = Session.getInstance(props, null);
  829. try {
  830. while (!Thread.currentThread().interrupted() && !destroyed) {
  831. try {
  832. MailImpl mail = (MailImpl)outgoing.accept(delayFilter);
  833. String key = mail.getName();
  834. try {
  835. if (isDebug) {
  836. StringBuffer logMessageBuffer =
  837. new StringBuffer(128)
  838. .append(Thread.currentThread().getName())
  839. .append(" will process mail ")
  840. .append(key);
  841. log(logMessageBuffer.toString());
  842. }
  843. if (deliver(mail, session)) {
  844. //Message was successfully delivered/fully failed... delete it
  845. outgoing.remove(key);
  846. } else {
  847. //Something happened that will delay delivery. Store any updates
  848. outgoing.store(mail);
  849. }
  850. //Clear the object handle to make sure it recycles this object.
  851. mail = null;
  852. } catch (Exception e) {
  853. // Prevent unexpected exceptions from causing looping by removing
  854. // message from outgoing.
  855. outgoing.remove(key);
  856. throw e;
  857. }
  858. } catch (Throwable e) {
  859. if (!destroyed) log("Exception caught in RemoteDelivery.run()", e);
  860. }
  861. }
  862. } finally {
  863. // Restore the thread state to non-interrupted.
  864. Thread.currentThread().interrupted();
  865. }
  866. }
  867. /**
  868. * @param list holding Delay objects
  869. * @return the total attempts for all delays
  870. **/
  871. private int calcTotalAttempts (ArrayList list) {
  872. int sum = 0;
  873. Iterator i = list.iterator();
  874. while (i.hasNext()) {
  875. Delay delay = (Delay)i.next();
  876. sum += delay.getAttempts();
  877. }
  878. return sum;
  879. }
  880. /**
  881. * This method expands an ArrayList containing Delay objects into an array holding the
  882. * only delaytime in the order.<p>
  883. * So if the list has 2 Delay objects the first having attempts=2 and delaytime 4000
  884. * the second having attempts=1 and delaytime=300000 will be expanded into this array:<p>
  885. * long[0] = 4000<p>
  886. * long[1] = 4000<p>
  887. * long[2] = 300000<p>
  888. * @param list the list to expand
  889. * @return the expanded list
  890. **/
  891. private long[] expandDelays (ArrayList list) {
  892. long[] delays = new long [calcTotalAttempts(list)];
  893. Iterator i = list.iterator();
  894. int idx = 0;
  895. while (i.hasNext()) {
  896. Delay delay = (Delay)i.next();
  897. for (int j=0; j<delay.getAttempts(); j++) {
  898. delays[idx++]= delay.getDelayTime();
  899. }
  900. }
  901. return delays;
  902. }
  903. /**
  904. * This method returns, given a retry-count, the next delay time to use.
  905. * @param retry_count the current retry_count.
  906. * @return the next delay time to use, given the retry count
  907. **/
  908. private long getNextDelay (int retry_count) {
  909. return delayTimes[retry_count-1];
  910. }
  911. /**
  912. * This class is used to hold a delay time and its corresponding number
  913. * of retries.
  914. **/
  915. private class Delay {
  916. private int attempts = 1;
  917. private long delayTime = DEFAULT_DELAY_TIME;
  918. /**
  919. * This constructor expects Strings of the form "[attempt\*]delaytime[unit]". <p>
  920. * The optional attempt is the number of tries this delay should be used (default = 1)
  921. * The unit if present must be one of (msec,sec,minute,hour,day) (default = msec)
  922. * The constructor multiplies the delaytime by the relevant multiplier for the unit,
  923. * so the delayTime instance variable is always in msec.
  924. * @param init_string the string to initialize this Delay object from
  925. **/
  926. public Delay (String init_string) throws MessagingException
  927. {
  928. String unit = "msec"; //default unit
  929. if (delayTimeMatcher.matches (init_string, PATTERN)) {
  930. MatchResult res = delayTimeMatcher.getMatch ();
  931. //the capturing groups will now hold
  932. //at 1: attempts * (if present)
  933. //at 2: delaytime
  934. //at 3: unit (if present)
  935. if (res.group(1) != null && !res.group(1).equals ("")) {
  936. //we have an attempt *
  937. String attempt_match = res.group(1);
  938. //strip the * and whitespace
  939. attempt_match = attempt_match.substring (0,attempt_match.length()-1).trim();
  940. attempts = Integer.parseInt (attempt_match);
  941. }
  942. delayTime = Long.parseLong (res.group(2));
  943. if (!res.group(3).equals ("")) {
  944. //we have a unit
  945. unit = res.group(3).toLowerCase();
  946. }
  947. } else {
  948. throw new MessagingException(init_string+" does not match "+PATTERN_STRING);
  949. }
  950. if (MULTIPLIERS.get (unit)!=null) {
  951. int multiplier = ((Integer)MULTIPLIERS.get (unit)).intValue();
  952. delayTime *= multiplier;
  953. } else {
  954. throw new MessagingException("Unknown unit: "+unit);
  955. }
  956. }
  957. /**
  958. * This constructor makes a default Delay object, ie. attempts=1 and delayTime=DEFAULT_DELAY_TIME
  959. **/
  960. public Delay () {
  961. }
  962. /**
  963. * @return the delayTime for this Delay
  964. **/
  965. public long getDelayTime () {
  966. return delayTime;
  967. }
  968. /**
  969. * @return the number attempts this Delay should be used.
  970. **/
  971. public int getAttempts () {
  972. return attempts;
  973. }
  974. /**
  975. * Set the number attempts this Delay should be used.
  976. **/
  977. public void setAttempts (int value) {
  978. attempts = value;
  979. }
  980. /**
  981. * Pretty prints this Delay
  982. **/
  983. public String toString () {
  984. StringBuffer buf = new StringBuffer(15);
  985. buf.append (getAttempts ());
  986. buf.append ('*');
  987. buf.append (getDelayTime());
  988. buf.append ("msec");
  989. return buf.toString();
  990. }
  991. }
  992. /*
  993. * Returns an Iterator over org.apache.mailet.HostAddress, a
  994. * specialized subclass of javax.mail.URLName, which provides
  995. * location information for servers that are specified as mail
  996. * handlers for the given hostname. If no host is found, the
  997. * Iterator returned will be empty and the first call to hasNext()
  998. * will return false. The Iterator is a nested iterator: the outer
  999. * iteration is over each gateway, and the inner iteration is over
  1000. * potentially multiple A records for each gateway.
  1001. *
  1002. * @see org.apache.james.DNSServer#getSMTPHostAddresses(String)
  1003. * @since v2.2.0a16-unstable
  1004. * @param gatewayServers - Collection of host[:port] Strings
  1005. * @return an Iterator over HostAddress instances, sorted by priority
  1006. */
  1007. private Iterator getGatewaySMTPHostAddresses(final Collection gatewayServers) {
  1008. return new Iterator() {
  1009. private Iterator gateways = gatewayServers.iterator();
  1010. private Iterator addresses = null;
  1011. public boolean hasNext() {
  1012. return gateways.hasNext();
  1013. }
  1014. public Object next() {
  1015. if (addresses == null || !addresses.hasNext())
  1016. {
  1017. String server = (String) gateways.next();
  1018. String port = "25";
  1019. int idx = server.indexOf(':');
  1020. if ( idx > 0) {
  1021. port = server.substring(idx+1);
  1022. server = server.substring(0,idx);
  1023. }
  1024. final String nextGateway = server;
  1025. final String nextGatewayPort = port;
  1026. try {
  1027. final InetAddress[] ips = org.apache.james.dnsserver.DNSServer.getAllByName(nextGateway);