View Javadoc

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