PageRenderTime 51ms CodeModel.GetById 18ms RepoModel.GetById 0ms app.codeStats 1ms

/projects/james-2.2.0/src/java/org/apache/james/James.java

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