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;
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;
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
220
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
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
278
279
280
281
282
283
284
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
311 String postMasterAddress = conf.getChild("postmaster").getValue("postmaster").toLowerCase(Locale.US);
312
313
314
315 if (postMasterAddress.indexOf('@') < 0) {
316 String domainName = null;
317
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;
322 }
323 }
324
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
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
361
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
373 compMgr.put( MailServer.ROLE, this);
374
375
376
377
378
379 attributes.put(Constants.AVALON_COMPONENT_MANAGER, compMgr);
380
381
382 java.io.File configDir = AvalonContextUtilities.getFile(myContext, "file://conf/");
383 attributes.put("confDir", configDir.getCanonicalPath());
384
385
386
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
414
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
473 MailHeaders headers = new MailHeaders(msg);
474
475
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
531 getLogger().error("Null mailbox for non-null key");
532 throw new RuntimeException("Error in getUserInbox.");
533 } else {
534
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
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
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
684 reply.getMessage().setFrom(bouncer.toInternetAddress());
685 reply.getMessage().saveChanges();
686
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
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 }