1 17 18 package org.apache.james; 19 20 import org.apache.avalon.framework.activity.Initializable; 21 import org.apache.avalon.framework.component.Component; 22 import org.apache.avalon.framework.component.Composable; 23 import org.apache.avalon.framework.component.DefaultComponentManager; 24 import org.apache.avalon.framework.component.ComponentManager; 25 import org.apache.avalon.framework.component.ComponentException; 26 import org.apache.avalon.framework.configuration.Configurable; 27 import org.apache.avalon.framework.configuration.Configuration; 28 import org.apache.avalon.framework.configuration.ConfigurationException; 29 import org.apache.avalon.framework.configuration.DefaultConfiguration; 30 import org.apache.avalon.framework.context.Context; 31 import org.apache.avalon.framework.context.Contextualizable; 32 import org.apache.avalon.framework.context.DefaultContext; 33 import org.apache.avalon.framework.logger.AbstractLogEnabled; 34 import org.apache.avalon.framework.logger.Logger; 35 import org.apache.james.core.MailHeaders; 36 import org.apache.james.core.MailImpl; 37 import org.apache.james.services.*; 38 import org.apache.james.userrepository.DefaultJamesUser; 39 import org.apache.james.util.RFC2822Headers; 40 import org.apache.james.util.RFC822DateFormat; 41 import org.apache.mailet.Mail; 42 import org.apache.mailet.MailAddress; 43 import org.apache.mailet.MailetContext; 44 45 import javax.mail.Address ; 46 import javax.mail.MessagingException ; 47 import javax.mail.internet.InternetAddress ; 48 import javax.mail.internet.MimeBodyPart ; 49 import javax.mail.internet.MimeMessage ; 50 import javax.mail.internet.MimeMultipart ; 51 import java.io.ByteArrayInputStream ; 52 import java.io.IOException ; 53 import java.io.InputStream ; 54 import java.io.SequenceInputStream ; 55 import java.net.InetAddress ; 56 import java.net.UnknownHostException ; 57 import java.util.*; 58 59 70 public class James 71 extends AbstractLogEnabled 72 implements Contextualizable, Composable, Configurable, JamesMBean, 73 Initializable, MailServer, MailetContext, Component { 74 75 78 private final static String SOFTWARE_NAME_VERSION = Constants.SOFTWARE_NAME + " " + Constants.SOFTWARE_VERSION; 79 80 83 private DefaultComponentManager compMgr; 85 89 private DefaultContext context; 90 91 94 private Configuration conf; 95 96 99 private Logger mailetLogger = null; 100 101 104 private MailStore mailstore; 105 106 109 private UsersStore usersStore; 110 111 114 private SpoolRepository spool; 115 116 119 private MailRepository localInbox; 120 121 124 private String inboxRootURL; 125 126 130 private UsersRepository localusers; 131 132 136 private Collection serverNames; 137 138 141 private boolean ignoreCase; 142 143 146 private boolean enableAliases; 147 148 151 private boolean enableForwarding; 152 153 157 private static long count; 158 159 162 private MailAddress postmaster; 163 164 168 private Map mailboxes; 170 174 private Hashtable attributes = new Hashtable(); 175 176 179 protected Context myContext; 180 181 184 private RFC822DateFormat rfc822DateFormat = new RFC822DateFormat(); 185 186 189 public void contextualize(final Context context) { 190 this.myContext = context; 191 } 192 193 196 public void compose(ComponentManager comp) { 197 compMgr = new DefaultComponentManager(comp); 198 mailboxes = new HashMap(31); 199 } 200 201 204 public void configure(Configuration conf) { 205 this.conf = conf; 206 } 207 208 211 public void initialize() throws Exception { 212 213 getLogger().info("JAMES init..."); 214 215 try { 218 mailstore = (MailStore) compMgr.lookup( MailStore.ROLE ); 219 } catch (Exception e) { 220 if (getLogger().isWarnEnabled()) { 221 getLogger().warn("Can't get Store: " + e); 222 } 223 } 224 if (getLogger().isDebugEnabled()) { 225 getLogger().debug("Using MailStore: " + mailstore.toString()); 226 } 227 try { 228 usersStore = (UsersStore) compMgr.lookup( UsersStore.ROLE ); 229 } catch (Exception e) { 230 if (getLogger().isWarnEnabled()) { 231 getLogger().warn("Can't get Store: " + e); 232 } 233 } 234 if (getLogger().isDebugEnabled()) { 235 getLogger().debug("Using UsersStore: " + usersStore.toString()); 236 } 237 238 String hostName = null; 239 try { 240 hostName = InetAddress.getLocalHost().getHostName(); 241 } catch (UnknownHostException ue) { 242 hostName = "localhost"; 243 } 244 245 context = new DefaultContext(); 246 context.put("HostName", hostName); 247 getLogger().info("Local host is: " + hostName); 248 249 serverNames = new HashSet(); 251 Configuration serverConf = conf.getChild("servernames"); 252 if (serverConf.getAttributeAsBoolean("autodetect") && (!hostName.equals("localhost"))) { 253 serverNames.add(hostName.toLowerCase(Locale.US)); 254 } 255 256 final Configuration[] serverNameConfs = 257 conf.getChild( "servernames" ).getChildren( "servername" ); 258 for ( int i = 0; i < serverNameConfs.length; i++ ) { 259 serverNames.add( serverNameConfs[i].getValue().toLowerCase(Locale.US)); 260 261 if (serverConf.getAttributeAsBoolean("autodetectIP", true)) { 262 try { 263 272 InetAddress [] addrs = InetAddress.getAllByName(serverNameConfs[i].getValue()); 273 for (int j = 0; j < addrs.length ; j++) { 274 serverNames.add(addrs[j].getHostAddress()); 275 } 276 } 277 catch(Exception genericException) { 278 getLogger().error("Cannot get IP address(es) for " + serverNameConfs[i].getValue()); 279 } 280 } 281 } 282 if (serverNames.isEmpty()) { 283 throw new ConfigurationException( "Fatal configuration error: no servernames specified!"); 284 } 285 286 if (getLogger().isInfoEnabled()) { 287 for (Iterator i = serverNames.iterator(); i.hasNext(); ) { 288 getLogger().info("Handling mail for: " + i.next()); 289 } 290 } 291 context.put(Constants.SERVER_NAMES, this.serverNames); 292 attributes.put(Constants.SERVER_NAMES, this.serverNames); 293 294 295 String postMasterAddress = conf.getChild("postmaster").getValue("postmaster").toLowerCase(Locale.US); 297 if (postMasterAddress.indexOf('@') < 0) { 301 String domainName = null; for ( int i = 0; domainName == null && i < serverNameConfs.length ; i++ ) { 304 String serverName = serverNameConfs[i].getValue().toLowerCase(Locale.US); 305 if (!("localhost".equals(serverName))) { 306 domainName = serverName; } 308 } 309 postMasterAddress = postMasterAddress + "@" + (domainName != null ? domainName : hostName); 311 } 312 this.postmaster = new MailAddress( postMasterAddress ); 313 context.put( Constants.POSTMASTER, postmaster ); 314 315 if (!isLocalServer(postmaster.getHost())) { 316 StringBuffer warnBuffer 317 = new StringBuffer (320) 318 .append("The specified postmaster address ( ") 319 .append(postmaster) 320 .append(" ) is not a local address. This is not necessarily a problem, but it does mean that emails addressed to the postmaster will be routed to another server. For some configurations this may cause problems."); 321 getLogger().warn(warnBuffer.toString()); 322 } 323 324 Configuration userNamesConf = conf.getChild("usernames"); 325 ignoreCase = userNamesConf.getAttributeAsBoolean("ignoreCase", false); 326 enableAliases = userNamesConf.getAttributeAsBoolean("enableAliases", false); 327 enableForwarding = userNamesConf.getAttributeAsBoolean("enableForwarding", false); 328 329 try { 331 localusers = (UsersRepository) usersStore.getRepository("LocalUsers"); 332 } catch (Exception e) { 333 getLogger().error("Cannot open private UserRepository"); 334 throw e; 335 } 336 compMgr.put( UsersRepository.ROLE, (Component)localusers); 338 getLogger().info("Local users repository opened"); 339 340 Configuration inboxConf = conf.getChild("inboxRepository"); 341 Configuration inboxRepConf = inboxConf.getChild("repository"); 342 try { 343 localInbox = (MailRepository) mailstore.select(inboxRepConf); 344 } catch (Exception e) { 345 getLogger().error("Cannot open private MailRepository"); 346 throw e; 347 } 348 inboxRootURL = inboxRepConf.getAttribute("destinationURL"); 349 350 getLogger().info("Private Repository LocalInbox opened"); 351 352 compMgr.put( MailServer.ROLE, this); 354 355 spool = mailstore.getInboundSpool(); 356 if (getLogger().isDebugEnabled()) { 357 getLogger().debug("Got spool"); 358 } 359 360 attributes.put(Constants.AVALON_COMPONENT_MANAGER, compMgr); 365 366 System.out.println(SOFTWARE_NAME_VERSION); 367 getLogger().info("JAMES ...init end"); 368 } 369 370 378 public void sendMail(MimeMessage message) throws MessagingException { 379 MailAddress sender = new MailAddress((InternetAddress )message.getFrom()[0]); 380 Collection recipients = new HashSet(); 381 Address addresses[] = message.getAllRecipients(); 382 if (addresses != null) { 383 for (int i = 0; i < addresses.length; i++) { 384 if ( addresses[i] instanceof InternetAddress ) { 387 recipients.add(new MailAddress((InternetAddress )addresses[i])); 388 } 389 } 390 } 391 sendMail(sender, recipients, message); 392 } 393 394 404 public void sendMail(MailAddress sender, Collection recipients, MimeMessage message) 405 throws MessagingException { 406 sendMail(sender, recipients, message, Mail.DEFAULT); 407 } 408 409 420 public void sendMail(MailAddress sender, Collection recipients, MimeMessage message, String state) 421 throws MessagingException { 422 MailImpl mail = new MailImpl(getId(), sender, recipients, message); 423 mail.setState(state); 424 sendMail(mail); 425 } 426 427 437 public void sendMail(MailAddress sender, Collection recipients, InputStream msg) 438 throws MessagingException { 439 MailHeaders headers = new MailHeaders(msg); 441 442 if (!headers.isValid()) { 444 throw new MessagingException ("Some REQURED header field is missing. Invalid Message"); 445 } 446 ByteArrayInputStream headersIn = new ByteArrayInputStream (headers.toByteArray()); 447 sendMail(new MailImpl(getId(), sender, recipients, new SequenceInputStream (headersIn, msg))); 448 } 449 450 458 public void sendMail(Mail mail) throws MessagingException { 459 MailImpl mailimpl = (MailImpl)mail; 460 try { 461 spool.store(mailimpl); 462 } catch (Exception e) { 463 try { 464 spool.remove(mailimpl); 465 } catch (Exception ignored) { 466 } 467 throw new MessagingException ("Exception spooling message: " + e.getMessage(), e); 468 } 469 if (getLogger().isDebugEnabled()) { 470 StringBuffer logBuffer = 471 new StringBuffer (64) 472 .append("Mail ") 473 .append(mailimpl.getName()) 474 .append(" pushed in spool"); 475 getLogger().debug(logBuffer.toString()); 476 } 477 } 478 479 488 public synchronized MailRepository getUserInbox(String userName) { 489 MailRepository userInbox = (MailRepository) null; 490 491 userInbox = (MailRepository) mailboxes.get(userName); 492 493 if (userInbox != null) { 494 return userInbox; 495 } else if (mailboxes.containsKey(userName)) { 496 getLogger().error("Null mailbox for non-null key"); 498 throw new RuntimeException ("Error in getUserInbox."); 499 } else { 500 if (getLogger().isDebugEnabled()) { 502 getLogger().debug("Retrieving and caching inbox for " + userName ); 503 } 504 StringBuffer destinationBuffer = 505 new StringBuffer (192) 506 .append(inboxRootURL) 507 .append(userName) 508 .append("/"); 509 String destination = destinationBuffer.toString(); 510 DefaultConfiguration mboxConf 511 = new DefaultConfiguration("repository", "generated:AvalonFileRepository.compose()"); 512 mboxConf.setAttribute("destinationURL", destination); 513 mboxConf.setAttribute("type", "MAIL"); 514 try { 515 userInbox = (MailRepository) mailstore.select(mboxConf); 516 mailboxes.put(userName, userInbox); 517 } catch (Exception e) { 518 if (getLogger().isErrorEnabled()) 519 { 520 getLogger().error("Cannot open user Mailbox" + e); 521 } 522 throw new RuntimeException ("Error in getUserInbox." + e); 523 } 524 return userInbox; 525 } 526 } 527 528 533 public String getId() { 534 long localCount = -1; 535 synchronized (James.class) { 536 localCount = count++; 537 } 538 StringBuffer idBuffer = 539 new StringBuffer (64) 540 .append("Mail") 541 .append(System.currentTimeMillis()) 542 .append("-") 543 .append(localCount); 544 return idBuffer.toString(); 545 } 546 547 553 public static void main(String [] args) { 554 System.out.println("ERROR!"); 555 System.out.println("Cannot execute James as a stand alone application."); 556 System.out.println("To run James, you need to have the Avalon framework installed."); 557 System.out.println("Please refer to the Readme file to know how to run James."); 558 } 559 560 562 569 public Collection getMailServers(String host) { 570 DNSServer dnsServer = null; 571 try { 572 dnsServer = (DNSServer) compMgr.lookup( DNSServer.ROLE ); 573 } catch ( final ComponentException cme ) { 574 getLogger().error("Fatal configuration error - DNS Servers lost!", cme ); 575 throw new RuntimeException ("Fatal configuration error - DNS Servers lost!"); 576 } 577 return dnsServer.findMXRecords(host); 578 } 579 580 public Object getAttribute(String key) { 581 return attributes.get(key); 582 } 583 584 public void setAttribute(String key, Object object) { 585 attributes.put(key, object); 586 } 587 588 public void removeAttribute(String key) { 589 attributes.remove(key); 590 } 591 592 public Iterator getAttributeNames() { 593 Vector names = new Vector(); 594 for (Enumeration e = attributes.keys(); e.hasMoreElements(); ) { 595 names.add(e.nextElement()); 596 } 597 return names.iterator(); 598 } 599 600 607 public void bounce(Mail mail, String message) throws MessagingException { 608 bounce(mail, message, getPostmaster()); 609 } 610 611 634 635 public void bounce(Mail mail, String message, MailAddress bouncer) throws MessagingException { 636 MimeMessage orig = mail.getMessage(); 637 638 MimeMessage reply = (MimeMessage ) orig.reply(false); 640 641 String [] returnPathHeaders = orig.getHeader(RFC2822Headers.RETURN_PATH); 643 String returnPathHeader = null; 644 if (returnPathHeaders != null) { 645 returnPathHeader = returnPathHeaders[0]; 648 if (returnPathHeader != null) { 649 returnPathHeader = returnPathHeader.trim(); 650 if (returnPathHeader.equals("<>")) { 651 if (getLogger().isInfoEnabled()) 652 getLogger().info("Processing a bounce request for a message with an empty return path. No bounce will be sent."); 653 return; 654 } else { 655 if (getLogger().isInfoEnabled()) 656 getLogger().info("Processing a bounce request for a message with a return path header. The bounce will be sent to " + returnPathHeader); 657 reply.setRecipient(MimeMessage.RecipientType.TO, new InternetAddress (returnPathHeader)); 659 } 660 } 661 } else { 662 getLogger().warn("Mail to be bounced does not contain a Return-Path header."); 663 } 664 665 reply.setSentDate(new Date()); 666 reply.setHeader(RFC2822Headers.RETURN_PATH,"<>"); 667 Collection recipients = new HashSet(); 669 Address addresses[] = reply.getAllRecipients(); 670 if (addresses != null) { 671 for (int i = 0; i < addresses.length; i++) { 672 if ( addresses[i] instanceof InternetAddress ) { 675 recipients.add(new MailAddress((InternetAddress )addresses[i])); 676 } 677 } 678 } 679 reply.setFrom(bouncer.toInternetAddress()); 681 try { 682 MimeMultipart multipart = new MimeMultipart ("mixed"); 684 685 MimeMultipart mpContent = new MimeMultipart ("alternative"); 687 MimeBodyPart contentPartRoot = new MimeBodyPart (); 688 contentPartRoot.setContent(mpContent); 689 690 multipart.addBodyPart(contentPartRoot); 691 692 MimeBodyPart part = new MimeBodyPart (); 693 part.setText(message); 694 mpContent.addBodyPart(part); 695 696 part = new MimeBodyPart (); 698 part.setContent(orig, "message/rfc822"); 699 if ((orig.getSubject() != null) && (orig.getSubject().trim().length() > 0)) { 700 part.setFileName(orig.getSubject().trim()); 701 } else { 702 part.setFileName("No Subject"); 703 } 704 part.setDisposition(javax.mail.Part.ATTACHMENT); 705 multipart.addBodyPart(part); 706 reply.setContent(multipart); 707 } catch (Exception ioe) { 708 throw new MessagingException ("Unable to create multipart body", ioe); 709 } 710 reply.saveChanges(); 711 sendMail(null, recipients, reply); 713 } 714 715 722 public boolean isLocalUser(String name) { 723 if (ignoreCase) { 724 return localusers.containsCaseInsensitive(name); 725 } else { 726 return localusers.contains(name); 727 } 728 } 729 730 735 public MailAddress getPostmaster() { 736 return postmaster; 737 } 738 739 public void storeMail(MailAddress sender, MailAddress recipient, MimeMessage message) 740 throws MessagingException { 741 String username; 742 if (recipient == null) { 743 throw new IllegalArgumentException ("Recipient for mail to be spooled cannot be null."); 744 } 745 if (message == null) { 746 throw new IllegalArgumentException ("Mail message to be spooled cannot be null."); 747 } 748 if (ignoreCase) { 749 String originalUsername = recipient.getUser(); 750 username = localusers.getRealName(originalUsername); 751 if (username == null) { 752 StringBuffer errorBuffer = 753 new StringBuffer (128) 754 .append("The inbox for user ") 755 .append(originalUsername) 756 .append(" was not found on this server."); 757 throw new MessagingException (errorBuffer.toString()); 758 } 759 } else { 760 username = recipient.getUser(); 761 } 762 JamesUser user; 763 if (enableAliases || enableForwarding) { 764 user = (JamesUser) localusers.getUserByName(username); 765 if (enableAliases && user.getAliasing()) { 766 username = user.getAlias(); 767 } 768 if (enableForwarding && user.getForwarding()) { 770 MailAddress forwardTo = user.getForwardingDestination(); 771 if (forwardTo == null) { 772 StringBuffer errorBuffer = 773 new StringBuffer (128) 774 .append("Forwarding was enabled for ") 775 .append(username) 776 .append(" but no forwarding address was set for this account."); 777 throw new MessagingException (errorBuffer.toString()); 778 } 779 Collection recipients = new HashSet(); 780 recipients.add(forwardTo); 781 try { 782 sendMail(sender, recipients, message); 783 if (getLogger().isInfoEnabled()) { 784 StringBuffer logBuffer = 785 new StringBuffer (128) 786 .append("Mail for ") 787 .append(username) 788 .append(" forwarded to ") 789 .append(forwardTo.toString()); 790 getLogger().info(logBuffer.toString()); 791 } 792 return; 793 } catch (MessagingException me) { 794 if (getLogger().isErrorEnabled()) { 795 StringBuffer logBuffer = 796 new StringBuffer (128) 797 .append("Error forwarding mail to ") 798 .append(forwardTo.toString()) 799 .append("attempting local delivery"); 800 getLogger().error(logBuffer.toString()); 801 } 802 throw me; 803 } 804 } 805 } 806 807 Collection recipients = new HashSet(); 808 recipients.add(recipient); 809 MailImpl mailImpl = new MailImpl(getId(), sender, recipients, message); 810 MailRepository userInbox = getUserInbox(username); 811 if (userInbox == null) { 812 StringBuffer errorBuffer = 813 new StringBuffer (128) 814 .append("The inbox for user ") 815 .append(username) 816 .append(" was not found on this server."); 817 throw new MessagingException (errorBuffer.toString()); 818 } 819 userInbox.store(mailImpl); 820 } 821 822 827 public int getMajorVersion() { 828 return 2; 829 } 830 831 836 public int getMinorVersion() { 837 return 1; 838 } 839 840 847 public boolean isLocalServer( final String serverName ) { 848 return serverNames.contains(serverName.toLowerCase(Locale.US)); 849 } 850 851 856 public String getServerInfo() { 857 return "Apache JAMES"; 858 } 859 860 865 private Logger getMailetLogger() { 866 if (mailetLogger == null) { 867 mailetLogger = getLogger().getChildLogger("Mailet"); 868 } 869 return mailetLogger; 870 } 871 872 877 public void log(String message) { 878 getMailetLogger().info(message); 879 } 880 881 887 public void log(String message, Throwable t) { 888 getMailetLogger().info(message,t); 889 } 890 891 900 public boolean addUser(String userName, String password) { 901 boolean success; 902 DefaultJamesUser user = new DefaultJamesUser(userName, "SHA"); 903 user.setPassword(password); 904 user.initialize(); 905 success = localusers.addUser(user); 906 return success; 907 } 908 909 925 public Iterator getSMTPHostAddresses(String domainName) { 926 DNSServer dnsServer = null; 927 try { 928 dnsServer = (DNSServer) compMgr.lookup( DNSServer.ROLE ); 929 } catch ( final ComponentException cme ) { 930 getLogger().error("Fatal configuration error - DNS Servers lost!", cme ); 931 throw new RuntimeException ("Fatal configuration error - DNS Servers lost!"); 932 } 933 return dnsServer.getSMTPHostAddresses(domainName); 934 } 935 } 936 | Popular Tags |