Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
12 changes: 12 additions & 0 deletions docs/modules/servers/partials/configure/smtp.adoc
Original file line number Diff line number Diff line change
Expand Up @@ -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.

Expand Down Expand Up @@ -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.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

nitpick

Suggested change
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.
Please note that this parameter only intends to prevent spoofing, and still allows 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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -31,8 +31,9 @@ public LMTPConfigurationImpl() {
}

@Override
public SenderVerificationMode verifyIdentity() {
return SenderVerificationMode.DISABLED;
public SenderVerificationConfiguration senderVerificationConfiguration() {
boolean allowUnauthenticatedSender = true;
return new SenderVerificationConfiguration(SenderVerificationMode.DISABLED, allowUnauthenticatedSender);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -37,12 +37,15 @@
*
*/
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;
Expand Down Expand Up @@ -77,7 +80,7 @@ public static SenderVerificationMode parse(String value) {
*/
boolean isAuthAnnounced(String remoteIP, boolean tlsStarted);

SenderVerificationMode verifyIdentity();
SenderVerificationConfiguration senderVerificationConfiguration();

/**
* Returns whether the remote server needs to send a HELO/EHLO
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public SMTPConfigurationImpl() {
}

@Override
public SenderVerificationMode verifyIdentity() {
return SenderVerificationMode.STRICT;
public SenderVerificationConfiguration senderVerificationConfiguration() {
boolean allowUnauthenticatedSender = true;
return new SenderVerificationConfiguration(SenderVerificationMode.STRICT, allowUnauthenticatedSender);
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down Expand Up @@ -136,6 +137,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down
2 changes: 2 additions & 0 deletions server/apps/jpa-app/sample-configuration/smtpserver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down Expand Up @@ -133,6 +134,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down
2 changes: 2 additions & 0 deletions server/apps/memory-app/sample-configuration/smtpserver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down Expand Up @@ -133,6 +134,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down
2 changes: 2 additions & 0 deletions server/apps/postgres-app/sample-configuration/smtpserver.xml
Original file line number Diff line number Diff line change
Expand Up @@ -86,6 +86,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down Expand Up @@ -133,6 +134,7 @@
<announce>forUnauthorizedAddresses</announce>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
<required>true</required>
<!-- Sample OIDC configuration -->
<!--
<oidc>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,7 @@ public static class Builder {
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;
Expand All @@ -84,6 +85,7 @@ public Builder() {
verifyIndentity = Optional.empty();
maxMessageSize = Optional.empty();
bracketEnforcement = Optional.empty();
allowUnauthenticatedSender = Optional.empty();
additionalHooks = ImmutableList.builder();
}

Expand Down Expand Up @@ -123,6 +125,11 @@ public Builder verifyIdentity() {
return this;
}

public Builder forbidUnauthenticatedSenders() {
this.allowUnauthenticatedSender = Optional.of(false);
return this;
}

public Builder doNotVerifyIdentity() {
this.verifyIndentity = Optional.of(SMTPConfiguration.SenderVerificationMode.DISABLED);
return this;
Expand All @@ -148,6 +155,7 @@ public SmtpConfiguration build() {
authRequired.orElse(!AUTH_REQUIRED),
startTls.orElse(false),
bracketEnforcement.orElse(true),
allowUnauthenticatedSender.orElse(true),
verifyIndentity.orElse(SMTPConfiguration.SenderVerificationMode.DISABLED),
maxMessageSize.orElse(DEFAULT_DISABLED),
additionalHooks.build());
Expand All @@ -162,6 +170,7 @@ public static Builder builder() {
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;
Expand All @@ -170,13 +179,15 @@ private SmtpConfiguration(Optional<String> authorizedAddresses,
boolean authRequired,
boolean startTls,
boolean bracketEnforcement,
boolean allowUnauthenticatedSenders,
SMTPConfiguration.SenderVerificationMode verifyIndentity,
String maxMessageSize,
ImmutableList<HookConfigurationEntry> additionalHooks) {
this.authorizedAddresses = authorizedAddresses;
this.authRequired = authRequired;
this.bracketEnforcement = bracketEnforcement;
this.verifyIndentity = verifyIndentity;
this.allowUnauthenticatedSenders = allowUnauthenticatedSenders;
this.maxMessageSize = maxMessageSize;
this.startTls = startTls;
this.additionalHooks = additionalHooks;
Expand All @@ -189,6 +200,7 @@ public String serializeAsXml() throws IOException {
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);
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,7 @@
<authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses>
{{/hasAuthorizedAddresses}}
<auth>
<required>{{forbidUnauthenticatedSenders}}</required>
<requireSSL>false</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
</auth>
Expand Down Expand Up @@ -79,6 +80,7 @@
<authorizedAddresses>{{authorizedAddresses}}</authorizedAddresses>
{{/hasAuthorizedAddresses}}
<auth>
<required>{{forbidUnauthenticatedSenders}}</required>
<requireSSL>true</requireSSL>
<plainAuthEnabled>true</plainAuthEnabled>
</auth>
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,31 @@ void remoteUserCanSendEmailsToLocalUsers(@TempDir File temporaryFolder) throws E
.sendMessage("other@domain.tld", 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("other@domain.tld", 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("other@domain.tld", USER))
.isInstanceOf(SMTPSendingException.class)
.hasMessageContaining("530 5.7.1 Authentication Required");
}

@Test
void relaxedShouldAcceptEmailsFromMXWhenLocalUsers(@TempDir File temporaryFolder) throws Exception {
createJamesServer(temporaryFolder, SmtpConfiguration.builder()
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -102,10 +102,11 @@ public class LMTPConfigurationImpl extends LMTPConfiguration {
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
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -39,8 +39,9 @@ public ExtendedSMTPSession(SMTPConfiguration smtpConfiguration, ProtocolTranspor
this.smtpConfiguration = smtpConfiguration;
}

public SMTPConfiguration.SenderVerificationMode verifyIdentity() {
return smtpConfiguration.verifyIdentity();

public SMTPConfiguration.SenderVerificationConfiguration senderVerificationConfiguration() {
return smtpConfiguration.senderVerificationConfiguration();
}

@Override
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -74,10 +74,14 @@ public SenderAuthIdentifyVerificationHook(DomainList domains, UsersRepository us
@Override
public HookResult doCheck(SMTPSession session, MaybeSender sender) {
ExtendedSMTPSession nSession = (ExtendedSMTPSession) session;
if (nSession.verifyIdentity() == SMTPConfiguration.SenderVerificationMode.STRICT) {
if (!session.isRelayingAllowed() && !nSession.senderVerificationConfiguration().allowUnauthenticatedSender()) {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In this comment on the corresponding jira it was discussed adding a new defaultly loaded hook instead of changing the behaviour of an existing hook

did you change your mind ?

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

No I have some local work adapting this that apparently is not shared in the right place.
I will make sure this is adressed timely.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

CF #2941
(Sorry)

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;
}
Expand Down Expand Up @@ -140,8 +144,8 @@ protected boolean isSenderAllowed(Username connectedUser, Username sender) {
@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();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -206,7 +206,7 @@ public Optional<OidcSASLConfiguration> getSaslConfiguration() {

private boolean addressBracketsEnforcement = true;

private SMTPConfiguration.SenderVerificationMode verifyIdentity;
private SMTPConfiguration.SenderVerificationConfiguration senderVerificationConfiguration;

private DNSService dns;
private String authorizedAddresses;
Expand Down Expand Up @@ -267,7 +267,9 @@ public void doConfigure(HierarchicalConfiguration<ImmutableNode> configuration)

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"));
}
Expand Down Expand Up @@ -340,12 +342,8 @@ public boolean isAuthAnnounced(String remoteIP, boolean tlsStarted) {
.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
Expand Down
9 changes: 9 additions & 0 deletions src/site/xdoc/server/config-smtp-lmtp.xml
Original file line number Diff line number Diff line change
Expand Up @@ -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>
Expand Down