You are viewing a plain text version of this content. The canonical link for it is here.
Posted to notifications@james.apache.org by rc...@apache.org on 2020/12/30 03:35:22 UTC
[james-project] 10/29: JAMES-3431 RemoteDelivery: should cary over
DSN parameters
This is an automated email from the ASF dual-hosted git repository.
rcordier pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git
commit 8d0889e783fa56c7c30381b2c744a7d56d850c75
Author: Benoit Tellier <bt...@linagora.com>
AuthorDate: Tue Dec 29 11:10:42 2020 +0700
JAMES-3431 RemoteDelivery: should cary over DSN parameters
Limitations:
- ORCPT parameter is not handled by javax.mail
- NOTIFY parameter is global to a SMTPMessage instance, thus
if NOTIFY parameters is not similar across recipients we
need to transmit a copy of the message for each one of them
---
.../java/org/apache/james/smtp/DSNRelayTest.java | 335 +++++++++++++++++++++
.../remote/delivery/MailDelivrerToHost.java | 83 ++++-
.../mock/smtp/server/ConfigurationClient.java | 1 +
3 files changed, 418 insertions(+), 1 deletion(-)
diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/DSNRelayTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/DSNRelayTest.java
new file mode 100644
index 0000000..a938790
--- /dev/null
+++ b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/DSNRelayTest.java
@@ -0,0 +1,335 @@
+/****************************************************************
+ * 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.smtp;
+
+import static org.apache.james.MemoryJamesServerMain.SMTP_AND_IMAP_MODULE;
+import static org.apache.james.mailets.configuration.Constants.DEFAULT_DOMAIN;
+import static org.apache.james.mailets.configuration.Constants.LOCALHOST_IP;
+import static org.apache.james.mailets.configuration.Constants.PASSWORD;
+import static org.apache.james.mailets.configuration.Constants.calmlyAwait;
+import static org.apache.james.util.docker.Images.MOCK_SMTP_SERVER;
+import static org.assertj.core.api.Assertions.assertThat;
+import static org.awaitility.Duration.TEN_SECONDS;
+
+import org.apache.commons.net.smtp.AuthenticatingSMTPClient;
+import org.apache.james.core.MailAddress;
+import org.apache.james.dnsservice.api.DNSService;
+import org.apache.james.dnsservice.api.InMemoryDNSService;
+import org.apache.james.mailets.TemporaryJamesServer;
+import org.apache.james.mailets.configuration.CommonProcessors;
+import org.apache.james.mailets.configuration.MailetConfiguration;
+import org.apache.james.mailets.configuration.MailetContainer;
+import org.apache.james.mailets.configuration.ProcessorConfiguration;
+import org.apache.james.mailets.configuration.SmtpConfiguration;
+import org.apache.james.mock.smtp.server.ConfigurationClient;
+import org.apache.james.mock.smtp.server.model.Mail;
+import org.apache.james.mock.smtp.server.model.SMTPExtension;
+import org.apache.james.mock.smtp.server.model.SMTPExtensions;
+import org.apache.james.modules.protocols.SmtpGuiceProbe;
+import org.apache.james.smtpserver.dsn.DSNEhloHook;
+import org.apache.james.smtpserver.dsn.DSNMailParameterHook;
+import org.apache.james.smtpserver.dsn.DSNMessageHook;
+import org.apache.james.smtpserver.dsn.DSNRcptParameterHook;
+import org.apache.james.transport.mailets.RemoteDelivery;
+import org.apache.james.transport.matchers.All;
+import org.apache.james.util.Host;
+import org.apache.james.util.docker.DockerContainer;
+import org.apache.james.utils.DataProbeImpl;
+import org.apache.james.utils.SMTPMessageSender;
+import org.apache.james.utils.TestIMAPClient;
+import org.junit.After;
+import org.junit.Before;
+import org.junit.ClassRule;
+import org.junit.Ignore;
+import org.junit.Rule;
+import org.junit.Test;
+import org.junit.rules.TemporaryFolder;
+import org.slf4j.Logger;
+import org.slf4j.LoggerFactory;
+
+public class DSNRelayTest {
+ private static final Logger LOGGER = LoggerFactory.getLogger(DSNRelayTest.class);
+
+ private static final String ANOTHER_DOMAIN = "other.com";
+ private static final String FROM = "from@" + DEFAULT_DOMAIN;
+ private static final String RECIPIENT = "touser@" + ANOTHER_DOMAIN;
+ private static final String RECIPIENT1 = "touser1@" + ANOTHER_DOMAIN;
+ private static final String RECIPIENT2 = "touser2@" + ANOTHER_DOMAIN;
+
+ private InMemoryDNSService inMemoryDNSService;
+ private ConfigurationClient mockSMTPConfiguration;
+
+ @Rule
+ public TemporaryFolder temporaryFolder = new TemporaryFolder();
+ @Rule
+ public TestIMAPClient testIMAPClient = new TestIMAPClient();
+ @Rule
+ public SMTPMessageSender messageSender = new SMTPMessageSender(DEFAULT_DOMAIN);
+ @ClassRule
+ public static DockerContainer mockSmtp = DockerContainer.fromName(MOCK_SMTP_SERVER)
+ .withLogConsumer(outputFrame -> LOGGER.debug("MockSMTP 1: " + outputFrame.getUtf8String()));
+
+ private TemporaryJamesServer jamesServer;
+
+ @Before
+ public void setUp() throws Exception {
+ inMemoryDNSService = new InMemoryDNSService()
+ .registerMxRecord(DEFAULT_DOMAIN, LOCALHOST_IP)
+ .registerMxRecord(ANOTHER_DOMAIN, mockSmtp.getContainerIp());
+
+ jamesServer = TemporaryJamesServer.builder()
+ .withBase(SMTP_AND_IMAP_MODULE)
+ .withOverrides(binder -> binder.bind(DNSService.class).toInstance(inMemoryDNSService))
+ .withMailetContainer(MailetContainer.builder()
+ .putProcessor(CommonProcessors.simpleRoot())
+ .putProcessor(CommonProcessors.error())
+ .putProcessor(directResolutionTransport())
+ .putProcessor(CommonProcessors.bounces()))
+ .withSmtpConfiguration(SmtpConfiguration.builder()
+ .addHook(DSNEhloHook.class.getName())
+ .addHook(DSNMailParameterHook.class.getName())
+ .addHook(DSNRcptParameterHook.class.getName())
+ .addHook(DSNMessageHook.class.getName()))
+ .build(temporaryFolder.newFolder());
+ jamesServer.start();
+
+ jamesServer.getProbe(DataProbeImpl.class)
+ .fluent()
+ .addDomain(DEFAULT_DOMAIN)
+ .addUser(FROM, PASSWORD);
+
+ mockSMTPConfiguration = configurationClient(mockSmtp);
+ mockSMTPConfiguration.setSMTPExtensions(SMTPExtensions.of(SMTPExtension.of("dsn")));
+
+ assertThat(mockSMTPConfiguration.version()).isEqualTo("0.2");
+ }
+
+ @After
+ public void tearDown() {
+ jamesServer.shutdown();
+
+ mockSMTPConfiguration.cleanServer();
+ }
+
+ @Ignore("JAMES-3431 No javax.mail support for ORCPT DSN parameter...")
+ @Test
+ public void orcptIsUnsupported() throws Exception {
+ AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+ try {
+ smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+ smtpClient.ehlo(DEFAULT_DOMAIN);
+ smtpClient.mail("<" + FROM + "> RET=HDRS ENVID=gabouzomeuh");
+ smtpClient.rcpt("<" + RECIPIENT + "> ORCPT=rfc822;" + RECIPIENT + " NOTIFY=FAILURE,DELAY");
+ smtpClient.sendShortMessageData("A short message...");
+ } finally {
+ smtpClient.disconnect();
+ }
+
+ calmlyAwait.atMost(TEN_SECONDS).untilAsserted(() -> assertThat(mockSMTPConfiguration.listMails())
+ .hasSize(3)
+ .extracting(Mail::getEnvelope)
+ .containsExactly(Mail.Envelope.builder()
+ .from(new MailAddress(FROM))
+ .addMailParameter(Mail.Parameter.builder()
+ .name("RET")
+ .value("HDRS")
+ .build())
+ .addMailParameter(Mail.Parameter.builder()
+ .name("ENVID")
+ .value("gabouzomeuh")
+ .build())
+ .addRecipient(Mail.Recipient.builder()
+ .address(new MailAddress(RECIPIENT))
+ .addParameter(Mail.Parameter.builder()
+ .name("ORCPT")
+ .value("rfc822;" + RECIPIENT)
+ .build())
+ .addParameter(Mail.Parameter.builder()
+ .name("notify")
+ .value("FAILURE,DELAY")
+ .build())
+ .build())
+ .build()));
+ }
+
+ @Test
+ public void remoteDeliveryShouldCarryOverDSNParameters() throws Exception {
+ AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+ try {
+ smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+ smtpClient.ehlo(DEFAULT_DOMAIN);
+ smtpClient.mail("<" + FROM + "> RET=HDRS ENVID=gabouzomeuh");
+ smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=FAILURE,DELAY");
+ smtpClient.rcpt("<" + RECIPIENT1 + "> NOTIFY=NEVER");
+ smtpClient.rcpt("<" + RECIPIENT2 + ">");
+ smtpClient.sendShortMessageData("A short message...");
+ } finally {
+ smtpClient.disconnect();
+ }
+
+ calmlyAwait.atMost(TEN_SECONDS).untilAsserted(() -> assertThat(mockSMTPConfiguration.listMails())
+ .hasSize(3)
+ .extracting(Mail::getEnvelope)
+ .containsOnly(Mail.Envelope.builder()
+ .from(new MailAddress(FROM))
+ .addMailParameter(Mail.Parameter.builder()
+ .name("RET")
+ .value("HDRS")
+ .build())
+ .addMailParameter(Mail.Parameter.builder()
+ .name("ENVID")
+ .value("gabouzomeuh")
+ .build())
+ .addRecipient(Mail.Recipient.builder()
+ .address(new MailAddress(RECIPIENT))
+ .addParameter(Mail.Parameter.builder()
+ .name("NOTIFY")
+ .value("FAILURE,DELAY")
+ .build())
+ .build())
+ .build(),
+ Mail.Envelope.builder()
+ .from(new MailAddress(FROM))
+ .addMailParameter(Mail.Parameter.builder()
+ .name("RET")
+ .value("HDRS")
+ .build())
+ .addMailParameter(Mail.Parameter.builder()
+ .name("ENVID")
+ .value("gabouzomeuh")
+ .build())
+ .addRecipient(Mail.Recipient.builder()
+ .address(new MailAddress(RECIPIENT1))
+ .addParameter(Mail.Parameter.builder()
+ .name("NOTIFY")
+ .value("NEVER")
+ .build())
+ .build())
+ .build(),
+ Mail.Envelope.builder()
+ .from(new MailAddress(FROM))
+ .addMailParameter(Mail.Parameter.builder()
+ .name("RET")
+ .value("HDRS")
+ .build())
+ .addMailParameter(Mail.Parameter.builder()
+ .name("ENVID")
+ .value("gabouzomeuh")
+ .build())
+ .addRecipient(Mail.Recipient.builder()
+ .address(new MailAddress(RECIPIENT2))
+ .build())
+ .build()));
+ }
+
+ @Test
+ public void remoteDeliveryShouldCarryOverDSNParametersWhenSingleRecipient() throws Exception {
+ AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+ try {
+ smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+ smtpClient.ehlo(DEFAULT_DOMAIN);
+ smtpClient.mail("<" + FROM + "> RET=HDRS ENVID=gabouzomeuh");
+ smtpClient.rcpt("<" + RECIPIENT + "> NOTIFY=FAILURE,DELAY");
+ smtpClient.sendShortMessageData("A short message...");
+ } finally {
+ smtpClient.disconnect();
+ }
+
+ calmlyAwait.atMost(TEN_SECONDS).untilAsserted(() -> assertThat(mockSMTPConfiguration.listMails())
+ .hasSize(1)
+ .extracting(Mail::getEnvelope)
+ .containsExactly(Mail.Envelope.builder()
+ .from(new MailAddress(FROM))
+ .addMailParameter(Mail.Parameter.builder()
+ .name("RET")
+ .value("HDRS")
+ .build())
+ .addMailParameter(Mail.Parameter.builder()
+ .name("ENVID")
+ .value("gabouzomeuh")
+ .build())
+ .addRecipient(Mail.Recipient.builder()
+ .address(new MailAddress(RECIPIENT))
+ .addParameter(Mail.Parameter.builder()
+ .name("NOTIFY")
+ .value("FAILURE,DELAY")
+ .build())
+ .build())
+ .build()));
+ }
+
+ @Test
+ public void remoteDeliveryShouldCarryOverDSNMailParameters() throws Exception {
+ AuthenticatingSMTPClient smtpClient = new AuthenticatingSMTPClient("TLS", "UTF-8");
+
+ try {
+ smtpClient.connect("localhost", jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort().getValue());
+ smtpClient.ehlo(DEFAULT_DOMAIN);
+ smtpClient.mail("<" + FROM + "> RET=HDRS ENVID=gabouzomeuh");
+ smtpClient.rcpt("<" + RECIPIENT2 + ">");
+ smtpClient.sendShortMessageData("A short message...");
+ } finally {
+ smtpClient.disconnect();
+ }
+
+ calmlyAwait.atMost(TEN_SECONDS).untilAsserted(() -> assertThat(mockSMTPConfiguration.listMails())
+ .hasSize(1)
+ .extracting(Mail::getEnvelope)
+ .containsExactly(Mail.Envelope.builder()
+ .from(new MailAddress(FROM))
+ .addMailParameter(Mail.Parameter.builder()
+ .name("RET")
+ .value("HDRS")
+ .build())
+ .addMailParameter(Mail.Parameter.builder()
+ .name("ENVID")
+ .value("gabouzomeuh")
+ .build())
+ .addRecipient(Mail.Recipient.builder()
+ .address(new MailAddress(RECIPIENT2))
+ .build())
+ .build()));
+ }
+
+
+ private ProcessorConfiguration.Builder directResolutionTransport() {
+ return ProcessorConfiguration.transport()
+ .addMailet(MailetConfiguration.BCC_STRIPPER)
+ .addMailet(MailetConfiguration.LOCAL_DELIVERY)
+ .addMailet(MailetConfiguration.builder()
+ .mailet(RemoteDelivery.class)
+ .matcher(All.class)
+ .addProperty("outgoingQueue", "outgoing")
+ .addProperty("delayTime", "3 * 10 ms")
+ .addProperty("maxRetries", "3")
+ .addProperty("maxDnsProblemRetries", "0")
+ .addProperty("deliveryThreads", "2")
+ .addProperty("sendpartial", "true"));
+ }
+
+ private ConfigurationClient configurationClient(DockerContainer mockSmtp) {
+ return ConfigurationClient.from(
+ Host.from(mockSmtp.getHostIp(),
+ mockSmtp.getMappedPort(8000)));
+ }
+}
diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remote/delivery/MailDelivrerToHost.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remote/delivery/MailDelivrerToHost.java
index 238a357..976b0c0 100644
--- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remote/delivery/MailDelivrerToHost.java
+++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/remote/delivery/MailDelivrerToHost.java
@@ -19,15 +19,28 @@
package org.apache.james.transport.mailets.remote.delivery;
+import static com.sun.mail.smtp.SMTPMessage.NOTIFY_DELAY;
+import static com.sun.mail.smtp.SMTPMessage.NOTIFY_FAILURE;
+import static com.sun.mail.smtp.SMTPMessage.NOTIFY_NEVER;
+import static com.sun.mail.smtp.SMTPMessage.NOTIFY_SUCCESS;
+import static com.sun.mail.smtp.SMTPMessage.RETURN_FULL;
+import static com.sun.mail.smtp.SMTPMessage.RETURN_HDRS;
+
import java.io.IOException;
import java.util.Collection;
+import java.util.EnumSet;
+import java.util.Optional;
import java.util.Properties;
+import javax.mail.Address;
import javax.mail.MessagingException;
import javax.mail.Session;
import javax.mail.internet.InternetAddress;
import javax.mail.internet.MimeMessage;
+import org.apache.commons.lang3.NotImplementedException;
+import org.apache.james.core.MailAddress;
+import org.apache.mailet.DsnParameters;
import org.apache.mailet.HostAddress;
import org.apache.mailet.Mail;
import org.apache.mailet.MailetContext;
@@ -35,6 +48,8 @@ import org.apache.mailet.base.Converter7Bit;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.github.fge.lambdas.Throwing;
+import com.sun.mail.smtp.SMTPMessage;
import com.sun.mail.smtp.SMTPTransport;
@SuppressWarnings("deprecation")
@@ -68,7 +83,11 @@ public class MailDelivrerToHost {
transport = (SMTPTransport) session.getTransport(outgoingMailServer);
transport.setLocalHost(props.getProperty("mail.smtp.localhost", configuration.getHeloNameProvider().getHeloName()));
connect(outgoingMailServer, transport);
- transport.sendMessage(adaptToTransport(mail.getMessage(), transport), addr.toArray(InternetAddress[]::new));
+ if (mail.dsnParameters().isPresent()) {
+ sendDSNAwareEmail(mail, transport, addr);
+ } else {
+ transport.sendMessage(adaptToTransport(mail.getMessage(), transport), addr.toArray(InternetAddress[]::new));
+ }
LOGGER.debug("Mail ({}) sent successfully to {} at {} from {} for {}", mail.getName(), outgoingMailServer.getHostName(),
outgoingMailServer.getHost(), props.get("mail.smtp.from"), mail.getRecipients());
} finally {
@@ -76,6 +95,68 @@ public class MailDelivrerToHost {
}
return ExecutionResult.success();
}
+ private void sendDSNAwareEmail(Mail mail, SMTPTransport transport, Collection<InternetAddress> addresses) {
+ addresses.forEach(Throwing.<InternetAddress>consumer(
+ address -> {
+ SMTPMessage smtpMessage = asSmtpMessage(mail, transport);
+ mail.dsnParameters()
+ .flatMap(Throwing.<DsnParameters, Optional<DsnParameters.RecipientDsnParameters>>function(
+ dsn -> Optional.ofNullable(dsn.getRcptParameters().get(new MailAddress(address.toString()))))
+ .sneakyThrow())
+ .flatMap(DsnParameters.RecipientDsnParameters::getNotifyParameter)
+ .map(this::toJavaxNotify)
+ .ifPresent(smtpMessage::setNotifyOptions);
+ InternetAddress[] rcpt = new InternetAddress[]{address};
+ transport.sendMessage(smtpMessage, rcpt);
+ }
+ ).sneakyThrow());
+ }
+
+ private SMTPMessage asSmtpMessage(Mail mail, SMTPTransport transport) throws MessagingException {
+ SMTPMessage smtpMessage = new SMTPMessage(adaptToTransport(mail.getMessage(), transport));
+ mail.dsnParameters().flatMap(DsnParameters::getRetParameter)
+ .map(this::toJavaxRet)
+ .ifPresent(smtpMessage::setReturnOption);
+ mail.dsnParameters().flatMap(DsnParameters::getEnvIdParameter)
+ .ifPresent(envId -> {
+ if (transport.supportsExtension("DSN")) {
+ smtpMessage.setMailExtension("ENVID=" + envId.asString());
+ }
+ });
+ return smtpMessage;
+ }
+
+ private int toJavaxRet(DsnParameters.Ret ret) {
+ switch (ret) {
+ case FULL:
+ return RETURN_FULL;
+ case HDRS:
+ return RETURN_HDRS;
+ default:
+ throw new NotImplementedException(ret + " cannot be converted to javax.mail parameters");
+ }
+ }
+
+ private int toJavaxNotify(EnumSet<DsnParameters.Notify> notifies) {
+ return notifies.stream()
+ .mapToInt(this::toJavaxNotify)
+ .sum();
+ }
+
+ private int toJavaxNotify(DsnParameters.Notify notify) {
+ switch (notify) {
+ case NEVER:
+ return NOTIFY_NEVER;
+ case SUCCESS:
+ return NOTIFY_SUCCESS;
+ case FAILURE:
+ return NOTIFY_FAILURE;
+ case DELAY:
+ return NOTIFY_DELAY;
+ default:
+ throw new NotImplementedException(notify + " cannot be converted to javax.mail parameters");
+ }
+ }
private Properties getPropertiesForMail(Mail mail) {
Properties props = session.getProperties();
diff --git a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ConfigurationClient.java b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ConfigurationClient.java
index dade1ba..484d628 100644
--- a/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ConfigurationClient.java
+++ b/server/mailet/mock-smtp-server/src/main/java/org/apache/james/mock/smtp/server/ConfigurationClient.java
@@ -210,6 +210,7 @@ public interface ConfigurationClient {
default void cleanServer() {
clearBehaviors();
clearMails();
+ clearSMTPExtensions();
}
default BehaviorsParamsBuilder.CommandStep addNewBehavior() {
---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org