This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch master in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 8f55920b5eee3e270e0f6966b8fa9835d89e2b8d Author: Benoit Tellier <[email protected]> AuthorDate: Thu May 13 10:08:52 2021 +0700 JAMES-3574 Verify DSN support for LMTP - We were missing the calls to James message hooks, eventually positionning the DSNs on the mail envelope. Also we slightly refactor tests: - Read LMTP data to be synchronous and thus NOT need to await --- .../james/lmtpserver/MailetContainerHandler.java | 1 + .../apache/james/lmtpserver/LmtpServerTest.java | 28 ++-- .../lmtpserver/MailetContainerHandlerTest.java | 143 ++++++++++++++++++--- .../protocols-lmtp/src/test/resources/lmtpdsn.xml | 39 ++++++ 4 files changed, 182 insertions(+), 29 deletions(-) diff --git a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/MailetContainerHandler.java b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/MailetContainerHandler.java index 3382331..628f88e 100644 --- a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/MailetContainerHandler.java +++ b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/MailetContainerHandler.java @@ -44,6 +44,7 @@ public class MailetContainerHandler extends DataLineJamesMessageHookHandler { @Override protected Response processExtensions(SMTPSession session, Mail mail) { try { + super.processExtensions(session, mail); mailProcessor.service(mail); return AbstractHookableCmdHandler.calcDefaultSMTPResponse(HookResult.builder() diff --git a/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/LmtpServerTest.java b/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/LmtpServerTest.java index 8db8842..dc2009c 100644 --- a/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/LmtpServerTest.java +++ b/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/LmtpServerTest.java @@ -61,7 +61,6 @@ import org.apache.james.server.core.configuration.Configuration; import org.apache.james.server.core.filesystem.FileSystemImpl; import org.apache.james.user.api.UsersRepository; import org.apache.james.user.memory.MemoryUsersRepository; -import org.awaitility.Awaitility; import org.jboss.netty.util.HashedWheelTimer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; @@ -145,29 +144,32 @@ class LmtpServerTest { void emailsShouldWellBeReceived() throws Exception { SocketChannel server = SocketChannel.open(); server.connect(new InetSocketAddress(LOCALHOST_IP, getLmtpPort(lmtpServerFactory))); + server.read(ByteBuffer.allocate(1024)); server.write(ByteBuffer.wrap(("LHLO <" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + server.read(ByteBuffer.allocate(1024)); server.write(ByteBuffer.wrap(("MAIL FROM: <bob@" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + server.read(ByteBuffer.allocate(1024)); server.write(ByteBuffer.wrap(("RCPT TO: <[email protected]>\r\n").getBytes(StandardCharsets.UTF_8))); + server.read(ByteBuffer.allocate(1024)); server.write(ByteBuffer.wrap(("DATA\r\n").getBytes(StandardCharsets.UTF_8))); server.read(ByteBuffer.allocate(1024)); // needed to synchronize server.write(ByteBuffer.wrap(("header:value\r\n\r\nbody").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap((".").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); + server.read(ByteBuffer.allocate(1024)); server.write(ByteBuffer.wrap(("QUIT\r\n").getBytes(StandardCharsets.UTF_8))); - Awaitility.await() - .untilAsserted(() -> { - Username username = Username.of("[email protected]"); - MailboxSession systemSession = mailboxManager.createSystemSession(username); - assertThatCode(() -> - assertThat(Flux.from(mailboxManager.getMailbox(MailboxPath.inbox(username), systemSession) - .listMessagesMetadata(MessageRange.all(), systemSession)) - .count() - .block()) - .isEqualTo(1)) - .doesNotThrowAnyException(); - }); + + Username username = Username.of("[email protected]"); + MailboxSession systemSession = mailboxManager.createSystemSession(username); + assertThatCode(() -> + assertThat(Flux.from(mailboxManager.getMailbox(MailboxPath.inbox(username), systemSession) + .listMessagesMetadata(MessageRange.all(), systemSession)) + .count() + .block()) + .isEqualTo(1)) + .doesNotThrowAnyException(); } } \ No newline at end of file diff --git a/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/MailetContainerHandlerTest.java b/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/MailetContainerHandlerTest.java index 2087d14..ff4a7a4 100644 --- a/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/MailetContainerHandlerTest.java +++ b/server/protocols/protocols-lmtp/src/test/java/org/apache/james/lmtpserver/MailetContainerHandlerTest.java @@ -22,16 +22,22 @@ package org.apache.james.lmtpserver; import static org.apache.james.jmap.JMAPTestingConstants.DOMAIN; import static org.apache.james.jmap.JMAPTestingConstants.LOCALHOST_IP; import static org.apache.james.lmtpserver.LmtpServerTest.getLmtpPort; +import static org.apache.mailet.DsnParameters.Notify.DELAY; +import static org.apache.mailet.DsnParameters.Notify.FAILURE; +import static org.apache.mailet.DsnParameters.Notify.SUCCESS; import static org.assertj.core.api.Assertions.assertThat; +import java.io.IOException; import java.net.InetSocketAddress; import java.nio.ByteBuffer; import java.nio.channels.SocketChannel; import java.nio.charset.StandardCharsets; import java.util.ArrayList; +import java.util.EnumSet; import java.util.List; import org.apache.james.core.Domain; +import org.apache.james.core.MailAddress; import org.apache.james.core.Username; import org.apache.james.dnsservice.api.DNSService; import org.apache.james.dnsservice.api.InMemoryDNSService; @@ -56,14 +62,16 @@ import org.apache.james.server.core.configuration.Configuration; import org.apache.james.server.core.filesystem.FileSystemImpl; import org.apache.james.user.api.UsersRepository; import org.apache.james.user.memory.MemoryUsersRepository; +import org.apache.mailet.DsnParameters; import org.apache.mailet.Mail; -import org.awaitility.Awaitility; import org.jboss.netty.util.HashedWheelTimer; import org.junit.jupiter.api.AfterEach; import org.junit.jupiter.api.BeforeEach; import org.junit.jupiter.api.Nested; import org.junit.jupiter.api.Test; +import com.github.fge.lambdas.Throwing; + class MailetContainerHandlerTest { static class RecordingMailProcessor implements MailProcessor { private final ArrayList<Mail> mails = new ArrayList<>(); @@ -86,7 +94,7 @@ class MailetContainerHandlerTest { } @Nested - class Normal { + class NormalTest { private RecordingMailProcessor recordingMailProcessor; private LMTPServerFactory lmtpServerFactory; @@ -142,25 +150,127 @@ class MailetContainerHandlerTest { void emailShouldTriggerTheMailProcessing() throws Exception { SocketChannel server = SocketChannel.open(); server.connect(new InetSocketAddress(LOCALHOST_IP, getLmtpPort(lmtpServerFactory))); + readBytes(server); server.write(ByteBuffer.wrap(("LHLO <" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); server.write(ByteBuffer.wrap(("MAIL FROM: <bob@" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); server.write(ByteBuffer.wrap(("RCPT TO: <[email protected]>\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); + server.write(ByteBuffer.wrap(("DATA\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); // needed to synchronize + server.write(ByteBuffer.wrap(("header:value\r\n\r\nbody").getBytes(StandardCharsets.UTF_8))); + server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); + server.write(ByteBuffer.wrap((".").getBytes(StandardCharsets.UTF_8))); + server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); + server.write(ByteBuffer.wrap(("QUIT\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); + + assertThat(recordingMailProcessor.getMails()).hasSize(1); + } + } + + @Nested + class DSNTest { + + private RecordingMailProcessor recordingMailProcessor; + private LMTPServerFactory lmtpServerFactory; + + @BeforeEach + void setUp() throws Exception { + InMemoryDNSService dnsService = new InMemoryDNSService() + .registerMxRecord(Domain.LOCALHOST.asString(), "127.0.0.1") + .registerMxRecord("examplebis.local", "127.0.0.1") + .registerMxRecord("127.0.0.1", "127.0.0.1"); + MemoryDomainList domainList = new MemoryDomainList(dnsService); + domainList.configure(DomainListConfiguration.builder() + .autoDetect(false) + .autoDetectIp(false) + .build()); + recordingMailProcessor = new RecordingMailProcessor(); + + domainList.addDomain(Domain.of("examplebis.local")); + MemoryUsersRepository usersRepository = MemoryUsersRepository.withVirtualHosting(domainList); + + usersRepository.addUser(Username.of("[email protected]"), "pwd"); + + FileSystem fileSystem = new FileSystemImpl(Configuration.builder() + .workingDirectory("../") + .configurationFromClasspath() + .build().directories()); + MemoryRecipientRewriteTable rewriteTable = new MemoryRecipientRewriteTable(); + rewriteTable.setConfiguration(RecipientRewriteTableConfiguration.DEFAULT_ENABLED); + AliasReverseResolver aliasReverseResolver = new AliasReverseResolverImpl(rewriteTable); + CanSendFrom canSendFrom = new CanSendFromImpl(rewriteTable, aliasReverseResolver); + MockProtocolHandlerLoader loader = MockProtocolHandlerLoader.builder() + .put(binder -> binder.bind(DomainList.class).toInstance(domainList)) + .put(binder -> binder.bind(RecipientRewriteTable.class).toInstance(rewriteTable)) + .put(binder -> binder.bind(CanSendFrom.class).toInstance(canSendFrom)) + .put(binder -> binder.bind(MailProcessor.class).toInstance(recordingMailProcessor)) + .put(binder -> binder.bind(FileSystem.class).toInstance(fileSystem)) + .put(binder -> binder.bind(DNSService.class).toInstance(dnsService)) + .put(binder -> binder.bind(UsersRepository.class).toInstance(usersRepository)) + .put(binder -> binder.bind(MetricFactory.class).to(RecordingMetricFactory.class)) + .build(); + lmtpServerFactory = new LMTPServerFactory(loader, fileSystem, new RecordingMetricFactory(), new HashedWheelTimer()); + + lmtpServerFactory.configure(ConfigLoader.getConfig(ClassLoader.getSystemResourceAsStream("lmtpdsn.xml"))); + lmtpServerFactory.init(); + } + + @AfterEach + void tearDown() { + lmtpServerFactory.destroy(); + } + + @Test + void emailShouldTriggerTheMailProcessing() throws Exception { + SocketChannel server = SocketChannel.open(); + server.connect(new InetSocketAddress(LOCALHOST_IP, getLmtpPort(lmtpServerFactory))); + readBytes(server); + server.write(ByteBuffer.wrap(("LHLO <" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); + server.write(ByteBuffer.wrap(("MAIL FROM: <bob@" + DOMAIN + "> RET=HDRS ENVID=QQ314159\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); + server.write(ByteBuffer.wrap(("RCPT TO: <[email protected]> NOTIFY=SUCCESS,FAILURE,DELAY ORCPT=rfc822;orcpt1@localhost\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); server.write(ByteBuffer.wrap(("DATA\r\n").getBytes(StandardCharsets.UTF_8))); - server.read(ByteBuffer.allocate(1024)); // needed to synchronize + readBytes(server); server.write(ByteBuffer.wrap(("header:value\r\n\r\nbody").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap((".").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); server.write(ByteBuffer.wrap(("QUIT\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); + + assertThat(recordingMailProcessor.getMails()) + .first() + .extracting(Mail::dsnParameters) + .satisfies(Throwing.consumer(maybeDSN -> assertThat(maybeDSN) + .isEqualTo(DsnParameters.builder() + .envId(DsnParameters.EnvId.of("QQ314159")) + .ret(DsnParameters.Ret.HDRS) + .addRcptParameter(new MailAddress("[email protected]"), DsnParameters.RecipientDsnParameters.of( + EnumSet.of(SUCCESS, FAILURE, DELAY), new MailAddress("orcpt1@localhost"))) + .build()))); + } - Awaitility.await() - .untilAsserted(() -> assertThat(recordingMailProcessor.getMails()).hasSize(1)); + @Test + void lhloShouldAdvertizeDSN() throws Exception { + SocketChannel server = SocketChannel.open(); + server.connect(new InetSocketAddress(LOCALHOST_IP, getLmtpPort(lmtpServerFactory))); + readBytes(server); + server.write(ByteBuffer.wrap(("LHLO <" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + + assertThat(new String(readBytes(server), StandardCharsets.UTF_8)).contains("250 DSN\r\n"); } } @Nested - class Throwing { + class ThrowingTest { private LMTPServerFactory lmtpServerFactory; @BeforeEach @@ -213,31 +323,32 @@ class MailetContainerHandlerTest { void emailShouldTriggerTheMailProcessing() throws Exception { SocketChannel server = SocketChannel.open(); server.connect(new InetSocketAddress(LOCALHOST_IP, getLmtpPort(lmtpServerFactory))); + readBytes(server); server.write(ByteBuffer.wrap(("LHLO <" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); server.write(ByteBuffer.wrap(("MAIL FROM: <bob@" + DOMAIN + ">\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); server.write(ByteBuffer.wrap(("RCPT TO: <[email protected]>\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); server.write(ByteBuffer.wrap(("DATA\r\n").getBytes(StandardCharsets.UTF_8))); - server.read(ByteBuffer.allocate(1024)); // Read Welcome message + readBytes(server); server.write(ByteBuffer.wrap(("header:value\r\n\r\nbody").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap((".").getBytes(StandardCharsets.UTF_8))); server.write(ByteBuffer.wrap(("\r\n").getBytes(StandardCharsets.UTF_8))); + byte[] dataResponse = readBytes(server); server.write(ByteBuffer.wrap(("QUIT\r\n").getBytes(StandardCharsets.UTF_8))); + readBytes(server); - server.read(ByteBuffer.allocate(1024)); // Read Capabilities - server.read(ByteBuffer.allocate(1024)); // Read LHLO reply string - server.read(ByteBuffer.allocate(1024)); // Read MAIL reply string - server.read(ByteBuffer.allocate(1024)); // Read RCPT reply string - - ByteBuffer buffer = ByteBuffer.allocate(1024); - server.read(buffer); // Read DATA reply string - assertThat(new String(readBytes(buffer), StandardCharsets.UTF_8)) + assertThat(new String(dataResponse, StandardCharsets.UTF_8)) .startsWith("451 4.0.0 Temporary error deliver message"); } } - private byte[] readBytes(ByteBuffer line) { + private byte[] readBytes(SocketChannel channel) throws IOException { + ByteBuffer line = ByteBuffer.allocate(1024); + channel.read(line); line.rewind(); byte[] bline = new byte[line.remaining()]; line.get(bline); diff --git a/server/protocols/protocols-lmtp/src/test/resources/lmtpdsn.xml b/server/protocols/protocols-lmtp/src/test/resources/lmtpdsn.xml new file mode 100644 index 0000000..a012ea3 --- /dev/null +++ b/server/protocols/protocols-lmtp/src/test/resources/lmtpdsn.xml @@ -0,0 +1,39 @@ +<?xml version="1.0"?> +<!-- + 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. + --> + +<lmtpservers> + <lmtpserver enabled="true"> + <jmxName>lmtpserver</jmxName> + <bind>0.0.0.0:0</bind> + <connectionBacklog>200</connectionBacklog> + <connectiontimeout>1200</connectiontimeout> + <connectionLimit>0</connectionLimit> + <connectionLimitPerIP>0</connectionLimitPerIP> + <maxmessagesize>0</maxmessagesize> + <handlerchain coreHandlersPackage="org.apache.james.lmtpserver.MailetContainerCmdHandlerLoader"> + <handler class="org.apache.james.lmtpserver.MailetContainerCmdHandlerLoader"/> + + <handler class="org.apache.james.smtpserver.dsn.DSNEhloHook"/> + <handler class="org.apache.james.smtpserver.dsn.DSNMailParameterHook"/> + <handler class="org.apache.james.smtpserver.dsn.DSNRcptParameterHook"/> + <handler class="org.apache.james.smtpserver.dsn.DSNMessageHook"/> + </handlerchain> + </lmtpserver> +</lmtpservers> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
