/projects/james-2.2.0/src/java/org/apache/james/transport/mailets/AbstractVirtualUserTable.java
Java | 313 lines | 165 code | 42 blank | 106 comment | 24 complexity | a386e6d3bc44afd57e30e4363f7968a6 MD5 | raw file
- /***********************************************************************
- * Copyright (c) 2000-2004 The Apache Software Foundation. *
- * All rights reserved. *
- * ------------------------------------------------------------------- *
- * Licensed under the Apache License, Version 2.0 (the "License"); you *
- * may not use this file except in compliance with the License. You *
- * may obtain a copy of the License at: *
- * *
- * http://www.apache.org/licenses/LICENSE-2.0 *
- * *
- * Unless required by applicable law or agreed to in writing, software *
- * distributed under the License is distributed on an "AS IS" BASIS, *
- * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or *
- * implied. See the License for the specific language governing *
- * permissions and limitations under the License. *
- ***********************************************************************/
- package org.apache.james.transport.mailets;
- import java.util.ArrayList;
- import java.util.Collection;
- import java.util.HashMap;
- import java.util.HashSet;
- import java.util.Iterator;
- import java.util.Map;
- import java.util.StringTokenizer;
- import javax.mail.MessagingException;
- import javax.mail.internet.ParseException;
- import org.apache.james.core.MailImpl;
- import org.apache.james.util.XMLResources;
- import org.apache.mailet.GenericMailet;
- import org.apache.mailet.Mail;
- import org.apache.mailet.MailAddress;
- import org.apache.oro.text.regex.MalformedPatternException;
- import org.apache.oro.text.regex.MatchResult;
- import org.apache.oro.text.regex.Pattern;
- import org.apache.oro.text.regex.Perl5Compiler;
- import org.apache.oro.text.regex.Perl5Matcher;
- /**
- * Provides an abstraction of common functionality needed for implementing
- * a Virtual User Table. Override the <code>mapRecipients</code> method to
- * map virtual recipients to real recipients.
- */
- public abstract class AbstractVirtualUserTable extends GenericMailet
- {
- static private final String MARKER = "org.apache.james.transport.mailets.AbstractVirtualUserTable.mapped";
- /**
- * Checks the recipient list of the email for user mappings. Maps recipients as
- * appropriate, modifying the recipient list of the mail and sends mail to any new
- * non-local recipients.
- *
- * @param mail the mail to process
- */
- public void service(Mail mail) throws MessagingException
- {
- if (mail.getAttribute(MARKER) != null) {
- mail.removeAttribute(MARKER);
- return;
- }
- Collection recipientsToRemove = new HashSet();
- Collection recipientsToAddLocal = new ArrayList();
- Collection recipientsToAddForward = new ArrayList();
- Collection recipients = mail.getRecipients();
- Map recipientsMap = new HashMap(recipients.size());
- for (Iterator iter = recipients.iterator(); iter.hasNext(); ) {
- MailAddress address = (MailAddress)iter.next();
- // Assume all addresses are non-virtual at start
- recipientsMap.put(address, null);
- }
- mapRecipients(recipientsMap);
- for (Iterator iter = recipientsMap.keySet().iterator(); iter.hasNext(); ) {
- MailAddress source = (MailAddress)iter.next();
- String targetString = (String)recipientsMap.get(source);
- // Only non-null mappings are translated
- if(targetString != null) {
- if (targetString.startsWith("error:")) {
- //Mark this source address as an address to remove from the recipient list
- recipientsToRemove.add(source);
- processDSN(mail, source, targetString);
- } else {
- StringTokenizer tokenizer = new StringTokenizer(targetString, getSeparator(targetString));
- while (tokenizer.hasMoreTokens()) {
- String targetAddress = tokenizer.nextToken().trim();
- // log("Attempting to map from " + source + " to " + targetAddress);
- if (targetAddress.startsWith("regex:")) {
- targetAddress = regexMap(mail, source, targetAddress);
- if (targetAddress == null) continue;
- }
- try {
- MailAddress target = (targetAddress.indexOf('@') < 0) ? new MailAddress(targetAddress, "localhost")
- : new MailAddress(targetAddress);
- //Mark this source address as an address to remove from the recipient list
- recipientsToRemove.add(source);
- // We need to separate local and remote
- // recipients. This is explained below.
- if (getMailetContext().isLocalServer(target.getHost())) {
- recipientsToAddLocal.add(target);
- } else {
- recipientsToAddForward.add(target);
- }
- StringBuffer buf = new StringBuffer().append("Translating virtual user ")
- .append(source)
- .append(" to ")
- .append(target);
- log(buf.toString());
- } catch (ParseException pe) {
- //Don't map this address... there's an invalid address mapping here
- StringBuffer exceptionBuffer =
- new StringBuffer(128)
- .append("There is an invalid map from ")
- .append(source)
- .append(" to ")
- .append(targetAddress);
- log(exceptionBuffer.toString());
- continue;
- }
- }
- }
- }
- }
- // Remove mapped recipients
- recipients.removeAll(recipientsToRemove);
- // Add mapped recipients that are local
- recipients.addAll(recipientsToAddLocal);
- // We consider an address that we map to be, by definition, a
- // local address. Therefore if we mapped to a remote address,
- // then we want to make sure that the mail can be relayed.
- // However, the original e-mail would typically be subjected to
- // relay testing. By posting a new mail back through the
- // system, we have a locally generated mail, which will not be
- // subjected to relay testing.
- // Forward to mapped recipients that are remote
- if (recipientsToAddForward.size() != 0) {
- // Can't use this ... some mappings could lead to an infinite loop
- // getMailetContext().sendMail(mail.getSender(), recipientsToAddForward, mail.getMessage());
- // duplicates the Mail object, to be able to modify the new mail keeping the original untouched
- MailImpl newMail = (MailImpl) ((MailImpl) mail).duplicate(newName((MailImpl) mail));
- try {
- newMail.setRemoteAddr(java.net.InetAddress.getLocalHost().getHostAddress());
- newMail.setRemoteHost(java.net.InetAddress.getLocalHost().getHostName());
- } catch (java.net.UnknownHostException _) {
- newMail.setRemoteAddr("127.0.0.1");
- newMail.setRemoteHost("localhost");
- }
- newMail.setRecipients(recipientsToAddForward);
- newMail.setAttribute(MARKER, Boolean.TRUE);
- getMailetContext().sendMail(newMail);
- }
- // If there are no recipients left, Ghost the message
- if (recipients.size() == 0) {
- mail.setState(Mail.GHOST);
- }
- }
- /**
- * Override to map virtual recipients to real recipients, both local and non-local.
- * Each key in the provided map corresponds to a potential virtual recipient, stored as
- * a <code>MailAddress</code> object.
- *
- * Translate virtual recipients to real recipients by mapping a string containing the
- * address of the real recipient as a value to a key. Leave the value <code>null<code>
- * if no mapping should be performed. Multiple recipients may be specified by delineating
- * the mapped string with commas, semi-colons or colons.
- *
- * @param recipientsMap the mapping of virtual to real recipients, as
- * <code>MailAddress</code>es to <code>String</code>s.
- */
- protected abstract void mapRecipients(Map recipientsMap) throws MessagingException;
-
- /**
- * Sends the message for DSN processing
- *
- * @param mail the Mail instance being processed
- * @param address the MailAddress causing the DSN
- * @param error a String in the form "error:<code> <msg>"
- */
- private void processDSN(Mail mail, MailAddress address, String error) {
- // parse "error:<code> <msg>"
- int msgPos = error.indexOf(' ');
- try {
- Integer code = Integer.valueOf(error.substring("error:".length(),msgPos));
- } catch (NumberFormatException e) {
- log("Cannot send DSN. Exception parsing DSN code from: " + error, e);
- return;
- }
- String msg = error.substring(msgPos + 1);
- // process bounce for "source" address
- try {
- getMailetContext().bounce(mail, error);
- }
- catch (MessagingException me) {
- log("Cannot send DSN. Exception during DSN processing: ", me);
- }
- }
- /**
- * Processes regex virtual user mapping
- *
- * If a mapped target string begins with the prefix regex:, it must be
- * formatted as regex:<regular-expression>:<parameterized-string>,
- * e.g., regex:(.*)@(.*):${1}@tld
- *
- * @param mail the Mail instance being processed
- * @param address the MailAddress to be mapped
- * @param targetString a String specifying the mapping
- */
- private String regexMap(Mail mail, MailAddress address, String targetString) {
- String result = null;
- try {
- int msgPos = targetString.indexOf(':', "regex:".length() + 1);
- // log("regex: targetString = " + targetString);
- // log("regex: msgPos = " + msgPos);
- // log("regex: compile " + targetString.substring("regex:".length(), msgPos));
- // log("regex: address = " + address.toString());
- // log("regex: replace = " + targetString.substring(msgPos + 1));
- Pattern pattern = new Perl5Compiler().compile(targetString.substring("regex:".length(), msgPos));
- Perl5Matcher matcher = new Perl5Matcher();
- if (matcher.matches(address.toString(), pattern)) {
- MatchResult match = matcher.getMatch();
- Map parameters = new HashMap(match.groups());
- for (int i = 1; i < match.groups(); i++) {
- parameters.put(Integer.toString(i), match.group(i));
- }
- result = XMLResources.replaceParameters(targetString.substring(msgPos + 1), parameters);
- }
- }
- catch (Exception e) {
- log("Exception during regexMap processing: ", e);
- }
- // log("regex: result = " + result);
- return result;
- }
- /**
- * Returns the character used to delineate multiple addresses.
- *
- * @param targetString the string to parse
- * @return the character to tokenize on
- */
- private String getSeparator(String targetString) {
- return (targetString.indexOf(',') > -1 ? "," : (targetString.indexOf(';') > -1 ? ";" : (targetString.indexOf("regex:") > -1? "" : ":" )));
- }
- private static final java.util.Random random = new java.util.Random(); // Used to generate new mail names
- /**
- * Create a unique new primary key name.
- *
- * @param mail the mail to use as the basis for the new mail name
- * @return a new name
- */
- private String newName(MailImpl mail) throws MessagingException {
- String oldName = mail.getName();
- // Checking if the original mail name is too long, perhaps because of a
- // loop caused by a configuration error.
- // it could cause a "null pointer exception" in AvalonMailRepository much
- // harder to understand.
- if (oldName.length() > 76) {
- int count = 0;
- int index = 0;
- while ((index = oldName.indexOf('!', index + 1)) >= 0) {
- count++;
- }
- // It looks like a configuration loop. It's better to stop.
- if (count > 7) {
- throw new MessagingException("Unable to create a new message name: too long. Possible loop in config.xml.");
- }
- else {
- oldName = oldName.substring(0, 76);
- }
- }
- StringBuffer nameBuffer =
- new StringBuffer(64)
- .append(oldName)
- .append("-!")
- .append(random.nextInt(1048576));
- return nameBuffer.toString();
- }
- }