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 2026/07/16 01:52:27 UTC

(james-project) branch master updated (3294aacee3 -> c3ad68ab9c)

This is an automated email from the ASF dual-hosted git repository.

Arsnael pushed a change to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git


    from 3294aacee3 [BUILD] ProvisioningContract: isolate using different users
     new a2632375e6 refactor: split mailbox and RRT checks into two methods
     new c20a917d44 feat: optional stricter checks of RRT when receiving email
     new 3e32f1a87c fix: ValidRcptHandlerTest
     new 0e4f2f5c66 test: add additional tests for the new checks in ValidRcptHandler
     new c3ad68ab9c docs: add documentation for the new checks in ValidRcptHandler

The 5 revisions listed above as "new" are entirely new to this
repository and will be described in separate emails.  The revisions
listed as "add" were already present in the repository and have only
been added to this reference.


Summary of changes:
 .../servers/partials/configure/smtp-hooks.adoc     |  27 ++--
 .../spring-app/src/main/resources/smtpserver.xml   |  21 ++-
 .../smtpserver/fastfail/ValidRcptHandler.java      | 102 ++++++++++++--
 .../james/smtpserver/ValidRcptHandlerTest.java     | 153 ++++++++++++++++++++-
 4 files changed, 270 insertions(+), 33 deletions(-)


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


(james-project) 01/05: refactor: split mailbox and RRT checks into two methods

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Arsnael pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit a2632375e6a307a7201e23f83285fc7aa638fca6
Author: Felix Auringer <fe...@protonmail.com>
AuthorDate: Wed Jun 24 18:23:07 2026 +0200

    refactor: split mailbox and RRT checks into two methods
    
    This enhances the extensibility as only one of them can be
    overwritten without having to touch the logic of the other.
---
 .../james/smtpserver/fastfail/ValidRcptHandler.java     | 17 ++++++++++-------
 1 file changed, 10 insertions(+), 7 deletions(-)

diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java
index f435f30571..c4f6110eed 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java
@@ -24,7 +24,6 @@ import org.apache.commons.configuration2.Configuration;
 import org.apache.commons.configuration2.ex.ConfigurationException;
 import org.apache.james.core.Domain;
 import org.apache.james.core.MailAddress;
-import org.apache.james.core.Username;
 import org.apache.james.domainlist.api.DomainList;
 import org.apache.james.domainlist.api.DomainListException;
 import org.apache.james.protocols.api.handler.ProtocolHandler;
@@ -64,17 +63,21 @@ public class ValidRcptHandler extends AbstractValidRcptHandler implements Protoc
 
     @Override
     protected boolean isValidRecipient(SMTPSession session, MailAddress recipient) throws UsersRepositoryException, RecipientRewriteTableException {
-        Username username = users.getUsername(recipient);
-
-        if (users.contains(username)) {
+        // Check existence of mailbox first to use RRT less often.
+        if (mailboxExists(recipient)) {
             return true;
         } else {
-            return supportsRecipientRewriteTable && isRedirected(recipient, username.asString());
+            // Check whether there is a valid RRT entry for the recipient.
+            return supportsRecipientRewriteTable && hasValidRRTEntry(recipient);
         }
     }
 
-    private boolean isRedirected(MailAddress recipient, String username) throws RecipientRewriteTableException {
-        LOGGER.debug("Unknown user {} check if it's an alias", username);
+    protected boolean mailboxExists(MailAddress recipient) throws UsersRepositoryException {
+        return users.contains(users.getUsername(recipient));
+    }
+
+    protected boolean hasValidRRTEntry(MailAddress recipient) throws RecipientRewriteTableException {
+        LOGGER.debug("Unknown recipient {}, resolving it via RRT", recipient);
 
         try {
             Mappings targetString = recipientRewriteTable.getResolvedMappings(recipient.getLocalPart(), recipient.getDomain());


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


(james-project) 05/05: docs: add documentation for the new checks in ValidRcptHandler

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Arsnael pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit c3ad68ab9ce3da317fe2b2a551b89d5cf1272bc7
Author: Felix Auringer <fe...@protonmail.com>
AuthorDate: Tue Jul 14 17:18:16 2026 +0200

    docs: add documentation for the new checks in ValidRcptHandler
---
 .../servers/partials/configure/smtp-hooks.adoc     | 27 +++++++++++++++-------
 .../spring-app/src/main/resources/smtpserver.xml   | 21 +++++++++++------
 2 files changed, 33 insertions(+), 15 deletions(-)

diff --git a/docs/modules/servers/partials/configure/smtp-hooks.adoc b/docs/modules/servers/partials/configure/smtp-hooks.adoc
index 5028910546..3e7e77cc30 100644
--- a/docs/modules/servers/partials/configure/smtp-hooks.adoc
+++ b/docs/modules/servers/partials/configure/smtp-hooks.adoc
@@ -328,23 +328,34 @@ Example configuration:
 
 == ValidRcptHandler
 
-With ValidRcptHandler, all email will get rejected which has no valid user.
+The ValidRcptHandler rejects all emails for which this email server does not handle the recipient.
 
-You need to add the recipient to the validRecipient list if you want
-to accept email for a recipient which not exist on the server.
+In any case, recipients for which a mailbox exists locally are accepted.
+Additionally, recipients with a valid entry in the RRT are accepted.
+This behavior can be disabled by setting the option `enableRecipientRewriteTable` to `false`. It is `true` by default.
 
-If you want James to act as a spamtrap or honeypot, you may comment ValidRcptHandler
-and implement the needed processors in spoolmanager.xml.
+If the RRT is enabled, there are three options how to check it.
+The desired option can be set with `recipientRewriteTableCheck` and must be one of the following:
 
-This handler should be considered stable.
+- `mappingExists` (default): The RRT must contain at least one mapping from the recipient. It may point to a mailbox on another email server or to a non-existing local mailbox. It may also be a mapping of type error.
+- `anyMappingValid`: After resolving the recipient using the RRT, at least one target must be an existing local mailbox. This ignores error mappings.
+- `allMappingsValid`: After resolving the recipient using the RRT, every target must be an existing local mailbox. If there is an error mapping, the check fails.
 
-Example configuration:
+Note that the last option completely forbids aliases to mailboxes on other email servers.
+Depending on your configuration of the recipient rewrite table, the recipient is resolved recursively.
+
+This handler is considered stable.
+
+Example configuration with default values:
 
 [source,xml]
 ....
 <handlerchain>
     <!-- ... -->
-    <handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/>
+    <handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler">
+        <enableRecipientRewriteTable>true</enableRecipientRewriteTable>
+        <recipientRewriteTableCheck>mappingExists</recipientRewriteTableCheck>
+    </handler>
 </handlerchain>
 ....
 
diff --git a/server/apps/spring-app/src/main/resources/smtpserver.xml b/server/apps/spring-app/src/main/resources/smtpserver.xml
index 9f8cd619d7..69c2795972 100644
--- a/server/apps/spring-app/src/main/resources/smtpserver.xml
+++ b/server/apps/spring-app/src/main/resources/smtpserver.xml
@@ -226,14 +226,21 @@
             <!--
             <handler class="org.apache.james.smtpserver.fastfail.ValidSenderDomainHandler"/>
              -->
-         
-            <!-- With ValidRcptHandler, all email will get rejected which has no valid user -->
-            <!-- You need to add the recipient to the validRecipient list if you want -->
-            <!-- to accept email for a recipient which not exist on the server -->
-            <!-- If you want James to act as a spamtrap or honeypot, you may comment ValidRcptHandler -->
-            <!-- and implement the needed processors in spoolmanager.xml -->
+
+            <!-- The ValidRcptHandler rejects all emails for which this email server does not handle the recipient. -->
+            <!-- In any case, recipients for which a mailbox exists locally are accepted. -->
+            <!-- Additionally, recipients with a valid entry in the RRT are accepted. -->
+            <!-- This behavior can be disabled by setting the option `enableRecipientRewriteTable` to `false`. It is `true` by default. -->
+            <!-- If the RRT is enabled, there are three options how to check it. -->
+            <!-- The desired option can be set with `recipientRewriteTableCheck` and must be one of the following: -->
+            <!-- - `mappingExists` (default): The RRT must contain at least one mapping from the recipient. It may point to a mailbox on another email server or to a non-existing local mailbox. It may also be a mapping of type error. -->
+            <!-- - `anyMappingValid`: After resolving the recipient using the RRT, at least one target must be an existing local mailbox. This ignores error mappings. -->
+            <!-- - `allMappingsValid`: After resolving the recipient using the RRT, every target must be an existing local mailbox. If there is an error mapping, the check fails. -->
+            <!-- Note that the last option completely forbids aliases to mailboxes on other email servers. -->
+            <!-- Depending on your configuration of the recipient rewrite table, the recipient is resolved recursively. -->
+            <!-- This handler is considered stable. -->
             <handler class="org.apache.james.smtpserver.fastfail.ValidRcptHandler"/>
-            
+
             <!-- If activated you can limit the maximal recipients -->
             <!-- 
             <handler class="org.apache.james.smtpserver.fastfail.MaxRcptHandler">


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


(james-project) 02/05: feat: optional stricter checks of RRT when receiving email

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Arsnael pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit c20a917d444fd0bcfa9fe22ef8827e1f7075026e
Author: Felix Auringer <fe...@protonmail.com>
AuthorDate: Mon Jun 29 10:06:18 2026 +0200

    feat: optional stricter checks of RRT when receiving email
    
    Currently, James only checks whether either the recipient exists
    or any alias for it exists. There is no guarantee that the alias
    actually points to an existing mailbox.
    
    The optional behavior checks whether any or all resolved mailboxes
    of the intended recipient actually exist locally.
    This allows to abort directly after the RCPT command instead of
    accepting the email and then error because the mailbox could not be
    found.
    However, with this additional check enabled, it is not possible
    anymore to forward emails to other email servers via the RRT.
---
 .../smtpserver/fastfail/ValidRcptHandler.java      | 87 ++++++++++++++++++++--
 1 file changed, 82 insertions(+), 5 deletions(-)

diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java
index c4f6110eed..92ddb18f7d 100644
--- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java
+++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/fastfail/ValidRcptHandler.java
@@ -18,6 +18,9 @@
  ****************************************************************/
 package org.apache.james.smtpserver.fastfail;
 
+import java.util.EnumSet;
+import java.util.Optional;
+
 import jakarta.inject.Inject;
 
 import org.apache.commons.configuration2.Configuration;
@@ -32,6 +35,7 @@ import org.apache.james.protocols.smtp.core.fastfail.AbstractValidRcptHandler;
 import org.apache.james.rrt.api.RecipientRewriteTable;
 import org.apache.james.rrt.api.RecipientRewriteTable.ErrorMappingException;
 import org.apache.james.rrt.api.RecipientRewriteTableException;
+import org.apache.james.rrt.lib.Mapping;
 import org.apache.james.rrt.lib.Mappings;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
@@ -44,11 +48,27 @@ import org.slf4j.LoggerFactory;
 public class ValidRcptHandler extends AbstractValidRcptHandler implements ProtocolHandler {
     private static final Logger LOGGER = LoggerFactory.getLogger(ValidRcptHandler.class);
 
+    public enum RecipientRewriteTableCheck {
+        MAPPING_EXISTS,
+        ANY_TARGET_HAS_LOCAL_MAILBOX,
+        ALL_TARGETS_HAVE_LOCAL_MAILBOX;
+
+        private static RecipientRewriteTableCheck parse(String value) throws ConfigurationException {
+            return switch (value) {
+                case "mappingExists" -> MAPPING_EXISTS;
+                case "anyMappingValid" -> ANY_TARGET_HAS_LOCAL_MAILBOX;
+                case "allMappingsValid" -> ALL_TARGETS_HAVE_LOCAL_MAILBOX;
+                default -> throw new ConfigurationException("ValidRcptHandler.RecipientRewriteTableCheck: unsupported value '" + value + "'");
+            };
+        }
+    }
+
     private final UsersRepository users;
     private final RecipientRewriteTable recipientRewriteTable;
     private final DomainList domains;
 
     private boolean supportsRecipientRewriteTable = true;
+    private RecipientRewriteTableCheck recipientRewriteTableCheck = RecipientRewriteTableCheck.MAPPING_EXISTS;
 
     @Inject
     public ValidRcptHandler(UsersRepository users, RecipientRewriteTable recipientRewriteTable, DomainList domains) {
@@ -61,6 +81,10 @@ public class ValidRcptHandler extends AbstractValidRcptHandler implements Protoc
         this.supportsRecipientRewriteTable = supportsRecipientRewriteTable;
     }
 
+    public void setRecipientRewriteTableCheck(RecipientRewriteTableCheck recipientRewriteTableCheck) {
+        this.recipientRewriteTableCheck = recipientRewriteTableCheck;
+    }
+
     @Override
     protected boolean isValidRecipient(SMTPSession session, MailAddress recipient) throws UsersRepositoryException, RecipientRewriteTableException {
         // Check existence of mailbox first to use RRT less often.
@@ -76,21 +100,73 @@ public class ValidRcptHandler extends AbstractValidRcptHandler implements Protoc
         return users.contains(users.getUsername(recipient));
     }
 
-    protected boolean hasValidRRTEntry(MailAddress recipient) throws RecipientRewriteTableException {
+    protected boolean hasValidRRTEntry(MailAddress recipient) throws RecipientRewriteTableException, UsersRepositoryException {
         LOGGER.debug("Unknown recipient {}, resolving it via RRT", recipient);
 
         try {
-            Mappings targetString = recipientRewriteTable.getResolvedMappings(recipient.getLocalPart(), recipient.getDomain());
+            return switch (this.recipientRewriteTableCheck) {
+                case MAPPING_EXISTS -> {
+                    Mappings mappings = recipientRewriteTable.getResolvedMappings(recipient.getLocalPart(), recipient.getDomain());
+                    yield !mappings.isEmpty();
+                }
+                case ANY_TARGET_HAS_LOCAL_MAILBOX -> {
+                    // As long as there is any mapping to a local mailbox, this check passes.
+                    // Error mappings are therefore irrelevant.
+                    Mappings mappings = recipientRewriteTable.getResolvedMappings(
+                        recipient.getLocalPart(),
+                        recipient.getDomain(),
+                        EnumSet.complementOf(EnumSet.of(Mapping.Type.Error))
+                    );
+                    yield anyResolvedMailboxExists(mappings);
+                }
+                case ALL_TARGETS_HAVE_LOCAL_MAILBOX -> {
+                    Mappings mappings = recipientRewriteTable.getResolvedMappings(recipient.getLocalPart(), recipient.getDomain());
+                    yield allResolvedMailboxesExist(mappings);
+                }
+            };
+        } catch (ErrorMappingException e) {
+            // Either an error mapping was encountered (ErrorMappingException) or the limit for recursively
+            // resolving a mapping was reached (TooManyMappingException).
+            return switch (this.recipientRewriteTableCheck) {
+                case MAPPING_EXISTS -> {
+                    // An error during mapping means that a mapping exists.
+                    LOGGER.info("Error while resolving recipient via RRT, allowing recipient {}: ", recipient, e);
+                    yield true;
+                }
+                case ANY_TARGET_HAS_LOCAL_MAILBOX -> {
+                    // It is unclear whether at least one mapping is valid.
+                    LOGGER.info("Error while resolving recipient via RRT, refusing recipient {}: ", recipient, e);
+                    yield false;
+                }
+                case ALL_TARGETS_HAVE_LOCAL_MAILBOX -> {
+                    // It is unclear whether all mappings are valid.
+                    LOGGER.info("Error while resolving recipient via RRT, refusing recipient {}: ", recipient, e);
+                    yield false;
+                }
+            };
+        }
+    }
 
-            if (!targetString.isEmpty()) {
+    private boolean anyResolvedMailboxExists(Mappings mappings) throws UsersRepositoryException {
+        for (Mapping mapping : mappings) {
+            Optional<MailAddress> email = mapping.asMailAddress();
+            if (email.isPresent() && mailboxExists(email.get())) {
                 return true;
             }
-        } catch (ErrorMappingException e) {
-            return true;
         }
         return false;
     }
 
+    private boolean allResolvedMailboxesExist(Mappings mappings) throws UsersRepositoryException {
+        for (Mapping mapping : mappings) {
+            Optional<MailAddress> email = mapping.asMailAddress();
+            if (email.isEmpty() || !mailboxExists(email.get())) {
+                return false;
+            }
+        }
+        return !mappings.isEmpty();
+    }
+
     @Override
     protected boolean isLocalDomain(SMTPSession session, Domain domain) throws DomainListException {
         return domains.containsDomain(domain);
@@ -99,5 +175,6 @@ public class ValidRcptHandler extends AbstractValidRcptHandler implements Protoc
     @Override
     public void init(Configuration config) throws ConfigurationException {
         setSupportsRecipientRewriteTable(config.getBoolean("enableRecipientRewriteTable", true));
+        setRecipientRewriteTableCheck(RecipientRewriteTableCheck.parse(config.getString("recipientRewriteTableCheck", "mappingExists")));
     }
 }


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


(james-project) 03/05: fix: ValidRcptHandlerTest

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Arsnael pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 3e32f1a87cb1e7b340e7508e3ce9650d3668893a
Author: Felix Auringer <fe...@protonmail.com>
AuthorDate: Tue Jul 14 17:16:09 2026 +0200

    fix: ValidRcptHandlerTest
    
    Some tests were not named properly and one test did not test what
    the name described.
---
 .../java/org/apache/james/smtpserver/ValidRcptHandlerTest.java | 10 +++++-----
 1 file changed, 5 insertions(+), 5 deletions(-)

diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
index dd0cc8d8bb..ea1fe2ed80 100644
--- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
+++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
@@ -134,7 +134,7 @@ class ValidRcptHandlerTest {
     }
 
     @Test
-    void doRcptShouldRejectNotExistingLocalUsersWhenNoRelay() {
+    void doRcptShouldDenyNotExistingLocalUsersWhenNoRelay() {
         SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
 
         HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, invalidUserEmail).getResult();
@@ -195,13 +195,13 @@ class ValidRcptHandlerTest {
 
         SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
 
-        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, validUserEmail).getResult();
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
 
         assertThat(rCode).isEqualTo(HookReturnCode.declined());
     }
 
     @Test
-    void doRcptShouldDenyWhenHasMappingLoop() throws Exception {
+    void doRcptShouldDeclineWhenHasMappingLoop() throws Exception {
         memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), USER2 + "@domain.tld");
         memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER2, DOMAIN_1), USER1 + "@domain.tld");
         // The loop needs to be created by a domain mapping
@@ -226,7 +226,7 @@ class ValidRcptHandlerTest {
     }
 
     @Test
-    void doRcptShouldReturnDenySoftWhenUsersRepositoryError() throws Exception {
+    void doRcptShouldDenySoftWhenUsersRepositoryError() throws Exception {
         SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
 
         UsersRepository users = mock(UsersRepository.class);
@@ -239,7 +239,7 @@ class ValidRcptHandlerTest {
     }
 
     @Test
-    void doRcptShouldReturnDeclineWhenInvalidUsername() throws Exception {
+    void doRcptShouldDenyWhenInvalidUsername() throws Exception {
         SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
 
         HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, new MailAddress("\"abc@\"@localhost")).getResult();


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org


(james-project) 04/05: test: add additional tests for the new checks in ValidRcptHandler

Posted by rc...@apache.org.
This is an automated email from the ASF dual-hosted git repository.

Arsnael pushed a commit to branch master
in repository https://gitbox.apache.org/repos/asf/james-project.git

commit 0e4f2f5c66499555e9e409706f3f9e04a46fe01e
Author: Felix Auringer <fe...@protonmail.com>
AuthorDate: Tue Jul 14 17:17:54 2026 +0200

    test: add additional tests for the new checks in ValidRcptHandler
---
 .../james/smtpserver/ValidRcptHandlerTest.java     | 145 ++++++++++++++++++++-
 1 file changed, 142 insertions(+), 3 deletions(-)

diff --git a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
index ea1fe2ed80..cd28046c63 100644
--- a/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
+++ b/server/protocols/protocols-smtp/src/test/java/org/apache/james/smtpserver/ValidRcptHandlerTest.java
@@ -41,6 +41,7 @@ import org.apache.james.rrt.api.RecipientRewriteTableConfiguration;
 import org.apache.james.rrt.lib.MappingSource;
 import org.apache.james.rrt.memory.MemoryRecipientRewriteTable;
 import org.apache.james.smtpserver.fastfail.ValidRcptHandler;
+import org.apache.james.smtpserver.fastfail.ValidRcptHandler.RecipientRewriteTableCheck;
 import org.apache.james.user.api.UsersRepository;
 import org.apache.james.user.api.UsersRepositoryException;
 import org.apache.james.user.memory.MemoryUsersRepository;
@@ -190,7 +191,7 @@ class ValidRcptHandlerTest {
     }
 
     @Test
-    void doRcptShouldDeclineWhenHasAddressMapping() throws Exception {
+    void doRcptMappingExistsShouldDeclineWhenHasAddressMapping() throws Exception {
         memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "address");
 
         SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
@@ -201,7 +202,77 @@ class ValidRcptHandlerTest {
     }
 
     @Test
-    void doRcptShouldDeclineWhenHasMappingLoop() throws Exception {
+    void doRcptAnyTargetValidShouldDenyWithoutAddressMapping() throws Exception {
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ANY_TARGET_HAS_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
+    @Test
+    void doRcptAnyTargetValidShouldDenyWhenHasOnlyInvalidAddressMapping() throws Exception {
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "address");
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ANY_TARGET_HAS_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
+    @Test
+    void doRcptAnyTargetValidShouldDeclineWhenHasValidAddressMapping() throws Exception {
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "address");
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), VALID_USER.asString());
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ANY_TARGET_HAS_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.declined());
+    }
+
+    @Test
+    void doRcptAllTargetsValidShouldDenyWithoutAddressMapping() throws Exception {
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ALL_TARGETS_HAVE_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
+    @Test
+    void doRcptAllTargetsValidShouldDenyWhenHasInvalidAddressMapping() throws Exception {
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "address");
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), VALID_USER.asString());
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ALL_TARGETS_HAVE_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
+    @Test
+    void doRcptAllTargetsValidShouldDeclineWhenHasOnlyValidAddressMapping() throws Exception {
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), VALID_USER.asString());
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ALL_TARGETS_HAVE_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.declined());
+    }
+
+    @Test
+    void doRcptMappingExistsShouldDeclineWhenHasMappingLoop() throws Exception {
         memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), USER2 + "@domain.tld");
         memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER2, DOMAIN_1), USER1 + "@domain.tld");
         // The loop needs to be created by a domain mapping
@@ -215,7 +286,37 @@ class ValidRcptHandlerTest {
     }
 
     @Test
-    void doRcptShouldDeclineWhenHasErrorMapping() throws Exception {
+    void doRcptAnyTargetValidShouldDenyWhenHasMappingLoop() throws Exception {
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), USER2 + "@domain.tld");
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER2, DOMAIN_1), USER1 + "@domain.tld");
+        // The loop needs to be created by a domain mapping
+        memoryRecipientRewriteTable.addDomainMapping(MappingSource.fromDomain(DOMAIN_1), Domain.LOCALHOST);
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ANY_TARGET_HAS_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
+    @Test
+    void doRcptAllTargetsValidShouldDenyWhenHasMappingLoop() throws Exception {
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), USER2 + "@domain.tld");
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER2, DOMAIN_1), USER1 + "@domain.tld");
+        // The loop needs to be created by a domain mapping
+        memoryRecipientRewriteTable.addDomainMapping(MappingSource.fromDomain(DOMAIN_1), Domain.LOCALHOST);
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ALL_TARGETS_HAVE_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
+    @Test
+    void doRcptMappingExistsShouldDeclineWhenHasErrorMapping() throws Exception {
         memoryRecipientRewriteTable.addErrorMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "554 BOUNCE");
 
         SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
