diff --git a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java index f6afa62ea..35ddddeaa 100644 --- a/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java +++ b/app/common/src/main/java/stirling/software/common/model/ApplicationProperties.java @@ -574,6 +574,16 @@ public class ApplicationProperties { private String username; @ToString.Exclude private String password; private String from; + // STARTTLS upgrades a plain SMTP connection to TLS after connecting (RFC 3207) + private Boolean startTlsEnable = true; + private Boolean startTlsRequired; + // SSL/TLS wrapper for implicit TLS (typically port 465) + private Boolean sslEnable; + // Hostnames or patterns (e.g., "smtp.example.com" or "*") to trust for TLS certificates; + // defaults to "*" (trust all) when not set + private String sslTrust; + // Enables hostname verification for TLS connections + private Boolean sslCheckServerIdentity; } @Data diff --git a/app/core/src/main/resources/settings.yml.template b/app/core/src/main/resources/settings.yml.template index 4c2ec003e..5a50ef903 100644 --- a/app/core/src/main/resources/settings.yml.template +++ b/app/core/src/main/resources/settings.yml.template @@ -104,6 +104,11 @@ mail: username: '' # SMTP server username password: '' # SMTP server password from: '' # sender email address + startTlsEnable: true # enable STARTTLS (explicit TLS upgrade after connecting) when supported by the SMTP server + startTlsRequired: false # require STARTTLS; connection fails if the upgrade command is not supported + sslEnable: false # enable SSL/TLS wrapper for implicit TLS (typically used with port 465) + sslTrust: '' # optional trusted host override, e.g. "smtp.example.com" or "*"; defaults to "*" (trust all) when empty + sslCheckServerIdentity: false # enable hostname verification when using SSL/TLS legal: termsAndConditions: https://www.stirling.com/legal/terms-of-service # URL to the terms and conditions of your application (e.g. https://example.com/terms). Empty string to disable or filename to load from local file in static folder diff --git a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java index 6a565cade..6538b7ee9 100644 --- a/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java +++ b/app/proprietary/src/main/java/stirling/software/proprietary/security/configuration/MailConfig.java @@ -33,7 +33,8 @@ public class MailConfig { // Creates a new instance of JavaMailSenderImpl, which is a Spring implementation JavaMailSenderImpl mailSender = new JavaMailSenderImpl(); - mailSender.setHost(mailProperties.getHost()); + String host = mailProperties.getHost(); + mailSender.setHost(host); mailSender.setPort(mailProperties.getPort()); mailSender.setDefaultEncoding("UTF-8"); @@ -70,8 +71,32 @@ public class MailConfig { log.info("SMTP authentication disabled - no credentials provided"); } + boolean startTlsEnabled = + mailProperties.getStartTlsEnable() == null || mailProperties.getStartTlsEnable(); // Enables STARTTLS to encrypt the connection if supported by the SMTP server - props.put("mail.smtp.starttls.enable", "true"); + props.put("mail.smtp.starttls.enable", Boolean.toString(startTlsEnabled)); + if (mailProperties.getStartTlsRequired() != null) { + props.put( + "mail.smtp.starttls.required", mailProperties.getStartTlsRequired().toString()); + } + + if (mailProperties.getSslEnable() != null) { + props.put("mail.smtp.ssl.enable", mailProperties.getSslEnable().toString()); + } + + // Trust the configured host to allow STARTTLS with self-signed certificates + String sslTrust = mailProperties.getSslTrust(); + if (sslTrust == null || sslTrust.trim().isEmpty()) { + sslTrust = "*"; + } + if (sslTrust != null && !sslTrust.trim().isEmpty()) { + props.put("mail.smtp.ssl.trust", sslTrust); + } + if (mailProperties.getSslCheckServerIdentity() != null) { + props.put( + "mail.smtp.ssl.checkserveridentity", + mailProperties.getSslCheckServerIdentity().toString()); + } // Returns the configured mail sender, ready to send emails return mailSender; diff --git a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java index 3db3493f4..5df56f8ef 100644 --- a/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java +++ b/app/proprietary/src/test/java/stirling/software/proprietary/security/service/MailConfigTest.java @@ -27,6 +27,11 @@ class MailConfigTest { when(mailProps.getPort()).thenReturn(587); when(mailProps.getUsername()).thenReturn("user@example.com"); when(mailProps.getPassword()).thenReturn("password"); + when(mailProps.getStartTlsEnable()).thenReturn(null); + when(mailProps.getStartTlsRequired()).thenReturn(null); + when(mailProps.getSslEnable()).thenReturn(null); + when(mailProps.getSslTrust()).thenReturn(null); + when(mailProps.getSslCheckServerIdentity()).thenReturn(null); } @Test @@ -50,6 +55,32 @@ class MailConfigTest { () -> assertEquals("password", impl.getPassword()), () -> assertEquals("UTF-8", impl.getDefaultEncoding()), () -> assertEquals("true", props.getProperty("mail.smtp.auth")), - () -> assertEquals("true", props.getProperty("mail.smtp.starttls.enable"))); + () -> assertEquals("true", props.getProperty("mail.smtp.starttls.enable")), + () -> assertEquals(null, props.getProperty("mail.smtp.starttls.required")), + () -> assertEquals(null, props.getProperty("mail.smtp.ssl.enable")), + () -> assertEquals("*", props.getProperty("mail.smtp.ssl.trust"))); + } + + @Test + void shouldRespectExplicitTlsOverrides() { + ApplicationProperties appProps = mock(ApplicationProperties.class); + when(mailProps.getStartTlsEnable()).thenReturn(false); + when(mailProps.getStartTlsRequired()).thenReturn(true); + when(mailProps.getSslEnable()).thenReturn(true); + when(mailProps.getSslTrust()).thenReturn("*"); + when(mailProps.getSslCheckServerIdentity()).thenReturn(true); + when(appProps.getMail()).thenReturn(mailProps); + + MailConfig config = new MailConfig(appProps); + JavaMailSenderImpl impl = (JavaMailSenderImpl) config.javaMailSender(); + + Properties props = impl.getJavaMailProperties(); + + assertAll( + () -> assertEquals("false", props.getProperty("mail.smtp.starttls.enable")), + () -> assertEquals("true", props.getProperty("mail.smtp.starttls.required")), + () -> assertEquals("true", props.getProperty("mail.smtp.ssl.enable")), + () -> assertEquals("*", props.getProperty("mail.smtp.ssl.trust")), + () -> assertEquals("true", props.getProperty("mail.smtp.ssl.checkserveridentity"))); } }