All Downloads are FREE. Search and download functionalities are using the official Maven repository.

org.jivesoftware.openfire.container.AdminConsolePlugin Maven / Gradle / Ivy

The newest version!
/*
 * Copyright (C) 2004-2008 Jive Software. All rights reserved.
 *
 * Licensed under the Apache License, Version 2.0 (the "License");
 * you may not use this file except in compliance with the License.
 * You may obtain a copy of the License at
 *
 *     http://www.apache.org/licenses/LICENSE-2.0
 *
 * Unless required by applicable law or agreed to in writing, software
 * distributed under the License is distributed on an "AS IS" BASIS,
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
 * See the License for the specific language governing permissions and
 * limitations under the License.
 */

package org.jivesoftware.openfire.container;

import java.io.File;
import java.security.KeyStore;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.List;

import org.apache.jasper.servlet.JasperInitializer;
import org.apache.tomcat.InstanceManager;
import org.apache.tomcat.SimpleInstanceManager;
import org.eclipse.jetty.annotations.ServletContainerInitializersStarter;
import org.eclipse.jetty.plus.annotation.ContainerInitializer;
import org.eclipse.jetty.server.Connector;
import org.eclipse.jetty.server.Handler;
import org.eclipse.jetty.server.HttpConfiguration;
import org.eclipse.jetty.server.HttpConnectionFactory;
import org.eclipse.jetty.server.SecureRequestCustomizer;
import org.eclipse.jetty.server.Server;
import org.eclipse.jetty.server.ServerConnector;
import org.eclipse.jetty.server.SslConnectionFactory;
import org.eclipse.jetty.server.handler.ContextHandlerCollection;
import org.eclipse.jetty.server.handler.DefaultHandler;
import org.eclipse.jetty.server.handler.HandlerCollection;
import org.eclipse.jetty.spdy.server.http.HTTPSPDYServerConnector;
import org.eclipse.jetty.util.ssl.SslContextFactory;
import org.eclipse.jetty.util.thread.QueuedThreadPool;
import org.eclipse.jetty.webapp.WebAppContext;
import org.jivesoftware.openfire.JMXManager;
import org.jivesoftware.openfire.XMPPServer;
import org.jivesoftware.openfire.keystore.CertificateStore;
import org.jivesoftware.openfire.keystore.IdentityStore;
import org.jivesoftware.openfire.spi.ConnectionConfiguration;
import org.jivesoftware.openfire.spi.ConnectionManagerImpl;
import org.jivesoftware.openfire.spi.ConnectionType;
import org.jivesoftware.openfire.spi.EncryptionArtifactFactory;
import org.jivesoftware.util.CertificateEventListener;
import org.jivesoftware.util.CertificateManager;
import org.jivesoftware.util.JiveGlobals;
import org.jivesoftware.util.LocaleUtils;
import org.jivesoftware.util.StringUtils;
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.
 *
 * @author Matt Tucker
 */
public class AdminConsolePlugin implements Plugin {

    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.
     */
    public final static String secret = StringUtils.randomString(64);

    private int adminPort;
    private int adminSecurePort;
    private Server adminServer;
    private ContextHandlerCollection contexts;
    private CertificateEventListener certificateListener;
    private boolean restartNeeded = false;
    private boolean sslEnabled = false;

    private File pluginDir;

    /**
     * Create a Jetty module.
     */
    public AdminConsolePlugin() {
        contexts = new ContextHandlerCollection();
    }

    /**
     * Starts the Jetty instance.
     */
    public void startup() {
        restartNeeded = false;

        // Add listener for certificate events
        certificateListener = new CertificateListener();
        CertificateManager.addListener(certificateListener);

        // 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);

        final QueuedThreadPool tp = new QueuedThreadPool();
        tp.setName("Jetty-QTP-AdminConsole");

        adminServer = new Server(tp);

        if (JMXManager.isEnabled()) {
            JMXManager jmx = JMXManager.getInstance();
            adminServer.addBean(jmx.getContainer());
        }

        // Create connector for http traffic if it's enabled.
        if (adminPort > 0) {
            final HttpConfiguration httpConfig = new HttpConfiguration();

            // 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));

            // Listen on a specific network interface if it has been set.
            String bindInterface = getBindInterface();
            httpConnector.setHost(bindInterface);
            httpConnector.setPort(adminPort);
            adminServer.addConnector(httpConnector);
        }

        // Create a connector for https traffic if it's enabled.
        sslEnabled = false;
        try {
            IdentityStore identityStore = null;
            if (XMPPServer.getInstance().getCertificateStoreManager() == null){
                Log.warn( "Admin console: CertifcateStoreManager has not been initialized yet. HTTPS will be unavailable." );
            } else {
                identityStore = XMPPServer.getInstance().getCertificateStoreManager().getIdentityStore( ConnectionType.WEBADMIN );
            }
            if (identityStore != null && adminSecurePort > 0 )
            {
                if ( identityStore.getAllCertificates().isEmpty() )
                {
                    Log.warn( "Admin console: Identity store does not have any certificates. HTTPS will be unavailable." );
                }
                else
                {
                    if ( !identityStore.containsDomainCertificate( "RSA" ) )
                    {
                        Log.warn( "Admin console: Using RSA certificates but they are not valid for the hosted domain" );
                    }

                    final ConnectionManagerImpl connectionManager = ( (ConnectionManagerImpl) XMPPServer.getInstance().getConnectionManager() );
                    final ConnectionConfiguration configuration = connectionManager.getListener( ConnectionType.WEBADMIN, true ).generateConnectionConfiguration();
                    final SslContextFactory sslContextFactory = new EncryptionArtifactFactory( configuration ).getSslContextFactory();

                    final ServerConnector httpsConnector;
                    if ( "npn".equals( JiveGlobals.getXMLProperty( "spdy.protocol", "" ) ) )
                    {
                        httpsConnector = new HTTPSPDYServerConnector( adminServer, sslContextFactory );
                    }
                    else
                    {
                        final HttpConfiguration httpsConfig = new HttpConfiguration();
                        httpsConfig.setSendServerVersion( false );
                        httpsConfig.setSecureScheme( "https" );
                        httpsConfig.setSecurePort( adminSecurePort );
                        httpsConfig.addCustomizer( new SecureRequestCustomizer() );

                        final HttpConnectionFactory httpConnectionFactory = new HttpConnectionFactory( httpsConfig );
                        final 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 );
                    }
                    final String bindInterface = getBindInterface();
                    httpsConnector.setHost(bindInterface);
                    httpsConnector.setPort(adminSecurePort);
                    adminServer.addConnector(httpsConnector);

                    sslEnabled = true;
                }
            }
        }
        catch ( Exception e )
        {
            Log.error( "An exception occurred while trying to make available the admin console via HTTPS.", e );
        }

        // Make sure that at least one connector was registered.
        if (adminServer.getConnectors() == null || adminServer.getConnectors().length == 0) {
            adminServer = null;
            // Log warning.
            log(LocaleUtils.getLocalizedString("admin.console.warning"));
            return;
        }

        HandlerCollection collection = new HandlerCollection();
        adminServer.setHandler(collection);
        collection.setHandlers(new Handler[] { contexts, new DefaultHandler() });

        try {
            adminServer.start();

            // Log the ports that the admin server is listening on.
            logAdminConsolePorts();
        }
        catch (Exception e) {
            Log.error("Could not start admin console server", e);
        }
    }

    /**
     * Shuts down the Jetty server.
     * */
    public void shutdown() {
        // Remove listener for certificate events
        if (certificateListener != null) {
            CertificateManager.removeListener(certificateListener);
        }
        //noinspection ConstantConditions
        try {
            if (adminServer != null && adminServer.isRunning()) {
                adminServer.stop();
            }
        }
        catch (Exception e) {
            Log.error("Error stopping admin console server", e);
        }
        adminServer = null;
    }

    @Override
    public void initializePlugin(PluginManager manager, File pluginDir) {
        this.pluginDir = pluginDir;

        createWebAppContext();

        startup();
    }

    @Override
    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.
     *
     * @return true if the Jetty server needs to be restarted.
     */
    public boolean isRestartNeeded() {
        return restartNeeded;
    }

    /**
     * Returns null 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.
     */
    public String getBindInterface() {
        String adminInterfaceName = JiveGlobals.getXMLProperty("adminConsole.interface");
        String globalInterfaceName = JiveGlobals.getXMLProperty("network.interface");
        String bindInterface = null;
        if (adminInterfaceName != null && adminInterfaceName.trim().length() > 0) {
            bindInterface = adminInterfaceName;
        }
        else if (globalInterfaceName != null && globalInterfaceName.trim().length() > 0) {
            bindInterface = globalInterfaceName;
         }
        return bindInterface;
    }

    /**
     * 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.
     */
    public int getAdminUnsecurePort() {
        return adminPort;
    }

    /**
     * Returns 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) {
            return 0;
        }
        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
     * other web applications that should be run as part of the admin console server
     * process. The following pseudo code demonstrates how to do this:
     *
     * 
     *   ContextHandlerCollection contexts = ((AdminConsolePlugin)pluginManager.getPlugin("admin")).getContexts();
     *   context = new WebAppContext(SOME_DIRECTORY, "/CONTEXT_NAME");
     *   contexts.addHandler(context);
     *   context.setWelcomeFiles(new String[]{"index.jsp"});
     *   context.start();
     * 
* * @return the Jetty handlers. */ public ContextHandlerCollection getContexts() { return contexts; } public void restart() { try { adminServer.stop(); adminServer.start(); } catch (Exception e) { Log.error("An exception occurred while restarting the admin console:", e); } } private void createWebAppContext() { WebAppContext context; // Add web-app. Check to see if we're in development mode. If so, we don't // add the normal web-app location, but the web-app in the project directory. boolean developmentMode = Boolean.getBoolean("developmentMode"); if( developmentMode ) { System.out.println(LocaleUtils.getLocalizedString("admin.console.devmode")); 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", "/"); } // Ensure the JSP engine is initialized correctly (in order to be able to cope with Tomcat/Jasper precompiled JSPs). final List initializers = new ArrayList<>(); initializers.add(new ContainerInitializer(new JasperInitializer(), null)); context.setAttribute("org.eclipse.jetty.containerInitializers", initializers); context.setAttribute(InstanceManager.class.getName(), new SimpleInstanceManager()); // The index.html includes a redirect to the index.jsp and doesn't bypass // the context security when in development mode context.setWelcomeFiles(new String[]{"index.html"}); // Make sure the context initialization is done when in development mode if( developmentMode ) { context.addBean( new ServletContainerInitializersStarter( context ), true ); } } private void log(String string) { Log.info(string); System.out.println(string); } private void logAdminConsolePorts() { // Log what ports the admin console is running on. String listening = LocaleUtils.getLocalizedString("admin.console.listening"); String hostname = getBindInterface() == null ? XMPPServer.getInstance().getServerInfo().getXMPPDomain() : getBindInterface(); boolean isPlainStarted = false; boolean isSecureStarted = false; boolean isSPDY = false; for (Connector connector : adminServer.getConnectors()) { if (((ServerConnector) connector).getPort() == adminPort) { isPlainStarted = true; } else if (((ServerConnector) connector).getPort() == adminSecurePort) { isSecureStarted = true; } if (connector instanceof HTTPSPDYServerConnector) { isSPDY = true; } } if (isPlainStarted && isSecureStarted) { log(listening + ":" + System.getProperty("line.separator") + " http://" + hostname + ":" + adminPort + System.getProperty("line.separator") + " https://" + hostname + ":" + adminSecurePort + (isSPDY ? " (SPDY)" : "")); } else if (isSecureStarted) { log(listening + " https://" + hostname + ":" + adminSecurePort + (isSPDY ? " (SPDY)" : "")); } else if (isPlainStarted) { log(listening + " http://" + hostname + ":" + adminPort); } } /** * Listens for security certificates being created and destroyed so we can track when the * admin console needs to be restarted. */ private class CertificateListener implements CertificateEventListener { @Override public void storeContentChanged( CertificateStore store ) { restartNeeded = true; } } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy