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 <[email protected]> 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: [email protected] For additional commands, e-mail: [email protected]
