Author: vincenzo Date: Sun Jun 11 13:20:10 2006 New Revision: 413525 URL: http://svn.apache.org/viewvc?rev=413525&view=rev Log: Adding whitelist support matcher and mailet (see http://issues.apache.org/jira/browse/JAMES-528).
Added: james/server/branches/v2.3/src/java/org/apache/james/transport/mailets/WhiteListManager.java james/server/branches/v2.3/src/java/org/apache/james/transport/matchers/IsInWhiteList.java Modified: james/server/branches/v2.3/src/conf/james-config.xml james/server/branches/v2.3/src/conf/sqlResources.xml Modified: james/server/branches/v2.3/src/conf/james-config.xml URL: http://svn.apache.org/viewvc/james/server/branches/v2.3/src/conf/james-config.xml?rev=413525&r1=413524&r2=413525&view=diff ============================================================================== --- james/server/branches/v2.3/src/conf/james-config.xml (original) +++ james/server/branches/v2.3/src/conf/james-config.xml Sun Jun 11 13:20:10 2006 @@ -214,6 +214,37 @@ </mailet> --> + <!-- Whitelist Management --> + <!-- Manages for each local user a "white list" of remote addresses whose messages --> + <!-- should never be blocked as spam. --> + <!-- --> + <!-- If <automaticInsert> is true, it will check, for a local sender, if a remote recipient --> + <!-- is already in the list: if not, it will be automatically inserted. --> + <!-- This is under the interpretation that if a local sender X sends a message to a --> + <!-- remote recipient Y, then later on if a message is sent by Y to X it should be --> + <!-- considered always valid and never blocked; hence Y should be in the white list --> + <!-- of X. --> + <!-- --> + <!-- Another mode of operations is when a local sender sends a message to <whitelistManagerAddress> --> + <!-- with one of three specific values in the subject, to --> + <!-- (i) send back a message displaying a list of the addresses in his own list (<displayFlag>); --> + <!-- (ii) insert some new addresses in his own list (<insertFlag>); --> + <!-- (iii) remove some addresses from his own list (<removeFlag>). --> + <!-- In all of the three above cases the message will be ghosted and the postmaster will reply --> + <!-- to the sender. --> + <!-- --> + <!-- The sender name is always converted to its primary name (handling aliases). --> + <!-- + <mailet match="SMTPAuthSuccessful" class="WhiteListManager" onMailetException="ignore"> + <repositoryPath>db://maildb</repositoryPath> + <automaticInsert>true</automaticInsert> + <whitelistManagerAddress>[EMAIL PROTECTED]</whitelistManagerAddress> + <displayFlag>display</displayFlag> + <insertFlag>insert</insertFlag> + <removeFlag>remove</removeFlag> + </mailet> + --> + <!-- "not spam" bayesian analysis feeder. --> <!-- <mailet match="[EMAIL PROTECTED]" class="BayesianAnalysisFeeder"> @@ -328,6 +359,15 @@ <processor> transport </processor> </mailet> --> + + <!-- If the sender is in a recipient's whitelist, it is a valid sender, --> + <!-- and as such the message should not be considered spam for such recipient. --> + <!-- + <mailet match="IsInWhiteList=db://maildb" class="ToProcessor" onMatchException="noMatch"> + <processor> transport </processor> + </mailet> + --> + <!-- End of White List --> <!-- Check for delivery from a known spam server --> Modified: james/server/branches/v2.3/src/conf/sqlResources.xml URL: http://svn.apache.org/viewvc/james/server/branches/v2.3/src/conf/sqlResources.xml?rev=413525&r1=413524&r2=413525&view=diff ============================================================================== --- james/server/branches/v2.3/src/conf/sqlResources.xml (original) +++ james/server/branches/v2.3/src/conf/sqlResources.xml Sun Jun 11 13:20:10 2006 @@ -722,5 +722,116 @@ </sqlDefs> +<!-- SQL statements to support the WhiteListManager mailet and the IsInWhiteList matcher --> +<!-- --> +<sqlDefs name="WhiteList"> + + <sql name="whiteListTableName">whitelist</sql> + + <!-- Statements used to retrieve a single entry. --> + <sql name="selectByPK">SELECT localUser, localHost FROM whitelist where (localUser=? AND localHost=? AND remoteUser=? AND remoteHost=?)</sql> + + <!-- Statements used to all entries by sender address. --> + <sql name="selectBySender">SELECT remoteUser, remoteHost FROM whitelist where (localUser=? AND localHost=?) ORDER BY remoteUser, remoteHost</sql> + + <!-- Statements used to insert an entry. --> + <sql name="insert">INSERT INTO whitelist (localUser, localHost, remoteUser, remoteHost) VALUES (?,?,?,?)</sql> + + <!-- Statements used to delete an entry. --> + <sql name="deleteByPK">DELETE FROM whitelist where (localUser=? AND localHost=? AND remoteUser=? AND remoteHost=?)</sql> + + <!-- Statements used to create the "whitelist" table. --> + <sql name="createWhiteListTable" db="hypersonic"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) </sql> + <sql name="createWhiteListTable" db="hsqldb"> + CREATE CACHED TABLE ${table} ( + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) </sql> + <sql name="createWhiteListTable" db="mysql"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) character set latin1 NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) character set latin1 NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) TYPE=InnoDB + </sql> + <sql name="createWhiteListTable" db="mssql"> + CREATE TABLE [whitelist] ( + [localUser] [varchar] (64) NOT NULL, + [localHost] [varchar] (255) NOT NULL, + [remoteUser] [varchar] (64) NOT NULL, + [remoteHost] [varchar] (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) + </sql> + <sql name="createWhiteListTable" db="oracle"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) + </sql> + <sql name="createWhiteListTable" db="postgresql"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) + </sql> + <sql name="createWhiteListTable" db="sapdb"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) + </sql> + <sql name="createWhiteListTable" db="db2"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) + </sql> + <sql name="createWhiteListTable" db="ingres"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) + </sql> + <sql name="createWhiteListTable" db="derby"> + CREATE TABLE whitelist ( + localUser varchar (64) NOT NULL, + localHost varchar (255) NOT NULL, + remoteUser varchar (64) NOT NULL, + remoteHost varchar (255) NOT NULL, + PRIMARY KEY (localUser, localHost, remoteUser, remoteHost) + ) + </sql> + +</sqlDefs> + </sqlResources> Added: james/server/branches/v2.3/src/java/org/apache/james/transport/mailets/WhiteListManager.java URL: http://svn.apache.org/viewvc/james/server/branches/v2.3/src/java/org/apache/james/transport/mailets/WhiteListManager.java?rev=413525&view=auto ============================================================================== --- james/server/branches/v2.3/src/java/org/apache/james/transport/mailets/WhiteListManager.java (added) +++ james/server/branches/v2.3/src/java/org/apache/james/transport/mailets/WhiteListManager.java Sun Jun 11 13:20:10 2006 @@ -0,0 +1,830 @@ +/*********************************************************************** + * Copyright (c) 2000-2006 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 org.apache.mailet.*; +import org.apache.mailet.dates.RFC822DateFormat; + +import org.apache.avalon.cornerstone.services.datasources.*; +import org.apache.avalon.excalibur.datasource.*; +import org.apache.avalon.framework.service.*; + +import org.apache.james.*; +import org.apache.james.core.*; +import org.apache.james.services.*; +import org.apache.james.util.*; + +import javax.mail.*; +import javax.mail.internet.*; + +import java.sql.*; +import java.util.*; +import java.text.*; +import java.io.*; + +/** <P>Manages for each local user a "white list" of remote addresses whose messages + * should never be blocked as spam.</P> + * <P>The normal behaviour is to check, for a local sender, if a remote recipient + * is already in the list: if not, it will be automatically inserted. + * This is under the interpretation that if a local sender <I>X</I> sends a message to a + * remote recipient <I>Y</I>, then later on if a message is sent by <I>Y</I> to <I>X</I> it should be + * considered always valid and never blocked; hence <I>Y</I> should be in the white list + * of <I>X</I>.</P> + * <P>Another mode of operations is when a local sender sends a message to <I>whitelistManagerAddress</I> + * with one of three specific values in the subject, to + * (i) send back a message displaying a list of the addresses in his own list; + * (ii) insert some new addresses in his own list; + * (iii) remove some addresses from his own list. + * In all this cases the message will be ghosted and the postmaster will reply + * to the sender.</P> + * <P> The sender name is always converted to its primary name (handling aliases).</P> + * <P>Sample configuration:</P> + * <PRE><CODE> + * <mailet match="SMTPAuthSuccessful" class="WhiteListManager"> + * <repositoryPath> db://maildb </repositoryPath> + * <!-- + * If true automatically inserts the local sender to remote recipients entries in the whitelist (default is false). + * --> + * <automaticInsert>true</automaticInsert> + * <!-- + * Set this to an email address of the "whitelist manager" to send commands to (default is null). + * --> + * <whitelistManagerAddress>[EMAIL PROTECTED]</whitelistManagerAddress> + * <!-- + * Set this to a unique text that you can use (by sending a message to the "whitelist manager" above) + * to tell the mailet to send back the contents of the white list (default is null). + * --> + * <displayFlag>display whitelist</displayFlag> + * <!-- + * Set this to a unique text that you can use (by sending a message to the "whitelist manager" above) + * to tell the mailet to insert some new remote recipients to the white list (default is null). + * --> + * <insertFlag>insert whitelist</insertFlag> + * <!-- + * Set this to a unique text that you can use (by sending a message to the "whitelist manager" above) + * to tell the mailet to remove some remote recipients from the white list (default is null). + * --> + * <removeFlag>remove whitelist</removeFlag> + * </mailet> + * </CODE></PRE> + * + * @see org.apache.james.transport.matchers.IsInWhiteList + * @version SVN $Revision: $ $Date: $ + * @since 2.3.0 + */ +public class WhiteListManager extends GenericMailet { + + private boolean automaticInsert; + private String displayFlag; + private String insertFlag; + private String removeFlag; + private MailAddress whitelistManagerAddress; + + private String selectByPK; + private String selectBySender; + private String insert; + private String deleteByPK; + + /** The date format object used to generate RFC 822 compliant date headers. */ + private RFC822DateFormat rfc822DateFormat = new RFC822DateFormat(); + + private DataSourceComponent datasource; + + /** The store containing the local user repository. */ + private UsersStore usersStore; + + /** The user repository for this mail server. Contains all the users with inboxes + * on this server. + */ + private UsersRepository localusers; + + /** + * The JDBCUtil helper class + */ + private final JDBCUtil theJDBCUtil = new JDBCUtil() { + protected void delegatedLog(String logString) { + log("WhiteListManager: " + logString); + } + }; + + /** + * Contains all of the sql strings for this component. + */ + private SqlResources sqlQueries = new SqlResources(); + + /** + * Holds value of property sqlFile. + */ + private File sqlFile; + + /** + * Holds value of property sqlParameters. + */ + private Map sqlParameters = new HashMap(); + + /** + * Getter for property sqlParameters. + * @return Value of property sqlParameters. + */ + private Map getSqlParameters() { + + return this.sqlParameters; + } + + /** + * Setter for property sqlParameters. + * @param sqlParameters New value of property sqlParameters. + */ + private void setSqlParameters(Map sqlParameters) { + + this.sqlParameters = sqlParameters; + } + + /** Initializes the mailet. + */ + public void init() throws MessagingException { + automaticInsert = new Boolean(getInitParameter("automaticInsert")).booleanValue(); + log("automaticInsert: " + automaticInsert); + + displayFlag = getInitParameter("displayFlag"); + insertFlag = getInitParameter("insertFlag"); + removeFlag = getInitParameter("removeFlag"); + + String whitelistManagerAddressString = getInitParameter("whitelistManagerAddress"); + if (whitelistManagerAddressString != null) { + whitelistManagerAddressString = whitelistManagerAddressString.trim(); + log("whitelistManagerAddress: " + whitelistManagerAddressString); + try { + whitelistManagerAddress = new MailAddress(whitelistManagerAddressString); + } + catch (javax.mail.internet.ParseException pe) { + throw new MessagingException("Bad whitelistManagerAddress", pe); + } + + if (displayFlag != null) { + displayFlag = displayFlag.trim(); + log("displayFlag: " + displayFlag); + } + else { + log("displayFlag is null"); + } + if (insertFlag != null) { + insertFlag = insertFlag.trim(); + log("insertFlag: " + insertFlag); + } + else { + log("insertFlag is null"); + } + if (removeFlag != null) { + removeFlag = removeFlag.trim(); + log("removeFlag: " + removeFlag); + } + else { + log("removeFlag is null"); + } + } + else { + log("whitelistManagerAddress is null; will ignore commands"); + } + + String repositoryPath = getInitParameter("repositoryPath"); + if (repositoryPath != null) { + log("repositoryPath: " + repositoryPath); + } + else { + throw new MessagingException("repositoryPath is null"); + } + + ServiceManager serviceManager = (ServiceManager) getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER); + + try { + // Get the DataSourceSelector block + DataSourceSelector datasources = (DataSourceSelector) serviceManager.lookup(DataSourceSelector.ROLE); + // Get the data-source required. + int stindex = repositoryPath.indexOf("://") + 3; + String datasourceName = repositoryPath.substring(stindex); + datasource = (DataSourceComponent) datasources.select(datasourceName); + } catch (Exception e) { + throw new MessagingException("Can't get datasource", e); + } + + try { + // Get the UsersRepository + usersStore = (UsersStore)serviceManager.lookup(UsersStore.ROLE); + localusers = (UsersRepository)usersStore.getRepository("LocalUsers"); + } catch (Exception e) { + throw new MessagingException("Can't get the local users repository", e); + } + + try { + initSqlQueries(datasource.getConnection(), getMailetContext()); + } catch (Exception e) { + throw new MessagingException("Exception initializing queries", e); + } + + selectByPK = sqlQueries.getSqlString("selectByPK", true); + selectBySender = sqlQueries.getSqlString("selectBySender", true); + insert = sqlQueries.getSqlString("insert", true); + deleteByPK = sqlQueries.getSqlString("deleteByPK", true); + } + + /** Services the mailet. + */ + public void service(Mail mail) throws MessagingException { + + // check if it's a local sender + MailAddress senderMailAddress = mail.getSender(); + if (senderMailAddress == null) { + return; + } + String senderUser = senderMailAddress.getUser(); + String senderHost = senderMailAddress.getHost(); + if ( !getMailetContext().isLocalServer(senderHost) + || !getMailetContext().isLocalUser(senderUser)) { + // not a local sender, so return + return; + } + + Collection recipients = mail.getRecipients(); + + if (recipients.size() == 1 + && whitelistManagerAddress != null + && whitelistManagerAddress.equals(recipients.toArray()[0])) { + + mail.setState(Mail.GHOST); + + String subject = mail.getMessage().getSubject(); + if (displayFlag != null && displayFlag.equals(subject)) { + manageDisplayRequest(mail); + } + else if (insertFlag != null && insertFlag.equals(subject)) { + manageInsertRequest(mail); + } + else if (removeFlag != null && removeFlag.equals(subject)) { + manageRemoveRequest(mail); + } + else { + StringWriter sout = new StringWriter(); + PrintWriter out = new PrintWriter(sout, true); + out.println("Answering on behalf of: " + whitelistManagerAddress); + out.println("ERROR: Unknown command in the subject line: " + subject); + sendReplyFromPostmaster(mail, sout.toString()); + } + return; + } + + if (automaticInsert) { + checkAndInsert(senderMailAddress, recipients); + } + + } + + /** Returns a string describing this mailet. + * + * @return a string describing this mailet + */ + public String getMailetInfo() { + return "White List Manager mailet"; + } + + /** Loops through each address in the recipient list, checks if in the senders + * list and inserts in it otherwise. + */ + private void checkAndInsert(MailAddress senderMailAddress, Collection recipients) throws MessagingException { + String senderUser = senderMailAddress.getUser().toLowerCase(Locale.US); + String senderHost = senderMailAddress.getHost().toLowerCase(Locale.US); + + senderUser = getPrimaryName(senderUser); + + Connection conn = null; + PreparedStatement selectStmt = null; + PreparedStatement insertStmt = null; + boolean dbUpdated = false; + + try { + + for (Iterator i = recipients.iterator(); i.hasNext(); ) { + ResultSet selectRS = null; + try { + MailAddress recipientMailAddress = (MailAddress)i.next(); + String recipientUser = recipientMailAddress.getUser().toLowerCase(Locale.US); + String recipientHost = recipientMailAddress.getHost().toLowerCase(Locale.US); + + if (getMailetContext().isLocalServer(recipientHost)) { + // not a remote recipient, so skip + continue; + } + + if (conn == null) { + conn = datasource.getConnection(); + } + + if (selectStmt == null) { + selectStmt = conn.prepareStatement(selectByPK); + } + selectStmt.setString(1, senderUser); + selectStmt.setString(2, senderHost); + selectStmt.setString(3, recipientUser); + selectStmt.setString(4, recipientHost); + selectRS = selectStmt.executeQuery(); + if (selectRS.next()) { + //This address was already in the list + continue; + } + + if (insertStmt == null) { + insertStmt = conn.prepareStatement(insert); + } + insertStmt.setString(1, senderUser); + insertStmt.setString(2, senderHost); + insertStmt.setString(3, recipientUser); + insertStmt.setString(4, recipientHost); + insertStmt.executeUpdate(); + dbUpdated = true; + + } finally { + theJDBCUtil.closeJDBCResultSet(selectRS); + } + + //Commit our changes if necessary. + if (conn != null && dbUpdated && !conn.getAutoCommit()) { + conn.commit(); + dbUpdated = false; + } + } + } catch (SQLException sqle) { + log("Error accessing database", sqle); + throw new MessagingException("Exception thrown", sqle); + } finally { + theJDBCUtil.closeJDBCStatement(selectStmt); + theJDBCUtil.closeJDBCStatement(insertStmt); + //Rollback our changes if necessary. + try { + if (conn != null && dbUpdated && !conn.getAutoCommit()) { + conn.rollback(); + dbUpdated = false; + } + } + catch (Exception e) {} + theJDBCUtil.closeJDBCConnection(conn); + } + } + + /** Manages a display request. + */ + private void manageDisplayRequest(Mail mail) + throws MessagingException { + MailAddress senderMailAddress = mail.getSender(); + String senderUser = senderMailAddress.getUser().toLowerCase(Locale.US); + String senderHost = senderMailAddress.getHost().toLowerCase(Locale.US); + + senderUser = getPrimaryName(senderUser); + + Connection conn = null; + PreparedStatement selectStmt = null; + ResultSet selectRS = null; + + StringWriter sout = new StringWriter(); + PrintWriter out = new PrintWriter(sout, true); + + try { + out.println("Answering on behalf of: " + whitelistManagerAddress); + out.println("Displaying white list of " + (new MailAddress(senderUser, senderHost)) + ":"); + out.println(); + + conn = datasource.getConnection(); + selectStmt = conn.prepareStatement(selectBySender); + selectStmt.setString(1, senderUser); + selectStmt.setString(2, senderHost); + selectRS = selectStmt.executeQuery(); + while (selectRS.next()) { + MailAddress mailAddress = + new MailAddress(selectRS.getString(1), selectRS.getString(2)); + out.println(mailAddress.toInternetAddress().toString()); + } + + out.println(); + out.println("Finished"); + + sendReplyFromPostmaster(mail, sout.toString()); + + } catch (SQLException sqle) { + out.println("Error accessing the database"); + sendReplyFromPostmaster(mail, sout.toString()); + throw new MessagingException("Error accessing database", sqle); + } finally { + theJDBCUtil.closeJDBCResultSet(selectRS); + theJDBCUtil.closeJDBCStatement(selectStmt); + theJDBCUtil.closeJDBCConnection(conn); + } + } + + /** Manages an insert request. + */ + private void manageInsertRequest(Mail mail) + throws MessagingException { + MailAddress senderMailAddress = mail.getSender(); + String senderUser = senderMailAddress.getUser().toLowerCase(Locale.US); + String senderHost = senderMailAddress.getHost().toLowerCase(Locale.US); + + senderUser = getPrimaryName(senderUser); + + Connection conn = null; + PreparedStatement selectStmt = null; + PreparedStatement insertStmt = null; + boolean dbUpdated = false; + + StringWriter sout = new StringWriter(); + PrintWriter out = new PrintWriter(sout, true); + + try { + out.println("Answering on behalf of: " + whitelistManagerAddress); + out.println("Inserting in the white list of " + (new MailAddress(senderUser, senderHost)) + " ..."); + out.println(); + + MimeMessage message = mail.getMessage() ; + + Object content= message.getContent(); + + if (message.getContentType().startsWith("text/plain") + && content instanceof String) { + StringTokenizer st = new StringTokenizer((String) content, " \t\n\r\f,;:<>"); + while (st.hasMoreTokens()) { + ResultSet selectRS = null; + try { + MailAddress recipientMailAddress; + try { + recipientMailAddress = new MailAddress(st.nextToken()); + } + catch (javax.mail.internet.ParseException pe) { + continue; + } + String recipientUser = recipientMailAddress.getUser().toLowerCase(Locale.US); + String recipientHost = recipientMailAddress.getHost().toLowerCase(Locale.US); + + if (getMailetContext().isLocalServer(recipientHost)) { + // not a remote recipient, so skip + continue; + } + + if (conn == null) { + conn = datasource.getConnection(); + } + + if (selectStmt == null) { + selectStmt = conn.prepareStatement(selectByPK); + } + selectStmt.setString(1, senderUser); + selectStmt.setString(2, senderHost); + selectStmt.setString(3, recipientUser); + selectStmt.setString(4, recipientHost); + selectRS = selectStmt.executeQuery(); + if (selectRS.next()) { + //This address was already in the list + out.println("Skipped: " + recipientMailAddress); + continue; + } + + if (insertStmt == null) { + insertStmt = conn.prepareStatement(insert); + } + insertStmt.setString(1, senderUser); + insertStmt.setString(2, senderHost); + insertStmt.setString(3, recipientUser); + insertStmt.setString(4, recipientHost); + insertStmt.executeUpdate(); + dbUpdated = true; + out.println("Inserted: " + recipientMailAddress); + + } finally { + theJDBCUtil.closeJDBCResultSet(selectRS); + } + } + + if (dbUpdated) { + log("Insertion request issued by " + senderMailAddress); + } + //Commit our changes if necessary. + if (conn != null && dbUpdated && !conn.getAutoCommit()) { + conn.commit() ; + dbUpdated = false; + } + } + else { + out.println("The message must be plain - no action"); + } + + out.println(); + out.println("Finished"); + + sendReplyFromPostmaster(mail, sout.toString()); + + } catch (SQLException sqle) { + out.println("Error accessing the database"); + sendReplyFromPostmaster(mail, sout.toString()); + throw new MessagingException("Error accessing the database", sqle); + } catch (IOException ioe) { + out.println("Error getting message content"); + sendReplyFromPostmaster(mail, sout.toString()); + throw new MessagingException("Error getting message content", ioe); + } finally { + theJDBCUtil.closeJDBCStatement(selectStmt); + theJDBCUtil.closeJDBCStatement(insertStmt); + //Rollback our changes if necessary. + try { + if (conn != null && dbUpdated && !conn.getAutoCommit()) { + conn.rollback() ; + dbUpdated = false; + } + } + catch (Exception e) {} + theJDBCUtil.closeJDBCConnection(conn); + } + } + + /** Manages a remove request. + */ + private void manageRemoveRequest(Mail mail) + throws MessagingException { + MailAddress senderMailAddress = mail.getSender(); + String senderUser = senderMailAddress.getUser().toLowerCase(Locale.US); + String senderHost = senderMailAddress.getHost().toLowerCase(Locale.US); + + senderUser = getPrimaryName(senderUser); + + Connection conn = null; + PreparedStatement selectStmt = null; + PreparedStatement deleteStmt = null; + boolean dbUpdated = false; + + StringWriter sout = new StringWriter(); + PrintWriter out = new PrintWriter(sout, true); + + try { + out.println("Answering on behalf of: " + whitelistManagerAddress); + out.println("Removing from the white list of " + (new MailAddress(senderUser, senderHost)) + " ..."); + out.println(); + + MimeMessage message = mail.getMessage() ; + + Object content= message.getContent(); + + if (message.getContentType().startsWith("text/plain") + && content instanceof String) { + StringTokenizer st = new StringTokenizer((String) content, " \t\n\r\f,;:<>"); + while (st.hasMoreTokens()) { + ResultSet selectRS = null; + try { + MailAddress recipientMailAddress; + try { + recipientMailAddress = new MailAddress(st.nextToken()); + } + catch (javax.mail.internet.ParseException pe) { + continue; + } + String recipientUser = recipientMailAddress.getUser().toLowerCase(Locale.US); + String recipientHost = recipientMailAddress.getHost().toLowerCase(Locale.US); + + if (getMailetContext().isLocalServer(recipientHost)) { + // not a remote recipient, so skip + continue; + } + + if (conn == null) { + conn = datasource.getConnection(); + } + + if (selectStmt == null) { + selectStmt = conn.prepareStatement(selectByPK); + } + selectStmt.setString(1, senderUser); + selectStmt.setString(2, senderHost); + selectStmt.setString(3, recipientUser); + selectStmt.setString(4, recipientHost); + selectRS = selectStmt.executeQuery(); + if (!selectRS.next()) { + //This address was not in the list + out.println("Skipped: " + recipientMailAddress); + continue; + } + + if (deleteStmt == null) { + deleteStmt = conn.prepareStatement(deleteByPK); + } + deleteStmt.setString(1, senderUser); + deleteStmt.setString(2, senderHost); + deleteStmt.setString(3, recipientUser); + deleteStmt.setString(4, recipientHost); + deleteStmt.executeUpdate(); + dbUpdated = true; + out.println("Removed: " + recipientMailAddress); + + } finally { + theJDBCUtil.closeJDBCResultSet(selectRS); + } + } + + if (dbUpdated) { + log("Removal request issued by " + senderMailAddress); + } + //Commit our changes if necessary. + if (conn != null && dbUpdated && !conn.getAutoCommit()) { + conn.commit() ; + dbUpdated = false; + } + } + else { + out.println("The message must be plain - no action"); + } + + out.println(); + out.println("Finished"); + + sendReplyFromPostmaster(mail, sout.toString()); + + } catch (SQLException sqle) { + out.println("Error accessing the database"); + sendReplyFromPostmaster(mail, sout.toString()); + throw new MessagingException("Error accessing the database", sqle); + } catch (IOException ioe) { + out.println("Error getting message content"); + sendReplyFromPostmaster(mail, sout.toString()); + throw new MessagingException("Error getting message content", ioe); + } finally { + theJDBCUtil.closeJDBCStatement(selectStmt); + theJDBCUtil.closeJDBCStatement(deleteStmt); + //Rollback our changes if necessary. + try { + if (conn != null && dbUpdated && !conn.getAutoCommit()) { + conn.rollback() ; + dbUpdated = false; + } + } + catch (Exception e) {} + theJDBCUtil.closeJDBCConnection(conn); + } + } + + private void sendReplyFromPostmaster(Mail mail, String stringContent) throws MessagingException { + try { + MailAddress notifier = getMailetContext().getPostmaster(); + + MailAddress senderMailAddress = mail.getSender(); + + MimeMessage message = mail.getMessage(); + //Create the reply message + MimeMessage reply = new MimeMessage(Session.getDefaultInstance(System.getProperties(), null)); + + //Create the list of recipients in the Address[] format + InternetAddress[] rcptAddr = new InternetAddress[1]; + rcptAddr[0] = senderMailAddress.toInternetAddress(); + reply.setRecipients(Message.RecipientType.TO, rcptAddr); + + //Set the sender... + reply.setFrom(notifier.toInternetAddress()); + + //Create the message body + MimeMultipart multipart = new MimeMultipart(); + //Add message as the first mime body part + MimeBodyPart part = new MimeBodyPart(); + part.setContent(stringContent, "text/plain"); + part.setHeader(RFC2822Headers.CONTENT_TYPE, "text/plain"); + multipart.addBodyPart(part); + + reply.setContent(multipart); + reply.setHeader(RFC2822Headers.CONTENT_TYPE, multipart.getContentType()); + + //Create the list of recipients in our MailAddress format + Set recipients = new HashSet(); + recipients.add(senderMailAddress); + + //Set additional headers + if (reply.getHeader(RFC2822Headers.DATE)==null){ + reply.setHeader(RFC2822Headers.DATE, rfc822DateFormat.format(new java.util.Date())); + } + String subject = message.getSubject(); + if (subject == null) { + subject = ""; + } + if (subject.indexOf("Re:") == 0){ + reply.setSubject(subject); + } else { + reply.setSubject("Re:" + subject); + } + reply.setHeader(RFC2822Headers.IN_REPLY_TO, message.getMessageID()); + + //Send it off... + getMailetContext().sendMail(notifier, recipients, reply); + } + catch (Exception e) { + log("Exception found sending reply", e); + } + } + + /** Gets the main name of a local customer, handling alias */ + private String getPrimaryName(String originalUsername) { + String username; + try { + username = localusers.getRealName(originalUsername); + JamesUser user = (JamesUser) localusers.getUserByName(username); + if (user.getAliasing()) { + username = user.getAlias(); + } + } + catch (Exception e) { + username = originalUsername; + } + return username; + } + + /** + * Initializes the sql query environment from the SqlResources file. + * Will look for conf/sqlResources.xml. + * @param conn The connection for accessing the database + * @param mailetContext The current mailet context, + * for finding the conf/sqlResources.xml file + * @throws Exception If any error occurs + */ + public void initSqlQueries(Connection conn, org.apache.mailet.MailetContext mailetContext) throws Exception { + try { + if (conn.getAutoCommit()) { + conn.setAutoCommit(false); + } + + this.sqlFile = new File((String) mailetContext.getAttribute("confDir"), "sqlResources.xml").getCanonicalFile(); + sqlQueries.init(this.sqlFile, "WhiteList" , conn, getSqlParameters()); + + checkTables(conn); + } finally { + theJDBCUtil.closeJDBCConnection(conn); + } + } + + private void checkTables(Connection conn) throws SQLException { + DatabaseMetaData dbMetaData = conn.getMetaData(); + // Need to ask in the case that identifiers are stored, ask the DatabaseMetaInfo. + // Try UPPER, lower, and MixedCase, to see if the table is there. + + boolean dbUpdated = false; + + dbUpdated = createTable(conn, "whiteListTableName", "createWhiteListTable"); + + //Commit our changes if necessary. + if (conn != null && dbUpdated && !conn.getAutoCommit()) { + conn.commit(); + dbUpdated = false; + } + + } + + private boolean createTable(Connection conn, String tableNameSqlStringName, String createSqlStringName) throws SQLException { + String tableName = sqlQueries.getSqlString(tableNameSqlStringName, true); + + DatabaseMetaData dbMetaData = conn.getMetaData(); + + // Try UPPER, lower, and MixedCase, to see if the table is there. + if (theJDBCUtil.tableExists(dbMetaData, tableName)) { + return false; + } + + PreparedStatement createStatement = null; + + try { + createStatement = + conn.prepareStatement(sqlQueries.getSqlString(createSqlStringName, true)); + createStatement.execute(); + + StringBuffer logBuffer = null; + logBuffer = + new StringBuffer(64) + .append("Created table '") + .append(tableName) + .append("' using sqlResources string '") + .append(createSqlStringName) + .append("'."); + log(logBuffer.toString()); + + } finally { + theJDBCUtil.closeJDBCStatement(createStatement); + } + + return true; + } + +} + Added: james/server/branches/v2.3/src/java/org/apache/james/transport/matchers/IsInWhiteList.java URL: http://svn.apache.org/viewvc/james/server/branches/v2.3/src/java/org/apache/james/transport/matchers/IsInWhiteList.java?rev=413525&view=auto ============================================================================== --- james/server/branches/v2.3/src/java/org/apache/james/transport/matchers/IsInWhiteList.java (added) +++ james/server/branches/v2.3/src/java/org/apache/james/transport/matchers/IsInWhiteList.java Sun Jun 11 13:20:10 2006 @@ -0,0 +1,269 @@ +/*********************************************************************** + * Copyright (c) 2000-2006 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.matchers; + +import org.apache.mailet.*; + +import org.apache.avalon.cornerstone.services.datasources.*; +import org.apache.avalon.excalibur.datasource.*; +import org.apache.avalon.framework.service.*; + +import org.apache.james.*; +import org.apache.james.core.*; +import org.apache.james.services.*; +import org.apache.james.util.*; + +import javax.mail.*; +import javax.mail.internet.*; + +import java.util.Collection; +import java.util.StringTokenizer; + +import java.sql.*; +import java.util.*; +import java.text.*; +import java.io.*; + +/** + * <P>Matches recipients having the mail sender in the recipient's private whitelist .</P> + * <P> The recipient name is always converted to its primary name (handling aliases).</P> + * <P>Configuration string: The database name containing the white list table.</P> + * <P>Example:</P> + * <PRE><CODE> + * <mailet match="IsInWhiteList=db://maildb" class="ToProcessor"> + * <processor> transport </processor> + * </mailet> + * </CODE></PRE> + * @see org.apache.james.transport.mailets.WhiteListManager + * @version SVN $Revision: $ $Date: $ + * @since 2.3.0 + */ +public class IsInWhiteList extends GenericMatcher { + + private String selectByPK; + + private DataSourceComponent datasource; + + /** The store containing the local user repository. */ + private UsersStore usersStore; + + /** The user repository for this mail server. Contains all the users with inboxes + * on this server. + */ + private UsersRepository localusers; + + /** + * The JDBCUtil helper class + */ + private final JDBCUtil theJDBCUtil = new JDBCUtil() { + protected void delegatedLog(String logString) { + log("IsInWhiteList: " + logString); + } + }; + + /** + * Contains all of the sql strings for this component. + */ + private SqlResources sqlQueries = new SqlResources(); + + /** + * Holds value of property sqlFile. + */ + private File sqlFile; + + /** + * Holds value of property sqlParameters. + */ + private Map sqlParameters = new HashMap(); + + /** + * Getter for property sqlParameters. + * @return Value of property sqlParameters. + */ + private Map getSqlParameters() { + + return this.sqlParameters; + } + + /** + * Setter for property sqlParameters. + * @param sqlParameters New value of property sqlParameters. + */ + private void setSqlParameters(Map sqlParameters) { + + this.sqlParameters = sqlParameters; + } + + public void init() throws javax.mail.MessagingException { + String repositoryPath = null; + StringTokenizer st = new StringTokenizer(getCondition(), ", \t", false); + if (st.hasMoreTokens()) { + repositoryPath = st.nextToken().trim(); + } + if (repositoryPath != null) { + log("repositoryPath: " + repositoryPath); + } + else { + throw new MessagingException("repositoryPath is null"); + } + + ServiceManager serviceManager = (ServiceManager) getMailetContext().getAttribute(Constants.AVALON_COMPONENT_MANAGER); + + try { + // Get the DataSourceSelector block + DataSourceSelector datasources = (DataSourceSelector) serviceManager.lookup(DataSourceSelector.ROLE); + // Get the data-source required. + int stindex = repositoryPath.indexOf("://") + 3; + String datasourceName = repositoryPath.substring(stindex); + datasource = (DataSourceComponent) datasources.select(datasourceName); + } catch (Exception e) { + throw new MessagingException("Can't get datasource", e); + } + + try { + // Get the UsersRepository + usersStore = (UsersStore)serviceManager.lookup(UsersStore.ROLE); + localusers = (UsersRepository)usersStore.getRepository("LocalUsers"); + } catch (Exception e) { + throw new MessagingException("Can't get the local users repository", e); + } + + try { + initSqlQueries(datasource.getConnection(), getMailetContext()); + } catch (Exception e) { + throw new MessagingException("Exception initializing queries", e); + } + + selectByPK = sqlQueries.getSqlString("selectByPK", true); + } + + public Collection match(Mail mail) throws MessagingException { + // check if it's a local sender + MailAddress senderMailAddress = mail.getSender(); + if (senderMailAddress == null) { + return null; + } + String senderUser = senderMailAddress.getUser(); + String senderHost = senderMailAddress.getHost(); + if ( getMailetContext().isLocalServer(senderHost) + && getMailetContext().isLocalUser(senderUser)) { + // is a local sender, so return + return null; + } + + senderUser = senderUser.toLowerCase(Locale.US); + senderHost = senderHost.toLowerCase(Locale.US); + + Collection recipients = mail.getRecipients(); + + Collection inWhiteList = new java.util.HashSet(); + + Connection conn = null; + PreparedStatement selectStmt = null; + ResultSet selectRS = null; + try { + + for (Iterator i = recipients.iterator(); i.hasNext(); ) { + try { + MailAddress recipientMailAddress = (MailAddress)i.next(); + String recipientUser = recipientMailAddress.getUser().toLowerCase(Locale.US); + String recipientHost = recipientMailAddress.getHost().toLowerCase(Locale.US); + + if (!getMailetContext().isLocalServer(recipientHost)) { + // not a local recipient, so skip + continue; + } + + recipientUser = getPrimaryName(recipientUser); + + if (conn == null) { + conn = datasource.getConnection(); + } + + if (selectStmt == null) { + selectStmt = conn.prepareStatement(selectByPK); + } + selectStmt.setString(1, recipientUser); + selectStmt.setString(2, recipientHost); + selectStmt.setString(3, senderUser); + selectStmt.setString(4, senderHost); + selectRS = selectStmt.executeQuery(); + if (selectRS.next()) { + //This address was already in the list + inWhiteList.add(recipientMailAddress); + } + + } finally { + theJDBCUtil.closeJDBCResultSet(selectRS); + } + + } + + return inWhiteList; + + } catch (SQLException sqle) { + log("Error accessing database", sqle); + throw new MessagingException("Exception thrown", sqle); + } finally { + theJDBCUtil.closeJDBCStatement(selectStmt); + theJDBCUtil.closeJDBCConnection(conn); + } + } + + /** Gets the main name of a local customer, handling alias */ + private String getPrimaryName(String originalUsername) { + String username; + try { + username = localusers.getRealName(originalUsername); + JamesUser user = (JamesUser) localusers.getUserByName(username); + if (user.getAliasing()) { + username = user.getAlias(); + } + } + catch (Exception e) { + username = originalUsername; + } + return username; + } + + /** + * Initializes the sql query environment from the SqlResources file. + * Will look for conf/sqlResources.xml. + * Will <I>not</I> create the database resources, if missing + * (this task is done, if needed, in the [EMAIL PROTECTED] WhiteListManager} + * initialization routine). + * @param conn The connection for accessing the database + * @param mailetContext The current mailet context, + * for finding the conf/sqlResources.xml file + * @throws Exception If any error occurs + */ + public void initSqlQueries(Connection conn, org.apache.mailet.MailetContext mailetContext) throws Exception { + try { + if (conn.getAutoCommit()) { + conn.setAutoCommit(false); + } + + this.sqlFile = new File((String) mailetContext.getAttribute("confDir"), "sqlResources.xml").getCanonicalFile(); + sqlQueries.init(this.sqlFile, "WhiteList" , conn, getSqlParameters()); + + } finally { + theJDBCUtil.closeJDBCConnection(conn); + } + } + +} --------------------------------------------------------------------- To unsubscribe, e-mail: [EMAIL PROTECTED] For additional commands, e-mail: [EMAIL PROTECTED]