diff --git a/server/configs/application.properties b/server/configs/application.properties index 365ce22ce9..74fa7b0a96 100644 --- a/server/configs/application.properties +++ b/server/configs/application.properties @@ -81,6 +81,9 @@ mail.smtpUser=@@smtpUser@@ #mail.smtpStartTlsEnable=@@smtpStartTlsEnable@@ #mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@ #mail.smtpAuth=@@smtpAuth@@ +#mail.smtpConnectionTimeout=@@smtpConnectionTimeout@@ +#mail.smtpTimeout=@@smtpTimeout@@ +#mail.smtpWriteTimeout=@@smtpWriteTimeout@@ ## Microsoft Graph configuration - if using this, comment out SMTP configuration #mail.graph.tenantId=@@graphTenantId@@ diff --git a/server/configs/webapps/embedded/config/application.properties b/server/configs/webapps/embedded/config/application.properties index 54258056d6..0c9cb6dc05 100644 --- a/server/configs/webapps/embedded/config/application.properties +++ b/server/configs/webapps/embedded/config/application.properties @@ -78,6 +78,9 @@ mail.smtpUser=Anonymous #mail.smtpStartTlsEnable=@@smtpStartTlsEnable@@ #mail.smtpSocketFactoryClass=@@smtpSocketFactoryClass@@ #mail.smtpAuth=@@smtpAuth@@ +#mail.smtpConnectionTimeout=@@smtpConnectionTimeout@@ +#mail.smtpTimeout=@@smtpTimeout@@ +#mail.smtpWriteTimeout=@@smtpWriteTimeout@@ #################################################################################### ## Microsoft Graph Configuration - if using this, comment out SMTP configuration diff --git a/server/embedded/src/org/labkey/embedded/LabKeyServer.java b/server/embedded/src/org/labkey/embedded/LabKeyServer.java index 1c49bcd268..edfe189a34 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyServer.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyServer.java @@ -41,11 +41,7 @@ public class LabKeyServer private static final String TERMINATE_ON_STARTUP_FAILURE = "terminateOnStartupFailure"; private static final String JARS_TO_SKIP = "tomcat.util.scan.StandardJarScanFilter.jarsToSkip"; private static final String JARS_TO_SCAN = "tomcat.util.scan.StandardJarScanFilter.jarsToScan"; - private static final String SERVER_GUID = "serverGUID"; - public static final String SERVER_GUID_PARAMETER_NAME = "org.labkey.mothership." + SERVER_GUID; - public static final String SERVER_SSL_KEYSTORE = "org.labkey.serverSslKeystore"; - public static final String CUSTOM_LOG4J_CONFIG = "org.labkey.customLog4JConfig"; - public static final String CORS_PREFIX = "cors."; + static final String MAX_TOTAL_CONNECTIONS_DEFAULT = "50"; static final String MAX_IDLE_DEFAULT = "10"; static final String MAX_WAIT_MILLIS_DEFAULT = "120000"; @@ -108,83 +104,88 @@ public static void main(String[] args) """; application.setDefaultProperties(new HashMap<>() - {{ + {{ // GitHub Issue 796: JSON logging stopped after Tomcat/Spring update - // Propagate log4j configuration to Spring Boot config, which is necessary with Spring Boot 4.x - String log4JConfig = System.getProperty("log4j.configurationFile"); - if (log4JConfig != null) - { - String[] log4JConfigParts = log4JConfig.split(","); - if (log4JConfigParts.length > 0) - { - if ("log4j2.xml".equals(log4JConfigParts[0])) - { - // Assume this is the one packaged with our embedded build and on the classpath - put("logging.config", "classpath:log4j2.xml"); - } - else - { - put("logging.config", log4JConfigParts[0]); - } - if (log4JConfigParts.length > 1) - { + // Propagate log4j configuration to Spring Boot config, which is necessary with Spring Boot 4.x + String log4JConfig = System.getProperty("log4j.configurationFile"); + if (log4JConfig != null) + { + String[] log4JConfigParts = log4JConfig.split(","); + if (log4JConfigParts.length > 0) + { + if ("log4j2.xml".equals(log4JConfigParts[0])) + { + // Assume this is the one packaged with our embedded build and on the classpath + put("logging.config", "classpath:log4j2.xml"); + } + else + { + put("logging.config", log4JConfigParts[0]); + } + if (log4JConfigParts.length > 1) + { put("logging.log4j2.config.override", String.join(",", Arrays.asList(log4JConfigParts).subList(1, log4JConfigParts.length))); - } - } - } + } + } + } - put("server.tomcat.basedir", "."); - put("server.tomcat.accesslog.directory", logHome); + put("server.tomcat.basedir", "."); + put("server.tomcat.accesslog.directory", logHome); - // Boost limits imposed by Tomcat v10.1.42 - put("server.tomcat.max-part-count", 500); - put("server.tomcat.max-part-header-size", 1024); // GitHub Issue 161: LKS insert forms can't handle long file field names - put("server.tomcat.max-connections", 250); + // Boost limits imposed by Tomcat v10.1.42 + put("server.tomcat.max-part-count", 500); + put("server.tomcat.max-part-header-size", 1024); // GitHub Issue 161: LKS insert forms can't handle long file field names + put("server.tomcat.max-connections", 250); // Boost limit back to Tomcat 10 level - put("server.tomcat.max-parameter-count", 10_000); - - // Enable HTTP compression for response content - put("server.compression.enabled", "true"); - // Spring Boot compresses HTML, JSON and other types by default, but not TSV, CSV, or SVG. - // We have to duplicate the defaults and add those types - put("server.compression.mime-types", "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml,text/tab-separated-values,text/csv,image/svg+xml"); - - put("server.tomcat.accesslog.enabled", "true"); - put("server.tomcat.accesslog.pattern", "%h %l %u %t \"%r\" %s %b %D %S %I \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i"); - put("jsonaccesslog.pattern", "%h %t %m %U %s %b %D %S \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i"); - - // Issue 52415: Omit stack traces from Tomcat error pages by default, but propagate error messages - put("server.error.include-stacktrace", "never"); - put("server.error.include-message", "always"); - - put("csp.enforce", enforceCsp); - put("csp.report", reportCsp); - - // GitHub Issue 692: Stop using CBC in HTTPS ciphers - // These settings configure HTTPS. Admins must opt in with additional settings - // in application.properties, like the key store. Without those other settings, - // HTTP-only startup fails unless "server.ssl.enabled" is explicitly set to false here - put("server.ssl.enabled", "false"); - put("server.ssl.protocol", "TLS"); - put("server.ssl.enabled-protocols", "TLSv1.3,TLSv1.2"); - // Use explicit JSSE cipher suite names to avoid CBC-mode suites - put("server.ssl.ciphers", - String.join(",", - // TLS 1.3 - "TLS_AES_256_GCM_SHA384", - "TLS_AES_128_GCM_SHA256", - "TLS_CHACHA20_POLY1305_SHA256", - // TLS 1.2 (AEAD only, no CBC) - "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", - "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", - "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", - "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" - ) - ); - put("server.ssl.use-cipher-suites-order", "true"); - }} + put("server.tomcat.max-parameter-count", 10_000); + + // Enable HTTP compression for response content + put("server.compression.enabled", "true"); + // Spring Boot compresses HTML, JSON and other types by default, but not TSV, CSV, or SVG. + // We have to duplicate the defaults and add those types + put("server.compression.mime-types", "text/html,text/xml,text/plain,text/css,text/javascript,application/javascript,application/json,application/xml,text/tab-separated-values,text/csv,image/svg+xml"); + + put("server.tomcat.accesslog.enabled", "true"); + put("server.tomcat.accesslog.pattern", "%h %l %u %t \"%r\" %s %b %D %S %I \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i"); + put("jsonaccesslog.pattern", "%h %t %m %U %s %b %D %S \"%{Referer}i\" \"%{User-Agent}i\" %{LABKEY.username}s %{X-Forwarded-For}i"); + + // Issue 52415: Omit stack traces from Tomcat error pages by default, but propagate error messages + put("server.error.include-stacktrace", "never"); + put("server.error.include-message", "always"); + + put("csp.enforce", enforceCsp); + put("csp.report", reportCsp); + + // GitHub Issue 692: Stop using CBC in HTTPS ciphers + // These settings configure HTTPS. Admins must opt in with additional settings + // in application.properties, like the key store. Without those other settings, + // HTTP-only startup fails unless "server.ssl.enabled" is explicitly set to false here + put("server.ssl.enabled", "false"); + put("server.ssl.protocol", "TLS"); + put("server.ssl.enabled-protocols", "TLSv1.3,TLSv1.2"); + // Use explicit JSSE cipher suite names to avoid CBC-mode suites + put("server.ssl.ciphers", + String.join(",", + // TLS 1.3 + "TLS_AES_256_GCM_SHA384", + "TLS_AES_128_GCM_SHA256", + "TLS_CHACHA20_POLY1305_SHA256", + // TLS 1.2 (AEAD only, no CBC) + "TLS_ECDHE_ECDSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_ECDSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_ECDSA_WITH_CHACHA20_POLY1305_SHA256", + "TLS_ECDHE_RSA_WITH_AES_256_GCM_SHA384", + "TLS_ECDHE_RSA_WITH_AES_128_GCM_SHA256", + "TLS_ECDHE_RSA_WITH_CHACHA20_POLY1305_SHA256" + ) + ); + put("server.ssl.use-cipher-suites-order", "true"); + + // GitHub Issue #1416 - default values for SMTP timeouts + put("mail.smtp.connectiontimeout", 10 * 1000); + put("mail.smtp.timeout", 60 * 1000); + put("mail.smtp.writetimeout", 60 * 1000); + }} ); application.setBannerMode(Banner.Mode.OFF); application.run(args); @@ -818,6 +819,9 @@ public static class MailProperties private String smtpStartTlsEnable; private String smtpSocketFactoryClass; private String smtpAuth; + private String smtpConnectionTimeout = "10000"; // Default to 10 seconds + private String smtpTimeout = "60000"; // Default to 60 seconds + private String smtpWriteTimeout = "60000"; // Default to 60 seconds public String getSmtpHost() { @@ -898,6 +902,36 @@ public void setSmtpAuth(String smtpAuth) { this.smtpAuth = smtpAuth; } + + public String getSmtpConnectionTimeout() + { + return smtpConnectionTimeout; + } + + public void setSmtpConnectionTimeout(String smtpConnectionTimeout) + { + this.smtpConnectionTimeout = smtpConnectionTimeout; + } + + public String getSmtpTimeout() + { + return smtpTimeout; + } + + public void setSmtpTimeout(String smtpTimeout) + { + this.smtpTimeout = smtpTimeout; + } + + public String getSmtpWriteTimeout() + { + return smtpWriteTimeout; + } + + public void setSmtpWriteTimeout(String smtpWriteTimeout) + { + this.smtpWriteTimeout = smtpWriteTimeout; + } } @Configuration diff --git a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java index 8eb8942e16..762b4f9522 100644 --- a/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java +++ b/server/embedded/src/org/labkey/embedded/LabKeyTomcatServletWebServerFactory.java @@ -29,6 +29,7 @@ import org.apache.logging.log4j.Logger; import org.apache.tomcat.util.descriptor.web.ContextResource; import org.labkey.bootstrap.ConfigException; +import org.labkey.embedded.LabKeyServer.MailProperties; import org.springframework.boot.tomcat.TomcatWebServer; import org.springframework.boot.tomcat.servlet.TomcatServletWebServerFactory; import org.springframework.boot.web.servlet.ServletContextInitializer; @@ -39,14 +40,15 @@ import java.util.Map; import java.util.Objects; -import static org.labkey.embedded.LabKeyServer.CORS_PREFIX; -import static org.labkey.embedded.LabKeyServer.CUSTOM_LOG4J_CONFIG; -import static org.labkey.embedded.LabKeyServer.SERVER_GUID_PARAMETER_NAME; -import static org.labkey.embedded.LabKeyServer.SERVER_SSL_KEYSTORE; - class LabKeyTomcatServletWebServerFactory extends TomcatServletWebServerFactory { private static final Logger LOG = LogManager.getLogger(LabKeyTomcatServletWebServerFactory.class); + private static final String SERVER_GUID = "serverGUID"; + private static final String SERVER_GUID_PARAMETER_NAME = "org.labkey.mothership." + SERVER_GUID; + private static final String SERVER_SSL_KEYSTORE = "org.labkey.serverSslKeystore"; + private static final String CUSTOM_LOG4J_CONFIG = "org.labkey.customLog4JConfig"; + private static final String CORS_PREFIX = "cors."; + private final LabKeyServer _server; public LabKeyTomcatServletWebServerFactory(LabKeyServer server) @@ -423,39 +425,26 @@ private String getPropValue(Map propValues, Integer resourceKey private void addSmtpProperties(StandardContext context) { // Get session/mail properties - LabKeyServer.MailProperties mailProps = _server.smtpSource(); + MailProperties mailProps = _server.smtpSource(); + + addSmtpProperty(context, "host", mailProps.getSmtpHost()); + addSmtpProperty(context, "user", mailProps.getSmtpUser()); + addSmtpProperty(context, "port", mailProps.getSmtpPort()); + addSmtpProperty(context, "from", mailProps.getSmtpFrom()); + addSmtpProperty(context, "password", mailProps.getSmtpPassword()); + addSmtpProperty(context, "starttls.enable", mailProps.getSmtpStartTlsEnable()); + addSmtpProperty(context, "socketFactory.class", mailProps.getSmtpSocketFactoryClass()); + addSmtpProperty(context, "auth", mailProps.getSmtpAuth()); + addSmtpProperty(context, "connectiontimeout", mailProps.getSmtpConnectionTimeout()); + addSmtpProperty(context, "timeout", mailProps.getSmtpTimeout()); + addSmtpProperty(context, "writetimeout", mailProps.getSmtpWriteTimeout()); + } - if (mailProps.getSmtpHost() != null) - { - context.addParameter("mail.smtp.host", mailProps.getSmtpHost()); - } - if (mailProps.getSmtpUser() != null) - { - context.addParameter("mail.smtp.user", mailProps.getSmtpUser()); - } - if (mailProps.getSmtpPort() != null) - { - context.addParameter("mail.smtp.port", mailProps.getSmtpPort()); - } - if (mailProps.getSmtpFrom() != null) - { - context.addParameter("mail.smtp.from", mailProps.getSmtpFrom()); - } - if (mailProps.getSmtpPassword() != null) - { - context.addParameter("mail.smtp.password", mailProps.getSmtpPassword()); - } - if (mailProps.getSmtpStartTlsEnable() != null) - { - context.addParameter("mail.smtp.starttls.enable", mailProps.getSmtpStartTlsEnable()); - } - if (mailProps.getSmtpSocketFactoryClass() != null) - { - context.addParameter("mail.smtp.socketFactory.class", mailProps.getSmtpSocketFactoryClass()); - } - if (mailProps.getSmtpAuth() != null) + private void addSmtpProperty(StandardContext context, String name, String value) + { + if (value != null) { - context.addParameter("mail.smtp.auth", mailProps.getSmtpAuth()); + context.addParameter("mail.smtp." + name, value); } }