View Javadoc

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