This is an automated email from the ASF dual-hosted git repository. btellier pushed a commit to branch 3.9.x in repository https://gitbox.apache.org/repos/asf/james-project.git
commit ff93b29af00926be039d4edaa6aaf2081bbad59e Author: Benoit TELLIER <[email protected]> AuthorDate: Mon Feb 9 23:44:20 2026 +0100 JAMES-4171 Allow configure strong distinction between submission and mx ports --- docs/modules/servers/partials/configure/smtp.adoc | 12 +++++++++++ .../protocols/lmtp/LMTPConfigurationImpl.java | 5 +++-- .../james/protocols/smtp/SMTPConfiguration.java | 7 ++++-- .../protocols/smtp/SMTPConfigurationImpl.java | 5 +++-- .../sample-configuration/smtpserver.xml | 2 ++ .../jpa-app/sample-configuration/smtpserver.xml | 2 ++ .../memory-app/sample-configuration/smtpserver.xml | 2 ++ .../sample-configuration/smtpserver.xml | 2 ++ .../mailets/configuration/SmtpConfiguration.java | 12 +++++++++++ .../src/main/resources/smtpserver.xml | 2 ++ .../james/smtp/SmtpIdentityVerificationTest.java | 25 ++++++++++++++++++++++ .../apache/james/lmtpserver/netty/LMTPServer.java | 7 +++--- .../james/smtpserver/ExtendedSMTPSession.java | 5 +++-- .../SenderAuthIdentifyVerificationHook.java | 14 +++++++----- .../apache/james/smtpserver/netty/SMTPServer.java | 14 ++++++------ src/site/xdoc/server/config-smtp-lmtp.xml | 9 ++++++++ 16 files changed, 101 insertions(+), 24 deletions(-) diff --git a/docs/modules/servers/partials/configure/smtp.adoc b/docs/modules/servers/partials/configure/smtp.adoc index 788f205b7b..f6d826a2f8 100644 --- a/docs/modules/servers/partials/configure/smtp.adoc +++ b/docs/modules/servers/partials/configure/smtp.adoc @@ -99,6 +99,16 @@ channels. | This is an optional tag, defaults to true. If false, AUTH PLAIN and AUTH LOGIN will not be exposed. This setting can be used to enforce strong authentication mechanisms. +| auth.required +| Authentication is required to send emails. Adapted for submission ports. + +Note that if false (legacy value and default for backward compatibility) then unauthenticated senders are allowed but +limited by sender verification (prevent spoofing) and relaying limits (must be authenticated to relay). + +We encourage setting this value to true on submission ports (465 + 587). + +Please note that `authorizedAddresses` are considered authenticated. + | auth.oidc.oidcConfigurationURL | Provide OIDC url address for information to user. Only configure this when you want to authenticate SMTP server using a OIDC provider. @@ -165,6 +175,8 @@ Backward compatibility is provided and thus the following values are supported: - `true`: act as `strict` - `false`: act as `disabled` +Please note that this parameter only intend to prevent spoofing, and still allow unauthenticated remote users (that do not use local identity) to send email to local users. + | maxmessagesize | This is an optional tag with a non-negative integer body. It specifies the maximum size, in kbytes, of any message that will be transmitted by this SMTP server. It is a service-wide, as opposed to diff --git a/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java b/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java index 4f874e011a..c81959b067 100644 --- a/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java +++ b/protocols/lmtp/src/test/java/org/apache/james/protocols/lmtp/LMTPConfigurationImpl.java @@ -31,8 +31,9 @@ public class LMTPConfigurationImpl extends LMTPConfiguration { } @Override - public SenderVerificationMode verifyIdentity() { - return SenderVerificationMode.DISABLED; + public SenderVerificationConfiguration senderVerificationConfiguration() { + boolean allowUnauthenticatedSender = true; + return new SenderVerificationConfiguration(SenderVerificationMode.DISABLED, allowUnauthenticatedSender); } @Override diff --git a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java index 10bffd7fea..a0eb940633 100644 --- a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java +++ b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfiguration.java @@ -37,12 +37,15 @@ import com.google.common.collect.ImmutableSet; * */ public interface SMTPConfiguration extends ProtocolConfiguration { + record SenderVerificationConfiguration(SenderVerificationMode mode, boolean allowUnauthenticatedSender) { + + } + enum SenderVerificationMode { STRICT, RELAXED, DISABLED; - // TODO unit tests public static SenderVerificationMode parse(String value) { return switch (value.toLowerCase(Locale.US).trim()) { case "true", "strict" -> STRICT; @@ -77,7 +80,7 @@ public interface SMTPConfiguration extends ProtocolConfiguration { */ boolean isAuthAnnounced(String remoteIP, boolean tlsStarted); - SenderVerificationMode verifyIdentity(); + SenderVerificationConfiguration senderVerificationConfiguration(); /** * Returns whether the remote server needs to send a HELO/EHLO diff --git a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java index 8da9de699d..3890210d72 100644 --- a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java +++ b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/SMTPConfigurationImpl.java @@ -39,8 +39,9 @@ public class SMTPConfigurationImpl extends ProtocolConfigurationImpl implements } @Override - public SenderVerificationMode verifyIdentity() { - return SenderVerificationMode.STRICT; + public SenderVerificationConfiguration senderVerificationConfiguration() { + boolean allowUnauthenticatedSender = true; + return new SenderVerificationConfiguration(SenderVerificationMode.STRICT, allowUnauthenticatedSender); } @Override diff --git a/server/apps/distributed-app/sample-configuration/smtpserver.xml b/server/apps/distributed-app/sample-configuration/smtpserver.xml index 1117546646..41f66dc318 100644 --- a/server/apps/distributed-app/sample-configuration/smtpserver.xml +++ b/server/apps/distributed-app/sample-configuration/smtpserver.xml @@ -89,6 +89,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> @@ -136,6 +137,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> diff --git a/server/apps/jpa-app/sample-configuration/smtpserver.xml b/server/apps/jpa-app/sample-configuration/smtpserver.xml index cd8b82c104..aa69f74f1c 100644 --- a/server/apps/jpa-app/sample-configuration/smtpserver.xml +++ b/server/apps/jpa-app/sample-configuration/smtpserver.xml @@ -86,6 +86,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> @@ -133,6 +134,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> diff --git a/server/apps/memory-app/sample-configuration/smtpserver.xml b/server/apps/memory-app/sample-configuration/smtpserver.xml index cd8b82c104..aa69f74f1c 100644 --- a/server/apps/memory-app/sample-configuration/smtpserver.xml +++ b/server/apps/memory-app/sample-configuration/smtpserver.xml @@ -86,6 +86,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> @@ -133,6 +134,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> diff --git a/server/apps/postgres-app/sample-configuration/smtpserver.xml b/server/apps/postgres-app/sample-configuration/smtpserver.xml index cd8b82c104..aa69f74f1c 100644 --- a/server/apps/postgres-app/sample-configuration/smtpserver.xml +++ b/server/apps/postgres-app/sample-configuration/smtpserver.xml @@ -86,6 +86,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> @@ -133,6 +134,7 @@ <announce>forUnauthorizedAddresses</announce> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> + <required>true</required> <!-- Sample OIDC configuration --> <!-- <oidc> diff --git a/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/SmtpConfiguration.java b/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/SmtpConfiguration.java index 3bb0556257..3640af9d2a 100644 --- a/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/SmtpConfiguration.java +++ b/server/mailet/integration-testing/src/main/java/org/apache/james/mailets/configuration/SmtpConfiguration.java @@ -73,6 +73,7 @@ public class SmtpConfiguration implements SerializableAsXml { private Optional<Boolean> startTls; private Optional<String> maxMessageSize; private Optional<SMTPConfiguration.SenderVerificationMode> verifyIndentity; + private Optional<Boolean> allowUnauthenticatedSender; private Optional<Boolean> bracketEnforcement; private Optional<String> authorizedAddresses; private final ImmutableList.Builder<HookConfigurationEntry> additionalHooks; @@ -84,6 +85,7 @@ public class SmtpConfiguration implements SerializableAsXml { verifyIndentity = Optional.empty(); maxMessageSize = Optional.empty(); bracketEnforcement = Optional.empty(); + allowUnauthenticatedSender = Optional.empty(); additionalHooks = ImmutableList.builder(); } @@ -123,6 +125,11 @@ public class SmtpConfiguration implements SerializableAsXml { return this; } + public Builder forbidUnauthenticatedSenders() { + this.allowUnauthenticatedSender = Optional.of(false); + return this; + } + public Builder doNotVerifyIdentity() { this.verifyIndentity = Optional.of(SMTPConfiguration.SenderVerificationMode.DISABLED); return this; @@ -148,6 +155,7 @@ public class SmtpConfiguration implements SerializableAsXml { authRequired.orElse(!AUTH_REQUIRED), startTls.orElse(false), bracketEnforcement.orElse(true), + allowUnauthenticatedSender.orElse(true), verifyIndentity.orElse(SMTPConfiguration.SenderVerificationMode.DISABLED), maxMessageSize.orElse(DEFAULT_DISABLED), additionalHooks.build()); @@ -162,6 +170,7 @@ public class SmtpConfiguration implements SerializableAsXml { private final boolean authRequired; private final boolean startTls; private final boolean bracketEnforcement; + private final boolean allowUnauthenticatedSenders; private final SMTPConfiguration.SenderVerificationMode verifyIndentity; private final String maxMessageSize; private final ImmutableList<HookConfigurationEntry> additionalHooks; @@ -170,6 +179,7 @@ public class SmtpConfiguration implements SerializableAsXml { boolean authRequired, boolean startTls, boolean bracketEnforcement, + boolean allowUnauthenticatedSenders, SMTPConfiguration.SenderVerificationMode verifyIndentity, String maxMessageSize, ImmutableList<HookConfigurationEntry> additionalHooks) { @@ -177,6 +187,7 @@ public class SmtpConfiguration implements SerializableAsXml { this.authRequired = authRequired; this.bracketEnforcement = bracketEnforcement; this.verifyIndentity = verifyIndentity; + this.allowUnauthenticatedSenders = allowUnauthenticatedSenders; this.maxMessageSize = maxMessageSize; this.startTls = startTls; this.additionalHooks = additionalHooks; @@ -189,6 +200,7 @@ public class SmtpConfiguration implements SerializableAsXml { authorizedAddresses.ifPresent(value -> scopes.put("authorizedAddresses", value)); scopes.put("authRequired", authRequired); scopes.put("verifyIdentity", verifyIndentity.toString()); + scopes.put("forbidUnauthenticatedSenders", Boolean.toString(!allowUnauthenticatedSenders)); scopes.put("maxmessagesize", maxMessageSize); scopes.put("bracketEnforcement", bracketEnforcement); scopes.put("startTls", startTls); diff --git a/server/mailet/integration-testing/src/main/resources/smtpserver.xml b/server/mailet/integration-testing/src/main/resources/smtpserver.xml index 0f94a58a45..e2a2c719fb 100644 --- a/server/mailet/integration-testing/src/main/resources/smtpserver.xml +++ b/server/mailet/integration-testing/src/main/resources/smtpserver.xml @@ -38,6 +38,7 @@ <authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses> {{/hasAuthorizedAddresses}} <auth> + <required>{{forbidUnauthenticatedSenders}}</required> <requireSSL>false</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> </auth> @@ -79,6 +80,7 @@ <authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses> {{/hasAuthorizedAddresses}} <auth> + <required>{{forbidUnauthenticatedSenders}}</required> <requireSSL>true</requireSSL> <plainAuthEnabled>true</plainAuthEnabled> </auth> diff --git a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpIdentityVerificationTest.java b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpIdentityVerificationTest.java index aec3211ed0..4a3d232354 100644 --- a/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpIdentityVerificationTest.java +++ b/server/mailet/integration-testing/src/test/java/org/apache/james/smtp/SmtpIdentityVerificationTest.java @@ -82,6 +82,31 @@ class SmtpIdentityVerificationTest { .sendMessage("[email protected]", USER); } + @Test + void remoteUserCanSendEmailsToLocalUsersWhenLocalNetwork(@TempDir File temporaryFolder) throws Exception { + createJamesServer(temporaryFolder, SmtpConfiguration.builder() + .requireAuthentication() + .withAutorizedAddresses("127.0.0.0/8") + .verifyIdentity() + .forbidUnauthenticatedSenders()); + + messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .sendMessage("[email protected]", USER); + } + + @Test + void remoteUserCannotSendEmailsToLocalUsersWhenUnauthorizedSendersAreRejected(@TempDir File temporaryFolder) throws Exception { + createJamesServer(temporaryFolder, SmtpConfiguration.builder() + .requireAuthentication() + .verifyIdentity() + .forbidUnauthenticatedSenders()); + + assertThatThrownBy(() -> messageSender.connect(LOCALHOST_IP, jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort()) + .sendMessage("[email protected]", USER)) + .isInstanceOf(SMTPSendingException.class) + .hasMessageContaining("530 5.7.1 Authentication Required"); + } + @Test void relaxedShouldAcceptEmailsFromMXWhenLocalUsers(@TempDir File temporaryFolder) throws Exception { createJamesServer(temporaryFolder, SmtpConfiguration.builder() diff --git a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java index f367ea7b9d..63ac70db6b 100644 --- a/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java +++ b/server/protocols/protocols-lmtp/src/main/java/org/apache/james/lmtpserver/netty/LMTPServer.java @@ -102,10 +102,11 @@ public class LMTPServer extends AbstractProtocolAsyncServer implements LMTPServe protected LMTPConfigurationImpl() { super("JAMES Protocols Server"); } - + @Override - public SenderVerificationMode verifyIdentity() { - return SenderVerificationMode.DISABLED; + public SenderVerificationConfiguration senderVerificationConfiguration() { + boolean allowUnauthenticatedSender = true; + return new SenderVerificationConfiguration(SenderVerificationMode.DISABLED, allowUnauthenticatedSender); } @Override diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/ExtendedSMTPSession.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/ExtendedSMTPSession.java index 52ae853c90..e89f145700 100644 --- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/ExtendedSMTPSession.java +++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/ExtendedSMTPSession.java @@ -39,8 +39,9 @@ public class ExtendedSMTPSession extends org.apache.james.protocols.smtp.SMTPSes this.smtpConfiguration = smtpConfiguration; } - public SMTPConfiguration.SenderVerificationMode verifyIdentity() { - return smtpConfiguration.verifyIdentity(); + + public SMTPConfiguration.SenderVerificationConfiguration senderVerificationConfiguration() { + return smtpConfiguration.senderVerificationConfiguration(); } @Override diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/SenderAuthIdentifyVerificationHook.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/SenderAuthIdentifyVerificationHook.java index 5620c9563e..8792e3c629 100644 --- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/SenderAuthIdentifyVerificationHook.java +++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/SenderAuthIdentifyVerificationHook.java @@ -74,10 +74,14 @@ public class SenderAuthIdentifyVerificationHook extends AbstractSenderAuthIdenti @Override public HookResult doCheck(SMTPSession session, MaybeSender sender) { ExtendedSMTPSession nSession = (ExtendedSMTPSession) session; - if (nSession.verifyIdentity() == SMTPConfiguration.SenderVerificationMode.STRICT) { + if (!session.isRelayingAllowed() && !nSession.senderVerificationConfiguration().allowUnauthenticatedSender()) { + LOGGER.info("Authentication is required for sending email (sender: {})", sender.asString()); + return AUTH_REQUIRED; + } + if (nSession.senderVerificationConfiguration().mode() == SMTPConfiguration.SenderVerificationMode.STRICT) { return super.doCheck(session, sender); - } else if (nSession.verifyIdentity() == SMTPConfiguration.SenderVerificationMode.RELAXED) { - return doCheckRelaxed(session, sender); + } else if (nSession.senderVerificationConfiguration().mode() == SMTPConfiguration.SenderVerificationMode.RELAXED) { + return doCheckRelaxed(nSession, sender); } else { return HookResult.DECLINED; } @@ -140,8 +144,8 @@ public class SenderAuthIdentifyVerificationHook extends AbstractSenderAuthIdenti @Override public HookResult onMessage(SMTPSession session, Mail mail) { ExtendedSMTPSession nSession = (ExtendedSMTPSession) session; - boolean shouldCheck = nSession.verifyIdentity() == SMTPConfiguration.SenderVerificationMode.STRICT || - (nSession.verifyIdentity() == SMTPConfiguration.SenderVerificationMode.RELAXED && session.getUsername() != null); + boolean shouldCheck = nSession.senderVerificationConfiguration().mode() == SMTPConfiguration.SenderVerificationMode.STRICT || + (nSession.senderVerificationConfiguration().mode() == SMTPConfiguration.SenderVerificationMode.RELAXED && session.getUsername() != null); if (shouldCheck) { try { Address[] fromAddresses = mail.getMessage().getFrom(); diff --git a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java index 5b2d0e3987..e509ac384c 100644 --- a/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java +++ b/server/protocols/protocols-smtp/src/main/java/org/apache/james/smtpserver/netty/SMTPServer.java @@ -206,7 +206,7 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe private boolean addressBracketsEnforcement = true; - private SMTPConfiguration.SenderVerificationMode verifyIdentity; + private SMTPConfiguration.SenderVerificationConfiguration senderVerificationConfiguration; private DNSService dns; private String authorizedAddresses; @@ -267,7 +267,9 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe addressBracketsEnforcement = configuration.getBoolean("addressBracketsEnforcement", true); - verifyIdentity = SMTPConfiguration.SenderVerificationMode.parse(configuration.getString("verifyIdentity", "strict")); + senderVerificationConfiguration = new SMTPConfiguration.SenderVerificationConfiguration( + SMTPConfiguration.SenderVerificationMode.parse(configuration.getString("verifyIdentity", "strict")), + !configuration.getBoolean("auth.required", false)); disabledFeatures = ImmutableSet.copyOf(configuration.getStringArray("disabledFeatures")); } @@ -340,12 +342,8 @@ public class SMTPServer extends AbstractProtocolAsyncServer implements SMTPServe .orElse(true); } - /** - * Return true if the username and mail from must match for a authorized - * user - */ - public SenderVerificationMode verifyIdentity() { - return SMTPServer.this.verifyIdentity; + public SenderVerificationConfiguration senderVerificationConfiguration() { + return senderVerificationConfiguration; } @Override diff --git a/src/site/xdoc/server/config-smtp-lmtp.xml b/src/site/xdoc/server/config-smtp-lmtp.xml index 4ffdd2f049..2471d4c5a7 100644 --- a/src/site/xdoc/server/config-smtp-lmtp.xml +++ b/src/site/xdoc/server/config-smtp-lmtp.xml @@ -109,6 +109,15 @@ Please note that emails are only relayed if, and only if, the user did authenticate, or is in an authorized network, regardless of this option.</dd> + <dt><strong>auth.required</strong></dt> + <dd>Authentication is required to send emails. Adapted for submission ports.<br/> + + Note that if false (legacy value and default for backward compatibility) then unauthenticated senders are allowed but + limited by sender verification (prevent spoofing) and relaying limits (must be authenticated to relay).<br/> + + We encourage setting this value to true on submission ports (465 + 587).<br/> + + Please note that `authorizedAddresses` are considered authenticated.</dd> <dt><strong>auth.requireSSL</strong></dt> <dd>This is an optional tag, defaults to true. If true, authentication is not advertised via capabilities on unencrypted channels.</dd> --------------------------------------------------------------------- To unsubscribe, e-mail: [email protected] For additional commands, e-mail: [email protected]
