Fields Summary |
---|
private static final String | SOFTWARE_NAME_VERSIONThe software name and version |
private org.apache.avalon.framework.service.DefaultServiceManager | compMgrThe component manager used both internally by James and by Mailets. |
private org.apache.avalon.framework.context.DefaultContext | contextTODO: Investigate what this is supposed to do. Looks like it
was supposed to be the Mailet context. |
private org.apache.avalon.framework.configuration.Configuration | confThe top level configuration object for this server. |
private org.apache.avalon.framework.logger.Logger | mailetLoggerThe logger used by the Mailet API. |
private org.apache.avalon.cornerstone.services.store.Store | storeThe mail store containing the inbox repository and the spool. |
private org.apache.james.services.UsersStore | usersStoreThe store containing the local user repository. |
private org.apache.james.services.SpoolRepository | spoolThe spool used for processing mail handled by this server. |
private String | inboxRootURLThe root URL used to get mailboxes from the repository |
private org.apache.james.services.UsersRepository | localusersThe user repository for this mail server. Contains all the users with inboxes
on this server. |
private Collection | serverNamesThe collection of domain/server names for which this instance of James
will receive and process mail. |
private boolean | ignoreCaseWhether to ignore case when looking up user names on this server |
private static long | countThe number of mails generated. Access needs to be synchronized for
thread safety and to ensure that all threads see the latest value. |
private org.apache.mailet.MailAddress | postmasterThe address of the postmaster for this server |
private Map | mailboxesA map used to store mailboxes and reduce the cost of lookup of individual
mailboxes. |
private Hashtable | attributesA hash table of server attributes
These are the MailetContext attributes |
protected org.apache.avalon.framework.context.Context | myContextThe Avalon context used by the instance |
protected org.apache.mailet.Mailet | localDeliveryMailetCurrently used by storeMail to avoid code duplication (we moved store logic to that mailet).
TODO We should remove this and its initialization when we remove storeMail method. |
Methods Summary |
---|
public boolean | addUser(java.lang.String userName, java.lang.String password)Adds a user to this mail server. Currently just adds user to a
UsersRepository.
boolean success;
DefaultJamesUser user = new DefaultJamesUser(userName, "SHA");
user.setPassword(password);
user.initialize();
success = localusers.addUser(user);
return success;
|
public void | bounce(org.apache.mailet.Mail mail, java.lang.String message)This generates a response to the Return-Path address, or the address of
the message's sender if the Return-Path is not available. Note that
this is different than a mail-client's reply, which would use the
Reply-To or From header. This will send the bounce with the server's
postmaster as the sender.
bounce(mail, message, getPostmaster());
|
public void | bounce(org.apache.mailet.Mail mail, java.lang.String message, org.apache.mailet.MailAddress bouncer)This generates a response to the Return-Path address, or the
address of the message's sender if the Return-Path is not
available. Note that this is different than a mail-client's
reply, which would use the Reply-To or From header.
Bounced messages are attached in their entirety (headers and
content) and the resulting MIME part type is "message/rfc822".
The attachment to the subject of the original message (or "No
Subject" if there is no subject in the original message)
There are outstanding issues with this implementation revolving
around handling of the return-path header.
MIME layout of the bounce message:
multipart (mixed)/
contentPartRoot (body) = mpContent (alternative)/
part (body) = message
part (body) = original
if (mail.getSender() == null) {
if (getLogger().isInfoEnabled())
getLogger().info("Mail to be bounced contains a null (<>) reverse path. No bounce will be sent.");
return;
} else {
// Bounce message goes to the reverse path, not to the Reply-To address
if (getLogger().isInfoEnabled())
getLogger().info("Processing a bounce request for a message with a reverse path of " + mail.getSender().toString());
}
MailImpl reply = rawBounce(mail,message);
//Change the sender...
reply.getMessage().setFrom(bouncer.toInternetAddress());
reply.getMessage().saveChanges();
//Send it off ... with null reverse-path
reply.setSender(null);
sendMail(reply);
ContainerUtil.dispose(reply);
|
public void | configure(org.apache.avalon.framework.configuration.Configuration conf)
this.conf = conf;
|
public void | contextualize(org.apache.avalon.framework.context.Context context)
this.myContext = context;
|
public java.lang.Object | getAttribute(java.lang.String key)
return attributes.get(key);
|
public java.util.Iterator | getAttributeNames()
Vector names = new Vector();
for (Enumeration e = attributes.keys(); e.hasMoreElements(); ) {
names.add(e.nextElement());
}
return names.iterator();
|
public java.lang.String | getId()Return a new mail id.
long localCount = -1;
synchronized (James.class) {
localCount = count++;
}
StringBuffer idBuffer =
new StringBuffer(64)
.append("Mail")
.append(System.currentTimeMillis())
.append("-")
.append(localCount);
return idBuffer.toString();
|
public java.util.Collection | getMailServers(java.lang.String host)Get the prioritized list of mail servers for a given host.
TODO: This needs to be made a more specific ordered subtype of Collection.
DNSServer dnsServer = null;
try {
dnsServer = (DNSServer) compMgr.lookup( DNSServer.ROLE );
} catch ( final ServiceException cme ) {
getLogger().error("Fatal configuration error - DNS Servers lost!", cme );
throw new RuntimeException("Fatal configuration error - DNS Servers lost!");
}
return dnsServer.findMXRecords(host);
|
private org.apache.avalon.framework.logger.Logger | getMailetLogger()Return the logger for the Mailet API
if (mailetLogger == null) {
mailetLogger = getLogger().getChildLogger("Mailet");
}
return mailetLogger;
|
public int | getMajorVersion()Return the major version number for the server
return 2;
|
public int | getMinorVersion()Return the minor version number for the server
return 3;
|
public org.apache.mailet.MailAddress | getPostmaster()Returns the address of the postmaster for this server.
return postmaster;
|
public java.util.Iterator | getSMTPHostAddresses(java.lang.String domainName)Performs DNS lookups as needed to find servers which should or might
support SMTP.
Returns an Iterator over HostAddress, a specialized subclass of
javax.mail.URLName, which provides location information for
servers that are specified as mail handlers for the given
hostname. This is done using MX records, and the HostAddress
instances are returned sorted by MX priority. If no host is
found for domainName, the Iterator returned will be empty and the
first call to hasNext() will return false.
DNSServer dnsServer = null;
try {
dnsServer = (DNSServer) compMgr.lookup( DNSServer.ROLE );
} catch ( final ServiceException cme ) {
getLogger().error("Fatal configuration error - DNS Servers lost!", cme );
throw new RuntimeException("Fatal configuration error - DNS Servers lost!");
}
return dnsServer.getSMTPHostAddresses(domainName);
|
public java.lang.String | getServerInfo()Return the type of the server
return "Apache JAMES";
|
public synchronized org.apache.james.services.MailRepository | getUserInbox(java.lang.String userName)Retrieve the mail repository for a user
For POP3 server only - at the moment.
MailRepository userInbox = null;
userInbox = (MailRepository) mailboxes.get(userName);
if (userInbox != null) {
return userInbox;
} else if (mailboxes.containsKey(userName)) {
// we have a problem
getLogger().error("Null mailbox for non-null key");
throw new RuntimeException("Error in getUserInbox.");
} else {
// need mailbox object
if (getLogger().isDebugEnabled()) {
getLogger().debug("Retrieving and caching inbox for " + userName );
}
StringBuffer destinationBuffer =
new StringBuffer(192)
.append(inboxRootURL)
.append(userName)
.append("/");
String destination = destinationBuffer.toString();
DefaultConfiguration mboxConf
= new DefaultConfiguration("repository", "generated:AvalonFileRepository.compose()");
mboxConf.setAttribute("destinationURL", destination);
mboxConf.setAttribute("type", "MAIL");
try {
userInbox = (MailRepository) store.select(mboxConf);
if (userInbox!=null) {
mailboxes.put(userName, userInbox);
}
} catch (Exception e) {
if (getLogger().isErrorEnabled())
{
getLogger().error("Cannot open user Mailbox" + e);
}
throw new RuntimeException("Error in getUserInbox." + e);
}
return userInbox;
}
|
public void | initialize()
getLogger().info("JAMES init...");
// TODO: This should retrieve a more specific named thread pool from
// Context that is set up in server.xml
try {
store = (Store) compMgr.lookup( Store.ROLE );
} catch (Exception e) {
if (getLogger().isWarnEnabled()) {
getLogger().warn("Can't get Store: " + e);
}
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Using Store: " + store.toString());
}
try {
spool = (SpoolRepository) compMgr.lookup( SpoolRepository.ROLE );
} catch (Exception e) {
if (getLogger().isWarnEnabled()) {
getLogger().warn("Can't get spoolRepository: " + e);
}
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Using SpoolRepository: " + spool.toString());
}
try {
usersStore = (UsersStore) compMgr.lookup( UsersStore.ROLE );
} catch (Exception e) {
if (getLogger().isWarnEnabled()) {
getLogger().warn("Can't get Store: " + e);
}
}
if (getLogger().isDebugEnabled()) {
getLogger().debug("Using UsersStore: " + usersStore.toString());
}
String hostName = null;
try {
hostName = InetAddress.getLocalHost().getHostName();
} catch (UnknownHostException ue) {
hostName = "localhost";
}
context = new DefaultContext();
context.put("HostName", hostName);
getLogger().info("Local host is: " + hostName);
// Get the domains and hosts served by this instance
serverNames = new HashSet();
Configuration serverConf = conf.getChild("servernames");
if (serverConf.getAttributeAsBoolean("autodetect") && (!hostName.equals("localhost"))) {
serverNames.add(hostName.toLowerCase(Locale.US));
}
final Configuration[] serverNameConfs =
conf.getChild( "servernames" ).getChildren( "servername" );
for ( int i = 0; i < serverNameConfs.length; i++ ) {
serverNames.add( serverNameConfs[i].getValue().toLowerCase(Locale.US));
if (serverConf.getAttributeAsBoolean("autodetectIP", true)) {
try {
/* This adds the IP address(es) for each host to support
* support <user@address-literal> - RFC 2821, sec 4.1.3.
* It might be proper to use the actual IP addresses
* available on this server, but we can't do that
* without NetworkInterface from JDK 1.4. Because of
* Virtual Hosting considerations, we may need to modify
* this to keep hostname and IP associated, rather than
* just both in the set.
*/
InetAddress[] addrs = InetAddress.getAllByName(serverNameConfs[i].getValue());
for (int j = 0; j < addrs.length ; j++) {
serverNames.add(addrs[j].getHostAddress());
}
}
catch(Exception genericException) {
getLogger().error("Cannot get IP address(es) for " + serverNameConfs[i].getValue());
}
}
}
if (serverNames.isEmpty()) {
throw new ConfigurationException( "Fatal configuration error: no servernames specified!");
}
if (getLogger().isInfoEnabled()) {
for (Iterator i = serverNames.iterator(); i.hasNext(); ) {
getLogger().info("Handling mail for: " + i.next());
}
}
String defaultDomain = (String) serverNames.iterator().next();
context.put(Constants.DEFAULT_DOMAIN, defaultDomain);
attributes.put(Constants.DEFAULT_DOMAIN, defaultDomain);
// Get postmaster
String postMasterAddress = conf.getChild("postmaster").getValue("postmaster").toLowerCase(Locale.US);
// if there is no @domain part, then add the first one from the
// list of supported domains that isn't localhost. If that
// doesn't work, use the hostname, even if it is localhost.
if (postMasterAddress.indexOf('@") < 0) {
String domainName = null; // the domain to use
// loop through candidate domains until we find one or exhaust the list
for ( int i = 0; domainName == null && i < serverNameConfs.length ; i++ ) {
String serverName = serverNameConfs[i].getValue().toLowerCase(Locale.US);
if (!("localhost".equals(serverName))) {
domainName = serverName; // ok, not localhost, so use it
}
}
// if we found a suitable domain, use it. Otherwise fallback to the host name.
postMasterAddress = postMasterAddress + "@" + (domainName != null ? domainName : hostName);
}
this.postmaster = new MailAddress( postMasterAddress );
context.put( Constants.POSTMASTER, postmaster );
if (!isLocalServer(postmaster.getHost())) {
StringBuffer warnBuffer
= new StringBuffer(320)
.append("The specified postmaster address ( ")
.append(postmaster)
.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.");
getLogger().warn(warnBuffer.toString());
}
Configuration userNamesConf = conf.getChild("usernames");
ignoreCase = userNamesConf.getAttributeAsBoolean("ignoreCase", false);
boolean enableAliases = userNamesConf.getAttributeAsBoolean("enableAliases", false);
boolean enableForwarding = userNamesConf.getAttributeAsBoolean("enableForwarding", false);
attributes.put(Constants.DEFAULT_ENABLE_ALIASES,new Boolean(enableAliases));
attributes.put(Constants.DEFAULT_ENABLE_FORWARDING,new Boolean(enableForwarding));
attributes.put(Constants.DEFAULT_IGNORE_USERNAME_CASE,new Boolean(ignoreCase));
//Get localusers
try {
localusers = (UsersRepository) compMgr.lookup(UsersRepository.ROLE);
} catch (Exception e) {
getLogger().error("Cannot open private UserRepository");
throw e;
}
//}
compMgr.put( UsersRepository.ROLE, localusers);
getLogger().info("Local users repository opened");
Configuration inboxConf = conf.getChild("inboxRepository");
Configuration inboxRepConf = inboxConf.getChild("repository");
// we could delete this block. I didn't remove this because I'm not sure
// wether we need the "check" of the inbox repository here, or not.
try {
store.select(inboxRepConf);
} catch (Exception e) {
getLogger().error("Cannot open private MailRepository");
throw e;
}
inboxRootURL = inboxRepConf.getAttribute("destinationURL");
getLogger().info("Private Repository LocalInbox opened");
// Add this to comp
compMgr.put( MailServer.ROLE, this);
// For mailet engine provide MailetContext
//compMgr.put("org.apache.mailet.MailetContext", this);
// For AVALON aware mailets and matchers, we put the Component object as
// an attribute
attributes.put(Constants.AVALON_COMPONENT_MANAGER, compMgr);
//Temporary get out to allow complex mailet config files to stop blocking sergei sozonoff's work on bouce processing
java.io.File configDir = AvalonContextUtilities.getFile(myContext, "file://conf/");
attributes.put("confDir", configDir.getCanonicalPath());
// We can safely remove this and the localDeliveryField when we
// remove the storeMail method from James and from the MailetContext
DefaultConfiguration conf = new DefaultConfiguration("mailet", "generated:James.initialize()");
MailetConfigImpl configImpl = new MailetConfigImpl();
configImpl.setMailetName("LocalDelivery");
configImpl.setConfiguration(conf);
configImpl.setMailetContext(this);
localDeliveryMailet = new LocalDelivery();
localDeliveryMailet.init(configImpl);
System.out.println(SOFTWARE_NAME_VERSION);
getLogger().info("JAMES ...init end");
|
public boolean | isLocalServer(java.lang.String serverName)Check whether the mail domain in question is to be
handled by this server.
return serverNames.contains(serverName.toLowerCase(Locale.US));
|
public boolean | isLocalUser(java.lang.String name)Returns whether that account has a local inbox on this server
if (ignoreCase) {
return localusers.containsCaseInsensitive(name);
} else {
return localusers.contains(name);
}
|
public void | log(java.lang.String message)Log a message to the Mailet logger
getMailetLogger().info(message);
|
public void | log(java.lang.String message, java.lang.Throwable t)Log a message and a Throwable to the Mailet logger
getMailetLogger().info(message,t);
|
public static void | main(java.lang.String[] args)The main method. Should never be invoked, as James must be called
from within an Avalon framework container.
System.out.println("ERROR!");
System.out.println("Cannot execute James as a stand alone application.");
System.out.println("To run James, you need to have the Avalon framework installed.");
System.out.println("Please refer to the Readme file to know how to run James.");
|
private org.apache.james.core.MailImpl | rawBounce(org.apache.mailet.Mail mail, java.lang.String bounceText)Generates a bounce mail that is a bounce of the original message.
//This sends a message to the james component that is a bounce of the sent message
MimeMessage original = mail.getMessage();
MimeMessage reply = (MimeMessage) original.reply(false);
reply.setSubject("Re: " + original.getSubject());
reply.setSentDate(new Date());
Collection recipients = new HashSet();
recipients.add(mail.getSender());
InternetAddress addr[] = { new InternetAddress(mail.getSender().toString())};
reply.setRecipients(Message.RecipientType.TO, addr);
reply.setFrom(new InternetAddress(mail.getRecipients().iterator().next().toString()));
reply.setText(bounceText);
reply.setHeader(RFC2822Headers.MESSAGE_ID, "replyTo-" + mail.getName());
return new MailImpl(
"replyTo-" + mail.getName(),
new MailAddress(mail.getRecipients().iterator().next().toString()),
recipients,
reply);
|
public void | removeAttribute(java.lang.String key)
attributes.remove(key);
|
public void | sendMail(javax.mail.internet.MimeMessage message)Place a mail on the spool for processing
MailAddress sender = new MailAddress((InternetAddress)message.getFrom()[0]);
Collection recipients = new HashSet();
Address addresses[] = message.getAllRecipients();
if (addresses != null) {
for (int i = 0; i < addresses.length; i++) {
// Javamail treats the "newsgroups:" header field as a
// recipient, so we want to filter those out.
if ( addresses[i] instanceof InternetAddress ) {
recipients.add(new MailAddress((InternetAddress)addresses[i]));
}
}
}
sendMail(sender, recipients, message);
|
public void | sendMail(org.apache.mailet.MailAddress sender, java.util.Collection recipients, javax.mail.internet.MimeMessage message)Place a mail on the spool for processing
sendMail(sender, recipients, message, Mail.DEFAULT);
|
public void | sendMail(org.apache.mailet.MailAddress sender, java.util.Collection recipients, javax.mail.internet.MimeMessage message, java.lang.String state)Place a mail on the spool for processing
MailImpl mail = new MailImpl(getId(), sender, recipients, message);
try {
mail.setState(state);
sendMail(mail);
} finally {
ContainerUtil.dispose(mail);
}
|
public void | sendMail(org.apache.mailet.MailAddress sender, java.util.Collection recipients, java.io.InputStream msg)Place a mail on the spool for processing
// parse headers
MailHeaders headers = new MailHeaders(msg);
// if headers do not contains minimum REQUIRED headers fields throw Exception
if (!headers.isValid()) {
throw new MessagingException("Some REQURED header field is missing. Invalid Message");
}
ByteArrayInputStream headersIn = new ByteArrayInputStream(headers.toByteArray());
sendMail(new MailImpl(getId(), sender, recipients, new SequenceInputStream(headersIn, msg)));
|
public void | sendMail(org.apache.mailet.Mail mail)Place a mail on the spool for processing
try {
spool.store(mail);
} catch (Exception e) {
getLogger().error("Error storing message: " + e.getMessage(),e);
try {
spool.remove(mail);
} catch (Exception ignored) {
getLogger().error("Error removing message after an error storing it: " + e.getMessage(),e);
}
throw new MessagingException("Exception spooling message: " + e.getMessage(), e);
}
if (getLogger().isDebugEnabled()) {
StringBuffer logBuffer =
new StringBuffer(64)
.append("Mail ")
.append(mail.getName())
.append(" pushed in spool");
getLogger().debug(logBuffer.toString());
}
|
public void | service(org.apache.avalon.framework.service.ServiceManager comp)
compMgr = new DefaultServiceManager(comp);
mailboxes = new ReferenceMap();
|
public void | setAttribute(java.lang.String key, java.lang.Object object)
attributes.put(key, object);
|
public void | storeMail(org.apache.mailet.MailAddress sender, org.apache.mailet.MailAddress recipient, javax.mail.internet.MimeMessage msg)This method has been moved to LocalDelivery (the only client of the method).
Now we can safely remove it from the Mailet API and from this implementation of MailetContext.
The local field localDeliveryMailet will be removed when we remove the storeMail method.
if (recipient == null) {
throw new IllegalArgumentException("Recipient for mail to be spooled cannot be null.");
}
if (msg == null) {
throw new IllegalArgumentException("Mail message to be spooled cannot be null.");
}
Collection recipients = new HashSet();
recipients.add(recipient);
MailImpl m = new MailImpl(getId(),sender,recipients,msg);
localDeliveryMailet.service(m);
ContainerUtil.dispose(m);
|