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  
21  
22  package org.apache.james;
23  
24  import org.apache.avalon.cornerstone.services.store.Store;
25  import org.apache.avalon.framework.activity.Initializable;
26  import org.apache.avalon.framework.configuration.Configurable;
27  import org.apache.avalon.framework.configuration.Configuration;
28  import org.apache.avalon.framework.configuration.ConfigurationException;
29  import org.apache.avalon.framework.configuration.DefaultConfiguration;
30  import org.apache.avalon.framework.container.ContainerUtil;
31  import org.apache.avalon.framework.logger.AbstractLogEnabled;
32  import org.apache.avalon.framework.logger.Logger;
33  import org.apache.avalon.framework.service.DefaultServiceManager;
34  import org.apache.avalon.framework.service.ServiceException;
35  import org.apache.avalon.framework.service.ServiceManager;
36  import org.apache.avalon.framework.service.Serviceable;
37  import org.apache.commons.collections.map.ReferenceMap;
38  
39  import org.apache.james.api.dnsservice.DNSService;
40  import org.apache.james.api.dnsservice.TemporaryResolutionException;
41  import org.apache.james.api.domainlist.DomainList;
42  import org.apache.james.api.domainlist.ManageableDomainList;
43  import org.apache.james.api.user.UsersRepository;
44  import org.apache.james.api.user.UsersStore;
45  import org.apache.james.core.MailHeaders;
46  import org.apache.james.core.MailImpl;
47  import org.apache.james.impl.jamesuser.JamesUsersRepository;
48  import org.apache.james.services.FileSystem;
49  import org.apache.james.services.MailRepository;
50  import org.apache.james.services.MailServer;
51  import org.apache.james.services.SpoolRepository;
52  import org.apache.james.transport.MailetConfigImpl;
53  import org.apache.james.transport.mailets.LocalDelivery;
54  import org.apache.mailet.Mail;
55  import org.apache.mailet.MailAddress;
56  import org.apache.mailet.Mailet;
57  import org.apache.mailet.MailetContext;
58  import org.apache.mailet.base.RFC2822Headers;
59  
60  import javax.mail.Address;
61  import javax.mail.Message;
62  import javax.mail.MessagingException;
63  import javax.mail.internet.InternetAddress;
64  import javax.mail.internet.MimeMessage;
65  import javax.mail.internet.ParseException;
66  
67  import java.io.ByteArrayInputStream;
68  import java.io.InputStream;
69  import java.io.SequenceInputStream;
70  import java.net.UnknownHostException;
71  import java.util.ArrayList;
72  import java.util.Collection;
73  import java.util.Collections;
74  import java.util.Date;
75  import java.util.Enumeration;
76  import java.util.HashSet;
77  import java.util.Hashtable;
78  import java.util.Iterator;
79  import java.util.List;
80  import java.util.Locale;
81  import java.util.Map;
82  import java.util.Vector;
83  
84  /**
85   * Core class for JAMES. Provides three primary services:
86   * <br> 1) Instantiates resources, such as user repository, and protocol
87   * handlers
88   * <br> 2) Handles interactions between components
89   * <br> 3) Provides container services for Mailets
90   *
91   *
92   * @version This is $Revision: 722820 $
93  
94   */
95  public class James
96      extends AbstractLogEnabled
97      implements Serviceable, Configurable, Initializable, MailServer, MailetContext {
98  
99      /**
100      * The software name and version
101      */
102     private final static String SOFTWARE_NAME_VERSION = Constants.SOFTWARE_NAME + " " + Constants.SOFTWARE_VERSION;
103 
104     /**
105      * The component manager used both internally by James and by Mailets.
106      */
107     private DefaultServiceManager compMgr; //Components shared
108 
109     /**
110      * The top level configuration object for this server.
111      */
112     private Configuration conf = null;
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 spool used for processing mail handled by this server.
126      */
127     private SpoolRepository spool;
128 
129     /**
130      * The root URL used to get mailboxes from the repository
131      */
132     private String inboxRootURL;
133 
134     /**
135      * The user repository for this mail server.  Contains all the users with inboxes
136      * on this server.
137      */
138     private UsersRepository localusers;
139 
140     /**
141      * The collection of domain/server names for which this instance of James
142      * will receive and process mail.
143      */
144     private Collection serverNames;
145 
146     /**
147      * The number of mails generated.  Access needs to be synchronized for
148      * thread safety and to ensure that all threads see the latest value.
149      */
150     private static int count = 0;
151     private static final Object countLock = new Object();
152 
153     /**
154      * The address of the postmaster for this server
155      */
156     private MailAddress postmaster;
157 
158     /**
159      * A map used to store mailboxes and reduce the cost of lookup of individual
160      * mailboxes.
161      */
162     private Map mailboxes; //Not to be shared!
163 
164     /**
165      * A hash table of server attributes
166      * These are the MailetContext attributes
167      */
168     private Hashtable attributes = new Hashtable();
169 
170     /**
171      * Currently used by storeMail to avoid code duplication (we moved store logic to that mailet).
172      * TODO We should remove this and its initialization when we remove storeMail method.
173      */
174     protected Mailet localDeliveryMailet;
175 
176     private FileSystem fileSystem;
177 
178     private DomainList domains;
179     
180     private boolean virtualHosting = false;
181     
182     private String defaultDomain = null;
183     
184     private String helloName = null;
185 
186 
187     /**
188      * @see org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
189      */
190     public void service(ServiceManager comp) throws ServiceException {
191         compMgr = new DefaultServiceManager(comp);
192         mailboxes = new ReferenceMap();
193         setFileSystem((FileSystem) comp.lookup(FileSystem.ROLE));
194         domains = (DomainList) comp.lookup(DomainList.ROLE);
195     }
196 
197     /**
198      * Sets the fileSystem service
199      * 
200      * @param system the new service
201      */
202     private void setFileSystem(FileSystem system) {
203         this.fileSystem = system;
204     }
205 
206     /**
207      * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
208      */
209     public void configure(Configuration conf) {
210         this.conf = conf;
211     }
212 
213     /**
214      * @see org.apache.avalon.framework.activity.Initializable#initialize()
215      */
216     public void initialize() throws Exception {
217 
218         final Logger logger = getLogger();
219         logger.info("JAMES init...");
220 
221         initializeServices();
222 
223         Configuration userNamesConf = conf.getChild("usernames");
224         if (userNamesConf != null) {
225             if (localusers instanceof JamesUsersRepository) {
226                 logger.warn("<usernames> parameter in James block is deprecated. Please configure this data in UsersRepository block: configuration injected for backward compatibility");
227                 ((JamesUsersRepository) localusers).setIgnoreCase(userNamesConf.getAttributeAsBoolean("ignoreCase", false));
228                 ((JamesUsersRepository) localusers).setEnableAliases(userNamesConf.getAttributeAsBoolean("enableAliases", false));
229                 ((JamesUsersRepository) localusers).setEnableForwarding(userNamesConf.getAttributeAsBoolean("enableForwarding", false));
230             } else {
231                 logger.error("<usernames> parameter is no more supported. Backward compatibility is provided when using an AbstractUsersRepository but this repository is a "+localusers.getClass().toString());
232             }
233         }
234         
235         Configuration serverConf = conf.getChild("servernames");
236         if (serverConf != null) {
237             if (domains instanceof ManageableDomainList) {
238                 logger.warn("<servernames> parameter in James block is deprecated. Please configure this data in domainlist block: configuration injected for backward compatibility");
239                 ManageableDomainList dom = (ManageableDomainList) domains;
240                 dom.setAutoDetect(serverConf.getAttributeAsBoolean("autodetect",true));    
241                 dom.setAutoDetectIP(serverConf.getAttributeAsBoolean("autodetectIP", true));
242             
243                 Configuration[] serverNameConfs = serverConf.getChildren( "servername" );
244                 for ( int i = 0; i < serverNameConfs.length; i++ ) {
245                     dom.addDomain( serverNameConfs[i].getValue().toLowerCase(Locale.US));
246                 }
247             } else {
248                 logger.error("<servernames> parameter is no more supported. Backward compatibility is provided when using an XMLDomainList");
249             }
250         }
251 
252         initializeServernamesAndPostmaster();
253 
254         // We don't need this. UsersRepository.ROLE is already in the compMgr we received
255         // We've just looked up it from the cmpManager
256         // compMgr.put( UsersRepository.ROLE, localusers);
257         // getLogger().info("Local users repository opened");
258 
259         inboxRootURL = conf.getChild("inboxRepository").getChild("repository").getAttribute("destinationURL");
260 
261         logger.info("Private Repository LocalInbox opened");
262         
263         Configuration virtualHostingConfig = conf.getChild("enableVirtualHosting");
264         if (virtualHostingConfig != null ) {
265             virtualHosting = virtualHostingConfig.getValueAsBoolean(false);
266         }
267         
268         logger.info("VirtualHosting supported: " + virtualHosting);
269         
270         Configuration defaultDomainConfig = conf.getChild("defaultDomain");
271         if (defaultDomainConfig != null ) {
272             defaultDomain = defaultDomainConfig.getValue(null);
273         } else if (virtualHosting) {
274             throw new ConfigurationException("Please configure a defaultDomain if using VirtualHosting");
275         }
276         
277         logger.info("Defaultdomain: " + defaultDomain);
278         
279         Configuration helloNameConfig = conf.getChild("helloName");
280         if (helloNameConfig != null) {
281             boolean autodetect = helloNameConfig.getAttributeAsBoolean("autodetect", true);
282             if (autodetect) {
283                 try {
284                     helloName = lookupDNSServer().getHostName(lookupDNSServer().getLocalHost());
285                 } catch (UnknownHostException e) {
286                     helloName = "localhost";
287                 }
288             } else {
289                 // Should we use the defaultdomain here ?
290                 helloName = helloNameConfig.getValue(defaultDomain);
291             }
292             attributes.put(Constants.HELLO_NAME, helloName);
293         }
294 
295         // Add this to comp
296         compMgr.put( MailServer.ROLE, this);
297 
298         // For mailet engine provide MailetContext
299         //compMgr.put("org.apache.mailet.MailetContext", this);
300         // For AVALON aware mailets and matchers, we put the Component object as
301         // an attribute
302         attributes.put(Constants.AVALON_COMPONENT_MANAGER, compMgr);
303 
304         //Temporary get out to allow complex mailet config files to stop blocking sergei sozonoff's work on bouce processing
305         String confDir;
306         try {
307             confDir = conf.getChild("configuration-directory").getValue();
308         } catch (ConfigurationException e) {
309             if (logger.isInfoEnabled()) {
310                 logger.info("Failed to read configuration directory configuration. Will continue using default.");
311             }
312             logger.debug("Failed to read configuration directory configuration", e);
313             confDir = null;
314         }
315         // defaults to the old behaviour
316         if (confDir == null) confDir = "file://conf/";
317         java.io.File configDir = fileSystem.getFile(confDir);
318         attributes.put("confDir", configDir.getCanonicalPath());
319 
320         try {
321             attributes.put(Constants.HOSTADDRESS, lookupDNSServer().getLocalHost().getHostAddress());
322             attributes.put(Constants.HOSTNAME, lookupDNSServer().getLocalHost().getHostName());
323         } catch (java.net.UnknownHostException _) {
324             attributes.put(Constants.HOSTADDRESS, "127.0.0.1");
325             attributes.put(Constants.HOSTNAME, "localhost");
326         }
327         
328         initializeLocalDeliveryMailet();
329 
330         System.out.println(SOFTWARE_NAME_VERSION);
331         logger.info("JAMES ...init end");
332     }
333 
334     private void initializeServices() throws Exception {
335         // TODO: This should retrieve a more specific named thread pool from
336         // Context that is set up in server.xml
337         try {
338             Store store = (Store) compMgr.lookup( Store.ROLE );
339             setStore(store);
340             if (getLogger().isDebugEnabled()) {
341                 getLogger().debug("Using Store: " + store.toString());
342             }
343         } catch (Exception e) {
344             if (getLogger().isWarnEnabled()) {
345                 getLogger().warn("Can't get Store: " + e);
346             }
347         }
348 
349         try {
350             SpoolRepository spool = (SpoolRepository) compMgr.lookup(SpoolRepository.ROLE);
351             setSpool(spool);
352             if (getLogger().isDebugEnabled()) {
353                 getLogger().debug("Using SpoolRepository: " + spool.toString());
354             }
355         } catch (Exception e) {
356             if (getLogger().isWarnEnabled()) {
357                 getLogger().warn("Can't get spoolRepository: " + e);
358             }
359         }
360 
361         try {
362             // lookup the usersStore.
363             // This is not used by James itself, but we check we received it here
364             // because mailets will try to lookup this later.
365             UsersStore usersStore = (UsersStore) compMgr.lookup( UsersStore.ROLE );
366             if (getLogger().isDebugEnabled()) {
367                 getLogger().debug("Using UsersStore: " + usersStore.toString());
368             }
369         } catch (Exception e) {
370             if (getLogger().isWarnEnabled()) {
371                 getLogger().warn("Can't get Store: " + e);
372             }
373         }
374 
375         try {
376             UsersRepository localusers = (UsersRepository) compMgr.lookup(UsersRepository.ROLE);
377             setLocalusers(localusers);
378             if (getLogger().isDebugEnabled()) {
379                 getLogger().debug("Using LocalUsersRepository: " + localusers.toString());
380             }
381         } catch (Exception e) {
382             getLogger().error("Cannot open private UserRepository");
383             throw e;
384         }
385     }
386 
387     private void initializeServernamesAndPostmaster() throws ConfigurationException, ParseException {
388         String defaultDomain = getDefaultDomain();
389         if (domains.containsDomain(defaultDomain) == false) {
390             if (domains instanceof ManageableDomainList) {
391                 if(((ManageableDomainList) domains).addDomain(defaultDomain) != false) {
392                     throw new ConfigurationException("Configured defaultdomain could not get added to DomainList");
393                 }
394             } else {
395                 throw new ConfigurationException("Configured defaultDomain not exist in DomainList");
396             }
397         }
398         serverNames = domains.getDomains();
399 
400         if (serverNames == null || serverNames.size() == 0) throw new ConfigurationException("No domainnames configured");
401         
402         // used by RemoteDelivery for HELO
403         attributes.put(Constants.DEFAULT_DOMAIN, defaultDomain);
404 
405         // Get postmaster
406         String postMasterAddress = conf.getChild("postmaster").getValue("postmaster").toLowerCase(Locale.US);
407         // if there is no @domain part, then add the first one from the
408         // list of supported domains that isn't localhost.  If that
409         // doesn't work, use the hostname, even if it is localhost.
410         if (postMasterAddress.indexOf('@') < 0) {
411             String domainName = null;    // the domain to use
412             // loop through candidate domains until we find one or exhaust the list
413             Iterator i = serverNames.iterator();
414             while (i.hasNext()) {
415                 String serverName = i.next().toString().toLowerCase(Locale.US);
416                 if (!("localhost".equals(serverName))) {
417                     domainName = serverName; // ok, not localhost, so use it
418                     continue;
419                 }
420             }
421             // if we found a suitable domain, use it.  Otherwise fallback to the host name.
422             postMasterAddress = postMasterAddress + "@" + (domainName != null ? domainName : defaultDomain);
423         }
424         this.postmaster = new MailAddress( postMasterAddress );
425 
426         if (!isLocalServer(postmaster.getHost())) {
427             StringBuffer warnBuffer
428                     = new StringBuffer(320)
429                     .append("The specified postmaster address ( ")
430                     .append(postmaster)
431                     .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.");
432             getLogger().warn(warnBuffer.toString());
433         }
434     }
435 
436     private void initializeLocalDeliveryMailet() throws MessagingException {
437         // We can safely remove this and the localDeliveryField when we 
438         // remove the storeMail method from James and from the MailetContext
439         DefaultConfiguration conf = new DefaultConfiguration("mailet", "generated:James.initialize()");
440         MailetConfigImpl configImpl = new MailetConfigImpl();
441         configImpl.setMailetName("LocalDelivery");
442         configImpl.setConfiguration(conf);
443         configImpl.setMailetContext(this);
444         localDeliveryMailet = new LocalDelivery();
445         localDeliveryMailet.init(configImpl);
446     }
447 
448     /**
449      * Set Store to use
450      * 
451      * @param store the Store to use
452      */
453     public void setStore(Store store) {
454         this.store = store;
455     }
456 
457     /**
458      * Set the SpoolRepository to use
459      * 
460      * @param spool the SpoleRepository to use
461      */
462     public void setSpool(SpoolRepository spool) {
463         this.spool = spool;
464     }
465 
466     /**
467      * Set the UsersRepository to use
468      * 
469      * @param localusers the UserRepository to use
470      */
471     public void setLocalusers(UsersRepository localusers) {
472         this.localusers = localusers;
473     }
474 
475     /**
476      * Place a mail on the spool for processing
477      *
478      * @param message the message to send
479      *
480      * @throws MessagingException if an exception is caught while placing the mail
481      *                            on the spool
482      */
483     public void sendMail(MimeMessage message) throws MessagingException {
484         MailAddress sender = new MailAddress((InternetAddress)message.getFrom()[0]);
485         Collection recipients = new HashSet();
486         Address addresses[] = message.getAllRecipients();
487         if (addresses != null) {
488             for (int i = 0; i < addresses.length; i++) {
489                 // Javamail treats the "newsgroups:" header field as a
490                 // recipient, so we want to filter those out.
491                 if ( addresses[i] instanceof InternetAddress ) {
492                     recipients.add(new MailAddress((InternetAddress)addresses[i]));
493                 }
494             }
495         }
496         sendMail(sender, recipients, message);
497     }
498 
499     /**
500      * @see org.apache.james.services.MailServer#sendMail(MailAddress, Collection, MimeMessage)
501      */
502     public void sendMail(MailAddress sender, Collection recipients, MimeMessage message)
503             throws MessagingException {
504         sendMail(sender, recipients, message, Mail.DEFAULT);
505     }
506 
507     /**
508      * @see org.apache.mailet.MailetContext#sendMail(MailAddress, Collection, MimeMessage, String)
509      */
510     public void sendMail(MailAddress sender, Collection recipients, MimeMessage message, String state)
511             throws MessagingException {
512         MailImpl mail = new MailImpl(getId(), sender, recipients, message);
513         try {
514             mail.setState(state);
515             sendMail(mail);
516         } finally {
517             ContainerUtil.dispose(mail);
518         }
519     }
520 
521     /**
522      * @see org.apache.james.services.MailServer#sendMail(MailAddress, Collection, InputStream)
523      */
524     public void sendMail(MailAddress sender, Collection recipients, InputStream msg)
525             throws MessagingException {
526         // parse headers
527         MailHeaders headers = new MailHeaders(msg);
528 
529         // if headers do not contains minimum REQUIRED headers fields throw Exception
530         if (!headers.isValid()) {
531             throw new MessagingException("Some REQURED header field is missing. Invalid Message");
532         }
533         ByteArrayInputStream headersIn = new ByteArrayInputStream(headers.toByteArray());
534         sendMail(new MailImpl(getId(), sender, recipients, new SequenceInputStream(headersIn, msg)));
535     }
536 
537     /**
538      * @see org.apache.james.services.MailServer#sendMail(Mail)
539      */
540     public void sendMail(Mail mail) throws MessagingException {
541         try {
542             spool.store(mail);
543         } catch (Exception e) {
544             getLogger().error("Error storing message: " + e.getMessage(),e);
545             try {
546                 spool.remove(mail);
547             } catch (Exception ignored) {
548                 getLogger().error("Error removing message after an error storing it: " + e.getMessage(),e);
549             }
550             throw new MessagingException("Exception spooling message: " + e.getMessage(), e);
551         }
552         if (getLogger().isDebugEnabled()) {
553             StringBuffer logBuffer =
554                 new StringBuffer(64)
555                         .append("Mail ")
556                         .append(mail.getName())
557                         .append(" pushed in spool");
558             getLogger().debug(logBuffer.toString());
559         }
560     }
561 
562     /**
563      * @see org.apache.james.services.MailServer#getUserInbox(java.lang.String)
564      */
565     public synchronized MailRepository getUserInbox(String userName) {
566         MailRepository userInbox = null;
567         
568         if (virtualHosting == false && (userName.indexOf("@") < 0) == false) {
569             userName = userName.split("@")[0];
570         }
571 
572         userInbox = (MailRepository) mailboxes.get(userName);
573 
574         if (userInbox != null) {
575             return userInbox;
576         /*
577          * we're using a ReferenceMap with HARD keys and SOFT values
578          * so it could happen to find a null value after a second pass
579          * of a full GC and we should simply lookup it again
580          */
581 //        } else if (mailboxes.containsKey(userName)) {
582 //            // we have a problem
583 //            getLogger().error("Null mailbox for non-null key");
584 //            throw new RuntimeException("Error in getUserInbox.");
585         } else {
586             // need mailbox object
587             if (getLogger().isDebugEnabled()) {
588                 getLogger().debug("Retrieving and caching inbox for " + userName );
589             }
590 
591             StringBuffer destinationBuffer = new StringBuffer(192);
592                   
593             if (virtualHosting == true && inboxRootURL.startsWith("file://") && !(userName.indexOf("@") < 0)) {
594                 String userArgs[] = userName.split("@");
595                             
596                 // build the url like : file://var/mail/inboxes/domain/username/
597                 destinationBuffer.append(inboxRootURL).append(userArgs[1]).append("/").append(userArgs[0]).append("/");
598             } else {
599                 destinationBuffer.append(inboxRootURL).append(userName).append("/");
600             }
601                  
602             String destination = destinationBuffer.toString();
603             try {
604                 // Copy the inboxRepository configuration and modify the destinationURL
605                 DefaultConfiguration mboxConf = new DefaultConfiguration(conf
606                         .getChild("inboxRepository").getChild("repository"));
607                 mboxConf.setAttribute("destinationURL", destination);
608 
609                 userInbox = (MailRepository) store.select(mboxConf);
610                 if (userInbox!=null) {
611                     mailboxes.put(userName, userInbox);
612                 }
613             } catch (Exception e) {
614                 if (getLogger().isErrorEnabled()) {
615                     getLogger().error("Cannot open user Mailbox",e);
616                 }
617                 throw new RuntimeException("Error in getUserInbox.",e);
618             }
619             return userInbox;
620         }
621     }
622 
623     /**
624      * <p>Note that this method ensures that James cannot be run in a distributed
625      * fashion.</p>
626      * <p>Two instances may return the same ID. 
627      * There are various ways that this could be fixed. 
628      * The most obvious would be to add a unique prefix. 
629      * The best approach would be for each instance to be configured
630      * with a name which would then be combined with the network
631      * address (for example, james.name@mail.example.org) to create a
632      * unique James instance identifier.
633      * </p><p> 
634      * Alternatively, using a data store backed identifier (for example, from a sequence
635      * when DB backed) should be enough to gaurantee uniqueness. This would imply
636      * that the Mail interface or the spool store should be responsible for creating
637      * new Mail implementations with ID preassigned. 
638      * </p><p>
639      * It would be useful for each 
640      * James cluster to have a unique name. Perhaps a random number could be generated by 
641      * the spool store upon first initialisation.
642      * </p><p>
643      * This ID is most likely
644      * to be used as message ID so this is probably useful in any case.
645      * </p>
646      * 
647      * @see org.apache.james.services.MailServer#getId()
648      */
649     public String getId() {
650         
651         final long localCount;
652         synchronized (countLock) {
653             localCount = count++;
654         }
655         StringBuffer idBuffer =
656             new StringBuffer(64)
657                     .append("Mail")
658                     .append(System.currentTimeMillis())
659                     .append("-")
660                     .append(localCount);
661         return idBuffer.toString();
662     }
663 
664     /**
665      * The main method.  Should never be invoked, as James must be called
666      * from within an Avalon framework container.
667      *
668      * @param args the command line arguments
669      */
670     public static void main(String[] args) {
671         System.out.println("ERROR!");
672         System.out.println("Cannot execute James as a stand alone application.");
673         System.out.println("To run James, you need to have the Avalon framework installed.");
674         System.out.println("Please refer to the Readme file to know how to run James.");
675     }
676 
677     //Methods for MailetContext
678 
679     /**
680      * @see org.apache.mailet.MailetContext#getMailServers(String)
681      */
682     public Collection getMailServers(String host) {
683         try {
684             return lookupDNSServer().findMXRecords(host);
685         } catch (TemporaryResolutionException e) {
686             //TODO: We only do this to not break backward compatiblity. Should fixed later
687             return Collections.unmodifiableCollection(new ArrayList(0));
688         }
689     }
690 
691     /**
692      * @see org.apache.mailet.MailetContext#getAttribute(java.lang.String)
693      */
694     public Object getAttribute(String key) {
695         return attributes.get(key);
696     }
697 
698     /**
699      * @see org.apache.mailet.MailetContext#setAttribute(java.lang.String, java.lang.Object)
700      */
701     public void setAttribute(String key, Object object) {
702         attributes.put(key, object);
703     }
704 
705     /**
706      * @see org.apache.mailet.MailetContext#removeAttribute(java.lang.String)
707      */
708     public void removeAttribute(String key) {
709         attributes.remove(key);
710     }
711 
712     /**
713      * @see org.apache.mailet.MailetContext#getAttributeNames()
714      */
715     public Iterator getAttributeNames() {
716         Vector names = new Vector();
717         for (Enumeration e = attributes.keys(); e.hasMoreElements(); ) {
718             names.add(e.nextElement());
719         }
720         return names.iterator();
721     }
722 
723     /**
724      * This generates a response to the Return-Path address, or the address of
725      * the message's sender if the Return-Path is not available.  Note that
726      * this is different than a mail-client's reply, which would use the
727      * Reply-To or From header. This will send the bounce with the server's
728      * postmaster as the sender.
729      * 
730      * @see org.apache.mailet.MailetContext#bounce(Mail, String)
731      */
732     public void bounce(Mail mail, String message) throws MessagingException {
733         bounce(mail, message, getPostmaster());
734     }
735 
736     /**
737      * This generates a response to the Return-Path address, or the
738      * address of the message's sender if the Return-Path is not
739      * available.  Note that this is different than a mail-client's
740      * reply, which would use the Reply-To or From header.
741      *
742      * Bounced messages are attached in their entirety (headers and
743      * content) and the resulting MIME part type is "message/rfc822".
744      *
745      * The attachment to the subject of the original message (or "No
746      * Subject" if there is no subject in the original message)
747      *
748      * There are outstanding issues with this implementation revolving
749      * around handling of the return-path header.
750      *
751      * MIME layout of the bounce message:
752      *
753      * multipart (mixed)/
754      *     contentPartRoot (body) = mpContent (alternative)/
755      *           part (body) = message
756      *     part (body) = original
757      *
758      * @see org.apache.mailet.MailetContext#bounce(Mail, String, MailAddress) 
759      */
760 
761     public void bounce(Mail mail, String message, MailAddress bouncer) throws MessagingException {
762         if (mail.getSender() == null) {
763             if (getLogger().isInfoEnabled())
764                 getLogger().info("Mail to be bounced contains a null (<>) reverse path.  No bounce will be sent.");
765             return;
766         } else {
767             // Bounce message goes to the reverse path, not to the Reply-To address
768             if (getLogger().isInfoEnabled())
769                 getLogger().info("Processing a bounce request for a message with a reverse path of " + mail.getSender().toString());
770         }
771 
772         MailImpl reply = rawBounce(mail,message);
773         //Change the sender...
774         reply.getMessage().setFrom(bouncer.toInternetAddress());
775         reply.getMessage().saveChanges();
776         //Send it off ... with null reverse-path
777         reply.setSender(null);
778         sendMail(reply);
779         ContainerUtil.dispose(reply);
780     }
781 
782     /**
783      * Generates a bounce mail that is a bounce of the original message.
784      *
785      * @param bounceText the text to be prepended to the message to describe the bounce condition
786      *
787      * @return the bounce mail
788      *
789      * @throws MessagingException if the bounce mail could not be created
790      */
791     private MailImpl rawBounce(Mail mail, String bounceText) throws MessagingException {
792         //This sends a message to the james component that is a bounce of the sent message
793         MimeMessage original = mail.getMessage();
794         MimeMessage reply = (MimeMessage) original.reply(false);
795         reply.setSubject("Re: " + original.getSubject());
796         reply.setSentDate(new Date());
797         Collection recipients = new HashSet();
798         recipients.add(mail.getSender());
799         InternetAddress addr[] = { new InternetAddress(mail.getSender().toString())};
800         reply.setRecipients(Message.RecipientType.TO, addr);
801         reply.setFrom(new InternetAddress(mail.getRecipients().iterator().next().toString()));
802         reply.setText(bounceText);
803         reply.setHeader(RFC2822Headers.MESSAGE_ID, "replyTo-" + mail.getName());
804         return new MailImpl(
805             "replyTo-" + mail.getName(),
806             new MailAddress(mail.getRecipients().iterator().next().toString()),
807             recipients,
808             reply);
809     }
810 
811     /**
812      * @see org.apache.mailet.MailetContext#isLocalUser(String)
813      */
814     public boolean isLocalUser(String name) {
815         if (name == null) {
816             return false;
817         }
818         try {
819             if (name.indexOf("@") == -1) {
820                 return isLocalEmail(new MailAddress(name,"localhost"));
821             } else {
822                 return isLocalEmail(new MailAddress(name));
823             }
824         } catch (ParseException e) {
825             log("Error checking isLocalUser for user "+name);
826             return false;
827         }
828     }
829     
830     /**
831      * @see org.apache.mailet.MailetContext#isLocalEmail(org.apache.mailet.MailAddress)
832      */
833     public boolean isLocalEmail(MailAddress mailAddress) {
834     String userName = mailAddress.toString();
835         if (!isLocalServer(mailAddress.getHost())) {
836             return false;
837         }
838         if (virtualHosting == false) {
839             userName = mailAddress.getUser();
840         }
841         return localusers.contains(userName);
842     }
843 
844     /**
845      * @see org.apache.mailet.MailetContext#getPostmaster()
846      */
847     public MailAddress getPostmaster() {
848         return postmaster;
849     }
850 
851     /**
852      * @see org.apache.mailet.MailetContext#getMajorVersion()
853      */
854     public int getMajorVersion() {
855         return 2;
856     }
857 
858     /**
859      * @see org.apache.mailet.MailetContext#getMinorVersion()
860      */
861     public int getMinorVersion() {
862         return 4;
863     }
864 
865     /**
866      * @see org.apache.james.services.MailServer#isLocalServer(java.lang.String)
867      */
868     public boolean isLocalServer( final String serverName ) {
869         String lowercase = serverName.toLowerCase(Locale.US);
870        
871         // Check if the serverName is localhost or the DomainList implementation contains the serverName. This
872         // allow some implementations to act more dynamic
873         if ("localhost".equals(serverName) || domains.containsDomain(lowercase)){
874             return  true;
875         } else {
876             return false;
877         }
878     }
879 
880     /**
881      * @see org.apache.mailet.MailetContext#getServerInfo()
882      */
883     public String getServerInfo() {
884         return "Apache JAMES";
885     }
886 
887     /**
888      * Return the logger for the Mailet API
889      *
890      * @return the logger for the Mailet API
891      */
892     private Logger getMailetLogger() {
893         if (mailetLogger == null) {
894             mailetLogger = getLogger().getChildLogger("Mailet");
895         }
896         return mailetLogger;
897     }
898 
899     /**
900      * @see org.apache.mailet.MailetContext#log(java.lang.String)
901      */
902     public void log(String message) {
903         getMailetLogger().info(message);
904     }
905 
906     /**
907      * @see org.apache.mailet.MailetContext#log(java.lang.String, java.lang.Throwable)
908      */
909     public void log(String message, Throwable t) {
910         getMailetLogger().info(message,t);
911     }
912 
913     /**
914      * Adds a user to this mail server. Currently just adds user to a
915      * UsersRepository.
916      *
917      * @param userName String representing user name, that is the portion of
918      * an email address before the '@<domain>'.
919      * @param password String plaintext password
920      * @return boolean true if user added succesfully, else false.
921      * 
922      * @deprecated we deprecated this in the MailServer interface and this is an implementation
923      * this component depends already depends on a UsersRepository: clients could directly 
924      * use the addUser of the usersRepository.
925      */
926     public boolean addUser(String userName, String password) {
927         return localusers.addUser(userName, password);
928     }
929 
930     /**
931      * Performs DNS lookups as needed to find servers which should or might
932      * support SMTP.
933      * Returns an Iterator over HostAddress, a specialized subclass of
934      * javax.mail.URLName, which provides location information for
935      * servers that are specified as mail handlers for the given
936      * hostname.  This is done using MX records, and the HostAddress
937      * instances are returned sorted by MX priority.  If no host is
938      * found for domainName, the Iterator returned will be empty and the
939      * first call to hasNext() will return false.
940      *
941      * @see org.apache.james.api.dnsservice.DNSService#getSMTPHostAddresses(String)
942      * @since Mailet API v2.2.0a16-unstable
943      * @param domainName - the domain for which to find mail servers
944      * @return an Iterator over HostAddress instances, sorted by priority
945      */
946     public Iterator getSMTPHostAddresses(String domainName) {
947         try {
948             return lookupDNSServer().getSMTPHostAddresses(domainName);
949         } catch (TemporaryResolutionException e) {
950             //TODO: We only do this to not break backward compatiblity. Should fixed later
951             return Collections.unmodifiableCollection(new ArrayList(0)).iterator();
952         }
953     }
954 
955     protected DNSService lookupDNSServer() {
956         DNSService dnsServer;
957         try {
958             dnsServer = (DNSService) compMgr.lookup( DNSService.ROLE );
959         } catch ( final ServiceException cme ) {
960             getLogger().error("Fatal configuration error - DNS Servers lost!", cme );
961             throw new RuntimeException("Fatal configuration error - DNS Servers lost!");
962         }
963         return dnsServer;
964     }
965 
966     /**
967      * This method has been moved to LocalDelivery (the only client of the method).
968      * Now we can safely remove it from the Mailet API and from this implementation of MailetContext.
969      *
970      * The local field localDeliveryMailet will be removed when we remove the storeMail method.
971      * 
972      * @deprecated since 2.2.0 look at the LocalDelivery code to find out how to do the local delivery.
973      * @see org.apache.mailet.MailetContext#storeMail(org.apache.mailet.MailAddress, org.apache.mailet.MailAddress, javax.mail.internet.MimeMessage)
974      */
975     public void storeMail(MailAddress sender, MailAddress recipient, MimeMessage msg) throws MessagingException {
976         if (recipient == null) {
977             throw new IllegalArgumentException("Recipient for mail to be spooled cannot be null.");
978         }
979         if (msg == null) {
980             throw new IllegalArgumentException("Mail message to be spooled cannot be null.");
981         }
982         Collection recipients = new HashSet();
983         recipients.add(recipient);
984         MailImpl m = new MailImpl(getId(),sender,recipients,msg);
985         localDeliveryMailet.service(m);
986         ContainerUtil.dispose(m);
987     }
988    
989     /**
990      * @see org.apache.james.services.MailServer#supportVirtualHosting()
991      */
992     public boolean supportVirtualHosting() {
993         return virtualHosting;
994     }
995 
996     /**
997      * @see org.apache.james.services.MailServer#getDefaultDomain()
998      */
999     public String getDefaultDomain() {
1000         if (defaultDomain == null) {
1001             List domainList = domains.getDomains();
1002             if (domainList == null || domainList.isEmpty()) {
1003                 return "localhost";
1004             } else {
1005                 return (String) domainList.get(0);
1006             }  
1007         } else {
1008             return defaultDomain;
1009         }
1010     }
1011 
1012     /**
1013      * @see org.apache.james.services.MailServer#getHelloName()
1014      */
1015     public String getHelloName() {
1016         if (helloName != null) {
1017             return helloName;
1018         } else {
1019             String hello = (String) getAttribute(Constants.HELLO_NAME);   
1020             if (hello == null) {
1021                 return defaultDomain;
1022             } else {
1023                 return hello;
1024             }
1025         }
1026     }
1027 }