001: /****************************************************************
002: * Licensed to the Apache Software Foundation (ASF) under one *
003: * or more contributor license agreements. See the NOTICE file *
004: * distributed with this work for additional information *
005: * regarding copyright ownership. The ASF licenses this file *
006: * to you under the Apache License, Version 2.0 (the *
007: * "License"); you may not use this file except in compliance *
008: * with the License. You may obtain a copy of the License at *
009: * *
010: * http://www.apache.org/licenses/LICENSE-2.0 *
011: * *
012: * Unless required by applicable law or agreed to in writing, *
013: * software distributed under the License is distributed on an *
014: * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY *
015: * KIND, either express or implied. See the License for the *
016: * specific language governing permissions and limitations *
017: * under the License. *
018: ****************************************************************/package org.apache.james;
019:
020: import org.apache.avalon.cornerstone.services.store.Store;
021: import org.apache.avalon.framework.activity.Initializable;
022: import org.apache.avalon.framework.configuration.Configurable;
023: import org.apache.avalon.framework.configuration.Configuration;
024: import org.apache.avalon.framework.configuration.ConfigurationException;
025: import org.apache.avalon.framework.configuration.DefaultConfiguration;
026: import org.apache.avalon.framework.container.ContainerUtil;
027: import org.apache.avalon.framework.context.Context;
028: import org.apache.avalon.framework.context.Contextualizable;
029: import org.apache.avalon.framework.context.DefaultContext;
030: import org.apache.avalon.framework.logger.AbstractLogEnabled;
031: import org.apache.avalon.framework.logger.Logger;
032: import org.apache.avalon.framework.service.DefaultServiceManager;
033: import org.apache.avalon.framework.service.ServiceException;
034: import org.apache.avalon.framework.service.ServiceManager;
035: import org.apache.avalon.framework.service.Serviceable;
036: import org.apache.commons.collections.ReferenceMap;
037:
038: import org.apache.james.context.AvalonContextUtilities;
039: import org.apache.james.core.MailHeaders;
040: import org.apache.james.core.MailImpl;
041: import org.apache.james.core.MailetConfigImpl;
042: import org.apache.james.services.DNSServer;
043: import org.apache.james.services.MailRepository;
044: import org.apache.james.services.MailServer;
045: import org.apache.james.services.SpoolRepository;
046: import org.apache.james.services.UsersRepository;
047: import org.apache.james.services.UsersStore;
048: import org.apache.james.transport.mailets.LocalDelivery;
049: import org.apache.james.userrepository.DefaultJamesUser;
050: import org.apache.mailet.Mail;
051: import org.apache.mailet.MailAddress;
052: import org.apache.mailet.Mailet;
053: import org.apache.mailet.MailetContext;
054: import org.apache.mailet.RFC2822Headers;
055:
056: import javax.mail.Address;
057: import javax.mail.Message;
058: import javax.mail.MessagingException;
059: import javax.mail.internet.InternetAddress;
060: import javax.mail.internet.MimeMessage;
061: import java.io.ByteArrayInputStream;
062: import java.io.InputStream;
063: import java.io.SequenceInputStream;
064: import java.net.InetAddress;
065: import java.net.UnknownHostException;
066: import java.util.Collection;
067: import java.util.Date;
068: import java.util.Enumeration;
069: import java.util.HashSet;
070: import java.util.Hashtable;
071: import java.util.Iterator;
072: import java.util.Locale;
073: import java.util.Map;
074: import java.util.Vector;
075:
076: /**
077: * Core class for JAMES. Provides three primary services:
078: * <br> 1) Instantiates resources, such as user repository, and protocol
079: * handlers
080: * <br> 2) Handles interactions between components
081: * <br> 3) Provides container services for Mailets
082: *
083: *
084: * @version This is $Revision: 494012 $
085:
086: */
087: public class James extends AbstractLogEnabled implements
088: Contextualizable, Serviceable, Configurable, JamesMBean,
089: Initializable, MailServer, MailetContext {
090:
091: /**
092: * The software name and version
093: */
094: private final static String SOFTWARE_NAME_VERSION = Constants.SOFTWARE_NAME
095: + " " + Constants.SOFTWARE_VERSION;
096:
097: /**
098: * The component manager used both internally by James and by Mailets.
099: */
100: private DefaultServiceManager compMgr; //Components shared
101:
102: /**
103: * TODO: Investigate what this is supposed to do. Looks like it
104: * was supposed to be the Mailet context.
105: */
106: private DefaultContext context;
107:
108: /**
109: * The top level configuration object for this server.
110: */
111: private Configuration conf;
112:
113: /**
114: * The logger used by the Mailet API.
115: */
116: private Logger mailetLogger = null;
117:
118: /**
119: * The mail store containing the inbox repository and the spool.
120: */
121: private Store store;
122:
123: /**
124: * The store containing the local user repository.
125: */
126: private UsersStore usersStore;
127:
128: /**
129: * The spool used for processing mail handled by this server.
130: */
131: private SpoolRepository spool;
132:
133: /**
134: * The root URL used to get mailboxes from the repository
135: */
136: private String inboxRootURL;
137:
138: /**
139: * The user repository for this mail server. Contains all the users with inboxes
140: * on this server.
141: */
142: private UsersRepository localusers;
143:
144: /**
145: * The collection of domain/server names for which this instance of James
146: * will receive and process mail.
147: */
148: private Collection serverNames;
149:
150: /**
151: * Whether to ignore case when looking up user names on this server
152: */
153: private boolean ignoreCase;
154:
155: /**
156: * The number of mails generated. Access needs to be synchronized for
157: * thread safety and to ensure that all threads see the latest value.
158: */
159: private static long count;
160:
161: /**
162: * The address of the postmaster for this server
163: */
164: private MailAddress postmaster;
165:
166: /**
167: * A map used to store mailboxes and reduce the cost of lookup of individual
168: * mailboxes.
169: */
170: private Map mailboxes; //Not to be shared!
171:
172: /**
173: * A hash table of server attributes
174: * These are the MailetContext attributes
175: */
176: private Hashtable attributes = new Hashtable();
177:
178: /**
179: * The Avalon context used by the instance
180: */
181: protected Context myContext;
182:
183: /**
184: * Currently used by storeMail to avoid code duplication (we moved store logic to that mailet).
185: * TODO We should remove this and its initialization when we remove storeMail method.
186: */
187: protected Mailet localDeliveryMailet;
188:
189: /**
190: * @see org.apache.avalon.framework.context.Contextualizable#contextualize(Context)
191: */
192: public void contextualize(final Context context) {
193: this .myContext = context;
194: }
195:
196: /**
197: * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
198: */
199: public void service(ServiceManager comp) {
200: compMgr = new DefaultServiceManager(comp);
201: mailboxes = new ReferenceMap();
202: }
203:
204: /**
205: * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
206: */
207: public void configure(Configuration conf) {
208: this .conf = conf;
209: }
210:
211: /**
212: * @see org.apache.avalon.framework.activity.Initializable#initialize()
213: */
214: public void initialize() throws Exception {
215:
216: getLogger().info("JAMES init...");
217:
218: // TODO: This should retrieve a more specific named thread pool from
219: // Context that is set up in server.xml
220: try {
221: store = (Store) compMgr.lookup(Store.ROLE);
222: } catch (Exception e) {
223: if (getLogger().isWarnEnabled()) {
224: getLogger().warn("Can't get Store: " + e);
225: }
226: }
227: if (getLogger().isDebugEnabled()) {
228: getLogger().debug("Using Store: " + store.toString());
229: }
230: try {
231: spool = (SpoolRepository) compMgr
232: .lookup(SpoolRepository.ROLE);
233: } catch (Exception e) {
234: if (getLogger().isWarnEnabled()) {
235: getLogger().warn("Can't get spoolRepository: " + e);
236: }
237: }
238: if (getLogger().isDebugEnabled()) {
239: getLogger().debug(
240: "Using SpoolRepository: " + spool.toString());
241: }
242: try {
243: usersStore = (UsersStore) compMgr.lookup(UsersStore.ROLE);
244: } catch (Exception e) {
245: if (getLogger().isWarnEnabled()) {
246: getLogger().warn("Can't get Store: " + e);
247: }
248: }
249: if (getLogger().isDebugEnabled()) {
250: getLogger().debug(
251: "Using UsersStore: " + usersStore.toString());
252: }
253:
254: String hostName = null;
255: try {
256: hostName = InetAddress.getLocalHost().getHostName();
257: } catch (UnknownHostException ue) {
258: hostName = "localhost";
259: }
260:
261: context = new DefaultContext();
262: context.put("HostName", hostName);
263: getLogger().info("Local host is: " + hostName);
264:
265: // Get the domains and hosts served by this instance
266: serverNames = new HashSet();
267: Configuration serverConf = conf.getChild("servernames");
268: if (serverConf.getAttributeAsBoolean("autodetect")
269: && (!hostName.equals("localhost"))) {
270: serverNames.add(hostName.toLowerCase(Locale.US));
271: }
272:
273: final Configuration[] serverNameConfs = conf.getChild(
274: "servernames").getChildren("servername");
275: for (int i = 0; i < serverNameConfs.length; i++) {
276: serverNames.add(serverNameConfs[i].getValue().toLowerCase(
277: Locale.US));
278:
279: if (serverConf.getAttributeAsBoolean("autodetectIP", true)) {
280: try {
281: /* This adds the IP address(es) for each host to support
282: * support <user@address-literal> - RFC 2821, sec 4.1.3.
283: * It might be proper to use the actual IP addresses
284: * available on this server, but we can't do that
285: * without NetworkInterface from JDK 1.4. Because of
286: * Virtual Hosting considerations, we may need to modify
287: * this to keep hostname and IP associated, rather than
288: * just both in the set.
289: */
290: InetAddress[] addrs = InetAddress
291: .getAllByName(serverNameConfs[i].getValue());
292: for (int j = 0; j < addrs.length; j++) {
293: serverNames.add(addrs[j].getHostAddress());
294: }
295: } catch (Exception genericException) {
296: getLogger().error(
297: "Cannot get IP address(es) for "
298: + serverNameConfs[i].getValue());
299: }
300: }
301: }
302: if (serverNames.isEmpty()) {
303: throw new ConfigurationException(
304: "Fatal configuration error: no servernames specified!");
305: }
306:
307: if (getLogger().isInfoEnabled()) {
308: for (Iterator i = serverNames.iterator(); i.hasNext();) {
309: getLogger().info("Handling mail for: " + i.next());
310: }
311: }
312:
313: String defaultDomain = (String) serverNames.iterator().next();
314: context.put(Constants.DEFAULT_DOMAIN, defaultDomain);
315: attributes.put(Constants.DEFAULT_DOMAIN, defaultDomain);
316:
317: // Get postmaster
318: String postMasterAddress = conf.getChild("postmaster")
319: .getValue("postmaster").toLowerCase(Locale.US);
320: // if there is no @domain part, then add the first one from the
321: // list of supported domains that isn't localhost. If that
322: // doesn't work, use the hostname, even if it is localhost.
323: if (postMasterAddress.indexOf('@') < 0) {
324: String domainName = null; // the domain to use
325: // loop through candidate domains until we find one or exhaust the list
326: for (int i = 0; domainName == null
327: && i < serverNameConfs.length; i++) {
328: String serverName = serverNameConfs[i].getValue()
329: .toLowerCase(Locale.US);
330: if (!("localhost".equals(serverName))) {
331: domainName = serverName; // ok, not localhost, so use it
332: }
333: }
334: // if we found a suitable domain, use it. Otherwise fallback to the host name.
335: postMasterAddress = postMasterAddress + "@"
336: + (domainName != null ? domainName : hostName);
337: }
338: this .postmaster = new MailAddress(postMasterAddress);
339: context.put(Constants.POSTMASTER, postmaster);
340:
341: if (!isLocalServer(postmaster.getHost())) {
342: StringBuffer warnBuffer = new StringBuffer(320)
343: .append("The specified postmaster address ( ")
344: .append(postmaster)
345: .append(
346: " ) 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.");
347: getLogger().warn(warnBuffer.toString());
348: }
349:
350: Configuration userNamesConf = conf.getChild("usernames");
351: ignoreCase = userNamesConf.getAttributeAsBoolean("ignoreCase",
352: false);
353: boolean enableAliases = userNamesConf.getAttributeAsBoolean(
354: "enableAliases", false);
355: boolean enableForwarding = userNamesConf.getAttributeAsBoolean(
356: "enableForwarding", false);
357: attributes.put(Constants.DEFAULT_ENABLE_ALIASES, new Boolean(
358: enableAliases));
359: attributes.put(Constants.DEFAULT_ENABLE_FORWARDING,
360: new Boolean(enableForwarding));
361: attributes.put(Constants.DEFAULT_IGNORE_USERNAME_CASE,
362: new Boolean(ignoreCase));
363:
364: //Get localusers
365: try {
366: localusers = (UsersRepository) compMgr
367: .lookup(UsersRepository.ROLE);
368: } catch (Exception e) {
369: getLogger().error("Cannot open private UserRepository");
370: throw e;
371: }
372: //}
373: compMgr.put(UsersRepository.ROLE, localusers);
374: getLogger().info("Local users repository opened");
375:
376: Configuration inboxConf = conf.getChild("inboxRepository");
377: Configuration inboxRepConf = inboxConf.getChild("repository");
378: // we could delete this block. I didn't remove this because I'm not sure
379: // wether we need the "check" of the inbox repository here, or not.
380: try {
381: store.select(inboxRepConf);
382: } catch (Exception e) {
383: getLogger().error("Cannot open private MailRepository");
384: throw e;
385: }
386: inboxRootURL = inboxRepConf.getAttribute("destinationURL");
387:
388: getLogger().info("Private Repository LocalInbox opened");
389:
390: // Add this to comp
391: compMgr.put(MailServer.ROLE, this );
392:
393: // For mailet engine provide MailetContext
394: //compMgr.put("org.apache.mailet.MailetContext", this);
395: // For AVALON aware mailets and matchers, we put the Component object as
396: // an attribute
397: attributes.put(Constants.AVALON_COMPONENT_MANAGER, compMgr);
398:
399: //Temporary get out to allow complex mailet config files to stop blocking sergei sozonoff's work on bouce processing
400: java.io.File configDir = AvalonContextUtilities.getFile(
401: myContext, "file://conf/");
402: attributes.put("confDir", configDir.getCanonicalPath());
403:
404: // We can safely remove this and the localDeliveryField when we
405: // remove the storeMail method from James and from the MailetContext
406: DefaultConfiguration conf = new DefaultConfiguration("mailet",
407: "generated:James.initialize()");
408: MailetConfigImpl configImpl = new MailetConfigImpl();
409: configImpl.setMailetName("LocalDelivery");
410: configImpl.setConfiguration(conf);
411: configImpl.setMailetContext(this );
412: localDeliveryMailet = new LocalDelivery();
413: localDeliveryMailet.init(configImpl);
414:
415: System.out.println(SOFTWARE_NAME_VERSION);
416: getLogger().info("JAMES ...init end");
417: }
418:
419: /**
420: * Place a mail on the spool for processing
421: *
422: * @param message the message to send
423: *
424: * @throws MessagingException if an exception is caught while placing the mail
425: * on the spool
426: */
427: public void sendMail(MimeMessage message) throws MessagingException {
428: MailAddress sender = new MailAddress((InternetAddress) message
429: .getFrom()[0]);
430: Collection recipients = new HashSet();
431: Address addresses[] = message.getAllRecipients();
432: if (addresses != null) {
433: for (int i = 0; i < addresses.length; i++) {
434: // Javamail treats the "newsgroups:" header field as a
435: // recipient, so we want to filter those out.
436: if (addresses[i] instanceof InternetAddress) {
437: recipients.add(new MailAddress(
438: (InternetAddress) addresses[i]));
439: }
440: }
441: }
442: sendMail(sender, recipients, message);
443: }
444:
445: /**
446: * Place a mail on the spool for processing
447: *
448: * @param sender the sender of the mail
449: * @param recipients the recipients of the mail
450: * @param message the message to send
451: *
452: * @throws MessagingException if an exception is caught while placing the mail
453: * on the spool
454: */
455: public void sendMail(MailAddress sender, Collection recipients,
456: MimeMessage message) throws MessagingException {
457: sendMail(sender, recipients, message, Mail.DEFAULT);
458: }
459:
460: /**
461: * Place a mail on the spool for processing
462: *
463: * @param sender the sender of the mail
464: * @param recipients the recipients of the mail
465: * @param message the message to send
466: * @param state the state of the message
467: *
468: * @throws MessagingException if an exception is caught while placing the mail
469: * on the spool
470: */
471: public void sendMail(MailAddress sender, Collection recipients,
472: MimeMessage message, String state)
473: throws MessagingException {
474: MailImpl mail = new MailImpl(getId(), sender, recipients,
475: message);
476: try {
477: mail.setState(state);
478: sendMail(mail);
479: } finally {
480: ContainerUtil.dispose(mail);
481: }
482: }
483:
484: /**
485: * Place a mail on the spool for processing
486: *
487: * @param sender the sender of the mail
488: * @param recipients the recipients of the mail
489: * @param msg an <code>InputStream</code> containing the message
490: *
491: * @throws MessagingException if an exception is caught while placing the mail
492: * on the spool
493: */
494: public void sendMail(MailAddress sender, Collection recipients,
495: InputStream msg) throws MessagingException {
496: // parse headers
497: MailHeaders headers = new MailHeaders(msg);
498:
499: // if headers do not contains minimum REQUIRED headers fields throw Exception
500: if (!headers.isValid()) {
501: throw new MessagingException(
502: "Some REQURED header field is missing. Invalid Message");
503: }
504: ByteArrayInputStream headersIn = new ByteArrayInputStream(
505: headers.toByteArray());
506: sendMail(new MailImpl(getId(), sender, recipients,
507: new SequenceInputStream(headersIn, msg)));
508: }
509:
510: /**
511: * Place a mail on the spool for processing
512: *
513: * @param mail the mail to place on the spool
514: *
515: * @throws MessagingException if an exception is caught while placing the mail
516: * on the spool
517: */
518: public void sendMail(Mail mail) throws MessagingException {
519: try {
520: spool.store(mail);
521: } catch (Exception e) {
522: getLogger().error(
523: "Error storing message: " + e.getMessage(), e);
524: try {
525: spool.remove(mail);
526: } catch (Exception ignored) {
527: getLogger().error(
528: "Error removing message after an error storing it: "
529: + e.getMessage(), e);
530: }
531: throw new MessagingException("Exception spooling message: "
532: + e.getMessage(), e);
533: }
534: if (getLogger().isDebugEnabled()) {
535: StringBuffer logBuffer = new StringBuffer(64).append(
536: "Mail ").append(mail.getName()).append(
537: " pushed in spool");
538: getLogger().debug(logBuffer.toString());
539: }
540: }
541:
542: /**
543: * <p>Retrieve the mail repository for a user</p>
544: *
545: * <p>For POP3 server only - at the moment.</p>
546: *
547: * @param userName the name of the user whose inbox is to be retrieved
548: *
549: * @return the POP3 inbox for the user
550: */
551: public synchronized MailRepository getUserInbox(String userName) {
552: MailRepository userInbox = null;
553:
554: userInbox = (MailRepository) mailboxes.get(userName);
555:
556: if (userInbox != null) {
557: return userInbox;
558: } else if (mailboxes.containsKey(userName)) {
559: // we have a problem
560: getLogger().error("Null mailbox for non-null key");
561: throw new RuntimeException("Error in getUserInbox.");
562: } else {
563: // need mailbox object
564: if (getLogger().isDebugEnabled()) {
565: getLogger().debug(
566: "Retrieving and caching inbox for " + userName);
567: }
568: StringBuffer destinationBuffer = new StringBuffer(192)
569: .append(inboxRootURL).append(userName).append("/");
570: String destination = destinationBuffer.toString();
571: DefaultConfiguration mboxConf = new DefaultConfiguration(
572: "repository",
573: "generated:AvalonFileRepository.compose()");
574: mboxConf.setAttribute("destinationURL", destination);
575: mboxConf.setAttribute("type", "MAIL");
576: try {
577: userInbox = (MailRepository) store.select(mboxConf);
578: if (userInbox != null) {
579: mailboxes.put(userName, userInbox);
580: }
581: } catch (Exception e) {
582: if (getLogger().isErrorEnabled()) {
583: getLogger().error("Cannot open user Mailbox" + e);
584: }
585: throw new RuntimeException("Error in getUserInbox." + e);
586: }
587: return userInbox;
588: }
589: }
590:
591: /**
592: * Return a new mail id.
593: *
594: * @return a new mail id
595: */
596: public String getId() {
597: long localCount = -1;
598: synchronized (James.class) {
599: localCount = count++;
600: }
601: StringBuffer idBuffer = new StringBuffer(64).append("Mail")
602: .append(System.currentTimeMillis()).append("-").append(
603: localCount);
604: return idBuffer.toString();
605: }
606:
607: /**
608: * The main method. Should never be invoked, as James must be called
609: * from within an Avalon framework container.
610: *
611: * @param args the command line arguments
612: */
613: public static void main(String[] args) {
614: System.out.println("ERROR!");
615: System.out
616: .println("Cannot execute James as a stand alone application.");
617: System.out
618: .println("To run James, you need to have the Avalon framework installed.");
619: System.out
620: .println("Please refer to the Readme file to know how to run James.");
621: }
622:
623: //Methods for MailetContext
624:
625: /**
626: * <p>Get the prioritized list of mail servers for a given host.</p>
627: *
628: * <p>TODO: This needs to be made a more specific ordered subtype of Collection.</p>
629: *
630: * @param host
631: */
632: public Collection getMailServers(String host) {
633: DNSServer dnsServer = null;
634: try {
635: dnsServer = (DNSServer) compMgr.lookup(DNSServer.ROLE);
636: } catch (final ServiceException cme) {
637: getLogger().error(
638: "Fatal configuration error - DNS Servers lost!",
639: cme);
640: throw new RuntimeException(
641: "Fatal configuration error - DNS Servers lost!");
642: }
643: return dnsServer.findMXRecords(host);
644: }
645:
646: public Object getAttribute(String key) {
647: return attributes.get(key);
648: }
649:
650: public void setAttribute(String key, Object object) {
651: attributes.put(key, object);
652: }
653:
654: public void removeAttribute(String key) {
655: attributes.remove(key);
656: }
657:
658: public Iterator getAttributeNames() {
659: Vector names = new Vector();
660: for (Enumeration e = attributes.keys(); e.hasMoreElements();) {
661: names.add(e.nextElement());
662: }
663: return names.iterator();
664: }
665:
666: /**
667: * This generates a response to the Return-Path address, or the address of
668: * the message's sender if the Return-Path is not available. Note that
669: * this is different than a mail-client's reply, which would use the
670: * Reply-To or From header. This will send the bounce with the server's
671: * postmaster as the sender.
672: */
673: public void bounce(Mail mail, String message)
674: throws MessagingException {
675: bounce(mail, message, getPostmaster());
676: }
677:
678: /**
679: * This generates a response to the Return-Path address, or the
680: * address of the message's sender if the Return-Path is not
681: * available. Note that this is different than a mail-client's
682: * reply, which would use the Reply-To or From header.
683: *
684: * Bounced messages are attached in their entirety (headers and
685: * content) and the resulting MIME part type is "message/rfc822".
686: *
687: * The attachment to the subject of the original message (or "No
688: * Subject" if there is no subject in the original message)
689: *
690: * There are outstanding issues with this implementation revolving
691: * around handling of the return-path header.
692: *
693: * MIME layout of the bounce message:
694: *
695: * multipart (mixed)/
696: * contentPartRoot (body) = mpContent (alternative)/
697: * part (body) = message
698: * part (body) = original
699: *
700: */
701:
702: public void bounce(Mail mail, String message, MailAddress bouncer)
703: throws MessagingException {
704: if (mail.getSender() == null) {
705: if (getLogger().isInfoEnabled())
706: getLogger()
707: .info(
708: "Mail to be bounced contains a null (<>) reverse path. No bounce will be sent.");
709: return;
710: } else {
711: // Bounce message goes to the reverse path, not to the Reply-To address
712: if (getLogger().isInfoEnabled())
713: getLogger().info(
714: "Processing a bounce request for a message with a reverse path of "
715: + mail.getSender().toString());
716: }
717:
718: MailImpl reply = rawBounce(mail, message);
719: //Change the sender...
720: reply.getMessage().setFrom(bouncer.toInternetAddress());
721: reply.getMessage().saveChanges();
722: //Send it off ... with null reverse-path
723: reply.setSender(null);
724: sendMail(reply);
725: ContainerUtil.dispose(reply);
726: }
727:
728: /**
729: * Generates a bounce mail that is a bounce of the original message.
730: *
731: * @param bounceText the text to be prepended to the message to describe the bounce condition
732: *
733: * @return the bounce mail
734: *
735: * @throws MessagingException if the bounce mail could not be created
736: */
737: private MailImpl rawBounce(Mail mail, String bounceText)
738: throws MessagingException {
739: //This sends a message to the james component that is a bounce of the sent message
740: MimeMessage original = mail.getMessage();
741: MimeMessage reply = (MimeMessage) original.reply(false);
742: reply.setSubject("Re: " + original.getSubject());
743: reply.setSentDate(new Date());
744: Collection recipients = new HashSet();
745: recipients.add(mail.getSender());
746: InternetAddress addr[] = { new InternetAddress(mail.getSender()
747: .toString()) };
748: reply.setRecipients(Message.RecipientType.TO, addr);
749: reply.setFrom(new InternetAddress(mail.getRecipients()
750: .iterator().next().toString()));
751: reply.setText(bounceText);
752: reply.setHeader(RFC2822Headers.MESSAGE_ID, "replyTo-"
753: + mail.getName());
754: return new MailImpl("replyTo-" + mail.getName(),
755: new MailAddress(mail.getRecipients().iterator().next()
756: .toString()), recipients, reply);
757: }
758:
759: /**
760: * Returns whether that account has a local inbox on this server
761: *
762: * @param name the name to be checked
763: *
764: * @return whether the account has a local inbox
765: */
766: public boolean isLocalUser(String name) {
767: if (ignoreCase) {
768: return localusers.containsCaseInsensitive(name);
769: } else {
770: return localusers.contains(name);
771: }
772: }
773:
774: /**
775: * Returns the address of the postmaster for this server.
776: *
777: * @return the <code>MailAddress</code> for the postmaster
778: */
779: public MailAddress getPostmaster() {
780: return postmaster;
781: }
782:
783: /**
784: * Return the major version number for the server
785: *
786: * @return the major vesion number for the server
787: */
788: public int getMajorVersion() {
789: return 2;
790: }
791:
792: /**
793: * Return the minor version number for the server
794: *
795: * @return the minor vesion number for the server
796: */
797: public int getMinorVersion() {
798: return 3;
799: }
800:
801: /**
802: * Check whether the mail domain in question is to be
803: * handled by this server.
804: *
805: * @param serverName the name of the server to check
806: * @return whether the server is local
807: */
808: public boolean isLocalServer(final String serverName) {
809: return serverNames.contains(serverName.toLowerCase(Locale.US));
810: }
811:
812: /**
813: * Return the type of the server
814: *
815: * @return the type of the server
816: */
817: public String getServerInfo() {
818: return "Apache JAMES";
819: }
820:
821: /**
822: * Return the logger for the Mailet API
823: *
824: * @return the logger for the Mailet API
825: */
826: private Logger getMailetLogger() {
827: if (mailetLogger == null) {
828: mailetLogger = getLogger().getChildLogger("Mailet");
829: }
830: return mailetLogger;
831: }
832:
833: /**
834: * Log a message to the Mailet logger
835: *
836: * @param message the message to pass to the Mailet logger
837: */
838: public void log(String message) {
839: getMailetLogger().info(message);
840: }
841:
842: /**
843: * Log a message and a Throwable to the Mailet logger
844: *
845: * @param message the message to pass to the Mailet logger
846: * @param t the <code>Throwable</code> to be logged
847: */
848: public void log(String message, Throwable t) {
849: getMailetLogger().info(message, t);
850: }
851:
852: /**
853: * Adds a user to this mail server. Currently just adds user to a
854: * UsersRepository.
855: *
856: * @param userName String representing user name, that is the portion of
857: * an email address before the '@<domain>'.
858: * @param password String plaintext password
859: * @return boolean true if user added succesfully, else false.
860: *
861: * @deprecated we deprecated this in the MailServer interface and this is an implementation
862: * this component depends already depends on a UsersRepository: clients could directly
863: * use the addUser of the usersRepository.
864: */
865: public boolean addUser(String userName, String password) {
866: boolean success;
867: DefaultJamesUser user = new DefaultJamesUser(userName, "SHA");
868: user.setPassword(password);
869: user.initialize();
870: success = localusers.addUser(user);
871: return success;
872: }
873:
874: /**
875: * Performs DNS lookups as needed to find servers which should or might
876: * support SMTP.
877: * Returns an Iterator over HostAddress, a specialized subclass of
878: * javax.mail.URLName, which provides location information for
879: * servers that are specified as mail handlers for the given
880: * hostname. This is done using MX records, and the HostAddress
881: * instances are returned sorted by MX priority. If no host is
882: * found for domainName, the Iterator returned will be empty and the
883: * first call to hasNext() will return false.
884: *
885: * @see org.apache.james.DNSServer#getSMTPHostAddresses(String)
886: * @since Mailet API v2.2.0a16-unstable
887: * @param domainName - the domain for which to find mail servers
888: * @return an Iterator over HostAddress instances, sorted by priority
889: */
890: public Iterator getSMTPHostAddresses(String domainName) {
891: DNSServer dnsServer = null;
892: try {
893: dnsServer = (DNSServer) compMgr.lookup(DNSServer.ROLE);
894: } catch (final ServiceException cme) {
895: getLogger().error(
896: "Fatal configuration error - DNS Servers lost!",
897: cme);
898: throw new RuntimeException(
899: "Fatal configuration error - DNS Servers lost!");
900: }
901: return dnsServer.getSMTPHostAddresses(domainName);
902: }
903:
904: /**
905: * This method has been moved to LocalDelivery (the only client of the method).
906: * Now we can safely remove it from the Mailet API and from this implementation of MailetContext.
907: *
908: * The local field localDeliveryMailet will be removed when we remove the storeMail method.
909: *
910: * @deprecated since 2.2.0 look at the LocalDelivery code to find out how to do the local delivery.
911: * @see org.apache.mailet.MailetContext#storeMail(org.apache.mailet.MailAddress, org.apache.mailet.MailAddress, javax.mail.internet.MimeMessage)
912: */
913: public void storeMail(MailAddress sender, MailAddress recipient,
914: MimeMessage msg) throws MessagingException {
915: if (recipient == null) {
916: throw new IllegalArgumentException(
917: "Recipient for mail to be spooled cannot be null.");
918: }
919: if (msg == null) {
920: throw new IllegalArgumentException(
921: "Mail message to be spooled cannot be null.");
922: }
923: Collection recipients = new HashSet();
924: recipients.add(recipient);
925: MailImpl m = new MailImpl(getId(), sender, recipients, msg);
926: localDeliveryMailet.service(m);
927: ContainerUtil.dispose(m);
928: }
929: }
|