| 1 17 18 package org.apache.james.transport.mailets; 19 20 import java.io.PrintWriter ; 21 import java.io.StringWriter ; 22 import java.net.ConnectException ; 23 import java.net.InetAddress ; 24 import java.net.SocketException ; 25 import java.net.UnknownHostException ; 26 import java.util.Arrays ; 27 import java.util.Collection ; 28 import java.util.Date ; 29 import java.util.Hashtable ; 30 import java.util.HashMap ; 31 import java.util.Iterator ; 32 import java.util.Locale ; 33 import java.util.Properties ; 34 import java.util.StringTokenizer ; 35 import java.util.Vector ; 36 import java.util.ArrayList ; 37 38 import javax.mail.Address ; 39 import javax.mail.MessagingException ; 40 import javax.mail.SendFailedException ; 41 import javax.mail.Session ; 42 import javax.mail.Transport ; 43 import javax.mail.URLName ; 44 import javax.mail.internet.AddressException ; 45 import javax.mail.internet.InternetAddress ; 46 import javax.mail.internet.MimeMessage ; 47 import javax.mail.internet.ParseException ; 48 49 import org.apache.avalon.framework.component.ComponentException; 50 import org.apache.avalon.framework.component.ComponentManager; 51 import org.apache.avalon.framework.configuration.DefaultConfiguration; 52 import org.apache.james.Constants; 53 import org.apache.james.core.MailImpl; 54 import org.apache.james.services.MailServer; 55 import org.apache.james.services.MailStore; 56 import org.apache.james.services.SpoolRepository; 57 import org.apache.mailet.MailetContext; 58 import org.apache.mailet.GenericMailet; 59 import org.apache.mailet.HostAddress; 60 import org.apache.mailet.Mail; 61 import org.apache.mailet.MailAddress; 62 63 import org.apache.oro.text.regex.MalformedPatternException; 64 import org.apache.oro.text.regex.Pattern; 65 import org.apache.oro.text.regex.Perl5Compiler; 66 import org.apache.oro.text.regex.Perl5Matcher; 67 import org.apache.oro.text.regex.MatchResult; 68 69 70 93 public class RemoteDelivery extends GenericMailet implements Runnable { 94 95 private static final long DEFAULT_DELAY_TIME = 21600000; private static final String PATTERN_STRING = 97 "\\s*([0-9]*\\s*[\\*])?\\s*([0-9]+)\\s*([a-z,A-Z]*)\\s*"; 100 private static Pattern PATTERN = null; private static final HashMap MULTIPLIERS = new HashMap (10); 104 109 static { 110 try { 111 Perl5Compiler compiler = new Perl5Compiler(); 112 PATTERN = compiler.compile(PATTERN_STRING, Perl5Compiler.READ_ONLY_MASK); 113 } catch(MalformedPatternException mpe) { 114 System.err.println ("Malformed pattern: " + PATTERN_STRING); 116 mpe.printStackTrace (System.err); 117 } 118 MULTIPLIERS.put ("msec", new Integer (1)); 120 MULTIPLIERS.put ("msecs", new Integer (1)); 121 MULTIPLIERS.put ("sec", new Integer (1000)); 122 MULTIPLIERS.put ("secs", new Integer (1000)); 123 MULTIPLIERS.put ("minute", new Integer (1000*60)); 124 MULTIPLIERS.put ("minutes", new Integer (1000*60)); 125 MULTIPLIERS.put ("hour", new Integer (1000*60*60)); 126 MULTIPLIERS.put ("hours", new Integer (1000*60*60)); 127 MULTIPLIERS.put ("day", new Integer (1000*60*60*24)); 128 MULTIPLIERS.put ("days", new Integer (1000*60*60*24)); 129 } 130 131 136 private class MultipleDelayFilter implements SpoolRepository.AcceptFilter 137 { 138 141 long youngest = 0; 142 143 151 public boolean accept (String key, String state, long lastUpdated, String errorMessage) { 152 if (state.equals(Mail.ERROR)) { 153 int retries = Integer.parseInt(errorMessage); 155 long delay = getNextDelay (retries); 156 long timeToProcess = delay + lastUpdated; 157 158 159 if (System.currentTimeMillis() > timeToProcess) { 160 return true; 162 } else { 163 if (youngest == 0 || youngest > timeToProcess) { 165 youngest = timeToProcess; 167 } 168 return false; 169 } 170 } else { 171 return true; 173 } 174 } 175 176 180 public long getWaitTime () { 181 if (youngest == 0) { 182 return 0; 183 } else { 184 long duration = youngest - System.currentTimeMillis(); 185 youngest = 0; return duration <= 0 ? 1 : duration; 187 } 188 } 189 } 190 191 194 private boolean isDebug = false; 195 196 private SpoolRepository outgoing; private long[] delayTimes; private int maxRetries = 5; private long smtpTimeout = 600000; private boolean sendPartial = false; private int connectionTimeout = 60000; private int deliveryThreadCount = 1; private Collection gatewayServer = null; private String bindAddress = null; private boolean isBindUsed = false; private Collection deliveryThreads = new Vector (); 209 private MailServer mailServer; 210 private volatile boolean destroyed = false; private String bounceProcessor = null; 213 private Perl5Matcher delayTimeMatcher; private MultipleDelayFilter delayFilter = new MultipleDelayFilter (); 216 219 public void init() throws MessagingException { 220 isDebug = (getInitParameter("debug") == null) ? false : new Boolean (getInitParameter("debug")).booleanValue(); 221 ArrayList delay_times_list = new ArrayList (); 222 try { 223 if (getInitParameter("delayTime") != null) { 224 delayTimeMatcher = new Perl5Matcher(); 225 String delay_times = getInitParameter("delayTime"); 226 StringTokenizer st = new StringTokenizer (delay_times,","); 228 while (st.hasMoreTokens()) { 229 String delay_time = st.nextToken(); 230 delay_times_list.add (new Delay(delay_time)); 231 } 232 } else { 233 delay_times_list.add (new Delay()); 235 } 236 } catch (Exception e) { 237 log("Invalid delayTime setting: " + getInitParameter("delayTime")); 238 } 239 try { 240 if (getInitParameter("maxRetries") != null) { 241 maxRetries = Integer.parseInt(getInitParameter("maxRetries")); 242 } 243 int total_attempts = calcTotalAttempts (delay_times_list); 245 if (total_attempts > maxRetries) { 246 log("Total number of delayTime attempts exceeds maxRetries specified. Increasing maxRetries from "+maxRetries+" to "+total_attempts); 247 maxRetries = total_attempts; 248 } else { 249 int extra = maxRetries - total_attempts; 250 if (extra != 0) { 251 log("maxRetries is larger than total number of attempts specified. Increasing last delayTime with "+extra+" attempts "); 252 253 if (delay_times_list.size() != 0) { 254 Delay delay = (Delay)delay_times_list.get (delay_times_list.size()-1); delay.setAttempts (delay.getAttempts()+extra); 256 log("Delay of "+delay.getDelayTime()+" msecs is now attempted: "+delay.getAttempts()+" times"); 257 } else { 258 log ("NO, delaytimes cannot continue"); 259 } 260 } 261 } 262 delayTimes = expandDelays (delay_times_list); 263 264 } catch (Exception e) { 265 log("Invalid maxRetries setting: " + getInitParameter("maxRetries")); 266 } 267 try { 268 if (getInitParameter("timeout") != null) { 269 smtpTimeout = Integer.parseInt(getInitParameter("timeout")); 270 } 271 } catch (Exception e) { 272 log("Invalid timeout setting: " + getInitParameter("timeout")); 273 } 274 275 try { 276 if (getInitParameter("connectiontimeout") != null) { 277 connectionTimeout = Integer.parseInt(getInitParameter("connectiontimeout")); 278 } 279 } catch (Exception e) { 280 log("Invalid timeout setting: " + getInitParameter("timeout")); 281 } 282 sendPartial = (getInitParameter("sendpartial") == null) ? false : new Boolean (getInitParameter("sendpartial")).booleanValue(); 283 284 bounceProcessor = getInitParameter("bounceProcessor"); 285 286 String gateway = getInitParameter("gateway"); 287 String gatewayPort = getInitParameter("gatewayPort"); 288 289 if (gateway != null) { 290 gatewayServer = new ArrayList (); 291 StringTokenizer st = new StringTokenizer (gateway, ",") ; 292 while (st.hasMoreTokens()) { 293 String server = st.nextToken().trim() ; 294 if (server.indexOf(':') < 0 && gatewayPort != null) { 295 server += ":"; 296 server += gatewayPort; 297 } 298 299 if (isDebug) log("Adding SMTP gateway: " + server) ; 300 gatewayServer.add(server); 301 } 302 } 303 304 ComponentManager compMgr = (ComponentManager)getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER); 305 String outgoingPath = getInitParameter("outgoing"); 306 if (outgoingPath == null) { 307 outgoingPath = "file:///../var/mail/outgoing"; 308 } 309 310 try { 311 MailStore mailstore = (MailStore) compMgr.lookup("org.apache.james.services.MailStore"); 313 314 DefaultConfiguration spoolConf 315 = new DefaultConfiguration("repository", "generated:RemoteDelivery.java"); 316 spoolConf.setAttribute("destinationURL", outgoingPath); 317 spoolConf.setAttribute("type", "SPOOL"); 318 outgoing = (SpoolRepository) mailstore.select(spoolConf); 319 } catch (ComponentException cnfe) { 320 log("Failed to retrieve Store component:" + cnfe.getMessage()); 321 } catch (Exception e) { 322 log("Failed to retrieve Store component:" + e.getMessage()); 323 } 324 325 try { 327 deliveryThreadCount = Integer.parseInt(getInitParameter("deliveryThreads")); 328 } catch (Exception e) { 329 } 330 for (int i = 0; i < deliveryThreadCount; i++) { 331 StringBuffer nameBuffer = 332 new StringBuffer (32) 333 .append("Remote delivery thread (") 334 .append(i) 335 .append(")"); 336 Thread t = new Thread (this, nameBuffer.toString()); 337 t.start(); 338 deliveryThreads.add(t); 339 } 340 341 bindAddress = getInitParameter("bind"); 342 isBindUsed = bindAddress != null; 343 try { 344 if (isBindUsed) RemoteDeliverySocketFactory.setBindAdress(bindAddress); 345 } catch (UnknownHostException e) { 346 log("Invalid bind setting (" + bindAddress + "): " + e.toString()); 347 } 348 } 349 350 361 private boolean deliver(MailImpl mail, Session session) { 362 try { 363 if (isDebug) { 364 log("Attempting to deliver " + mail.getName()); 365 } 366 MimeMessage message = mail.getMessage(); 367 368 Collection recipients = mail.getRecipients(); 370 InternetAddress addr[] = new InternetAddress [recipients.size()]; 371 int j = 0; 372 for (Iterator i = recipients.iterator(); i.hasNext(); j++) { 373 MailAddress rcpt = (MailAddress)i.next(); 374 addr[j] = rcpt.toInternetAddress(); 375 } 376 377 if (addr.length <= 0) { 378 log("No recipients specified... not sure how this could have happened."); 379 return true; 380 } 381 382 Iterator targetServers = null; 385 if (gatewayServer == null) { 386 MailAddress rcpt = (MailAddress) recipients.iterator().next(); 387 String host = rcpt.getHost(); 388 389 targetServers = getMailetContext().getSMTPHostAddresses(host); 391 if (!targetServers.hasNext()) { 392 log("No mail server found for: " + host); 393 StringBuffer exceptionBuffer = 394 new StringBuffer (128) 395 .append("There are no DNS entries for the hostname ") 396 .append(host) 397 .append(". I cannot determine where to send this message."); 398 return failMessage(mail, new MessagingException (exceptionBuffer.toString()), false); 399 } 400 } else { 401 targetServers = getGatewaySMTPHostAddresses(gatewayServer); 402 } 403 404 MessagingException lastError = null; 405 406 while ( targetServers.hasNext()) { 407 try { 408 HostAddress outgoingMailServer = (HostAddress) targetServers.next(); 409 StringBuffer logMessageBuffer = 410 new StringBuffer (256) 411 .append("Attempting delivery of ") 412 .append(mail.getName()) 413 .append(" to host ") 414 .append(outgoingMailServer.getHostName()) 415 .append(" at ") 416 .append(outgoingMailServer.getHost()) 417 .append(" to addresses ") 418 .append(Arrays.asList(addr)); 419 log(logMessageBuffer.toString()); 420 421 Properties props = session.getProperties(); 422 if (mail.getSender() == null) { 423 props.put("mail.smtp.from", "<>"); 424 } else { 425 String sender = mail.getSender().toString(); 426 props.put("mail.smtp.from", sender); 427 } 428 429 435 Transport transport = null; 436 try { 437 transport = session.getTransport(outgoingMailServer); 438 try { 439 transport.connect(); 440 } catch (MessagingException me) { 441 log(me.getMessage()); 446 continue; 447 } 448 transport.sendMessage(message, addr); 449 } finally { 450 if (transport != null) { 451 transport.close(); 452 transport = null; 453 } 454 } 455 logMessageBuffer = 456 new StringBuffer (256) 457 .append("Mail (") 458 .append(mail.getName()) 459 .append(") sent successfully to ") 460 .append(outgoingMailServer.getHostName()) 461 .append(" at ") 462 .append(outgoingMailServer.getHost()); 463 log(logMessageBuffer.toString()); 464 return true; 465 } catch (SendFailedException sfe) { 466 if (sfe.getValidSentAddresses() == null 467 || sfe.getValidSentAddresses().length < 1) { 468 if (isDebug) log("Send failed, continuing with any other servers"); 469 lastError = sfe; 470 continue; 471 } else { 472 throw sfe; 475 } 476 } catch (MessagingException me) { 477 StringBuffer exceptionBuffer = 479 new StringBuffer (256) 480 .append("Exception delivering message (") 481 .append(mail.getName()) 482 .append(") - ") 483 .append(me.getMessage()); 484 log(exceptionBuffer.toString()); 485 if ((me.getNextException() != null) && 486 (me.getNextException() instanceof java.io.IOException )) { 487 489 lastError = me; 492 continue; 493 } 494 throw me; 501 } 502 } if (lastError != null) { 510 throw lastError; 511 } 512 } catch (SendFailedException sfe) { 513 boolean deleteMessage = false; 514 Collection recipients = mail.getRecipients(); 515 516 if (isDebug) log("Recipients: " + recipients); 518 519 535 536 556 557 if (sfe.getInvalidAddresses() != null) { 558 Address [] address = sfe.getInvalidAddresses(); 559 if (address.length > 0) { 560 recipients.clear(); 561 for (int i = 0; i < address.length; i++) { 562 try { 563 recipients.add(new MailAddress(address[i].toString())); 564 } catch (ParseException pe) { 565 log("Can't parse invalid address: " + pe.getMessage()); 569 } 570 } 571 if (isDebug) log("Invalid recipients: " + recipients); 572 deleteMessage = failMessage(mail, sfe, true); 573 } 574 } 575 576 if (sfe.getValidUnsentAddresses() != null) { 577 Address [] address = sfe.getValidUnsentAddresses(); 578 if (address.length > 0) { 579 recipients.clear(); 580 for (int i = 0; i < address.length; i++) { 581 try { 582 recipients.add(new MailAddress(address[i].toString())); 583 } catch (ParseException pe) { 584 log("Can't parse unsent address: " + pe.getMessage()); 588 } 589 } 590 if (isDebug) log("Unsent recipients: " + recipients); 591 deleteMessage = failMessage(mail, sfe, false); 592 } 593 } 594 595 return deleteMessage; 596 } catch (MessagingException ex) { 597 602 604 return failMessage(mail, ex, ('5' == ex.getMessage().charAt(0))); 609 } 610 611 617 return failMessage(mail, new MessagingException ("No mail server(s) available at this time."), false); 618 } 619 620 628 private boolean failMessage(MailImpl mail, MessagingException ex, boolean permanent) { 629 StringWriter sout = new StringWriter (); 630 PrintWriter out = new PrintWriter (sout, true); 631 if (permanent) { 632 out.print("Permanent"); 633 } else { 634 out.print("Temporary"); 635 } 636 StringBuffer logBuffer = 637 new StringBuffer  |