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 <btell...@linagora.com>
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 <rcord...@linagora.com>
---
 .../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: vic...@spoofed.info;\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: u...@james.org, vic...@spoofed.info;\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: vic...@spoofed.info;\r
+            subject: test\r
+            \r
+            content\r
+            .\r
+            """;
+
+        assertThatCode(() ->
+            messageSender.connect(LOCALHOST_IP, 
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+                .sendMessageWithHeaders("vic...@spoofed.info", 
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("vic...@spoofed.info", 
ImmutableList.of(USER), message))
+            .doesNotThrowAnyException();
+    }
+
+    @Test
+    void shouldAcceptGroupWhenOnlyUser(@TempDir File temporaryFolder) throws 
Exception {
+        createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+            .requireAuthentication()
+            .verifyIdentity());
+
+        String message = """
+            FROM: MyGroup: u...@james.org;\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: notifications-unsubscr...@james.apache.org
For additional commands, e-mail: notifications-h...@james.apache.org

Reply via email to