diff --git a/symmetric-core/src/main/java/org/jumpmind/symmetric/common/ServerConstants.java b/symmetric-core/src/main/java/org/jumpmind/symmetric/common/ServerConstants.java index 9b8f909862..56067da213 100644 --- a/symmetric-core/src/main/java/org/jumpmind/symmetric/common/ServerConstants.java +++ b/symmetric-core/src/main/java/org/jumpmind/symmetric/common/ServerConstants.java @@ -38,5 +38,9 @@ public class ServerConstants { public final static String JMX_HTTP_ENABLE = "jmx.http.enable"; public final static String JMX_HTTP_PORT = "jmx.http.port"; + + public static final String SERVER_ALLOW_DIR_LISTING = "server.allow.dir.list"; + public static final String SERVER_ALLOW_HTTP_METHODS = "server.allow.http.methods"; + public static final String SERVER_DISALLOW_HTTP_METHODS = "server.disallow.http.methods"; } \ No newline at end of file diff --git a/symmetric-server/src/main/java/org/jumpmind/symmetric/SymmetricWebServer.java b/symmetric-server/src/main/java/org/jumpmind/symmetric/SymmetricWebServer.java index 5373c3f5a8..587ef50190 100644 --- a/symmetric-server/src/main/java/org/jumpmind/symmetric/SymmetricWebServer.java +++ b/symmetric-server/src/main/java/org/jumpmind/symmetric/SymmetricWebServer.java @@ -33,18 +33,17 @@ import java.io.Serializable; import java.lang.management.ManagementFactory; import java.util.ArrayList; +import java.util.EnumSet; import java.util.Enumeration; import javax.management.Attribute; import javax.management.MBeanServer; import javax.management.MalformedObjectNameException; import javax.management.ObjectName; +import javax.servlet.DispatcherType; import javax.servlet.ServletContext; import javax.servlet.http.HttpServletRequest; -import mx4j.tools.adaptor.http.HttpAdaptor; -import mx4j.tools.adaptor.http.XSLTProcessor; - import org.apache.commons.lang.StringUtils; import org.eclipse.jetty.http.HttpVersion; import org.eclipse.jetty.security.ConstraintMapping; @@ -61,6 +60,7 @@ import org.eclipse.jetty.server.session.AbstractSession; import org.eclipse.jetty.server.session.HashSessionManager; import org.eclipse.jetty.server.session.HashedSession; +import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.util.security.Constraint; import org.eclipse.jetty.util.security.Password; import org.eclipse.jetty.util.ssl.SslContextFactory; @@ -73,6 +73,7 @@ import org.jumpmind.symmetric.common.ServerConstants; import org.jumpmind.symmetric.common.SystemConstants; import org.jumpmind.symmetric.transport.TransportManagerFactory; +import org.jumpmind.symmetric.web.HttpMethodFilter; import org.jumpmind.symmetric.web.ServletUtils; import org.jumpmind.symmetric.web.SymmetricEngineHolder; import org.jumpmind.symmetric.web.WebConstants; @@ -83,6 +84,9 @@ import org.springframework.web.context.WebApplicationContext; import org.springframework.web.context.support.WebApplicationContextUtils; +import mx4j.tools.adaptor.http.HttpAdaptor; +import mx4j.tools.adaptor.http.XSLTProcessor; + /** * Start up SymmetricDS through an embedded Jetty instance. * @@ -149,6 +153,12 @@ public enum Mode { protected String httpSslVerifiedServerNames = "all"; + protected String allowDirListing = "false"; + + protected String disallowedMethods = "OPTIONS"; + + protected String allowedMethods = ""; + protected boolean allowSelfSignedCerts = true; public SymmetricWebServer() { @@ -203,7 +213,9 @@ protected void initFromProperties() { System.getProperty(ServerConstants.HTTPS_VERIFIED_SERVERS, httpSslVerifiedServerNames)); allowSelfSignedCerts = serverProperties.is(ServerConstants.HTTPS_ALLOW_SELF_SIGNED_CERTS, Boolean.parseBoolean(System.getProperty(ServerConstants.HTTPS_ALLOW_SELF_SIGNED_CERTS, "" + allowSelfSignedCerts))); - + allowDirListing = serverProperties.get(ServerConstants.SERVER_ALLOW_DIR_LISTING, "false"); + allowedMethods = serverProperties.get(ServerConstants.SERVER_ALLOW_HTTP_METHODS, ""); + disallowedMethods = serverProperties.get(ServerConstants.SERVER_DISALLOW_HTTP_METHODS, "OPTIONS"); } public SymmetricWebServer start(int httpPort, int jmxPort, String propertiesUrl) throws Exception { @@ -257,11 +269,17 @@ public SymmetricWebServer start(int httpPort, int securePort, int httpJmxPort, M webapp.setContextPath(webHome); webapp.setWar(webAppDir); webapp.setResourceBase(webAppDir); - // webapp.addServlet(DefaultServlet.class, "/*"); + + webapp.setInitParameter("org.eclipse.jetty.servlet.Default.dirAllowed", allowDirListing); SessionManager sm = new SessionManager(); webapp.getSessionHandler().setSessionManager(sm); + FilterHolder filterHolder = new FilterHolder(HttpMethodFilter.class); + filterHolder.setInitParameter("server.allow.http.methods", allowedMethods); + filterHolder.setInitParameter("server.disallow.http.methods", disallowedMethods); + webapp.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); + webapp.getServletContext().getContextHandler() .setMaxFormContentSize(Integer.parseInt(System.getProperty("org.eclipse.jetty.server.Request.maxFormContentSize", "800000"))); webapp.getServletContext().getContextHandler() @@ -388,25 +406,25 @@ protected Connector[] getConnectors(Server server, int port, int securePort, Mod /* Prevent POODLE attack */ String ignoredProtocols = System.getProperty(SecurityConstants.SYSPROP_SSL_IGNORE_PROTOCOLS); if (ignoredProtocols != null && ignoredProtocols.length() > 0) { - String[] protocols = ignoredProtocols.split(","); - sslConnectorFactory.addExcludeProtocols(protocols); + String[] protocols = ignoredProtocols.split(","); + sslConnectorFactory.addExcludeProtocols(protocols); } else { - sslConnectorFactory.addExcludeProtocols("SSLv3"); + sslConnectorFactory.addExcludeProtocols("SSLv3"); } - + String ignoredCiphers = System.getProperty(SecurityConstants.SYSPROP_SSL_IGNORE_CIPHERS); if (ignoredCiphers != null && ignoredCiphers.length() > 0) { - String[] ciphers = ignoredCiphers.split(","); - sslConnectorFactory.addExcludeCipherSuites(ciphers); + String[] ciphers = ignoredCiphers.split(","); + sslConnectorFactory.addExcludeCipherSuites(ciphers); } - + sslConnectorFactory.setCertAlias(System.getProperty(SecurityConstants.SYSPROP_KEYSTORE_CERT_ALIAS, SecurityConstants.ALIAS_SYM_PRIVATE_KEY)); sslConnectorFactory.setKeyStore(securityService.getKeyStore()); sslConnectorFactory.setTrustStore(securityService.getTrustStore()); - + HttpConfiguration httpsConfig = new HttpConfiguration(httpConfig); httpsConfig.addCustomizer(new SecureRequestCustomizer()); @@ -593,9 +611,9 @@ public SessionManager() { setLazyLoad(true); setDeleteUnrestorableSessions(true); setSessionCookie(getSessionCookie() + (httpPort > 0 ? httpPort - : httpsPort)); + : httpsPort)); } - + @Override protected AbstractSession newSession(HttpServletRequest request) { return new Session(this, request); diff --git a/symmetric-server/src/main/java/org/jumpmind/symmetric/web/HttpMethodFilter.java b/symmetric-server/src/main/java/org/jumpmind/symmetric/web/HttpMethodFilter.java new file mode 100644 index 0000000000..6451de16bb --- /dev/null +++ b/symmetric-server/src/main/java/org/jumpmind/symmetric/web/HttpMethodFilter.java @@ -0,0 +1,87 @@ +/** + * Licensed to JumpMind Inc under one or more contributor + * license agreements. See the NOTICE file distributed + * with this work for additional information regarding + * copyright ownership. JumpMind Inc licenses this file + * to you under the GNU General Public License, version 3.0 (GPLv3) + * (the "License"); you may not use this file except in compliance + * with the License. + * + * You should have received a copy of the GNU General Public License, + * version 3.0 (GPLv3) along with this library; if not, see + * . + * + * Unless required by applicable law or agreed to in writing, + * software distributed under the License is distributed on an + * "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY + * KIND, either express or implied. See the License for the + * specific language governing permissions and limitations + * under the License. + */ +package org.jumpmind.symmetric.web; + +import java.io.IOException; +import java.util.HashSet; +import java.util.Set; + +import javax.servlet.Filter; +import javax.servlet.FilterChain; +import javax.servlet.FilterConfig; +import javax.servlet.ServletException; +import javax.servlet.ServletRequest; +import javax.servlet.ServletResponse; +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpServletResponse; + +import org.apache.commons.lang.StringUtils; + +public class HttpMethodFilter implements Filter { + + private Set allowedMethods = new HashSet(); + private Set disallowedMethods = new HashSet(); + + @Override + public void init(FilterConfig filterConfig) throws ServletException { + String allowMethodsConfig = filterConfig.getInitParameter("server.allow.http.methods"); + loadMethods(allowMethodsConfig, allowedMethods); + String disallowMethodsConfig = filterConfig.getInitParameter("server.disallow.http.methods"); + loadMethods(disallowMethodsConfig, disallowedMethods); + } + + @Override + public void doFilter(ServletRequest request, ServletResponse response, + FilterChain filterChain) throws IOException, ServletException { + HttpServletRequest httpRequest = (HttpServletRequest) request; + String method = httpRequest.getMethod().toUpperCase(); + + if (disallowedMethods.contains(method)) { + forbid(method, request, response); + } else if (!allowedMethods.isEmpty() && !allowedMethods.contains(method)) { + forbid(method, request, response); + } else { + filterChain.doFilter(request, response); + } + } + + protected void forbid(String method, ServletRequest request, ServletResponse response) throws IOException { + HttpServletResponse httpResponse = (HttpServletResponse)response; + httpResponse.sendError(HttpServletResponse.SC_FORBIDDEN, "Method " + method + " is not allowed."); + } + + protected void loadMethods(String configuredValue, Set methods) { + if (!StringUtils.isEmpty(configuredValue)) { + String[] methodsSplit = configuredValue.split(","); + for (String method : methodsSplit) { + if (!StringUtils.isEmpty(method)) { + methods.add(method.toUpperCase()); + } + } + } + } + + @Override + public void destroy() { + // Empty. + } + +}