This patch enhances virtual user processing in JAMES, and affects JDBCVirtualUserTable. Included:
1) Refactoring of common functionality in an abstract superclass, AbstractVirtualUserTable. Subclasses need only map a virtual input MailAddress to a target recipient (or recipients). See javadoc for details.
2) Mutiple recipients are now allowed as targets. This has been done using a delimited list, rather than multiple rows, because:
a) Primary key limitations on the database means the latter could not be backwards compatible;
b) The result is, IMHO, easier to read and update.
3) Some logging of the address translation has been added.
4) A new subclass of AbstractVirtualUserTable has been added, called
XMLVirtualUserTable. This mailet is used to specify the virtual user table in config.xml, and lends itself to simpler setups where changes are not as dynamic, or a database is not present. It has all the functionality of JDBCVirtualUserTable.
5) I have not included any DSN error reporting functionality. It seemed to me on further reflection that a virtual user table should translate virtual uses to real users, and leave error processing to another mailet. In any case, I wanted to work in smaller updates.
6) Patch is against branch_2_1_fcs.
Craig
--- Craig Raw [EMAIL PROTECTED] Quirk Business Solutions www.quirk.co.za
Index: src/java/org/apache/james/transport/mailets/AbstractVirtualUserTable.java =================================================================== RCS file: src/java/org/apache/james/transport/mailets/AbstractVirtualUserTable.java diff -N src/java/org/apache/james/transport/mailets/AbstractVirtualUserTable.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/james/transport/mailets/AbstractVirtualUserTable.java 21 Nov 2003 07:32:37 -0000 @@ -0,0 +1,195 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache", "Jakarta", "JAMES" and "Apache Software Foundation" + * must not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact [EMAIL PROTECTED] + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +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.mailet.GenericMailet; +import org.apache.mailet.Mail; +import org.apache.mailet.MailAddress; + +/** + * 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. + * + * @author Craig Raw <[EMAIL PROTECTED]> + */ +public abstract class AbstractVirtualUserTable extends GenericMailet +{ + /** + * 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 + { + 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 ) { + StringTokenizer tokenizer = new StringTokenizer(targetString, getSeparator(targetString)); + + while (tokenizer.hasMoreTokens()) { + String targetAddress = tokenizer.nextToken().trim(); + + 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); + + //Need to separate local and remote recipients. + 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); + + // Forward to mapped recipients that are remote + if (recipientsToAddForward.size() != 0) { + getMailetContext().sendMail(mail.getSender(), recipientsToAddForward, mail.getMessage()); + } + + // 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; + + /** + * 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? ";" : ":" )); + } +} Index: src/java/org/apache/james/transport/mailets/JDBCVirtualUserTable.java =================================================================== RCS file: /home/cvspublic/james-server/src/java/org/apache/james/transport/mailets/JDBCVirtualUserTable.java,v retrieving revision 1.1.4.2 diff -u -r1.1.4.2 JDBCVirtualUserTable.java --- src/java/org/apache/james/transport/mailets/JDBCVirtualUserTable.java 8 Mar 2003 21:54:08 -0000 1.1.4.2 +++ src/java/org/apache/james/transport/mailets/JDBCVirtualUserTable.java 21 Nov 2003 07:33:00 -0000 @@ -58,24 +58,25 @@ package org.apache.james.transport.mailets; +import java.sql.Connection; +import java.sql.DatabaseMetaData; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.util.Collection; +import java.util.Iterator; +import java.util.Map; + +import javax.mail.MessagingException; + import org.apache.avalon.cornerstone.services.datasource.DataSourceSelector; import org.apache.avalon.excalibur.datasource.DataSourceComponent; import org.apache.avalon.framework.component.ComponentManager; import org.apache.james.Constants; import org.apache.james.util.JDBCUtil; -import org.apache.mailet.GenericMailet; -import org.apache.mailet.Mail; import org.apache.mailet.MailAddress; import org.apache.mailet.MailetException; -import javax.mail.MessagingException; -import javax.mail.internet.ParseException; -import java.sql.*; -import java.util.Collection; -import java.util.Iterator; -import java.util.Locale; -import java.util.Vector; - /** * Implements a Virtual User Table for JAMES. Derived from the * JDBCAlias mailet, but whereas that mailet uses a simple map from a @@ -105,6 +106,12 @@ * PRIMARY KEY (user,domain) * ); * + * The user column specifies the username of the virtual recipient, the domain + * column the domain of the virtual recipient, and the target_address column + * the email address of the real recipient. The target_address column can contain + * just the username in the case of a local user, and multiple recipients can be + * specified in a list separated by commas, semi-colons or colons. + * * The standard query used with VirtualUserTable is: * * select VirtualUserTable.target_address from VirtualUserTable, VirtualUserTable as VUTDomains @@ -131,8 +138,9 @@ * </mailet> * * @author Noel J. Begman <[EMAIL PROTECTED]> + * @author Craig Raw <[EMAIL PROTECTED]> */ -public class JDBCVirtualUserTable extends GenericMailet +public class JDBCVirtualUserTable extends AbstractVirtualUserTable { protected DataSourceComponent datasource; @@ -203,28 +211,23 @@ theJDBCUtil.closeJDBCConnection(conn); } } - + /** - * 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 + * Map any virtual recipients to real recipients using the configured + * JDBC connection, table and query. + * + * @param recipientsMap the mapping of virtual to real recipients */ - public void service(Mail mail) - throws MessagingException { + protected void mapRecipients(Map recipientsMap) throws MessagingException { Connection conn = null; PreparedStatement mappingStmt = null; - - Collection recipients = mail.getRecipients(); - Collection recipientsToRemove = new Vector(); - Collection recipientsToAddLocal = new Vector(); - Collection recipientsToAddForward = new Vector(); - + + Collection recipients = recipientsMap.keySet(); + try { conn = datasource.getConnection(); mappingStmt = conn.prepareStatement(query); - + for (Iterator i = recipients.iterator(); i.hasNext(); ) { ResultSet mappingRS = null; try { @@ -234,31 +237,8 @@ mappingStmt.setString(3, source.getHost()); mappingRS = mappingStmt.executeQuery(); if (mappingRS.next()) { - try { - String targetString = mappingRS.getString(1); - MailAddress target = (targetString.indexOf('@') < 0) ? new MailAddress(targetString, "localhost") - : new MailAddress(targetString); - - //Mark this source address as an address to remove from the recipient list - recipientsToRemove.add(source); - - //Need to separate local and remote recipients. - if (getMailetContext().isLocalServer(target.getHost())) { - recipientsToAddLocal.add(target); - } else { - recipientsToAddForward.add(target); - } - } 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(mappingRS.getString(1)); - log(exceptionBuffer.toString()); - continue; - } + String targetString = mappingRS.getString(1); + recipientsMap.put(source, targetString); } } finally { theJDBCUtil.closeJDBCResultSet(mappingRS); @@ -270,24 +250,8 @@ theJDBCUtil.closeJDBCStatement(mappingStmt); theJDBCUtil.closeJDBCConnection(conn); } - - // Remove mapped recipients - recipients.removeAll(recipientsToRemove); - - // Add mapped recipients that are local - recipients.addAll(recipientsToAddLocal); - - // Forward to mapped recipients that are remote - if (recipientsToAddForward.size() != 0) { - getMailetContext().sendMail(mail.getSender(), recipientsToAddForward, mail.getMessage()); - } - - // If there are no recipients left, Ghost the message - if (recipients.size() == 0) { - mail.setState(Mail.GHOST); - } } - + public String getMailetInfo() { return "JDBC Virtual User Table mailet"; } Index: src/java/org/apache/james/transport/mailets/XMLVirtualUserTable.java =================================================================== RCS file: src/java/org/apache/james/transport/mailets/XMLVirtualUserTable.java diff -N src/java/org/apache/james/transport/mailets/XMLVirtualUserTable.java --- /dev/null 1 Jan 1970 00:00:00 -0000 +++ src/java/org/apache/james/transport/mailets/XMLVirtualUserTable.java 21 Nov 2003 07:33:23 -0000 @@ -0,0 +1,183 @@ +/* ==================================================================== + * The Apache Software License, Version 1.1 + * + * Copyright (c) 2000-2003 The Apache Software Foundation. All rights + * reserved. + * + * Redistribution and use in source and binary forms, with or without + * modification, are permitted provided that the following conditions + * are met: + * + * 1. Redistributions of source code must retain the above copyright + * notice, this list of conditions and the following disclaimer. + * + * 2. Redistributions in binary form must reproduce the above copyright + * notice, this list of conditions and the following disclaimer in + * the documentation and/or other materials provided with the + * distribution. + * + * 3. The end-user documentation included with the redistribution, + * if any, must include the following acknowledgment: + * "This product includes software developed by the + * Apache Software Foundation (http://www.apache.org/)." + * Alternately, this acknowledgment may appear in the software itself, + * if and wherever such third-party acknowledgments normally appear. + * + * 4. The names "Apache", "Jakarta", "JAMES" and "Apache Software Foundation" + * must not be used to endorse or promote products derived from this + * software without prior written permission. For written + * permission, please contact [EMAIL PROTECTED] + * + * 5. Products derived from this software may not be called "Apache", + * nor may "Apache" appear in their name, without prior written + * permission of the Apache Software Foundation. + * + * THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED + * WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES + * OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE + * DISCLAIMED. IN NO EVENT SHALL THE APACHE SOFTWARE FOUNDATION OR + * ITS CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, + * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT + * LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF + * USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND + * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, + * OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT + * OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF + * SUCH DAMAGE. + * ==================================================================== + * + * This software consists of voluntary contributions made by many + * individuals on behalf of the Apache Software Foundation. For more + * information on the Apache Software Foundation, please see + * <http://www.apache.org/>. + * + * Portions of this software are based upon public domain software + * originally written at the National Center for Supercomputing Applications, + * University of Illinois, Urbana-Champaign. + */ + +package org.apache.james.transport.mailets; + +import java.util.Collection; +import java.util.HashMap; +import java.util.Iterator; +import java.util.Map; +import java.util.StringTokenizer; + +import javax.mail.MessagingException; + +import org.apache.mailet.MailAddress; + +/** + * Implements a Virtual User Table to translate virtual users + * to real users. This implementation has the same functionality + * as <code>JDBCVirtualUserTable</code>, but is configured in the + * JAMES configuration and is thus probably most suitable for smaller + * and less dynamic mapping requirements. + * + * The configuration is specified in the form: + * + * <mailet match="All" class="XMLVirtualUserTable"> + * <mapping>[EMAIL PROTECTED]@yyy][;[EMAIL PROTECTED]</mapping> + * <mapping>[EMAIL PROTECTED]@yyy][;[EMAIL PROTECTED]</mapping> + * ... + * </mailet> + * + * As many <mapping> elements can be added as necessary. As indicated, + * wildcards are supported, and multiple recipients can be specified with a + * semicolon-separated list. The target domain does not need to be specified if + * the real user is local to the server. + * + * Matching is done in the following order: + * 1. [EMAIL PROTECTED] - explicit mapping for [EMAIL PROTECTED] + * 2. [EMAIL PROTECTED] - catchall mapping for user anywhere + * 3. [EMAIL PROTECTED] - catchall mapping for anyone at domain + * 4. null - no valid mapping + * + * @author Craig Raw <[EMAIL PROTECTED]> + */ +public class XMLVirtualUserTable extends AbstractVirtualUserTable +{ + /** + * Holds the configured mappings + */ + private Map mappings = new HashMap(); + + /** + * Initialize the mailet + */ + public void init() throws MessagingException { + String mapping = getInitParameter("mapping"); + + if(mapping != null) { + StringTokenizer tokenizer = new StringTokenizer(mapping, ","); + while(tokenizer.hasMoreTokens()) { + String mappingItem = tokenizer.nextToken(); + int index = mappingItem.indexOf('='); + String virtual = mappingItem.substring(0, index).trim().toLowerCase(); + String real = mappingItem.substring(index + 1).trim().toLowerCase(); + mappings.put(virtual, real); + } + } + } + + /** + * Map any virtual recipients to real recipients using the configured mapping. + * + * @param recipientsMap the mapping of virtual to real recipients + */ + protected void mapRecipients(Map recipientsMap) throws MessagingException { + Collection recipients = recipientsMap.keySet(); + + for (Iterator i = recipients.iterator(); i.hasNext(); ) { + MailAddress source = (MailAddress)i.next(); + String user = source.getUser().toLowerCase(); + String domain = source.getHost().toLowerCase(); + + String targetString = getTargetString(user, domain); + + if (targetString != null) { + recipientsMap.put(source, targetString); + } + } + } + + /** + * Returns the real recipient given a virtual username and domain. + * + * @param user the virtual user + * @param domain the virtual domain + * @return the real recipient address, or <code>null</code> if no mapping exists + */ + private String getTargetString(String user, String domain) { + StringBuffer buf; + String target; + + //Look for exact ([EMAIL PROTECTED]) match + buf = new StringBuffer().append(user).append("@").append(domain); + target = (String)mappings.get(buf.toString()); + if (target != null) { + return target; + } + + //Look for [EMAIL PROTECTED] match + buf = new StringBuffer().append(user).append("@*"); + target = (String)mappings.get(buf.toString()); + if (target != null) { + return target; + } + + //Look for [EMAIL PROTECTED] match + buf = new StringBuffer().append("*@").append(domain); + target = (String)mappings.get(buf.toString()); + if (target != null) { + return target; + } + + return null; + } + + public String getMailetInfo() { + return "XML Virtual User Table mailet"; + } +}
--------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]