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 d7b0c26128e0c84ab6d01c41221f67d171426aca Author: Benoit TELLIER <btell...@linagora.com> AuthorDate: Mon Dec 18 20:58:05 2023 +0100 JAMES-3944 Record forward loops as rrt-error --- .../mailets/FilterForwardIntegrationTest.java | 43 +++++++++++ .../james/mailets/ForwardIntegrationTest.java | 45 +++++++++++ .../james/mailets/ForwardLoopIntegrationTest.java | 88 +++++++++++++++++++++- .../mailets/RecipientRewriteTableProcessor.java | 24 +++++- .../james/jmap/mailet/filter/ActionApplier.java | 34 +++++++-- .../james/jmap/mailet/filter/JMAPFiltering.java | 11 ++- 6 files changed, 233 insertions(+), 12 deletions(-) diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterForwardIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterForwardIntegrationTest.java index adeb67b63b..aded25a862 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterForwardIntegrationTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/FilterForwardIntegrationTest.java @@ -21,9 +21,11 @@ package org.apache.james.mailets; import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.CONTAINS; import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.FROM; +import static org.apache.james.mailets.configuration.CommonProcessors.RRT_ERROR_REPOSITORY; 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.assertj.core.api.Assertions.assertThat; import java.io.File; @@ -46,6 +48,7 @@ import org.apache.james.utils.FilteringManagementProbeImpl; import org.apache.james.utils.GuiceProbe; import org.apache.james.utils.MailRepositoryProbeImpl; import org.apache.james.utils.SMTPMessageSender; +import org.apache.james.utils.SpoolerProbe; import org.apache.mailet.Mail; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.AfterEach; @@ -95,6 +98,7 @@ public class FilterForwardIntegrationTest { .mailet(ToRepository.class) .addProperty("repositoryPath", CUSTOM_REPOSITORY.asString()))) .putProcessor(CommonProcessors.error()) + .putProcessor(CommonProcessors.rrtError()) .putProcessor(CommonProcessors.transport()) .putProcessor(CommonProcessors.bounces())) .build(temporaryFolder); @@ -152,4 +156,43 @@ public class FilterForwardIntegrationTest { SoftAssertions.assertSoftly(Throwing.consumer(softly -> softly.assertThat(mailRepositoryProbe.listMails(CUSTOM_REPOSITORY) .anyMatch(Throwing.predicate(mail -> mail.getRecipients().contains(BOB.asMailAddress())))).isFalse())); } + + @Test + void regularForwardShouldNotLeadToRecordingRRTError() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).withoutACopy())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + + @Test + void localCopyShouldNotLeadToRecordingRRTError() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).keepACopy())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 2L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + + @Test + void localCopyAloneShouldNotLeadToRecordingRRTError() throws Exception { + filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to().keepACopy())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(ALICE.asString(), PASSWORD) + .sendMessage(ALICE.asString(), BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } } diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardIntegrationTest.java index 2c93ef3617..73f59c5cf5 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardIntegrationTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardIntegrationTest.java @@ -20,9 +20,11 @@ package org.apache.james.mailets; import static org.apache.james.MemoryJamesServerMain.SMTP_ONLY_MODULE; +import static org.apache.james.mailets.configuration.CommonProcessors.RRT_ERROR_REPOSITORY; 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.assertj.core.api.Assertions.assertThat; import java.io.File; @@ -45,6 +47,7 @@ import org.apache.james.transport.matchers.All; import org.apache.james.utils.DataProbeImpl; import org.apache.james.utils.MailRepositoryProbeImpl; import org.apache.james.utils.SMTPMessageSender; +import org.apache.james.utils.SpoolerProbe; import org.apache.mailet.Mail; import org.assertj.core.api.SoftAssertions; import org.junit.jupiter.api.AfterEach; @@ -360,6 +363,48 @@ public class ForwardIntegrationTest { })); } + @Test + void localCopyAloneShouldNotLeadToRecordingRRTError() throws Exception { + jamesServer.getProbe(DataProbeImpl.class) + .addMapping(MappingSource.fromUser(BOB), + Mapping.forward(BOB.asString())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(FROM, PASSWORD) + .sendMessage(FROM, BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + + @Test + void localCopyShouldNotLeadToRecordingRRTError() throws Exception { + jamesServer.getProbe(DataProbeImpl.class).addMapping(MappingSource.fromUser(BOB), Mapping.forward(BOB.asString())); + jamesServer.getProbe(DataProbeImpl.class).addMapping(MappingSource.fromUser(BOB), Mapping.forward(CEDRIC.asString())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(FROM, PASSWORD) + .sendMessage(FROM, BOB.asString()); + + Awaitility.await().until(() -> jamesServer.getProbe(SpoolerProbe.class).processingFinished()); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + + @Test + void forwardShouldNotLeadToRecordingRRTError() throws Exception { + jamesServer.getProbe(DataProbeImpl.class).addMapping(MappingSource.fromUser(BOB), Mapping.forward(CEDRIC.asString())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(FROM, PASSWORD) + .sendMessage(FROM, BOB.asString()); + + Awaitility.await().until(() -> mailRepositoryProbe.getRepositoryMailCount(CUSTOM_REPOSITORY) == 1L); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isZero(); + } + @Test void forwardShouldSupportSeveralTargets() throws Exception { jamesServer.getProbe(DataProbeImpl.class) diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardLoopIntegrationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardLoopIntegrationTest.java index 5c86a41d6e..1168a86d28 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardLoopIntegrationTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/mailets/ForwardLoopIntegrationTest.java @@ -21,13 +21,14 @@ package org.apache.james.mailets; import static org.apache.james.jmap.api.filtering.Rule.Condition.Comparator.NOT_CONTAINS; import static org.apache.james.jmap.api.filtering.Rule.Condition.Field.FROM; +import static org.apache.james.mailets.configuration.CommonProcessors.RRT_ERROR_REPOSITORY; 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.assertj.core.api.Assertions.assertThat; import java.io.File; import java.util.List; -import java.util.stream.Stream; import org.apache.james.core.Username; import org.apache.james.jmap.api.filtering.Rule; @@ -97,7 +98,8 @@ public class ForwardLoopIntegrationTest { .putProcessor(ProcessorConfiguration.root() .addMailet(MailetConfiguration.builder() .matcher(All.class) - .mailet(RecipientRewriteTable.class)) + .mailet(RecipientRewriteTable.class) + .addProperty("errorProcessor", "rrt-error")) .addMailet(MailetConfiguration.builder() .matcher(All.class) .mailet(JMAPFiltering.class)) @@ -106,6 +108,12 @@ public class ForwardLoopIntegrationTest { .mailet(ToRepository.class) .addProperty("repositoryPath", CUSTOM_REPOSITORY.asString()))) .putProcessor(CommonProcessors.error()) + .putProcessor(ProcessorConfiguration.builder() + .state("rrt-error") + .addMailet(MailetConfiguration.builder() + .matcher(All.class) + .mailet(ToRepository.class) + .addProperty("repositoryPath", RRT_ERROR_REPOSITORY.asString()))) .putProcessor(CommonProcessors.transport()) .putProcessor(CommonProcessors.bounces())) .build(temporaryFolder); @@ -198,6 +206,82 @@ public class ForwardLoopIntegrationTest { })); } + @Test + void loopShouldLeadToRrtError() throws Exception { + dataProbe.addMapping(MappingSource.fromUser(ALICE), Mapping.forward(BOB.asString())); + dataProbe.addMapping(MappingSource.fromUser(BOB), Mapping.forward(CEDRIC.asString())); + dataProbe.addMapping(MappingSource.fromUser(CEDRIC), Mapping.forward(ALICE.asString())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(SENDER.asString(), PASSWORD) + .sendMessage(SENDER.asString(), ALICE.asString()); + + Awaitility.await().until(() -> jamesServer.getProbe(SpoolerProbe.class).processingFinished()); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isEqualTo(1); + } + + @Test + void shouldDetectSenderInLoopWhenAliasAndForwardRules() throws Exception { + dataProbe.addMapping(MappingSource.fromUser(Username.of("sender-alias@" + DEFAULT_DOMAIN)), + Mapping.alias(SENDER.asString())); + filteringManagementProbe.defineRulesForUser(ALICE, asRule(Forward.to(SENDER.asMailAddress()).withoutACopy())); + filteringManagementProbe.defineRulesForUser(SENDER, asRule(Forward.to(ALICE.asMailAddress()).withoutACopy())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(SENDER.asString(), PASSWORD) + .sendMessage("sender-alias@" + DEFAULT_DOMAIN, ALICE.asString()); + + Awaitility.await().until(() -> jamesServer.getProbe(SpoolerProbe.class).processingFinished()); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isEqualTo(1); + } + + @Test + void loopWithRuleShouldLeadToRrtError() throws Exception { + filteringManagementProbe.defineRulesForUser(ALICE, asRule(Forward.to(BOB.asMailAddress()).withoutACopy())); + filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).withoutACopy())); + filteringManagementProbe.defineRulesForUser(CEDRIC, asRule(Forward.to(ALICE.asMailAddress()).withoutACopy())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(SENDER.asString(), PASSWORD) + .sendMessage(SENDER.asString(), ALICE.asString()); + + Awaitility.await().until(() -> jamesServer.getProbe(SpoolerProbe.class).processingFinished()); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isEqualTo(1); + } + + @Test + void loopWithLocalCopyShouldLeadToRrtError() throws Exception { + filteringManagementProbe.defineRulesForUser(ALICE, asRule(Forward.to(BOB.asMailAddress()).keepACopy())); + filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).withoutACopy())); + filteringManagementProbe.defineRulesForUser(CEDRIC, asRule(Forward.to(ALICE.asMailAddress()).withoutACopy())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(SENDER.asString(), PASSWORD) + .sendMessage(SENDER.asString(), ALICE.asString()); + + Awaitility.await().until(() -> jamesServer.getProbe(SpoolerProbe.class).processingFinished()); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isEqualTo(1); + } + + @Test + void mixedLoopShouldLeadToRrtError() throws Exception { + dataProbe.addMapping(MappingSource.fromUser(ALICE), Mapping.forward(BOB.asString())); + filteringManagementProbe.defineRulesForUser(BOB, asRule(Forward.to(CEDRIC.asMailAddress()).withoutACopy())); + filteringManagementProbe.defineRulesForUser(CEDRIC, asRule(Forward.to(ALICE.asMailAddress()).withoutACopy())); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .authenticate(SENDER.asString(), PASSWORD) + .sendMessage(SENDER.asString(), ALICE.asString()); + + Awaitility.await().until(() -> jamesServer.getProbe(SpoolerProbe.class).processingFinished()); + + assertThat(mailRepositoryProbe.getRepositoryMailCount(RRT_ERROR_REPOSITORY)).isEqualTo(1); + } + @Test void forwardShouldNotCreateLoopErrorWhenFilterForwardAndRegularForwardWorkTogether() throws Exception { filteringManagementProbe.defineRulesForUser(ALICE, asRule(Forward.to(BOB.asMailAddress()).withoutACopy())); diff --git a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java index a34c12482c..a6dd91e2d5 100644 --- a/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java +++ b/server/mailet/mailets/src/main/java/org/apache/james/transport/mailets/RecipientRewriteTableProcessor.java @@ -244,6 +244,23 @@ public class RecipientRewriteTableProcessor { }; } + static ForwardDecision recordLoop(MailetContext context, + MailAddress originalRecipient, + ProcessingState errorProcessor) { + return mail -> { + MailImpl copy = MailImpl.duplicate(mail); + try { + copy.setRecipients(ImmutableList.of(originalRecipient)); + copy.setState(errorProcessor.getValue()); + copy.setName(MailImpl.getId()); + + context.sendMail(copy, errorProcessor.getValue()); + } finally { + LifecycleUtil.dispose(copy); + } + }; + } + private static void recordInAuditTrail(Mail mail, MailImpl copy, MailAddress originalRecipient) { AuditTrail.entry() .protocol("mailetcontainer") @@ -300,9 +317,14 @@ public class RecipientRewriteTableProcessor { if (!forwardRecipients.isEmpty()) { result.add(ForwardDecision.sendACopy(mailetContext, originalRecipient, forwardRecipients)); } - if (!newRecipients.contains(originalRecipient)) { + boolean localCopy = newRecipients.contains(originalRecipient); + if (!localCopy) { result.add(ForwardDecision.removeRecipient(originalRecipient)); } + boolean emailIsDropped = !forwardedRecipients.isEmpty() && forwardRecipients.isEmpty() && !localCopy; + if (emailIsDropped) { + result.add(ForwardDecision.recordLoop(mailetContext, originalRecipient, errorProcessor)); + } return result.build().stream(); } diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java index f5c2038223..0017dba5ca 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/ActionApplier.java @@ -19,11 +19,12 @@ package org.apache.james.jmap.mailet.filter; +import static org.apache.james.jmap.mailet.filter.JMAPFiltering.RRT_ERROR; + import java.util.Optional; import java.util.Set; import java.util.stream.Stream; -import com.google.common.collect.Sets; import javax.inject.Inject; import javax.mail.MessagingException; import javax.mail.internet.MimeMessage; @@ -52,6 +53,7 @@ import com.google.common.annotations.VisibleForTesting; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Sets; public class ActionApplier { public static final Logger LOGGER = LoggerFactory.getLogger(ActionApplier.class); @@ -146,9 +148,14 @@ public class ActionApplier { if (!forwardRecipients.isEmpty()) { sendACopy(recordedRecipients, forwardRecipients); } - if (!newRecipients.contains(mailAddress)) { + boolean localCopy = newRecipients.contains(mailAddress); + if (!localCopy) { removeFromRecipients(); } + boolean emailIsDropped = !forward.getAddresses().isEmpty() && forwardRecipients.isEmpty() && !localCopy; + if (emailIsDropped) { + recordLoop(); + } })); } @@ -210,17 +217,15 @@ public class ActionApplier { } } - private void sendACopy(MailetContext context, - MailAddress originalRecipient, - LoopPrevention.RecordedRecipients recordedRecipients, + private void sendACopy(LoopPrevention.RecordedRecipients recordedRecipients, Set<MailAddress> newRecipients) throws MessagingException { MailImpl copy = MailImpl.duplicate(mail); try { - copy.setSender(originalRecipient); + copy.setSender(mailAddress); copy.setRecipients(newRecipients); - recordedRecipients.merge(originalRecipient).recordOn(copy); + recordedRecipients.merge(mailAddress).recordOn(copy); - context.sendMail(copy); + mailetContext.sendMail(copy); recordInAuditTrail(copy); } finally { @@ -228,6 +233,19 @@ public class ActionApplier { } } + private void recordLoop() throws MessagingException { + MailImpl copy = MailImpl.duplicate(mail); + try { + copy.setRecipients(ImmutableList.of(mailAddress)); + copy.setState(RRT_ERROR.getValue()); + copy.setName(MailImpl.getId()); + + mailetContext.sendMail(copy); + } finally { + LifecycleUtil.dispose(copy); + } + } + private void recordInAuditTrail(MailImpl copy) { AuditTrail.entry() .protocol("mailetcontainer") diff --git a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java index ce150ab6ee..af16c98ac4 100644 --- a/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java +++ b/server/protocols/jmap-draft/src/main/java/org/apache/james/jmap/mailet/filter/JMAPFiltering.java @@ -19,6 +19,7 @@ package org.apache.james.jmap.mailet.filter; +import java.util.Collection; import java.util.Optional; import java.util.stream.Stream; @@ -32,10 +33,13 @@ import org.apache.james.jmap.api.filtering.Rules; import org.apache.james.user.api.UsersRepository; import org.apache.james.user.api.UsersRepositoryException; import org.apache.mailet.Mail; +import org.apache.mailet.ProcessingState; import org.apache.mailet.base.GenericMailet; import org.slf4j.Logger; import org.slf4j.LoggerFactory; +import com.google.common.collect.ImmutableList; + import reactor.core.publisher.Mono; /** @@ -50,7 +54,7 @@ import reactor.core.publisher.Mono; * <mailet match="RecipientIsLocal" class="org.apache.james.jmap.mailet.filter.JMAPFiltering"/> */ public class JMAPFiltering extends GenericMailet { - + static final ProcessingState RRT_ERROR = new ProcessingState("rrt-error"); private final Logger logger = LoggerFactory.getLogger(JMAPFiltering.class); private final FilteringManagement filteringManagement; @@ -94,4 +98,9 @@ public class JMAPFiltering extends GenericMailet { return Optional.empty(); } } + + @Override + public Collection<ProcessingState> requiredProcessingState() { + return ImmutableList.of(RRT_ERROR); + } } --------------------------------------------------------------------- To unsubscribe, e-mail: notifications-unsubscr...@james.apache.org For additional commands, e-mail: notifications-h...@james.apache.org