Commit 938c0f26 authored by Guus der Kinderen's avatar Guus der Kinderen

OF-946: Merge SSLConfig.Type with Purpose

parent 97f7cf3f
This source diff could not be displayed because it is too large. You can view the blob instead.
...@@ -63,14 +63,14 @@ public class SSLProtocolSocketFactory implements SecureProtocolSocketFactory { ...@@ -63,14 +63,14 @@ public class SSLProtocolSocketFactory implements SecureProtocolSocketFactory {
private SSLContext createSSLContext(String host) { private SSLContext createSSLContext(String host) {
try { try {
final SSLContext context = SSLConfig.getSSLContext( SSLConfig.Type.ADMIN ); final SSLContext context = SSLConfig.getSSLContext( Purpose.ADMIN );
context.init( context.init(
null, null,
new TrustManager[] { new TrustManager[] {
new ClearspaceX509TrustManager( new ClearspaceX509TrustManager(
host, host,
manager.getProperties(), manager.getProperties(),
SSLConfig.getStore( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ) SSLConfig.getTrustStore( Purpose.ADMIN ) )
}, },
null); null);
return context; return context;
......
/** /**
* $Revision: 3034 $ * $Revision: 3034 $
* $Date: 2005-11-04 21:02:33 -0300 (Fri, 04 Nov 2005) $ * $Date: 2005-11-04 21:02:33 -0300 (Fri, 04 Nov 2005) $
* *
* Copyright (C) 2004-2008 Jive Software. All rights reserved. * Copyright (C) 2004-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.container; package org.jivesoftware.openfire.container;
import java.io.File; import java.io.File;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.List; import java.util.List;
import org.apache.tomcat.InstanceManager; import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager; import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer; import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.plus.annotation.ContainerInitializer; import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector; import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.webapp.WebAppContext;
import org.jivesoftware.openfire.JMXManager; import org.jivesoftware.openfire.JMXManager;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig; import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.keystore.Purpose; import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.CertificateStoreConfig; import org.jivesoftware.openfire.keystore.CertificateStoreConfig;
import org.jivesoftware.openfire.net.SSLConfig; import org.jivesoftware.openfire.keystore.TrustStoreConfig;
import org.jivesoftware.util.CertificateEventListener; import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.util.CertificateManager; import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.LocaleUtils;
import org.slf4j.Logger; import org.jivesoftware.util.StringUtils;
import org.slf4j.LoggerFactory; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* The admin console plugin. It starts a Jetty instance on the configured /**
* port and loads the admin console web application. * The admin console plugin. It starts a Jetty instance on the configured
* * port and loads the admin console web application.
* @author Matt Tucker *
*/ * @author Matt Tucker
public class AdminConsolePlugin implements Plugin { */
public class AdminConsolePlugin implements Plugin {
private static final Logger Log = LoggerFactory.getLogger(AdminConsolePlugin.class);
private static final Logger Log = LoggerFactory.getLogger(AdminConsolePlugin.class);
/**
* Random secret used by JVM to allow SSO. Only other cluster nodes can use this secret /**
* as a way to integrate the admin consoles of each cluster node. * Random secret used by JVM to allow SSO. Only other cluster nodes can use this secret
*/ * as a way to integrate the admin consoles of each cluster node.
public final static String secret = StringUtils.randomString(64); */
public final static String secret = StringUtils.randomString(64);
private int adminPort;
private int adminSecurePort; private int adminPort;
private Server adminServer; private int adminSecurePort;
private ContextHandlerCollection contexts; private Server adminServer;
private CertificateEventListener certificateListener; private ContextHandlerCollection contexts;
private boolean restartNeeded = false; private CertificateEventListener certificateListener;
private boolean sslEnabled = false; private boolean restartNeeded = false;
private boolean sslEnabled = false;
private File pluginDir;
private File pluginDir;
/**
* Create a Jetty module. /**
*/ * Create a Jetty module.
public AdminConsolePlugin() { */
contexts = new ContextHandlerCollection(); public AdminConsolePlugin() {
contexts = new ContextHandlerCollection();
// JSP 2.0 uses commons-logging, so also override that implementation.
System.setProperty("org.apache.commons.logging.LogFactory", "org.jivesoftware.util.log.util.CommonsLogFactory"); // JSP 2.0 uses commons-logging, so also override that implementation.
} System.setProperty("org.apache.commons.logging.LogFactory", "org.jivesoftware.util.log.util.CommonsLogFactory");
}
/**
* Starts the Jetty instance. /**
*/ * Starts the Jetty instance.
public void startup() { */
restartNeeded = false; public void startup() {
restartNeeded = false;
// Add listener for certificate events
certificateListener = new CertificateListener(); // Add listener for certificate events
CertificateManager.addListener(certificateListener); certificateListener = new CertificateListener();
CertificateManager.addListener(certificateListener);
// the number of threads allocated to each connector/port
int serverThreads = JiveGlobals.getXMLProperty("adminConsole.serverThreads", 2); // the number of threads allocated to each connector/port
int serverThreads = JiveGlobals.getXMLProperty("adminConsole.serverThreads", 2);
adminPort = JiveGlobals.getXMLProperty("adminConsole.port", 9090);
adminSecurePort = JiveGlobals.getXMLProperty("adminConsole.securePort", 9091); adminPort = JiveGlobals.getXMLProperty("adminConsole.port", 9090);
adminSecurePort = JiveGlobals.getXMLProperty("adminConsole.securePort", 9091);
final QueuedThreadPool tp = new QueuedThreadPool();
tp.setName("Jetty-QTP-AdminConsole"); final QueuedThreadPool tp = new QueuedThreadPool();
tp.setName("Jetty-QTP-AdminConsole");
adminServer = new Server(tp);
adminServer = new Server(tp);
if (JMXManager.isEnabled()) {
JMXManager jmx = JMXManager.getInstance(); if (JMXManager.isEnabled()) {
adminServer.addBean(jmx.getContainer()); JMXManager jmx = JMXManager.getInstance();
} adminServer.addBean(jmx.getContainer());
}
// Create connector for http traffic if it's enabled.
if (adminPort > 0) { // Create connector for http traffic if it's enabled.
final HttpConfiguration httpConfig = new HttpConfiguration(); if (adminPort > 0) {
final HttpConfiguration httpConfig = new HttpConfiguration();
// Do not send Jetty info in HTTP headers
httpConfig.setSendServerVersion( false ); // Do not send Jetty info in HTTP headers
httpConfig.setSendServerVersion( false );
final ServerConnector httpConnector = new ServerConnector(adminServer, null, null, null, -1, serverThreads, new HttpConnectionFactory(httpConfig));
final ServerConnector httpConnector = new ServerConnector(adminServer, null, null, null, -1, serverThreads, new HttpConnectionFactory(httpConfig));
// Listen on a specific network interface if it has been set.
String bindInterface = getBindInterface(); // Listen on a specific network interface if it has been set.
httpConnector.setHost(bindInterface); String bindInterface = getBindInterface();
httpConnector.setPort(adminPort); httpConnector.setHost(bindInterface);
adminServer.addConnector(httpConnector); httpConnector.setPort(adminPort);
} adminServer.addConnector(httpConnector);
}
// Create a connector for https traffic if it's enabled.
sslEnabled = false; // Create a connector for https traffic if it's enabled.
try { sslEnabled = false;
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.WEBADMIN_IDENTITYSTORE ); try {
if (adminSecurePort > 0 && identityStoreConfig.getStore().aliases().hasMoreElements() ) final IdentityStoreConfig identityStoreConfig = SSLConfig.getInstance().getIdentityStoreConfig( Purpose.WEBADMIN );
{ if (adminSecurePort > 0 && identityStoreConfig.getStore().aliases().hasMoreElements() )
if ( !identityStoreConfig.containsDomainCertificate( "RSA" )) { {
Log.warn("Admin console: Using RSA certificates but they are not valid for the hosted domain"); if ( !identityStoreConfig.containsDomainCertificate( "RSA" )) {
} Log.warn("Admin console: Using RSA certificates but they are not valid for the hosted domain");
}
final CertificateStoreConfig trustStoreConfig = SSLConfig.getInstance().getStoreConfig( Purpose.WEBADMIN_TRUSTSTORE );
final TrustStoreConfig trustStoreConfig = SSLConfig.getInstance().getTrustStoreConfig( Purpose.WEBADMIN );
final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setTrustStorePath( trustStoreConfig.getCanonicalPath() ); final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setTrustStorePassword( trustStoreConfig.getPassword() ); sslContextFactory.setTrustStorePath( trustStoreConfig.getCanonicalPath() );
sslContextFactory.setTrustStoreType( trustStoreConfig.getType() ); sslContextFactory.setTrustStorePassword( trustStoreConfig.getPassword() );
sslContextFactory.setKeyStorePath( identityStoreConfig.getCanonicalPath() ); sslContextFactory.setTrustStoreType( trustStoreConfig.getType() );
sslContextFactory.setKeyStorePassword( identityStoreConfig.getPassword() ); sslContextFactory.setKeyStorePath( identityStoreConfig.getCanonicalPath() );
sslContextFactory.setKeyStoreType( identityStoreConfig.getType() ); sslContextFactory.setKeyStorePassword( identityStoreConfig.getPassword() );
sslContextFactory.setKeyStoreType( identityStoreConfig.getType() );
sslContextFactory.addExcludeProtocols( "SSLv3" );
sslContextFactory.setNeedClientAuth( false ); sslContextFactory.addExcludeProtocols( "SSLv3" );
sslContextFactory.setWantClientAuth( false ); sslContextFactory.setNeedClientAuth( false );
sslContextFactory.setWantClientAuth( false );
final ServerConnector httpsConnector;
if ("npn".equals(JiveGlobals.getXMLProperty("spdy.protocol", ""))) final ServerConnector httpsConnector;
{ if ("npn".equals(JiveGlobals.getXMLProperty("spdy.protocol", "")))
httpsConnector = new HTTPSPDYServerConnector(adminServer, sslContextFactory); {
httpsConnector = new HTTPSPDYServerConnector(adminServer, sslContextFactory);
} else {
HttpConfiguration httpsConfig = new HttpConfiguration(); } else {
httpsConfig.setSendServerVersion( false ); HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.setSecureScheme("https"); httpsConfig.setSendServerVersion( false );
httpsConfig.setSecurePort(adminSecurePort); httpsConfig.setSecureScheme("https");
httpsConfig.addCustomizer(new SecureRequestCustomizer()); httpsConfig.setSecurePort(adminSecurePort);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpsConfig);
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, org.eclipse.jetty.http.HttpVersion.HTTP_1_1.toString()); HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory(httpsConfig);
SslConnectionFactory sslConnectionFactory = new SslConnectionFactory(sslContextFactory, org.eclipse.jetty.http.HttpVersion.HTTP_1_1.toString());
httpsConnector = new ServerConnector(adminServer, null, null, null, -1, serverThreads,
sslConnectionFactory, httpConnectionFactory); httpsConnector = new ServerConnector(adminServer, null, null, null, -1, serverThreads,
} sslConnectionFactory, httpConnectionFactory);
}
String bindInterface = getBindInterface();
httpsConnector.setHost(bindInterface); String bindInterface = getBindInterface();
httpsConnector.setPort(adminSecurePort); httpsConnector.setHost(bindInterface);
adminServer.addConnector(httpsConnector); httpsConnector.setPort(adminSecurePort);
adminServer.addConnector(httpsConnector);
sslEnabled = true;
} sslEnabled = true;
} }
catch (Exception e) { }
Log.error(e.getMessage(), e); catch (Exception e) {
} Log.error(e.getMessage(), e);
}
// Make sure that at least one connector was registered.
if (adminServer.getConnectors() == null || adminServer.getConnectors().length == 0) { // Make sure that at least one connector was registered.
adminServer = null; if (adminServer.getConnectors() == null || adminServer.getConnectors().length == 0) {
// Log warning. adminServer = null;
log(LocaleUtils.getLocalizedString("admin.console.warning")); // Log warning.
return; log(LocaleUtils.getLocalizedString("admin.console.warning"));
} return;
}
HandlerCollection collection = new HandlerCollection();
adminServer.setHandler(collection); HandlerCollection collection = new HandlerCollection();
collection.setHandlers(new Handler[] { contexts, new DefaultHandler() }); adminServer.setHandler(collection);
collection.setHandlers(new Handler[] { contexts, new DefaultHandler() });
try {
adminServer.start(); try {
} adminServer.start();
catch (Exception e) { }
Log.error("Could not start admin console server", e); catch (Exception e) {
} Log.error("Could not start admin console server", e);
}
// Log the ports that the admin server is listening on.
logAdminConsolePorts(); // Log the ports that the admin server is listening on.
} logAdminConsolePorts();
}
/**
* Shuts down the Jetty server. /**
* */ * Shuts down the Jetty server.
public void shutdown() { * */
// Remove listener for certificate events public void shutdown() {
if (certificateListener != null) { // Remove listener for certificate events
CertificateManager.removeListener(certificateListener); if (certificateListener != null) {
} CertificateManager.removeListener(certificateListener);
//noinspection ConstantConditions }
try { //noinspection ConstantConditions
if (adminServer != null && adminServer.isRunning()) { try {
adminServer.stop(); if (adminServer != null && adminServer.isRunning()) {
} adminServer.stop();
} }
catch (Exception e) { }
Log.error("Error stopping admin console server", e); catch (Exception e) {
} Log.error("Error stopping admin console server", e);
adminServer = null; }
} adminServer = null;
}
@Override
public void initializePlugin(PluginManager manager, File pluginDir) { @Override
this.pluginDir = pluginDir; public void initializePlugin(PluginManager manager, File pluginDir) {
this.pluginDir = pluginDir;
createWebAppContext();
createWebAppContext();
startup();
} startup();
}
@Override
public void destroyPlugin() { @Override
shutdown(); public void destroyPlugin() {
} shutdown();
}
/**
* Returns true if the Jetty server needs to be restarted. This is usually required when /**
* certificates are added, deleted or modified or when server ports were modified. * Returns true if the Jetty server needs to be restarted. This is usually required when
* * certificates are added, deleted or modified or when server ports were modified.
* @return true if the Jetty server needs to be restarted. *
*/ * @return true if the Jetty server needs to be restarted.
public boolean isRestartNeeded() { */
return restartNeeded; public boolean isRestartNeeded() {
} return restartNeeded;
}
/**
* Returns <tt>null</tt> if the admin console will be available in all network interfaces of this machine /**
* or a String representing the only interface where the admin console will be available. * Returns <tt>null</tt> if the admin console will be available in all network interfaces of this machine
* * or a String representing the only interface where the admin console will be available.
* @return String representing the only interface where the admin console will be available or null if it *
* will be available in all interfaces. * @return String representing the only interface where the admin console will be available or null if it
*/ * will be available in all interfaces.
public String getBindInterface() { */
String adminInterfaceName = JiveGlobals.getXMLProperty("adminConsole.interface"); public String getBindInterface() {
String globalInterfaceName = JiveGlobals.getXMLProperty("network.interface"); String adminInterfaceName = JiveGlobals.getXMLProperty("adminConsole.interface");
String bindInterface = null; String globalInterfaceName = JiveGlobals.getXMLProperty("network.interface");
if (adminInterfaceName != null && adminInterfaceName.trim().length() > 0) { String bindInterface = null;
bindInterface = adminInterfaceName; if (adminInterfaceName != null && adminInterfaceName.trim().length() > 0) {
} bindInterface = adminInterfaceName;
else if (globalInterfaceName != null && globalInterfaceName.trim().length() > 0) { }
bindInterface = globalInterfaceName; else if (globalInterfaceName != null && globalInterfaceName.trim().length() > 0) {
} bindInterface = globalInterfaceName;
return bindInterface; }
} return bindInterface;
}
/**
* Returns the non-SSL port on which the admin console is currently operating. /**
* * Returns the non-SSL port on which the admin console is currently operating.
* @return the non-SSL port on which the admin console is currently operating. *
*/ * @return the non-SSL port on which the admin console is currently operating.
public int getAdminUnsecurePort() { */
return adminPort; public int getAdminUnsecurePort() {
} return adminPort;
}
/**
* Returns the SSL port on which the admin console is current operating. /**
* * Returns the SSL port on which the admin console is current operating.
* @return the SSL port on which the admin console is current operating. *
*/ * @return the SSL port on which the admin console is current operating.
public int getAdminSecurePort() { */
if (!sslEnabled) { public int getAdminSecurePort() {
return 0; if (!sslEnabled) {
} return 0;
return adminSecurePort; }
} return adminSecurePort;
}
/**
* Returns the collection of Jetty contexts used in the admin console. A root context "/" /**
* is where the admin console lives. Additional contexts can be added dynamically for * Returns the collection of Jetty contexts used in the admin console. A root context "/"
* other web applications that should be run as part of the admin console server * is where the admin console lives. Additional contexts can be added dynamically for
* process. The following pseudo code demonstrates how to do this: * other web applications that should be run as part of the admin console server
* * process. The following pseudo code demonstrates how to do this:
* <pre> *
* ContextHandlerCollection contexts = ((AdminConsolePlugin)pluginManager.getPlugin("admin")).getContexts(); * <pre>
* context = new WebAppContext(SOME_DIRECTORY, "/CONTEXT_NAME"); * ContextHandlerCollection contexts = ((AdminConsolePlugin)pluginManager.getPlugin("admin")).getContexts();
* contexts.addHandler(context); * context = new WebAppContext(SOME_DIRECTORY, "/CONTEXT_NAME");
* context.setWelcomeFiles(new String[]{"index.jsp"}); * contexts.addHandler(context);
* context.start(); * context.setWelcomeFiles(new String[]{"index.jsp"});
* </pre> * context.start();
* * </pre>
* @return the Jetty handlers. *
*/ * @return the Jetty handlers.
public ContextHandlerCollection getContexts() { */
return contexts; public ContextHandlerCollection getContexts() {
} return contexts;
}
public void restart() {
try { public void restart() {
adminServer.stop(); try {
adminServer.start(); adminServer.stop();
} adminServer.start();
catch (Exception e) { }
Log.error(e.getMessage(), e); catch (Exception e) {
} Log.error(e.getMessage(), e);
} }
}
private void createWebAppContext() {
ServletContextHandler context; private void createWebAppContext() {
// Add web-app. Check to see if we're in development mode. If so, we don't ServletContextHandler context;
// add the normal web-app location, but the web-app in the project directory. // Add web-app. Check to see if we're in development mode. If so, we don't
if (Boolean.getBoolean("developmentMode")) { // add the normal web-app location, but the web-app in the project directory.
System.out.println(LocaleUtils.getLocalizedString("admin.console.devmode")); if (Boolean.getBoolean("developmentMode")) {
context = new WebAppContext(contexts, pluginDir.getParentFile().getParentFile().getParentFile().getParent() + System.out.println(LocaleUtils.getLocalizedString("admin.console.devmode"));
File.separator + "src" + File.separator + "web", "/"); context = new WebAppContext(contexts, pluginDir.getParentFile().getParentFile().getParentFile().getParent() +
} File.separator + "src" + File.separator + "web", "/");
else { }
context = new WebAppContext(contexts, pluginDir.getAbsoluteFile() + File.separator + "webapp", else {
"/"); context = new WebAppContext(contexts, pluginDir.getAbsoluteFile() + File.separator + "webapp",
} "/");
}
// Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs). context.setWelcomeFiles(new String[]{"index.jsp"});
final List<ContainerInitializer> initializers = new ArrayList<>(); }
initializers.add(new ContainerInitializer(new JettyJasperInitializer(), null));
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers); private void log(String string) {
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager()); Log.info(string);
System.out.println(string);
context.setWelcomeFiles(new String[]{"index.jsp"}); }
}
private void logAdminConsolePorts() {
private void log(String string) { // Log what ports the admin console is running on.
Log.info(string); String listening = LocaleUtils.getLocalizedString("admin.console.listening");
System.out.println(string); String hostname = getBindInterface() == null ?
} XMPPServer.getInstance().getServerInfo().getXMPPDomain() :
getBindInterface();
private void logAdminConsolePorts() { boolean isPlainStarted = false;
// Log what ports the admin console is running on. boolean isSecureStarted = false;
String listening = LocaleUtils.getLocalizedString("admin.console.listening"); boolean isSPDY = false;
String hostname = getBindInterface() == null ?
XMPPServer.getInstance().getServerInfo().getXMPPDomain() : for (Connector connector : adminServer.getConnectors()) {
getBindInterface(); if (((ServerConnector) connector).getPort() == adminPort) {
boolean isPlainStarted = false; isPlainStarted = true;
boolean isSecureStarted = false; }
boolean isSPDY = false; else if (((ServerConnector) connector).getPort() == adminSecurePort) {
isSecureStarted = true;
for (Connector connector : adminServer.getConnectors()) { }
if (((ServerConnector) connector).getPort() == adminPort) {
isPlainStarted = true; if (connector instanceof HTTPSPDYServerConnector) {
} isSPDY = true;
else if (((ServerConnector) connector).getPort() == adminSecurePort) { }
isSecureStarted = true; }
}
if (isPlainStarted && isSecureStarted) {
if (connector instanceof HTTPSPDYServerConnector) { log(listening + ":" + System.getProperty("line.separator") +
isSPDY = true; " http://" + hostname + ":" +
} adminPort + System.getProperty("line.separator") +
} " https://" + hostname + ":" +
adminSecurePort + (isSPDY ? " (SPDY)" : ""));
if (isPlainStarted && isSecureStarted) { }
log(listening + ":" + System.getProperty("line.separator") + else if (isSecureStarted) {
" http://" + hostname + ":" + log(listening + " https://" + hostname + ":" + adminSecurePort + (isSPDY ? " (SPDY)" : ""));
adminPort + System.getProperty("line.separator") + }
" https://" + hostname + ":" + else if (isPlainStarted) {
adminSecurePort + (isSPDY ? " (SPDY)" : "")); log(listening + " http://" + hostname + ":" + adminPort);
} }
else if (isSecureStarted) { }
log(listening + " https://" + hostname + ":" + adminSecurePort + (isSPDY ? " (SPDY)" : ""));
} /**
else if (isPlainStarted) { * Listens for security certificates being created and destroyed so we can track when the
log(listening + " http://" + hostname + ":" + adminPort); * admin console needs to be restarted.
} */
} private class CertificateListener implements CertificateEventListener {
/** @Override
* Listens for security certificates being created and destroyed so we can track when the public void certificateCreated(KeyStore keyStore, String alias, X509Certificate cert) {
* admin console needs to be restarted. // If new certificate is RSA then (re)start the HTTPS service
*/ if ("RSA".equals(cert.getPublicKey().getAlgorithm())) {
private class CertificateListener implements CertificateEventListener { restartNeeded = true;
}
@Override }
public void certificateCreated(KeyStore keyStore, String alias, X509Certificate cert) {
// If new certificate is RSA then (re)start the HTTPS service @Override
if ("RSA".equals(cert.getPublicKey().getAlgorithm())) { public void certificateDeleted(KeyStore keyStore, String alias) {
restartNeeded = true; restartNeeded = true;
} }
}
@Override
@Override public void certificateSigned(KeyStore keyStore, String alias,
public void certificateDeleted(KeyStore keyStore, String alias) { List<X509Certificate> certificates) {
restartNeeded = true; // If new certificate is RSA then (re)start the HTTPS service
} if ("RSA".equals(certificates.get(0).getPublicKey().getAlgorithm())) {
restartNeeded = true;
@Override }
public void certificateSigned(KeyStore keyStore, String alias, }
List<X509Certificate> certificates) { }
// If new certificate is RSA then (re)start the HTTPS service }
if ("RSA".equals(certificates.get(0).getPublicKey().getAlgorithm())) {
restartNeeded = true;
}
}
}
}
/** /**
* $RCSfile$ * $RCSfile$
* $Revision: $ * $Revision: $
* $Date: $ * $Date: $
* *
* Copyright (C) 2005-2008 Jive Software. All rights reserved. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.http; package org.jivesoftware.openfire.http;
import java.io.File; import java.io.File;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.*; import java.util.EnumSet;
import java.util.HashMap;
import javax.servlet.DispatcherType; import java.util.List;
import javax.servlet.Filter; import java.util.Map;
import javax.servlet.FilterConfig;
import javax.servlet.ServletException; import javax.servlet.DispatcherType;
import javax.servlet.Filter;
import org.apache.tomcat.InstanceManager; import javax.servlet.FilterConfig;
import org.apache.tomcat.SimpleInstanceManager; import javax.servlet.ServletException;
import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.http.HttpMethod; import org.apache.tomcat.InstanceManager;
import org.eclipse.jetty.plus.annotation.ContainerInitializer; import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.server.Connector; import org.eclipse.jetty.apache.jsp.JettyJasperInitializer;
import org.eclipse.jetty.server.ForwardedRequestCustomizer; import org.eclipse.jetty.http.HttpMethod;
import org.eclipse.jetty.server.Handler; import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.HttpConfiguration; import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.HttpConnectionFactory; import org.eclipse.jetty.server.ForwardedRequestCustomizer;
import org.eclipse.jetty.server.SecureRequestCustomizer; import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.Server; import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.ServerConnector; import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SslConnectionFactory; import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.handler.ContextHandlerCollection; import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.handler.DefaultHandler; import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.handler.HandlerCollection; import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.servlet.FilterHolder; import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.servlet.ServletContextHandler; import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.servlet.ServletHolder; import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.servlets.AsyncGzipFilter; import org.eclipse.jetty.servlet.FilterHolder;
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector; import org.eclipse.jetty.servlet.ServletContextHandler;
import org.eclipse.jetty.util.ssl.SslContextFactory; import org.eclipse.jetty.servlet.ServletHolder;
import org.eclipse.jetty.util.thread.QueuedThreadPool; import org.eclipse.jetty.servlets.AsyncGzipFilter;
import org.eclipse.jetty.webapp.WebAppContext; import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector;
import org.jivesoftware.openfire.Connection; import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.jivesoftware.openfire.JMXManager; import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.jivesoftware.openfire.XMPPServer; import org.eclipse.jetty.webapp.WebAppContext;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig; import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.keystore.Purpose; import org.jivesoftware.openfire.JMXManager;
import org.jivesoftware.openfire.keystore.CertificateStoreConfig; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.net.SSLConfig; import org.jivesoftware.openfire.keystore.IdentityStoreConfig;
import org.jivesoftware.openfire.session.ConnectionSettings; import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.util.CertificateEventListener; import org.jivesoftware.openfire.keystore.CertificateStoreConfig;
import org.jivesoftware.util.CertificateManager; import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.util.PropertyEventDispatcher; import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.PropertyEventListener; import org.jivesoftware.util.CertificateManager;
import org.slf4j.Logger; import org.jivesoftware.util.JiveGlobals;
import org.slf4j.LoggerFactory; import org.jivesoftware.util.PropertyEventDispatcher;
import org.jivesoftware.util.PropertyEventListener;
/** import org.slf4j.Logger;
* import org.slf4j.LoggerFactory;
*/
public final class HttpBindManager { /**
*
private static final Logger Log = LoggerFactory.getLogger(HttpBindManager.class); */
public final class HttpBindManager {
public static final String HTTP_BIND_ENABLED = "httpbind.enabled";
private static final Logger Log = LoggerFactory.getLogger(HttpBindManager.class);
public static final boolean HTTP_BIND_ENABLED_DEFAULT = true;
public static final String HTTP_BIND_ENABLED = "httpbind.enabled";
public static final String HTTP_BIND_PORT = "httpbind.port.plain";
public static final boolean HTTP_BIND_ENABLED_DEFAULT = true;
public static final int HTTP_BIND_PORT_DEFAULT = 7070;
public static final String HTTP_BIND_PORT = "httpbind.port.plain";
public static final String HTTP_BIND_SECURE_PORT = "httpbind.port.secure";
public static final int HTTP_BIND_PORT_DEFAULT = 7070;
public static final int HTTP_BIND_SECURE_PORT_DEFAULT = 7443;
public static final String HTTP_BIND_SECURE_PORT = "httpbind.port.secure";
public static final String HTTP_BIND_THREADS = "httpbind.client.processing.threads";
public static final int HTTP_BIND_SECURE_PORT_DEFAULT = 7443;
public static final String HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY = "httpbind.client.cert.policy";
public static final String HTTP_BIND_THREADS = "httpbind.client.processing.threads";
public static final int HTTP_BIND_THREADS_DEFAULT = 8;
public static final String HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY = "httpbind.client.cert.policy";
private static final String HTTP_BIND_FORWARDED = "httpbind.forwarded.enabled";
public static final int HTTP_BIND_THREADS_DEFAULT = 8;
private static final String HTTP_BIND_FORWARDED_FOR = "httpbind.forwarded.for.header";
private static final String HTTP_BIND_FORWARDED = "httpbind.forwarded.enabled";
private static final String HTTP_BIND_FORWARDED_SERVER = "httpbind.forwarded.server.header";
private static final String HTTP_BIND_FORWARDED_FOR = "httpbind.forwarded.for.header";
private static final String HTTP_BIND_FORWARDED_HOST = "httpbind.forwarded.host.header";
private static final String HTTP_BIND_FORWARDED_SERVER = "httpbind.forwarded.server.header";
private static final String HTTP_BIND_FORWARDED_HOST_NAME = "httpbind.forwarded.host.name";
private static final String HTTP_BIND_FORWARDED_HOST = "httpbind.forwarded.host.header";
// http binding CORS default properties
private static final String HTTP_BIND_FORWARDED_HOST_NAME = "httpbind.forwarded.host.name";
public static final String HTTP_BIND_CORS_ENABLED = "httpbind.CORS.enabled";
// http binding CORS default properties
public static final boolean HTTP_BIND_CORS_ENABLED_DEFAULT = true;
public static final String HTTP_BIND_CORS_ENABLED = "httpbind.CORS.enabled";
public static final String HTTP_BIND_CORS_ALLOW_ORIGIN = "httpbind.CORS.domains";
public static final boolean HTTP_BIND_CORS_ENABLED_DEFAULT = true;
public static final String HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT = "*";
public static final String HTTP_BIND_CORS_ALLOW_ORIGIN = "httpbind.CORS.domains";
public static final String HTTP_BIND_CORS_ALLOW_METHODS_DEFAULT = "PROPFIND, PROPPATCH, COPY, MOVE, DELETE, MKCOL, LOCK, UNLOCK, PUT, GETLIB, VERSION-CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, REPORT, UPDATE, CANCELUPLOAD, HEAD, OPTIONS, GET, POST";
public static final String HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT = "*";
public static final String HTTP_BIND_CORS_ALLOW_HEADERS_DEFAULT = "Overwrite, Destination, Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control";
public static final String HTTP_BIND_CORS_ALLOW_METHODS_DEFAULT = "PROPFIND, PROPPATCH, COPY, MOVE, DELETE, MKCOL, LOCK, UNLOCK, PUT, GETLIB, VERSION-CONTROL, CHECKIN, CHECKOUT, UNCHECKOUT, REPORT, UPDATE, CANCELUPLOAD, HEAD, OPTIONS, GET, POST";
public static final String HTTP_BIND_CORS_MAX_AGE_DEFAULT = "86400";
public static final String HTTP_BIND_CORS_ALLOW_HEADERS_DEFAULT = "Overwrite, Destination, Content-Type, Depth, User-Agent, X-File-Size, X-Requested-With, If-Modified-Since, X-File-Name, Cache-Control";
public static final String HTTP_BIND_REQUEST_HEADER_SIZE = "httpbind.request.header.size";
public static final String HTTP_BIND_CORS_MAX_AGE_DEFAULT = "86400";
public static final int HTTP_BIND_REQUEST_HEADER_SIZE_DEFAULT = 32768;
public static final String HTTP_BIND_REQUEST_HEADER_SIZE = "httpbind.request.header.size";
public static Map<String, Boolean> HTTP_BIND_ALLOWED_ORIGINS = new HashMap<>();
public static final int HTTP_BIND_REQUEST_HEADER_SIZE_DEFAULT = 32768;
private static HttpBindManager instance = new HttpBindManager();
public static Map<String, Boolean> HTTP_BIND_ALLOWED_ORIGINS = new HashMap<>();
// Compression "optional" by default; use "disabled" to disable compression (restart required)
// When enabled, http response will be compressed if the http request includes an private static HttpBindManager instance = new HttpBindManager();
// "Accept" header with a value of "gzip" and/or "deflate"
private static boolean isCompressionEnabled = !(JiveGlobals.getProperty( // Compression "optional" by default; use "disabled" to disable compression (restart required)
ConnectionSettings.Client.COMPRESSION_SETTINGS, Connection.CompressionPolicy.optional.toString()) // When enabled, http response will be compressed if the http request includes an
.equalsIgnoreCase(Connection.CompressionPolicy.disabled.toString())); // "Accept" header with a value of "gzip" and/or "deflate"
private static boolean isCompressionEnabled = !(JiveGlobals.getProperty(
private Server httpBindServer; ConnectionSettings.Client.COMPRESSION_SETTINGS, Connection.CompressionPolicy.optional.toString())
.equalsIgnoreCase(Connection.CompressionPolicy.disabled.toString()));
private int bindPort;
private Server httpBindServer;
private int bindSecurePort;
private int bindPort;
private Connector httpConnector;
private Connector httpsConnector; private int bindSecurePort;
private CertificateListener certificateListener; private Connector httpConnector;
private Connector httpsConnector;
private HttpSessionManager httpSessionManager;
private CertificateListener certificateListener;
private ContextHandlerCollection contexts;
private HttpSessionManager httpSessionManager;
// is all orgin allowed flag
private boolean allowAllOrigins; private ContextHandlerCollection contexts;
public static HttpBindManager getInstance() { // is all orgin allowed flag
return instance; private boolean allowAllOrigins;
}
public static HttpBindManager getInstance() {
private HttpBindManager() { return instance;
// JSP 2.0 uses commons-logging, so also override that implementation. }
System.setProperty("org.apache.commons.logging.LogFactory", "org.jivesoftware.util.log.util.CommonsLogFactory");
private HttpBindManager() {
JiveGlobals.migrateProperty(HTTP_BIND_ENABLED); // JSP 2.0 uses commons-logging, so also override that implementation.
JiveGlobals.migrateProperty(HTTP_BIND_PORT); System.setProperty("org.apache.commons.logging.LogFactory", "org.jivesoftware.util.log.util.CommonsLogFactory");
JiveGlobals.migrateProperty(HTTP_BIND_SECURE_PORT);
JiveGlobals.migrateProperty(HTTP_BIND_THREADS); JiveGlobals.migrateProperty(HTTP_BIND_ENABLED);
JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED); JiveGlobals.migrateProperty(HTTP_BIND_PORT);
JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_FOR); JiveGlobals.migrateProperty(HTTP_BIND_SECURE_PORT);
JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_SERVER); JiveGlobals.migrateProperty(HTTP_BIND_THREADS);
JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_HOST); JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED);
JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_HOST_NAME); JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_FOR);
JiveGlobals.migrateProperty(HTTP_BIND_CORS_ENABLED); JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_SERVER);
JiveGlobals.migrateProperty(HTTP_BIND_CORS_ALLOW_ORIGIN); JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_HOST);
JiveGlobals.migrateProperty(HTTP_BIND_REQUEST_HEADER_SIZE); JiveGlobals.migrateProperty(HTTP_BIND_FORWARDED_HOST_NAME);
JiveGlobals.migrateProperty(HTTP_BIND_CORS_ENABLED);
PropertyEventDispatcher.addListener(new HttpServerPropertyListener()); JiveGlobals.migrateProperty(HTTP_BIND_CORS_ALLOW_ORIGIN);
this.httpSessionManager = new HttpSessionManager(); JiveGlobals.migrateProperty(HTTP_BIND_REQUEST_HEADER_SIZE);
// we need to initialise contexts at constructor time in order for plugins to add their contexts before start() PropertyEventDispatcher.addListener(new HttpServerPropertyListener());
contexts = new ContextHandlerCollection(); this.httpSessionManager = new HttpSessionManager();
// setup the cache for the allowed origins // we need to initialise contexts at constructor time in order for plugins to add their contexts before start()
this.setupAllowedOriginsMap(); contexts = new ContextHandlerCollection();
}
// setup the cache for the allowed origins
public void start() { this.setupAllowedOriginsMap();
certificateListener = new CertificateListener(); }
CertificateManager.addListener(certificateListener);
public void start() {
if (!isHttpBindServiceEnabled()) { certificateListener = new CertificateListener();
return; CertificateManager.addListener(certificateListener);
}
bindPort = getHttpBindUnsecurePort(); if (!isHttpBindServiceEnabled()) {
bindSecurePort = getHttpBindSecurePort(); return;
configureHttpBindServer(bindPort, bindSecurePort); }
bindPort = getHttpBindUnsecurePort();
try { bindSecurePort = getHttpBindSecurePort();
httpBindServer.start(); configureHttpBindServer(bindPort, bindSecurePort);
Log.info("HTTP bind service started");
} try {
catch (Exception e) { httpBindServer.start();
Log.error("Error starting HTTP bind service", e); Log.info("HTTP bind service started");
} }
} catch (Exception e) {
Log.error("Error starting HTTP bind service", e);
public void stop() { }
CertificateManager.removeListener(certificateListener); }
if (httpBindServer != null) { public void stop() {
try { CertificateManager.removeListener(certificateListener);
httpBindServer.stop();
Log.info("HTTP bind service stopped"); if (httpBindServer != null) {
} try {
catch (Exception e) { httpBindServer.stop();
Log.error("Error stopping HTTP bind service", e); Log.info("HTTP bind service stopped");
} }
httpBindServer = null; catch (Exception e) {
} Log.error("Error stopping HTTP bind service", e);
} }
httpBindServer = null;
public HttpSessionManager getSessionManager() { }
return httpSessionManager; }
}
public HttpSessionManager getSessionManager() {
private boolean isHttpBindServiceEnabled() { return httpSessionManager;
return JiveGlobals.getBooleanProperty(HTTP_BIND_ENABLED, HTTP_BIND_ENABLED_DEFAULT); }
}
private boolean isHttpBindServiceEnabled() {
private void createConnector(int port, int bindThreads) { return JiveGlobals.getBooleanProperty(HTTP_BIND_ENABLED, HTTP_BIND_ENABLED_DEFAULT);
httpConnector = null; }
if (port > 0) {
HttpConfiguration httpConfig = new HttpConfiguration(); private void createConnector(int port, int bindThreads) {
configureProxiedConnector(httpConfig); httpConnector = null;
ServerConnector connector = new ServerConnector(httpBindServer, null, null, null, -1, bindThreads, if (port > 0) {
new HttpConnectionFactory(httpConfig)); HttpConfiguration httpConfig = new HttpConfiguration();
configureProxiedConnector(httpConfig);
// Listen on a specific network interface if it has been set. ServerConnector connector = new ServerConnector(httpBindServer, null, null, null, -1, bindThreads,
connector.setHost(getBindInterface()); new HttpConnectionFactory(httpConfig));
connector.setPort(port);
httpConnector = connector; // Listen on a specific network interface if it has been set.
} connector.setHost(getBindInterface());
} connector.setPort(port);
httpConnector = connector;
private void createSSLConnector(int securePort, int bindThreads) { }
httpsConnector = null; }
try {
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.BOSHBASED_IDENTITYSTORE ); private void createSSLConnector(int securePort, int bindThreads) {
final KeyStore keyStore = identityStoreConfig.getStore(); httpsConnector = null;
try {
if (securePort > 0 && identityStoreConfig.getStore().aliases().hasMoreElements() ) { final IdentityStoreConfig identityStoreConfig = SSLConfig.getInstance().getIdentityStoreConfig( Purpose.BOSH_C2S );
if ( !identityStoreConfig.containsDomainCertificate( "RSA" ) ) { final KeyStore keyStore = identityStoreConfig.getStore();
Log.warn("HTTP binding: Using RSA certificates but they are not valid for " +
"the hosted domain"); if (securePort > 0 && identityStoreConfig.getStore().aliases().hasMoreElements() ) {
} if ( !identityStoreConfig.containsDomainCertificate( "RSA" ) ) {
Log.warn("HTTP binding: Using RSA certificates but they are not valid for " +
final CertificateStoreConfig trustStoreConfig = SSLConfig.getInstance().getStoreConfig( Purpose.BOSHBASED_C2S_TRUSTSTORE ); "the hosted domain");
}
final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setTrustStorePath( trustStoreConfig.getCanonicalPath() ); final TrustStoreConfig trustStoreConfig = SSLConfig.getInstance().getTrustStoreConfig( Purpose.BOSH_C2S );
sslContextFactory.setTrustStorePassword( trustStoreConfig.getPassword() );
sslContextFactory.setTrustStoreType( trustStoreConfig.getType() ); final SslContextFactory sslContextFactory = new SslContextFactory();
sslContextFactory.setKeyStorePath( identityStoreConfig.getCanonicalPath() ); sslContextFactory.setTrustStorePath( trustStoreConfig.getCanonicalPath() );
sslContextFactory.setKeyStorePassword( identityStoreConfig.getPassword() ); sslContextFactory.setTrustStorePassword( trustStoreConfig.getPassword() );
sslContextFactory.setKeyStoreType( identityStoreConfig.getType() ); sslContextFactory.setTrustStoreType( trustStoreConfig.getType() );
sslContextFactory.setKeyStorePath( identityStoreConfig.getCanonicalPath() );
sslContextFactory.addExcludeProtocols( "SSLv3" ); sslContextFactory.setKeyStorePassword( identityStoreConfig.getPassword() );
sslContextFactory.setKeyStoreType( identityStoreConfig.getType() );
// Set policy for checking client certificates
String certPol = JiveGlobals.getProperty(HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY, "disabled"); sslContextFactory.addExcludeProtocols( "SSLv3" );
if(certPol.equals("needed")) {
sslContextFactory.setNeedClientAuth(true); // Set policy for checking client certificates
sslContextFactory.setWantClientAuth(true); String certPol = JiveGlobals.getProperty(HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY, "disabled");
} else if(certPol.equals("wanted")) { if(certPol.equals("needed")) {
sslContextFactory.setNeedClientAuth(false); sslContextFactory.setNeedClientAuth(true);
sslContextFactory.setWantClientAuth(true); sslContextFactory.setWantClientAuth(true);
} else { } else if(certPol.equals("wanted")) {
sslContextFactory.setNeedClientAuth(false); sslContextFactory.setNeedClientAuth(false);
sslContextFactory.setWantClientAuth(false); sslContextFactory.setWantClientAuth(true);
} } else {
sslContextFactory.setNeedClientAuth(false);
HttpConfiguration httpsConfig = new HttpConfiguration(); sslContextFactory.setWantClientAuth(false);
httpsConfig.setSecureScheme("https"); }
httpsConfig.setSecurePort(securePort);
configureProxiedConnector(httpsConfig); HttpConfiguration httpsConfig = new HttpConfiguration();
httpsConfig.addCustomizer(new SecureRequestCustomizer()); httpsConfig.setSecureScheme("https");
httpsConfig.setSecurePort(securePort);
ServerConnector sslConnector = null; configureProxiedConnector(httpsConfig);
httpsConfig.addCustomizer(new SecureRequestCustomizer());
if ("npn".equals(JiveGlobals.getXMLProperty("spdy.protocol", "")))
{ ServerConnector sslConnector = null;
sslConnector = new HTTPSPDYServerConnector(httpBindServer, sslContextFactory);
} else { if ("npn".equals(JiveGlobals.getXMLProperty("spdy.protocol", "")))
{
sslConnector = new ServerConnector(httpBindServer, null, null, null, -1, bindThreads, sslConnector = new HTTPSPDYServerConnector(httpBindServer, sslContextFactory);
new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpsConfig)); } else {
}
sslConnector.setHost(getBindInterface()); sslConnector = new ServerConnector(httpBindServer, null, null, null, -1, bindThreads,
sslConnector.setPort(securePort); new SslConnectionFactory(sslContextFactory, "http/1.1"), new HttpConnectionFactory(httpsConfig));
httpsConnector = sslConnector; }
} sslConnector.setHost(getBindInterface());
} sslConnector.setPort(securePort);
catch (Exception e) { httpsConnector = sslConnector;
Log.error("Error creating SSL connector for Http bind", e); }
} }
} catch (Exception e) {
Log.error("Error creating SSL connector for Http bind", e);
private void configureProxiedConnector(HttpConfiguration httpConfig) { }
// Check to see if we are deployed behind a proxy }
// Refer to http://eclipse.org/jetty/documentation/current/configuring-connectors.html
if (isXFFEnabled()) { private void configureProxiedConnector(HttpConfiguration httpConfig) {
ForwardedRequestCustomizer customizer = new ForwardedRequestCustomizer(); // Check to see if we are deployed behind a proxy
// default: "X-Forwarded-For" // Refer to http://eclipse.org/jetty/documentation/current/configuring-connectors.html
String forwardedForHeader = getXFFHeader(); if (isXFFEnabled()) {
if (forwardedForHeader != null) { ForwardedRequestCustomizer customizer = new ForwardedRequestCustomizer();
customizer.setForwardedForHeader(forwardedForHeader); // default: "X-Forwarded-For"
} String forwardedForHeader = getXFFHeader();
// default: "X-Forwarded-Server" if (forwardedForHeader != null) {
String forwardedServerHeader = getXFFServerHeader(); customizer.setForwardedForHeader(forwardedForHeader);
if (forwardedServerHeader != null) { }
customizer.setForwardedServerHeader(forwardedServerHeader); // default: "X-Forwarded-Server"
} String forwardedServerHeader = getXFFServerHeader();
// default: "X-Forwarded-Host" if (forwardedServerHeader != null) {
String forwardedHostHeader = getXFFHostHeader(); customizer.setForwardedServerHeader(forwardedServerHeader);
if (forwardedHostHeader != null) { }
customizer.setForwardedHostHeader(forwardedHostHeader); // default: "X-Forwarded-Host"
} String forwardedHostHeader = getXFFHostHeader();
// default: none if (forwardedHostHeader != null) {
String hostName = getXFFHostName(); customizer.setForwardedHostHeader(forwardedHostHeader);
if (hostName != null) { }
customizer.setHostHeader(hostName); // default: none
} String hostName = getXFFHostName();
if (hostName != null) {
httpConfig.addCustomizer(customizer); customizer.setHostHeader(hostName);
} }
httpConfig.setRequestHeaderSize(JiveGlobals.getIntProperty(HTTP_BIND_REQUEST_HEADER_SIZE, HTTP_BIND_REQUEST_HEADER_SIZE_DEFAULT));
} httpConfig.addCustomizer(customizer);
}
private String getBindInterface() { httpConfig.setRequestHeaderSize(JiveGlobals.getIntProperty(HTTP_BIND_REQUEST_HEADER_SIZE, HTTP_BIND_REQUEST_HEADER_SIZE_DEFAULT));
String interfaceName = JiveGlobals.getXMLProperty("network.interface"); }
String bindInterface = null;
if (interfaceName != null) { private String getBindInterface() {
if (interfaceName.trim().length() > 0) { String interfaceName = JiveGlobals.getXMLProperty("network.interface");
bindInterface = interfaceName; String bindInterface = null;
} if (interfaceName != null) {
} if (interfaceName.trim().length() > 0) {
return bindInterface; bindInterface = interfaceName;
} }
}
/** return bindInterface;
* Returns true if the HTTP binding server is currently enabled. }
*
* @return true if the HTTP binding server is currently enabled. /**
*/ * Returns true if the HTTP binding server is currently enabled.
public boolean isHttpBindEnabled() { *
return httpBindServer != null && httpBindServer.isRunning(); * @return true if the HTTP binding server is currently enabled.
} */
public boolean isHttpBindEnabled() {
/** return httpBindServer != null && httpBindServer.isRunning();
* Returns true if a listener on the HTTP binding port is running. }
*
* @return true if a listener on the HTTP binding port is running. /**
*/ * Returns true if a listener on the HTTP binding port is running.
public boolean isHttpBindActive() { *
return httpConnector != null && httpConnector.isRunning(); * @return true if a listener on the HTTP binding port is running.
} */
public boolean isHttpBindActive() {
/** return httpConnector != null && httpConnector.isRunning();
* Returns true if a listener on the HTTPS binding port is running. }
*
* @return true if a listener on the HTTPS binding port is running. /**
*/ * Returns true if a listener on the HTTPS binding port is running.
public boolean isHttpsBindActive() { *
return httpsConnector != null && httpsConnector.isRunning(); * @return true if a listener on the HTTPS binding port is running.
} */
public boolean isHttpsBindActive() {
public String getHttpBindUnsecureAddress() { return httpsConnector != null && httpsConnector.isRunning();
return "http://" + XMPPServer.getInstance().getServerInfo().getXMPPDomain() + ":" + }
bindPort + "/http-bind/";
} public String getHttpBindUnsecureAddress() {
return "http://" + XMPPServer.getInstance().getServerInfo().getXMPPDomain() + ":" +
public String getHttpBindSecureAddress() { bindPort + "/http-bind/";
return "https://" + XMPPServer.getInstance().getServerInfo().getXMPPDomain() + ":" + }
bindSecurePort + "/http-bind/";
} public String getHttpBindSecureAddress() {
return "https://" + XMPPServer.getInstance().getServerInfo().getXMPPDomain() + ":" +
public String getJavaScriptUrl() { bindSecurePort + "/http-bind/";
return "http://" + XMPPServer.getInstance().getServerInfo().getXMPPDomain() + ":" + }
bindPort + "/scripts/";
} public String getJavaScriptUrl() {
return "http://" + XMPPServer.getInstance().getServerInfo().getXMPPDomain() + ":" +
// http binding CORS support start bindPort + "/scripts/";
}
private void setupAllowedOriginsMap() {
String originString = getCORSAllowOrigin(); // http binding CORS support start
if (originString.equals(HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT)) {
allowAllOrigins = true; private void setupAllowedOriginsMap() {
} else { String originString = getCORSAllowOrigin();
allowAllOrigins = false; if (originString.equals(HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT)) {
String[] origins = originString.split(","); allowAllOrigins = true;
// reset the cache } else {
HTTP_BIND_ALLOWED_ORIGINS.clear(); allowAllOrigins = false;
for (String str : origins) { String[] origins = originString.split(",");
HTTP_BIND_ALLOWED_ORIGINS.put(str, true); // reset the cache
} HTTP_BIND_ALLOWED_ORIGINS.clear();
} for (String str : origins) {
} HTTP_BIND_ALLOWED_ORIGINS.put(str, true);
}
public boolean isCORSEnabled() { }
return JiveGlobals.getBooleanProperty(HTTP_BIND_CORS_ENABLED, HTTP_BIND_CORS_ENABLED_DEFAULT); }
}
public boolean isCORSEnabled() {
public void setCORSEnabled(Boolean value) { return JiveGlobals.getBooleanProperty(HTTP_BIND_CORS_ENABLED, HTTP_BIND_CORS_ENABLED_DEFAULT);
if (value != null) }
JiveGlobals.setProperty(HTTP_BIND_CORS_ENABLED, String.valueOf(value));
} public void setCORSEnabled(Boolean value) {
if (value != null)
public String getCORSAllowOrigin() { JiveGlobals.setProperty(HTTP_BIND_CORS_ENABLED, String.valueOf(value));
return JiveGlobals.getProperty(HTTP_BIND_CORS_ALLOW_ORIGIN , HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT); }
}
public String getCORSAllowOrigin() {
public void setCORSAllowOrigin(String origins) { return JiveGlobals.getProperty(HTTP_BIND_CORS_ALLOW_ORIGIN , HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT);
if (origins == null || origins.trim().length() == 0) }
origins = HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT;
else { public void setCORSAllowOrigin(String origins) {
origins = origins.replaceAll("\\s+", ""); if (origins == null || origins.trim().length() == 0)
} origins = HTTP_BIND_CORS_ALLOW_ORIGIN_DEFAULT;
JiveGlobals.setProperty(HTTP_BIND_CORS_ALLOW_ORIGIN, origins); else {
setupAllowedOriginsMap(); origins = origins.replaceAll("\\s+", "");
} }
JiveGlobals.setProperty(HTTP_BIND_CORS_ALLOW_ORIGIN, origins);
public boolean isAllOriginsAllowed() { setupAllowedOriginsMap();
return allowAllOrigins; }
}
public boolean isAllOriginsAllowed() {
public boolean isThisOriginAllowed(String origin) { return allowAllOrigins;
return HTTP_BIND_ALLOWED_ORIGINS.get(origin) != null; }
}
public boolean isThisOriginAllowed(String origin) {
// http binding CORS support end return HTTP_BIND_ALLOWED_ORIGINS.get(origin) != null;
}
public boolean isXFFEnabled() {
return JiveGlobals.getBooleanProperty(HTTP_BIND_FORWARDED, false); // http binding CORS support end
}
public boolean isXFFEnabled() {
public void setXFFEnabled(boolean enabled) { return JiveGlobals.getBooleanProperty(HTTP_BIND_FORWARDED, false);
JiveGlobals.setProperty(HTTP_BIND_FORWARDED, String.valueOf(enabled)); }
}
public void setXFFEnabled(boolean enabled) {
public String getXFFHeader() { JiveGlobals.setProperty(HTTP_BIND_FORWARDED, String.valueOf(enabled));
return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_FOR); }
}
public String getXFFHeader() {
public void setXFFHeader(String header) { return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_FOR);
if (header == null || header.trim().length() == 0) { }
JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_FOR);
} else { public void setXFFHeader(String header) {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_FOR, header); if (header == null || header.trim().length() == 0) {
} JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_FOR);
} } else {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_FOR, header);
public String getXFFServerHeader() { }
return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_SERVER); }
}
public String getXFFServerHeader() {
public void setXFFServerHeader(String header) { return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_SERVER);
if (header == null || header.trim().length() == 0) { }
JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_SERVER);
} else { public void setXFFServerHeader(String header) {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_SERVER, header); if (header == null || header.trim().length() == 0) {
} JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_SERVER);
} } else {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_SERVER, header);
public String getXFFHostHeader() { }
return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_HOST); }
}
public String getXFFHostHeader() {
public void setXFFHostHeader(String header) { return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_HOST);
if (header == null || header.trim().length() == 0) { }
JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_HOST);
} else { public void setXFFHostHeader(String header) {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_HOST, header); if (header == null || header.trim().length() == 0) {
} JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_HOST);
} } else {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_HOST, header);
public String getXFFHostName() { }
return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_HOST_NAME); }
}
public String getXFFHostName() {
public void setXFFHostName(String name) { return JiveGlobals.getProperty(HTTP_BIND_FORWARDED_HOST_NAME);
if (name == null || name.trim().length() == 0) { }
JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_HOST_NAME);
} else { public void setXFFHostName(String name) {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_HOST_NAME, name); if (name == null || name.trim().length() == 0) {
} JiveGlobals.deleteProperty(HTTP_BIND_FORWARDED_HOST_NAME);
} } else {
JiveGlobals.setProperty(HTTP_BIND_FORWARDED_HOST_NAME, name);
public void setHttpBindEnabled(boolean isEnabled) { }
JiveGlobals.setProperty(HTTP_BIND_ENABLED, String.valueOf(isEnabled)); }
}
public void setHttpBindEnabled(boolean isEnabled) {
/** JiveGlobals.setProperty(HTTP_BIND_ENABLED, String.valueOf(isEnabled));
* Set the ports on which the HTTP binding service will be running. }
*
* @param unsecurePort the unsecured connection port which clients can connect to. /**
* @param securePort the secured connection port which clients can connect to. * Set the ports on which the HTTP binding service will be running.
* @throws Exception when there is an error configuring the HTTP binding ports. *
*/ * @param unsecurePort the unsecured connection port which clients can connect to.
public void setHttpBindPorts(int unsecurePort, int securePort) throws Exception { * @param securePort the secured connection port which clients can connect to.
if (unsecurePort != HTTP_BIND_PORT_DEFAULT) { * @throws Exception when there is an error configuring the HTTP binding ports.
JiveGlobals.setProperty(HTTP_BIND_PORT, String.valueOf(unsecurePort)); */
} public void setHttpBindPorts(int unsecurePort, int securePort) throws Exception {
else { if (unsecurePort != HTTP_BIND_PORT_DEFAULT) {
JiveGlobals.deleteProperty(HTTP_BIND_PORT); JiveGlobals.setProperty(HTTP_BIND_PORT, String.valueOf(unsecurePort));
} }
if (securePort != HTTP_BIND_SECURE_PORT_DEFAULT) { else {
JiveGlobals.setProperty(HTTP_BIND_SECURE_PORT, String.valueOf(securePort)); JiveGlobals.deleteProperty(HTTP_BIND_PORT);
} }
else { if (securePort != HTTP_BIND_SECURE_PORT_DEFAULT) {
JiveGlobals.deleteProperty(HTTP_BIND_SECURE_PORT); JiveGlobals.setProperty(HTTP_BIND_SECURE_PORT, String.valueOf(securePort));
} }
} else {
JiveGlobals.deleteProperty(HTTP_BIND_SECURE_PORT);
/** }
* Starts an HTTP Bind server on the specified port and secure port. }
*
* @param port the port to start the normal (unsecured) HTTP Bind service on. /**
* @param securePort the port to start the TLS (secure) HTTP Bind service on. * Starts an HTTP Bind server on the specified port and secure port.
*/ *
private synchronized void configureHttpBindServer(int port, int securePort) { * @param port the port to start the normal (unsecured) HTTP Bind service on.
// this is the number of threads allocated to each connector/port * @param securePort the port to start the TLS (secure) HTTP Bind service on.
int bindThreads = JiveGlobals.getIntProperty(HTTP_BIND_THREADS, HTTP_BIND_THREADS_DEFAULT); */
private synchronized void configureHttpBindServer(int port, int securePort) {
final QueuedThreadPool tp = new QueuedThreadPool(); // this is the number of threads allocated to each connector/port
tp.setName("Jetty-QTP-BOSH"); int bindThreads = JiveGlobals.getIntProperty(HTTP_BIND_THREADS, HTTP_BIND_THREADS_DEFAULT);
httpBindServer = new Server(tp); final QueuedThreadPool tp = new QueuedThreadPool();
if (JMXManager.isEnabled()) { tp.setName("Jetty-QTP-BOSH");
JMXManager jmx = JMXManager.getInstance();
httpBindServer.addBean(jmx.getContainer()); httpBindServer = new Server(tp);
} if (JMXManager.isEnabled()) {
JMXManager jmx = JMXManager.getInstance();
createConnector(port, bindThreads); httpBindServer.addBean(jmx.getContainer());
createSSLConnector(securePort, bindThreads); }
if (httpConnector == null && httpsConnector == null) {
httpBindServer = null; createConnector(port, bindThreads);
return; createSSLConnector(securePort, bindThreads);
} if (httpConnector == null && httpsConnector == null) {
if (httpConnector != null) { httpBindServer = null;
httpBindServer.addConnector(httpConnector); return;
} }
if (httpsConnector != null) { if (httpConnector != null) {
httpBindServer.addConnector(httpsConnector); httpBindServer.addConnector(httpConnector);
} }
if (httpsConnector != null) {
//contexts = new ContextHandlerCollection(); httpBindServer.addConnector(httpsConnector);
// TODO implement a way to get plugins to add their their web services to contexts }
createBoshHandler(contexts, "/http-bind"); //contexts = new ContextHandlerCollection();
createCrossDomainHandler(contexts, "/crossdomain.xml"); // TODO implement a way to get plugins to add their their web services to contexts
loadStaticDirectory(contexts);
createBoshHandler(contexts, "/http-bind");
HandlerCollection collection = new HandlerCollection(); createCrossDomainHandler(contexts, "/crossdomain.xml");
httpBindServer.setHandler(collection); loadStaticDirectory(contexts);
collection.setHandlers(new Handler[] { contexts, new DefaultHandler() });
} HandlerCollection collection = new HandlerCollection();
httpBindServer.setHandler(collection);
private void createBoshHandler(ContextHandlerCollection contexts, String boshPath) collection.setHandlers(new Handler[] { contexts, new DefaultHandler() });
{ }
ServletContextHandler context = new ServletContextHandler(contexts, boshPath, ServletContextHandler.SESSIONS);
private void createBoshHandler(ContextHandlerCollection contexts, String boshPath)
// Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs). {
final List<ContainerInitializer> initializers = new ArrayList<>(); ServletContextHandler context = new ServletContextHandler(contexts, boshPath, ServletContextHandler.SESSIONS);
initializers.add(new ContainerInitializer(new JettyJasperInitializer(), null)); // Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs).
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers); final List<ContainerInitializer> initializers = new ArrayList<>();
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager()); initializers.add(new ContainerInitializer(new JettyJasperInitializer(), null));
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
context.addServlet(new ServletHolder(new HttpBindServlet()),"/*"); context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
if (isHttpCompressionEnabled()) { context.addServlet(new ServletHolder(new HttpBindServlet()),"/*");
Filter gzipFilter = new AsyncGzipFilter() { if (isHttpCompressionEnabled()) {
@Override Filter gzipFilter = new AsyncGzipFilter() {
public void init(FilterConfig config) throws ServletException { @Override
super.init(config); public void init(FilterConfig config) throws ServletException {
_methods.add(HttpMethod.POST.asString()); super.init(config);
Log.info("Installed response compression filter"); _methods.add(HttpMethod.POST.asString());
} Log.info("Installed response compression filter");
}; }
FilterHolder filterHolder = new FilterHolder(); };
filterHolder.setFilter(gzipFilter); FilterHolder filterHolder = new FilterHolder();
context.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST)); filterHolder.setFilter(gzipFilter);
} context.addFilter(filterHolder, "/*", EnumSet.of(DispatcherType.REQUEST));
} }
}
// NOTE: enabled by default
private boolean isHttpCompressionEnabled() { // NOTE: enabled by default
return isCompressionEnabled; private boolean isHttpCompressionEnabled() {
} return isCompressionEnabled;
}
private void createCrossDomainHandler(ContextHandlerCollection contexts, String crossPath)
{ private void createCrossDomainHandler(ContextHandlerCollection contexts, String crossPath)
ServletContextHandler context = new ServletContextHandler(contexts, crossPath, ServletContextHandler.SESSIONS); {
ServletContextHandler context = new ServletContextHandler(contexts, crossPath, ServletContextHandler.SESSIONS);
// Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs). // Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs).
final List<ContainerInitializer> initializers = new ArrayList<>(); final List<ContainerInitializer> initializers = new ArrayList<>();
initializers.add(new ContainerInitializer(new JettyJasperInitializer(), null)); initializers.add(new ContainerInitializer(new JettyJasperInitializer(), null));
context.setAttribute("org.eclipse.jetty.containerInitializers", initializers); context.setAttribute("org.eclipse.jetty.containerInitializers", initializers);
context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager()); context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager());
context.addServlet(new ServletHolder(new FlashCrossDomainServlet()),"");
context.addServlet(new ServletHolder(new FlashCrossDomainServlet()),""); }
}
private void loadStaticDirectory(ContextHandlerCollection contexts) {
private void loadStaticDirectory(ContextHandlerCollection contexts) { File spankDirectory = new File(JiveGlobals.getHomeDirectory() + File.separator
File spankDirectory = new File(JiveGlobals.getHomeDirectory() + File.separator + "resources" + File.separator + "spank");
+ "resources" + File.separator + "spank"); if (spankDirectory.exists()) {
if (spankDirectory.exists()) { if (spankDirectory.canRead()) {
if (spankDirectory.canRead()) { WebAppContext context = new WebAppContext(contexts, spankDirectory.getPath(), "/");
WebAppContext context = new WebAppContext(contexts, spankDirectory.getPath(), "/"); context.setWelcomeFiles(new String[]{"index.html"});
context.setWelcomeFiles(new String[]{"index.html"}); }
} else {
else { Log.warn("Openfire cannot read the directory: " + spankDirectory);
Log.warn("Openfire cannot read the directory: " + spankDirectory); }
} }
} }
}
public ContextHandlerCollection getContexts() {
public ContextHandlerCollection getContexts() { return contexts;
return contexts; }
}
private void doEnableHttpBind(boolean shouldEnable) {
private void doEnableHttpBind(boolean shouldEnable) { if (shouldEnable && httpBindServer == null) {
if (shouldEnable && httpBindServer == null) { start();
start(); }
} else if (!shouldEnable && httpBindServer != null) {
else if (!shouldEnable && httpBindServer != null) { stop();
stop(); }
} }
}
/**
/** * Returns the HTTP binding port which does not use SSL.
* Returns the HTTP binding port which does not use SSL. *
* * @return the HTTP binding port which does not use SSL.
* @return the HTTP binding port which does not use SSL. */
*/ public int getHttpBindUnsecurePort() {
public int getHttpBindUnsecurePort() { return JiveGlobals.getIntProperty(HTTP_BIND_PORT, HTTP_BIND_PORT_DEFAULT);
return JiveGlobals.getIntProperty(HTTP_BIND_PORT, HTTP_BIND_PORT_DEFAULT); }
}
/**
/** * Returns the HTTP binding port which uses SSL.
* Returns the HTTP binding port which uses SSL. *
* * @return the HTTP binding port which uses SSL.
* @return the HTTP binding port which uses SSL. */
*/ public int getHttpBindSecurePort() {
public int getHttpBindSecurePort() { return JiveGlobals.getIntProperty(HTTP_BIND_SECURE_PORT, HTTP_BIND_SECURE_PORT_DEFAULT);
return JiveGlobals.getIntProperty(HTTP_BIND_SECURE_PORT, HTTP_BIND_SECURE_PORT_DEFAULT); }
}
/**
/** * Returns true if script syntax is enabled. Script syntax allows BOSH to be used in
* Returns true if script syntax is enabled. Script syntax allows BOSH to be used in * environments where clients may be restricted to using a particular server. Instead of using
* environments where clients may be restricted to using a particular server. Instead of using * standard HTTP Post requests to transmit data, HTTP Get requests are used.
* standard HTTP Post requests to transmit data, HTTP Get requests are used. *
* * @return true if script syntax is enabled.
* @return true if script syntax is enabled. * @see <a href="http://www.xmpp.org/extensions/xep-0124.html#script">BOSH: Alternative Script
* @see <a href="http://www.xmpp.org/extensions/xep-0124.html#script">BOSH: Alternative Script * Syntax</a>
* Syntax</a> */
*/ public boolean isScriptSyntaxEnabled() {
public boolean isScriptSyntaxEnabled() { return JiveGlobals.getBooleanProperty("xmpp.httpbind.scriptSyntax.enabled", false);
return JiveGlobals.getBooleanProperty("xmpp.httpbind.scriptSyntax.enabled", false); }
}
/**
/** * Enables or disables script syntax.
* Enables or disables script syntax. *
* * @param isEnabled true to enable script syntax and false to disable it.
* @param isEnabled true to enable script syntax and false to disable it. * @see #isScriptSyntaxEnabled()
* @see #isScriptSyntaxEnabled() * @see <a href="http://www.xmpp.org/extensions/xep-0124.html#script">BOSH: Alternative Script
* @see <a href="http://www.xmpp.org/extensions/xep-0124.html#script">BOSH: Alternative Script * Syntax</a>
* Syntax</a> */
*/ public void setScriptSyntaxEnabled(boolean isEnabled) {
public void setScriptSyntaxEnabled(boolean isEnabled) { final String property = "xmpp.httpbind.scriptSyntax.enabled";
final String property = "xmpp.httpbind.scriptSyntax.enabled"; if(!isEnabled) {
if(!isEnabled) { JiveGlobals.deleteProperty(property);
JiveGlobals.deleteProperty(property); }
} else {
else { JiveGlobals.setProperty(property, String.valueOf(isEnabled));
JiveGlobals.setProperty(property, String.valueOf(isEnabled)); }
} }
}
private void setUnsecureHttpBindPort(int value) {
private void setUnsecureHttpBindPort(int value) { if (value == bindPort) {
if (value == bindPort) { return;
return; }
} restartServer();
restartServer(); }
}
private void setSecureHttpBindPort(int value) {
private void setSecureHttpBindPort(int value) { if (value == bindSecurePort) {
if (value == bindSecurePort) { return;
return; }
} restartServer();
restartServer(); }
}
private synchronized void restartServer() {
private synchronized void restartServer() { stop();
stop(); start();
start(); }
}
/** Listens for changes to Jive properties that affect the HTTP server manager. */
/** Listens for changes to Jive properties that affect the HTTP server manager. */ private class HttpServerPropertyListener implements PropertyEventListener {
private class HttpServerPropertyListener implements PropertyEventListener {
@Override
@Override public void propertySet(String property, Map<String, Object> params) {
public void propertySet(String property, Map<String, Object> params) { if (property.equalsIgnoreCase(HTTP_BIND_ENABLED)) {
if (property.equalsIgnoreCase(HTTP_BIND_ENABLED)) { doEnableHttpBind(Boolean.valueOf(params.get("value").toString()));
doEnableHttpBind(Boolean.valueOf(params.get("value").toString())); }
} else if (property.equalsIgnoreCase(HTTP_BIND_PORT)) {
else if (property.equalsIgnoreCase(HTTP_BIND_PORT)) { int value;
int value; try {
try { value = Integer.valueOf(params.get("value").toString());
value = Integer.valueOf(params.get("value").toString()); }
} catch (NumberFormatException ne) {
catch (NumberFormatException ne) { JiveGlobals.deleteProperty(HTTP_BIND_PORT);
JiveGlobals.deleteProperty(HTTP_BIND_PORT); return;
return; }
} setUnsecureHttpBindPort(value);
setUnsecureHttpBindPort(value); }
} else if (property.equalsIgnoreCase(HTTP_BIND_SECURE_PORT)) {
else if (property.equalsIgnoreCase(HTTP_BIND_SECURE_PORT)) { int value;
int value; try {
try { value = Integer.valueOf(params.get("value").toString());
value = Integer.valueOf(params.get("value").toString()); }
} catch (NumberFormatException ne) {
catch (NumberFormatException ne) { JiveGlobals.deleteProperty(HTTP_BIND_SECURE_PORT);
JiveGlobals.deleteProperty(HTTP_BIND_SECURE_PORT); return;
return; }
} setSecureHttpBindPort(value);
setSecureHttpBindPort(value); }
} else if (HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY.equalsIgnoreCase( property )) {
else if (HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY.equalsIgnoreCase( property )) { restartServer();
restartServer(); }
} }
}
@Override
@Override public void propertyDeleted(String property, Map<String, Object> params) {
public void propertyDeleted(String property, Map<String, Object> params) { if (property.equalsIgnoreCase(HTTP_BIND_ENABLED)) {
if (property.equalsIgnoreCase(HTTP_BIND_ENABLED)) { doEnableHttpBind(HTTP_BIND_ENABLED_DEFAULT);
doEnableHttpBind(HTTP_BIND_ENABLED_DEFAULT); }
} else if (property.equalsIgnoreCase(HTTP_BIND_PORT)) {
else if (property.equalsIgnoreCase(HTTP_BIND_PORT)) { setUnsecureHttpBindPort(HTTP_BIND_PORT_DEFAULT);
setUnsecureHttpBindPort(HTTP_BIND_PORT_DEFAULT); }
} else if (property.equalsIgnoreCase(HTTP_BIND_SECURE_PORT)) {
else if (property.equalsIgnoreCase(HTTP_BIND_SECURE_PORT)) { setSecureHttpBindPort(HTTP_BIND_SECURE_PORT_DEFAULT);
setSecureHttpBindPort(HTTP_BIND_SECURE_PORT_DEFAULT); }
} else if (HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY.equalsIgnoreCase( property )) {
else if (HTTP_BIND_AUTH_PER_CLIENTCERT_POLICY.equalsIgnoreCase( property )) { restartServer();
restartServer(); }
} }
}
@Override
@Override public void xmlPropertySet(String property, Map<String, Object> params) {
public void xmlPropertySet(String property, Map<String, Object> params) { }
}
@Override
@Override public void xmlPropertyDeleted(String property, Map<String, Object> params) {
public void xmlPropertyDeleted(String property, Map<String, Object> params) { }
} }
}
private class CertificateListener implements CertificateEventListener {
private class CertificateListener implements CertificateEventListener {
@Override
@Override public void certificateCreated(KeyStore keyStore, String alias, X509Certificate cert) {
public void certificateCreated(KeyStore keyStore, String alias, X509Certificate cert) { // If new certificate is RSA then (re)start the HTTPS service
// If new certificate is RSA then (re)start the HTTPS service if ("RSA".equals(cert.getPublicKey().getAlgorithm())) {
if ("RSA".equals(cert.getPublicKey().getAlgorithm())) { restartServer();
restartServer(); }
} }
}
@Override
@Override public void certificateDeleted(KeyStore keyStore, String alias) {
public void certificateDeleted(KeyStore keyStore, String alias) { restartServer();
restartServer(); }
}
@Override
@Override public void certificateSigned(KeyStore keyStore, String alias,
public void certificateSigned(KeyStore keyStore, String alias, List<X509Certificate> certificates) {
List<X509Certificate> certificates) { // If new certificate is RSA then (re)start the HTTPS service
// If new certificate is RSA then (re)start the HTTPS service if ("RSA".equals(certificates.get(0).getPublicKey().getAlgorithm())) {
if ("RSA".equals(certificates.get(0).getPublicKey().getAlgorithm())) { restartServer();
restartServer(); }
} }
} }
} }
}
...@@ -54,7 +54,7 @@ public abstract class CertificateStoreConfig ...@@ -54,7 +54,7 @@ public abstract class CertificateStoreConfig
{ {
try try
{ {
this.canonicalPath = SSLConfig.canonicalize( path ); this.canonicalPath = Purpose.canonicalize( path );
final File file = new File( canonicalPath ); final File file = new File( canonicalPath );
if ( createIfAbsent && !file.exists() ) if ( createIfAbsent && !file.exists() )
......
package org.jivesoftware.openfire.keystore; package org.jivesoftware.openfire.keystore;
import org.jivesoftware.util.JiveGlobals;
import java.io.File;
import java.io.IOException;
/** /**
* Potential intended usages for keystores * Potential intended usages (for TLS connectivity).
* *
* @author Guus der Kinderen, guus.der.kinderen@gmail.com * @author Guus der Kinderen, guus.der.kinderen@gmail.com
*/ */
public enum Purpose public enum Purpose
{ {
/** /**
* Identification of this Openfire instance used by regular socket-based connections. * Socket-based server-to-server (XMPP federation) connectivity.
*/ */
SOCKETBASED_IDENTITYSTORE( false ), SOCKET_S2S( "xmpp.socket.ssl.", null ),
/** /**
* Identification of remote servers that you choose to trust, applies to server-to-server federation via regular socket-based connections. * Socket-based client connectivity.
*/ */
SOCKETBASED_S2S_TRUSTSTORE( true ), SOCKET_C2S( "xmpp.socket.ssl.client.", null ),
/** /**
* Identification of clients that you choose to trust, applies to mutual authentication via regular socket-based connections. * BOSH (HTTP-bind) based client connectivity.
*/ */
SOCKETBASED_C2S_TRUSTSTORE( true ), BOSH_C2S( "xmpp.bosh.ssl.client.", SOCKET_C2S),
/** /**
* Identification of this Openfire instance used by regular BOSH (HTTP-bind) connections. * Generic administrative services (eg: user providers).
*/ */
BOSHBASED_IDENTITYSTORE( false ), ADMIN( "admin.ssl.", SOCKET_S2S),
/** /**
* Identification of clients that you choose to trust, applies to mutual authentication via BOSH (HTTP-bind) connections. * Openfire web-admin console.
*/ */
BOSHBASED_C2S_TRUSTSTORE( true ), WEBADMIN( "admin.web.ssl.", ADMIN);
/** String prefix;
* Identification of this Openfire instance used by connections to administrative services (eg: user providers). Purpose fallback;
*/ Purpose( String prefix, Purpose fallback) {
ADMINISTRATIVE_IDENTITYSTORE( false ), this.prefix = prefix;
this.fallback = fallback;
}
/** public String getPrefix()
* Identification of remote applications/servers that provide administrative functionality (eg: user providers). {
*/ return prefix;
ADMINISTRATIVE_TRUSTSTORE( true ), }
/** public Purpose getFallback()
* Openfire web-admin console. {
*/ return fallback;
WEBADMIN_IDENTITYSTORE( false ), }
/** public String getIdentityStoreType()
* Openfire web-admin console. {
*/ final String propertyName = prefix + "storeType";
WEBADMIN_TRUSTSTORE( true ); final String defaultValue = "jks";
private final boolean isTrustStore; if ( fallback == null )
{
return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
}
else
{
return JiveGlobals.getProperty( propertyName, fallback.getIdentityStoreType() ).trim();
}
}
Purpose( boolean isTrustStore ) public String getTrustStoreType()
{ {
this.isTrustStore = isTrustStore; return getIdentityStoreType();
} }
public boolean isIdentityStore() public String getIdentityStorePassword()
{ {
return !isTrustStore; final String propertyName = prefix + "keypass";
final String defaultValue = "changeit";
if ( fallback == null )
{
return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
}
else
{
return JiveGlobals.getProperty( propertyName, fallback.getIdentityStorePassword() ).trim();
}
} }
public boolean isTrustStore() public String getTrustStorePassword()
{ {
return isTrustStore; final String propertyName = prefix + "trustpass";
final String defaultValue = "changeit";
if ( fallback == null )
{
return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
}
else
{
return JiveGlobals.getProperty( propertyName, fallback.getTrustStorePassword() ).trim();
}
} }
public boolean acceptSelfSigned()
{
// TODO these are new properties! Deprecate (migrate?) all existing 'accept-selfsigned properties' (Eg: org.jivesoftware.openfire.session.ConnectionSettings.Server.TLS_ACCEPT_SELFSIGNED_CERTS )
final String propertyName = prefix + "certificate.accept-selfsigned";
final boolean defaultValue = false;
if ( fallback == null )
{
return JiveGlobals.getBooleanProperty( propertyName, defaultValue );
}
else
{
return JiveGlobals.getBooleanProperty( propertyName, fallback.acceptSelfSigned() );
}
}
public boolean verifyValidity()
{
// TODO these are new properties! Deprecate (migrate?) all existing 'verify / verify-validity properties' (Eg: org.jivesoftware.openfire.session.ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY_VALIDITY )
final String propertyName = prefix + "certificate.verify.validity";
final boolean defaultValue = true;
if ( fallback == null )
{
return JiveGlobals.getBooleanProperty( propertyName, defaultValue );
}
else
{
return JiveGlobals.getBooleanProperty( propertyName, fallback.acceptSelfSigned() );
}
}
public String getIdentityStoreLocation() throws IOException
{
return canonicalize( getIdentityStoreLocation() );
}
public String getIdentityStoreLocationNonCanonicalized()
{
final String propertyName = prefix + "keystore";
final String defaultValue = "resources" + File.separator + "security" + File.separator + "keystore";
if ( fallback == null )
{
return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
}
else
{
return JiveGlobals.getProperty( propertyName, fallback.getIdentityStoreLocationNonCanonicalized() ).trim();
}
}
public String getTrustStoreLocation() throws IOException
{
return canonicalize( getTrustStoreLocation() );
}
public String getTrustStoreLocationNonCanonicalized()
{
final String propertyName = prefix + "truststore";
final String defaultValue = "resources" + File.separator + "security" + File.separator + "truststore";
if ( fallback == null )
{
return JiveGlobals.getProperty( propertyName, defaultValue ).trim();
}
else
{
return JiveGlobals.getProperty( propertyName, fallback.getTrustStoreLocationNonCanonicalized() ).trim();
}
}
public static String canonicalize( String path ) throws IOException
{
File file = new File( path );
if (!file.isAbsolute()) {
file = new File( JiveGlobals.getHomeDirectory() + File.separator + path );
}
return file.getCanonicalPath();
}
} }
/** /**
* $RCSfile$ * $RCSfile$
* $Revision: $ * $Revision: $
* $Date: $ * $Date: $
* *
* Copyright (C) 2005-2008 Jive Software. All rights reserved. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.net; package org.jivesoftware.openfire.net;
import java.net.UnknownHostException; import java.io.UnsupportedEncodingException;
import java.nio.charset.StandardCharsets; import java.net.UnknownHostException;
import java.security.KeyStore; import java.nio.charset.StandardCharsets;
import java.security.Security; import java.security.KeyStore;
import java.security.cert.Certificate; import java.security.Security;
import java.security.cert.X509Certificate; import java.security.cert.Certificate;
import java.util.ArrayList; import java.security.cert.X509Certificate;
import java.util.HashSet; import java.util.ArrayList;
import java.util.Iterator; import java.util.HashSet;
import java.util.Map; import java.util.Iterator;
import java.util.Set; import java.util.Map;
import java.util.StringTokenizer; import java.util.Set;
import java.util.TreeMap; import java.util.StringTokenizer;
import java.util.regex.Pattern; import java.util.TreeMap;
import java.util.regex.Pattern;
import javax.security.sasl.Sasl;
import javax.security.sasl.SaslException; import javax.security.sasl.Sasl;
import javax.security.sasl.SaslServer; import javax.security.sasl.SaslException;
import javax.security.sasl.SaslServer;
import org.dom4j.DocumentHelper;
import org.dom4j.Element; import org.dom4j.DocumentHelper;
import org.dom4j.Namespace; import org.dom4j.Element;
import org.dom4j.QName; import org.dom4j.Namespace;
import org.jivesoftware.openfire.Connection; import org.dom4j.QName;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.auth.AuthFactory; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken; import org.jivesoftware.openfire.auth.AuthFactory;
import org.jivesoftware.openfire.auth.AuthorizationManager; import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.keystore.Purpose; import org.jivesoftware.openfire.auth.AuthorizationManager;
import org.jivesoftware.openfire.lockout.LockOutManager; import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.session.ClientSession; import org.jivesoftware.openfire.lockout.LockOutManager;
import org.jivesoftware.openfire.session.ConnectionSettings; import org.jivesoftware.openfire.session.ClientSession;
import org.jivesoftware.openfire.session.IncomingServerSession; import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.openfire.session.LocalClientSession; import org.jivesoftware.openfire.session.IncomingServerSession;
import org.jivesoftware.openfire.session.LocalIncomingServerSession; import org.jivesoftware.openfire.session.LocalClientSession;
import org.jivesoftware.openfire.session.LocalSession; import org.jivesoftware.openfire.session.LocalIncomingServerSession;
import org.jivesoftware.openfire.session.Session; import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.util.CertificateManager; import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.StringUtils; import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger; import org.jivesoftware.util.StringUtils;
import org.slf4j.LoggerFactory; import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* SASLAuthentication is responsible for returning the available SASL mechanisms to use and for /**
* actually performing the SASL authentication.<p> * SASLAuthentication is responsible for returning the available SASL mechanisms to use and for
* * actually performing the SASL authentication.<p>
* The list of available SASL mechanisms is determined by: *
* <ol> * The list of available SASL mechanisms is determined by:
* <li>The type of {@link org.jivesoftware.openfire.user.UserProvider} being used since * <ol>
* some SASL mechanisms require the server to be able to retrieve user passwords</li> * <li>The type of {@link org.jivesoftware.openfire.user.UserProvider} being used since
* <li>Whether anonymous logins are enabled or not.</li> * some SASL mechanisms require the server to be able to retrieve user passwords</li>
* <li>Whether shared secret authentication is enabled or not.</li> * <li>Whether anonymous logins are enabled or not.</li>
* <li>Whether the underlying connection has been secured or not.</li> * <li>Whether shared secret authentication is enabled or not.</li>
* </ol> * <li>Whether the underlying connection has been secured or not.</li>
* * </ol>
* @author Hao Chen *
* @author Gaston Dombiak * @author Hao Chen
*/ * @author Gaston Dombiak
public class SASLAuthentication { */
public class SASLAuthentication {
private static final Logger Log = LoggerFactory.getLogger(SASLAuthentication.class);
private static final Logger Log = LoggerFactory.getLogger(SASLAuthentication.class);
// http://stackoverflow.com/questions/8571501/how-to-check-whether-the-string-is-base64-encoded-or-not
// plus an extra regex alternative to catch a single equals sign ('=', see RFC 6120 6.4.2) // http://stackoverflow.com/questions/8571501/how-to-check-whether-the-string-is-base64-encoded-or-not
private static final Pattern BASE64_ENCODED = Pattern.compile("^(=|([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==))$"); // plus an extra regex alternative to catch a single equals sign ('=', see RFC 6120 6.4.2)
private static final Pattern BASE64_ENCODED = Pattern.compile("^(=|([A-Za-z0-9+/]{4})*([A-Za-z0-9+/]{4}|[A-Za-z0-9+/]{3}=|[A-Za-z0-9+/]{2}==))$");
private static final String SASL_NAMESPACE = "xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"";
private static Map<String, ElementType> typeMap = new TreeMap<>(); private static final String SASL_NAMESPACE = "xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"";
private static Set<String> mechanisms = null; private static Map<String, ElementType> typeMap = new TreeMap<>();
static { private static Set<String> mechanisms = null;
initMechanisms();
} static {
initMechanisms();
public enum ElementType { }
ABORT("abort"), AUTH("auth"), RESPONSE("response"), CHALLENGE("challenge"), FAILURE("failure"), UNDEF(""); public enum ElementType {
private String name = null; ABORT("abort"), AUTH("auth"), RESPONSE("response"), CHALLENGE("challenge"), FAILURE("failure"), UNDEF("");
@Override private String name = null;
public String toString() {
return name; @Override
} public String toString() {
return name;
private ElementType(String name) { }
this.name = name;
typeMap.put(this.name, this); private ElementType(String name) {
} this.name = name;
typeMap.put(this.name, this);
public static ElementType valueof(String name) { }
if (name == null) {
return UNDEF; public static ElementType valueof(String name) {
} if (name == null) {
ElementType e = typeMap.get(name); return UNDEF;
return e != null ? e : UNDEF; }
} ElementType e = typeMap.get(name);
} return e != null ? e : UNDEF;
}
private enum Failure { }
ABORTED("aborted"), private enum Failure {
ACCOUNT_DISABLED("account-disabled"),
CREDENTIALS_EXPIRED("credentials-expired"), ABORTED("aborted"),
ENCRYPTION_REQUIRED("encryption-required"), ACCOUNT_DISABLED("account-disabled"),
INCORRECT_ENCODING("incorrect-encoding"), CREDENTIALS_EXPIRED("credentials-expired"),
INVALID_AUTHZID("invalid-authzid"), ENCRYPTION_REQUIRED("encryption-required"),
INVALID_MECHANISM("invalid-mechanism"), INCORRECT_ENCODING("incorrect-encoding"),
MALFORMED_REQUEST("malformed-request"), INVALID_AUTHZID("invalid-authzid"),
MECHANISM_TOO_WEAK("mechanism-too-weak"), INVALID_MECHANISM("invalid-mechanism"),
NOT_AUTHORIZED("not-authorized"), MALFORMED_REQUEST("malformed-request"),
TEMPORARY_AUTH_FAILURE("temporary-auth-failure"); MECHANISM_TOO_WEAK("mechanism-too-weak"),
NOT_AUTHORIZED("not-authorized"),
private String name = null; TEMPORARY_AUTH_FAILURE("temporary-auth-failure");
private Failure(String name) { private String name = null;
this.name = name;
} private Failure(String name) {
this.name = name;
@Override }
public String toString() {
return name; @Override
} public String toString() {
} return name;
}
public enum Status { }
/**
* Entity needs to respond last challenge. Session is still negotiating public enum Status {
* SASL authentication. /**
*/ * Entity needs to respond last challenge. Session is still negotiating
needResponse, * SASL authentication.
/** */
* SASL negotiation has failed. The entity may retry a few times before the connection needResponse,
* is closed. /**
*/ * SASL negotiation has failed. The entity may retry a few times before the connection
failed, * is closed.
/** */
* SASL negotiation has been successful. failed,
*/ /**
authenticated; * SASL negotiation has been successful.
} */
authenticated;
/** }
* Returns a string with the valid SASL mechanisms available for the specified session. If
* the session's connection is not secured then only include the SASL mechanisms that don't /**
* require TLS. * Returns a string with the valid SASL mechanisms available for the specified session. If
* * the session's connection is not secured then only include the SASL mechanisms that don't
* @param session The current session * require TLS.
* *
* @return a string with the valid SASL mechanisms available for the specified session. * @param session The current session
*/ *
public static String getSASLMechanisms(LocalSession session) { * @return a string with the valid SASL mechanisms available for the specified session.
if (!(session instanceof ClientSession) && !(session instanceof IncomingServerSession)) { */
return ""; public static String getSASLMechanisms(LocalSession session) {
} if (!(session instanceof ClientSession) && !(session instanceof IncomingServerSession)) {
Element mechs = getSASLMechanismsElement(session); return "";
return mechs.asXML(); }
} Element mechs = getSASLMechanismsElement(session);
return mechs.asXML();
public static Element getSASLMechanismsElement(Session session) { }
if (!(session instanceof ClientSession) && !(session instanceof IncomingServerSession)) {
return null; public static Element getSASLMechanismsElement(Session session) {
} if (!(session instanceof ClientSession) && !(session instanceof IncomingServerSession)) {
return null;
Element mechs = DocumentHelper.createElement( new QName( "mechanisms", }
new Namespace( "", "urn:ietf:params:xml:ns:xmpp-sasl" ) ) );
if (session instanceof LocalIncomingServerSession) { Element mechs = DocumentHelper.createElement( new QName( "mechanisms",
// Server connections don't follow the same rules as clients new Namespace( "", "urn:ietf:params:xml:ns:xmpp-sasl" ) ) );
if (session.isSecure()) { if (session instanceof LocalIncomingServerSession) {
LocalIncomingServerSession svr = (LocalIncomingServerSession)session; // Server connections don't follow the same rules as clients
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ); if (session.isSecure()) {
final KeyStore trustStore = SSLConfig.getStore( Purpose.SOCKETBASED_S2S_TRUSTSTORE ); LocalIncomingServerSession svr = (LocalIncomingServerSession)session;
final X509Certificate trusted = CertificateManager.getEndEntityCertificate( svr.getConnection().getPeerCertificates(), keyStore, trustStore ); final KeyStore keyStore = SSLConfig.getIdentityStore( Purpose.SOCKET_S2S );
final KeyStore trustStore = SSLConfig.getTrustStore( Purpose.SOCKET_S2S );
boolean haveTrustedCertificate = trusted != null; final X509Certificate trusted = CertificateManager.getEndEntityCertificate( svr.getConnection().getPeerCertificates(), keyStore, trustStore );
if (trusted != null && svr.getDefaultIdentity() != null) {
haveTrustedCertificate = verifyCertificate(trusted, svr.getDefaultIdentity()); boolean haveTrustedCertificate = trusted != null;
} if (trusted != null && svr.getDefaultIdentity() != null) {
if (haveTrustedCertificate) { haveTrustedCertificate = verifyCertificate(trusted, svr.getDefaultIdentity());
// Offer SASL EXTERNAL only if TLS has already been negotiated and the peer has a trusted cert. }
Element mechanism = mechs.addElement("mechanism"); if (haveTrustedCertificate) {
mechanism.setText("EXTERNAL"); // Offer SASL EXTERNAL only if TLS has already been negotiated and the peer has a trusted cert.
} Element mechanism = mechs.addElement("mechanism");
} mechanism.setText("EXTERNAL");
} }
else { }
for (String mech : getSupportedMechanisms()) { }
Element mechanism = mechs.addElement("mechanism"); else {
mechanism.setText(mech); for (String mech : getSupportedMechanisms()) {
} Element mechanism = mechs.addElement("mechanism");
} mechanism.setText(mech);
return mechs; }
} }
return mechs;
/** }
* Handles the SASL authentication packet. The entity may be sending an initial
* authentication request or a response to a challenge made by the server. The returned /**
* value indicates whether the authentication has finished either successfully or not or * Handles the SASL authentication packet. The entity may be sending an initial
* if the entity is expected to send a response to a challenge. * authentication request or a response to a challenge made by the server. The returned
* * value indicates whether the authentication has finished either successfully or not or
* @param session the session that is authenticating with the server. * if the entity is expected to send a response to a challenge.
* @param doc the stanza sent by the authenticating entity. *
* @return value that indicates whether the authentication has finished either successfully * @param session the session that is authenticating with the server.
* or not or if the entity is expected to send a response to a challenge. * @param doc the stanza sent by the authenticating entity.
*/ * @return value that indicates whether the authentication has finished either successfully
public static Status handle(LocalSession session, Element doc) { * or not or if the entity is expected to send a response to a challenge.
Status status; */
String mechanism; public static Status handle(LocalSession session, Element doc) {
if (doc.getNamespace().asXML().equals(SASL_NAMESPACE)) { Status status;
ElementType type = ElementType.valueof(doc.getName()); String mechanism;
switch (type) { if (doc.getNamespace().asXML().equals(SASL_NAMESPACE)) {
case ABORT: ElementType type = ElementType.valueof(doc.getName());
authenticationFailed(session, Failure.ABORTED); switch (type) {
status = Status.failed; case ABORT:
break; authenticationFailed(session, Failure.ABORTED);
case AUTH: status = Status.failed;
mechanism = doc.attributeValue("mechanism"); break;
// http://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-mechanism case AUTH:
// The initiating entity did not specify a mechanism mechanism = doc.attributeValue("mechanism");
if (mechanism == null) { // http://xmpp.org/rfcs/rfc6120.html#sasl-errors-invalid-mechanism
authenticationFailed(session, Failure.INVALID_MECHANISM); // The initiating entity did not specify a mechanism
status = Status.failed; if (mechanism == null) {
break; authenticationFailed(session, Failure.INVALID_MECHANISM);
} status = Status.failed;
// Store the requested SASL mechanism by the client break;
session.setSessionData("SaslMechanism", mechanism); }
//Log.debug("SASLAuthentication.doHandshake() AUTH entered: "+mechanism); // Store the requested SASL mechanism by the client
if (mechanism.equalsIgnoreCase("ANONYMOUS") && session.setSessionData("SaslMechanism", mechanism);
mechanisms.contains("ANONYMOUS")) { //Log.debug("SASLAuthentication.doHandshake() AUTH entered: "+mechanism);
status = doAnonymousAuthentication(session); if (mechanism.equalsIgnoreCase("ANONYMOUS") &&
} mechanisms.contains("ANONYMOUS")) {
else if (mechanism.equalsIgnoreCase("EXTERNAL")) { status = doAnonymousAuthentication(session);
status = doExternalAuthentication(session, doc); }
} else if (mechanism.equalsIgnoreCase("EXTERNAL")) {
else if (mechanisms.contains(mechanism)) { status = doExternalAuthentication(session, doc);
// The selected SASL mechanism requires the server to send a challenge }
// to the client else if (mechanisms.contains(mechanism)) {
try { // The selected SASL mechanism requires the server to send a challenge
Map<String, String> props = new TreeMap<>(); // to the client
props.put(Sasl.QOP, "auth"); try {
if (mechanism.equals("GSSAPI")) { Map<String, String> props = new TreeMap<>();
props.put(Sasl.SERVER_AUTH, "TRUE"); props.put(Sasl.QOP, "auth");
} if (mechanism.equals("GSSAPI")) {
SaslServer ss = Sasl.createSaslServer(mechanism, "xmpp", props.put(Sasl.SERVER_AUTH, "TRUE");
session.getServerName(), props, }
new XMPPCallbackHandler()); SaslServer ss = Sasl.createSaslServer(mechanism, "xmpp",
session.getServerName(), props,
if (ss == null) { new XMPPCallbackHandler());
authenticationFailed(session, Failure.INVALID_MECHANISM);
return Status.failed; if (ss == null) {
} authenticationFailed(session, Failure.INVALID_MECHANISM);
return Status.failed;
// evaluateResponse doesn't like null parameter }
byte[] token = new byte[0];
String value = doc.getTextTrim(); // evaluateResponse doesn't like null parameter
if (value.length() > 0) { byte[] token = new byte[0];
if (!BASE64_ENCODED.matcher(value).matches()) { String value = doc.getTextTrim();
authenticationFailed(session, Failure.INCORRECT_ENCODING); if (value.length() > 0) {
return Status.failed; if (!BASE64_ENCODED.matcher(value).matches()) {
} authenticationFailed(session, Failure.INCORRECT_ENCODING);
// If auth request includes a value then validate it return Status.failed;
token = StringUtils.decodeBase64(value); }
if (token == null) { // If auth request includes a value then validate it
token = new byte[0]; token = StringUtils.decodeBase64(value);
} if (token == null) {
} token = new byte[0];
if (mechanism.equals("DIGEST-MD5")) { }
// RFC2831 (DIGEST-MD5) says the client MAY provide an initial response on subsequent }
// authentication. Java SASL does not (currently) support this and thows an exception if (mechanism.equals("DIGEST-MD5")) {
// if we try. This violates the RFC, so we just strip any initial token. // RFC2831 (DIGEST-MD5) says the client MAY provide an initial response on subsequent
token = new byte[0]; // authentication. Java SASL does not (currently) support this and thows an exception
} // if we try. This violates the RFC, so we just strip any initial token.
byte[] challenge = ss.evaluateResponse(token); token = new byte[0];
if (ss.isComplete()) { }
authenticationSuccessful(session, ss.getAuthorizationID(), byte[] challenge = ss.evaluateResponse(token);
challenge); if (ss.isComplete()) {
status = Status.authenticated; authenticationSuccessful(session, ss.getAuthorizationID(),
} challenge);
else { status = Status.authenticated;
// Send the challenge }
sendChallenge(session, challenge); else {
status = Status.needResponse; // Send the challenge
} sendChallenge(session, challenge);
session.setSessionData("SaslServer", ss); status = Status.needResponse;
} }
catch (SaslException e) { session.setSessionData("SaslServer", ss);
Log.info("User Login Failed. " + e.getMessage()); }
authenticationFailed(session, Failure.NOT_AUTHORIZED); catch (SaslException e) {
status = Status.failed; Log.info("User Login Failed. " + e.getMessage());
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
} status = Status.failed;
else { }
Log.warn("Client wants to do a MECH we don't support: '" + }
mechanism + "'"); else {
authenticationFailed(session, Failure.INVALID_MECHANISM); Log.warn("Client wants to do a MECH we don't support: '" +
status = Status.failed; mechanism + "'");
} authenticationFailed(session, Failure.INVALID_MECHANISM);
break; status = Status.failed;
case RESPONSE: }
// Store the requested SASL mechanism by the client break;
mechanism = (String) session.getSessionData("SaslMechanism"); case RESPONSE:
if (mechanism.equalsIgnoreCase("EXTERNAL")) { // Store the requested SASL mechanism by the client
status = doExternalAuthentication(session, doc); mechanism = (String) session.getSessionData("SaslMechanism");
} if (mechanism.equalsIgnoreCase("EXTERNAL")) {
else if (mechanism.equalsIgnoreCase("JIVE-SHAREDSECRET")) { status = doExternalAuthentication(session, doc);
status = doSharedSecretAuthentication(session, doc); }
} else if (mechanism.equalsIgnoreCase("JIVE-SHAREDSECRET")) {
else if (mechanisms.contains(mechanism)) { status = doSharedSecretAuthentication(session, doc);
SaslServer ss = (SaslServer) session.getSessionData("SaslServer"); }
if (ss != null) { else if (mechanisms.contains(mechanism)) {
boolean ssComplete = ss.isComplete(); SaslServer ss = (SaslServer) session.getSessionData("SaslServer");
String response = doc.getTextTrim(); if (ss != null) {
if (response.length() > 0) { boolean ssComplete = ss.isComplete();
if (!BASE64_ENCODED.matcher(response).matches()) { String response = doc.getTextTrim();
authenticationFailed(session, Failure.INCORRECT_ENCODING); if (response.length() > 0) {
return Status.failed; if (!BASE64_ENCODED.matcher(response).matches()) {
} authenticationFailed(session, Failure.INCORRECT_ENCODING);
} return Status.failed;
try { }
if (ssComplete) { }
authenticationSuccessful(session, ss.getAuthorizationID(), try {
null); if (ssComplete) {
status = Status.authenticated; authenticationSuccessful(session, ss.getAuthorizationID(),
} null);
else { status = Status.authenticated;
byte[] data = StringUtils.decodeBase64(response); }
if (data == null) { else {
data = new byte[0]; byte[] data = StringUtils.decodeBase64(response);
} if (data == null) {
byte[] challenge = ss.evaluateResponse(data); data = new byte[0];
if (ss.isComplete()) { }
authenticationSuccessful(session, ss.getAuthorizationID(), byte[] challenge = ss.evaluateResponse(data);
challenge); if (ss.isComplete()) {
status = Status.authenticated; authenticationSuccessful(session, ss.getAuthorizationID(),
} challenge);
else { status = Status.authenticated;
// Send the challenge }
sendChallenge(session, challenge); else {
status = Status.needResponse; // Send the challenge
} sendChallenge(session, challenge);
} status = Status.needResponse;
} }
catch (SaslException e) { }
Log.debug("SASLAuthentication: SaslException", e); }
authenticationFailed(session, Failure.NOT_AUTHORIZED); catch (SaslException e) {
status = Status.failed; Log.debug("SASLAuthentication: SaslException", e);
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
} status = Status.failed;
else { }
Log.error("SaslServer is null, should be valid object instead."); }
authenticationFailed(session, Failure.NOT_AUTHORIZED); else {
status = Status.failed; Log.error("SaslServer is null, should be valid object instead.");
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
} status = Status.failed;
else { }
Log.warn( }
"Client responded to a MECH we don't support: '" + mechanism + "'"); else {
authenticationFailed(session, Failure.INVALID_MECHANISM); Log.warn(
status = Status.failed; "Client responded to a MECH we don't support: '" + mechanism + "'");
} authenticationFailed(session, Failure.INVALID_MECHANISM);
break; status = Status.failed;
default: }
authenticationFailed(session, Failure.NOT_AUTHORIZED); break;
status = Status.failed; default:
// Ignore authenticationFailed(session, Failure.NOT_AUTHORIZED);
break; status = Status.failed;
} // Ignore
} break;
else { }
Log.debug("SASLAuthentication: Unknown namespace sent in auth element: " + doc.asXML()); }
authenticationFailed(session, Failure.MALFORMED_REQUEST); else {
status = Status.failed; Log.debug("SASLAuthentication: Unknown namespace sent in auth element: " + doc.asXML());
} authenticationFailed(session, Failure.MALFORMED_REQUEST);
// Check if SASL authentication has finished so we can clean up temp information status = Status.failed;
if (status == Status.failed || status == Status.authenticated) { }
// Remove the SaslServer from the Session // Check if SASL authentication has finished so we can clean up temp information
session.removeSessionData("SaslServer"); if (status == Status.failed || status == Status.authenticated) {
// Remove the requested SASL mechanism by the client // Remove the SaslServer from the Session
session.removeSessionData("SaslMechanism"); session.removeSessionData("SaslServer");
} // Remove the requested SASL mechanism by the client
return status; session.removeSessionData("SaslMechanism");
} }
return status;
/** }
* Returns true if shared secret authentication is enabled. Shared secret
* authentication creates an anonymous session, but requires that the authenticating /**
* entity know a shared secret key. The client sends a digest of the secret key, * Returns true if shared secret authentication is enabled. Shared secret
* which is compared against a digest of the local shared key. * authentication creates an anonymous session, but requires that the authenticating
* * entity know a shared secret key. The client sends a digest of the secret key,
* @return true if shared secret authentication is enabled. * which is compared against a digest of the local shared key.
*/ *
public static boolean isSharedSecretAllowed() { * @return true if shared secret authentication is enabled.
return JiveGlobals.getBooleanProperty("xmpp.auth.sharedSecretEnabled"); */
} public static boolean isSharedSecretAllowed() {
return JiveGlobals.getBooleanProperty("xmpp.auth.sharedSecretEnabled");
/** }
* Sets whether shared secret authentication is enabled. Shared secret
* authentication creates an anonymous session, but requires that the authenticating /**
* entity know a shared secret key. The client sends a digest of the secret key, * Sets whether shared secret authentication is enabled. Shared secret
* which is compared against a digest of the local shared key. * authentication creates an anonymous session, but requires that the authenticating
* * entity know a shared secret key. The client sends a digest of the secret key,
* @param sharedSecretAllowed true if shared secret authentication should be enabled. * which is compared against a digest of the local shared key.
*/ *
public static void setSharedSecretAllowed(boolean sharedSecretAllowed) { * @param sharedSecretAllowed true if shared secret authentication should be enabled.
JiveGlobals.setProperty("xmpp.auth.sharedSecretEnabled", sharedSecretAllowed ? "true" : "false"); */
} public static void setSharedSecretAllowed(boolean sharedSecretAllowed) {
JiveGlobals.setProperty("xmpp.auth.sharedSecretEnabled", sharedSecretAllowed ? "true" : "false");
/** }
* Returns the shared secret value, or <tt>null</tt> if shared secret authentication is
* disabled. If this is the first time the shared secret value has been requested (and /**
* shared secret auth is enabled), the key will be randomly generated and stored in the * Returns the shared secret value, or <tt>null</tt> if shared secret authentication is
* property <tt>xmpp.auth.sharedSecret</tt>. * disabled. If this is the first time the shared secret value has been requested (and
* * shared secret auth is enabled), the key will be randomly generated and stored in the
* @return the shared secret value. * property <tt>xmpp.auth.sharedSecret</tt>.
*/ *
public static String getSharedSecret() { * @return the shared secret value.
if (!isSharedSecretAllowed()) { */
return null; public static String getSharedSecret() {
} if (!isSharedSecretAllowed()) {
String sharedSecret = JiveGlobals.getProperty("xmpp.auth.sharedSecret"); return null;
if (sharedSecret == null) { }
sharedSecret = StringUtils.randomString(8); String sharedSecret = JiveGlobals.getProperty("xmpp.auth.sharedSecret");
JiveGlobals.setProperty("xmpp.auth.sharedSecret", sharedSecret); if (sharedSecret == null) {
} sharedSecret = StringUtils.randomString(8);
return sharedSecret; JiveGlobals.setProperty("xmpp.auth.sharedSecret", sharedSecret);
} }
return sharedSecret;
/** }
* Returns true if the supplied digest matches the shared secret value. The digest
* must be an MD5 hash of the secret key, encoded as hex. This value is supplied /**
* by clients attempting shared secret authentication. * Returns true if the supplied digest matches the shared secret value. The digest
* * must be an MD5 hash of the secret key, encoded as hex. This value is supplied
* @param digest the MD5 hash of the secret key, encoded as hex. * by clients attempting shared secret authentication.
* @return true if authentication succeeds. *
*/ * @param digest the MD5 hash of the secret key, encoded as hex.
public static boolean authenticateSharedSecret(String digest) { * @return true if authentication succeeds.
if (!isSharedSecretAllowed()) { */
return false; public static boolean authenticateSharedSecret(String digest) {
} if (!isSharedSecretAllowed()) {
String sharedSecert = getSharedSecret(); return false;
return StringUtils.hash(sharedSecert).equals( digest ); }
} String sharedSecert = getSharedSecret();
return StringUtils.hash(sharedSecert).equals( digest );
}
private static Status doAnonymousAuthentication(LocalSession session) {
if (XMPPServer.getInstance().getIQAuthHandler().isAnonymousAllowed()) {
// Verify that client can connect from his IP address private static Status doAnonymousAuthentication(LocalSession session) {
boolean forbidAccess = false; if (XMPPServer.getInstance().getIQAuthHandler().isAnonymousAllowed()) {
try { // Verify that client can connect from his IP address
String hostAddress = session.getConnection().getHostAddress(); boolean forbidAccess = false;
if (!LocalClientSession.getAllowedAnonymIPs().isEmpty() && try {
!LocalClientSession.getAllowedAnonymIPs().containsKey(hostAddress)) { String hostAddress = session.getConnection().getHostAddress();
byte[] address = session.getConnection().getAddress(); if (!LocalClientSession.getAllowedAnonymIPs().isEmpty() &&
String range1 = (address[0] & 0xff) + "." + (address[1] & 0xff) + "." + !LocalClientSession.getAllowedAnonymIPs().containsKey(hostAddress)) {
(address[2] & 0xff) + byte[] address = session.getConnection().getAddress();
".*"; String range1 = (address[0] & 0xff) + "." + (address[1] & 0xff) + "." +
String range2 = (address[0] & 0xff) + "." + (address[1] & 0xff) + ".*.*"; (address[2] & 0xff) +
String range3 = (address[0] & 0xff) + ".*.*.*"; ".*";
if (!LocalClientSession.getAllowedAnonymIPs().containsKey(range1) && String range2 = (address[0] & 0xff) + "." + (address[1] & 0xff) + ".*.*";
!LocalClientSession.getAllowedAnonymIPs().containsKey(range2) && String range3 = (address[0] & 0xff) + ".*.*.*";
!LocalClientSession.getAllowedAnonymIPs().containsKey(range3)) { if (!LocalClientSession.getAllowedAnonymIPs().containsKey(range1) &&
forbidAccess = true; !LocalClientSession.getAllowedAnonymIPs().containsKey(range2) &&
} !LocalClientSession.getAllowedAnonymIPs().containsKey(range3)) {
} forbidAccess = true;
} catch (UnknownHostException e) { }
forbidAccess = true; }
} } catch (UnknownHostException e) {
if (forbidAccess) { forbidAccess = true;
authenticationFailed(session, Failure.NOT_AUTHORIZED); }
return Status.failed; if (forbidAccess) {
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
// Just accept the authentication :) return Status.failed;
authenticationSuccessful(session, null, null); }
return Status.authenticated; // Just accept the authentication :)
} authenticationSuccessful(session, null, null);
else { return Status.authenticated;
// anonymous login is disabled so close the connection }
authenticationFailed(session, Failure.NOT_AUTHORIZED); else {
return Status.failed; // anonymous login is disabled so close the connection
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
} return Status.failed;
}
private static Status doExternalAuthentication(LocalSession session, Element doc) { }
// At this point the connection has already been secured using TLS
private static Status doExternalAuthentication(LocalSession session, Element doc) {
if (session instanceof IncomingServerSession) { // At this point the connection has already been secured using TLS
String hostname = doc.getTextTrim(); if (session instanceof IncomingServerSession) {
if (hostname == null || hostname.length() == 0) {
// No hostname was provided so send a challenge to get it String hostname = doc.getTextTrim();
sendChallenge(session, new byte[0]); if (hostname == null || hostname.length() == 0) {
return Status.needResponse; // No hostname was provided so send a challenge to get it
} sendChallenge(session, new byte[0]);
return Status.needResponse;
hostname = new String(StringUtils.decodeBase64(hostname), StandardCharsets.UTF_8); }
if (hostname.length() == 0) {
hostname = null; hostname = new String(StringUtils.decodeBase64(hostname), StandardCharsets.UTF_8);
} if (hostname.length() == 0) {
try { hostname = null;
LocalIncomingServerSession svr = (LocalIncomingServerSession)session; }
String defHostname = svr.getDefaultIdentity(); try {
if (hostname == null) { LocalIncomingServerSession svr = (LocalIncomingServerSession)session;
hostname = defHostname; String defHostname = svr.getDefaultIdentity();
} else if (!hostname.equals(defHostname)) { if (hostname == null) {
// Mismatch; really odd. hostname = defHostname;
Log.info("SASLAuthentication rejected from='{}' and authzid='{}'", hostname, defHostname); } else if (!hostname.equals(defHostname)) {
authenticationFailed(session, Failure.NOT_AUTHORIZED); // Mismatch; really odd.
return Status.failed; Log.info("SASLAuthentication rejected from='{}' and authzid='{}'", hostname, defHostname);
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
} catch(Exception e) { return Status.failed;
// Erm. Nothing? }
} } catch(Exception e) {
if (hostname == null) { // Erm. Nothing?
Log.info("No authzid supplied for anonymous session."); }
authenticationFailed(session, Failure.NOT_AUTHORIZED); if (hostname == null) {
return Status.failed; Log.info("No authzid supplied for anonymous session.");
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
// Check if certificate validation is disabled for s2s return Status.failed;
// Flag that indicates if certificates of the remote server should be validated. }
// Disabling certificate validation is not recommended for production environments. // Check if certificate validation is disabled for s2s
boolean verify = // Flag that indicates if certificates of the remote server should be validated.
JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true); // Disabling certificate validation is not recommended for production environments.
if (!verify) { boolean verify =
authenticationSuccessful(session, hostname, null); JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_CERTIFICATE_VERIFY, true);
return Status.authenticated; if (!verify) {
} else if(verifyCertificates(session.getConnection().getPeerCertificates(), hostname, true)) { authenticationSuccessful(session, hostname, null);
authenticationSuccessful(session, hostname, null); return Status.authenticated;
LocalIncomingServerSession s = (LocalIncomingServerSession)session; } else if(verifyCertificates(session.getConnection().getPeerCertificates(), hostname, true)) {
if (s != null) { authenticationSuccessful(session, hostname, null);
s.tlsAuth(); LocalIncomingServerSession s = (LocalIncomingServerSession)session;
} if (s != null) {
return Status.authenticated; s.tlsAuth();
} }
} return Status.authenticated;
else if (session instanceof LocalClientSession) { }
// Client EXTERNAL login }
Log.debug("SASLAuthentication: EXTERNAL authentication via SSL certs for c2s connection"); else if (session instanceof LocalClientSession) {
// Client EXTERNAL login
// This may be null, we will deal with that later Log.debug("SASLAuthentication: EXTERNAL authentication via SSL certs for c2s connection");
String username = new String(StringUtils.decodeBase64(doc.getTextTrim()), StandardCharsets.UTF_8);
String principal = ""; // This may be null, we will deal with that later
ArrayList<String> principals = new ArrayList<>(); String username = new String(StringUtils.decodeBase64(doc.getTextTrim()), StandardCharsets.UTF_8);
Connection connection = session.getConnection(); String principal = "";
if (connection.getPeerCertificates().length < 1) { ArrayList<String> principals = new ArrayList<>();
Log.debug("SASLAuthentication: EXTERNAL authentication requested, but no certificates found."); Connection connection = session.getConnection();
authenticationFailed(session, Failure.NOT_AUTHORIZED); if (connection.getPeerCertificates().length < 1) {
return Status.failed; Log.debug("SASLAuthentication: EXTERNAL authentication requested, but no certificates found.");
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
return Status.failed;
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ); }
final KeyStore trustStore = SSLConfig.getStore( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
final X509Certificate trusted = CertificateManager.getEndEntityCertificate( connection.getPeerCertificates(), keyStore, trustStore ); final KeyStore keyStore = SSLConfig.getIdentityStore( Purpose.SOCKET_C2S );
final KeyStore trustStore = SSLConfig.getTrustStore( Purpose.SOCKET_C2S );
if (trusted == null) { final X509Certificate trusted = CertificateManager.getEndEntityCertificate( connection.getPeerCertificates(), keyStore, trustStore );
Log.debug("SASLAuthentication: EXTERNAL authentication requested, but EE cert untrusted.");
authenticationFailed(session, Failure.NOT_AUTHORIZED); if (trusted == null) {
return Status.failed; Log.debug("SASLAuthentication: EXTERNAL authentication requested, but EE cert untrusted.");
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
principals.addAll(CertificateManager.getClientIdentities(trusted)); return Status.failed;
}
if(principals.size() == 1) { principals.addAll(CertificateManager.getClientIdentities(trusted));
principal = principals.get(0);
} else if(principals.size() > 1) { if(principals.size() == 1) {
Log.debug("SASLAuthentication: EXTERNAL authentication: more than one principal found, using first."); principal = principals.get(0);
principal = principals.get(0); } else if(principals.size() > 1) {
} else { Log.debug("SASLAuthentication: EXTERNAL authentication: more than one principal found, using first.");
Log.debug("SASLAuthentication: EXTERNAL authentication: No principals found."); principal = principals.get(0);
} } else {
Log.debug("SASLAuthentication: EXTERNAL authentication: No principals found.");
if (username == null || username.length() == 0) { }
// No username was provided, according to XEP-0178 we need to:
// * attempt to get it from the cert first if (username == null || username.length() == 0) {
// * have the server assign one // No username was provided, according to XEP-0178 we need to:
// * attempt to get it from the cert first
// There shouldn't be more than a few principals in here. One ideally // * have the server assign one
// We set principal to the first one in the list to have a sane default
// If this list is empty, then the cert had no identity at all, which // There shouldn't be more than a few principals in here. One ideally
// will cause an authorization failure // We set principal to the first one in the list to have a sane default
for(String princ : principals) { // If this list is empty, then the cert had no identity at all, which
String u = AuthorizationManager.map(princ); // will cause an authorization failure
if(!u.equals(princ)) { for(String princ : principals) {
username = u; String u = AuthorizationManager.map(princ);
principal = princ; if(!u.equals(princ)) {
break; username = u;
} principal = princ;
} break;
if (username == null || username.length() == 0) { }
// Still no username. Punt. }
username = principal; if (username == null || username.length() == 0) {
} // Still no username. Punt.
Log.debug("SASLAuthentication: no username requested, using "+username); username = principal;
} }
Log.debug("SASLAuthentication: no username requested, using "+username);
//Its possible that either/both username and principal are null here }
//The providers should not allow a null authorization
if (AuthorizationManager.authorize(username,principal)) { //Its possible that either/both username and principal are null here
Log.debug("SASLAuthentication: "+principal+" authorized to "+username); //The providers should not allow a null authorization
authenticationSuccessful(session, username, null); if (AuthorizationManager.authorize(username,principal)) {
return Status.authenticated; Log.debug("SASLAuthentication: "+principal+" authorized to "+username);
} authenticationSuccessful(session, username, null);
} else { return Status.authenticated;
Log.debug("SASLAuthentication: unknown session type. Cannot perform EXTERNAL authentication"); }
} } else {
authenticationFailed(session, Failure.NOT_AUTHORIZED); Log.debug("SASLAuthentication: unknown session type. Cannot perform EXTERNAL authentication");
return Status.failed; }
} authenticationFailed(session, Failure.NOT_AUTHORIZED);
return Status.failed;
public static boolean verifyCertificate(X509Certificate trustedCert, String hostname) { }
for (String identity : CertificateManager.getServerIdentities(trustedCert)) {
// Verify that either the identity is the same as the hostname, or for wildcarded public static boolean verifyCertificate(X509Certificate trustedCert, String hostname) {
// identities that the hostname ends with .domainspecified or -is- domainspecified. for (String identity : CertificateManager.getServerIdentities(trustedCert)) {
if ((identity.startsWith("*.") // Verify that either the identity is the same as the hostname, or for wildcarded
&& (hostname.endsWith(identity.replace("*.", ".")) // identities that the hostname ends with .domainspecified or -is- domainspecified.
|| hostname.equals(identity.replace("*.", "")))) if ((identity.startsWith("*.")
|| hostname.equals(identity)) { && (hostname.endsWith(identity.replace("*.", "."))
return true; || hostname.equals(identity.replace("*.", ""))))
} || hostname.equals(identity)) {
} return true;
return false; }
} }
return false;
/** }
* @deprecated Use {@link #verifyCertificates(Certificate[], String, boolean)} instead.
*/ /**
@Deprecated * @deprecated Use {@link #verifyCertificates(Certificate[], String, boolean)} instead.
public static boolean verifyCertificates(Certificate[] chain, String hostname) { */
return verifyCertificates( chain, hostname, true ); @Deprecated
} public static boolean verifyCertificates(Certificate[] chain, String hostname) {
return verifyCertificates( chain, hostname, true );
public static boolean verifyCertificates(Certificate[] chain, String hostname, boolean isS2S) { }
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE );
final KeyStore trustStore = SSLConfig.getStore( isS2S ? Purpose.SOCKETBASED_S2S_TRUSTSTORE : Purpose.SOCKETBASED_C2S_TRUSTSTORE ); public static boolean verifyCertificates(Certificate[] chain, String hostname, boolean isS2S) {
final X509Certificate trusted = CertificateManager.getEndEntityCertificate( chain, keyStore, trustStore ); final Purpose purpose = isS2S ? Purpose.SOCKET_S2S : Purpose.SOCKET_C2S;
if (trusted != null) { final KeyStore keyStore = SSLConfig.getIdentityStore( purpose );
return verifyCertificate(trusted, hostname); final KeyStore trustStore = SSLConfig.getTrustStore( purpose );
} final X509Certificate trusted = CertificateManager.getEndEntityCertificate( chain, keyStore, trustStore );
return false; if (trusted != null) {
} return verifyCertificate(trusted, hostname);
}
private static Status doSharedSecretAuthentication(LocalSession session, Element doc) { return false;
String secretDigest; }
String response = doc.getTextTrim();
if (response == null || response.length() == 0) { private static Status doSharedSecretAuthentication(LocalSession session, Element doc) {
// No info was provided so send a challenge to get it String secretDigest;
sendChallenge(session, new byte[0]); String response = doc.getTextTrim();
return Status.needResponse; if (response == null || response.length() == 0) {
} // No info was provided so send a challenge to get it
sendChallenge(session, new byte[0]);
// Parse data and obtain username & password return Status.needResponse;
String data = new String(StringUtils.decodeBase64(response), StandardCharsets.UTF_8); }
StringTokenizer tokens = new StringTokenizer(data, "\0");
tokens.nextToken(); // Parse data and obtain username & password
secretDigest = tokens.nextToken(); String data = new String(StringUtils.decodeBase64(response), StandardCharsets.UTF_8);
if (authenticateSharedSecret(secretDigest)) { StringTokenizer tokens = new StringTokenizer(data, "\0");
authenticationSuccessful(session, null, null); tokens.nextToken();
return Status.authenticated; secretDigest = tokens.nextToken();
} if (authenticateSharedSecret(secretDigest)) {
// Otherwise, authentication failed. authenticationSuccessful(session, null, null);
authenticationFailed(session, Failure.NOT_AUTHORIZED); return Status.authenticated;
return Status.failed; }
} // Otherwise, authentication failed.
authenticationFailed(session, Failure.NOT_AUTHORIZED);
private static void sendChallenge(Session session, byte[] challenge) { return Status.failed;
StringBuilder reply = new StringBuilder(250); }
if (challenge == null) {
challenge = new byte[0]; private static void sendChallenge(Session session, byte[] challenge) {
} StringBuilder reply = new StringBuilder(250);
String challenge_b64 = StringUtils.encodeBase64(challenge).trim(); if (challenge == null) {
if ("".equals(challenge_b64)) { challenge = new byte[0];
challenge_b64 = "="; // Must be padded if null }
} String challenge_b64 = StringUtils.encodeBase64(challenge).trim();
reply.append( if ("".equals(challenge_b64)) {
"<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">"); challenge_b64 = "="; // Must be padded if null
reply.append(challenge_b64); }
reply.append("</challenge>"); reply.append(
session.deliverRawText(reply.toString()); "<challenge xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\">");
} reply.append(challenge_b64);
reply.append("</challenge>");
private static void authenticationSuccessful(LocalSession session, String username, session.deliverRawText(reply.toString());
byte[] successData) { }
if (username != null && LockOutManager.getInstance().isAccountDisabled(username)) {
// Interception! This person is locked out, fail instead! private static void authenticationSuccessful(LocalSession session, String username,
LockOutManager.getInstance().recordFailedLogin(username); byte[] successData) {
authenticationFailed(session, Failure.ACCOUNT_DISABLED); if (username != null && LockOutManager.getInstance().isAccountDisabled(username)) {
return; // Interception! This person is locked out, fail instead!
} LockOutManager.getInstance().recordFailedLogin(username);
StringBuilder reply = new StringBuilder(80); authenticationFailed(session, Failure.ACCOUNT_DISABLED);
reply.append("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\""); return;
if (successData != null) { }
String successData_b64 = StringUtils.encodeBase64(successData).trim(); StringBuilder reply = new StringBuilder(80);
reply.append('>').append(successData_b64).append("</success>"); reply.append("<success xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"");
} if (successData != null) {
else { String successData_b64 = StringUtils.encodeBase64(successData).trim();
reply.append("/>"); reply.append('>').append(successData_b64).append("</success>");
} }
session.deliverRawText( reply.toString() ); else {
// We only support SASL for c2s reply.append("/>");
if (session instanceof ClientSession) { }
((LocalClientSession) session).setAuthToken(new AuthToken(username)); session.deliverRawText( reply.toString() );
} // We only support SASL for c2s
else if (session instanceof IncomingServerSession) { if (session instanceof ClientSession) {
String hostname = username; ((LocalClientSession) session).setAuthToken(new AuthToken(username));
// Add the validated domain as a valid domain. The remote server can }
// now send packets from this address else if (session instanceof IncomingServerSession) {
((LocalIncomingServerSession) session).addValidatedDomain(hostname); String hostname = username;
Log.info("Inbound Server {} authenticated (via TLS)", username); // Add the validated domain as a valid domain. The remote server can
} // now send packets from this address
} ((LocalIncomingServerSession) session).addValidatedDomain(hostname);
Log.info("Inbound Server {} authenticated (via TLS)", username);
private static void authenticationFailed(LocalSession session, Failure failure) { }
StringBuilder reply = new StringBuilder(80); }
reply.append("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><");
reply.append(failure.toString()); private static void authenticationFailed(LocalSession session, Failure failure) {
reply.append("/></failure>"); StringBuilder reply = new StringBuilder(80);
session.deliverRawText(reply.toString()); reply.append("<failure xmlns=\"urn:ietf:params:xml:ns:xmpp-sasl\"><");
// Give a number of retries before closing the connection reply.append(failure.toString());
Integer retries = (Integer) session.getSessionData("authRetries"); reply.append("/></failure>");
if (retries == null) { session.deliverRawText(reply.toString());
retries = 1; // Give a number of retries before closing the connection
} Integer retries = (Integer) session.getSessionData("authRetries");
else { if (retries == null) {
retries = retries + 1; retries = 1;
} }
session.setSessionData("authRetries", retries); else {
if (retries >= JiveGlobals.getIntProperty("xmpp.auth.retries", 3) ) { retries = retries + 1;
// Close the connection }
session.close(); session.setSessionData("authRetries", retries);
} if (retries >= JiveGlobals.getIntProperty("xmpp.auth.retries", 3) ) {
} // Close the connection
session.close();
/** }
* Adds a new SASL mechanism to the list of supported SASL mechanisms by the server. The }
* new mechanism will be offered to clients and connection managers as stream features.<p>
* /**
* Note: this method simply registers the SASL mechanism to be advertised as a supported * Adds a new SASL mechanism to the list of supported SASL mechanisms by the server. The
* mechanism by Openfire. Actual SASL handling is done by Java itself, so you must add * new mechanism will be offered to clients and connection managers as stream features.<p>
* the provider to Java. *
* * Note: this method simply registers the SASL mechanism to be advertised as a supported
* @param mechanism the new SASL mechanism. * mechanism by Openfire. Actual SASL handling is done by Java itself, so you must add
*/ * the provider to Java.
public static void addSupportedMechanism(String mechanism) { *
mechanisms.add(mechanism); * @param mechanism the new SASL mechanism.
} */
public static void addSupportedMechanism(String mechanism) {
/** mechanisms.add(mechanism);
* Removes a SASL mechanism from the list of supported SASL mechanisms by the server. }
*
* @param mechanism the SASL mechanism to remove. /**
*/ * Removes a SASL mechanism from the list of supported SASL mechanisms by the server.
public static void removeSupportedMechanism(String mechanism) { *
mechanisms.remove(mechanism); * @param mechanism the SASL mechanism to remove.
} */
public static void removeSupportedMechanism(String mechanism) {
/** mechanisms.remove(mechanism);
* Returns the list of supported SASL mechanisms by the server. Note that Java may have }
* support for more mechanisms but some of them may not be returned since a special setup
* is required that might be missing. Use {@link #addSupportedMechanism(String)} to add /**
* new SASL mechanisms. * Returns the list of supported SASL mechanisms by the server. Note that Java may have
* * support for more mechanisms but some of them may not be returned since a special setup
* @return the list of supported SASL mechanisms by the server. * is required that might be missing. Use {@link #addSupportedMechanism(String)} to add
*/ * new SASL mechanisms.
public static Set<String> getSupportedMechanisms() { *
Set<String> answer = new HashSet<>(mechanisms); * @return the list of supported SASL mechanisms by the server.
// Clean up not-available mechanisms */
for (Iterator<String> it=answer.iterator(); it.hasNext();) { public static Set<String> getSupportedMechanisms() {
String mech = it.next(); Set<String> answer = new HashSet<>(mechanisms);
if (mech.equals("CRAM-MD5") || mech.equals("DIGEST-MD5")) { // Clean up not-available mechanisms
// Check if the user provider in use supports passwords retrieval. Accessing for (Iterator<String> it=answer.iterator(); it.hasNext();) {
// to the users passwords will be required by the CallbackHandler String mech = it.next();
if (!AuthFactory.supportsPasswordRetrieval()) { if (mech.equals("CRAM-MD5") || mech.equals("DIGEST-MD5")) {
it.remove(); // Check if the user provider in use supports passwords retrieval. Accessing
} // to the users passwords will be required by the CallbackHandler
} if (!AuthFactory.supportsPasswordRetrieval()) {
else if (mech.equals("SCRAM-SHA-1")) { it.remove();
if (!AuthFactory.supportsPasswordRetrieval() && !AuthFactory.supportsScram()) { }
it.remove(); }
} else if (mech.equals("SCRAM-SHA-1")) {
} if (!AuthFactory.supportsPasswordRetrieval() && !AuthFactory.supportsScram()) {
else if (mech.equals("ANONYMOUS")) { it.remove();
// Check anonymous is supported }
if (!XMPPServer.getInstance().getIQAuthHandler().isAnonymousAllowed()) { }
it.remove(); else if (mech.equals("ANONYMOUS")) {
} // Check anonymous is supported
} if (!XMPPServer.getInstance().getIQAuthHandler().isAnonymousAllowed()) {
else if (mech.equals("JIVE-SHAREDSECRET")) { it.remove();
// Check shared secret is supported }
if (!isSharedSecretAllowed()) { }
it.remove(); else if (mech.equals("JIVE-SHAREDSECRET")) {
} // Check shared secret is supported
} if (!isSharedSecretAllowed()) {
} it.remove();
return answer; }
} }
}
private static void initMechanisms() { return answer;
// Convert XML based provider setup to Database based }
JiveGlobals.migrateProperty("sasl.mechs");
JiveGlobals.migrateProperty("sasl.gssapi.debug"); private static void initMechanisms() {
JiveGlobals.migrateProperty("sasl.gssapi.config"); // Convert XML based provider setup to Database based
JiveGlobals.migrateProperty("sasl.gssapi.useSubjectCredsOnly"); JiveGlobals.migrateProperty("sasl.mechs");
JiveGlobals.migrateProperty("sasl.gssapi.debug");
mechanisms = new HashSet<>(); JiveGlobals.migrateProperty("sasl.gssapi.config");
String available = JiveGlobals.getProperty("sasl.mechs"); JiveGlobals.migrateProperty("sasl.gssapi.useSubjectCredsOnly");
if (available == null) {
mechanisms.add("ANONYMOUS"); mechanisms = new HashSet<>();
mechanisms.add("PLAIN"); String available = JiveGlobals.getProperty("sasl.mechs");
mechanisms.add("DIGEST-MD5"); if (available == null) {
mechanisms.add("CRAM-MD5"); mechanisms.add("ANONYMOUS");
mechanisms.add("SCRAM-SHA-1"); mechanisms.add("PLAIN");
mechanisms.add("JIVE-SHAREDSECRET"); mechanisms.add("DIGEST-MD5");
} mechanisms.add("CRAM-MD5");
else { mechanisms.add("SCRAM-SHA-1");
StringTokenizer st = new StringTokenizer(available, " ,\t\n\r\f"); mechanisms.add("JIVE-SHAREDSECRET");
while (st.hasMoreTokens()) { }
String mech = st.nextToken().toUpperCase(); else {
// Check that the mech is a supported mechansim. Maybe we shouldnt check this and allow any? StringTokenizer st = new StringTokenizer(available, " ,\t\n\r\f");
if (mech.equals("ANONYMOUS") || while (st.hasMoreTokens()) {
mech.equals("PLAIN") || String mech = st.nextToken().toUpperCase();
mech.equals("DIGEST-MD5") || // Check that the mech is a supported mechansim. Maybe we shouldnt check this and allow any?
mech.equals("CRAM-MD5") || if (mech.equals("ANONYMOUS") ||
mech.equals("SCRAM-SHA-1") || mech.equals("PLAIN") ||
mech.equals("GSSAPI") || mech.equals("DIGEST-MD5") ||
mech.equals("EXTERNAL") || mech.equals("CRAM-MD5") ||
mech.equals("JIVE-SHAREDSECRET")) mech.equals("SCRAM-SHA-1") ||
{ mech.equals("GSSAPI") ||
Log.debug("SASLAuthentication: Added " + mech + " to mech list"); mech.equals("EXTERNAL") ||
mechanisms.add(mech); mech.equals("JIVE-SHAREDSECRET"))
} {
} Log.debug("SASLAuthentication: Added " + mech + " to mech list");
mechanisms.add(mech);
if (mechanisms.contains("GSSAPI")) { }
if (JiveGlobals.getProperty("sasl.gssapi.config") != null) { }
System.setProperty("java.security.krb5.debug",
JiveGlobals.getProperty("sasl.gssapi.debug", "false")); if (mechanisms.contains("GSSAPI")) {
System.setProperty("java.security.auth.login.config", if (JiveGlobals.getProperty("sasl.gssapi.config") != null) {
JiveGlobals.getProperty("sasl.gssapi.config")); System.setProperty("java.security.krb5.debug",
System.setProperty("javax.security.auth.useSubjectCredsOnly", JiveGlobals.getProperty("sasl.gssapi.debug", "false"));
JiveGlobals.getProperty("sasl.gssapi.useSubjectCredsOnly", "false")); System.setProperty("java.security.auth.login.config",
} JiveGlobals.getProperty("sasl.gssapi.config"));
else { System.setProperty("javax.security.auth.useSubjectCredsOnly",
//Not configured, remove the option. JiveGlobals.getProperty("sasl.gssapi.useSubjectCredsOnly", "false"));
Log.debug("SASLAuthentication: Removed GSSAPI from mech list"); }
mechanisms.remove("GSSAPI"); else {
} //Not configured, remove the option.
} Log.debug("SASLAuthentication: Removed GSSAPI from mech list");
} mechanisms.remove("GSSAPI");
//Add our providers to the Security class }
Security.addProvider(new org.jivesoftware.openfire.sasl.SaslProvider()); }
} }
} //Add our providers to the Security class
Security.addProvider(new org.jivesoftware.openfire.sasl.SaslProvider());
}
}
...@@ -51,7 +51,8 @@ public class SSLConfig ...@@ -51,7 +51,8 @@ public class SSLConfig
private static final Logger Log = LoggerFactory.getLogger( SSLConfig.class ); private static final Logger Log = LoggerFactory.getLogger( SSLConfig.class );
private final ConcurrentMap<Purpose, String> locationByPurpose = new ConcurrentHashMap<>(); private final ConcurrentMap<Purpose, String> locationByPurpose = new ConcurrentHashMap<>();
private final ConcurrentMap<String, CertificateStoreConfig> storesByLocation = new ConcurrentHashMap<>(); private final ConcurrentMap<String, IdentityStoreConfig> identityStoresByLocation = new ConcurrentHashMap<>();
private final ConcurrentMap<String, TrustStoreConfig> trustStoresByLocation = new ConcurrentHashMap<>();
private static SSLConfig INSTANCE; private static SSLConfig INSTANCE;
...@@ -73,201 +74,230 @@ public class SSLConfig ...@@ -73,201 +74,230 @@ public class SSLConfig
} }
/** /**
* An utility method that is short-hand for getInstance().getStoreConfig(purpose).getStore(); * An utility method that is short-hand for getInstance().getIdentityStoreConfig(purpose).getStore();
* @param purpose The purpose for which to return a store. * @param purpose The purpose for which to return a store.
* @return a store (never null). * @return a store (never null).
*/ */
public static synchronized KeyStore getStore( Purpose purpose ) public static synchronized KeyStore getIdentityStore( Purpose purpose )
{ {
return getInstance().getStoreConfig( purpose ).getStore(); return getInstance().getIdentityStoreConfig( purpose ).getStore();
} }
/** /**
* Openfire allows a store to be re-used for multiple purposes. This method will find the store used for the * An utility method that is short-hand for getInstance().getTrustStoreConfig(purpose).getStore();
* provided purpose, and based on that will return all <em>other</em> purposes for which the same store is used. * @param purpose The purpose for which to return a store.
* * @return a store (never null).
* @param purpose The purpose for which to find a store (cannot be null).
* @return all <em>other</em> purposes for which the store is used (never null, but possibly an empty collection).
* @throws IOException
*/ */
public Set<Purpose> getOtherPurposesForSameStore( Purpose purpose ) throws IOException public static synchronized KeyStore getTrustStore( Purpose purpose )
{ {
if ( purpose == null ) return getInstance().getTrustStoreConfig( purpose ).getStore();
{
throw new IllegalArgumentException( "Argument 'purpose' cannot be null." );
}
final Set<Purpose> results = new HashSet<>();
final String location = getLocation( purpose );
for ( Map.Entry<Purpose, String> entry : locationByPurpose.entrySet() )
{
if ( entry.getValue().equalsIgnoreCase( location ) ) {
results.add( entry.getKey() );
}
}
return results;
} }
public static String getNonCanonicalizedLocation(Purpose purpose) // /**
{ // * Openfire allows a store to be re-used for multiple purposes. This method will find the store used for the
final String path; // * provided purpose, and based on that will return all <em>other</em> purposes for which the same store is used.
switch ( purpose ) // *
{ // * @param purpose The purpose for which to find a store (cannot be null).
// Identity store for socket-based IO (this is the default identity store) // * @return all <em>other</em> purposes for which the store is used (never null, but possibly an empty collection).
case SOCKETBASED_IDENTITYSTORE: // * @throws IOException
path = JiveGlobals.getProperty( "xmpp.socket.ssl.keystore", "resources" + File.separator + "security" + File.separator + "keystore" ); // */
break; // public Set<Purpose> getOtherPurposesForSameStore( Type type, boolean isTrustStore ) throws IOException
// {
// Identity store for BOSH-based IO (falls back to the default identity store when not configured) // if ( type == null )
case BOSHBASED_IDENTITYSTORE: // {
path = JiveGlobals.getProperty( "xmpp.bosh.ssl.keystore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE ) ); // throw new IllegalArgumentException( "Argument 'type' cannot be null." );
break; // }
//
// Identity store for administrative IO (falls back to the default identity store when not configured) // final Set<Purpose> results = new HashSet<>();
case ADMINISTRATIVE_IDENTITYSTORE: //
path = JiveGlobals.getProperty( "admin.ssl.keystore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE ) ); // for ( Map.Entry<Purpose, String> entry : locationByPurpose.entrySet() )
break; // {
// if ( entry.getValue().equalsIgnoreCase( location ) )
// Identity store for admin panel (falls back to the administrative identity store when not configured) // {
case WEBADMIN_IDENTITYSTORE: // results.add( entry.getKey() );
path = JiveGlobals.getProperty( "admin.web.ssl.keystore", getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) ); // }
break; // }
//
// server-to-server trust store (This is the only / default S2S trust store. S2S over BOSH is unsupported by Openfire). // return results;
case SOCKETBASED_S2S_TRUSTSTORE: // }
path = JiveGlobals.getProperty( "xmpp.socket.ssl.truststore", "resources" + File.separator + "security" + File.separator + "truststore" );
break;
// client-to-server trust store for socket-based IO (This is the default C2S trust store).
case SOCKETBASED_C2S_TRUSTSTORE:
path = JiveGlobals.getProperty( "xmpp.socket.ssl.client.truststore", "resources" + File.separator + "security" + File.separator + "client.truststore" );
break;
// client-to-server trust store for BOSH-based IO (falls back to the default C2S trust store when not configured). // public static String getNonCanonicalizedLocation(Purpose purpose)
case BOSHBASED_C2S_TRUSTSTORE: // {
path = JiveGlobals.getProperty( "xmpp.bosh.ssl.client.truststore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) ); // final String path;
break; // switch ( purpose )
// {
// // Identity store for socket-based IO (this is the default identity store)
// case SOCKETBASED_IDENTITYSTORE:
// path = JiveGlobals.getProperty( "xmpp.socket.ssl.keystore", "resources" + File.separator + "security" + File.separator + "keystore" );
// break;
//
// // Identity store for BOSH-based IO (falls back to the default identity store when not configured)
// case BOSHBASED_IDENTITYSTORE:
// path = JiveGlobals.getProperty( "xmpp.bosh.ssl.keystore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE ) );
// break;
//
// // Identity store for administrative IO (falls back to the default identity store when not configured)
// case ADMINISTRATIVE_IDENTITYSTORE:
// path = JiveGlobals.getProperty( "admin.ssl.keystore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE ) );
// break;
//
// // Identity store for admin panel (falls back to the administrative identity store when not configured)
// case WEBADMIN_IDENTITYSTORE:
// path = JiveGlobals.getProperty( "admin.web.ssl.keystore", getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) );
// break;
//
// // server-to-server trust store (This is the only / default S2S trust store. S2S over BOSH is unsupported by Openfire).
// case SOCKETBASED_S2S_TRUSTSTORE:
// path = JiveGlobals.getProperty( "xmpp.socket.ssl.truststore", "resources" + File.separator + "security" + File.separator + "truststore" );
// break;
//
// // client-to-server trust store for socket-based IO (This is the default C2S trust store).
// case SOCKETBASED_C2S_TRUSTSTORE:
// path = JiveGlobals.getProperty( "xmpp.socket.ssl.client.truststore", "resources" + File.separator + "security" + File.separator + "client.truststore" );
// break;
//
// // client-to-server trust store for BOSH-based IO (falls back to the default C2S trust store when not configured).
// case BOSHBASED_C2S_TRUSTSTORE:
// path = JiveGlobals.getProperty( "xmpp.bosh.ssl.client.truststore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) );
// break;
//
// // Administrative trust store (falls back to the default trust store when not configured)
// case ADMINISTRATIVE_TRUSTSTORE:
// path = JiveGlobals.getProperty( "admin.ssl.truststore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) );
// break;
//
// // Trust store for admin panel (falls back to the administrative trust store when not configured)
// case WEBADMIN_TRUSTSTORE:
// path = JiveGlobals.getProperty( "admin.web.ssl.truststore", getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_TRUSTSTORE ) );
// break;
//
// default:
// throw new IllegalStateException( "Unrecognized purpose: " + purpose );
// }
// return path;
// }
// Administrative trust store (falls back to the default trust store when not configured) // public static String getLocation(Purpose purpose, boolean isTrustStore) throws IOException
case ADMINISTRATIVE_TRUSTSTORE: // {
path = JiveGlobals.getProperty( "admin.ssl.truststore", getNonCanonicalizedLocation( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) ); // final String location;
break; // if ( isTrustStore ) {
// location = purpose.getTrustStoreLocationNonCanonicalized();
// } else {
// location = purpose.getIdentityStoreLocationNonCanonicalized();
// }
// return canonicalize( location );
// }
// Trust store for admin panel (falls back to the administrative trust store when not configured) // protected String getPassword( Type type, boolean isTrustStore )
case WEBADMIN_TRUSTSTORE: // {
path = JiveGlobals.getProperty( "admin.web.ssl.truststore", getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ); // switch ( purpose ) {
break; // case SOCKETBASED_IDENTITYSTORE:
// return JiveGlobals.getProperty( "xmpp.socket.ssl.keypass", "changeit" ).trim();
//
// case BOSHBASED_IDENTITYSTORE:
// return JiveGlobals.getProperty( "xmpp.bosh.ssl.keypass", getPassword( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
//
// case ADMINISTRATIVE_IDENTITYSTORE:
// return JiveGlobals.getProperty( "admin.ssl.keypass", getPassword( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
//
// case WEBADMIN_IDENTITYSTORE:
// return JiveGlobals.getProperty( "admin.web.ssl.keypass", getPassword( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) ).trim();
//
// case SOCKETBASED_S2S_TRUSTSTORE:
// return JiveGlobals.getProperty( "xmpp.socket.ssl.trustpass", "changeit" ).trim();
//
// case SOCKETBASED_C2S_TRUSTSTORE:
// return JiveGlobals.getProperty( "xmpp.socket.ssl.client.trustpass", "changeit" ).trim();
//
// case BOSHBASED_C2S_TRUSTSTORE:
// return JiveGlobals.getProperty( "xmpp.bosh.ssl.client.trustpass", getPassword( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) ).trim();
//
// case ADMINISTRATIVE_TRUSTSTORE:
// return JiveGlobals.getProperty( "admin.ssl.trustpass", getPassword( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) ).trim();
//
// case WEBADMIN_TRUSTSTORE:
// return JiveGlobals.getProperty( "admin..web.ssl.trustpass", getPassword( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ).trim();
//
// default:
// throw new IllegalStateException( "Unrecognized purpose: " + purpose );
// }
// }
default: // protected String getStoreType( Type type, boolean isTrustStore )
throw new IllegalStateException( "Unrecognized purpose: " + purpose ); // {
} // // FIXME All properties should be unique, instead of being re-used by different stores.
return path; // switch ( purpose )
} // {
// case SOCKETBASED_IDENTITYSTORE:
// return JiveGlobals.getProperty( "xmpp.socket.ssl.storeType", "jks" ).trim();
//
// case BOSHBASED_IDENTITYSTORE:
// return JiveGlobals.getProperty( "xmpp.bosh.ssl.storeType", getStoreType( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
//
// case ADMINISTRATIVE_IDENTITYSTORE:
// return JiveGlobals.getProperty( "admin.ssl.storeType", getStoreType( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
//
// case WEBADMIN_IDENTITYSTORE:
// return JiveGlobals.getProperty( "admin.web.ssl.storeType", getStoreType( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) ).trim();
//
// case SOCKETBASED_S2S_TRUSTSTORE:
// return JiveGlobals.getProperty( "xmpp.socket.ssl.storeType", "jks" ).trim();
//
// case SOCKETBASED_C2S_TRUSTSTORE:
// return JiveGlobals.getProperty( "xmpp.socket.ssl.client.storeType", "jks" ).trim();
//
// case BOSHBASED_C2S_TRUSTSTORE:
// return JiveGlobals.getProperty( "xmpp.bosh.ssl.client.storeType", getStoreType( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) ).trim();
//
// case ADMINISTRATIVE_TRUSTSTORE:
// return JiveGlobals.getProperty( "admin.ssl.storeType", getStoreType( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) ).trim();
//
// case WEBADMIN_TRUSTSTORE:
// return JiveGlobals.getProperty( "admin.web.ssl.storeType", getStoreType( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ).trim();
//
// default:
// throw new IllegalStateException( "Unrecognized purpose: " + purpose );
// }
// }
public static String getLocation(Purpose purpose) throws IOException private SSLConfig() throws CertificateStoreConfigException, IOException, NoSuchAlgorithmException
{ {
return canonicalize( getNonCanonicalizedLocation( purpose ) ); for ( final Purpose purpose : Purpose.values() )
}
protected String getPassword(Purpose purpose){
switch ( purpose ) {
case SOCKETBASED_IDENTITYSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.keypass", "changeit" ).trim();
case BOSHBASED_IDENTITYSTORE:
return JiveGlobals.getProperty( "xmpp.bosh.ssl.keypass", getPassword( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
case ADMINISTRATIVE_IDENTITYSTORE:
return JiveGlobals.getProperty( "admin.ssl.keypass", getPassword( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim();
case WEBADMIN_IDENTITYSTORE:
return JiveGlobals.getProperty( "admin.web.ssl.keypass", getPassword( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) ).trim();
case SOCKETBASED_S2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.trustpass", "changeit" ).trim();
case SOCKETBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.client.trustpass", "changeit" ).trim();
case BOSHBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.bosh.ssl.client.trustpass", getPassword( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) ).trim();
case ADMINISTRATIVE_TRUSTSTORE:
return JiveGlobals.getProperty( "admin.ssl.trustpass", getPassword( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) ).trim();
case WEBADMIN_TRUSTSTORE:
return JiveGlobals.getProperty( "admin..web.ssl.trustpass", getPassword( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ).trim();
default:
throw new IllegalStateException( "Unrecognized purpose: " + purpose );
}
}
protected String getStoreType(Purpose purpose) {
// FIXME All properties should be unique, instead of being re-used by different stores.
switch ( purpose )
{ {
case SOCKETBASED_IDENTITYSTORE: // Instantiate an identity store.
return JiveGlobals.getProperty( "xmpp.socket.ssl.storeType", "jks" ).trim(); final String locationIdent = purpose.getIdentityStoreLocation();
locationByPurpose.put( purpose, locationIdent );
case BOSHBASED_IDENTITYSTORE: if ( !identityStoresByLocation.containsKey( locationIdent ) )
return JiveGlobals.getProperty( "xmpp.bosh.ssl.storeType", getStoreType( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim(); {
final IdentityStoreConfig storeConfig = new IdentityStoreConfig( purpose.getIdentityStoreLocation(), purpose.getIdentityStorePassword(), purpose.getIdentityStoreType(), false );
case ADMINISTRATIVE_IDENTITYSTORE: identityStoresByLocation.put( locationIdent, storeConfig );
return JiveGlobals.getProperty( "admin.ssl.storeType", getStoreType( Purpose.SOCKETBASED_IDENTITYSTORE ) ).trim(); }
case WEBADMIN_IDENTITYSTORE:
return JiveGlobals.getProperty( "admin.web.ssl.storeType", getStoreType( Purpose.ADMINISTRATIVE_IDENTITYSTORE ) ).trim();
case SOCKETBASED_S2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.storeType", "jks" ).trim();
case SOCKETBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.socket.ssl.client.storeType", "jks" ).trim();
case BOSHBASED_C2S_TRUSTSTORE:
return JiveGlobals.getProperty( "xmpp.bosh.ssl.client.storeType", getStoreType( Purpose.SOCKETBASED_C2S_TRUSTSTORE ) ).trim();
case ADMINISTRATIVE_TRUSTSTORE:
return JiveGlobals.getProperty( "admin.ssl.storeType", getStoreType( Purpose.SOCKETBASED_S2S_TRUSTSTORE ) ).trim();
case WEBADMIN_TRUSTSTORE:
return JiveGlobals.getProperty( "admin.web.ssl.storeType", getStoreType( Purpose.ADMINISTRATIVE_TRUSTSTORE ) ).trim();
default: // Instantiate trust store.
throw new IllegalStateException( "Unrecognized purpose: " + purpose ); final String locationTrust = purpose.getTrustStoreLocation();
locationByPurpose.put( purpose, locationTrust );
if ( !trustStoresByLocation.containsKey( locationTrust ) )
{
final TrustStoreConfig storeConfig = new TrustStoreConfig( purpose.getTrustStoreLocation(), purpose.getTrustStorePassword(), purpose.getTrustStoreType(), false, purpose.acceptSelfSigned(), purpose.verifyValidity() );
trustStoresByLocation.put( locationTrust, storeConfig );
}
} }
} }
private SSLConfig() throws CertificateStoreConfigException, IOException, NoSuchAlgorithmException public IdentityStoreConfig getIdentityStoreConfig( Purpose purpose )
{ {
for (Purpose purpose : Purpose.values()) { if ( purpose == null ) {
final String location = getLocation( purpose ); throw new IllegalArgumentException( "Argument 'purpose' cannot be null.");
if ( !storesByLocation.containsKey( location )) {
final CertificateStoreConfig storeConfig;
if (purpose.isTrustStore()) {
final boolean acceptSelfSigned = false; // TODO make configurable
final boolean checkValidity = true; ; // TODO make configurable
storeConfig = new TrustStoreConfig( getLocation( purpose ), getPassword( purpose ), getStoreType( purpose ), false, acceptSelfSigned, checkValidity );
} else {
storeConfig = new IdentityStoreConfig( getLocation( purpose ), getPassword( purpose ), getStoreType( purpose ), false );
}
storesByLocation.put( location, storeConfig );
}
locationByPurpose.put(purpose, location);
} }
return identityStoresByLocation.get( locationByPurpose.get( purpose ) );
} }
public CertificateStoreConfig getStoreConfig( Purpose purpose ) { public TrustStoreConfig getTrustStoreConfig( Purpose purpose )
{
if ( purpose == null ) { if ( purpose == null ) {
throw new IllegalArgumentException( "Argument 'purpose' cannot be null."); throw new IllegalArgumentException( "Argument 'purpose' cannot be null.");
} }
return storesByLocation.get( locationByPurpose.get( purpose ) ); return trustStoresByLocation.get( locationByPurpose.get( purpose ) );
} }
// public void useStoreForPurpose( Purpose purpose, String location, String password, String storeType, boolean createIfAbsent ) throws IOException, CertificateStoreConfigException // public void useStoreForPurpose( Purpose purpose, String location, String password, String storeType, boolean createIfAbsent ) throws IOException, CertificateStoreConfigException
...@@ -369,83 +399,12 @@ public class SSLConfig ...@@ -369,83 +399,12 @@ public class SSLConfig
// // TODO notify listeners // // TODO notify listeners
// } // }
public static String canonicalize( String path ) throws IOException public static SSLContext getSSLContext( final Purpose purpose ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException
{ {
File file = new File( path ); final KeyManager[] keyManagers = getInstance().getIdentityStoreConfig( purpose ).getKeyManagers();
if (!file.isAbsolute()) { final TrustManager[] trustManagers = getInstance().getTrustStoreConfig( purpose ).getTrustManagers();
file = new File( JiveGlobals.getHomeDirectory() + File.separator + path );
}
return file.getCanonicalPath();
}
// TODO merge this with Purpose!
public enum Type {
SOCKET_S2S( "xmpp.socket.ssl.", null ),
SOCKET_C2S( "xmpp.socket.ssl.client.", null ),
BOSH_C2S( "xmpp.bosh.ssl.client.", SOCKET_C2S),
ADMIN( "admin.ssl.", SOCKET_S2S),
WEBADMIN( "admin.web.ssl.", ADMIN);
String prefix;
Type fallback;
Type( String prefix, Type fallback) {
this.prefix = prefix;
this.fallback = fallback;
}
public String getPrefix()
{
return prefix;
}
public Type getFallback()
{
return fallback;
}
}
public static SSLContext getSSLContext( final Type type ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException
{
// TODO: allow different algorithms for different connection types (eg client/server/bosh etc)
final String algorithm = JiveGlobals.getProperty( ConnectionSettings.Client.TLS_ALGORITHM, "TLS" );
final Purpose idPurpose;
final Purpose trustPurpose;
switch ( type ) {
case SOCKET_S2S:
idPurpose = Purpose.SOCKETBASED_IDENTITYSTORE;
trustPurpose = Purpose.SOCKETBASED_S2S_TRUSTSTORE;
break;
case SOCKET_C2S:
idPurpose = Purpose.SOCKETBASED_IDENTITYSTORE;
trustPurpose = Purpose.SOCKETBASED_C2S_TRUSTSTORE;
break;
case BOSH_C2S:
idPurpose = Purpose.BOSHBASED_IDENTITYSTORE;
trustPurpose = Purpose.BOSHBASED_C2S_TRUSTSTORE;
break;
case ADMIN:
idPurpose = Purpose.ADMINISTRATIVE_IDENTITYSTORE;
trustPurpose = Purpose.ADMINISTRATIVE_TRUSTSTORE;
break;
case WEBADMIN:
idPurpose = Purpose.WEBADMIN_IDENTITYSTORE;
trustPurpose = Purpose.WEBADMIN_TRUSTSTORE;
break;
default:
throw new IllegalStateException( "Unsupported type: " + type );
}
final KeyManager[] keyManagers = ((IdentityStoreConfig) getInstance().getStoreConfig( idPurpose )).getKeyManagers();
final TrustManager[] trustManagers = ((TrustStoreConfig) getInstance().getStoreConfig( trustPurpose )).getTrustManagers();
final SSLContext sslContext = SSLContext.getInstance( algorithm ); final SSLContext sslContext = SSLContext.getInstance( purpose.getIdentityStoreType() );
sslContext.init( keyManagers, trustManagers, new SecureRandom() ); sslContext.init( keyManagers, trustManagers, new SecureRandom() );
return sslContext; return sslContext;
...@@ -456,13 +415,13 @@ public class SSLConfig ...@@ -456,13 +415,13 @@ public class SSLConfig
* *
* For Openfire, an engine is of this mode used for most purposes (as Openfire is a server by nature). * For Openfire, an engine is of this mode used for most purposes (as Openfire is a server by nature).
* *
* @param type The type of connectivity for which to configure a new SSLEngine instance. Cannot be null. * @param purpose The type of connectivity for which to configure a new SSLEngine instance. Cannot be null.
* @param clientAuth indication of the desired level of client-sided authentication (mutual authentication). Cannot be null. * @param clientAuth indication of the desired level of client-sided authentication (mutual authentication). Cannot be null.
* @return An initialized SSLEngine instance (never null). * @return An initialized SSLEngine instance (never null).
*/ */
public static SSLEngine getServerModeSSLEngine( Type type, Connection.ClientAuth clientAuth ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException public static SSLEngine getServerModeSSLEngine( Purpose purpose, Connection.ClientAuth clientAuth ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException
{ {
final SSLEngine sslEngine = getSSLEngine( type ); final SSLEngine sslEngine = getSSLEngine( purpose );
sslEngine.setUseClientMode( false ); sslEngine.setUseClientMode( false );
switch ( clientAuth ) switch ( clientAuth )
...@@ -488,12 +447,12 @@ public class SSLConfig ...@@ -488,12 +447,12 @@ public class SSLConfig
* *
* For Openfire, an engine of this mode is typically used when the server tries to connect to another server. * For Openfire, an engine of this mode is typically used when the server tries to connect to another server.
* *
* @param type The type of connectivity for which to configure a new SSLEngine instance. Cannot be null. * @param purpose The type of connectivity for which to configure a new SSLEngine instance. Cannot be null.
* @return An initialized SSLEngine instance (never null). * @return An initialized SSLEngine instance (never null).
*/ */
public static SSLEngine getClientModeSSLEngine( Type type ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException public static SSLEngine getClientModeSSLEngine( Purpose purpose ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException
{ {
final SSLEngine sslEngine = getSSLEngine( type ); final SSLEngine sslEngine = getSSLEngine( purpose );
sslEngine.setUseClientMode( true ); sslEngine.setUseClientMode( true );
return sslEngine; return sslEngine;
...@@ -506,16 +465,16 @@ public class SSLConfig ...@@ -506,16 +465,16 @@ public class SSLConfig
* The returned value lacks further configuration. In most cases, developers will want to use getClientModeSSLEngine * The returned value lacks further configuration. In most cases, developers will want to use getClientModeSSLEngine
* or getServerModeSSLEngine instead of this method. * or getServerModeSSLEngine instead of this method.
* *
* @param type The type of connectivity for which to pre-configure a new SSLEngine instance. Cannot be null. * @param purpose The type of connectivity for which to pre-configure a new SSLEngine instance. Cannot be null.
* @return A pre-configured SSLEngine (never null). * @return A pre-configured SSLEngine (never null).
*/ */
private static SSLEngine getSSLEngine( final Type type ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException private static SSLEngine getSSLEngine( final Purpose purpose ) throws NoSuchAlgorithmException, KeyManagementException, KeyStoreException
{ {
final SSLContext sslContext = getSSLContext( type ); final SSLContext sslContext = getSSLContext( purpose );
final SSLEngine sslEngine = sslContext.createSSLEngine(); final SSLEngine sslEngine = sslContext.createSSLEngine();
configureProtocols( sslEngine, type ); configureProtocols( sslEngine, purpose );
configureCipherSuites( sslEngine, type ); configureCipherSuites( sslEngine, purpose );
return sslEngine; return sslEngine;
} }
...@@ -533,16 +492,16 @@ public class SSLConfig ...@@ -533,16 +492,16 @@ public class SSLConfig
* where the SSLEngine default gets filtered but not replaced. * where the SSLEngine default gets filtered but not replaced.
* *
* @param sslEngine The instance to configure. Cannot be null. * @param sslEngine The instance to configure. Cannot be null.
* @param type The type of configuration to use (used to select the relevent property). Cannot be null. * @param purpose The type of configuration to use (used to select the relevent property). Cannot be null.
*/ */
private static void configureProtocols( SSLEngine sslEngine, Type type ) private static void configureProtocols( SSLEngine sslEngine, Purpose purpose )
{ {
// Find configuration, using fallback where applicable. // Find configuration, using fallback where applicable.
String enabledProtocols = JiveGlobals.getProperty( type.getPrefix() + "enabled.protocols" ); String enabledProtocols = JiveGlobals.getProperty( purpose.getPrefix() + "enabled.protocols" );
while (enabledProtocols == null && type.getFallback() != null) while (enabledProtocols == null && purpose.getFallback() != null)
{ {
type = type.getFallback(); purpose = purpose.getFallback();
enabledProtocols = JiveGlobals.getProperty( type.getPrefix() + "enabled.protocols" ); enabledProtocols = JiveGlobals.getProperty( purpose.getPrefix() + "enabled.protocols" );
} }
if (enabledProtocols != null ) if (enabledProtocols != null )
...@@ -585,15 +544,15 @@ public class SSLConfig ...@@ -585,15 +544,15 @@ public class SSLConfig
* entire SSLEngine default gets replaced. * entire SSLEngine default gets replaced.
* *
* @param sslEngine The instance to configure. Cannot be null. * @param sslEngine The instance to configure. Cannot be null.
* @param type The type of configuration to use (used to select the relevent property). Cannot be null. * @param purpose The type of configuration to use (used to select the relevent property). Cannot be null.
*/ */
private static void configureCipherSuites( SSLEngine sslEngine, Type type ) private static void configureCipherSuites( SSLEngine sslEngine, Purpose purpose )
{ {
String enabledCipherSuites = JiveGlobals.getProperty( type.getPrefix() + "enabled.ciphersuites" ); String enabledCipherSuites = JiveGlobals.getProperty( purpose.getPrefix() + "enabled.ciphersuites" );
while (enabledCipherSuites == null && type.getFallback() != null) while (enabledCipherSuites == null && purpose.getFallback() != null)
{ {
type = type.getFallback(); purpose = purpose.getFallback();
enabledCipherSuites = JiveGlobals.getProperty( type.getPrefix() + "enabled.ciphersuites" ); enabledCipherSuites = JiveGlobals.getProperty( purpose.getPrefix() + "enabled.ciphersuites" );
} }
if (enabledCipherSuites != null ) if (enabledCipherSuites != null )
...@@ -638,14 +597,14 @@ public class SSLConfig ...@@ -638,14 +597,14 @@ public class SSLConfig
* Instead of an SSLContext or SSLEngine, Apache MINA uses an SslFilter instance. It is generally not needed to * Instead of an SSLContext or SSLEngine, Apache MINA uses an SslFilter instance. It is generally not needed to
* create both SSLContext/SSLEngine as well as SslFilter instances. * create both SSLContext/SSLEngine as well as SslFilter instances.
* *
* @param type Communication type (used to select the relevant property). Cannot be null. * @param purpose Communication type (used to select the relevant property). Cannot be null.
* @param clientAuth indication of the desired level of client-sided authentication (mutual authentication). Cannot be null. * @param clientAuth indication of the desired level of client-sided authentication (mutual authentication). Cannot be null.
* @return An initialized SslFilter instance (never null) * @return An initialized SslFilter instance (never null)
*/ */
public static SslFilter getServerModeSslFilter( SSLConfig.Type type, Connection.ClientAuth clientAuth ) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException public static SslFilter getServerModeSslFilter( Purpose purpose, Connection.ClientAuth clientAuth ) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException
{ {
final SSLContext sslContext = SSLConfig.getSSLContext( type ); final SSLContext sslContext = SSLConfig.getSSLContext( purpose );
final SSLEngine sslEngine = SSLConfig.getServerModeSSLEngine( type, clientAuth ); final SSLEngine sslEngine = SSLConfig.getServerModeSSLEngine( purpose, clientAuth );
return getSslFilter( sslContext, sslEngine ); return getSslFilter( sslContext, sslEngine );
} }
...@@ -658,13 +617,13 @@ public class SSLConfig ...@@ -658,13 +617,13 @@ public class SSLConfig
* Instead of an SSLContext or SSLEngine, Apache MINA uses an SslFilter instance. It is generally not needed to * Instead of an SSLContext or SSLEngine, Apache MINA uses an SslFilter instance. It is generally not needed to
* create both SSLContext/SSLEngine as well as SslFilter instances. * create both SSLContext/SSLEngine as well as SslFilter instances.
* *
* @param type Communication type (used to select the relevant property). Cannot be null. * @param purpose Communication type (used to select the relevant property). Cannot be null.
* @return An initialized SslFilter instance (never null) * @return An initialized SslFilter instance (never null)
*/ */
public static SslFilter getClientModeSslFilter( SSLConfig.Type type ) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException public static SslFilter getClientModeSslFilter( Purpose purpose ) throws KeyManagementException, NoSuchAlgorithmException, KeyStoreException
{ {
final SSLContext sslContext = SSLConfig.getSSLContext( type ); final SSLContext sslContext = SSLConfig.getSSLContext( purpose );
final SSLEngine sslEngine = SSLConfig.getClientModeSSLEngine( type ); final SSLEngine sslEngine = SSLConfig.getClientModeSSLEngine( purpose );
return getSslFilter( sslContext, sslEngine ); return getSslFilter( sslContext, sslEngine );
} }
......
...@@ -31,6 +31,7 @@ import javax.net.ssl.SSLEngineResult.HandshakeStatus; ...@@ -31,6 +31,7 @@ import javax.net.ssl.SSLEngineResult.HandshakeStatus;
import javax.net.ssl.SSLEngineResult.Status; import javax.net.ssl.SSLEngineResult.Status;
import org.jivesoftware.openfire.Connection; import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.keystore.Purpose;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
...@@ -77,12 +78,12 @@ public class TLSWrapper { ...@@ -77,12 +78,12 @@ public class TLSWrapper {
final SSLEngine sslEngine; final SSLEngine sslEngine;
if ( clientMode ) if ( clientMode )
{ {
sslEngine = SSLConfig.getClientModeSSLEngine( SSLConfig.Type.SOCKET_S2S ); sslEngine = SSLConfig.getClientModeSSLEngine( Purpose.SOCKET_S2S );
} }
else else
{ {
final SSLConfig.Type type = isPeerClient ? SSLConfig.Type.SOCKET_C2S : SSLConfig.Type.SOCKET_S2S; final Purpose purpose = isPeerClient ? Purpose.SOCKET_C2S : Purpose.SOCKET_S2S;
sslEngine = SSLConfig.getServerModeSSLEngine( type, clientAuth ); sslEngine = SSLConfig.getServerModeSSLEngine( purpose, clientAuth );
} }
final SSLSession sslSession = sslEngine.getSession(); final SSLSession sslSession = sslEngine.getSession();
......
...@@ -28,19 +28,13 @@ import java.net.InetAddress; ...@@ -28,19 +28,13 @@ import java.net.InetAddress;
import java.net.InetSocketAddress; import java.net.InetSocketAddress;
import java.net.SocketAddress; import java.net.SocketAddress;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.nio.charset.Charset;
import java.nio.charset.CharsetEncoder; import java.nio.charset.CharsetEncoder;
import java.nio.charset.CodingErrorAction; import java.nio.charset.CodingErrorAction;
import java.nio.charset.StandardCharsets;
import java.security.KeyStore;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.util.concurrent.atomic.AtomicReference;
import java.util.concurrent.locks.ReentrantLock; import java.util.concurrent.locks.ReentrantLock;
import javax.net.ssl.SSLContext; import javax.net.ssl.*;
import javax.net.ssl.SSLPeerUnverifiedException;
import javax.net.ssl.SSLSession;
import javax.net.ssl.TrustManager;
import org.apache.mina.core.buffer.IoBuffer; import org.apache.mina.core.buffer.IoBuffer;
import org.apache.mina.core.filterchain.IoFilterChain; import org.apache.mina.core.filterchain.IoFilterChain;
...@@ -52,26 +46,20 @@ import org.jivesoftware.openfire.Connection; ...@@ -52,26 +46,20 @@ import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.ConnectionCloseListener; import org.jivesoftware.openfire.ConnectionCloseListener;
import org.jivesoftware.openfire.PacketDeliverer; import org.jivesoftware.openfire.PacketDeliverer;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.keystore.IdentityStoreConfig; import org.jivesoftware.openfire.keystore.*;
import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.keystore.TrustStoreConfig;
import org.jivesoftware.openfire.net.*; import org.jivesoftware.openfire.net.*;
import org.jivesoftware.openfire.session.ConnectionSettings;
import org.jivesoftware.openfire.session.LocalSession; import org.jivesoftware.openfire.session.LocalSession;
import org.jivesoftware.openfire.session.Session; import org.jivesoftware.openfire.session.Session;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.XMLWriter; import org.jivesoftware.util.XMLWriter;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
/** /**
* Implementation of {@link Connection} inteface specific for NIO connections when using * Implementation of {@link Connection} interface specific for NIO connections when using the Apache MINA framework.
* the MINA framework.<p>
*
* MINA project can be found at <a href="http://mina.apache.org">here</a>.
* *
* @author Gaston Dombiak * @author Gaston Dombiak
* @see <a href="http://mina.apache.org">Apache MINA</a>
*/ */
public class NIOConnection implements Connection { public class NIOConnection implements Connection {
...@@ -378,65 +366,28 @@ public class NIOConnection implements Connection { ...@@ -378,65 +366,28 @@ public class NIOConnection implements Connection {
@Deprecated @Deprecated
@Override @Override
public void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws Exception { public void startTLS(boolean clientMode, String remoteServer, ClientAuth authentication) throws Exception {
final boolean isClientToServer = ( remoteServer == null ); final boolean isPeerClient = ( remoteServer == null );
startTLS( clientMode, isClientToServer, authentication ); startTLS( clientMode, isPeerClient, authentication );
} }
public void startTLS(boolean clientMode, boolean isClientToServer, ClientAuth authentication) throws Exception { public void startTLS(boolean clientMode, boolean isPeerClient, ClientAuth authentication) throws Exception {
Log.debug( "StartTLS: using {}", isClientToServer ? "c2s" : "s2s" );
final SSLConfig sslConfig = SSLConfig.getInstance(); final SslFilter filter;
final TrustStoreConfig storeConfig; if ( clientMode ) {
if (isClientToServer) { filter = SSLConfig.getClientModeSslFilter( Purpose.SOCKET_S2S );
storeConfig = (TrustStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
} else {
storeConfig = (TrustStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_S2S_TRUSTSTORE );
} }
else
final TrustManager[] tm; {
if (clientMode || authentication == ClientAuth.needed || authentication == ClientAuth.wanted) { final Purpose purpose = isPeerClient ? Purpose.SOCKET_C2S : Purpose.SOCKET_S2S;
// We might need to verify a certificate from our peer, so get different TrustManager[]'s filter = SSLConfig.getServerModeSslFilter( purpose, authentication );
final KeyStore ksTrust = storeConfig.getStore();
if(isClientToServer) {
// Check if we can trust certificates presented by the client
tm = new TrustManager[]{new ClientTrustManager(ksTrust)};
} else {
// Check if we can trust certificates presented by the server
tm = new TrustManager[]{new ServerTrustManager(ksTrust)};
}
} else {
tm = storeConfig.getTrustManagers();
} }
final SSLContext tlsContext = SSLConfig.getSSLContext();
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) sslConfig.getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE );
tlsContext.init( identityStoreConfig.getKeyManagers(), tm, null);
SslFilter filter = new SslFilter(tlsContext);
filter.setUseClientMode(clientMode);
// Disable SSLv3 due to POODLE vulnerability.
if (clientMode) {
filter.setEnabledProtocols(new String[]{"TLSv1", "TLSv1.1", "TLSv1.2"});
} else {
// ... but accept a SSLv2 Hello when in server mode.
filter.setEnabledProtocols(new String[]{"SSLv2Hello", "TLSv1", "TLSv1.1", "TLSv1.2"});
}
if (authentication == ClientAuth.needed) {
filter.setNeedClientAuth(true);
}
else if (authentication == ClientAuth.wanted) {
// Just indicate that we would like to authenticate the client but if client
// certificates are self-signed or have no certificate chain then we are still
// good
filter.setWantClientAuth(true);
}
ioSession.getFilterChain().addBefore(EXECUTOR_FILTER_NAME, TLS_FILTER_NAME, filter); ioSession.getFilterChain().addBefore(EXECUTOR_FILTER_NAME, TLS_FILTER_NAME, filter);
ioSession.setAttribute(SslFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE); ioSession.setAttribute(SslFilter.DISABLE_ENCRYPTION_ONCE, Boolean.TRUE);
if (!clientMode) { if ( !clientMode ) {
// Indicate the client that the server is ready to negotiate TLS // Indicate the client that the server is ready to negotiate TLS
deliverRawText("<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>"); deliverRawText( "<proceed xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\"/>" );
} }
} }
......
/** /**
* $RCSfile$ * $RCSfile$
* $Revision: 3187 $ * $Revision: 3187 $
* $Date: 2005-12-11 13:34:34 -0300 (Sun, 11 Dec 2005) $ * $Date: 2005-12-11 13:34:34 -0300 (Sun, 11 Dec 2005) $
* *
* Copyright (C) 2005-2008 Jive Software. All rights reserved. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.session; package org.jivesoftware.openfire.session;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import java.util.Date; import java.util.Date;
import java.util.HashMap; import java.util.HashMap;
import java.util.Iterator; import java.util.Iterator;
import java.util.Map; import java.util.Map;
import java.util.StringTokenizer; import java.util.StringTokenizer;
import org.jivesoftware.openfire.Connection; import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager; import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID; import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.XMPPServer; import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.auth.AuthToken; import org.jivesoftware.openfire.auth.AuthToken;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.cluster.ClusterManager; import org.jivesoftware.openfire.cluster.ClusterManager;
import org.jivesoftware.openfire.keystore.Purpose; import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.net.SASLAuthentication; import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.SSLConfig; import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.SocketConnection; import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.privacy.PrivacyList; import org.jivesoftware.openfire.privacy.PrivacyList;
import org.jivesoftware.openfire.privacy.PrivacyListManager; import org.jivesoftware.openfire.privacy.PrivacyListManager;
import org.jivesoftware.openfire.streammanagement.StreamManager; import org.jivesoftware.openfire.streammanagement.StreamManager;
import org.jivesoftware.openfire.user.PresenceEventDispatcher; import org.jivesoftware.openfire.user.PresenceEventDispatcher;
import org.jivesoftware.openfire.user.UserNotFoundException; import org.jivesoftware.openfire.user.UserNotFoundException;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils; import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.cache.Cache; import org.jivesoftware.util.cache.Cache;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
import org.xmpp.packet.Presence; import org.xmpp.packet.Presence;
import org.xmpp.packet.StreamError; import org.xmpp.packet.StreamError;
/** /**
* Represents a session between the server and a client. * Represents a session between the server and a client.
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class LocalClientSession extends LocalSession implements ClientSession { public class LocalClientSession extends LocalSession implements ClientSession {
private static final Logger Log = LoggerFactory.getLogger(LocalClientSession.class); private static final Logger Log = LoggerFactory.getLogger(LocalClientSession.class);
private static final String ETHERX_NAMESPACE = "http://etherx.jabber.org/streams"; private static final String ETHERX_NAMESPACE = "http://etherx.jabber.org/streams";
private static final String FLASH_NAMESPACE = "http://www.jabber.com/streams/flash"; private static final String FLASH_NAMESPACE = "http://www.jabber.com/streams/flash";
/** /**
* Keep the list of IP address that are allowed to connect to the server. If the list is * Keep the list of IP address that are allowed to connect to the server. If the list is
* empty then anyone is allowed to connect to the server.<p> * empty then anyone is allowed to connect to the server.<p>
* *
* Note: Key = IP address or IP range; Value = empty string. A hash map is being used for * Note: Key = IP address or IP range; Value = empty string. A hash map is being used for
* performance reasons. * performance reasons.
*/ */
private static Map<String,String> allowedIPs = new HashMap<>(); private static Map<String,String> allowedIPs = new HashMap<>();
private static Map<String,String> allowedAnonymIPs = new HashMap<>(); private static Map<String,String> allowedAnonymIPs = new HashMap<>();
private boolean messageCarbonsEnabled; private boolean messageCarbonsEnabled;
/** /**
* The authentication token for this session. * The authentication token for this session.
*/ */
protected AuthToken authToken; protected AuthToken authToken;
/** /**
* Flag indicating if this session has been initialized yet (upon first available transition). * Flag indicating if this session has been initialized yet (upon first available transition).
*/ */
private boolean initialized; private boolean initialized;
/** /**
* Flag that indicates if the session was available ever. * Flag that indicates if the session was available ever.
*/ */
private boolean wasAvailable = false; private boolean wasAvailable = false;
/** /**
* Flag indicating if the user requested to not receive offline messages when sending * Flag indicating if the user requested to not receive offline messages when sending
* an available presence. The user may send a disco request with node * an available presence. The user may send a disco request with node
* "http://jabber.org/protocol/offline" so that no offline messages are sent to the * "http://jabber.org/protocol/offline" so that no offline messages are sent to the
* user when he becomes online. If the user is connected from many resources then * user when he becomes online. If the user is connected from many resources then
* if one of the sessions stopped the flooding then no session should flood the user. * if one of the sessions stopped the flooding then no session should flood the user.
*/ */
private boolean offlineFloodStopped = false; private boolean offlineFloodStopped = false;
private Presence presence = null; private Presence presence = null;
private int conflictCount = 0; private int conflictCount = 0;
/** /**
* Privacy list that overrides the default privacy list. This list affects only this * Privacy list that overrides the default privacy list. This list affects only this
* session and only for the duration of the session. * session and only for the duration of the session.
*/ */
private String activeList; private String activeList;
/** /**
* Default privacy list used for the session's user. This list is processed if there * Default privacy list used for the session's user. This list is processed if there
* is no active list set for the session. * is no active list set for the session.
*/ */
private String defaultList; private String defaultList;
static { static {
// Fill out the allowedIPs with the system property // Fill out the allowedIPs with the system property
String allowed = JiveGlobals.getProperty(ConnectionSettings.Client.LOGIN_ALLOWED, ""); String allowed = JiveGlobals.getProperty(ConnectionSettings.Client.LOGIN_ALLOWED, "");
StringTokenizer tokens = new StringTokenizer(allowed, ", "); StringTokenizer tokens = new StringTokenizer(allowed, ", ");
while (tokens.hasMoreTokens()) { while (tokens.hasMoreTokens()) {
String address = tokens.nextToken().trim(); String address = tokens.nextToken().trim();
allowedIPs.put(address, ""); allowedIPs.put(address, "");
} }
String allowedAnonym = JiveGlobals.getProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED, ""); String allowedAnonym = JiveGlobals.getProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED, "");
tokens = new StringTokenizer(allowedAnonym, ", "); tokens = new StringTokenizer(allowedAnonym, ", ");
while (tokens.hasMoreTokens()) { while (tokens.hasMoreTokens()) {
String address = tokens.nextToken().trim(); String address = tokens.nextToken().trim();
allowedAnonymIPs.put(address, ""); allowedAnonymIPs.put(address, "");
} }
} }
/** /**
* Returns the list of IP address that are allowed to connect to the server. If the list is * Returns the list of IP address that are allowed to connect to the server. If the list is
* empty then anyone is allowed to connect to the server except for anonymous users that are * empty then anyone is allowed to connect to the server except for anonymous users that are
* subject to {@link #getAllowedAnonymIPs()}. This list is used for both anonymous and * subject to {@link #getAllowedAnonymIPs()}. This list is used for both anonymous and
* non-anonymous users. * non-anonymous users.
* *
* @return the list of IP address that are allowed to connect to the server. * @return the list of IP address that are allowed to connect to the server.
*/ */
public static Map<String, String> getAllowedIPs() { public static Map<String, String> getAllowedIPs() {
return allowedIPs; return allowedIPs;
} }
/** /**
* Returns the list of IP address that are allowed to connect to the server for anonymous * Returns the list of IP address that are allowed to connect to the server for anonymous
* users. If the list is empty then anonymous will be only restricted by {@link #getAllowedIPs()}. * users. If the list is empty then anonymous will be only restricted by {@link #getAllowedIPs()}.
* *
* @return the list of IP address that are allowed to connect to the server. * @return the list of IP address that are allowed to connect to the server.
*/ */
public static Map<String, String> getAllowedAnonymIPs() { public static Map<String, String> getAllowedAnonymIPs() {
return allowedAnonymIPs; return allowedAnonymIPs;
} }
/** /**
* Returns a newly created session between the server and a client. The session will * Returns a newly created session between the server and a client. The session will
* be created and returned only if correct name/prefix (i.e. 'stream' or 'flash') * be created and returned only if correct name/prefix (i.e. 'stream' or 'flash')
* and namespace were provided by the client. * and namespace were provided by the client.
* *
* @param serverName the name of the server where the session is connecting to. * @param serverName the name of the server where the session is connecting to.
* @param xpp the parser that is reading the provided XML through the connection. * @param xpp the parser that is reading the provided XML through the connection.
* @param connection the connection with the client. * @param connection the connection with the client.
* @return a newly created session between the server and a client. * @return a newly created session between the server and a client.
* @throws org.xmlpull.v1.XmlPullParserException if an error occurs while parsing incoming data. * @throws org.xmlpull.v1.XmlPullParserException if an error occurs while parsing incoming data.
*/ */
public static LocalClientSession createSession(String serverName, XmlPullParser xpp, Connection connection) public static LocalClientSession createSession(String serverName, XmlPullParser xpp, Connection connection)
throws XmlPullParserException { throws XmlPullParserException {
boolean isFlashClient = xpp.getPrefix().equals("flash"); boolean isFlashClient = xpp.getPrefix().equals("flash");
connection.setFlashClient(isFlashClient); connection.setFlashClient(isFlashClient);
// Conduct error checking, the opening tag should be 'stream' // Conduct error checking, the opening tag should be 'stream'
// in the 'etherx' namespace // in the 'etherx' namespace
if (!xpp.getName().equals("stream") && !isFlashClient) { if (!xpp.getName().equals("stream") && !isFlashClient) {
throw new XmlPullParserException( throw new XmlPullParserException(
LocaleUtils.getLocalizedString("admin.error.bad-stream")); LocaleUtils.getLocalizedString("admin.error.bad-stream"));
} }
if (!xpp.getNamespace(xpp.getPrefix()).equals(ETHERX_NAMESPACE) && if (!xpp.getNamespace(xpp.getPrefix()).equals(ETHERX_NAMESPACE) &&
!(isFlashClient && xpp.getNamespace(xpp.getPrefix()).equals(FLASH_NAMESPACE))) !(isFlashClient && xpp.getNamespace(xpp.getPrefix()).equals(FLASH_NAMESPACE)))
{ {
throw new XmlPullParserException(LocaleUtils.getLocalizedString( throw new XmlPullParserException(LocaleUtils.getLocalizedString(
"admin.error.bad-namespace")); "admin.error.bad-namespace"));
} }
if (!allowedIPs.isEmpty()) { if (!allowedIPs.isEmpty()) {
String hostAddress = "Unknown"; String hostAddress = "Unknown";
// The server is using a whitelist so check that the IP address of the client // The server is using a whitelist so check that the IP address of the client
// is authorized to connect to the server // is authorized to connect to the server
try { try {
hostAddress = connection.getHostAddress(); hostAddress = connection.getHostAddress();
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
// Do nothing // Do nothing
} }
if (!isAllowed(connection)) { if (!isAllowed(connection)) {
// Client cannot connect from this IP address so end the stream and // Client cannot connect from this IP address so end the stream and
// TCP connection // TCP connection
Log.debug("LocalClientSession: Closed connection to client attempting to connect from: " + hostAddress); Log.debug("LocalClientSession: Closed connection to client attempting to connect from: " + hostAddress);
// Include the not-authorized error in the response // Include the not-authorized error in the response
StreamError error = new StreamError(StreamError.Condition.not_authorized); StreamError error = new StreamError(StreamError.Condition.not_authorized);
connection.deliverRawText(error.toXML()); connection.deliverRawText(error.toXML());
// Close the underlying connection // Close the underlying connection
connection.close(); connection.close();
return null; return null;
} }
} }
// Default language is English ("en"). // Default language is English ("en").
String language = "en"; String language = "en";
// Default to a version of "0.0". Clients written before the XMPP 1.0 spec may // Default to a version of "0.0". Clients written before the XMPP 1.0 spec may
// not report a version in which case "0.0" should be assumed (per rfc3920 // not report a version in which case "0.0" should be assumed (per rfc3920
// section 4.4.1). // section 4.4.1).
int majorVersion = 0; int majorVersion = 0;
int minorVersion = 0; int minorVersion = 0;
for (int i = 0; i < xpp.getAttributeCount(); i++) { for (int i = 0; i < xpp.getAttributeCount(); i++) {
if ("lang".equals(xpp.getAttributeName(i))) { if ("lang".equals(xpp.getAttributeName(i))) {
language = xpp.getAttributeValue(i); language = xpp.getAttributeValue(i);
} }
if ("version".equals(xpp.getAttributeName(i))) { if ("version".equals(xpp.getAttributeName(i))) {
try { try {
int[] version = decodeVersion(xpp.getAttributeValue(i)); int[] version = decodeVersion(xpp.getAttributeValue(i));
majorVersion = version[0]; majorVersion = version[0];
minorVersion = version[1]; minorVersion = version[1];
} }
catch (Exception e) { catch (Exception e) {
Log.error(e.getMessage(), e); Log.error(e.getMessage(), e);
} }
} }
} }
// If the client supports a greater major version than the server, // If the client supports a greater major version than the server,
// set the version to the highest one the server supports. // set the version to the highest one the server supports.
if (majorVersion > MAJOR_VERSION) { if (majorVersion > MAJOR_VERSION) {
majorVersion = MAJOR_VERSION; majorVersion = MAJOR_VERSION;
minorVersion = MINOR_VERSION; minorVersion = MINOR_VERSION;
} }
else if (majorVersion == MAJOR_VERSION) { else if (majorVersion == MAJOR_VERSION) {
// If the client supports a greater minor version than the // If the client supports a greater minor version than the
// server, set the version to the highest one that the server // server, set the version to the highest one that the server
// supports. // supports.
if (minorVersion > MINOR_VERSION) { if (minorVersion > MINOR_VERSION) {
minorVersion = MINOR_VERSION; minorVersion = MINOR_VERSION;
} }
} }
// Store language and version information in the connection. // Store language and version information in the connection.
connection.setLanaguage(language); connection.setLanaguage(language);
connection.setXMPPVersion(majorVersion, minorVersion); connection.setXMPPVersion(majorVersion, minorVersion);
// Indicate the TLS policy to use for this connection // Indicate the TLS policy to use for this connection
if (!connection.isSecure()) { if (!connection.isSecure()) {
boolean hasCertificates = false; boolean hasCertificates = false;
try { try {
hasCertificates = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ).size() > 0; hasCertificates = SSLConfig.getIdentityStore( Purpose.SOCKET_C2S ).size() > 0;
} }
catch (Exception e) { catch (Exception e) {
Log.error(e.getMessage(), e); Log.error(e.getMessage(), e);
} }
Connection.TLSPolicy tlsPolicy = getTLSPolicy(); Connection.TLSPolicy tlsPolicy = getTLSPolicy();
if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) { if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) {
Log.error("Client session rejected. TLS is required but no certificates " + Log.error("Client session rejected. TLS is required but no certificates " +
"were created."); "were created.");
return null; return null;
} }
// Set default TLS policy // Set default TLS policy
connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled); connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled);
} else { } else {
// Set default TLS policy // Set default TLS policy
connection.setTlsPolicy(Connection.TLSPolicy.disabled); connection.setTlsPolicy(Connection.TLSPolicy.disabled);
} }
// Indicate the compression policy to use for this connection // Indicate the compression policy to use for this connection
connection.setCompressionPolicy(getCompressionPolicy()); connection.setCompressionPolicy(getCompressionPolicy());
// Create a ClientSession for this user. // Create a ClientSession for this user.
LocalClientSession session = SessionManager.getInstance().createClientSession(connection); LocalClientSession session = SessionManager.getInstance().createClientSession(connection);
// Build the start packet response // Build the start packet response
StringBuilder sb = new StringBuilder(200); StringBuilder sb = new StringBuilder(200);
sb.append("<?xml version='1.0' encoding='"); sb.append("<?xml version='1.0' encoding='");
sb.append(CHARSET); sb.append(CHARSET);
sb.append("'?>"); sb.append("'?>");
if (isFlashClient) { if (isFlashClient) {
sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" "); sb.append("<flash:stream xmlns:flash=\"http://www.jabber.com/streams/flash\" ");
} }
else { else {
sb.append("<stream:stream "); sb.append("<stream:stream ");
} }
sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\" from=\""); sb.append("xmlns:stream=\"http://etherx.jabber.org/streams\" xmlns=\"jabber:client\" from=\"");
sb.append(serverName); sb.append(serverName);
sb.append("\" id=\""); sb.append("\" id=\"");
sb.append(session.getStreamID().toString()); sb.append(session.getStreamID().toString());
sb.append("\" xml:lang=\""); sb.append("\" xml:lang=\"");
sb.append(language); sb.append(language);
// Don't include version info if the version is 0.0. // Don't include version info if the version is 0.0.
if (majorVersion != 0) { if (majorVersion != 0) {
sb.append("\" version=\""); sb.append("\" version=\"");
sb.append(majorVersion).append('.').append(minorVersion); sb.append(majorVersion).append('.').append(minorVersion);
} }
sb.append("\">"); sb.append("\">");
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
// If this is a "Jabber" connection, the session is now initialized and we can // If this is a "Jabber" connection, the session is now initialized and we can
// return to allow normal packet parsing. // return to allow normal packet parsing.
if (majorVersion == 0) { if (majorVersion == 0) {
return session; return session;
} }
// Otherwise, this is at least XMPP 1.0 so we need to announce stream features. // Otherwise, this is at least XMPP 1.0 so we need to announce stream features.
sb = new StringBuilder(490); sb = new StringBuilder(490);
sb.append("<stream:features>"); sb.append("<stream:features>");
if (connection.getTlsPolicy() != Connection.TLSPolicy.disabled) { if (connection.getTlsPolicy() != Connection.TLSPolicy.disabled) {
sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"); sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
if (connection.getTlsPolicy() == Connection.TLSPolicy.required) { if (connection.getTlsPolicy() == Connection.TLSPolicy.required) {
sb.append("<required/>"); sb.append("<required/>");
} }
sb.append("</starttls>"); sb.append("</starttls>");
} }
// Include available SASL Mechanisms // Include available SASL Mechanisms
sb.append(SASLAuthentication.getSASLMechanisms(session)); sb.append(SASLAuthentication.getSASLMechanisms(session));
// Include Stream features // Include Stream features
String specificFeatures = session.getAvailableStreamFeatures(); String specificFeatures = session.getAvailableStreamFeatures();
if (specificFeatures != null) { if (specificFeatures != null) {
sb.append(specificFeatures); sb.append(specificFeatures);
} }
sb.append("</stream:features>"); sb.append("</stream:features>");
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
return session; return session;
} }
public static boolean isAllowed(Connection connection) { public static boolean isAllowed(Connection connection) {
if (!allowedIPs.isEmpty()) { if (!allowedIPs.isEmpty()) {
// The server is using a whitelist so check that the IP address of the client // The server is using a whitelist so check that the IP address of the client
// is authorized to connect to the server // is authorized to connect to the server
boolean forbidAccess = false; boolean forbidAccess = false;
try { try {
if (!allowedIPs.containsKey(connection.getHostAddress())) { if (!allowedIPs.containsKey(connection.getHostAddress())) {
byte[] address = connection.getAddress(); byte[] address = connection.getAddress();
String range1 = (address[0] & 0xff) + "." + (address[1] & 0xff) + "." + String range1 = (address[0] & 0xff) + "." + (address[1] & 0xff) + "." +
(address[2] & 0xff) + (address[2] & 0xff) +
".*"; ".*";
String range2 = (address[0] & 0xff) + "." + (address[1] & 0xff) + ".*.*"; String range2 = (address[0] & 0xff) + "." + (address[1] & 0xff) + ".*.*";
String range3 = (address[0] & 0xff) + ".*.*.*"; String range3 = (address[0] & 0xff) + ".*.*.*";
if (!allowedIPs.containsKey(range1) && !allowedIPs.containsKey(range2) && if (!allowedIPs.containsKey(range1) && !allowedIPs.containsKey(range2) &&
!allowedIPs.containsKey(range3)) { !allowedIPs.containsKey(range3)) {
forbidAccess = true; forbidAccess = true;
} }
} }
} catch (UnknownHostException e) { } catch (UnknownHostException e) {
forbidAccess = true; forbidAccess = true;
} }
return !forbidAccess; return !forbidAccess;
} }
return true; return true;
} }
/** /**
* Sets the list of IP address that are allowed to connect to the server. If the list is * Sets the list of IP address that are allowed to connect to the server. If the list is
* empty then anyone is allowed to connect to the server except for anonymous users that are * empty then anyone is allowed to connect to the server except for anonymous users that are
* subject to {@link #getAllowedAnonymIPs()}. This list is used for both anonymous and * subject to {@link #getAllowedAnonymIPs()}. This list is used for both anonymous and
* non-anonymous users. * non-anonymous users.
* *
* @param allowed the list of IP address that are allowed to connect to the server. * @param allowed the list of IP address that are allowed to connect to the server.
*/ */
public static void setAllowedIPs(Map<String, String> allowed) { public static void setAllowedIPs(Map<String, String> allowed) {
allowedIPs = allowed; allowedIPs = allowed;
if (allowedIPs.isEmpty()) { if (allowedIPs.isEmpty()) {
JiveGlobals.deleteProperty(ConnectionSettings.Client.LOGIN_ALLOWED); JiveGlobals.deleteProperty(ConnectionSettings.Client.LOGIN_ALLOWED);
} }
else { else {
// Iterate through the elements in the map. // Iterate through the elements in the map.
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
Iterator<String> iter = allowedIPs.keySet().iterator(); Iterator<String> iter = allowedIPs.keySet().iterator();
if (iter.hasNext()) { if (iter.hasNext()) {
buf.append(iter.next()); buf.append(iter.next());
} }
while (iter.hasNext()) { while (iter.hasNext()) {
buf.append(", ").append(iter.next()); buf.append(", ").append(iter.next());
} }
JiveGlobals.setProperty(ConnectionSettings.Client.LOGIN_ALLOWED, buf.toString()); JiveGlobals.setProperty(ConnectionSettings.Client.LOGIN_ALLOWED, buf.toString());
} }
} }
/** /**
* Sets the list of IP address that are allowed to connect to the server for anonymous * Sets the list of IP address that are allowed to connect to the server for anonymous
* users. If the list is empty then anonymous will be only restricted by {@link #getAllowedIPs()}. * users. If the list is empty then anonymous will be only restricted by {@link #getAllowedIPs()}.
* *
* @param allowed the list of IP address that are allowed to connect to the server. * @param allowed the list of IP address that are allowed to connect to the server.
*/ */
public static void setAllowedAnonymIPs(Map<String, String> allowed) { public static void setAllowedAnonymIPs(Map<String, String> allowed) {
allowedAnonymIPs = allowed; allowedAnonymIPs = allowed;
if (allowedAnonymIPs.isEmpty()) { if (allowedAnonymIPs.isEmpty()) {
JiveGlobals.deleteProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED); JiveGlobals.deleteProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED);
} }
else { else {
// Iterate through the elements in the map. // Iterate through the elements in the map.
StringBuilder buf = new StringBuilder(); StringBuilder buf = new StringBuilder();
Iterator<String> iter = allowedAnonymIPs.keySet().iterator(); Iterator<String> iter = allowedAnonymIPs.keySet().iterator();
if (iter.hasNext()) { if (iter.hasNext()) {
buf.append(iter.next()); buf.append(iter.next());
} }
while (iter.hasNext()) { while (iter.hasNext()) {
buf.append(", ").append(iter.next()); buf.append(", ").append(iter.next());
} }
JiveGlobals.setProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED, buf.toString()); JiveGlobals.setProperty(ConnectionSettings.Client.LOGIN_ANONYM_ALLOWED, buf.toString());
} }
} }
/** /**
* Returns whether TLS is mandatory, optional or is disabled for clients. When TLS is * Returns whether TLS is mandatory, optional or is disabled for clients. When TLS is
* mandatory clients are required to secure their connections or otherwise their connections * mandatory clients are required to secure their connections or otherwise their connections
* will be closed. On the other hand, when TLS is disabled clients are not allowed to secure * will be closed. On the other hand, when TLS is disabled clients are not allowed to secure
* their connections using TLS. Their connections will be closed if they try to secure the * their connections using TLS. Their connections will be closed if they try to secure the
* connection. in this last case. * connection. in this last case.
* *
* @return whether TLS is mandatory, optional or is disabled. * @return whether TLS is mandatory, optional or is disabled.
*/ */
public static SocketConnection.TLSPolicy getTLSPolicy() { public static SocketConnection.TLSPolicy getTLSPolicy() {
// Set the TLS policy stored as a system property // Set the TLS policy stored as a system property
String policyName = JiveGlobals.getProperty(ConnectionSettings.Client.TLS_POLICY, Connection.TLSPolicy.optional.toString()); String policyName = JiveGlobals.getProperty(ConnectionSettings.Client.TLS_POLICY, Connection.TLSPolicy.optional.toString());
SocketConnection.TLSPolicy tlsPolicy; SocketConnection.TLSPolicy tlsPolicy;
try { try {
tlsPolicy = Connection.TLSPolicy.valueOf(policyName); tlsPolicy = Connection.TLSPolicy.valueOf(policyName);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.error("Error parsing xmpp.client.tls.policy: " + policyName, e); Log.error("Error parsing xmpp.client.tls.policy: " + policyName, e);
tlsPolicy = Connection.TLSPolicy.optional; tlsPolicy = Connection.TLSPolicy.optional;
} }
return tlsPolicy; return tlsPolicy;
} }
/** /**
* Sets whether TLS is mandatory, optional or is disabled for clients. When TLS is * Sets whether TLS is mandatory, optional or is disabled for clients. When TLS is
* mandatory clients are required to secure their connections or otherwise their connections * mandatory clients are required to secure their connections or otherwise their connections
* will be closed. On the other hand, when TLS is disabled clients are not allowed to secure * will be closed. On the other hand, when TLS is disabled clients are not allowed to secure
* their connections using TLS. Their connections will be closed if they try to secure the * their connections using TLS. Their connections will be closed if they try to secure the
* connection. in this last case. * connection. in this last case.
* *
* @param policy whether TLS is mandatory, optional or is disabled. * @param policy whether TLS is mandatory, optional or is disabled.
*/ */
public static void setTLSPolicy(SocketConnection.TLSPolicy policy) { public static void setTLSPolicy(SocketConnection.TLSPolicy policy) {
JiveGlobals.setProperty(ConnectionSettings.Client.TLS_POLICY, policy.toString()); JiveGlobals.setProperty(ConnectionSettings.Client.TLS_POLICY, policy.toString());
} }
/** /**
* Returns whether compression is optional or is disabled for clients. * Returns whether compression is optional or is disabled for clients.
* *
* @return whether compression is optional or is disabled. * @return whether compression is optional or is disabled.
*/ */
public static SocketConnection.CompressionPolicy getCompressionPolicy() { public static SocketConnection.CompressionPolicy getCompressionPolicy() {
// Set the Compression policy stored as a system property // Set the Compression policy stored as a system property
String policyName = JiveGlobals String policyName = JiveGlobals
.getProperty(ConnectionSettings.Client.COMPRESSION_SETTINGS, Connection.CompressionPolicy.optional.toString()); .getProperty(ConnectionSettings.Client.COMPRESSION_SETTINGS, Connection.CompressionPolicy.optional.toString());
SocketConnection.CompressionPolicy compressionPolicy; SocketConnection.CompressionPolicy compressionPolicy;
try { try {
compressionPolicy = Connection.CompressionPolicy.valueOf(policyName); compressionPolicy = Connection.CompressionPolicy.valueOf(policyName);
} catch (IllegalArgumentException e) { } catch (IllegalArgumentException e) {
Log.error("Error parsing xmpp.client.compression.policy: " + policyName, e); Log.error("Error parsing xmpp.client.compression.policy: " + policyName, e);
compressionPolicy = Connection.CompressionPolicy.optional; compressionPolicy = Connection.CompressionPolicy.optional;
} }
return compressionPolicy; return compressionPolicy;
} }
/** /**
* Sets whether compression is optional or is disabled for clients. * Sets whether compression is optional or is disabled for clients.
* *
* @param policy whether compression is optional or is disabled. * @param policy whether compression is optional or is disabled.
*/ */
public static void setCompressionPolicy(SocketConnection.CompressionPolicy policy) { public static void setCompressionPolicy(SocketConnection.CompressionPolicy policy) {
JiveGlobals.setProperty(ConnectionSettings.Client.COMPRESSION_SETTINGS, policy.toString()); JiveGlobals.setProperty(ConnectionSettings.Client.COMPRESSION_SETTINGS, policy.toString());
} }
/** /**
* Returns the Privacy list that overrides the default privacy list. This list affects * Returns the Privacy list that overrides the default privacy list. This list affects
* only this session and only for the duration of the session. * only this session and only for the duration of the session.
* *
* @return the Privacy list that overrides the default privacy list. * @return the Privacy list that overrides the default privacy list.
*/ */
@Override @Override
public PrivacyList getActiveList() { public PrivacyList getActiveList() {
if (activeList != null) { if (activeList != null) {
try { try {
return PrivacyListManager.getInstance().getPrivacyList(getUsername(), activeList); return PrivacyListManager.getInstance().getPrivacyList(getUsername(), activeList);
} catch (UserNotFoundException e) { } catch (UserNotFoundException e) {
Log.error(e.getMessage(), e); Log.error(e.getMessage(), e);
} }
} }
return null; return null;
} }
/** /**
* Sets the Privacy list that overrides the default privacy list. This list affects * Sets the Privacy list that overrides the default privacy list. This list affects
* only this session and only for the duration of the session. * only this session and only for the duration of the session.
* *
* @param activeList the Privacy list that overrides the default privacy list. * @param activeList the Privacy list that overrides the default privacy list.
*/ */
@Override @Override
public void setActiveList(PrivacyList activeList) { public void setActiveList(PrivacyList activeList) {
this.activeList = activeList != null ? activeList.getName() : null; this.activeList = activeList != null ? activeList.getName() : null;
if (ClusterManager.isClusteringStarted()) { if (ClusterManager.isClusteringStarted()) {
// Track information about the session and share it with other cluster nodes // Track information about the session and share it with other cluster nodes
Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache(); Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache();
cache.put(getAddress().toString(), new ClientSessionInfo(this)); cache.put(getAddress().toString(), new ClientSessionInfo(this));
} }
} }
/** /**
* Returns the default Privacy list used for the session's user. This list is * Returns the default Privacy list used for the session's user. This list is
* processed if there is no active list set for the session. * processed if there is no active list set for the session.
* *
* @return the default Privacy list used for the session's user. * @return the default Privacy list used for the session's user.
*/ */
@Override @Override
public PrivacyList getDefaultList() { public PrivacyList getDefaultList() {
if (defaultList != null) { if (defaultList != null) {
try { try {
return PrivacyListManager.getInstance().getPrivacyList(getUsername(), defaultList); return PrivacyListManager.getInstance().getPrivacyList(getUsername(), defaultList);
} catch (UserNotFoundException e) { } catch (UserNotFoundException e) {
Log.error(e.getMessage(), e); Log.error(e.getMessage(), e);
} }
} }
return null; return null;
} }
/** /**
* Sets the default Privacy list used for the session's user. This list is * Sets the default Privacy list used for the session's user. This list is
* processed if there is no active list set for the session. * processed if there is no active list set for the session.
* *
* @param defaultList the default Privacy list used for the session's user. * @param defaultList the default Privacy list used for the session's user.
*/ */
@Override @Override
public void setDefaultList(PrivacyList defaultList) { public void setDefaultList(PrivacyList defaultList) {
// Do nothing if nothing has changed // Do nothing if nothing has changed
if ((this.defaultList == null && defaultList == null) || if ((this.defaultList == null && defaultList == null) ||
(defaultList != null && defaultList.getName().equals(this.defaultList))) { (defaultList != null && defaultList.getName().equals(this.defaultList))) {
return; return;
} }
this.defaultList = defaultList != null ? defaultList.getName() : null; this.defaultList = defaultList != null ? defaultList.getName() : null;
if (ClusterManager.isClusteringStarted()) { if (ClusterManager.isClusteringStarted()) {
// Track information about the session and share it with other cluster nodes // Track information about the session and share it with other cluster nodes
Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache(); Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache();
cache.put(getAddress().toString(), new ClientSessionInfo(this)); cache.put(getAddress().toString(), new ClientSessionInfo(this));
} }
} }
/** /**
* Creates a session with an underlying connection and permission protection. * Creates a session with an underlying connection and permission protection.
* *
* @param serverName name of the server. * @param serverName name of the server.
* @param connection The connection we are proxying. * @param connection The connection we are proxying.
* @param streamID unique identifier of this session. * @param streamID unique identifier of this session.
*/ */
public LocalClientSession(String serverName, Connection connection, StreamID streamID) { public LocalClientSession(String serverName, Connection connection, StreamID streamID) {
super(serverName, connection, streamID); super(serverName, connection, streamID);
// Set an unavailable initial presence // Set an unavailable initial presence
presence = new Presence(); presence = new Presence();
presence.setType(Presence.Type.unavailable); presence.setType(Presence.Type.unavailable);
} }
/** /**
* Returns the username associated with this session. Use this information * Returns the username associated with this session. Use this information
* with the user manager to obtain the user based on username. * with the user manager to obtain the user based on username.
* *
* @return the username associated with this session * @return the username associated with this session
* @throws org.jivesoftware.openfire.user.UserNotFoundException if a user is not associated with a session * @throws org.jivesoftware.openfire.user.UserNotFoundException if a user is not associated with a session
* (the session has not authenticated yet) * (the session has not authenticated yet)
*/ */
@Override @Override
public String getUsername() throws UserNotFoundException { public String getUsername() throws UserNotFoundException {
if (authToken == null) { if (authToken == null) {
throw new UserNotFoundException(); throw new UserNotFoundException();
} }
return getAddress().getNode(); return getAddress().getNode();
} }
/** /**
* Sets the new Authorization Token for this session. The session is not yet considered fully * Sets the new Authorization Token for this session. The session is not yet considered fully
* authenticated (i.e. active) since a resource has not been binded at this point. This * authenticated (i.e. active) since a resource has not been binded at this point. This
* message will be sent after SASL authentication was successful but yet resource binding * message will be sent after SASL authentication was successful but yet resource binding
* is required. * is required.
* *
* @param auth the authentication token obtained from SASL authentication. * @param auth the authentication token obtained from SASL authentication.
*/ */
public void setAuthToken(AuthToken auth) { public void setAuthToken(AuthToken auth) {
authToken = auth; authToken = auth;
} }
/** /**
* Initialize the session with a valid authentication token and * Initialize the session with a valid authentication token and
* resource name. This automatically upgrades the session's * resource name. This automatically upgrades the session's
* status to authenticated and enables many features that are not * status to authenticated and enables many features that are not
* available until authenticated (obtaining managers for example). * available until authenticated (obtaining managers for example).
* *
* @param auth the authentication token obtained from the AuthFactory. * @param auth the authentication token obtained from the AuthFactory.
* @param resource the resource this session authenticated under. * @param resource the resource this session authenticated under.
*/ */
public void setAuthToken(AuthToken auth, String resource) { public void setAuthToken(AuthToken auth, String resource) {
setAddress(new JID(auth.getUsername(), getServerName(), resource)); setAddress(new JID(auth.getUsername(), getServerName(), resource));
authToken = auth; authToken = auth;
setStatus(Session.STATUS_AUTHENTICATED); setStatus(Session.STATUS_AUTHENTICATED);
// Set default privacy list for this session // Set default privacy list for this session
setDefaultList(PrivacyListManager.getInstance().getDefaultPrivacyList(auth.getUsername())); setDefaultList(PrivacyListManager.getInstance().getDefaultPrivacyList(auth.getUsername()));
// Add session to the session manager. The session will be added to the routing table as well // Add session to the session manager. The session will be added to the routing table as well
sessionManager.addSession(this); sessionManager.addSession(this);
} }
/** /**
* Initialize the session as an anonymous login. This automatically upgrades the session's * Initialize the session as an anonymous login. This automatically upgrades the session's
* status to authenticated and enables many features that are not available until * status to authenticated and enables many features that are not available until
* authenticated (obtaining managers for example).<p> * authenticated (obtaining managers for example).<p>
*/ */
public void setAnonymousAuth() { public void setAnonymousAuth() {
// Anonymous users have a full JID. Use the random resource as the JID's node // Anonymous users have a full JID. Use the random resource as the JID's node
String resource = getAddress().getResource(); String resource = getAddress().getResource();
setAddress(new JID(resource, getServerName(), resource, true)); setAddress(new JID(resource, getServerName(), resource, true));
setStatus(Session.STATUS_AUTHENTICATED); setStatus(Session.STATUS_AUTHENTICATED);
if (authToken == null) { if (authToken == null) {
authToken = new AuthToken(resource, true); authToken = new AuthToken(resource, true);
} }
// Add session to the session manager. The session will be added to the routing table as well // Add session to the session manager. The session will be added to the routing table as well
sessionManager.addSession(this); sessionManager.addSession(this);
} }
/** /**
* Returns the authentication token associated with this session. * Returns the authentication token associated with this session.
* *
* @return the authentication token associated with this session (can be null). * @return the authentication token associated with this session (can be null).
*/ */
public AuthToken getAuthToken() { public AuthToken getAuthToken() {
return authToken; return authToken;
} }
@Override @Override
public boolean isAnonymousUser() { public boolean isAnonymousUser() {
return authToken == null || authToken.isAnonymous(); return authToken == null || authToken.isAnonymous();
} }
/** /**
* Flag indicating if this session has been initialized once coming * Flag indicating if this session has been initialized once coming
* online. Session initialization occurs after the session receives * online. Session initialization occurs after the session receives
* the first "available" presence update from the client. Initialization * the first "available" presence update from the client. Initialization
* actions include pushing offline messages, presence subscription requests, * actions include pushing offline messages, presence subscription requests,
* and presence statuses to the client. Initialization occurs only once * and presence statuses to the client. Initialization occurs only once
* following the first available presence transition. * following the first available presence transition.
* *
* @return True if the session has already been initializsed * @return True if the session has already been initializsed
*/ */
@Override @Override
public boolean isInitialized() { public boolean isInitialized() {
return initialized; return initialized;
} }
/** /**
* Sets the initialization state of the session. * Sets the initialization state of the session.
* *
* @param isInit True if the session has been initialized * @param isInit True if the session has been initialized
* @see #isInitialized * @see #isInitialized
*/ */
@Override @Override
public void setInitialized(boolean isInit) { public void setInitialized(boolean isInit) {
initialized = isInit; initialized = isInit;
} }
/** /**
* Returns true if the session was available ever. * Returns true if the session was available ever.
* *
* @return true if the session was available ever. * @return true if the session was available ever.
*/ */
public boolean wasAvailable() { public boolean wasAvailable() {
return wasAvailable; return wasAvailable;
} }
/** /**
* Returns true if the offline messages of the user should be sent to the user when * Returns true if the offline messages of the user should be sent to the user when
* the user becomes online. If the user sent a disco request with node * the user becomes online. If the user sent a disco request with node
* "http://jabber.org/protocol/offline" before the available presence then do not * "http://jabber.org/protocol/offline" before the available presence then do not
* flood the user with the offline messages. If the user is connected from many resources * flood the user with the offline messages. If the user is connected from many resources
* then if one of the sessions stopped the flooding then no session should flood the user. * then if one of the sessions stopped the flooding then no session should flood the user.
* *
* @return true if the offline messages of the user should be sent to the user when the user * @return true if the offline messages of the user should be sent to the user when the user
* becomes online. * becomes online.
* @see <a href="http://www.xmpp.org/extensions/xep-0160.html">XEP-0160: Best Practices for Handling Offline Messages</a> * @see <a href="http://www.xmpp.org/extensions/xep-0160.html">XEP-0160: Best Practices for Handling Offline Messages</a>
*/ */
@Override @Override
public boolean canFloodOfflineMessages() { public boolean canFloodOfflineMessages() {
// XEP-0160: When the recipient next sends non-negative available presence to the server, the server delivers the message to the resource that has sent that presence. // XEP-0160: When the recipient next sends non-negative available presence to the server, the server delivers the message to the resource that has sent that presence.
if(offlineFloodStopped || presence.getPriority() < 0) { if(offlineFloodStopped || presence.getPriority() < 0) {
return false; return false;
} }
String username = getAddress().getNode(); String username = getAddress().getNode();
for (ClientSession session : sessionManager.getSessions(username)) { for (ClientSession session : sessionManager.getSessions(username)) {
if (session.isOfflineFloodStopped()) { if (session.isOfflineFloodStopped()) {
return false; return false;
} }
} }
return true; return true;
} }
/** /**
* Returns true if the user requested to not receive offline messages when sending * Returns true if the user requested to not receive offline messages when sending
* an available presence. The user may send a disco request with node * an available presence. The user may send a disco request with node
* "http://jabber.org/protocol/offline" so that no offline messages are sent to the * "http://jabber.org/protocol/offline" so that no offline messages are sent to the
* user when he becomes online. If the user is connected from many resources then * user when he becomes online. If the user is connected from many resources then
* if one of the sessions stopped the flooding then no session should flood the user. * if one of the sessions stopped the flooding then no session should flood the user.
* *
* @return true if the user requested to not receive offline messages when sending * @return true if the user requested to not receive offline messages when sending
* an available presence. * an available presence.
*/ */
@Override @Override
public boolean isOfflineFloodStopped() { public boolean isOfflineFloodStopped() {
return offlineFloodStopped; return offlineFloodStopped;
} }
/** /**
* Sets if the user requested to not receive offline messages when sending * Sets if the user requested to not receive offline messages when sending
* an available presence. The user may send a disco request with node * an available presence. The user may send a disco request with node
* "http://jabber.org/protocol/offline" so that no offline messages are sent to the * "http://jabber.org/protocol/offline" so that no offline messages are sent to the
* user when he becomes online. If the user is connected from many resources then * user when he becomes online. If the user is connected from many resources then
* if one of the sessions stopped the flooding then no session should flood the user. * if one of the sessions stopped the flooding then no session should flood the user.
* *
* @param offlineFloodStopped if the user requested to not receive offline messages when * @param offlineFloodStopped if the user requested to not receive offline messages when
* sending an available presence. * sending an available presence.
*/ */
public void setOfflineFloodStopped(boolean offlineFloodStopped) { public void setOfflineFloodStopped(boolean offlineFloodStopped) {
this.offlineFloodStopped = offlineFloodStopped; this.offlineFloodStopped = offlineFloodStopped;
if (ClusterManager.isClusteringStarted()) { if (ClusterManager.isClusteringStarted()) {
// Track information about the session and share it with other cluster nodes // Track information about the session and share it with other cluster nodes
Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache(); Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache();
cache.put(getAddress().toString(), new ClientSessionInfo(this)); cache.put(getAddress().toString(), new ClientSessionInfo(this));
} }
} }
/** /**
* Obtain the presence of this session. * Obtain the presence of this session.
* *
* @return The presence of this session or null if not authenticated * @return The presence of this session or null if not authenticated
*/ */
@Override @Override
public Presence getPresence() { public Presence getPresence() {
return presence; return presence;
} }
/** /**
* Set the presence of this session * Set the presence of this session
* *
* @param presence The presence for the session * @param presence The presence for the session
*/ */
@Override @Override
public void setPresence(Presence presence) { public void setPresence(Presence presence) {
Presence oldPresence = this.presence; Presence oldPresence = this.presence;
this.presence = presence; this.presence = presence;
if (oldPresence.isAvailable() && !this.presence.isAvailable()) { if (oldPresence.isAvailable() && !this.presence.isAvailable()) {
// The client is no longer available // The client is no longer available
sessionManager.sessionUnavailable(this); sessionManager.sessionUnavailable(this);
// Mark that the session is no longer initialized. This means that if the user sends // Mark that the session is no longer initialized. This means that if the user sends
// an available presence again the session will be initialized again thus receiving // an available presence again the session will be initialized again thus receiving
// offline messages and offline presence subscription requests // offline messages and offline presence subscription requests
setInitialized(false); setInitialized(false);
// Notify listeners that the session is no longer available // Notify listeners that the session is no longer available
PresenceEventDispatcher.unavailableSession(this, presence); PresenceEventDispatcher.unavailableSession(this, presence);
} }
else if (!oldPresence.isAvailable() && this.presence.isAvailable()) { else if (!oldPresence.isAvailable() && this.presence.isAvailable()) {
// The client is available // The client is available
sessionManager.sessionAvailable(this, presence); sessionManager.sessionAvailable(this, presence);
wasAvailable = true; wasAvailable = true;
// Notify listeners that the session is now available // Notify listeners that the session is now available
PresenceEventDispatcher.availableSession(this, presence); PresenceEventDispatcher.availableSession(this, presence);
} }
else if (this.presence.isAvailable() && oldPresence.getPriority() != this.presence.getPriority()) else if (this.presence.isAvailable() && oldPresence.getPriority() != this.presence.getPriority())
{ {
// The client has changed the priority of his presence // The client has changed the priority of his presence
sessionManager.changePriority(this, oldPresence.getPriority()); sessionManager.changePriority(this, oldPresence.getPriority());
// Notify listeners that the priority of the session/resource has changed // Notify listeners that the priority of the session/resource has changed
PresenceEventDispatcher.presenceChanged(this, presence); PresenceEventDispatcher.presenceChanged(this, presence);
} }
else if (this.presence.isAvailable()) { else if (this.presence.isAvailable()) {
// Notify listeners that the show or status value of the presence has changed // Notify listeners that the show or status value of the presence has changed
PresenceEventDispatcher.presenceChanged(this, presence); PresenceEventDispatcher.presenceChanged(this, presence);
} }
if (ClusterManager.isClusteringStarted()) { if (ClusterManager.isClusteringStarted()) {
// Track information about the session and share it with other cluster nodes // Track information about the session and share it with other cluster nodes
Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache(); Cache<String,ClientSessionInfo> cache = SessionManager.getInstance().getSessionInfoCache();
cache.put(getAddress().toString(), new ClientSessionInfo(this)); cache.put(getAddress().toString(), new ClientSessionInfo(this));
} }
} }
@Override @Override
public String getAvailableStreamFeatures() { public String getAvailableStreamFeatures() {
// Offer authenticate and registration only if TLS was not required or if required // Offer authenticate and registration only if TLS was not required or if required
// then the connection is already secured // then the connection is already secured
if (conn.getTlsPolicy() == Connection.TLSPolicy.required && !conn.isSecure()) { if (conn.getTlsPolicy() == Connection.TLSPolicy.required && !conn.isSecure()) {
return null; return null;
} }
StringBuilder sb = new StringBuilder(200); StringBuilder sb = new StringBuilder(200);
// Include Stream Compression Mechanism // Include Stream Compression Mechanism
if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled && if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled &&
!conn.isCompressed()) { !conn.isCompressed()) {
sb.append( sb.append(
"<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>"); "<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>");
} }
if (getAuthToken() == null) { if (getAuthToken() == null) {
// Advertise that the server supports Non-SASL Authentication // Advertise that the server supports Non-SASL Authentication
sb.append("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>"); sb.append("<auth xmlns=\"http://jabber.org/features/iq-auth\"/>");
// Advertise that the server supports In-Band Registration // Advertise that the server supports In-Band Registration
if (XMPPServer.getInstance().getIQRegisterHandler().isInbandRegEnabled()) { if (XMPPServer.getInstance().getIQRegisterHandler().isInbandRegEnabled()) {
sb.append("<register xmlns=\"http://jabber.org/features/iq-register\"/>"); sb.append("<register xmlns=\"http://jabber.org/features/iq-register\"/>");
} }
} }
else { else {
// If the session has been authenticated then offer resource binding, // If the session has been authenticated then offer resource binding,
// and session establishment // and session establishment
sb.append("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>"); sb.append("<bind xmlns=\"urn:ietf:params:xml:ns:xmpp-bind\"/>");
sb.append("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"><optional/></session>"); sb.append("<session xmlns=\"urn:ietf:params:xml:ns:xmpp-session\"><optional/></session>");
// Offer XEP-0198 stream management capabilities if enabled. // Offer XEP-0198 stream management capabilities if enabled.
if(JiveGlobals.getBooleanProperty("stream.management.active", true)) { if(JiveGlobals.getBooleanProperty("stream.management.active", true)) {
sb.append(String.format("<sm xmlns='%s'/>", StreamManager.NAMESPACE_V2)); sb.append(String.format("<sm xmlns='%s'/>", StreamManager.NAMESPACE_V2));
sb.append(String.format("<sm xmlns='%s'/>", StreamManager.NAMESPACE_V3)); sb.append(String.format("<sm xmlns='%s'/>", StreamManager.NAMESPACE_V3));
} }
} }
return sb.toString(); return sb.toString();
} }
/** /**
* Increments the conflict by one. * Increments the conflict by one.
*/ */
@Override @Override
public int incrementConflictCount() { public int incrementConflictCount() {
conflictCount++; conflictCount++;
return conflictCount; return conflictCount;
} }
@Override @Override
public boolean isMessageCarbonsEnabled() { public boolean isMessageCarbonsEnabled() {
return messageCarbonsEnabled; return messageCarbonsEnabled;
} }
@Override @Override
public void setMessageCarbonsEnabled(boolean enabled) { public void setMessageCarbonsEnabled(boolean enabled) {
messageCarbonsEnabled = enabled; messageCarbonsEnabled = enabled;
} }
/** /**
* Returns true if the specified packet must not be blocked based on the active or default * Returns true if the specified packet must not be blocked based on the active or default
* privacy list rules. The active list will be tried first. If none was found then the * privacy list rules. The active list will be tried first. If none was found then the
* default list is going to be used. If no default list was defined for this user then * default list is going to be used. If no default list was defined for this user then
* allow the packet to flow. * allow the packet to flow.
* *
* @param packet the packet to analyze if it must be blocked. * @param packet the packet to analyze if it must be blocked.
* @return true if the specified packet must be blocked. * @return true if the specified packet must be blocked.
*/ */
@Override @Override
public boolean canProcess(Packet packet) { public boolean canProcess(Packet packet) {
PrivacyList list = getActiveList(); PrivacyList list = getActiveList();
if (list != null) { if (list != null) {
// If a privacy list is active then make sure that the packet is not blocked // If a privacy list is active then make sure that the packet is not blocked
return !list.shouldBlockPacket(packet); return !list.shouldBlockPacket(packet);
} }
else { else {
list = getDefaultList(); list = getDefaultList();
// There is no active list so check if there exists a default list and make // There is no active list so check if there exists a default list and make
// sure that the packet is not blocked // sure that the packet is not blocked
return list == null || !list.shouldBlockPacket(packet); return list == null || !list.shouldBlockPacket(packet);
} }
} }
@Override @Override
public void deliver(Packet packet) throws UnauthorizedException { public void deliver(Packet packet) throws UnauthorizedException {
conn.deliver(packet); conn.deliver(packet);
if(streamManager.isEnabled()) { if(streamManager.isEnabled()) {
streamManager.incrementServerSentStanzas(); streamManager.incrementServerSentStanzas();
// Temporarily store packet until delivery confirmed // Temporarily store packet until delivery confirmed
streamManager.getUnacknowledgedServerStanzas().addLast(new StreamManager.UnackedPacket(new Date(), packet.createCopy())); streamManager.getUnacknowledgedServerStanzas().addLast(new StreamManager.UnackedPacket(new Date(), packet.createCopy()));
if(getNumServerPackets() % JiveGlobals.getLongProperty("stream.management.requestFrequency", 5) == 0) { if(getNumServerPackets() % JiveGlobals.getLongProperty("stream.management.requestFrequency", 5) == 0) {
streamManager.sendServerRequest(); streamManager.sendServerRequest();
} }
} }
} }
@Override @Override
public String toString() { public String toString() {
return super.toString() + " presence: " + presence; return super.toString() + " presence: " + presence;
} }
} }
/** /**
* $RCSfile: $ * $RCSfile: $
* $Revision: $ * $Revision: $
* $Date: $ * $Date: $
* *
* Copyright (C) 2005-2008 Jive Software. All rights reserved. * Copyright (C) 2005-2008 Jive Software. All rights reserved.
* *
* Licensed under the Apache License, Version 2.0 (the "License"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License. * you may not use this file except in compliance with the License.
* You may obtain a copy of the License at * You may obtain a copy of the License at
* *
* http://www.apache.org/licenses/LICENSE-2.0 * http://www.apache.org/licenses/LICENSE-2.0
* *
* Unless required by applicable law or agreed to in writing, software * Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS, * distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package org.jivesoftware.openfire.session; package org.jivesoftware.openfire.session;
import java.io.IOException; import java.io.IOException;
import java.security.KeyStore; import java.security.KeyStore;
import java.security.KeyStoreException; import java.security.KeyStoreException;
import java.security.cert.Certificate; import java.security.cert.Certificate;
import java.security.cert.X509Certificate; import java.security.cert.X509Certificate;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashSet; import java.util.HashSet;
import java.util.Set; import java.util.Set;
import org.dom4j.Element; import org.dom4j.Element;
import org.dom4j.io.XMPPPacketReader; import org.dom4j.io.XMPPPacketReader;
import org.jivesoftware.openfire.Connection; import org.jivesoftware.openfire.Connection;
import org.jivesoftware.openfire.SessionManager; import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.StreamID; import org.jivesoftware.openfire.StreamID;
import org.jivesoftware.openfire.auth.UnauthorizedException; import org.jivesoftware.openfire.auth.UnauthorizedException;
import org.jivesoftware.openfire.keystore.Purpose; import org.jivesoftware.openfire.keystore.Purpose;
import org.jivesoftware.openfire.net.SASLAuthentication; import org.jivesoftware.openfire.net.SASLAuthentication;
import org.jivesoftware.openfire.net.SSLConfig; import org.jivesoftware.openfire.net.SSLConfig;
import org.jivesoftware.openfire.net.SocketConnection; import org.jivesoftware.openfire.net.SocketConnection;
import org.jivesoftware.openfire.server.ServerDialback; import org.jivesoftware.openfire.server.ServerDialback;
import org.jivesoftware.util.CertificateManager; import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveGlobals; import org.jivesoftware.util.JiveGlobals;
import org.slf4j.Logger; import org.slf4j.Logger;
import org.slf4j.LoggerFactory; import org.slf4j.LoggerFactory;
import org.xmlpull.v1.XmlPullParser; import org.xmlpull.v1.XmlPullParser;
import org.xmlpull.v1.XmlPullParserException; import org.xmlpull.v1.XmlPullParserException;
import org.xmpp.packet.JID; import org.xmpp.packet.JID;
import org.xmpp.packet.Packet; import org.xmpp.packet.Packet;
/** /**
* Server-to-server communication is done using two TCP connections between the servers. One * Server-to-server communication is done using two TCP connections between the servers. One
* connection is used for sending packets while the other connection is used for receiving packets. * connection is used for sending packets while the other connection is used for receiving packets.
* The <tt>IncomingServerSession</tt> represents the connection to a remote server that will only * The <tt>IncomingServerSession</tt> represents the connection to a remote server that will only
* be used for receiving packets.<p> * be used for receiving packets.<p>
* *
* Currently only the Server Dialback method is being used for authenticating the remote server. * Currently only the Server Dialback method is being used for authenticating the remote server.
* Once the remote server has been authenticated incoming packets will be processed by this server. * Once the remote server has been authenticated incoming packets will be processed by this server.
* It is also possible for remote servers to authenticate more domains once the session has been * It is also possible for remote servers to authenticate more domains once the session has been
* established. For optimization reasons the existing connection is used between the servers. * established. For optimization reasons the existing connection is used between the servers.
* Therefore, the incoming server session holds the list of authenticated domains which are allowed * Therefore, the incoming server session holds the list of authenticated domains which are allowed
* to send packets to this server.<p> * to send packets to this server.<p>
* *
* Using the Server Dialback method it is possible that this server may also act as the * Using the Server Dialback method it is possible that this server may also act as the
* Authoritative Server. This implies that an incoming connection will be established with this * Authoritative Server. This implies that an incoming connection will be established with this
* server for authenticating a domain. This incoming connection will only last for a brief moment * server for authenticating a domain. This incoming connection will only last for a brief moment
* and after the domain has been authenticated the connection will be closed and no session will * and after the domain has been authenticated the connection will be closed and no session will
* exist. * exist.
* *
* @author Gaston Dombiak * @author Gaston Dombiak
*/ */
public class LocalIncomingServerSession extends LocalServerSession implements IncomingServerSession { public class LocalIncomingServerSession extends LocalServerSession implements IncomingServerSession {
private static final Logger Log = LoggerFactory.getLogger(LocalIncomingServerSession.class); private static final Logger Log = LoggerFactory.getLogger(LocalIncomingServerSession.class);
/** /**
* List of domains, subdomains and virtual hostnames of the remote server that were * List of domains, subdomains and virtual hostnames of the remote server that were
* validated with this server. The remote server is allowed to send packets to this * validated with this server. The remote server is allowed to send packets to this
* server from any of the validated domains. * server from any of the validated domains.
*/ */
private Set<String> validatedDomains = new HashSet<>(); private Set<String> validatedDomains = new HashSet<>();
/** /**
* Domains or subdomain of this server that was used by the remote server * Domains or subdomain of this server that was used by the remote server
* when validating the new connection. This information is useful to prevent * when validating the new connection. This information is useful to prevent
* many connections from the same remote server to the same local domain. * many connections from the same remote server to the same local domain.
*/ */
private String localDomain = null; private String localDomain = null;
/** /**
* Default domain, as supplied in stream header typically. * Default domain, as supplied in stream header typically.
*/ */
private String fromDomain = null; private String fromDomain = null;
/** /**
* Creates a new session that will receive packets. The new session will be authenticated * Creates a new session that will receive packets. The new session will be authenticated
* before being returned. If the authentication process fails then the answer will be * before being returned. If the authentication process fails then the answer will be
* <tt>null</tt>.<p> * <tt>null</tt>.<p>
* *
* @param serverName hostname of this server. * @param serverName hostname of this server.
* @param reader reader on the new established connection with the remote server. * @param reader reader on the new established connection with the remote server.
* @param connection the new established connection with the remote server. * @param connection the new established connection with the remote server.
* @return a new session that will receive packets or null if a problem occured while * @return a new session that will receive packets or null if a problem occured while
* authenticating the remote server or when acting as the Authoritative Server during * authenticating the remote server or when acting as the Authoritative Server during
* a Server Dialback authentication process. * a Server Dialback authentication process.
* @throws org.xmlpull.v1.XmlPullParserException if an error occurs while parsing the XML. * @throws org.xmlpull.v1.XmlPullParserException if an error occurs while parsing the XML.
* @throws java.io.IOException if an input/output error occurs while using the connection. * @throws java.io.IOException if an input/output error occurs while using the connection.
*/ */
public static LocalIncomingServerSession createSession(String serverName, XMPPPacketReader reader, public static LocalIncomingServerSession createSession(String serverName, XMPPPacketReader reader,
SocketConnection connection) throws XmlPullParserException, IOException { SocketConnection connection) throws XmlPullParserException, IOException {
XmlPullParser xpp = reader.getXPPParser(); XmlPullParser xpp = reader.getXPPParser();
String version = xpp.getAttributeValue("", "version"); String version = xpp.getAttributeValue("", "version");
String fromDomain = xpp.getAttributeValue("", "from"); String fromDomain = xpp.getAttributeValue("", "from");
int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0,0}; int[] serverVersion = version != null ? decodeVersion(version) : new int[] {0,0};
try { try {
// Get the stream ID for the new session // Get the stream ID for the new session
StreamID streamID = SessionManager.getInstance().nextStreamID(); StreamID streamID = SessionManager.getInstance().nextStreamID();
// Create a server Session for the remote server // Create a server Session for the remote server
LocalIncomingServerSession session = LocalIncomingServerSession session =
SessionManager.getInstance().createIncomingServerSession(connection, streamID, fromDomain); SessionManager.getInstance().createIncomingServerSession(connection, streamID, fromDomain);
// Send the stream header // Send the stream header
StringBuilder openingStream = new StringBuilder(); StringBuilder openingStream = new StringBuilder();
openingStream.append("<stream:stream"); openingStream.append("<stream:stream");
openingStream.append(" xmlns:db=\"jabber:server:dialback\""); openingStream.append(" xmlns:db=\"jabber:server:dialback\"");
openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\""); openingStream.append(" xmlns:stream=\"http://etherx.jabber.org/streams\"");
openingStream.append(" xmlns=\"jabber:server\""); openingStream.append(" xmlns=\"jabber:server\"");
openingStream.append(" from=\"").append(serverName).append("\""); openingStream.append(" from=\"").append(serverName).append("\"");
if (fromDomain != null) { if (fromDomain != null) {
openingStream.append(" to=\"").append(fromDomain).append("\""); openingStream.append(" to=\"").append(fromDomain).append("\"");
} }
openingStream.append(" id=\"").append(streamID).append("\""); openingStream.append(" id=\"").append(streamID).append("\"");
// OF-443: Not responding with a 1.0 version in the stream header when federating with older // OF-443: Not responding with a 1.0 version in the stream header when federating with older
// implementations appears to reduce connection issues with those domains (patch by Marcin Cieślak). // implementations appears to reduce connection issues with those domains (patch by Marcin Cieślak).
if (serverVersion[0] >= 1) { if (serverVersion[0] >= 1) {
openingStream.append(" version=\"1.0\">"); openingStream.append(" version=\"1.0\">");
} else { } else {
openingStream.append('>'); openingStream.append('>');
} }
connection.deliverRawText(openingStream.toString()); connection.deliverRawText(openingStream.toString());
if (serverVersion[0] >= 1) { if (serverVersion[0] >= 1) {
// Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection (and server dialback) // Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection (and server dialback)
// Indicate the TLS policy to use for this connection // Indicate the TLS policy to use for this connection
Connection.TLSPolicy tlsPolicy = Connection.TLSPolicy tlsPolicy =
ServerDialback.isEnabled() ? Connection.TLSPolicy.optional : ServerDialback.isEnabled() ? Connection.TLSPolicy.optional :
Connection.TLSPolicy.required; Connection.TLSPolicy.required;
boolean hasCertificates = false; boolean hasCertificates = false;
try { try {
hasCertificates = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ).size() > 0; hasCertificates = SSLConfig.getIdentityStore( Purpose.SOCKET_S2S ).size() > 0;
} }
catch (Exception e) { catch (Exception e) {
Log.error(e.getMessage(), e); Log.error(e.getMessage(), e);
} }
if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) { if (Connection.TLSPolicy.required == tlsPolicy && !hasCertificates) {
Log.error("Server session rejected. TLS is required but no certificates " + Log.error("Server session rejected. TLS is required but no certificates " +
"were created."); "were created.");
return null; return null;
} }
connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled); connection.setTlsPolicy(hasCertificates ? tlsPolicy : Connection.TLSPolicy.disabled);
} }
// Indicate the compression policy to use for this connection // Indicate the compression policy to use for this connection
String policyName = JiveGlobals.getProperty(ConnectionSettings.Server.COMPRESSION_SETTINGS, String policyName = JiveGlobals.getProperty(ConnectionSettings.Server.COMPRESSION_SETTINGS,
Connection.CompressionPolicy.disabled.toString()); Connection.CompressionPolicy.disabled.toString());
Connection.CompressionPolicy compressionPolicy = Connection.CompressionPolicy compressionPolicy =
Connection.CompressionPolicy.valueOf(policyName); Connection.CompressionPolicy.valueOf(policyName);
connection.setCompressionPolicy(compressionPolicy); connection.setCompressionPolicy(compressionPolicy);
StringBuilder sb = new StringBuilder(); StringBuilder sb = new StringBuilder();
if (serverVersion[0] >= 1) { if (serverVersion[0] >= 1) {
// Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection (and server dialback) // Remote server is XMPP 1.0 compliant so offer TLS and SASL to establish the connection (and server dialback)
// Don't offer stream-features to pre-1.0 servers, as it confuses them. Sending features to Openfire < 3.7.1 confuses it too - OF-443) // Don't offer stream-features to pre-1.0 servers, as it confuses them. Sending features to Openfire < 3.7.1 confuses it too - OF-443)
sb.append("<stream:features>"); sb.append("<stream:features>");
if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true)) { if (JiveGlobals.getBooleanProperty(ConnectionSettings.Server.TLS_ENABLED, true)) {
sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">"); sb.append("<starttls xmlns=\"urn:ietf:params:xml:ns:xmpp-tls\">");
if (!ServerDialback.isEnabled()) { if (!ServerDialback.isEnabled()) {
// Server dialback is disabled so TLS is required // Server dialback is disabled so TLS is required
sb.append("<required/>"); sb.append("<required/>");
} }
sb.append("</starttls>"); sb.append("</starttls>");
} }
// Include available SASL Mechanisms // Include available SASL Mechanisms
sb.append(SASLAuthentication.getSASLMechanisms(session)); sb.append(SASLAuthentication.getSASLMechanisms(session));
if (ServerDialback.isEnabled()) { if (ServerDialback.isEnabled()) {
// Also offer server dialback (when TLS is not required). Server dialback may be offered // Also offer server dialback (when TLS is not required). Server dialback may be offered
// after TLS has been negotiated and a self-signed certificate is being used // after TLS has been negotiated and a self-signed certificate is being used
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>"); sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>");
} }
sb.append("</stream:features>"); sb.append("</stream:features>");
} }
connection.deliverRawText(sb.toString()); connection.deliverRawText(sb.toString());
// Set the domain or subdomain of the local server targeted by the remote server // Set the domain or subdomain of the local server targeted by the remote server
session.setLocalDomain(serverName); session.setLocalDomain(serverName);
return session; return session;
} }
catch (Exception e) { catch (Exception e) {
Log.error("Error establishing connection from remote server:" + connection, e); Log.error("Error establishing connection from remote server:" + connection, e);
connection.close(); connection.close();
return null; return null;
} }
} }
public LocalIncomingServerSession(String serverName, Connection connection, StreamID streamID, String fromDomain) { public LocalIncomingServerSession(String serverName, Connection connection, StreamID streamID, String fromDomain) {
super(serverName, connection, streamID); super(serverName, connection, streamID);
this.fromDomain = fromDomain; this.fromDomain = fromDomain;
} }
public String getDefaultIdentity() { public String getDefaultIdentity() {
return this.fromDomain; return this.fromDomain;
} }
@Override @Override
boolean canProcess(Packet packet) { boolean canProcess(Packet packet) {
return true; return true;
} }
@Override @Override
void deliver(Packet packet) throws UnauthorizedException { void deliver(Packet packet) throws UnauthorizedException {
// Do nothing // Do nothing
} }
/** /**
* Returns true if the request of a new domain was valid. Sessions may receive subsequent * Returns true if the request of a new domain was valid. Sessions may receive subsequent
* domain validation request. If the validation of the new domain fails then the session and * domain validation request. If the validation of the new domain fails then the session and
* the underlying TCP connection will be closed.<p> * the underlying TCP connection will be closed.<p>
* *
* For optimization reasons, the same session may be servicing several domains of a * For optimization reasons, the same session may be servicing several domains of a
* remote server. * remote server.
* *
* @param dbResult the DOM stanza requesting the domain validation. * @param dbResult the DOM stanza requesting the domain validation.
* @return true if the requested domain was valid. * @return true if the requested domain was valid.
*/ */
public boolean validateSubsequentDomain(Element dbResult) { public boolean validateSubsequentDomain(Element dbResult) {
ServerDialback method = new ServerDialback(getConnection(), getServerName()); ServerDialback method = new ServerDialback(getConnection(), getServerName());
if (method.validateRemoteDomain(dbResult, getStreamID())) { if (method.validateRemoteDomain(dbResult, getStreamID())) {
// Add the validated domain as a valid domain // Add the validated domain as a valid domain
addValidatedDomain(dbResult.attributeValue("from")); addValidatedDomain(dbResult.attributeValue("from"));
return true; return true;
} }
return false; return false;
} }
/** /**
* Returns true if the specified domain has been validated for this session. The remote * Returns true if the specified domain has been validated for this session. The remote
* server should send a "db:result" packet for registering new subdomains or even * server should send a "db:result" packet for registering new subdomains or even
* virtual hosts.<p> * virtual hosts.<p>
* *
* In the spirit of being flexible we allow remote servers to not register subdomains * In the spirit of being flexible we allow remote servers to not register subdomains
* and even so consider subdomains that include the server domain in their domain part * and even so consider subdomains that include the server domain in their domain part
* as valid domains. * as valid domains.
* *
* @param domain the domain to validate. * @param domain the domain to validate.
* @return true if the specified domain has been validated for this session. * @return true if the specified domain has been validated for this session.
*/ */
public boolean isValidDomain(String domain) { public boolean isValidDomain(String domain) {
// Check if the specified domain is contained in any of the validated domains // Check if the specified domain is contained in any of the validated domains
for (String validatedDomain : getValidatedDomains()) { for (String validatedDomain : getValidatedDomains()) {
if (domain.equals(validatedDomain)) { if (domain.equals(validatedDomain)) {
return true; return true;
} }
} }
return false; return false;
} }
/** /**
* Returns a collection with all the domains, subdomains and virtual hosts that where * Returns a collection with all the domains, subdomains and virtual hosts that where
* validated. The remote server is allowed to send packets from any of these domains, * validated. The remote server is allowed to send packets from any of these domains,
* subdomains and virtual hosts. * subdomains and virtual hosts.
* *
* @return domains, subdomains and virtual hosts that where validated. * @return domains, subdomains and virtual hosts that where validated.
*/ */
@Override public Collection<String> getValidatedDomains() {
public Collection<String> getValidatedDomains() { return Collections.unmodifiableCollection(validatedDomains);
return Collections.unmodifiableCollection(validatedDomains); }
}
/**
/** * Adds a new validated domain, subdomain or virtual host to the list of
* Adds a new validated domain, subdomain or virtual host to the list of * validated domains for the remote server.
* validated domains for the remote server. *
* * @param domain the new validated domain, subdomain or virtual host to add.
* @param domain the new validated domain, subdomain or virtual host to add. */
*/ public void addValidatedDomain(String domain) {
public void addValidatedDomain(String domain) { if (validatedDomains.add(domain)) {
if (validatedDomains.add(domain)) { // Set the first validated domain as the address of the session
// Set the first validated domain as the address of the session if (validatedDomains.size() < 2) {
if (validatedDomains.size() < 2) { setAddress(new JID(null, domain, null));
setAddress(new JID(null, domain, null)); }
} // Register the new validated domain for this server session in SessionManager
// Register the new validated domain for this server session in SessionManager SessionManager.getInstance().registerIncomingServerSession(domain, this);
SessionManager.getInstance().registerIncomingServerSession(domain, this); }
} }
}
/**
/** * Removes the previously validated domain from the list of validated domains. The remote
* Removes the previously validated domain from the list of validated domains. The remote * server will no longer be able to send packets from the removed domain, subdomain or
* server will no longer be able to send packets from the removed domain, subdomain or * virtual host.
* virtual host. *
* * @param domain the domain, subdomain or virtual host to remove from the list of
* @param domain the domain, subdomain or virtual host to remove from the list of * validated domains.
* validated domains. */
*/ public void removeValidatedDomain(String domain) {
public void removeValidatedDomain(String domain) { validatedDomains.remove(domain);
validatedDomains.remove(domain); // Unregister the validated domain for this server session in SessionManager
// Unregister the validated domain for this server session in SessionManager SessionManager.getInstance().unregisterIncomingServerSession(domain, this);
SessionManager.getInstance().unregisterIncomingServerSession(domain, this); }
}
/**
/** * Returns the domain or subdomain of the local server used by the remote server
* Returns the domain or subdomain of the local server used by the remote server * when validating the session. This information is only used to prevent many
* when validating the session. This information is only used to prevent many * connections from the same remote server to the same domain or subdomain of
* connections from the same remote server to the same domain or subdomain of * the local server.
* the local server. *
* * @return the domain or subdomain of the local server used by the remote server
* @return the domain or subdomain of the local server used by the remote server * when validating the session.
* when validating the session. */
*/ @Override
@Override public String getLocalDomain() {
public String getLocalDomain() { return localDomain;
return localDomain; }
}
/**
/** * Sets the domain or subdomain of the local server used by the remote server when asking
* Sets the domain or subdomain of the local server used by the remote server when asking * to validate the session. This information is only used to prevent many connections from
* to validate the session. This information is only used to prevent many connections from * the same remote server to the same domain or subdomain of the local server.
* the same remote server to the same domain or subdomain of the local server. *
* * @param domain the domain or subdomain of the local server used when validating the
* @param domain the domain or subdomain of the local server used when validating the * session.
* session. */
*/ public void setLocalDomain(String domain) {
public void setLocalDomain(String domain) { localDomain = domain;
localDomain = domain; }
}
/**
/** * Verifies the received key sent by the remote server. This server is trying to generate
* Verifies the received key sent by the remote server. This server is trying to generate * an outgoing connection to the remote server and the remote server is reusing an incoming
* an outgoing connection to the remote server and the remote server is reusing an incoming * connection for validating the key.
* connection for validating the key. *
* * @param doc the received Element that contains the key to verify.
* @param doc the received Element that contains the key to verify. */
*/ public void verifyReceivedKey(Element doc) {
public void verifyReceivedKey(Element doc) { ServerDialback.verifyReceivedKey(doc, getConnection());
ServerDialback.verifyReceivedKey(doc, getConnection()); }
}
@Override
@Override public String getAvailableStreamFeatures() {
public String getAvailableStreamFeatures() { StringBuilder sb = new StringBuilder();
StringBuilder sb = new StringBuilder();
// Include Stream Compression Mechanism
// Include Stream Compression Mechanism if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled &&
if (conn.getCompressionPolicy() != Connection.CompressionPolicy.disabled && !conn.isCompressed()) {
!conn.isCompressed()) { sb.append("<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>");
sb.append("<compression xmlns=\"http://jabber.org/features/compress\"><method>zlib</method></compression>"); }
}
// Offer server dialback if using self-signed certificates and no authentication has been done yet
// Offer server dialback if using self-signed certificates and no authentication has been done yet boolean usingSelfSigned;
boolean usingSelfSigned; final Certificate[] chain = conn.getLocalCertificates();
final Certificate[] chain = conn.getLocalCertificates(); if (chain == null || chain.length == 0) {
if (chain == null || chain.length == 0) { usingSelfSigned = true;
usingSelfSigned = true; } else {
} else { try {
try { final KeyStore keyStore = SSLConfig.getIdentityStore( Purpose.SOCKET_S2S );
final KeyStore keyStore = SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ); usingSelfSigned = CertificateManager.isSelfSignedCertificate(keyStore, (X509Certificate) chain[0]);
usingSelfSigned = CertificateManager.isSelfSignedCertificate(keyStore, (X509Certificate) chain[0]); } catch (KeyStoreException ex) {
} catch (KeyStoreException ex) { Log.warn("Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.", ex);
Log.warn("Exception occurred while trying to determine whether local certificate is self-signed. Proceeding as if it is.", ex); usingSelfSigned = true;
usingSelfSigned = true; }
} }
}
if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) {
if (usingSelfSigned && ServerDialback.isEnabledForSelfSigned() && validatedDomains.isEmpty()) { sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>");
sb.append("<dialback xmlns=\"urn:xmpp:features:dialback\"><errors/></dialback>"); }
}
return sb.toString();
return sb.toString(); }
}
public void tlsAuth() {
public void tlsAuth() { usingServerDialback = false;
usingServerDialback = false; }
} }
}
...@@ -56,14 +56,7 @@ import org.apache.mina.integration.jmx.IoServiceMBean; ...@@ -56,14 +56,7 @@ import org.apache.mina.integration.jmx.IoServiceMBean;
import org.apache.mina.integration.jmx.IoSessionMBean; import org.apache.mina.integration.jmx.IoSessionMBean;
import org.apache.mina.transport.socket.SocketSessionConfig; import org.apache.mina.transport.socket.SocketSessionConfig;
import org.apache.mina.transport.socket.nio.NioSocketAcceptor; import org.apache.mina.transport.socket.nio.NioSocketAcceptor;
import org.jivesoftware.openfire.ConnectionManager; import org.jivesoftware.openfire.*;
import org.jivesoftware.openfire.JMXManager;
import org.jivesoftware.openfire.PacketDeliverer;
import org.jivesoftware.openfire.PacketRouter;
import org.jivesoftware.openfire.RoutingTable;
import org.jivesoftware.openfire.ServerPort;
import org.jivesoftware.openfire.SessionManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.container.BasicModule; import org.jivesoftware.openfire.container.BasicModule;
import org.jivesoftware.openfire.container.PluginManager; import org.jivesoftware.openfire.container.PluginManager;
import org.jivesoftware.openfire.container.PluginManagerListener; import org.jivesoftware.openfire.container.PluginManagerListener;
...@@ -451,6 +444,7 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana ...@@ -451,6 +444,7 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
Log.debug("Throttling read buffer for connections from sslSocketAcceptor={} to max={} bytes", Log.debug("Throttling read buffer for connections from sslSocketAcceptor={} to max={} bytes",
sslSocketAcceptor, maxBufferSize); sslSocketAcceptor, maxBufferSize);
// Add the SSL filter now since sockets are "borned" encrypted in the old ssl method // Add the SSL filter now since sockets are "borned" encrypted in the old ssl method
Connection.ClientAuth clientAuth; Connection.ClientAuth clientAuth;
try { try {
...@@ -459,9 +453,8 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana ...@@ -459,9 +453,8 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
clientAuth = Connection.ClientAuth.disabled; clientAuth = Connection.ClientAuth.disabled;
} }
final SslFilter sslFilter = SSLConfig.getServerModeSslFilter( SSLConfig.Type.SOCKET_C2S, clientAuth ); final SslFilter sslFilter = SSLConfig.getServerModeSslFilter( Purpose.SOCKET_C2S, clientAuth );
sslSocketAcceptor.getFilterChain().addAfter(EXECUTOR_FILTER_NAME, TLS_FILTER_NAME, sslFilter); sslSocketAcceptor.getFilterChain().addAfter(EXECUTOR_FILTER_NAME, TLS_FILTER_NAME, sslFilter);
} }
catch (Exception e) { catch (Exception e) {
System.err.println("Error starting SSL XMPP listener on port " + port + ": " + System.err.println("Error starting SSL XMPP listener on port " + port + ": " +
...@@ -615,7 +608,7 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana ...@@ -615,7 +608,7 @@ public class ConnectionManagerImpl extends BasicModule implements ConnectionMana
@Override @Override
public boolean isClientSSLListenerEnabled() { public boolean isClientSSLListenerEnabled() {
try { try {
return JiveGlobals.getBooleanProperty(ConnectionSettings.Client.ENABLE_OLD_SSLPORT, false) && SSLConfig.getStore( Purpose.SOCKETBASED_IDENTITYSTORE ).size() > 0; return JiveGlobals.getBooleanProperty(ConnectionSettings.Client.ENABLE_OLD_SSLPORT, false) && SSLConfig.getIdentityStore( Purpose.SOCKET_C2S ).size() > 0;
} catch (KeyStoreException e) { } catch (KeyStoreException e) {
return false; return false;
} }
......
...@@ -31,11 +31,6 @@ ...@@ -31,11 +31,6 @@
storePurpose = null; storePurpose = null;
} }
if (! storePurpose.isIdentityStore() ) {
errors.put( "storePurpose", "shoud be an identity store (not a trust store)");
storePurpose = null;
}
pageContext.setAttribute( "storePurpose", storePurpose ); pageContext.setAttribute( "storePurpose", storePurpose );
if (save) { if (save) {
...@@ -47,7 +42,7 @@ ...@@ -47,7 +42,7 @@
} }
if (errors.isEmpty()) { if (errors.isEmpty()) {
try { try {
final IdentityStoreConfig identityStoreConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose ); final IdentityStoreConfig identityStoreConfig = SSLConfig.getInstance().getIdentityStoreConfig( storePurpose );
// Create an alias for the signed certificate // Create an alias for the signed certificate
String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain(); String domain = XMPPServer.getInstance().getServerInfo().getXMPPDomain();
...@@ -62,7 +57,7 @@ ...@@ -62,7 +57,7 @@
identityStoreConfig.installCertificate( alias, privateKey, passPhrase, certificate ); identityStoreConfig.installCertificate( alias, privateKey, passPhrase, certificate );
// Log the event // Log the event
webManager.logEvent("imported SSL certificate in "+ storePurposeText, "alias = "+alias); webManager.logEvent("imported SSL certificate in identity store "+ storePurposeText, "alias = "+alias);
response.sendRedirect("security-keystore.jsp?storePurpose="+storePurposeText); response.sendRedirect("security-keystore.jsp?storePurpose="+storePurposeText);
return; return;
...@@ -77,8 +72,8 @@ ...@@ -77,8 +72,8 @@
<html> <html>
<head> <head>
<title><fmt:message key="ssl.import.certificate.keystore.${connectivityType}.title"/></title> <title><fmt:message key="ssl.import.certificate.keystore.${storePurpose}.title"/></title>
<meta name="pageID" content="security-keystore-${connectivityType}"/> <meta name="pageID" content="security-keystore-${storePurpose}"/>
</head> </head>
<body> <body>
...@@ -120,7 +115,7 @@ ...@@ -120,7 +115,7 @@
<!-- BEGIN 'Import Private Key and Certificate' --> <!-- BEGIN 'Import Private Key and Certificate' -->
<form action="import-keystore-certificate.jsp" method="post" name="f"> <form action="import-keystore-certificate.jsp" method="post" name="f">
<input type="hidden" name="connectivityType" value="${connectivityType}"/> <input type="hidden" name="storePurpose" value="${storePurpose}"/>
<div class="jive-contentBoxHeader"> <div class="jive-contentBoxHeader">
<fmt:message key="ssl.import.certificate.keystore.boxtitle" /> <fmt:message key="ssl.import.certificate.keystore.boxtitle" />
</div> </div>
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
<% final boolean save = ParamUtils.getParameter(request, "save") != null; <% final boolean save = ParamUtils.getParameter(request, "save") != null;
final String alias = ParamUtils.getParameter(request, "alias"); final String alias = ParamUtils.getParameter(request, "alias");
final String certificate = ParamUtils.getParameter(request, "certificate"); final String certificate = ParamUtils.getParameter(request, "certificate");
final String storePurposeText = ParamUtils.getParameter(request, "storePurpose"); final String storePurposeText = ParamUtils.getParameter(request, "storePurpose");
final Map<String, String> errors = new HashMap<String, String>(); final Map<String, String> errors = new HashMap<String, String>();
...@@ -29,16 +29,11 @@ ...@@ -29,16 +29,11 @@
storePurpose = null; storePurpose = null;
} }
if (! storePurpose.isTrustStore() ) {
errors.put( "storePurpose", "shoud be a trust store (not an identity store)");
storePurpose = null;
}
pageContext.setAttribute( "storePurpose", storePurpose ); pageContext.setAttribute( "storePurpose", storePurpose );
if (save && errors.isEmpty()) if (save && errors.isEmpty())
{ {
final TrustStoreConfig trustStoreConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose ); final TrustStoreConfig trustStoreConfig = SSLConfig.getInstance().getTrustStoreConfig( storePurpose );
if (alias == null || "".equals(alias)) if (alias == null || "".equals(alias))
{ {
...@@ -62,7 +57,7 @@ ...@@ -62,7 +57,7 @@
trustStoreConfig.installCertificate( alias, certificate ); trustStoreConfig.installCertificate( alias, certificate );
// Log the event // Log the event
webManager.logEvent("imported SSL certificate in "+ storePurposeText, "alias = "+alias); webManager.logEvent("imported SSL certificate in trust store "+ storePurposeText, "alias = "+alias);
response.sendRedirect( "security-truststore.jsp?storePurpose=" + storePurposeText + "&importsuccess=true" ); response.sendRedirect( "security-truststore.jsp?storePurpose=" + storePurposeText + "&importsuccess=true" );
return; return;
...@@ -79,9 +74,9 @@ ...@@ -79,9 +74,9 @@
<html> <html>
<head> <head>
<title> <title>
<fmt:message key="ssl.import.certificate.keystore.${connectivityType}.title"/> - <fmt:message key="ssl.certificates.truststore.${param.type}-title"/> <fmt:message key="ssl.import.certificate.keystore.${storePurpose}.title"/> - <fmt:message key="ssl.certificates.truststore.${param.type}-title"/>
</title> </title>
<meta name="pageID" content="security-truststore-${connectivityType}-${param.type}"/> <meta name="pageID" content="security-truststore-${storePurpose}-${param.type}"/>
</head> </head>
<body> <body>
...@@ -129,7 +124,7 @@ ...@@ -129,7 +124,7 @@
<!-- BEGIN 'Import Certificate' --> <!-- BEGIN 'Import Certificate' -->
<form action="import-truststore-certificate.jsp?type=${param.type}" method="post" name="f"> <form action="import-truststore-certificate.jsp?type=${param.type}" method="post" name="f">
<input type="hidden" name="connectivityType" value="${connectivityType}"/> <input type="hidden" name="connectivityType" value="${storePurpose}"/>
<div class="jive-contentBoxHeader"> <div class="jive-contentBoxHeader">
<fmt:message key="ssl.import.certificate.keystore.boxtitle"/> <fmt:message key="ssl.import.certificate.keystore.boxtitle"/>
</div> </div>
......
...@@ -253,7 +253,7 @@ ...@@ -253,7 +253,7 @@
<fmt:message key="index.server_name" /> <fmt:message key="index.server_name" />
</td> </td>
<td class="c2"> <td class="c2">
<% final IdentityStoreConfig storeConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( Purpose.SOCKETBASED_IDENTITYSTORE ); %> <% final IdentityStoreConfig storeConfig = SSLConfig.getInstance().getIdentityStoreConfig( Purpose.SOCKET_C2S ); %>
<% try { %> <% try { %>
<% if (!storeConfig.containsDomainCertificate( "RSA" )) {%> <% if (!storeConfig.containsDomainCertificate( "RSA" )) {%>
<img src="images/warning-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="index.certificate-warning" />" title="<fmt:message key="index.certificate-warning" />">&nbsp; <img src="images/warning-16x16.gif" width="16" height="16" border="0" alt="<fmt:message key="index.certificate-warning" />" title="<fmt:message key="index.certificate-warning" />">&nbsp;
......
...@@ -9,6 +9,7 @@ ...@@ -9,6 +9,7 @@
<%@ page import="java.security.AlgorithmParameters" %> <%@ page import="java.security.AlgorithmParameters" %>
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %> <%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.CertificateStoreConfig" %> <%@ page import="org.jivesoftware.openfire.keystore.CertificateStoreConfig" %>
<%@ page import="java.security.KeyStore" %>
<%@ taglib uri="admin" prefix="admin" %> <%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
...@@ -21,6 +22,7 @@ ...@@ -21,6 +22,7 @@
final String alias = ParamUtils.getParameter( request, "alias" ); final String alias = ParamUtils.getParameter( request, "alias" );
final String storePurposeText = ParamUtils.getParameter( request, "storePurpose" ); final String storePurposeText = ParamUtils.getParameter( request, "storePurpose" );
final boolean isTrustStore = ParamUtils.getBooleanParameter( request, "isTrustStore" );
final Map<String, String> errors = new HashMap<String, String>(); final Map<String, String> errors = new HashMap<String, String>();
...@@ -42,10 +44,15 @@ ...@@ -42,10 +44,15 @@
{ {
try try
{ {
final CertificateStoreConfig certificateStoreConfig = SSLConfig.getInstance().getStoreConfig( storePurpose ); final KeyStore store;
if (isTrustStore) {
store = SSLConfig.getTrustStore( storePurpose );
} else {
store = SSLConfig.getIdentityStore( storePurpose );
}
// Get the certificate // Get the certificate
final X509Certificate certificate = (X509Certificate) certificateStoreConfig.getStore().getCertificate( alias ); final X509Certificate certificate = (X509Certificate) store.getCertificate( alias );
if ( certificate == null ) { if ( certificate == null ) {
errors.put( "alias", "alias" ); errors.put( "alias", "alias" );
...@@ -62,7 +69,7 @@ ...@@ -62,7 +69,7 @@
// Handle a "go back" click: // Handle a "go back" click:
if ( request.getParameter( "back" ) != null ) { if ( request.getParameter( "back" ) != null ) {
if ( storePurpose.isTrustStore() ) { if ( isTrustStore ) {
response.sendRedirect( "security-truststore.jsp?storePurpose=" + storePurpose ); response.sendRedirect( "security-truststore.jsp?storePurpose=" + storePurpose );
} else { } else {
response.sendRedirect( "security-keystore.jsp?storePurpose=" + storePurpose ); response.sendRedirect( "security-keystore.jsp?storePurpose=" + storePurpose );
...@@ -77,11 +84,11 @@ ...@@ -77,11 +84,11 @@
<head> <head>
<title><fmt:message key="ssl.certificate.details.title"/></title> <title><fmt:message key="ssl.certificate.details.title"/></title>
<c:choose> <c:choose>
<c:when test="${storePurpose.identityStore}"> <c:when test="${isTrustStore}">
<meta name="pageID" content="security-keystore"/> <meta name="pageID" content="security-truststore"/>
</c:when> </c:when>
<c:otherwise> <c:otherwise>
<meta name="pageID" content="security-truststore"/> <meta name="pageID" content="security-keystore"/>
</c:otherwise> </c:otherwise>
</c:choose> </c:choose>
</head> </head>
......
...@@ -16,41 +16,12 @@ ...@@ -16,41 +16,12 @@
// Read parameters // Read parameters
final boolean save = request.getParameter("save") != null; final boolean save = request.getParameter("save") != null;
final String paramLocKeySocket = ParamUtils.getParameter(request, "loc-key-socket");
final String paramLocTrustSocketS2S = ParamUtils.getParameter(request, "loc-trust-socket-s2s");
final String paramLocTrustSocketC2S = ParamUtils.getParameter(request, "loc-trust-socket-c2s");
final String paramLocKeyBosh = ParamUtils.getParameter(request, "loc-key-bosh");
final String paramLocTrustBoshC2S = ParamUtils.getParameter(request, "loc-trust-bosh-c2s");
final String paramLocKeyWebadmin = ParamUtils.getParameter(request, "loc-key-webadmin");
final String paramLocTrustWebadmin = ParamUtils.getParameter(request, "loc-trust-webadmin");
final String paramLocKeyAdministrative = ParamUtils.getParameter( request, "loc-key-administrative" );
final String paramLocTrustAdministrative = ParamUtils.getParameter( request, "loc-trust-administrative" );
// TODO actually save something! // TODO actually save something!
// Pre-update property values // Pre-update property values
final String locKeySocket = SSLConfig.getNonCanonicalizedLocation( Purpose.SOCKETBASED_IDENTITYSTORE );
final String locTrustSocketS2S = SSLConfig.getNonCanonicalizedLocation( Purpose.SOCKETBASED_S2S_TRUSTSTORE );
final String locTrustSocketC2S = SSLConfig.getNonCanonicalizedLocation( Purpose.SOCKETBASED_C2S_TRUSTSTORE );
final String locKeyBosh = SSLConfig.getNonCanonicalizedLocation( Purpose.BOSHBASED_IDENTITYSTORE );
final String locTrustBoshC2S = SSLConfig.getNonCanonicalizedLocation( Purpose.BOSHBASED_C2S_TRUSTSTORE );
final String locKeyWebadmin = SSLConfig.getNonCanonicalizedLocation( Purpose.WEBADMIN_IDENTITYSTORE );
final String locTrustWebadmin = SSLConfig.getNonCanonicalizedLocation( Purpose.WEBADMIN_TRUSTSTORE );
final String locKeyAdministrative = SSLConfig.getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_IDENTITYSTORE );
final String locTrustAdministrative = SSLConfig.getNonCanonicalizedLocation( Purpose.ADMINISTRATIVE_TRUSTSTORE );
final Map<String, String> errors = new HashMap<>(); final Map<String, String> errors = new HashMap<>();
pageContext.setAttribute( "errors", errors ); pageContext.setAttribute( "errors", errors );
pageContext.setAttribute( "locKeySocket", locKeySocket );
pageContext.setAttribute( "locTrustSocketS2S",locTrustSocketS2S );
pageContext.setAttribute( "locTrustSocketC2S", locTrustSocketC2S );
pageContext.setAttribute( "locKeyBosh", locKeyBosh );
pageContext.setAttribute( "locTrustBoshC2S", locTrustBoshC2S );
pageContext.setAttribute( "locKeyWebadmin", locKeyWebadmin );
pageContext.setAttribute( "locTrustWebadmin", locTrustWebadmin );
pageContext.setAttribute( "locKeyAdministrative", locKeyAdministrative );
pageContext.setAttribute( "locTrustAdministrative", locTrustAdministrative );
%> %>
<html> <html>
......
...@@ -36,18 +36,10 @@ ...@@ -36,18 +36,10 @@
try try
{ {
storePurpose = Purpose.valueOf( storePurposeText ); storePurpose = Purpose.valueOf( storePurposeText );
storeConfig = SSLConfig.getInstance().getIdentityStoreConfig( storePurpose );
if ( !storePurpose.isIdentityStore() ) if ( storeConfig == null )
{
errors.put( "storePurpose", "should be an identity store (not a trust store)");
}
else
{ {
storeConfig = (IdentityStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose ); errors.put( "storeConfig", "Unable to get an instance." );
if ( storeConfig == null )
{
errors.put( "storeConfig", "Unable to get an instance." );
}
} }
} }
catch (RuntimeException ex) catch (RuntimeException ex)
...@@ -60,7 +52,7 @@ ...@@ -60,7 +52,7 @@
pageContext.setAttribute( "storePurpose", storePurpose ); pageContext.setAttribute( "storePurpose", storePurpose );
pageContext.setAttribute( "storeConfig", storeConfig ); pageContext.setAttribute( "storeConfig", storeConfig );
final Set<Purpose> sameStorePurposes = SSLConfig.getInstance().getOtherPurposesForSameStore( storePurpose ); final Set<Purpose> sameStorePurposes = Collections.EMPTY_SET; // TODO FIXME: SSLConfig.getInstance().getOtherPurposesForSameStore( storePurpose );
pageContext.setAttribute( "sameStorePurposes", sameStorePurposes ); pageContext.setAttribute( "sameStorePurposes", sameStorePurposes );
final Map<String, X509Certificate> certificates = storeConfig.getAllCertificates(); final Map<String, X509Certificate> certificates = storeConfig.getAllCertificates();
......
...@@ -7,6 +7,7 @@ ...@@ -7,6 +7,7 @@
<%@ page import="org.jivesoftware.openfire.keystore.Purpose" %> <%@ page import="org.jivesoftware.openfire.keystore.Purpose" %>
<%@ page import="org.jivesoftware.openfire.keystore.TrustStoreConfig" %> <%@ page import="org.jivesoftware.openfire.keystore.TrustStoreConfig" %>
<%@ page import="java.util.Set" %> <%@ page import="java.util.Set" %>
<%@ page import="java.util.Collections" %>
<%@ taglib uri="admin" prefix="admin" %> <%@ taglib uri="admin" prefix="admin" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/core" prefix="c" %>
<%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %> <%@ taglib uri="http://java.sun.com/jsp/jstl/fmt" prefix="fmt" %>
...@@ -28,18 +29,10 @@ ...@@ -28,18 +29,10 @@
try try
{ {
storePurpose = Purpose.valueOf( storePurposeText ); storePurpose = Purpose.valueOf( storePurposeText );
storeConfig = SSLConfig.getInstance().getTrustStoreConfig( storePurpose );
if ( !storePurpose.isTrustStore() ) if ( storeConfig == null )
{
errors.put( "storePurpose", "should be a trust store (not an identity store)");
}
else
{ {
storeConfig = (TrustStoreConfig) SSLConfig.getInstance().getStoreConfig( storePurpose ); errors.put( "storeConfig", "Unable to get an instance." );
if ( storeConfig == null )
{
errors.put( "storeConfig", "Unable to get an instance." );
}
} }
} }
catch (RuntimeException ex) catch (RuntimeException ex)
...@@ -52,7 +45,7 @@ ...@@ -52,7 +45,7 @@
pageContext.setAttribute( "storePurpose", storePurpose ); pageContext.setAttribute( "storePurpose", storePurpose );
pageContext.setAttribute( "storeConfig", storeConfig ); pageContext.setAttribute( "storeConfig", storeConfig );
final Set<Purpose> sameStorePurposes = SSLConfig.getInstance().getOtherPurposesForSameStore( storePurpose ); final Set<Purpose> sameStorePurposes = Collections.EMPTY_SET; // TODO FIXME: SSLConfig.getInstance().getOtherPurposesForSameStore( storePurpose );
pageContext.setAttribute( "sameStorePurposes", sameStorePurposes ); pageContext.setAttribute( "sameStorePurposes", sameStorePurposes );
if ( delete ) if ( delete )
......
...@@ -38,10 +38,6 @@ ...@@ -38,10 +38,6 @@
storePurpose = null; storePurpose = null;
} }
if (! storePurpose.isIdentityStore() ) {
errors.put( "storePurpose", "shoud be an identity store (not a trust store)");
storePurpose = null;
}
pageContext.setAttribute( "storePurpose", storePurpose ); pageContext.setAttribute( "storePurpose", storePurpose );
// if (save) { // if (save) {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment