PageRenderTime 42ms CodeModel.GetById 14ms RepoModel.GetById 0ms app.codeStats 0ms

/projects/james-2.2.0/proposals/imap2/java/org/apache/james/James.java

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