This is an automated email from the ASF dual-hosted git repository.
rcordier 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 2ab06c3f6d JAMES-4034 SMTP submission: validate FROM header (#2246)
2ab06c3f6d is described below
commit 2ab06c3f6daa427e4f5e2f7c5897ce51f12c6a55
Author: Benoit TELLIER <[email protected]>
AuthorDate: Fri May 17 09:12:56 2024 +0200
JAMES-4034 SMTP submission: validate FROM header (#2246)
---
...AbstractSenderAuthIdentifyVerificationHook.java | 2 +-
.../james/smtp/SmtpIdentityVerificationTest.java | 68 ++++++++++++++++++++++
.../SenderAuthIdentifyVerificationHook.java | 60 ++++++++++++++++++-
3 files changed, 128 insertions(+), 2 deletions(-)
diff --git
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/AbstractSenderAuthIdentifyVerificationHook.java
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/AbstractSenderAuthIdentifyVerificationHook.java
index edf54e4c5f..a79899fac9 100644
---
a/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/AbstractSenderAuthIdentifyVerificationHook.java
+++
b/protocols/smtp/src/main/java/org/apache/james/protocols/smtp/core/AbstractSenderAuthIdentifyVerificationHook.java
@@ -36,7 +36,7 @@ import com.google.common.base.Preconditions;
* Handler which check if the authenticated user is the same as the one used
as MAIL FROM
*/
public abstract class AbstractSenderAuthIdentifyVerificationHook implements
MailHook, RcptHook {
- private static final HookResult INVALID_AUTH = HookResult.builder()
+ protected static final HookResult INVALID_AUTH = HookResult.builder()
.hookReturnCode(HookReturnCode.deny())
.smtpReturnCode(SMTPRetCode.BAD_SEQUENCE)
.smtpDescription(DSNStatus.getStatus(DSNStatus.PERMANENT,
DSNStatus.SECURITY_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 54d14c72e3..1fa9874a6c 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
@@ -40,6 +40,8 @@ import org.junit.jupiter.api.Test;
import org.junit.jupiter.api.extension.RegisterExtension;
import org.junit.jupiter.api.io.TempDir;
+import com.google.common.collect.ImmutableList;
+
class SmtpIdentityVerificationTest {
private static final String ATTACKER_PASSWORD = "secret";
@@ -110,6 +112,72 @@ class SmtpIdentityVerificationTest {
.authenticate(USER, PASSWORD).sendMessage(USER, USER);
}
+ @Test
+ void spoofingAttemptsShouldBeRejectedInFromField(@TempDir File
temporaryFolder) throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: [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 spoofingInternalAddressAttemptsShouldBeRejectedInFromField(@TempDir
File temporaryFolder) throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ String message = """
+ FROM: [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 aliasShouldBeSupportedInFromField(@TempDir File temporaryFolder)
throws Exception {
+ createJamesServer(temporaryFolder, SmtpConfiguration.builder()
+ .requireAuthentication()
+ .verifyIdentity());
+
+ jamesServer.getProbe(DataProbeImpl.class)
+ .addUserAliasMapping("alias", DEFAULT_DOMAIN, USER);
+
+ String message = """
+ FROM: [email protected]\r
+ subject: test\r
+ \r
+ content\r
+ .\r
+ """;
+
+ messageSender.connect(LOCALHOST_IP,
jamesServer.getProbe(SmtpGuiceProbe.class).getSmtpPort())
+ .authenticate(USER, PASSWORD)
+ .sendMessageWithHeaders(USER, ImmutableList.of(USER), message);
+ }
+
@Test
void verifyIdentityShouldRejectNullSenderWHenAuthenticated(@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 690b2266b4..e4d53109a7 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,7 +18,13 @@
****************************************************************/
package org.apache.james.smtpserver;
+import java.util.stream.Stream;
+
import jakarta.inject.Inject;
+import jakarta.mail.Address;
+import jakarta.mail.MessagingException;
+import jakarta.mail.internet.AddressException;
+import jakarta.mail.internet.InternetAddress;
import org.apache.james.core.Domain;
import org.apache.james.core.MailAddress;
@@ -32,13 +38,15 @@ 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.StreamUtils;
+import org.apache.mailet.Mail;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Handler which check if the authenticated user is incorrect
*/
-public class SenderAuthIdentifyVerificationHook extends
AbstractSenderAuthIdentifyVerificationHook {
+public class SenderAuthIdentifyVerificationHook extends
AbstractSenderAuthIdentifyVerificationHook implements JamesMessageHook {
private static final Logger LOGGER =
LoggerFactory.getLogger(SenderAuthIdentifyVerificationHook.class);
private final DomainList domains;
@@ -90,4 +98,54 @@ public class SenderAuthIdentifyVerificationHook extends
AbstractSenderAuthIdenti
}
return allowed;
}
+
+ @Override
+ public HookResult onMessage(SMTPSession session, Mail mail) {
+ ExtendedSMTPSession nSession = (ExtendedSMTPSession) session;
+ if (nSession.verifyIdentity()) {
+ try {
+ return StreamUtils.ofNullable(mail.getMessage().getFrom())
+ .distinct()
+ .flatMap(address -> doCheckMessage(session, address))
+ .findFirst()
+ .orElse(HookResult.DECLINED);
+ } catch (MessagingException e) {
+ throw new RuntimeException(e);
+ }
+ } else {
+ return HookResult.DECLINED;
+ }
+ }
+
+ private Stream<HookResult> doCheckMessage(SMTPSession session, Address
from) {
+ if (fromDoesNotMatchAuthUser(session, from)) {
+ return Stream.of(INVALID_AUTH);
+ } else {
+ return Stream.empty();
+ }
+ }
+
+ 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));
+ } catch (AddressException e) {
+ // Never happens as valid InternetAddress are valid MailAddress
+ throw new RuntimeException(e);
+ }
+ }
+ return false;
+ }
+
+ private boolean fromMatchSessionUser(MailAddress from, SMTPSession
session) {
+ Username authUser = session.getUsername();
+ Username sender = getUser(from);
+ return isSenderAllowed(authUser, sender);
+ }
+
+ private boolean belongsToLocalDomain(MailAddress from) {
+ return isLocalDomain(from.getDomain());
+ }
}
---------------------------------------------------------------------
To unsubscribe, e-mail: [email protected]
For additional commands, e-mail: [email protected]