@@ -225,6 +326,44 @@ class ValidRcptHandlerTest {
         assertThat(rCode).isEqualTo(HookReturnCode.declined());
     }
 
+    @Test
+    void doRcptAnyTargetValidShouldDenyWhenHasOnlyErrorMapping() throws Exception {
+        memoryRecipientRewriteTable.addErrorMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "554 BOUNCE");
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ANY_TARGET_HAS_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
+    @Test
+    void doRcptAnyTargetValidShouldDeclineWhenHasErrorMapping() throws Exception {
+        memoryRecipientRewriteTable.addErrorMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "554 BOUNCE");
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), VALID_USER.asString());
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ANY_TARGET_HAS_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.declined());
+    }
+
+    @Test
+    void doRcptAllTargetsValidShouldDenyWhenHasErrorMapping() throws Exception {
+        memoryRecipientRewriteTable.addErrorMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), "554 BOUNCE");
+        memoryRecipientRewriteTable.addAddressMapping(MappingSource.fromUser(USER1, Domain.LOCALHOST), VALID_USER.asString());
+
+        SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);
+        handler.setRecipientRewriteTableCheck(RecipientRewriteTableCheck.ALL_TARGETS_HAVE_LOCAL_MAILBOX);
+
+        HookReturnCode rCode = handler.doRcpt(session, MAYBE_SENDER, user1mail).getResult();
+
+        assertThat(rCode).isEqualTo(HookReturnCode.deny());
+    }
+
     @Test
     void doRcptShouldDenySoftWhenUsersRepositoryError() throws Exception {
         SMTPSession session = setupMockedSMTPSession(!RELAYING_ALLOWED);


---------------------------------------------------------------------
To unsubscribe, e-mail: notifications-unsubscribe@james.apache.org
For additional commands, e-mail: notifications-help@james.apache.org