You are viewing a plain text version of this content. The canonical link for it is here.
Posted to server-dev@james.apache.org by no...@apache.org on 2006/12/25 18:31:12 UTC
svn commit: r490155 [2/3] - in
/james/server/sandbox/handlerapi-experiment/src:
java/org/apache/james/core/ java/org/apache/james/nntpserver/
java/org/apache/james/pop3server/ java/org/apache/james/remotemanager/
java/org/apache/james/smtpserver/ java/...
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/HeloFilterCmdHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/HeloFilterCmdHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/HeloFilterCmdHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/HeloFilterCmdHandler.java Mon Dec 25 09:31:07 2006
@@ -27,6 +27,7 @@
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.james.smtpserver.CommandHandler;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
@@ -45,30 +46,25 @@
*
* @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
**/
- public void onCommand(SMTPSession session) {
- doHELO(session, session.getCommandArgument());
+ public SMTPResponse onCommand(SMTPSession session, String command, String arguments) {
+ return doHELO(session, arguments);
}
/**
* @param session SMTP session object
* @param argument the argument passed in with the command by the SMTP client
*/
- private void doHELO(SMTPSession session, String argument) {
- String responseString = null;
-
+ private SMTPResponse doHELO(SMTPSession session, String argument) {
session.resetState();
if (argument == null) {
- responseString = "501 Domain address required: " + COMMAND_NAME;
- session.writeResponse(responseString);
+ String responseString = "Domain address required: " + COMMAND_NAME;
getLogger().info(responseString);
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
+ return new SMTPResponse("501", responseString);
} else {
// store provided name
session.getState().put(SMTPSession.CURRENT_HELO_NAME,argument);
+ return null;
}
}
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/MailFilterCmdHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/MailFilterCmdHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/MailFilterCmdHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/MailFilterCmdHandler.java Mon Dec 25 09:31:07 2006
@@ -28,6 +28,7 @@
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.james.smtpserver.CommandHandler;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
import org.apache.james.util.mail.dsn.DSNStatus;
import org.apache.mailet.MailAddress;
@@ -48,8 +49,8 @@
*
* @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
*/
- public void onCommand(SMTPSession session) {
- doMAIL(session, session.getCommandArgument());
+ public SMTPResponse onCommand(SMTPSession session, String command, String arguments) {
+ return doMAIL(session, arguments);
}
@@ -57,8 +58,7 @@
* @param session SMTP session object
* @param argument the argument passed in with the command by the SMTP client
*/
- private void doMAIL(SMTPSession session, String argument) {
- String responseString = null;
+ private SMTPResponse doMAIL(SMTPSession session, String argument) {
String sender = null;
if ((argument != null) && (argument.indexOf(":") > 0)) {
@@ -67,27 +67,12 @@
argument = argument.substring(0, colonIndex);
}
if (session.getState().containsKey(SMTPSession.SENDER)) {
- responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Sender already specified";
- session.writeResponse(responseString);
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
+ return new SMTPResponse("503", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Sender already specified");
} else if (!session.getConnectionState().containsKey(SMTPSession.CURRENT_HELO_MODE) && session.useHeloEhloEnforcement()) {
- responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Need HELO or EHLO before MAIL";
- session.writeResponse(responseString);
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
+ return new SMTPResponse("503", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Need HELO or EHLO before MAIL");
} else if (argument == null || !argument.toUpperCase(Locale.US).equals("FROM")
|| sender == null) {
- responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Usage: MAIL FROM:<sender>";
- session.writeResponse(responseString);
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
+ return new SMTPResponse("501", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Usage: MAIL FROM:<sender>");
} else {
sender = sender.trim();
// the next gt after the first lt ... AUTH may add more <>
@@ -114,8 +99,9 @@
// Handle the SIZE extension keyword
if (mailOptionName.startsWith(MAIL_OPTION_SIZE)) {
- if (!(doMailSize(session, mailOptionValue, sender))) {
- return;
+ SMTPResponse res = doMailSize(session, mailOptionValue, sender);
+ if (res != null) {
+ return res;
}
} else {
// Unexpected option attached to the Mail command
@@ -132,8 +118,6 @@
}
}
if ( session.getConfigurationData().useAddressBracketsEnforcement() && (!sender.startsWith("<") || !sender.endsWith(">"))) {
- responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX_SENDER)+" Syntax error in MAIL command";
- session.writeResponse(responseString);
if (getLogger().isErrorEnabled()) {
StringBuffer errorBuffer =
new StringBuffer(128)
@@ -142,10 +126,7 @@
.append(": did not start and end with < >");
getLogger().error(errorBuffer.toString());
}
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return;
+ return new SMTPResponse("501", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX_SENDER)+" Syntax error in MAIL command");
}
MailAddress senderAddress = null;
@@ -165,8 +146,6 @@
try {
senderAddress = new MailAddress(sender);
} catch (Exception pe) {
- responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX_SENDER)+" Syntax error in sender address";
- session.writeResponse(responseString);
if (getLogger().isErrorEnabled()) {
StringBuffer errorBuffer =
new StringBuffer(256)
@@ -176,17 +155,14 @@
.append(pe.getMessage());
getLogger().error(errorBuffer.toString());
}
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return;
+ return new SMTPResponse("501", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX_SENDER)+" Syntax error in sender address");
}
}
// Store the senderAddress in session map
session.getState().put(SMTPSession.SENDER, senderAddress);
}
+ return null;
}
/**
@@ -197,20 +173,14 @@
* @param tempSender the sender specified in this mail command (for logging purpose)
* @return true if further options should be processed, false otherwise
*/
- private boolean doMailSize(SMTPSession session, String mailOptionValue, String tempSender) {
+ private SMTPResponse doMailSize(SMTPSession session, String mailOptionValue, String tempSender) {
int size = 0;
try {
size = Integer.parseInt(mailOptionValue);
} catch (NumberFormatException pe) {
- // This is a malformed option value. We return an error
- String responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Syntactically incorrect value for SIZE parameter";
- session.writeResponse(responseString);
getLogger().error("Rejected syntactically incorrect value for SIZE parameter.");
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return false;
+ // This is a malformed option value. We return an error
+ return new SMTPResponse("501", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_INVALID_ARG)+" Syntactically incorrect value for SIZE parameter");
}
if (getLogger().isDebugEnabled()) {
StringBuffer debugBuffer =
@@ -223,8 +193,6 @@
long maxMessageSize = session.getConfigurationData().getMaxMessageSize();
if ((maxMessageSize > 0) && (size > maxMessageSize)) {
// Let the client know that the size limit has been hit.
- String responseString = "552 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SYSTEM_MSG_TOO_BIG)+" Message size exceeds fixed maximum message size";
- session.writeResponse(responseString);
StringBuffer errorBuffer =
new StringBuffer(256)
.append("Rejected message from ")
@@ -240,16 +208,13 @@
.append("based on SIZE option.");
getLogger().error(errorBuffer.toString());
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return false;
+ return new SMTPResponse("552", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SYSTEM_MSG_TOO_BIG)+" Message size exceeds fixed maximum message size");
} else {
// put the message size in the message state so it can be used
// later to restrict messages for user quotas, etc.
session.getState().put(MESG_SIZE, new Integer(size));
}
- return true;
+ return null;
}
/**
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/RcptFilterCmdHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/RcptFilterCmdHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/RcptFilterCmdHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/RcptFilterCmdHandler.java Mon Dec 25 09:31:07 2006
@@ -29,7 +29,9 @@
import org.apache.avalon.framework.logger.AbstractLogEnabled;
import org.apache.james.smtpserver.CommandHandler;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
+import org.apache.james.util.mail.SMTPRetCode;
import org.apache.james.util.mail.dsn.DSNStatus;
import org.apache.mailet.MailAddress;
@@ -45,8 +47,8 @@
*
* @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
**/
- public void onCommand(SMTPSession session) {
- doRCPT(session, session.getCommandArgument());
+ public SMTPResponse onCommand(SMTPSession session, String command, String parameters) {
+ return doRCPT(session, parameters);
}
@@ -54,9 +56,7 @@
* @param session SMTP session object
* @param argument the argument passed in with the command by the SMTP client
*/
- private void doRCPT(SMTPSession session, String argument) {
- String responseString = null;
-
+ private SMTPResponse doRCPT(SMTPSession session, String argument) {
String recipient = null;
if ((argument != null) && (argument.indexOf(":") > 0)) {
int colonIndex = argument.indexOf(":");
@@ -64,20 +64,10 @@
argument = argument.substring(0, colonIndex);
}
if (!session.getState().containsKey(SMTPSession.SENDER)) {
- responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Need MAIL before RCPT";
- session.writeResponse(responseString);
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
+ return new SMTPResponse("503", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_OTHER)+" Need MAIL before RCPT");
} else if (argument == null || !argument.toUpperCase(Locale.US).equals("TO")
|| recipient == null) {
- responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_SYNTAX)+" Usage: RCPT TO:<recipient>";
- session.writeResponse(responseString);
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
+ return new SMTPResponse("501", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_SYNTAX)+" Usage: RCPT TO:<recipient>");
} else {
Collection rcptColl = (Collection) session.getState().get(SMTPSession.RCPT_LIST);
if (rcptColl == null) {
@@ -95,8 +85,6 @@
recipient = recipient.substring(0, lastChar + 1);
}
if (session.getConfigurationData().useAddressBracketsEnforcement() && (!recipient.startsWith("<") || !recipient.endsWith(">"))) {
- responseString = "501 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_SYNTAX)+" Syntax error in parameters or arguments";
- session.writeResponse(responseString);
if (getLogger().isErrorEnabled()) {
StringBuffer errorBuffer =
new StringBuffer(192)
@@ -105,11 +93,7 @@
.append(getContext(session,null,recipient));
getLogger().error(errorBuffer.toString());
}
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return;
+ return new SMTPResponse("501", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.DELIVERY_SYNTAX)+" Syntax error in parameters or arguments");
}
MailAddress recipientAddress = null;
//Remove < and >
@@ -125,14 +109,6 @@
try {
recipientAddress = new MailAddress(recipient);
} catch (Exception pe) {
- /*
- * from RFC2822;
- * 553 Requested action not taken: mailbox name not allowed
- * (e.g., mailbox syntax incorrect)
- */
- responseString = "553 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX)+" Syntax error in recipient address";
- session.writeResponse(responseString);
-
if (getLogger().isErrorEnabled()) {
StringBuffer errorBuffer =
new StringBuffer(192)
@@ -141,79 +117,65 @@
.append(pe.getMessage());
getLogger().error(errorBuffer.toString());
}
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return;
+ /*
+ * from RFC2822;
+ * 553 Requested action not taken: mailbox name not allowed
+ * (e.g., mailbox syntax incorrect)
+ */
+ return new SMTPResponse("553", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.ADDRESS_SYNTAX)+" Syntax error in recipient address");
}
-
- if (session.isAuthRequired() && !session.isRelayingAllowed()) {
- // Make sure the mail is being sent locally if not
- // authenticated else reject.
- if (session.getUser() == null) {
+ if (!session.isRelayingAllowed()) {
+ if (session.isAuthRequired()) {
+ // Make sure the mail is being sent locally if not
+ // authenticated else reject.
+ if (session.getUser() == null) {
+ String toDomain = recipientAddress.getHost();
+ if (!session.getConfigurationData().getMailServer().isLocalServer(toDomain)) {
+ StringBuffer sb = new StringBuffer(128);
+ sb.append("Rejected message - authentication is required for mail request");
+ sb.append(getContext(session,recipientAddress,recipient));
+ getLogger().error(sb.toString());
+ return new SMTPResponse("530", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Authentication Required");
+ }
+ } else {
+ // Identity verification checking
+ if (session.getConfigurationData().isVerifyIdentity()) {
+ String authUser = (session.getUser()).toLowerCase(Locale.US);
+ MailAddress senderAddress = (MailAddress) session.getState().get(SMTPSession.SENDER);
+
+ if ((senderAddress == null) || (!authUser.equals(senderAddress.getUser())) ||
+ (!session.getConfigurationData().getMailServer().isLocalServer(senderAddress.getHost()))) {
+ if (getLogger().isErrorEnabled()) {
+ StringBuffer errorBuffer =
+ new StringBuffer(128)
+ .append("User ")
+ .append(authUser)
+ .append(" authenticated, however tried sending email as ")
+ .append(senderAddress)
+ .append(getContext(session,recipientAddress,recipient));
+ getLogger().error(errorBuffer.toString());
+ }
+
+ return new SMTPResponse("503", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Incorrect Authentication for Specified Email Address");
+ }
+ }
+ }
+ } else {
String toDomain = recipientAddress.getHost();
if (!session.getConfigurationData().getMailServer().isLocalServer(toDomain)) {
- responseString = "530 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Authentication Required";
- session.writeResponse(responseString);
- StringBuffer sb = new StringBuffer(128);
- sb.append("Rejected message - authentication is required for mail request");
- sb.append(getContext(session,recipientAddress,recipient));
- getLogger().error(sb.toString());
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
+ StringBuffer errorBuffer = new StringBuffer(128)
+ .append("Rejected message - ")
+ .append(session.getRemoteIPAddress())
+ .append(" not authorized to relay to ")
+ .append(toDomain)
+ .append(getContext(session,recipientAddress,recipient));
+ getLogger().error(errorBuffer.toString());
- return;
- }
- } else {
- // Identity verification checking
- if (session.getConfigurationData().isVerifyIdentity()) {
- String authUser = (session.getUser()).toLowerCase(Locale.US);
- MailAddress senderAddress = (MailAddress) session.getState().get(SMTPSession.SENDER);
-
- if ((senderAddress == null) || (!authUser.equals(senderAddress.getUser())) ||
- (!session.getConfigurationData().getMailServer().isLocalServer(senderAddress.getHost()))) {
- responseString = "503 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Incorrect Authentication for Specified Email Address";
- session.writeResponse(responseString);
- if (getLogger().isErrorEnabled()) {
- StringBuffer errorBuffer =
- new StringBuffer(128)
- .append("User ")
- .append(authUser)
- .append(" authenticated, however tried sending email as ")
- .append(senderAddress)
- .append(getContext(session,recipientAddress,recipient));
- getLogger().error(errorBuffer.toString());
- }
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return;
- }
+ return new SMTPResponse("550", DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Requested action not taken: relaying denied");
}
}
- } else if (!session.isRelayingAllowed()) {
- String toDomain = recipientAddress.getHost();
- if (!session.getConfigurationData().getMailServer().isLocalServer(toDomain)) {
- responseString = "550 "+DSNStatus.getStatus(DSNStatus.PERMANENT,DSNStatus.SECURITY_AUTH)+" Requested action not taken: relaying denied";
- session.writeResponse(responseString);
- StringBuffer errorBuffer = new StringBuffer(128)
- .append("Rejected message - ")
- .append(session.getRemoteIPAddress())
- .append(" not authorized to relay to ")
- .append(toDomain)
- .append(getContext(session,recipientAddress,recipient));
- getLogger().error(errorBuffer.toString());
-
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
- return;
- }
}
if (rcptOptionString != null) {
@@ -239,15 +201,14 @@
getLogger().debug(debugBuffer.toString());
}
- // After this filter match we should not call any other handler!
- session.setStopHandlerProcessing(true);
-
+ return new SMTPResponse(SMTPRetCode.PARAMETER_NOT_IMPLEMENTED, "Unrecognized or unsupported option: "+rcptOptionName);
}
optionTokenizer = null;
}
session.getState().put(SMTPSession.CURRENT_RECIPIENT,recipientAddress);
}
+ return null;
}
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/AbstractJunkHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/AbstractJunkHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/AbstractJunkHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/AbstractJunkHandler.java Mon Dec 25 09:31:07 2006
@@ -25,6 +25,7 @@
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.avalon.framework.logger.AbstractLogEnabled;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
import org.apache.james.util.junkscore.JunkScore;
import org.apache.james.util.junkscore.JunkScoreConfigUtil;
@@ -95,7 +96,7 @@
*
* @param session the SMTPSession
*/
- protected void doProcessing(SMTPSession session) {
+ protected SMTPResponse doProcessing(SMTPSession session) {
if (check(session)) {
JunkHandlerData data = getJunkHandlerData(session);
@@ -110,11 +111,13 @@
if (data.getRejectLogString() != null) getLogger().info(data.getRejectLogString());
- session.writeResponse(response);
// After this filter match we should not call any other handler!
session.setStopHandlerProcessing(true);
+
+ return new SMTPResponse(response);
}
- }
+ }
+ return null;
}
/**
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/DNSRBLHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/DNSRBLHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/DNSRBLHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/DNSRBLHandler.java Mon Dec 25 09:31:07 2006
@@ -30,6 +30,7 @@
import org.apache.james.services.DNSServer;
import org.apache.james.smtpserver.CommandHandler;
import org.apache.james.smtpserver.ConnectHandler;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
import org.apache.james.util.junkscore.JunkScore;
import org.apache.james.util.mail.dsn.DSNStatus;
@@ -256,8 +257,9 @@
/**
* @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
*/
- public void onCommand(SMTPSession session) {
- doProcessing(session);
+ public SMTPResponse onCommand(SMTPSession session, String command, String parameters) {
+ doProcessing(session);
+ return null;
}
/**
@@ -299,4 +301,5 @@
data.setScoreName("DNSRBLCheck");
return data;
}
+
}
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/GreylistHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/GreylistHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/GreylistHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/GreylistHandler.java Mon Dec 25 09:31:07 2006
@@ -1,686 +1,682 @@
-/****************************************************************
- * 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.io.File;
-import java.sql.Connection;
-import java.sql.DatabaseMetaData;
-import java.sql.PreparedStatement;
-import java.sql.ResultSet;
-import java.sql.SQLException;
-import java.util.ArrayList;
-import java.util.Collection;
-import java.util.HashMap;
-import java.util.Iterator;
-import java.util.Map;
-import java.util.StringTokenizer;
-import java.sql.Timestamp;
-
-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.services.DNSServer;
-import org.apache.james.services.FileSystem;
-import org.apache.james.smtpserver.CommandHandler;
-import org.apache.james.smtpserver.SMTPSession;
-import org.apache.james.util.JDBCUtil;
-import org.apache.james.util.NetMatcher;
-import org.apache.james.util.SqlResources;
-import org.apache.james.util.TimeConverter;
-import org.apache.james.util.mail.dsn.DSNStatus;
-import org.apache.mailet.MailAddress;
-
-/**
- * GreylistHandler which can be used to activate Greylisting
- */
-public class GreylistHandler extends AbstractLogEnabled implements
- CommandHandler, Configurable, Serviceable, Initializable {
-
- private DataSourceSelector datasources = null;
-
- private DataSourceComponent datasource = null;
-
- private FileSystem fileSystem = null;
-
- // 1 hour
- private long tempBlockTime = 3600000;
-
- // 36 days
- private long autoWhiteListLifeTime = 3110400000L;
-
- // 4 hours
- private long unseenLifeTime = 14400000;
-
- private String selectQuery;
-
- private String insertQuery;
-
- private String deleteQuery;
-
- private String deleteAutoWhiteListQuery;
-
- private String updateQuery;
-
- /**
- * Contains all of the sql strings for this component.
- */
- private SqlResources sqlQueries = new SqlResources();
-
- /**
- * The sqlFileUrl
- */
- private String sqlFileUrl;
-
- /**
- * Holds value of property sqlParameters.
- */
- private Map sqlParameters = new HashMap();
-
- /**
- * The repositoryPath
- */
- private String repositoryPath;
-
- private DNSServer dnsServer;
-
- private NetMatcher wNetworks;
-
- /**
- * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
- */
- public void configure(Configuration handlerConfiguration) throws ConfigurationException {
- Configuration configTemp = handlerConfiguration.getChild("tempBlockTime", false);
- if (configTemp != null) {
- try {
- setTempBlockTime(configTemp.getValue());
-
- } catch (NumberFormatException e) {
- throw new ConfigurationException(e.getMessage());
- }
- }
-
- Configuration configAutoWhiteList = handlerConfiguration.getChild("autoWhiteListLifeTime", false);
- if (configAutoWhiteList != null) {
- try {
- setAutoWhiteListLifeTime(configAutoWhiteList.getValue());
- } catch (NumberFormatException e) {
- throw new ConfigurationException(e.getMessage());
- }
- }
-
- Configuration configUnseen = handlerConfiguration.getChild("unseenLifeTime", false);
- if (configUnseen != null) {
- try {
- setUnseenLifeTime(configUnseen.getValue());
- } catch (NumberFormatException e) {
- throw new ConfigurationException(e.getMessage());
- }
- }
-
- Configuration configRepositoryPath = handlerConfiguration.getChild("repositoryPath", false);
- if (configRepositoryPath != null) {
- setRepositoryPath(configRepositoryPath.getValue());
- } else {
- throw new ConfigurationException("repositoryPath is not configured");
- }
-
- // Get the SQL file location
- Configuration sFile = handlerConfiguration.getChild("sqlFile", false);
- if (sFile != null) {
- setSqlFileUrl(sFile.getValue());
- if (!sqlFileUrl.startsWith("file://")) {
- throw new ConfigurationException(
- "Malformed sqlFile - Must be of the format \"file://<filename>\".");
- }
- } else {
- throw new ConfigurationException("sqlFile is not configured");
- }
-
- Configuration whitelistedNetworks = handlerConfiguration.getChild("whitelistedNetworks", false);
- if (whitelistedNetworks != null) {
- Collection nets = whitelistedNetworks(whitelistedNetworks.getValue());
-
- if (nets != null) {
- wNetworks = new NetMatcher(nets,dnsServer);
- getLogger().info("Whitelisted addresses: " + wNetworks.toString());
- }
- }
- }
-
- /**
- * @see org.apache.avalon.framework.activity.Initializable#initialize()
- */
- public void initialize() throws Exception {
- setDataSource(initDataSource(repositoryPath));
- initSqlQueries(datasource.getConnection(), sqlFileUrl);
-
- // create table if not exist
- createTable(datasource.getConnection(), "greyListTableName", "createGreyListTable");
- }
-
- /**
- * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
- */
- public void service(ServiceManager serviceMan) throws ServiceException {
- setDataSources((DataSourceSelector) serviceMan.lookup(DataSourceSelector.ROLE));
- setDnsServer((DNSServer) serviceMan.lookup(DNSServer.ROLE));
- setFileSystem((FileSystem) serviceMan.lookup(FileSystem.ROLE));
- }
-
- /**
- * Set the DNSServer
- *
- * @param dnsServer
- * The DNSServer
- */
- public void setDnsServer(DNSServer dnsServer) {
- this.dnsServer = dnsServer;
- }
-
- /**
- * Set the sqlFileUrl to use for getting the sqlRessource.xml file
- *
- * @param sqlFileUrl
- * The fileUrl
- */
- public void setSqlFileUrl(String sqlFileUrl) {
- this.sqlFileUrl = sqlFileUrl;
- }
-
- /**
- * Set the repositoryPath to use
- *
- * @param repositoryPath
- * The repositoryPath
- */
- public void setRepositoryPath(String repositoryPath) {
- this.repositoryPath = repositoryPath;
- }
-
- /**
- * Set the datasources
- *
- * @param datasources
- * The datasources
- */
- public void setDataSources(DataSourceSelector datasources) {
- this.datasources = datasources;
- }
-
- /**
- * Sets the filesystem service
- *
- * @param system The filesystem service
- */
- private void setFileSystem(FileSystem system) {
- this.fileSystem = system;
- }
-
-
- /**
- * Set the datasource
- *
- * @param datasource
- * the datasource
- */
- public void setDataSource(DataSourceComponent datasource) {
- this.datasource = datasource;
- }
-
- /**
- * Setup the temporary blocking time
- *
- * @param tempBlockTime
- * The temporary blocking time
- */
- public void setTempBlockTime(String tempBlockTime) {
- this.tempBlockTime = TimeConverter.getMilliSeconds(tempBlockTime);
- }
-
- /**
- * Setup the autowhitelist lifetime for which we should whitelist a triplet.
- * After this lifetime the record will be deleted
- *
- * @param autoWhiteListLifeTime
- * The lifeTime
- */
- public void setAutoWhiteListLifeTime(String autoWhiteListLifeTime) {
- this.autoWhiteListLifeTime = TimeConverter.getMilliSeconds(autoWhiteListLifeTime);
- }
-
- /**
- * Set up the liftime of only once seen triplet. After this liftime the
- * record will be deleted
- *
- * @param unseenLifeTime
- * The lifetime
- */
- public void setUnseenLifeTime(String unseenLifeTime) {
- this.unseenLifeTime = TimeConverter.getMilliSeconds(unseenLifeTime);
- }
-
- /**
- * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
- */
- public void onCommand(SMTPSession session) {
- if (!session.isRelayingAllowed() && !(session.isAuthRequired() && session.getUser() != null)) {
-
- if ((wNetworks == null) || (!wNetworks.matchInetNetwork(session.getRemoteIPAddress()))) {
- doGreyListCheck(session, session.getCommandArgument());
- } else {
- getLogger().info("IpAddress " + session.getRemoteIPAddress() + " is whitelisted. Skip greylisting.");
- }
- } else {
- getLogger().info("IpAddress " + session.getRemoteIPAddress() + " is allowed to send. Skip greylisting.");
- }
- }
-
- /**
- * Handler method called upon receipt of a RCPT command. Calls a greylist
- * check
- *
- *
- * @param session
- * SMTP session object
- * @param argument
- */
- private void doGreyListCheck(SMTPSession session, String argument) {
- String recip = "";
- String sender = "";
- MailAddress recipAddress = (MailAddress) session.getState().get(SMTPSession.CURRENT_RECIPIENT);
- MailAddress senderAddress = (MailAddress) session.getState().get(SMTPSession.SENDER);
-
- if (recipAddress != null) recip = recipAddress.toString();
- if (senderAddress != null) sender = senderAddress.toString();
-
- long time = System.currentTimeMillis();
- String ipAddress = session.getRemoteIPAddress();
-
- try {
- long createTimeStamp = 0;
- int count = 0;
-
- // get the timestamp when he triplet was last seen
- Iterator data = getGreyListData(datasource.getConnection(), ipAddress, sender, recip);
-
- if (data.hasNext()) {
- createTimeStamp = Long.parseLong(data.next().toString());
- count = Integer.parseInt(data.next().toString());
- }
-
- getLogger().debug("Triplet " + ipAddress + " | " + sender + " | " + recip +" -> TimeStamp: " + createTimeStamp);
-
-
- // if the timestamp is bigger as 0 we have allready a triplet stored
- if (createTimeStamp > 0) {
- long acceptTime = createTimeStamp + tempBlockTime;
-
- if ((time < acceptTime) && (count == 0)) {
- String responseString = "451 " + DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_DIR_SERVER)
- + " Temporary rejected: Reconnect to fast. Please try again later";
-
- // reconnect to fast block it again
- session.writeResponse(responseString);
- session.setStopHandlerProcessing(true);
-
- } else {
-
- getLogger().debug("Update triplet " + ipAddress + " | " + sender + " | " + recip + " -> timestamp: " + time);
-
- // update the triplet..
- updateTriplet(datasource.getConnection(), ipAddress, sender, recip, count, time);
-
- }
- } else {
- getLogger().debug("New triplet " + ipAddress + " | " + sender + " | " + recip );
-
- // insert a new triplet
- insertTriplet(datasource.getConnection(), ipAddress, sender, recip, count, time);
-
- // Tempory block on new triplet!
- String responseString = "451 " + DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_DIR_SERVER)
- + " Temporary rejected: Please try again later";
-
- session.writeResponse(responseString);
- session.setStopHandlerProcessing(true);
- }
-
- // some kind of random cleanup process
- if (Math.random() > 0.99) {
- // cleanup old entries
-
- getLogger().debug("Delete old entries");
-
- cleanupAutoWhiteListGreyList(datasource.getConnection(),(time - autoWhiteListLifeTime));
- cleanupGreyList(datasource.getConnection(), (time - unseenLifeTime));
- }
-
- } catch (SQLException e) {
- // just log the exception
- getLogger().error("Error on SQLquery: " + e.getMessage());
- }
- }
-
- /**
- * Get all necessary data for greylisting based on provided triplet
- *
- * @param conn
- * The Connection
- * @param ipAddress
- * The ipAddress of the client
- * @param sender
- * The mailFrom
- * @param recip
- * The rcptTo
- * @return data
- * The data
- * @throws SQLException
- */
- private Iterator getGreyListData(Connection conn, String ipAddress,
- String sender, String recip) throws SQLException {
- Collection data = new ArrayList(2);
- PreparedStatement mappingStmt = null;
- try {
- mappingStmt = conn.prepareStatement(selectQuery);
- ResultSet mappingRS = null;
- try {
- mappingStmt.setString(1, ipAddress);
- mappingStmt.setString(2, sender);
- mappingStmt.setString(3, recip);
- mappingRS = mappingStmt.executeQuery();
-
- if (mappingRS.next()) {
- data.add(String.valueOf(mappingRS.getTimestamp(1).getTime()));
- data.add(String.valueOf(mappingRS.getInt(2)));
- }
- } finally {
- theJDBCUtil.closeJDBCResultSet(mappingRS);
- }
- } finally {
- theJDBCUtil.closeJDBCStatement(mappingStmt);
- theJDBCUtil.closeJDBCConnection(conn);
- }
- return data.iterator();
- }
-
- /**
- * Insert new triplet in the store
- *
- * @param conn
- * The Connection
- * @param ipAddress
- * The ipAddress of the client
- * @param sender
- * The mailFrom
- * @param recip
- * The rcptTo
- * @param count
- * The count
- * @param createTime
- * The createTime
- * @throws SQLException
- */
- private void insertTriplet(Connection conn, String ipAddress,
- String sender, String recip, int count, long createTime)
- throws SQLException {
-
- PreparedStatement mappingStmt = null;
-
- try {
- mappingStmt = conn.prepareStatement(insertQuery);
-
- mappingStmt.setString(1, ipAddress);
- mappingStmt.setString(2, sender);
- mappingStmt.setString(3, recip);
- mappingStmt.setInt(4, count);
- mappingStmt.setTimestamp(5, new Timestamp(createTime));
- mappingStmt.executeUpdate();
- } finally {
- theJDBCUtil.closeJDBCStatement(mappingStmt);
- theJDBCUtil.closeJDBCConnection(conn);
- }
- }
-
- /**
- * Update the triplet
- *
- * @param conn
- * The Connection
- *
- * @param ipAddress
- * The ipAddress of the client
- * @param sender
- * The mailFrom
- * @param recip
- * The rcptTo
- * @param count
- * The count
- * @param time
- * the current time in ms
- * @throws SQLException
- */
- private void updateTriplet(Connection conn, String ipAddress,
- String sender, String recip, int count, long time)
- throws SQLException {
-
- PreparedStatement mappingStmt = null;
-
- try {
- mappingStmt = conn.prepareStatement(updateQuery);
- mappingStmt.setTimestamp(1, new Timestamp(time));
- mappingStmt.setInt(2, (count + 1));
- mappingStmt.setString(3, ipAddress);
- mappingStmt.setString(4, sender);
- mappingStmt.setString(5, recip);
- mappingStmt.executeUpdate();
- } finally {
- theJDBCUtil.closeJDBCStatement(mappingStmt);
- theJDBCUtil.closeJDBCConnection(conn);
- }
- }
-
- /**
- * Init the dataSource
- *
- * @param repositoryPath
- * The repositoryPath
- * @return dataSource The DataSourceComponent
- * @throws ServiceException
- * @throws SQLException
- */
- private DataSourceComponent initDataSource(String repositoryPath)
- throws ServiceException, SQLException {
-
- int stindex = repositoryPath.indexOf("://") + 3;
- String datasourceName = repositoryPath.substring(stindex);
-
- return (DataSourceComponent) datasources.select(datasourceName);
- }
-
- /**
- * Cleanup the autowhitelist
- *
- * @param conn
- * The Connection
- * @param time
- * The time which must be reached before delete the records
- * @throws SQLException
- */
- private void cleanupAutoWhiteListGreyList(Connection conn, long time)
- throws SQLException {
- PreparedStatement mappingStmt = null;
-
- try {
- mappingStmt = conn.prepareStatement(deleteAutoWhiteListQuery);
-
- mappingStmt.setTimestamp(1, new Timestamp(time));
-
- mappingStmt.executeUpdate();
- } finally {
- theJDBCUtil.closeJDBCStatement(mappingStmt);
- theJDBCUtil.closeJDBCConnection(conn);
- }
- }
-
- /**
- * Cleanup the autowhitelist
- *
- * @param conn
- * The Connection
- * @param time
- * The time which must be reached before delete the records
- * @throws SQLException
- */
- private void cleanupGreyList(Connection conn, long time)
- throws SQLException {
- PreparedStatement mappingStmt = null;
-
- try {
- mappingStmt = conn.prepareStatement(deleteQuery);
-
- mappingStmt.setTimestamp(1, new Timestamp(time));
-
- mappingStmt.executeUpdate();
- } finally {
- theJDBCUtil.closeJDBCStatement(mappingStmt);
- theJDBCUtil.closeJDBCConnection(conn);
- }
- }
-
- /**
- * The JDBCUtil helper class
- */
- private final JDBCUtil theJDBCUtil = new JDBCUtil() {
- protected void delegatedLog(String logString) {
- getLogger().debug("JDBCVirtualUserTable: " + logString);
- }
- };
-
- /**
- * Initializes the sql query environment from the SqlResources file. Will
- * look for conf/sqlResources.xml.
- *
- * @param conn
- * The connection for accessing the database
- * @param sqlFileUrl
- * The url which we use to get the sql file
- * @throws Exception
- * If any error occurs
- */
- public void initSqlQueries(Connection conn, String sqlFileUrl)
- throws Exception {
- try {
-
- File sqlFile = null;
-
- try {
- sqlFile = fileSystem.getFile(sqlFileUrl);
- sqlFileUrl = null;
- } catch (Exception e) {
- getLogger().fatalError(e.getMessage(), e);
- throw e;
- }
-
- sqlQueries.init(sqlFile.getCanonicalFile(), "GreyList", conn, sqlParameters);
-
- selectQuery = sqlQueries.getSqlString("selectQuery", true);
- insertQuery = sqlQueries.getSqlString("insertQuery", true);
- deleteQuery = sqlQueries.getSqlString("deleteQuery", true);
- deleteAutoWhiteListQuery = sqlQueries.getSqlString("deleteAutoWhitelistQuery", true);
- updateQuery = sqlQueries.getSqlString("updateQuery", true);
-
- } finally {
- theJDBCUtil.closeJDBCConnection(conn);
- }
- }
-
- /**
- * Create the table if not exists.
- *
- * @param conn
- * The connection
- * @param tableNameSqlStringName
- * The tableSqlname
- * @param createSqlStringName
- * The createSqlname
- * @return true or false
- * @throws SQLException
- */
- 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("'.");
- getLogger().info(logBuffer.toString());
-
- } finally {
- theJDBCUtil.closeJDBCStatement(createStatement);
- }
- return true;
- }
-
- /**
- * Return a Collection which holds the values of the given string splitted
- * on ","
- *
- * @param networks
- * The commaseperated list of values
- * @return wNetworks The Collection which holds the whitelistNetworks
- */
- private Collection whitelistedNetworks(String networks) {
- Collection wNetworks = null;
- StringTokenizer st = new StringTokenizer(networks, ", ", false);
- wNetworks = new ArrayList();
-
- while (st.hasMoreTokens())
- wNetworks.add(st.nextToken());
- return wNetworks;
- }
-
- public Collection getImplCommands() {
- Collection c = new ArrayList();
- c.add("RCPT");
- return c;
- }
-}
+/****************************************************************
+ * 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.io.File;
+import java.sql.Connection;
+import java.sql.DatabaseMetaData;
+import java.sql.PreparedStatement;
+import java.sql.ResultSet;
+import java.sql.SQLException;
+import java.util.ArrayList;
+import java.util.Collection;
+import java.util.HashMap;
+import java.util.Iterator;
+import java.util.Map;
+import java.util.StringTokenizer;
+import java.sql.Timestamp;
+
+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.services.DNSServer;
+import org.apache.james.services.FileSystem;
+import org.apache.james.smtpserver.CommandHandler;
+import org.apache.james.smtpserver.SMTPResponse;
+import org.apache.james.smtpserver.SMTPSession;
+import org.apache.james.util.JDBCUtil;
+import org.apache.james.util.NetMatcher;
+import org.apache.james.util.SqlResources;
+import org.apache.james.util.TimeConverter;
+import org.apache.james.util.mail.dsn.DSNStatus;
+import org.apache.mailet.MailAddress;
+
+/**
+ * GreylistHandler which can be used to activate Greylisting
+ */
+public class GreylistHandler extends AbstractLogEnabled implements
+ CommandHandler, Configurable, Serviceable, Initializable {
+
+ private DataSourceSelector datasources = null;
+
+ private DataSourceComponent datasource = null;
+
+ private FileSystem fileSystem = null;
+
+ // 1 hour
+ private long tempBlockTime = 3600000;
+
+ // 36 days
+ private long autoWhiteListLifeTime = 3110400000L;
+
+ // 4 hours
+ private long unseenLifeTime = 14400000;
+
+ private String selectQuery;
+
+ private String insertQuery;
+
+ private String deleteQuery;
+
+ private String deleteAutoWhiteListQuery;
+
+ private String updateQuery;
+
+ /**
+ * Contains all of the sql strings for this component.
+ */
+ private SqlResources sqlQueries = new SqlResources();
+
+ /**
+ * The sqlFileUrl
+ */
+ private String sqlFileUrl;
+
+ /**
+ * Holds value of property sqlParameters.
+ */
+ private Map sqlParameters = new HashMap();
+
+ /**
+ * The repositoryPath
+ */
+ private String repositoryPath;
+
+ private DNSServer dnsServer;
+
+ private NetMatcher wNetworks;
+
+ /**
+ * @see org.apache.avalon.framework.configuration.Configurable#configure(Configuration)
+ */
+ public void configure(Configuration handlerConfiguration) throws ConfigurationException {
+ Configuration configTemp = handlerConfiguration.getChild("tempBlockTime", false);
+ if (configTemp != null) {
+ try {
+ setTempBlockTime(configTemp.getValue());
+
+ } catch (NumberFormatException e) {
+ throw new ConfigurationException(e.getMessage());
+ }
+ }
+
+ Configuration configAutoWhiteList = handlerConfiguration.getChild("autoWhiteListLifeTime", false);
+ if (configAutoWhiteList != null) {
+ try {
+ setAutoWhiteListLifeTime(configAutoWhiteList.getValue());
+ } catch (NumberFormatException e) {
+ throw new ConfigurationException(e.getMessage());
+ }
+ }
+
+ Configuration configUnseen = handlerConfiguration.getChild("unseenLifeTime", false);
+ if (configUnseen != null) {
+ try {
+ setUnseenLifeTime(configUnseen.getValue());
+ } catch (NumberFormatException e) {
+ throw new ConfigurationException(e.getMessage());
+ }
+ }
+
+ Configuration configRepositoryPath = handlerConfiguration.getChild("repositoryPath", false);
+ if (configRepositoryPath != null) {
+ setRepositoryPath(configRepositoryPath.getValue());
+ } else {
+ throw new ConfigurationException("repositoryPath is not configured");
+ }
+
+ // Get the SQL file location
+ Configuration sFile = handlerConfiguration.getChild("sqlFile", false);
+ if (sFile != null) {
+ setSqlFileUrl(sFile.getValue());
+ if (!sqlFileUrl.startsWith("file://")) {
+ throw new ConfigurationException(
+ "Malformed sqlFile - Must be of the format \"file://<filename>\".");
+ }
+ } else {
+ throw new ConfigurationException("sqlFile is not configured");
+ }
+
+ Configuration whitelistedNetworks = handlerConfiguration.getChild("whitelistedNetworks", false);
+ if (whitelistedNetworks != null) {
+ Collection nets = whitelistedNetworks(whitelistedNetworks.getValue());
+
+ if (nets != null) {
+ wNetworks = new NetMatcher(nets,dnsServer);
+ getLogger().info("Whitelisted addresses: " + wNetworks.toString());
+ }
+ }
+ }
+
+ /**
+ * @see org.apache.avalon.framework.activity.Initializable#initialize()
+ */
+ public void initialize() throws Exception {
+ setDataSource(initDataSource(repositoryPath));
+ initSqlQueries(datasource.getConnection(), sqlFileUrl);
+
+ // create table if not exist
+ createTable(datasource.getConnection(), "greyListTableName", "createGreyListTable");
+ }
+
+ /**
+ * @see org.apache.avalon.framework.service.Serviceable#service(org.apache.avalon.framework.service.ServiceManager)
+ */
+ public void service(ServiceManager serviceMan) throws ServiceException {
+ setDataSources((DataSourceSelector) serviceMan.lookup(DataSourceSelector.ROLE));
+ setDnsServer((DNSServer) serviceMan.lookup(DNSServer.ROLE));
+ setFileSystem((FileSystem) serviceMan.lookup(FileSystem.ROLE));
+ }
+
+ /**
+ * Set the DNSServer
+ *
+ * @param dnsServer
+ * The DNSServer
+ */
+ public void setDnsServer(DNSServer dnsServer) {
+ this.dnsServer = dnsServer;
+ }
+
+ /**
+ * Set the sqlFileUrl to use for getting the sqlRessource.xml file
+ *
+ * @param sqlFileUrl
+ * The fileUrl
+ */
+ public void setSqlFileUrl(String sqlFileUrl) {
+ this.sqlFileUrl = sqlFileUrl;
+ }
+
+ /**
+ * Set the repositoryPath to use
+ *
+ * @param repositoryPath
+ * The repositoryPath
+ */
+ public void setRepositoryPath(String repositoryPath) {
+ this.repositoryPath = repositoryPath;
+ }
+
+ /**
+ * Set the datasources
+ *
+ * @param datasources
+ * The datasources
+ */
+ public void setDataSources(DataSourceSelector datasources) {
+ this.datasources = datasources;
+ }
+
+ /**
+ * Sets the filesystem service
+ *
+ * @param system The filesystem service
+ */
+ private void setFileSystem(FileSystem system) {
+ this.fileSystem = system;
+ }
+
+
+ /**
+ * Set the datasource
+ *
+ * @param datasource
+ * the datasource
+ */
+ public void setDataSource(DataSourceComponent datasource) {
+ this.datasource = datasource;
+ }
+
+ /**
+ * Setup the temporary blocking time
+ *
+ * @param tempBlockTime
+ * The temporary blocking time
+ */
+ public void setTempBlockTime(String tempBlockTime) {
+ this.tempBlockTime = TimeConverter.getMilliSeconds(tempBlockTime);
+ }
+
+ /**
+ * Setup the autowhitelist lifetime for which we should whitelist a triplet.
+ * After this lifetime the record will be deleted
+ *
+ * @param autoWhiteListLifeTime
+ * The lifeTime
+ */
+ public void setAutoWhiteListLifeTime(String autoWhiteListLifeTime) {
+ this.autoWhiteListLifeTime = TimeConverter.getMilliSeconds(autoWhiteListLifeTime);
+ }
+
+ /**
+ * Set up the liftime of only once seen triplet. After this liftime the
+ * record will be deleted
+ *
+ * @param unseenLifeTime
+ * The lifetime
+ */
+ public void setUnseenLifeTime(String unseenLifeTime) {
+ this.unseenLifeTime = TimeConverter.getMilliSeconds(unseenLifeTime);
+ }
+
+ /**
+ * @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
+ */
+ public SMTPResponse onCommand(SMTPSession session, String command, String arguments) {
+ if (!session.isRelayingAllowed() && !(session.isAuthRequired() && session.getUser() != null)) {
+
+ if ((wNetworks == null) || (!wNetworks.matchInetNetwork(session.getRemoteIPAddress()))) {
+ return doGreyListCheck(session, session.getCommandArgument());
+ } else {
+ getLogger().info("IpAddress " + session.getRemoteIPAddress() + " is whitelisted. Skip greylisting.");
+ }
+ } else {
+ getLogger().info("IpAddress " + session.getRemoteIPAddress() + " is allowed to send. Skip greylisting.");
+ }
+ return null;
+ }
+
+ /**
+ * Handler method called upon receipt of a RCPT command. Calls a greylist
+ * check
+ *
+ *
+ * @param session
+ * SMTP session object
+ * @param argument
+ */
+ private SMTPResponse doGreyListCheck(SMTPSession session, String argument) {
+ String recip = "";
+ String sender = "";
+ MailAddress recipAddress = (MailAddress) session.getState().get(SMTPSession.CURRENT_RECIPIENT);
+ MailAddress senderAddress = (MailAddress) session.getState().get(SMTPSession.SENDER);
+
+ if (recipAddress != null) recip = recipAddress.toString();
+ if (senderAddress != null) sender = senderAddress.toString();
+
+ long time = System.currentTimeMillis();
+ String ipAddress = session.getRemoteIPAddress();
+
+ SMTPResponse ret = null;
+ try {
+ long createTimeStamp = 0;
+ int count = 0;
+
+ // get the timestamp when he triplet was last seen
+ Iterator data = getGreyListData(datasource.getConnection(), ipAddress, sender, recip);
+
+ if (data.hasNext()) {
+ createTimeStamp = Long.parseLong(data.next().toString());
+ count = Integer.parseInt(data.next().toString());
+ }
+
+ getLogger().debug("Triplet " + ipAddress + " | " + sender + " | " + recip +" -> TimeStamp: " + createTimeStamp);
+
+
+ // if the timestamp is bigger as 0 we have allready a triplet stored
+ if (createTimeStamp > 0) {
+ long acceptTime = createTimeStamp + tempBlockTime;
+
+ if ((time < acceptTime) && (count == 0)) {
+ ret = new SMTPResponse("451", DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_DIR_SERVER)
+ + " Temporary rejected: Reconnect to fast. Please try again later");
+ } else {
+
+ getLogger().debug("Update triplet " + ipAddress + " | " + sender + " | " + recip + " -> timestamp: " + time);
+
+ // update the triplet..
+ updateTriplet(datasource.getConnection(), ipAddress, sender, recip, count, time);
+
+ }
+ } else {
+ getLogger().debug("New triplet " + ipAddress + " | " + sender + " | " + recip );
+
+ // insert a new triplet
+ insertTriplet(datasource.getConnection(), ipAddress, sender, recip, count, time);
+
+ // Tempory block on new triplet!
+ ret = new SMTPResponse("451", DSNStatus.getStatus(DSNStatus.TRANSIENT, DSNStatus.NETWORK_DIR_SERVER)
+ + " Temporary rejected: Please try again later");
+ }
+
+ // some kind of random cleanup process
+ if (Math.random() > 0.99) {
+ // cleanup old entries
+
+ getLogger().debug("Delete old entries");
+
+ cleanupAutoWhiteListGreyList(datasource.getConnection(),(time - autoWhiteListLifeTime));
+ cleanupGreyList(datasource.getConnection(), (time - unseenLifeTime));
+ }
+
+ } catch (SQLException e) {
+ // just log the exception
+ getLogger().error("Error on SQLquery: " + e.getMessage());
+ }
+ return ret;
+ }
+
+ /**
+ * Get all necessary data for greylisting based on provided triplet
+ *
+ * @param conn
+ * The Connection
+ * @param ipAddress
+ * The ipAddress of the client
+ * @param sender
+ * The mailFrom
+ * @param recip
+ * The rcptTo
+ * @return data
+ * The data
+ * @throws SQLException
+ */
+ private Iterator getGreyListData(Connection conn, String ipAddress,
+ String sender, String recip) throws SQLException {
+ Collection data = new ArrayList(2);
+ PreparedStatement mappingStmt = null;
+ try {
+ mappingStmt = conn.prepareStatement(selectQuery);
+ ResultSet mappingRS = null;
+ try {
+ mappingStmt.setString(1, ipAddress);
+ mappingStmt.setString(2, sender);
+ mappingStmt.setString(3, recip);
+ mappingRS = mappingStmt.executeQuery();
+
+ if (mappingRS.next()) {
+ data.add(String.valueOf(mappingRS.getTimestamp(1).getTime()));
+ data.add(String.valueOf(mappingRS.getInt(2)));
+ }
+ } finally {
+ theJDBCUtil.closeJDBCResultSet(mappingRS);
+ }
+ } finally {
+ theJDBCUtil.closeJDBCStatement(mappingStmt);
+ theJDBCUtil.closeJDBCConnection(conn);
+ }
+ return data.iterator();
+ }
+
+ /**
+ * Insert new triplet in the store
+ *
+ * @param conn
+ * The Connection
+ * @param ipAddress
+ * The ipAddress of the client
+ * @param sender
+ * The mailFrom
+ * @param recip
+ * The rcptTo
+ * @param count
+ * The count
+ * @param createTime
+ * The createTime
+ * @throws SQLException
+ */
+ private void insertTriplet(Connection conn, String ipAddress,
+ String sender, String recip, int count, long createTime)
+ throws SQLException {
+
+ PreparedStatement mappingStmt = null;
+
+ try {
+ mappingStmt = conn.prepareStatement(insertQuery);
+
+ mappingStmt.setString(1, ipAddress);
+ mappingStmt.setString(2, sender);
+ mappingStmt.setString(3, recip);
+ mappingStmt.setInt(4, count);
+ mappingStmt.setTimestamp(5, new Timestamp(createTime));
+ mappingStmt.executeUpdate();
+ } finally {
+ theJDBCUtil.closeJDBCStatement(mappingStmt);
+ theJDBCUtil.closeJDBCConnection(conn);
+ }
+ }
+
+ /**
+ * Update the triplet
+ *
+ * @param conn
+ * The Connection
+ *
+ * @param ipAddress
+ * The ipAddress of the client
+ * @param sender
+ * The mailFrom
+ * @param recip
+ * The rcptTo
+ * @param count
+ * The count
+ * @param time
+ * the current time in ms
+ * @throws SQLException
+ */
+ private void updateTriplet(Connection conn, String ipAddress,
+ String sender, String recip, int count, long time)
+ throws SQLException {
+
+ PreparedStatement mappingStmt = null;
+
+ try {
+ mappingStmt = conn.prepareStatement(updateQuery);
+ mappingStmt.setTimestamp(1, new Timestamp(time));
+ mappingStmt.setInt(2, (count + 1));
+ mappingStmt.setString(3, ipAddress);
+ mappingStmt.setString(4, sender);
+ mappingStmt.setString(5, recip);
+ mappingStmt.executeUpdate();
+ } finally {
+ theJDBCUtil.closeJDBCStatement(mappingStmt);
+ theJDBCUtil.closeJDBCConnection(conn);
+ }
+ }
+
+ /**
+ * Init the dataSource
+ *
+ * @param repositoryPath
+ * The repositoryPath
+ * @return dataSource The DataSourceComponent
+ * @throws ServiceException
+ * @throws SQLException
+ */
+ private DataSourceComponent initDataSource(String repositoryPath)
+ throws ServiceException, SQLException {
+
+ int stindex = repositoryPath.indexOf("://") + 3;
+ String datasourceName = repositoryPath.substring(stindex);
+
+ return (DataSourceComponent) datasources.select(datasourceName);
+ }
+
+ /**
+ * Cleanup the autowhitelist
+ *
+ * @param conn
+ * The Connection
+ * @param time
+ * The time which must be reached before delete the records
+ * @throws SQLException
+ */
+ private void cleanupAutoWhiteListGreyList(Connection conn, long time)
+ throws SQLException {
+ PreparedStatement mappingStmt = null;
+
+ try {
+ mappingStmt = conn.prepareStatement(deleteAutoWhiteListQuery);
+
+ mappingStmt.setTimestamp(1, new Timestamp(time));
+
+ mappingStmt.executeUpdate();
+ } finally {
+ theJDBCUtil.closeJDBCStatement(mappingStmt);
+ theJDBCUtil.closeJDBCConnection(conn);
+ }
+ }
+
+ /**
+ * Cleanup the autowhitelist
+ *
+ * @param conn
+ * The Connection
+ * @param time
+ * The time which must be reached before delete the records
+ * @throws SQLException
+ */
+ private void cleanupGreyList(Connection conn, long time)
+ throws SQLException {
+ PreparedStatement mappingStmt = null;
+
+ try {
+ mappingStmt = conn.prepareStatement(deleteQuery);
+
+ mappingStmt.setTimestamp(1, new Timestamp(time));
+
+ mappingStmt.executeUpdate();
+ } finally {
+ theJDBCUtil.closeJDBCStatement(mappingStmt);
+ theJDBCUtil.closeJDBCConnection(conn);
+ }
+ }
+
+ /**
+ * The JDBCUtil helper class
+ */
+ private final JDBCUtil theJDBCUtil = new JDBCUtil() {
+ protected void delegatedLog(String logString) {
+ getLogger().debug("JDBCVirtualUserTable: " + logString);
+ }
+ };
+
+ /**
+ * Initializes the sql query environment from the SqlResources file. Will
+ * look for conf/sqlResources.xml.
+ *
+ * @param conn
+ * The connection for accessing the database
+ * @param sqlFileUrl
+ * The url which we use to get the sql file
+ * @throws Exception
+ * If any error occurs
+ */
+ public void initSqlQueries(Connection conn, String sqlFileUrl)
+ throws Exception {
+ try {
+
+ File sqlFile = null;
+
+ try {
+ sqlFile = fileSystem.getFile(sqlFileUrl);
+ sqlFileUrl = null;
+ } catch (Exception e) {
+ getLogger().fatalError(e.getMessage(), e);
+ throw e;
+ }
+
+ sqlQueries.init(sqlFile.getCanonicalFile(), "GreyList", conn, sqlParameters);
+
+ selectQuery = sqlQueries.getSqlString("selectQuery", true);
+ insertQuery = sqlQueries.getSqlString("insertQuery", true);
+ deleteQuery = sqlQueries.getSqlString("deleteQuery", true);
+ deleteAutoWhiteListQuery = sqlQueries.getSqlString("deleteAutoWhitelistQuery", true);
+ updateQuery = sqlQueries.getSqlString("updateQuery", true);
+
+ } finally {
+ theJDBCUtil.closeJDBCConnection(conn);
+ }
+ }
+
+ /**
+ * Create the table if not exists.
+ *
+ * @param conn
+ * The connection
+ * @param tableNameSqlStringName
+ * The tableSqlname
+ * @param createSqlStringName
+ * The createSqlname
+ * @return true or false
+ * @throws SQLException
+ */
+ 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("'.");
+ getLogger().info(logBuffer.toString());
+
+ } finally {
+ theJDBCUtil.closeJDBCStatement(createStatement);
+ }
+ return true;
+ }
+
+ /**
+ * Return a Collection which holds the values of the given string splitted
+ * on ","
+ *
+ * @param networks
+ * The commaseperated list of values
+ * @return wNetworks The Collection which holds the whitelistNetworks
+ */
+ private Collection whitelistedNetworks(String networks) {
+ Collection wNetworks = null;
+ StringTokenizer st = new StringTokenizer(networks, ", ", false);
+ wNetworks = new ArrayList();
+
+ while (st.hasMoreTokens())
+ wNetworks.add(st.nextToken());
+ return wNetworks;
+ }
+
+ public Collection getImplCommands() {
+ Collection c = new ArrayList();
+ c.add("RCPT");
+ return c;
+ }
+}
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/MaxRcptHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/MaxRcptHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/MaxRcptHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/MaxRcptHandler.java Mon Dec 25 09:31:07 2006
@@ -28,6 +28,7 @@
import org.apache.avalon.framework.configuration.Configuration;
import org.apache.avalon.framework.configuration.ConfigurationException;
import org.apache.james.smtpserver.CommandHandler;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
import org.apache.james.util.mail.dsn.DSNStatus;
@@ -67,8 +68,8 @@
/**
* @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
*/
- public void onCommand(SMTPSession session) {
- doProcessing(session);
+ public SMTPResponse onCommand(SMTPSession session, String command, String arguments) {
+ return doProcessing(session);
}
/**
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/ResolvableEhloHeloHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/ResolvableEhloHeloHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/ResolvableEhloHeloHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/ResolvableEhloHeloHandler.java Mon Dec 25 09:31:07 2006
@@ -27,6 +27,7 @@
import org.apache.avalon.framework.service.Serviceable;
import org.apache.james.services.DNSServer;
import org.apache.james.smtpserver.CommandHandler;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
import org.apache.james.util.junkscore.JunkScore;
import org.apache.james.util.mail.dsn.DSNStatus;
@@ -110,15 +111,14 @@
/**
* @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
*/
- public void onCommand(SMTPSession session) {
- String argument = session.getCommandArgument();
- String command = session.getCommandName();
+ public SMTPResponse onCommand(SMTPSession session, String command, String parameters) {
if (command.equals("HELO")
|| command.equals("EHLO")) {
- checkEhloHelo(session, argument);
+ checkEhloHelo(session, parameters);
} else if (command.equals("RCPT")) {
- doProcessing(session);
+ return doProcessing(session);
}
+ return null;
}
/**
Modified: james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/SPFHandler.java
URL: http://svn.apache.org/viewvc/james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/SPFHandler.java?view=diff&rev=490155&r1=490154&r2=490155
==============================================================================
--- james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/SPFHandler.java (original)
+++ james/server/sandbox/handlerapi-experiment/src/java/org/apache/james/smtpserver/core/filter/fastfail/SPFHandler.java Mon Dec 25 09:31:07 2006
@@ -33,6 +33,7 @@
import org.apache.james.jspf.core.DNSService;
import org.apache.james.smtpserver.CommandHandler;
import org.apache.james.smtpserver.MessageHandler;
+import org.apache.james.smtpserver.SMTPResponse;
import org.apache.james.smtpserver.SMTPSession;
import org.apache.james.util.mail.dsn.DSNStatus;
import org.apache.mailet.Mail;
@@ -160,12 +161,13 @@
*
* @see org.apache.james.smtpserver.CommandHandler#onCommand(SMTPSession)
*/
- public void onCommand(SMTPSession session) {
+ public SMTPResponse onCommand(SMTPSession session, String command, String parameters) {
if (session.getCommandName().equals("MAIL")) {
doSPFCheck(session);
} else if (session.getCommandName().equals("RCPT")) {
- doProcessing(session);
+ return doProcessing(session);
}
+ return null;
}
/**
---------------------------------------------------------------------
To unsubscribe, e-mail: server-dev-unsubscribe@james.apache.org
For additional commands, e-mail: server-dev-help@james.apache.org