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
The following commit(s) were added to refs/heads/master by this push:
new b8fc8a0ab2 [FIX] Improve and test SenderAuthIdentifyVerificationHook
resiliency (weird inputs) (#2565)
b8fc8a0ab2 is described below
commit b8fc8a0ab2776ec3621f33f612020b2ee7f033e6
Author: Benoit TELLIER <[email protected]>
AuthorDate: Thu Dec 12 17:12:37 2024 +0100
[FIX] Improve and test SenderAuthIdentifyVerificationHook resiliency (weird
inputs) (#2565)
* [FIX] SenderAuthIdentifyVerificationHook: Do not interpret From when no
user
This prevents rejection of mails that parses as
InternetAddress but fails MailAddress validation.
* [FIX] SenderAuthIdentifyVerificationHook: reject invalid FROM only for
our users
* [FIX] SenderAuthIdentifyVerificationHook: handle groups
* [FIX] SenderAuthIdentifyVerificationHook: Fix typo
* [FIX] SenderAuthIdentifyVerificationHook covers new edge case
---------
Co-authored-by: Rene Cordier <[email protected]>
---
.../james/smtp/SmtpIdentityVerificationTest.java | 149 +++++++++++++++++++++
.../SenderAuthIdentifyVerificationHook.java | 32 ++++-
2 files changed, 175 insertions(+), 6 deletions(-)
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 60e3e3d64a..6a5059939a 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
@@ -184,6 +184,155 @@ class SmtpIdentityVerificationTest {
.hasMessageContaining("503 5.7.1 Incorrect Authentication for
Specified Email Address");
}
+ @Test
+ void spoofingAttemptsShouldBeRejectedInFromFieldWhenGroup(@TempDir File
temporaryFolder) throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: MyGroup: [email protected];\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ assertThatThrownBy(() ->
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .authenticate(USER, PASSWORD)
+ .sendMessageWithHeaders(USER, ImmutableList.of(USER), message))
+ .isInstanceOf(SMTPSendingException.class)
+ .hasMessageContaining("503 5.7.1 Incorrect Authentication for
Specified Email Address");
+ }
+
+ @Test
+ void
spoofingAttemptsShouldBeRejectedInFromFieldWhenGroupWithOtherUsers(@TempDir
File temporaryFolder) throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: MyGroup: [email protected], [email protected];\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ assertThatThrownBy(() ->
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .authenticate(USER, PASSWORD)
+ .sendMessageWithHeaders(USER, ImmutableList.of(USER), message))
+ .isInstanceOf(SMTPSendingException.class)
+ .hasMessageContaining("503 5.7.1 Incorrect Authentication for
Specified Email Address");
+ }
+
+ @Test
+ void shouldRejectEmptyGroups(@TempDir File temporaryFolder) throws
Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: MyGroup: ;\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ assertThatThrownBy(() ->
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .authenticate(USER, PASSWORD)
+ .sendMessageWithHeaders(USER, ImmutableList.of(USER), message))
+ .isInstanceOf(SMTPSendingException.class)
+ .hasMessageContaining("503 5.7.1 Incorrect Authentication for
Specified Email Address");
+ }
+
+ @Test
+ void shouldRejectInvalidWhenLocalUser(@TempDir File temporaryFolder)
throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: INVALID\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ assertThatThrownBy(() ->
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .authenticate(USER, PASSWORD)
+ .sendMessageWithHeaders(USER, ImmutableList.of(USER), message))
+ .isInstanceOf(SMTPSendingException.class)
+ .hasMessageContaining("503 5.7.1 Incorrect Authentication for
Specified Email Address");
+ }
+
+ @Test
+ void shouldAcceptGroupWhenUnauthenticated(@TempDir File temporaryFolder)
throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: MyGroup: [email protected];\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ assertThatCode(() ->
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .sendMessageWithHeaders("[email protected]",
ImmutableList.of(USER), message))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void shouldAcceptInvalidFromWhenUnauthenticated(@TempDir File
temporaryFolder) throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: INVALID\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ assertThatCode(() ->
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .sendMessageWithHeaders("[email protected]",
ImmutableList.of(USER), message))
+ .doesNotThrowAnyException();
+ }
+
+ @Test
+ void shouldAcceptGroupWhenOnlyUser(@TempDir File temporaryFolder) throws
Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: MyGroup: [email protected];\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ assertThatCode(() ->
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .authenticate(USER, PASSWORD)
+ .sendMessageWithHeaders(USER, ImmutableList.of(USER), message))
+ .doesNotThrowAnyException();
+ }
+
@Test
void errorsShouldBeIgnoredWhenUnAuthed(@TempDir File temporaryFolder)
throws Exception {
createJamesServer(temporaryFolder, SmtpConfiguration.builder()
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 a5a0bc4325..5f908ffa33 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
@@ -18,6 +18,7 @@
****************************************************************/
package org.apache.james.smtpserver;
+import java.util.Arrays;
import java.util.Optional;
import java.util.stream.Stream;
@@ -41,11 +42,13 @@ import org.apache.james.protocols.smtp.hook.HookResult;
import org.apache.james.rrt.api.CanSendFrom;
import org.apache.james.user.api.UsersRepository;
import org.apache.james.user.api.UsersRepositoryException;
+import org.apache.james.util.MemoizedSupplier;
import org.apache.james.util.StreamUtils;
import org.apache.mailet.Mail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
+import com.github.fge.lambdas.Throwing;
import com.google.common.net.InternetDomainName;
/**
@@ -148,7 +151,7 @@ public class SenderAuthIdentifyVerificationHook extends
AbstractSenderAuthIdenti
// Ignore invalid from header for relays
return HookResult.DECLINED;
} else {
- LOGGER.warn("Local user {} attempted to use an invalid
From header", e);
+ LOGGER.warn("Local user {} attempted to use an invalid
From header", session.getUsername(), e);
throw new RuntimeException(e);
}
}
@@ -168,17 +171,34 @@ public class SenderAuthIdentifyVerificationHook extends
AbstractSenderAuthIdenti
private boolean fromDoesNotMatchAuthUser(SMTPSession session, Address
from) {
if (from instanceof InternetAddress internetAddress) {
try {
- MailAddress mailAddress = new
MailAddress(internetAddress.getAddress());
- return session.getUsername() != null &&
- (!fromMatchSessionUser(mailAddress, session) ||
!belongsToLocalDomain(mailAddress));
+ if (internetAddress.isGroup()) {
+ boolean strict = true;
+ InternetAddress[] addressGroup =
internetAddress.getGroup(!strict);
+ if (session.getUsername() != null && addressGroup.length
== 0) {
+ return true;
+ }
+ return Arrays.stream(addressGroup)
+ .map(address -> fromDoesNotMatchAuthUser(session,
address))
+ .filter(b -> b)
+ .findAny()
+ .orElse(false);
+ }
+ return fromDoesNotMatchAuthUser(session, internetAddress);
} catch (AddressException e) {
- // Never happens as valid InternetAddress are valid MailAddress
- throw new RuntimeException(e);
+ LOGGER.warn("Local user {} attempted to use an invalid From
header", session.getUsername(), e);
+ return session.getUsername() != null; // Accept external
invalid form header, reject invalid from from our users.
}
}
return false;
}
+ private boolean fromDoesNotMatchAuthUser(SMTPSession session,
InternetAddress internetAddress) {
+ MemoizedSupplier<MailAddress> mailAddress =
MemoizedSupplier.of(Throwing.supplier(
+ () -> new
MailAddress(internetAddress.getAddress())).sneakyThrow());
+ return session.getUsername() != null &&
+ (!fromMatchSessionUser(mailAddress.get(), session) ||
!belongsToLocalDomain(mailAddress.get()));
+ }
+
private boolean fromMatchSessionUser(MailAddress from, SMTPSession
session) {
Username authUser = session.getUsername();
Username sender = getUser(from);
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]