Author: norman
Date: Sun Aug 27 10:28:40 2006
New Revision: 437418
URL: http://svn.apache.org/viewvc?rev=437418&view=rev
Log:
Add fastfail handler for check for valid RCPT before accept the email. See
JAMES-510
Added:
james/server/trunk/src/java/org/apache/james/smtpserver/core/filter/fastfail/ValidRcptHandler.java
james/server/trunk/src/test/org/apache/james/smtpserver/ValidRcptHandlerTest.java
Modified:
james/server/trunk/src/conf/james-smtphandlerchain.xml
Modified: james/server/trunk/src/conf/james-smtphandlerchain.xml
URL:
http://svn.apache.org/viewvc/james/server/trunk/src/conf/james-smtphandlerchain.xml?rev=437418&r1=437417&r2=437418&view=diff
==============================================================================
--- james/server/trunk/src/conf/james-smtphandlerchain.xml (original)
+++ james/server/trunk/src/conf/james-smtphandlerchain.xml Sun Aug 27 10:28:40
2006
@@ -94,6 +94,20 @@
<checkAuthClients> false </checkAuthClients>
</handler>
-->
+
+ <!-- If activated all email will get rejected which has no valid user -->
+ <!-- You need to add the recipient to the validRecipient list if you want
-->
+ <!-- to accept email for a recipient which not exist on the server -->
+ <!-- If you enable the useVirtualUserTable its possible to use this
handler with the JDBCVirtualUserTable mailet -->
+ <!--
+ <handler
class="org.apache.james.smtpserver.core.filter.fastfail.ValidRcptHandler"
command="MAIL">
+ <validRecipients> </validRecipients>
+ <validDomains> </validDomains>
+ <validRegexPattern> </validRegexPattern>
+ <repositoryPath> db://maildb </repositoryPath>
+ <useVirtualUserTable> false </useVirtualUserTable>
+ </handler>
+ -->
<!-- If activated you can limit the maximal recipients -->
<!--
Added:
james/server/trunk/src/java/org/apache/james/smtpserver/core/filter/fastfail/ValidRcptHandler.java
URL:
http://svn.apache.org/viewvc/james/server/trunk/src/java/org/apache/james/smtpserver/core/filter/fastfail/ValidRcptHandler.java?rev=437418&view=auto
==============================================================================
---
james/server/trunk/src/java/org/apache/james/smtpserver/core/filter/fastfail/ValidRcptHandler.java
(added)
+++
james/server/trunk/src/java/org/apache/james/smtpserver/core/filter/fastfail/ValidRcptHandler.java
Sun Aug 27 10:28:40 2006
@@ -0,0 +1,312 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you 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.smtpserver.core.filter.fastfail;
+
+import java.sql.Connection;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.Iterator;
+import java.util.StringTokenizer;
+
+import org.apache.avalon.cornerstone.services.datasources.DataSourceSelector;
+import org.apache.avalon.excalibur.datasource.DataSourceComponent;
+import org.apache.avalon.framework.activity.Initializable;
+import org.apache.avalon.framework.configuration.Configurable;
+import org.apache.avalon.framework.configuration.Configuration;
+import org.apache.avalon.framework.configuration.ConfigurationException;
+import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.avalon.framework.service.ServiceException;
+import org.apache.avalon.framework.service.ServiceManager;
+import org.apache.avalon.framework.service.Serviceable;
+import org.apache.james.smtpserver.CommandHandler;
+import org.apache.james.smtpserver.SMTPSession;
+import org.apache.james.util.JDBCUtil;
+import org.apache.james.util.mail.dsn.DSNStatus;
+import org.apache.mailet.MailAddress;
+import org.apache.oro.text.regex.MalformedPatternException;
+import org.apache.oro.text.regex.Pattern;
+import org.apache.oro.text.regex.Perl5Compiler;
+import org.apache.oro.text.regex.Perl5Matcher;
+
+/**
+ * Handler which reject invalid recipients
+ */
+public class ValidRcptHandler extends AbstractLogEnabled implements
CommandHandler, Configurable,Serviceable,Initializable{
+
+ private Collection recipients = new ArrayList();
+ private Collection domains = new ArrayList();
+ private Collection regex = new ArrayList();
+ private String repositoryPath;
+ private DataSourceComponent dataSource;
+ private DataSourceSelector selector;
+ private boolean useVirtualUserTable = false;
+ private boolean useSql = false;
+ private String query = "select VirtualUserTable.target_address from
VirtualUserTable, VirtualUserTable as VUTDomains where (VirtualUserTable.user
like ? or VirtualUserTable.user like '\\%') and (VirtualUserTable.domain like ?
or (VirtualUserTable.domain like '\\%' and VUTDomains.domain like ?)) order by
concat(VirtualUserTable.user,'@',VirtualUserTable.domain) desc limit 1";
+
+ /**
+ * @see
org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
+ */
+ public void configure(Configuration arg0) throws ConfigurationException {
+ Configuration recipientsConfig = arg0.getChild("validRecipients");
+ if (recipientsConfig != null) {
+ setValidRecipients(recipientsConfig.getValue());
+ }
+
+ Configuration domainConfig = arg0.getChild("validDomains");
+ if (domainConfig != null) {
+ setValidDomains(domainConfig.getValue());
+ }
+
+ Configuration regexConfig = arg0.getChild("validRegexPattern");
+ if (regexConfig != null) {
+ try {
+ setValidRegex(regexConfig.getValue());
+ } catch(MalformedPatternException mpe) {
+ throw new ConfigurationException("Malformed pattern: ", mpe);
+ }
+ }
+
+ Configuration virtualUserTableConfig =
arg0.getChild("useVirtualUserTable", false);
+ if (virtualUserTableConfig != null) {
+ useVirtualUserTable =
virtualUserTableConfig.getValueAsBoolean(false);
+ }
+
+
+ Configuration configRepositoryPath = arg0.getChild("repositoryPath",
false);
+ if (configRepositoryPath != null) {
+ repositoryPath = configRepositoryPath.getValue();
+ useSql = true;
+
+ Configuration configQuery = arg0.getChild("query", false);
+ if (configQuery != null) {
+ this.query = configQuery.getValue();
+ }
+ }
+ }
+
+ /**
+ * Set the valid recipients.
+ *
+ * @param recip The valid recipients. Commaseperated list
+ */
+ public void setValidRecipients(String recip) {
+ StringTokenizer st = new StringTokenizer(recip, ", ", false);
+
+ while (st.hasMoreTokens()) {
+ String recipient = st.nextToken().toLowerCase();
+
+ getLogger().debug("Add recipient to valid recipients: " +
recipient);
+ recipients.add(recipient);
+ }
+ }
+
+ /**
+ * Set the valid domains.
+ *
+ * @param recip The valid domains. Commaseperated list
+ */
+ public void setValidDomains(String dom) {
+ StringTokenizer st = new StringTokenizer(dom, ", ", false);
+
+ while (st.hasMoreTokens()) {
+ String domain = st.nextToken().toLowerCase();
+ getLogger().debug("Add domain to valid domains: " + domain);
+ domains.add(domain);
+ }
+ }
+
+ /**
+ *
+ * @param reg
+ * @throws MalformedPatternException
+ */
+ public void setValidRegex(String reg) throws MalformedPatternException {
+ Perl5Compiler compiler = new Perl5Compiler();
+
+ StringTokenizer st = new StringTokenizer(reg, ", ", false);
+
+ while (st.hasMoreTokens()) {
+ String patternString = st.nextToken().trim();
+
+ getLogger().debug("Add regex to valid regex: " + patternString);
+
+ Pattern pattern = compiler.compile(patternString,
Perl5Compiler.READ_ONLY_MASK);
+ regex.add(pattern);
+
+ }
+ }
+
+ /**
+ * @see
org.apache.avalon.framework.service.Serviceable#service(ServiceManager)
+ */
+ public void service(ServiceManager arg0) throws ServiceException {
+ selector = (DataSourceSelector) arg0.lookup(DataSourceSelector.ROLE);
+ }
+
+ /**
+ * @see org.apache.james.smtpserver.CommandHandler#getImplCommands()
+ */
+ public Collection getImplCommands() {
+ Collection c = new ArrayList();
+ c.add("RCPT");
+
+ return c;
+ }
+
+ /**
+ * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
+ */
+ public void onCommand(SMTPSession session) {
+ if (!session.isRelayingAllowed() && !(session.isAuthRequired() &&
session.getUser() != null)) {
+ checkValidRcpt(session);
+ } else {
+ getLogger().debug("Sender allowed to relay");
+ }
+ }
+
+
+
+ /**
+ * Check if the recipient should be accepted
+ *
+ * @param session The SMTPSession
+ */
+ private void checkValidRcpt(SMTPSession session) {
+ MailAddress rcpt = (MailAddress)
session.getState().get(SMTPSession.CURRENT_RECIPIENT);
+ boolean invalidUser = true;
+
+ if
(session.getConfigurationData().getUsersRepository().contains(rcpt.getUser())
== true || recipients.contains(rcpt.toString().toLowerCase()) ||
domains.contains(rcpt.getHost().toLowerCase())) {
+ invalidUser = false;
+ }
+
+ if (invalidUser == true && !regex.isEmpty()) {
+ Iterator reg = regex.iterator();
+ Perl5Matcher matcher = new Perl5Matcher();
+
+ while (reg.hasNext()) {
+ if (matcher.matches(rcpt.toString(), (Pattern) reg.next())) {
+ // regex match
+ invalidUser = false;
+ break;
+ }
+ }
+ }
+
+ if (useSql == true && invalidUser == true) {
+ invalidUser = userExist(rcpt,useVirtualUserTable);
+ }
+
+ if (invalidUser == true) {
+ //user not exist
+ String responseString = "554 " +
DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_MAILBOX) + " Unknown
user: " + rcpt.toString();
+
+ getLogger().info("Rejected message. Unknown user: " +
rcpt.toString());
+
+ session.writeResponse(responseString);
+ session.setStopHandlerProcessing(true);
+ }
+ }
+
+ /**
+ * Return true if the MailAddress exists in the table
+ *
+ * @param rcpt The MailAddress to check
+ * @return true if the MailAddress exists in the table. If not false
+ */
+ private boolean userExist(MailAddress rcpt, boolean useVirtualUserTable) {
+ Connection conn = null;
+ PreparedStatement mappingStmt = null;
+ try {
+ conn = dataSource.getConnection();
+ mappingStmt = conn.prepareStatement(query);
+ ResultSet mappingRS = null;
+ try {
+ if (useVirtualUserTable == true) {
+ mappingStmt.setString(1, rcpt.getUser());
+ mappingStmt.setString(2, rcpt.getHost());
+ mappingStmt.setString(3, rcpt.getHost());
+ } else {
+ mappingStmt.setString(1, rcpt.toString());
+ }
+ mappingRS = mappingStmt.executeQuery();
+
+ if (mappingRS.next()) {
+ return true;
+ }
+ } finally {
+ theJDBCUtil.closeJDBCResultSet(mappingRS);
+ }
+
+ } catch (SQLException sqle) {
+ getLogger().error("Error accessing database", sqle);
+
+ // safety first
+ return true;
+ } finally {
+ theJDBCUtil.closeJDBCStatement(mappingStmt);
+ theJDBCUtil.closeJDBCConnection(conn);
+ }
+ return false;
+
+ }
+
+ /**
+ * The JDBCUtil helper class
+ */
+ private final JDBCUtil theJDBCUtil = new JDBCUtil() {
+ protected void delegatedLog(String logString) {
+ getLogger().debug("ValidRcpt: " + logString);
+ }
+ };
+
+ /**
+ * Return the DataSourceSelector
+ *
+ * @param datasources The DataSourceSelector
+ * @param repositoryPath The repositoryPath
+ * @return true or false
+ *
+ * @throws ServiceException
+ */
+ private DataSourceComponent getDataSource(DataSourceSelector
datasources,String repositoryPath) throws ServiceException {
+
+ int stindex = repositoryPath.indexOf("://") + 3;
+ String datasourceName = repositoryPath.substring(stindex);
+
+ return (DataSourceComponent) datasources.select(datasourceName);
+ }
+
+ /**
+ * @see org.apache.avalon.framework.activity.Initializable#initialize()
+ */
+ public void initialize() throws Exception {
+ if (useSql == true) {
+ // init the datasource
+ dataSource = getDataSource(selector,repositoryPath);
+ getLogger().debug("SQL is used");
+ }
+ }
+}
Added:
james/server/trunk/src/test/org/apache/james/smtpserver/ValidRcptHandlerTest.java
URL:
http://svn.apache.org/viewvc/james/server/trunk/src/test/org/apache/james/smtpserver/ValidRcptHandlerTest.java?rev=437418&view=auto
==============================================================================
---
james/server/trunk/src/test/org/apache/james/smtpserver/ValidRcptHandlerTest.java
(added)
+++
james/server/trunk/src/test/org/apache/james/smtpserver/ValidRcptHandlerTest.java
Sun Aug 27 10:28:40 2006
@@ -0,0 +1,252 @@
+/****************************************************************
+ * Licensed to the Apache Software Foundation (ASF) under one *
+ * or more contributor license agreements. See the NOTICE file *
+ * distributed with this work for additional information *
+ * regarding copyright ownership. The ASF licenses this file *
+ * to you 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.smtpserver;
+
+import java.util.HashMap;
+import java.util.Map;
+
+import javax.mail.internet.ParseException;
+
+import org.apache.avalon.framework.container.ContainerUtil;
+import org.apache.james.services.MailServer;
+import org.apache.james.services.UsersRepository;
+import org.apache.james.smtpserver.core.filter.fastfail.ValidRcptHandler;
+import org.apache.james.test.mock.avalon.MockLogger;
+import org.apache.james.userrepository.MockUsersRepository;
+import org.apache.mailet.MailAddress;
+import org.apache.oro.text.regex.MalformedPatternException;
+
+import junit.framework.TestCase;
+
+public class ValidRcptHandlerTest extends TestCase {
+
+ private final static String VALID_USER = "postmaster";
+ private final static String INVALID_USER = "invalid";
+ private String response = null;
+
+ public void setUp() {
+ response = null;
+ }
+
+ private SMTPSession setupMockedSMTPSession(final
SMTPHandlerConfigurationData conf, final MailAddress rcpt, final boolean
relayingAllowed, final boolean authRequired, final String username) {
+ SMTPSession session = new AbstractSMTPSession() {
+ HashMap state = new HashMap();
+ boolean stop = false;
+
+ public boolean isAuthRequired() {
+ return authRequired;
+ }
+
+ public String getUser() {
+ return username;
+ }
+
+ public boolean isRelayingAllowed() {
+ return relayingAllowed;
+ }
+
+ public SMTPHandlerConfigurationData getConfigurationData() {
+ return conf;
+ }
+
+ public Map getState() {
+ state.put(SMTPSession.CURRENT_RECIPIENT,rcpt);
+
+ return state;
+ }
+
+ public void writeResponse(String resp) {
+ response = resp;
+ }
+
+ public void setStopHandlerProcessing(boolean stop) {
+ this.stop = stop;
+ }
+
+ public boolean getStopHandlerProcessing() {
+ return stop;
+ }
+ };
+
+ return session;
+ }
+
+ private SMTPHandlerConfigurationData setupMockedSMTPConfiguration() {
+ SMTPHandlerConfigurationData conf = new SMTPHandlerConfigurationData()
{
+ UsersRepository user = new MockUsersRepository();
+
+ public String getHelloName() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public MailServer getMailServer() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public long getMaxMessageSize() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public int getResetLength() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public String getSMTPGreeting() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public UsersRepository getUsersRepository() {
+ user.addUser(VALID_USER,"xxx");
+ return user;
+ }
+
+ public boolean isAuthRequired(String remoteIP) {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public boolean isAuthRequired() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public boolean isRelayingAllowed(String remoteIP) {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public boolean isVerifyIdentity() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ public boolean useHeloEhloEnforcement() {
+ throw new UnsupportedOperationException("Unimplemented Stub
Method");
+ }
+
+ };
+
+ return conf;
+ }
+
+ public void testRejectInvalidUser() throws ParseException {
+ ValidRcptHandler handler = new ValidRcptHandler();
+ SMTPSession session =
setupMockedSMTPSession(setupMockedSMTPConfiguration(),new
MailAddress(INVALID_USER + "@localhost"),false,false,null);
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ handler.onCommand(session);
+
+ assertTrue("Rejected",session.getStopHandlerProcessing());
+ assertNotNull("Rejected",response);
+ }
+
+ public void testNotRejectInvalidUserAuth() throws ParseException {
+ ValidRcptHandler handler = new ValidRcptHandler();
+ SMTPSession session =
setupMockedSMTPSession(setupMockedSMTPConfiguration(),new
MailAddress(INVALID_USER + "@localhost"),false,true,"authedUser");
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ handler.onCommand(session);
+
+ assertFalse("Not rejected",session.getStopHandlerProcessing());
+ assertNull("Not rejected",response);
+ }
+
+ public void testNotRejectInvalidUserRelay() throws ParseException {
+ ValidRcptHandler handler = new ValidRcptHandler();
+ SMTPSession session =
setupMockedSMTPSession(setupMockedSMTPConfiguration(),new
MailAddress(INVALID_USER + "@localhost"),true,false,null);
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ handler.onCommand(session);
+
+ assertFalse("Not rejected",session.getStopHandlerProcessing());
+ assertNull("Not rejected",response);
+ }
+
+ public void testNotRejectValidUser() throws ParseException {
+ ValidRcptHandler handler = new ValidRcptHandler();
+ SMTPSession session =
setupMockedSMTPSession(setupMockedSMTPConfiguration(),new
MailAddress(VALID_USER + "@localhost"),false,false,null);
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ handler.onCommand(session);
+
+ assertFalse("Not rejected",session.getStopHandlerProcessing());
+ assertNull("Not rejected",response);
+ }
+
+ public void testNotRejectValidUserRecipient() throws ParseException {
+ String recipient = "[EMAIL PROTECTED]";
+ ValidRcptHandler handler = new ValidRcptHandler();
+ SMTPSession session =
setupMockedSMTPSession(setupMockedSMTPConfiguration(),new
MailAddress(recipient),false,false,null);
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ handler.setValidRecipients(recipient);
+ handler.onCommand(session);
+
+
+ assertFalse("Not rejected",session.getStopHandlerProcessing());
+ assertNull("Not rejected",response);
+ }
+
+ public void testNotRejectValidUserDomain() throws ParseException {
+ String domain = "domain";
+ String recipient = "recip@" + domain;
+
+ ValidRcptHandler handler = new ValidRcptHandler();
+ SMTPSession session =
setupMockedSMTPSession(setupMockedSMTPConfiguration(),new
MailAddress(recipient),false,false,null);
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ handler.setValidDomains(domain);
+ handler.onCommand(session);
+
+
+ assertFalse("Not rejected",session.getStopHandlerProcessing());
+ assertNull("Not rejected",response);
+ }
+
+ public void testNotRejectValidUserRegex() throws ParseException,
MalformedPatternException {
+ String domain = "domain";
+ String recipient = "recip@" + domain;
+
+ ValidRcptHandler handler = new ValidRcptHandler();
+ SMTPSession session =
setupMockedSMTPSession(setupMockedSMTPConfiguration(),new
MailAddress(recipient),false,false,null);
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ handler.setValidRegex("reci.*");
+ handler.onCommand(session);
+
+
+ assertFalse("Not rejected",session.getStopHandlerProcessing());
+ assertNull("Not rejected",response);
+ }
+
+ public void testInvalidRegex() throws ParseException{
+ boolean exception = false;
+ ValidRcptHandler handler = new ValidRcptHandler();
+ ContainerUtil.enableLogging(handler,new MockLogger());
+
+ try {
+ handler.setValidRegex("(.*");
+ } catch (MalformedPatternException e) {
+ exception = true;
+ }
+
+ assertTrue("Invalid Config",exception);
+ }
+
+}
---------------------------------------------------------------------
To unsubscribe, e-mail: [EMAIL PROTECTED]
For additional commands, e-mail: [EMAIL PROTECTED]