I have made a RemoteDeliveryX that can handle smtp authentication as
well as ssl (optional). I have not submitted it here because someone
else has mentioned that they are doing the same already. My
RemoteDeliveryX does not support multiple gateways. regards, Glen Noel J. Bergman wrote: Can the Gateway SMTP be used with SMTP Authorization?With some change, yes. Not as compiled by us at this time.See: http://java.sun.com/products/javamail/javadocs/com/sun/mail/smtp/package-sum mary.html --- Noel --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED] |
/*********************************************************************** * Copyright (c) 2000-2004 The Apache Software Foundation. * * All rights reserved. * * ------------------------------------------------------------------- * * Licensed under the Apache License, Version 2.0 (the "License"); you * * may not use this file except in compliance with the License. You * * may obtain a copy of the License at: * * * * http://www.apache.org/licenses/LICENSE-2.0 * * * * Unless required by applicable law or agreed to in writing, software * * distributed under the License is distributed on an "AS IS" BASIS, * * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or * * implied. See the License for the specific language governing * * permissions and limitations under the License. * ***********************************************************************/ package net.model3.james; import java.io.PrintWriter; import java.io.StringWriter; import java.net.ConnectException; import java.net.InetAddress; import java.net.SocketException; import java.net.UnknownHostException; import java.util.Arrays; import java.util.Collection; import java.util.Date; import java.util.Hashtable; import java.util.HashMap; import java.util.Iterator; import java.util.Locale; import java.util.Properties; import java.util.StringTokenizer; import java.util.Vector; import java.util.ArrayList; import javax.mail.Address; import javax.mail.Authenticator; import javax.mail.MessagingException; import javax.mail.PasswordAuthentication; import javax.mail.SendFailedException; import javax.mail.Session; import javax.mail.Transport; import javax.mail.URLName; import javax.mail.internet.AddressException; import javax.mail.internet.InternetAddress; import javax.mail.internet.MimeMessage; import javax.mail.internet.ParseException; import org.apache.avalon.framework.component.ComponentException; import org.apache.avalon.framework.component.ComponentManager; import org.apache.avalon.framework.configuration.DefaultConfiguration; import org.apache.james.Constants; import org.apache.james.core.MailImpl; import org.apache.james.services.MailServer; import org.apache.james.services.MailStore; import org.apache.james.services.SpoolRepository; import org.apache.mailet.MailetContext; import org.apache.mailet.GenericMailet; import org.apache.mailet.HostAddress; import org.apache.mailet.Mail; import org.apache.mailet.MailAddress; import org.apache.oro.text.regex.MalformedPatternException; import org.apache.oro.text.regex.Pattern; import org.apache.oro.text.regex.Perl5Compiler; import org.apache.oro.text.regex.Perl5Matcher; import org.apache.oro.text.regex.MatchResult; /** * Receives a MessageContainer from JamesSpoolManager and takes care of delivery * the message to remote hosts. If for some reason mail can't be delivered * store it in the "outgoing" Repository and set an Alarm. After the next "delayTime" the * Alarm will wake the servlet that will try to send it again. After "maxRetries" * the mail will be considered undeliverable and will be returned to sender. * * TO DO (in priority): * 1. Support a gateway (a single server where all mail will be delivered) (DONE) * 2. Provide better failure messages (DONE) * 3. More efficiently handle numerous recipients * 4. Migrate to use Phoenix for the delivery threads * * You really want to read the JavaMail documentation if you are * working in here, and you will want to view the list of JavaMail * attributes, which are documented here: * * http://java.sun.com/products/javamail/1.3/docs/javadocs/com/sun/mail/smtp/package-summary.html * * as well as other places. * * @version CVS $Revision: 1.33.4.21 $ $Date: 2004/05/02 06:08:37 $ */ public class RemoteDeliveryX extends GenericMailet implements Runnable { private static final long DEFAULT_DELAY_TIME = 21600000; // default is 6*60*60*1000 millis (6 hours) private static final String PATTERN_STRING = "\\s*([0-9]*\\s*[\\*])?\\s*([0-9]+)\\s*([a-z,A-Z]*)\\s*";//pattern to match //[attempts*]delay[units] private static Pattern PATTERN = null; //the compiled pattern of the above String private static final HashMap MULTIPLIERS = new HashMap (10); //holds allowed units for delaytime together with //the factor to turn it into the equivalent time in msec /* * Static initializer.<p> * Compiles pattern for processing delaytime entries.<p> * Initializes MULTIPLIERS with the supported unit quantifiers */ static { try { Perl5Compiler compiler = new Perl5Compiler(); PATTERN = compiler.compile(PATTERN_STRING, Perl5Compiler.READ_ONLY_MASK); } catch(MalformedPatternException mpe) { //this should not happen as the pattern string is hardcoded. System.err.println ("Malformed pattern: " + PATTERN_STRING); mpe.printStackTrace (System.err); } //add allowed units and their respective multiplier MULTIPLIERS.put ("msec", new Integer (1)); MULTIPLIERS.put ("msecs", new Integer (1)); MULTIPLIERS.put ("sec", new Integer (1000)); MULTIPLIERS.put ("secs", new Integer (1000)); MULTIPLIERS.put ("minute", new Integer (1000*60)); MULTIPLIERS.put ("minutes", new Integer (1000*60)); MULTIPLIERS.put ("hour", new Integer (1000*60*60)); MULTIPLIERS.put ("hours", new Integer (1000*60*60)); MULTIPLIERS.put ("day", new Integer (1000*60*60*24)); MULTIPLIERS.put ("days", new Integer (1000*60*60*24)); } /** * This filter is used in the accept call to the spool. * It will select the next mail ready for processing according to the mails * retrycount and lastUpdated time **/ private class MultipleDelayFilter implements SpoolRepository.AcceptFilter { /** * holds the time to wait for the youngest mail to get ready for processing **/ long youngest = 0; /** * Uses the getNextDelay to determine if a mail is ready for processing based on the delivered parameters * errorMessage (which holds the retrycount), lastUpdated and state * @param key the name/key of the message * @param state the mails state * @param lastUpdated the mail was last written to the spool at this time. * @param errorMessage actually holds the retrycount as a string (see failMessage below) **/ public boolean accept (String key, String state, long lastUpdated, String errorMessage) { if (state.equals(Mail.ERROR)) { //Test the time... int retries = Integer.parseInt(errorMessage); long delay = getNextDelay (retries); long timeToProcess = delay + lastUpdated; if (System.currentTimeMillis() > timeToProcess) { //We're ready to process this again return true; } else { //We're not ready to process this. if (youngest == 0 || youngest > timeToProcess) { //Mark this as the next most likely possible mail to process youngest = timeToProcess; } return false; } } else { //This mail is good to go... return the key return true; } } /** * @return the optimal time the SpoolRepository.accept(AcceptFilter) method should wait before * trying to find a mail ready for processing again. **/ public long getWaitTime () { if (youngest == 0) { return 0; } else { long duration = youngest - System.currentTimeMillis(); youngest = 0; //get ready for next run return duration <= 0 ? 1 : duration; } } } /** * Controls certain log messages */ private boolean isDebug = false; private SpoolRepository outgoing; // The spool of outgoing mail private long[] delayTimes; //holds expanded delayTimes private int maxRetries = 5; // default number of retries private long smtpTimeout = 600000; //default number of ms to timeout on smtp delivery private boolean sendPartial = false; // If false then ANY address errors will cause the transmission to fail private int connectionTimeout = 60000; // The amount of time JavaMail will wait before giving up on a socket connect() private int deliveryThreadCount = 1; // default number of delivery threads private Collection gatewayServer = null; // the server(s) to send all email to private boolean gatewaySsl = false; private String gatewayUser = null; private String gatewayPassword = null; private int gatewayPort; private String bindAddress = null; // JavaMail delivery socket binds to this local address. If null the JavaMail default will be used. private boolean isBindUsed = false; // true, if the bind configuration // parameter is supplied, RemoteDeliverySocketFactory // will be used in this case private Collection deliveryThreads = new Vector(); private MailServer mailServer; private volatile boolean destroyed = false; //Flag that the run method will check and end itself if set to true private String bounceProcessor = null; // the processor for creating Bounces private Perl5Matcher delayTimeMatcher; //matcher use at init time to parse delaytime parameters private MultipleDelayFilter delayFilter = new MultipleDelayFilter ();//used by accept to selcet the next mail ready for processing /** * Initialize the mailet */ public void init() throws MessagingException { isDebug = (getInitParameter("debug") == null) ? false : new Boolean(getInitParameter("debug")).booleanValue(); ArrayList delay_times_list = new ArrayList(); try { if (getInitParameter("delayTime") != null) { delayTimeMatcher = new Perl5Matcher(); String delay_times = getInitParameter("delayTime"); //split on comma's StringTokenizer st = new StringTokenizer (delay_times,","); while (st.hasMoreTokens()) { String delay_time = st.nextToken(); delay_times_list.add (new Delay(delay_time)); } } else { //use default delayTime. delay_times_list.add (new Delay()); } } catch (Exception e) { log("Invalid delayTime setting: " + getInitParameter("delayTime")); } try { if (getInitParameter("maxRetries") != null) { maxRetries = Integer.parseInt(getInitParameter("maxRetries")); } //check consistency with delay_times_list attempts int total_attempts = calcTotalAttempts (delay_times_list); if (total_attempts > maxRetries) { log("Total number of delayTime attempts exceeds maxRetries specified. Increasing maxRetries from "+maxRetries+" to "+total_attempts); maxRetries = total_attempts; } else { int extra = maxRetries - total_attempts; if (extra != 0) { log("maxRetries is larger than total number of attempts specified. Increasing last delayTime with "+extra+" attempts "); if (delay_times_list.size() != 0) { Delay delay = (Delay)delay_times_list.get (delay_times_list.size()-1); //last Delay delay.setAttempts (delay.getAttempts()+extra); log("Delay of "+delay.getDelayTime()+" msecs is now attempted: "+delay.getAttempts()+" times"); } else { log ("NO, delaytimes cannot continue"); } } } delayTimes = expandDelays (delay_times_list); } catch (Exception e) { log("Invalid maxRetries setting: " + getInitParameter("maxRetries")); } try { if (getInitParameter("timeout") != null) { smtpTimeout = Integer.parseInt(getInitParameter("timeout")); } } catch (Exception e) { log("Invalid timeout setting: " + getInitParameter("timeout")); } try { if (getInitParameter("connectiontimeout") != null) { connectionTimeout = Integer.parseInt(getInitParameter("connectiontimeout")); } } catch (Exception e) { log("Invalid timeout setting: " + getInitParameter("timeout")); } sendPartial = (getInitParameter("sendpartial") == null) ? false : new Boolean(getInitParameter("sendpartial")).booleanValue(); bounceProcessor = getInitParameter("bounceProcessor"); String gateway = getInitParameter("gateway"); if (gateway != null) { String gatewayPortAsString = getInitParameter("gatewayPort"); if ( gatewayPortAsString == null ) { gatewayPort = 25; // default smtp port } else { gatewayPort = Integer.parseInt( gatewayPortAsString ); } gatewayServer = new ArrayList(); StringTokenizer st = new StringTokenizer(gateway, ",") ; while (st.hasMoreTokens()) { String server = st.nextToken().trim() ; if (server.indexOf(':') < 0 && gatewayPortAsString != null) { server += ":"; server += gatewayPortAsString; } if (isDebug) log("Adding SMTP gateway: " + server) ; gatewayServer.add(server); } String gatewaySslAsString = getInitParameter( "gatewaySsl" ); if ( gatewaySslAsString != null && gatewaySslAsString.equalsIgnoreCase( "true" ) ) { gatewaySsl = true; } gatewayPassword = getInitParameter( "gatewayPassword" ); gatewayUser = getInitParameter( "gatewayUser" ); } ComponentManager compMgr = (ComponentManager)getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER); String outgoingPath = getInitParameter("outgoing"); if (outgoingPath == null) { outgoingPath = "file:///../var/mail/outgoing"; } try { // Instantiate the a MailRepository for outgoing mails MailStore mailstore = (MailStore) compMgr.lookup("org.apache.james.services.MailStore"); DefaultConfiguration spoolConf = new DefaultConfiguration("repository", "generated:RemoteDelivery.java"); spoolConf.setAttribute("destinationURL", outgoingPath); spoolConf.setAttribute("type", "SPOOL"); outgoing = (SpoolRepository) mailstore.select(spoolConf); } catch (ComponentException cnfe) { log("Failed to retrieve Store component:" + cnfe.getMessage()); } catch (Exception e) { log("Failed to retrieve Store component:" + e.getMessage()); } //Start up a number of threads try { deliveryThreadCount = Integer.parseInt(getInitParameter("deliveryThreads")); } catch (Exception e) { } for (int i = 0; i < deliveryThreadCount; i++) { StringBuffer nameBuffer = new StringBuffer(32) .append("Remote delivery thread (") .append(i) .append(")"); Thread t = new Thread(this, nameBuffer.toString()); t.start(); deliveryThreads.add(t); } bindAddress = getInitParameter("bind"); isBindUsed = bindAddress != null; // try { if (isBindUsed) { log( "bind is disabled" ); //RemoteDeliverySocketFactory.setBindAdress(bindAddress); } // } catch (UnknownHostException e) { // log("Invalid bind setting (" + bindAddress + "): " + e.toString()); // } } /** * We can assume that the recipients of this message are all going to the same * mail server. We will now rely on the DNS server to do DNS MX record lookup * and try to deliver to the multiple mail servers. If it fails, it should * throw an exception. * * Creation date: (2/24/00 11:25:00 PM) * @param mail org.apache.james.core.MailImpl * @param session javax.mail.Session * @return boolean Whether the delivery was successful and the message can be deleted */ private boolean deliver(MailImpl mail, Session session) { try { if (isDebug) { log("Attempting to deliver " + mail.getName()); } MimeMessage message = mail.getMessage(); //Create an array of the recipients as InternetAddress objects Collection recipients = mail.getRecipients(); InternetAddress addr[] = new InternetAddress[recipients.size()]; int j = 0; for (Iterator i = recipients.iterator(); i.hasNext(); j++) { MailAddress rcpt = (MailAddress)i.next(); addr[j] = rcpt.toInternetAddress(); } if (addr.length <= 0) { log("No recipients specified... not sure how this could have happened."); return true; } //Figure out which servers to try to send to. This collection // will hold all the possible target servers Iterator targetServers = null; if (gatewayServer == null) { MailAddress rcpt = (MailAddress) recipients.iterator().next(); String host = rcpt.getHost(); //Lookup the possible targets targetServers = getMailetContext().getSMTPHostAddresses(host); if (!targetServers.hasNext()) { log("No mail server found for: " + host); StringBuffer exceptionBuffer = new StringBuffer(128) .append("There are no DNS entries for the hostname ") .append(host) .append(". I cannot determine where to send this message."); return failMessage(mail, new MessagingException(exceptionBuffer.toString()), false); } } else { targetServers = getGatewaySMTPHostAddresses(gatewayServer); } MessagingException lastError = null; while ( targetServers.hasNext()) { try { HostAddress outgoingMailServer = (HostAddress) targetServers.next(); StringBuffer logMessageBuffer = new StringBuffer(256) .append("Attempting delivery of ") .append(mail.getName()) .append(" to host ") .append(outgoingMailServer.getHostName()) .append(" at ") .append(outgoingMailServer.getHost()) .append(" to addresses ") .append(Arrays.asList(addr)); log(logMessageBuffer.toString()); Properties props = session.getProperties(); if (mail.getSender() == null) { props.put("mail.smtp.from", "<>"); } else { String sender = mail.getSender().toString(); props.put("mail.smtp.from", sender); } //Many of these properties are only in later JavaMail versions //"mail.smtp.ehlo" //default true //"mail.smtp.auth" //default false //"mail.smtp.dsn.ret" //default to nothing... appended as RET= after MAIL FROM line. //"mail.smtp.dsn.notify" //default to nothing...appended as NOTIFY= after RCPT TO line. Transport transport = null; try { transport = session.getTransport(outgoingMailServer); try { transport.connect(); } catch (MessagingException me) { // Any error on connect should cause the mailet to attempt // to connect to the next SMTP server associated with this // MX record. Just log the exception. We'll worry about // failing the message at the end of the loop. log(me.getMessage()); continue; } transport.sendMessage(message, addr); } finally { if (transport != null) { transport.close(); transport = null; } } logMessageBuffer = new StringBuffer(256) .append("Mail (") .append(mail.getName()) .append(") sent successfully to ") .append(outgoingMailServer.getHostName()) .append(" at ") .append(outgoingMailServer.getHost()); log(logMessageBuffer.toString()); return true; } catch (SendFailedException sfe) { if (sfe.getValidSentAddresses() == null || sfe.getValidSentAddresses().length < 1) { if (isDebug) log("Send failed, continuing with any other servers"); lastError = sfe; continue; } else { // If any mail was sent then the outgoing // server config must be ok, therefore rethrow throw sfe; } } catch (MessagingException me) { //MessagingException are horribly difficult to figure out what actually happened. StringBuffer exceptionBuffer = new StringBuffer(256) .append("Exception delivering message (") .append(mail.getName()) .append(") - ") .append(me.getMessage()); log(exceptionBuffer.toString()); if ((me.getNextException() != null) && (me.getNextException() instanceof java.io.IOException)) { //This is more than likely a temporary failure // If it's an IO exception with no nested exception, it's probably // some socket or weird I/O related problem. lastError = me; continue; } // This was not a connection or I/O error particular to one // SMTP server of an MX set. Instead, it is almost certainly // a protocol level error. In this case we assume that this // is an error we'd encounter with any of the SMTP servers // associated with this MX record, and we pass the exception // to the code in the outer block that determines its severity. throw me; } } // end while //If we encountered an exception while looping through, //throw the last MessagingException we caught. We only //do this if we were unable to send the message to any //server. If sending eventually succeeded, we exit //deliver() though the return at the end of the try //block. if (lastError != null) { throw lastError; } } catch (SendFailedException sfe) { boolean deleteMessage = false; Collection recipients = mail.getRecipients(); //Would like to log all the types of email addresses if (isDebug) log("Recipients: " + recipients); /* if (sfe.getValidSentAddresses() != null) { Address[] validSent = sfe.getValidSentAddresses(); Collection recipients = mail.getRecipients(); //Remove these addresses for the recipients for (int i = 0; i < validSent.length; i++) { try { MailAddress addr = new MailAddress(validSent[i].toString()); recipients.remove(addr); } catch (ParseException pe) { //ignore once debugging done pe.printStackTrace(); } } } */ /* * The rest of the recipients failed for one reason or * another. * * SendFailedException actually handles this for us. For * example, if you send a message that has multiple invalid * addresses, you'll get a top-level SendFailedException * that that has the valid, valid-unsent, and invalid * address lists, with all of the server response messages * will be contained within the nested exceptions. [Note: * the content of the nested exceptions is implementation * dependent.] * * sfe.getInvalidAddresses() should be considered permanent. * sfe.getValidUnsentAddresses() should be considered temporary. * * JavaMail v1.3 properly populates those collections based * upon the 4xx and 5xx response codes. * */ if (sfe.getInvalidAddresses() != null) { Address[] address = sfe.getInvalidAddresses(); if (address.length > 0) { recipients.clear(); for (int i = 0; i < address.length; i++) { try { recipients.add(new MailAddress(address[i].toString())); } catch (ParseException pe) { // this should never happen ... we should have // caught malformed addresses long before we // got to this code. log("Can't parse invalid address: " + pe.getMessage()); } } if (isDebug) log("Invalid recipients: " + recipients); deleteMessage = failMessage(mail, sfe, true); } } if (sfe.getValidUnsentAddresses() != null) { Address[] address = sfe.getValidUnsentAddresses(); if (address.length > 0) { recipients.clear(); for (int i = 0; i < address.length; i++) { try { recipients.add(new MailAddress(address[i].toString())); } catch (ParseException pe) { // this should never happen ... we should have // caught malformed addresses long before we // got to this code. log("Can't parse unsent address: " + pe.getMessage()); } } if (isDebug) log("Unsent recipients: " + recipients); deleteMessage = failMessage(mail, sfe, false); } } return deleteMessage; } catch (MessagingException ex) { // We should do a better job checking this... if the failure is a general // connect exception, this is less descriptive than more specific SMTP command // failure... have to lookup and see what are the various Exception // possibilities // Unable to deliver message after numerous tries... fail accordingly // We check whether this is a 5xx error message, which // indicates a permanent failure (like account doesn't exist // or mailbox is full or domain is setup wrong). // We fail permanently if this was a 5xx error return failMessage(mail, ex, ('5' == ex.getMessage().charAt(0))); } /* If we get here, we've exhausted the loop of servers without * sending the message or throwing an exception. One case * where this might happen is if we get a MessagingException on * each transport.connect(), e.g., if there is only one server * and we get a connect exception. */ return failMessage(mail, new MessagingException("No mail server(s) available at this time."), false); } /** * Insert the method's description here. * Creation date: (2/25/00 1:14:18 AM) * @param mail org.apache.james.core.MailImpl * @param exception javax.mail.MessagingException * @param boolean permanent * @return boolean Whether the message failed fully and can be deleted */ private boolean failMessage(MailImpl mail, MessagingException ex, boolean permanent) { StringWriter sout = new StringWriter(); PrintWriter out = new PrintWriter(sout, true); if (permanent) { out.print("Permanent"); } else { out.print("Temporary"); } StringBuffer logBuffer = new StringBuffer(64) .append(" exception delivering mail (") .append(mail.getName()) .append(": "); out.print(logBuffer.toString()); ex.printStackTrace(out); log(sout.toString()); if (!permanent) { if (!mail.getState().equals(Mail.ERROR)) { mail.setState(Mail.ERROR); mail.setErrorMessage("0"); mail.setLastUpdated(new Date()); } int retries = Integer.parseInt(mail.getErrorMessage()); if (retries < maxRetries) { logBuffer = new StringBuffer(128) .append("Storing message ") .append(mail.getName()) .append(" into outgoing after ") .append(retries) .append(" retries"); log(logBuffer.toString()); ++retries; mail.setErrorMessage(retries + ""); mail.setLastUpdated(new Date()); return false; } else { logBuffer = new StringBuffer(128) .append("Bouncing message ") .append(mail.getName()) .append(" after ") .append(retries) .append(" retries"); log(logBuffer.toString()); } } if (bounceProcessor != null) { // do the new DSN bounce // setting attributes for DSN mailet mail.setAttribute("delivery-error", ex); mail.setState(bounceProcessor); // re-insert the mail into the spool for getting it passed to the dsn-processor MailetContext mc = getMailetContext(); try { mc.sendMail(mail); } catch (MessagingException e) { // we shouldn't get an exception, because the mail was already processed log("Exception re-inserting failed mail: ", e); } } else { // do an old style bounce bounce(mail, ex); } return true; } private void bounce(MailImpl mail, MessagingException ex) { StringWriter sout = new StringWriter(); PrintWriter out = new PrintWriter(sout, true); String machine = "[unknown]"; try { InetAddress me = InetAddress.getLocalHost(); machine = me.getHostName(); } catch(Exception e){ machine = "[address unknown]"; } StringBuffer bounceBuffer = new StringBuffer(128) .append("Hi. This is the James mail server at ") .append(machine) .append("."); out.println(bounceBuffer.toString()); out.println("I'm afraid I wasn't able to deliver your message to the following addresses."); out.println("This is a permanent error; I've given up. Sorry it didn't work out. Below"); out.println("I include the list of recipients and the reason why I was unable to deliver"); out.println("your message."); out.println(); for (Iterator i = mail.getRecipients().iterator(); i.hasNext(); ) { out.println(i.next()); } if (ex.getNextException() == null) { out.println(ex.getMessage().trim()); } else { Exception ex1 = ex.getNextException(); if (ex1 instanceof SendFailedException) { out.println("Remote mail server told me: " + ex1.getMessage().trim()); } else if (ex1 instanceof UnknownHostException) { out.println("Unknown host: " + ex1.getMessage().trim()); out.println("This could be a DNS server error, a typo, or a problem with the recipient's mail server."); } else if (ex1 instanceof ConnectException) { //Already formatted as "Connection timed out: connect" out.println(ex1.getMessage().trim()); } else if (ex1 instanceof SocketException) { out.println("Socket exception: " + ex1.getMessage().trim()); } else { out.println(ex1.getMessage().trim()); } } out.println(); out.println("The original message is attached."); log("Sending failure message " + mail.getName()); try { getMailetContext().bounce(mail, sout.toString()); } catch (MessagingException me) { log("Encountered unexpected messaging exception while bouncing message: " + me.getMessage()); } catch (Exception e) { log("Encountered unexpected exception while bouncing message: " + e.getMessage()); } } public String getMailetInfo() { return "RemoteDelivery Mailet"; } /** * For this message, we take the list of recipients, organize these into distinct * servers, and duplicate the message for each of these servers, and then call * the deliver (messagecontainer) method for each server-specific * messagecontainer ... that will handle storing it in the outgoing queue if needed. * * @param mail org.apache.mailet.Mail */ public void service(Mail genericmail) throws MessagingException{ MailImpl mail = (MailImpl)genericmail; // Do I want to give the internal key, or the message's Message ID if (isDebug) { log("Remotely delivering mail " + mail.getName()); } Collection recipients = mail.getRecipients(); if (gatewayServer == null) { // Must first organize the recipients into distinct servers (name made case insensitive) Hashtable targets = new Hashtable(); for (Iterator i = recipients.iterator(); i.hasNext();) { MailAddress target = (MailAddress)i.next(); String targetServer = target.getHost().toLowerCase(Locale.US); Collection temp = (Collection)targets.get(targetServer); if (temp == null) { temp = new ArrayList(); targets.put(targetServer, temp); } temp.add(target); } //We have the recipients organized into distinct servers... put them into the //delivery store organized like this... this is ultra inefficient I think... // Store the new message containers, organized by server, in the outgoing mail repository String name = mail.getName(); for (Iterator i = targets.keySet().iterator(); i.hasNext(); ) { String host = (String) i.next(); Collection rec = (Collection) targets.get(host); if (isDebug) { StringBuffer logMessageBuffer = new StringBuffer(128) .append("Sending mail to ") .append(rec) .append(" on host ") .append(host); log(logMessageBuffer.toString()); } mail.setRecipients(rec); StringBuffer nameBuffer = new StringBuffer(128) .append(name) .append("-to-") .append(host); mail.setName(nameBuffer.toString()); outgoing.store(mail); //Set it to try to deliver (in a separate thread) immediately (triggered by storage) } } else { // Store the mail unaltered for processing by the gateway server(s) if (isDebug) { StringBuffer logMessageBuffer = new StringBuffer(128) .append("Sending mail to ") .append(mail.getRecipients()) .append(" via ") .append(gatewayServer); log(logMessageBuffer.toString()); } //Set it to try to deliver (in a separate thread) immediately (triggered by storage) outgoing.store(mail); } mail.setState(Mail.GHOST); } // Need to synchronize to get object monitor for notifyAll() public synchronized void destroy() { //Mark flag so threads from this mailet stop themselves destroyed = true; //Wake up all threads from waiting for an accept for (Iterator i = deliveryThreads.iterator(); i.hasNext(); ) { Thread t = (Thread)i.next(); t.interrupt(); } notifyAll(); } /** * Handles checking the outgoing spool for new mail and delivering them if * there are any */ public void run() { /* todo: CHANGE ME!!! The problem is that we need to wait for James to * finish initializing. We expect the HELLO_NAME to be put into * the MailetContext, but in the current configuration we get * started before the SMTP Server, which establishes the value. * Since there is no contractual guarantee that there will be a * HELLO_NAME value, we can't just wait for it. As a temporary * measure, I'm inserting this philosophically unsatisfactory * fix. */ long stop = System.currentTimeMillis() + 60000; while ((getMailetContext().getAttribute(Constants.HELLO_NAME) == null) && stop > System.currentTimeMillis()) { try { Thread.sleep(1000); } catch (Exception ignored) {} // wait for James to finish initializing } //Checks the pool and delivers a mail message Properties props = new Properties(); //Not needed for production environment props.put("mail.debug", "false"); //Prevents problems encountered with 250 OK Messages props.put("mail.smtp.ehlo", "false"); //Sets timeout on going connections props.put("mail.smtp.timeout", smtpTimeout + ""); props.put("mail.smtp.connectiontimeout", connectionTimeout + ""); props.put("mail.smtp.sendpartial",String.valueOf(sendPartial)); //Set the hostname we'll use as this server if (getMailetContext().getAttribute(Constants.HELLO_NAME) != null) { props.put("mail.smtp.localhost", (String) getMailetContext().getAttribute(Constants.HELLO_NAME)); } else { Collection servernames = (Collection) getMailetContext().getAttribute(Constants.SERVER_NAMES); if ((servernames != null) && (servernames.size() > 0)) { props.put("mail.smtp.localhost", (String) servernames.iterator().next()); } } if ( isBindUsed && gatewaySsl ) { log( "cannot use bind and gatewayssl together gatewayssl forced to be false" ); } if (isBindUsed) { // undocumented JavaMail 1.2 feature, smtp transport will use // our socket factory, which will also set the local address props.put("mail.smtp.socketFactory.class", "org.apache.james.transport.mailets.RemoteDeliverySocketFactory"); // Don't fallback to the standard socket factory on error, do throw an exception props.put("mail.smtp.socketFactory.fallback", "false"); } else if ( gatewaySsl ) { props.put("mail.smtp.socketFactory.class", TrustEverythingSslSocketFactory.class.getName() ); // Don't fallback to the standard socket factory on error, do throw an exception props.put("mail.smtp.socketFactory.fallback", "false"); props.put("mail.smtp.socketFactory.port", String.valueOf( gatewayPort ) ); } if ( gatewayUser != null && gatewayPassword != null ) { Authenticator authenticator = new Authenticator() { protected PasswordAuthentication getPasswordAuthentication() { return new PasswordAuthentication( gatewayUser, gatewayPassword ); } }; } Session session = Session.getInstance(props, null); try { while (!Thread.interrupted() && !destroyed) { try { MailImpl mail = (MailImpl)outgoing.accept(delayFilter); String key = mail.getName(); try { if (isDebug) { StringBuffer logMessageBuffer = new StringBuffer(128) .append(Thread.currentThread().getName()) .append(" will process mail ") .append(key); log(logMessageBuffer.toString()); } if (deliver(mail, session)) { //Message was successfully delivered/fully failed... delete it outgoing.remove(key); } else { //Something happened that will delay delivery. Store any updates outgoing.store(mail); } //Clear the object handle to make sure it recycles this object. mail = null; } catch (Exception e) { // Prevent unexpected exceptions from causing looping by removing // message from outgoing. outgoing.remove(key); throw e; } } catch (Throwable e) { if (!destroyed) log("Exception caught in RemoteDelivery.run()", e); } } } finally { // Restore the thread state to non-interrupted. Thread.interrupted(); } } /** * @param list holding Delay objects * @return the total attempts for all delays **/ private int calcTotalAttempts (ArrayList list) { int sum = 0; Iterator i = list.iterator(); while (i.hasNext()) { Delay delay = (Delay)i.next(); sum += delay.getAttempts(); } return sum; } /** * This method expands an ArrayList containing Delay objects into an array holding the * only delaytime in the order.<p> * So if the list has 2 Delay objects the first having attempts=2 and delaytime 4000 * the second having attempts=1 and delaytime=300000 will be expanded into this array:<p> * long[0] = 4000<p> * long[1] = 4000<p> * long[2] = 300000<p> * @param list the list to expand * @return the expanded list **/ private long[] expandDelays (ArrayList list) { long[] delays = new long [calcTotalAttempts(list)]; Iterator i = list.iterator(); int idx = 0; while (i.hasNext()) { Delay delay = (Delay)i.next(); for (int j=0; j<delay.getAttempts(); j++) { delays[idx++]= delay.getDelayTime(); } } return delays; } /** * This method returns, given a retry-count, the next delay time to use. * @param retry_count the current retry_count. * @return the next delay time to use, given the retry count **/ private long getNextDelay (int retry_count) { return delayTimes[retry_count-1]; } /** * This class is used to hold a delay time and its corresponding number * of retries. **/ private class Delay { private int attempts = 1; private long delayTime = DEFAULT_DELAY_TIME; /** * This constructor expects Strings of the form "[attempt\*]delaytime[unit]". <p> * The optional attempt is the number of tries this delay should be used (default = 1) * The unit if present must be one of (msec,sec,minute,hour,day) (default = msec) * The constructor multiplies the delaytime by the relevant multiplier for the unit, * so the delayTime instance variable is always in msec. * @param init_string the string to initialize this Delay object from **/ public Delay (String init_string) throws MessagingException { String unit = "msec"; //default unit if (delayTimeMatcher.matches (init_string, PATTERN)) { MatchResult res = delayTimeMatcher.getMatch (); //the capturing groups will now hold //at 1: attempts * (if present) //at 2: delaytime //at 3: unit (if present) if (res.group(1) != null && !res.group(1).equals ("")) { //we have an attempt * String attempt_match = res.group(1); //strip the * and whitespace attempt_match = attempt_match.substring (0,attempt_match.length()-1).trim(); attempts = Integer.parseInt (attempt_match); } delayTime = Long.parseLong (res.group(2)); if (!res.group(3).equals ("")) { //we have a unit unit = res.group(3).toLowerCase(); } } else { throw new MessagingException(init_string+" does not match "+PATTERN_STRING); } if (MULTIPLIERS.get (unit)!=null) { int multiplier = ((Integer)MULTIPLIERS.get (unit)).intValue(); delayTime *= multiplier; } else { throw new MessagingException("Unknown unit: "+unit); } } /** * This constructor makes a default Delay object, ie. attempts=1 and delayTime=DEFAULT_DELAY_TIME **/ public Delay () { } /** * @return the delayTime for this Delay **/ public long getDelayTime () { return delayTime; } /** * @return the number attempts this Delay should be used. **/ public int getAttempts () { return attempts; } /** * Set the number attempts this Delay should be used. **/ public void setAttempts (int value) { attempts = value; } /** * Pretty prints this Delay **/ public String toString () { StringBuffer buf = new StringBuffer(15); buf.append (getAttempts ()); buf.append ('*'); buf.append (getDelayTime()); buf.append ("msec"); return buf.toString(); } } /* * Returns an Iterator over org.apache.mailet.HostAddress, a * specialized subclass of javax.mail.URLName, which provides * location information for servers that are specified as mail * handlers for the given hostname. If no host is found, the * Iterator returned will be empty and the first call to hasNext() * will return false. The Iterator is a nested iterator: the outer * iteration is over each gateway, and the inner iteration is over * potentially multiple A records for each gateway. * * @see org.apache.james.DNSServer#getSMTPHostAddresses(String) * @since v2.2.0a16-unstable * @param gatewayServers - Collection of host[:port] Strings * @return an Iterator over HostAddress instances, sorted by priority */ private Iterator getGatewaySMTPHostAddresses(final Collection gatewayServers) { return new Iterator() { private Iterator gateways = gatewayServers.iterator(); private Iterator addresses = null; public boolean hasNext() { return gateways.hasNext(); } public Object next() { if (addresses == null || !addresses.hasNext()) { String server = (String) gateways.next(); String port = "25"; int idx = server.indexOf(':'); if ( idx > 0) { port = server.substring(idx+1); server = server.substring(0,idx); } final String nextGateway = server; final String nextGatewayPort = port; try { final InetAddress[] ips = org.apache.james.dnsserver.DNSServer.getAllByName(nextGateway); addresses = new Iterator() { private InetAddress[] ipAddresses = ips; int i = 0; public boolean hasNext() { return i < ipAddresses.length; } public Object next() { return new org.apache.mailet.HostAddress(nextGateway, "smtp://" + (ipAddresses[i++]).getHostAddress() + ":" + nextGatewayPort); } public void remove() { throw new UnsupportedOperationException ("remove not supported by this iterator"); } }; } catch (java.net.UnknownHostException uhe) { log("Unknown gateway host: " + uhe.getMessage().trim()); log("This could be a DNS server error or configuration error."); } } return (addresses != null) ? addresses.next() : null; } public void remove() { throw new UnsupportedOperationException ("remove not supported by this iterator"); } }; } }
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]