diff --git a/config/alfresco/messages/email-service.properties b/config/alfresco/messages/email-service.properties index 49cbcec8fc..09f0bbe3fd 100644 --- a/config/alfresco/messages/email-service.properties +++ b/config/alfresco/messages/email-service.properties @@ -3,6 +3,7 @@ email.server.msg.default_subject=Email-{0} email.server.err.duplicate_alias=Node with email alias ''{0}'' already exists. Duplicate isn't allowed. email.server.err.sender_blocked=''{0}'' has been denied access. +email.server.err.from_syntax=Syntax: MAIL FROM:
Error in parameters email.server.err.inbound_mail_disabled=The Alfresco server is not configured to accept inbound emails. email.server.err.access_denied=''{0}'' has been denied access to ''{1}''. email.server.err.invalid_subject=The subject line must be a valid file name. diff --git a/config/alfresco/subsystems/email/InboundSMTP/inboundSMTP-context.xml b/config/alfresco/subsystems/email/InboundSMTP/inboundSMTP-context.xml index e073c627e0..c15cfc0bb0 100755 --- a/config/alfresco/subsystems/email/InboundSMTP/inboundSMTP-context.xml +++ b/config/alfresco/subsystems/email/InboundSMTP/inboundSMTP-context.xml @@ -51,6 +51,9 @@ + + ${email.inbound.unknownUser} + diff --git a/source/java/org/alfresco/email/server/EmailServer.java b/source/java/org/alfresco/email/server/EmailServer.java index 65d2d9cfcd..16824beb40 100644 --- a/source/java/org/alfresco/email/server/EmailServer.java +++ b/source/java/org/alfresco/email/server/EmailServer.java @@ -42,6 +42,7 @@ import org.springframework.context.support.ClassPathXmlApplicationContext; public abstract class EmailServer extends AbstractLifecycleBean { private static final String ERR_SENDER_BLOCKED = "email.server.err.sender_blocked"; + private static final String ERR_FROM_SYNTAX_INCORRECT = "email.server.err.from_syntax"; private boolean enabled; private String domain; @@ -56,6 +57,7 @@ public abstract class EmailServer extends AbstractLifecycleBean private EmailService emailService; private AuthenticationComponent authenticationComponent; + private String unknownUser; protected EmailServer() { @@ -184,6 +186,20 @@ public abstract class EmailServer extends AbstractLifecycleBean { this.emailService = emailService; } + + /** + * Used only for check "isNullReversePatAllowed". + * @param unknownUser authority name + */ + public void setUnknownUser(String unknownUser) + { + this.unknownUser = unknownUser; + } + + protected boolean isNullReversePatAllowed() + { + return isAuthenticate() || (unknownUser != null && !unknownUser.isEmpty()); + } /** * Filter incoming message by its sender e-mail address. @@ -193,6 +209,19 @@ public abstract class EmailServer extends AbstractLifecycleBean */ protected void filterSender(String sender) { + if (sender == null) + { + if (isNullReversePatAllowed()) + { + // allow null reverse-path: e.g.: an undeliverable mail response + return; + } + else + { + throw new EmailMessageException(ERR_FROM_SYNTAX_INCORRECT); + } + } + // Check if the sender is in the blocked list for (String blockedSender : blockedSenders) { diff --git a/source/test-java/org/alfresco/Repository01TestSuite.java b/source/test-java/org/alfresco/Repository01TestSuite.java index 82ceeecbc4..e8d029a09a 100644 --- a/source/test-java/org/alfresco/Repository01TestSuite.java +++ b/source/test-java/org/alfresco/Repository01TestSuite.java @@ -52,6 +52,7 @@ public class Repository01TestSuite extends TestSuite static void tests3(TestSuite suite) // tests="76" time="82.566" { suite.addTestSuite(org.alfresco.email.server.EmailServiceImplTest.class); + suite.addTestSuite(org.alfresco.email.server.EmailServerTest.class); suite.addTestSuite(org.alfresco.filesys.FTPServerTest.class); suite.addTestSuite(org.alfresco.filesys.repo.CifsIntegrationTest.class); suite.addTestSuite(org.alfresco.filesys.repo.ContentDiskDriverTest.class); diff --git a/source/test-java/org/alfresco/email/server/EmailServerTest.java b/source/test-java/org/alfresco/email/server/EmailServerTest.java new file mode 100644 index 0000000000..20a68c0da1 --- /dev/null +++ b/source/test-java/org/alfresco/email/server/EmailServerTest.java @@ -0,0 +1,231 @@ +package org.alfresco.email.server; + +import java.io.BufferedReader; +import java.io.InputStreamReader; +import java.io.PrintWriter; +import java.net.Socket; +import java.util.ArrayList; + +import org.alfresco.repo.management.subsystems.ChildApplicationContextFactory; +import org.alfresco.service.cmr.email.EmailService; +import org.alfresco.util.ApplicationContextHelper; +import org.junit.After; +import org.junit.Before; +import org.junit.Test; +import org.springframework.context.ApplicationContext; + +import junit.framework.TestCase; + +public class EmailServerTest extends TestCase +{ + /** + * Services used by the tests + */ + private static ApplicationContext ctx = ApplicationContextHelper.getApplicationContext(); + + private EmailServer emailServer; + + // Linux-based env. should use port bigger than 1024 + private final int TEST_PORT = 2225; + private final String TEST_HOST = "localhost"; + private final int TEST_CLIENT_TIMEOUT = 20000; + + private EmailService emailService; + + @Before + @Override + public void setUp() throws Exception + { + ChildApplicationContextFactory emailSubsystem = (ChildApplicationContextFactory) ctx.getBean("InboundSMTP"); + assertNotNull("emailSubsystem", emailSubsystem); + ApplicationContext emailCtx = emailSubsystem.getApplicationContext(); + emailServer = (EmailServer) emailCtx.getBean("emailServer"); + emailService = (EmailService)emailCtx.getBean("emailService"); + assertNotNull("emailService", emailService); + } + + @After + public void tearDown() throws Exception + { + // nothing now + } + + /* + * Check null reverse-path if + * email.server.auth.enabled=false + * and + * email.inbound.unknownUser isn't set + * + * Null reverse-path must be disallowed + */ + @Test + public void testDisallowedNulableFromUser() throws Exception + { + emailServer.onShutdown(null); + + emailServer.setEnableTLS(false); + emailServer.setEnabled(true); + + emailServer.setAuthenticate(false); + emailServer.setUnknownUser(null); + + emailServer.setPort(TEST_PORT); + emailServer.startup(); + + String[] response = getMailFromNullableResponse(TEST_HOST, TEST_PORT); + checkResponse(response); + + // expects smth. like: "504 some data" + // we are expect error code first + assertTrue("Response should have error code", response[1].indexOf("5") == 0); + } + + /* + * Check null reverse-path if + * email.server.auth.enabled=true + * and + * email.inbound.unknownUser isn't set + * + * Null reverse-path must be allowed + */ + @Test + public void testAllowedNulableFromUserWithAuth() throws Exception + { + emailServer.onShutdown(null); + + emailServer.setEnableTLS(false); + emailServer.setEnabled(true); + + emailServer.setAuthenticate(true); + emailServer.setUnknownUser(null); + + emailServer.setPort(TEST_PORT); + emailServer.startup(); + + String[] response = getMailFromNullableResponse(TEST_HOST, TEST_PORT); + checkResponse(response); + + // expects smth. like: "250 some data" + // we aren't expect error code + assertTrue("Response should have error code", response[1].indexOf("2") == 0); + } + + /* + * Check null reverse-path if + * email.server.auth.enabled=false + * and + * email.inbound.unknownUser is set + * + * Null reverse-path must be allowed + */ + @Test + public void testAllowedNulableFromUserWithAnonymous() throws Exception + { + emailServer.onShutdown(null); + + emailServer.setEnableTLS(false); + emailServer.setEnabled(true); + + emailServer.setAuthenticate(false); + emailServer.setUnknownUser("anonymous"); + + emailServer.setPort(TEST_PORT); + emailServer.startup(); + + String[] response = getMailFromNullableResponse(TEST_HOST, TEST_PORT); + checkResponse(response); + + // expects smth. like: "250 some data" + // we aren't expect error code + assertTrue("Response should have error code", response[1].indexOf("2") == 0); + } + + /* + * Check for data accepting if "From" user absent + * and + * "email.inbound.unknownUser" isn't set + * email.server.auth.enabled=true + */ + @Test + public void testForDataAcceptingIfUserIsEmpty() throws Exception + { + emailServer.onShutdown(null); + + // we need to delete value from "email.inbound.unknownUser" in service too + if (emailService instanceof EmailServiceImpl) + { + ((EmailServiceImpl) emailService).setUnknownUser(""); + } + emailServer.setUnknownUser(null); + + emailServer.setAuthenticate(true); + emailServer.setEnableTLS(false); + emailServer.setEnabled(true); + + emailServer.setPort(TEST_PORT); + emailServer.startup(); + + String[] request = new String[] + { + "MAIL FROM:<>\r\n", + "RCPT TO:\r\n", + "DATA\r\n", + "Hello world\r\n.\r\n", + "QUIT\r\n" + }; + String[] response = getResponse(TEST_HOST, TEST_PORT, request); + + checkResponse(response); + + assertTrue("Response incorrect", response.length > 4); + // expects smth. like: "554 some data" + // we are expect error code + assertTrue("Response should have error code", response[4].indexOf("5") == 0); + } + + private void checkResponse(String[] response) + { + assertNotNull("Client hasn't response", response); + assertNotNull("Client hasn empty response", response[0]); + } + + private String[] getMailFromNullableResponse(String host, int port) throws Exception + { + // Send null reverse-path + return getResponse(host, port, new String[]{"MAIL FROM:<>\r\n", "QUIT\r\n"}); + } + + private String[] getResponse(String host, int port, String requestStrings[]) throws Exception + { + Socket sock = new Socket(host, port); + sock.setSoTimeout(TEST_CLIENT_TIMEOUT); + PrintWriter out = new PrintWriter(sock.getOutputStream(), true); + BufferedReader in = new BufferedReader(new InputStreamReader(sock.getInputStream())); + ArrayList response = new ArrayList(); + + try + { + // Read first server response. It is smth. like: ESMTP SubEthaSMTP 3.1.6 + response.add(in.readLine()); + + for (String reqStr : requestStrings) + { + out.print(reqStr); + out.flush(); + response.add(in.readLine()); + } + +// for (String s : response) +// { +// System.out.println(s); +// } + return response.toArray(new String[response.size()]); + } + finally + { + in.close(); + out.close(); + sock.close(); + } + } +}