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

org.apache.catalina.manager.ManagerServlet Maven / Gradle / Ivy

There is a newer version: 11.0.0-M20
Show newest version
/*
 * Licensed to the Apache Software Foundation (ASF) under one or more
 * contributor license agreements.  See the NOTICE file distributed with
 * this work for additional information regarding copyright ownership.
 * The ASF licenses this file to You 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.apache.catalina.manager;

import java.io.File;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.OutputStream;
import java.io.PrintWriter;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Enumeration;
import java.util.HashMap;
import java.util.LinkedHashSet;
import java.util.List;
import java.util.Locale;
import java.util.Map;
import java.util.Set;

import javax.management.MBeanServer;
import javax.management.MalformedObjectNameException;
import javax.management.ObjectName;
import javax.naming.Binding;
import javax.naming.NamingEnumeration;

import jakarta.servlet.ServletContext;
import jakarta.servlet.ServletException;
import jakarta.servlet.ServletInputStream;
import jakarta.servlet.UnavailableException;
import jakarta.servlet.http.HttpServlet;
import jakarta.servlet.http.HttpServletRequest;
import jakarta.servlet.http.HttpServletResponse;

import org.apache.catalina.Container;
import org.apache.catalina.ContainerServlet;
import org.apache.catalina.Context;
import org.apache.catalina.Engine;
import org.apache.catalina.Host;
import org.apache.catalina.Manager;
import org.apache.catalina.Server;
import org.apache.catalina.Service;
import org.apache.catalina.Session;
import org.apache.catalina.Wrapper;
import org.apache.catalina.connector.Connector;
import org.apache.catalina.core.StandardHost;
import org.apache.catalina.startup.ExpandWar;
import org.apache.catalina.util.ContextName;
import org.apache.catalina.util.IOTools;
import org.apache.catalina.util.ServerInfo;
import org.apache.coyote.ProtocolHandler;
import org.apache.coyote.http11.AbstractHttp11Protocol;
import org.apache.tomcat.util.Diagnostics;
import org.apache.tomcat.util.ExceptionUtils;
import org.apache.tomcat.util.IntrospectionUtils;
import org.apache.tomcat.util.buf.StringUtils;
import org.apache.tomcat.util.modeler.Registry;
import org.apache.tomcat.util.net.SSLContext;
import org.apache.tomcat.util.net.SSLHostConfig;
import org.apache.tomcat.util.net.SSLHostConfigCertificate;
import org.apache.tomcat.util.net.SSLUtilBase;
import org.apache.tomcat.util.res.StringManager;
import org.apache.tomcat.util.security.Escape;


/**
 * Servlet that enables remote management of the web applications installed within the same virtual host as this web
 * application is. Normally, this functionality will be protected by a security constraint in the web application
 * deployment descriptor. However, this requirement can be relaxed during testing.
 * 

* This servlet examines the value returned by getPathInfo() and related query parameters to determine what * action is being requested. The following actions and parameters (starting after the servlet path) are supported: *

    *
  • /deploy?config={config-url} - Install and start a new web application, based on the contents of the * context configuration file found at the specified URL. The docBase attribute of the context * configuration file is used to locate the actual WAR or directory containing the application.
  • *
  • /deploy?config={config-url}&war={war-url}/ - Install and start a new web application, based on the * contents of the context configuration file found at {config-url}, overriding the docBase * attribute with the contents of the web application archive found at {war-url}.
  • *
  • /deploy?path=/xxx&war={war-url} - Install and start a new web application attached to context path * /xxx, based on the contents of the web application archive found at the specified URL.
  • *
  • /list - List the context paths of all currently installed web applications for this virtual host. Each * context will be listed with the following format path:status:sessions. Where path is the context path. * Status is either running or stopped. Sessions is the number of active Sessions.
  • *
  • /reload?path=/xxx - Reload the Java classes and resources for the application at the specified path.
  • *
  • /resources?type=xxxx - Enumerate the available global JNDI resources, optionally limited to those of the * specified type (fully qualified Java class name), if available.
  • *
  • /serverinfo - Display system OS and JVM properties. *
  • /sessions - Deprecated. Use expire. *
  • /expire?path=/xxx - List session idle time information about the web application attached to context path * /xxx for this virtual host.
  • *
  • /expire?path=/xxx&idle=mm - Expire sessions for the context path /xxx which were idle for * at least mm minutes.
  • *
  • /sslConnectorCiphers - Display diagnostic info on SSL/TLS ciphers that are currently configured for each * connector. *
  • /start?path=/xxx - Start the web application attached to context path /xxx for this virtual * host.
  • *
  • /stop?path=/xxx - Stop the web application attached to context path /xxx for this virtual * host.
  • *
  • /threaddump - Write a JVM thread dump.
  • *
  • /undeploy?path=/xxx - Shutdown and remove the web application attached to context path /xxx * for this virtual host, and remove the underlying WAR file or document base directory. (NOTE - This is only * allowed if the WAR file or document base is stored in the appBase directory of this host, typically as a * result of being placed there via the /deploy command.
  • *
  • /vminfo - Write some VM info.
  • *
  • /save - Save the current server configuration to server.xml
  • *
  • /save?path=/xxx - Save the context configuration for the web application deployed with path * /xxx to an appropriately named context.xml file in the xmlBase for the associated * Host.
  • *
*

* Use path=/ for the ROOT context. *

*

* The syntax of the URL for a web application archive must conform to one of the following patterns to be successfully * deployed: *

*
    *
  • file:/absolute/path/to/a/directory - You can specify the absolute path of a directory that contains the * unpacked version of a web application. This directory will be attached to the context path you specify without any * changes.
  • *
*

* NOTE - Attempting to reload or remove the application containing this servlet itself will not succeed. * Therefore, this servlet should generally be deployed as a separate web application within the virtual host to be * managed. *

* The following servlet initialization parameters are recognized: *

    *
  • debug - The debugging detail level that controls the amount of information that is logged by this servlet. * Default is zero. *
* * @author Craig R. McClanahan * @author Remy Maucherat */ public class ManagerServlet extends HttpServlet implements ContainerServlet { private static final long serialVersionUID = 1L; // ----------------------------------------------------- Instance Variables /** * Path where context descriptors should be deployed. */ protected File configBase = null; /** * The Context container associated with our web application. */ protected transient Context context = null; /** * The debugging detail level for this servlet. */ protected int debug = 1; /** * Path used to store revisions of webapps. */ protected File versioned = null; /** * The associated host. */ protected transient Host host = null; /** * MBean server. */ protected transient MBeanServer mBeanServer = null; /** * The associated deployer ObjectName. */ protected ObjectName oname = null; /** * The global JNDI NamingContext for this server, if available. */ protected transient javax.naming.Context global = null; /** * The string manager for this package. */ protected static final StringManager sm = StringManager.getManager(Constants.Package); /** * The Wrapper container associated with this servlet. */ protected transient Wrapper wrapper = null; // ----------------------------------------------- ContainerServlet Methods /** * Return the Wrapper with which we are associated. */ @Override public Wrapper getWrapper() { return this.wrapper; } /** * Set the Wrapper with which we are associated. * * @param wrapper The new wrapper */ @Override public void setWrapper(Wrapper wrapper) { this.wrapper = wrapper; if (wrapper == null) { context = null; host = null; oname = null; } else { context = (Context) wrapper.getParent(); host = (Host) context.getParent(); Engine engine = (Engine) host.getParent(); String name = engine.getName() + ":type=Deployer,host=" + host.getName(); try { oname = new ObjectName(name); } catch (Exception e) { log(sm.getString("managerServlet.objectNameFail", name), e); } } // Retrieve the MBean server mBeanServer = Registry.getRegistry(null, null).getMBeanServer(); } // --------------------------------------------------------- Public Methods /** * Finalize this servlet. */ @Override public void destroy() { // No actions necessary } /** * Process a GET request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs */ @Override public void doGet(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { StringManager smClient = StringManager.getManager(Constants.Package, request.getLocales()); // Identify the request parameters that we need String command = request.getPathInfo(); if (command == null) { command = request.getServletPath(); } String path = request.getParameter("path"); String war = request.getParameter("war"); String config = request.getParameter("config"); ContextName cn = null; if (path != null) { cn = new ContextName(path, request.getParameter("version")); } else if (config != null) { cn = ContextName.extractFromPath(config); } else if (war != null) { cn = ContextName.extractFromPath(war); } String type = request.getParameter("type"); String tag = request.getParameter("tag"); boolean update = false; if (request.getParameter("update") != null && request.getParameter("update").equals("true")) { update = true; } String tlsHostName = request.getParameter("tlsHostName"); boolean statusLine = false; if ("true".equals(request.getParameter("statusLine"))) { statusLine = true; } // Prepare our output writer to generate the response message response.setContentType("text/plain; charset=" + Constants.CHARSET); // Stop older versions of IE thinking they know best. We set text/plain // in the line above for a reason. IE's behaviour is unwanted at best // and dangerous at worst. response.setHeader("X-Content-Type-Options", "nosniff"); PrintWriter writer = response.getWriter(); // Process the requested command if (command == null) { writer.println(smClient.getString("managerServlet.noCommand")); } else if (command.equals("/deploy")) { if (war != null || config != null) { deploy(writer, config, cn, war, update, smClient); } else if (tag != null) { deploy(writer, cn, tag, smClient); } else { writer.println(smClient.getString("managerServlet.invalidCommand", command)); } } else if (command.equals("/list")) { list(writer, smClient); } else if (command.equals("/reload")) { reload(writer, cn, smClient); } else if (command.equals("/resources")) { resources(writer, type, smClient); } else if (command.equals("/save")) { save(writer, path, smClient); } else if (command.equals("/serverinfo")) { serverinfo(writer, smClient); } else if (command.equals("/sessions")) { expireSessions(writer, cn, request, smClient); } else if (command.equals("/expire")) { expireSessions(writer, cn, request, smClient); } else if (command.equals("/start")) { start(writer, cn, smClient); } else if (command.equals("/stop")) { stop(writer, cn, smClient); } else if (command.equals("/undeploy")) { undeploy(writer, cn, smClient); } else if (command.equals("/findleaks")) { findleaks(statusLine, writer, smClient); } else if (command.equals("/vminfo")) { vmInfo(writer, smClient, request.getLocales()); } else if (command.equals("/threaddump")) { threadDump(writer, smClient, request.getLocales()); } else if (command.equals("/sslConnectorCiphers")) { sslConnectorCiphers(writer, smClient); } else if (command.equals("/sslConnectorCerts")) { sslConnectorCerts(writer, smClient); } else if (command.equals("/sslConnectorTrustedCerts")) { sslConnectorTrustedCerts(writer, smClient); } else if (command.equals("/sslReload")) { sslReload(writer, tlsHostName, smClient); } else { writer.println(smClient.getString("managerServlet.unknownCommand", command)); } // Finish up the response writer.flush(); writer.close(); } /** * Process a PUT request for the specified resource. * * @param request The servlet request we are processing * @param response The servlet response we are creating * * @exception IOException if an input/output error occurs * @exception ServletException if a servlet-specified error occurs */ @Override public void doPut(HttpServletRequest request, HttpServletResponse response) throws IOException, ServletException { StringManager smClient = StringManager.getManager(Constants.Package, request.getLocales()); // Identify the request parameters that we need String command = request.getPathInfo(); if (command == null) { command = request.getServletPath(); } String path = request.getParameter("path"); ContextName cn = null; if (path != null) { cn = new ContextName(path, request.getParameter("version")); } String config = request.getParameter("config"); String tag = request.getParameter("tag"); boolean update = false; if (request.getParameter("update") != null && request.getParameter("update").equals("true")) { update = true; } // Prepare our output writer to generate the response message response.setContentType("text/plain;charset=" + Constants.CHARSET); // Stop older versions of IE thinking they know best. We set text/plain // in the line above for a reason. IE's behaviour is unwanted at best // and dangerous at worst. response.setHeader("X-Content-Type-Options", "nosniff"); PrintWriter writer = response.getWriter(); // Process the requested command if (command == null) { writer.println(smClient.getString("managerServlet.noCommand")); } else if (command.equals("/deploy")) { deploy(writer, config, cn, tag, update, request, smClient); } else { writer.println(smClient.getString("managerServlet.unknownCommand", command)); } // Finish up the response writer.flush(); writer.close(); } /** * Initialize this servlet. */ @Override public void init() throws ServletException { // Ensure that our ContainerServlet properties have been set if (wrapper == null || context == null) { throw new UnavailableException(sm.getString("managerServlet.noWrapper")); } // Set our properties from the initialization parameters String value = null; try { value = getServletConfig().getInitParameter("debug"); debug = Integer.parseInt(value); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } // Acquire global JNDI resources if available Server server = ((Engine) host.getParent()).getService().getServer(); if (server != null) { global = server.getGlobalNamingContext(); } // Calculate the directory into which we will be deploying applications versioned = (File) getServletContext().getAttribute(ServletContext.TEMPDIR); configBase = new File(context.getCatalinaBase(), "conf"); Container container = context; Container host = null; Container engine = null; while (container != null) { if (container instanceof Host) { host = container; } if (container instanceof Engine) { engine = container; } container = container.getParent(); } if (engine != null) { configBase = new File(configBase, engine.getName()); } if (host != null) { configBase = new File(configBase, host.getName()); } // Note: The directory must exist for this to work. // Log debugging messages as necessary if (debug >= 1) { log("init: Associated with Deployer '" + oname + "'"); if (global != null) { log("init: Global resources are available"); } } } // -------------------------------------------------------- Private Methods /** * Find potential memory leaks caused by web application reload. * * @param statusLine Print a status line * @param writer The output writer * @param smClient StringManager for the client's locale */ protected void findleaks(boolean statusLine, PrintWriter writer, StringManager smClient) { if (!(host instanceof StandardHost)) { writer.println(smClient.getString("managerServlet.findleaksFail")); return; } String[] results = ((StandardHost) host).findReloadedContextMemoryLeaks(); if (results.length > 0) { if (statusLine) { writer.println(smClient.getString("managerServlet.findleaksList")); } for (String result : results) { if (result.isEmpty()) { result = "/"; } writer.println(result); } } else if (statusLine) { writer.println(smClient.getString("managerServlet.findleaksNone")); } } protected void sslReload(PrintWriter writer, String tlsHostName, StringManager smClient) { Connector connectors[] = getConnectors(); boolean found = false; for (Connector connector : connectors) { if (Boolean.TRUE.equals(connector.getProperty("SSLEnabled"))) { ProtocolHandler protocol = connector.getProtocolHandler(); if (protocol instanceof AbstractHttp11Protocol) { AbstractHttp11Protocol http11Protoocol = (AbstractHttp11Protocol) protocol; if (tlsHostName == null || tlsHostName.length() == 0) { found = true; http11Protoocol.reloadSslHostConfigs(); } else { SSLHostConfig[] sslHostConfigs = http11Protoocol.findSslHostConfigs(); for (SSLHostConfig sslHostConfig : sslHostConfigs) { // tlsHostName is as provided by the user so use a case insensitive // comparison as host names are case insensitive. if (sslHostConfig.getHostName().equalsIgnoreCase(tlsHostName)) { found = true; http11Protoocol.reloadSslHostConfig(tlsHostName); } } } } } } if (found) { if (tlsHostName == null || tlsHostName.length() == 0) { writer.println(smClient.getString("managerServlet.sslReloadAll")); } else { writer.println(smClient.getString("managerServlet.sslReload", tlsHostName)); } } else { writer.println(smClient.getString("managerServlet.sslReloadFail")); } } /** * Write some VM info. * * @param writer The output writer * @param smClient StringManager for the client's locale * @param requestedLocales the client's locales */ protected void vmInfo(PrintWriter writer, StringManager smClient, Enumeration requestedLocales) { writer.println(smClient.getString("managerServlet.vminfo")); writer.print(Diagnostics.getVMInfo(requestedLocales)); } /** * Write a JVM thread dump. * * @param writer The output writer * @param smClient StringManager for the client's locale * @param requestedLocales the client's locales */ protected void threadDump(PrintWriter writer, StringManager smClient, Enumeration requestedLocales) { writer.println(smClient.getString("managerServlet.threaddump")); writer.print(Diagnostics.getThreadDump(requestedLocales)); } protected void sslConnectorCiphers(PrintWriter writer, StringManager smClient) { writer.println(smClient.getString("managerServlet.sslConnectorCiphers")); Map> connectorCiphers = getConnectorCiphers(smClient); for (Map.Entry> entry : connectorCiphers.entrySet()) { writer.println(entry.getKey()); for (String cipher : entry.getValue()) { writer.print(" "); writer.println(cipher); } } } private void sslConnectorCerts(PrintWriter writer, StringManager smClient) { writer.println(smClient.getString("managerServlet.sslConnectorCerts")); Map> connectorCerts = getConnectorCerts(smClient); for (Map.Entry> entry : connectorCerts.entrySet()) { writer.println(entry.getKey()); for (String cert : entry.getValue()) { writer.println(cert); } } } private void sslConnectorTrustedCerts(PrintWriter writer, StringManager smClient) { writer.println(smClient.getString("managerServlet.sslConnectorTrustedCerts")); Map> connectorTrustedCerts = getConnectorTrustedCerts(smClient); for (Map.Entry> entry : connectorTrustedCerts.entrySet()) { writer.println(entry.getKey()); for (String cert : entry.getValue()) { writer.println(cert); } } } /** * Store server configuration. * * @param writer Destination for any user message(s) during this operation * @param path Optional context path to save * @param smClient i18n support for current client's locale */ protected synchronized void save(PrintWriter writer, String path, StringManager smClient) { ObjectName storeConfigOname; try { // Note: Hard-coded domain used since this object is per Server/JVM storeConfigOname = new ObjectName("Catalina:type=StoreConfig"); } catch (MalformedObjectNameException e) { // Should never happen. The name above is valid. log(sm.getString("managerServlet.exception"), e); writer.println(smClient.getString("managerServlet.exception", e.toString())); return; } if (!mBeanServer.isRegistered(storeConfigOname)) { writer.println(smClient.getString("managerServlet.storeConfig.noMBean", storeConfigOname)); return; } if (path == null || path.length() == 0 || !path.startsWith("/")) { try { mBeanServer.invoke(storeConfigOname, "storeConfig", null, null); writer.println(smClient.getString("managerServlet.saved")); } catch (Exception e) { log(sm.getString("managerServlet.error.storeConfig"), e); writer.println(smClient.getString("managerServlet.exception", e.toString())); } } else { String contextPath = path; if (path.equals("/")) { contextPath = ""; } Context context = (Context) host.findChild(contextPath); if (context == null) { writer.println(smClient.getString("managerServlet.noContext", path)); return; } try { Boolean result = (Boolean) mBeanServer.invoke(storeConfigOname, "store", new Object[] { context }, new String[] { "org.apache.catalina.Context" }); if (result.booleanValue()) { writer.println(smClient.getString("managerServlet.savedContext", path)); } else { writer.println(smClient.getString("managerServlet.savedContextFail", path)); } } catch (Exception e) { log(sm.getString("managerServlet.error.storeContextConfig", path), e); writer.println(smClient.getString("managerServlet.exception", e.toString())); } } } /** * Deploy a web application archive (included in the current request) at the specified context path. * * @param writer Writer to render results to * @param config URL of the context configuration file to be installed * @param cn Name of the application to be installed * @param tag Tag to be associated with the webapp * @param update Flag that indicates that any existing app should be replaced * @param request Servlet request we are processing * @param smClient i18n messages using the locale of the client */ protected void deploy(PrintWriter writer, String config, ContextName cn, String tag, boolean update, HttpServletRequest request, StringManager smClient) { if (config != null && config.length() == 0) { config = null; } if (debug >= 1) { if (config == null) { log("deploy: Deploying web application '" + cn + "'"); } else { log("deploy: Deploying web application '" + cn + "' " + "with context configuration at '" + config + "'"); } } // Validate the requested context path if (!validateContextName(cn, writer, smClient)) { return; } String name = cn.getName(); String baseName = cn.getBaseName(); String displayPath = cn.getDisplayName(); // If app exists deployment can only proceed if update is true // Note existing WAR will be deleted and then replaced Context context = (Context) host.findChild(name); if (context != null && !update) { writer.println(smClient.getString("managerServlet.alreadyContext", displayPath)); return; } if (config != null && config.startsWith("file:")) { config = config.substring("file:".length()); } File deployedWar = new File(host.getAppBaseFile(), baseName + ".war"); // Determine full path for uploaded WAR File uploadedWar; if (tag == null) { if (update) { // Append ".tmp" to the file name so it won't get deployed if auto // deployment is enabled. It also means the old war won't get // deleted if the upload fails uploadedWar = new File(deployedWar.getAbsolutePath() + ".tmp"); if (uploadedWar.exists() && !uploadedWar.delete()) { writer.println(smClient.getString("managerServlet.deleteFail", uploadedWar)); } } else { uploadedWar = deployedWar; } } else { File uploadPath = new File(versioned, tag); if (!uploadPath.mkdirs() && !uploadPath.isDirectory()) { writer.println(smClient.getString("managerServlet.mkdirFail", uploadPath)); return; } uploadedWar = new File(uploadPath, baseName + ".war"); } if (debug >= 2) { log("Uploading WAR file to " + uploadedWar); } try { if (tryAddServiced(name)) { try { if (config != null) { if (!configBase.mkdirs() && !configBase.isDirectory()) { writer.println(smClient.getString("managerServlet.mkdirFail", configBase)); return; } if (ExpandWar.copy(new File(config), new File(configBase, baseName + ".xml")) == false) { throw new Exception(sm.getString("managerServlet.copyError", config)); } } // Upload WAR uploadWar(writer, request, uploadedWar, smClient); if (update && tag == null) { if (deployedWar.exists() && !deployedWar.delete()) { writer.println(smClient.getString("managerServlet.deleteFail", deployedWar)); return; } // Rename uploaded WAR file if (!uploadedWar.renameTo(deployedWar)) { writer.println(smClient.getString("managerServlet.renameFail", uploadedWar, deployedWar)); return; } } if (tag != null) { // Copy WAR to the host's appBase ExpandWar.copy(uploadedWar, deployedWar); } } finally { removeServiced(name); } // Perform new deployment check(name); } else { writer.println(smClient.getString("managerServlet.inService", displayPath)); } } catch (Exception e) { log(sm.getString("managerServlet.error.deploy", displayPath), e); writer.println(smClient.getString("managerServlet.exception", e.toString())); return; } writeDeployResult(writer, smClient, name, displayPath); } /** * Install an application for the specified path from the specified web application archive. * * @param writer Writer to render results to * @param tag Revision tag to deploy from * @param cn Name of the application to be installed * @param smClient i18n messages using the locale of the client */ protected void deploy(PrintWriter writer, ContextName cn, String tag, StringManager smClient) { // NOTE: It is assumed that update is always true in this method. // Validate the requested context path if (!validateContextName(cn, writer, smClient)) { return; } String baseName = cn.getBaseName(); String name = cn.getName(); String displayPath = cn.getDisplayName(); // Find the local WAR file File localWar = new File(new File(versioned, tag), baseName + ".war"); File deployedWar = new File(host.getAppBaseFile(), baseName + ".war"); // Copy WAR to appBase try { if (tryAddServiced(name)) { try { if (!deployedWar.delete()) { writer.println(smClient.getString("managerServlet.deleteFail", deployedWar)); return; } ExpandWar.copy(localWar, deployedWar); } finally { removeServiced(name); } // Perform new deployment check(name); } else { writer.println(smClient.getString("managerServlet.inService", displayPath)); } } catch (Exception e) { log(sm.getString("managerServlet.error.deploy", displayPath), e); writer.println(smClient.getString("managerServlet.exception", e.toString())); return; } writeDeployResult(writer, smClient, name, displayPath); } /** * Install an application for the specified path from the specified web application archive. * * @param writer Writer to render results to * @param config URL of the context configuration file to be installed * @param cn Name of the application to be installed * @param war URL of the web application archive to be installed * @param update true to override any existing webapp on the path * @param smClient i18n messages using the locale of the client */ protected void deploy(PrintWriter writer, String config, ContextName cn, String war, boolean update, StringManager smClient) { if (config != null && config.length() == 0) { config = null; } if (war != null && war.length() == 0) { war = null; } if (debug >= 1) { if (config != null) { if (war != null) { log("install: Installing context configuration at '" + config + "' from '" + war + "'"); } else { log("install: Installing context configuration at '" + config + "'"); } } else { if (cn != null) { log("install: Installing web application '" + cn + "' from '" + war + "'"); } else { log("install: Installing web application from '" + war + "'"); } } } if (!validateContextName(cn, writer, smClient)) { return; } @SuppressWarnings("null") // checked in call above String name = cn.getName(); String baseName = cn.getBaseName(); String displayPath = cn.getDisplayName(); // If app exists deployment can only proceed if update is true // Note existing files will be deleted and then replaced Context context = (Context) host.findChild(name); if (context != null && !update) { writer.println(smClient.getString("managerServlet.alreadyContext", displayPath)); return; } if (config != null && config.startsWith("file:")) { config = config.substring("file:".length()); } if (war != null && war.startsWith("file:")) { war = war.substring("file:".length()); } try { if (tryAddServiced(name)) { try { if (config != null) { if (!configBase.mkdirs() && !configBase.isDirectory()) { writer.println(smClient.getString("managerServlet.mkdirFail", configBase)); return; } File localConfigFile = new File(configBase, baseName + ".xml"); File configFile = new File(config); // Skip delete and copy if source == destination if (!configFile.getCanonicalPath().equals(localConfigFile.getCanonicalPath())) { if (localConfigFile.isFile() && !localConfigFile.delete()) { writer.println(smClient.getString("managerServlet.deleteFail", localConfigFile)); return; } ExpandWar.copy(configFile, localConfigFile); } } if (war != null) { File localWarFile; if (war.endsWith(".war")) { localWarFile = new File(host.getAppBaseFile(), baseName + ".war"); } else { localWarFile = new File(host.getAppBaseFile(), baseName); } File warFile = new File(war); if (!warFile.isAbsolute()) { warFile = new File(host.getAppBaseFile(), war); } // Skip delete and copy if source == destination if (!warFile.getCanonicalPath().equals(localWarFile.getCanonicalPath())) { if (localWarFile.exists() && !ExpandWar.delete(localWarFile)) { writer.println(smClient.getString("managerServlet.deleteFail", localWarFile)); return; } ExpandWar.copy(warFile, localWarFile); } } } finally { removeServiced(name); } // Perform new deployment check(name); } else { writer.println(smClient.getString("managerServlet.inService", displayPath)); } writeDeployResult(writer, smClient, name, displayPath); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.deploy", displayPath), t); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } private void writeDeployResult(PrintWriter writer, StringManager smClient, String name, String displayPath) { Context deployed = (Context) host.findChild(name); if (deployed != null && deployed.getConfigured() && deployed.getState().isAvailable()) { writer.println(smClient.getString("managerServlet.deployed", displayPath)); } else if (deployed != null && !deployed.getState().isAvailable()) { writer.println(smClient.getString("managerServlet.deployedButNotStarted", displayPath)); } else { // Something failed writer.println(smClient.getString("managerServlet.deployFailed", displayPath)); } } /** * Render a list of the currently active Contexts in our virtual host. * * @param writer Writer to render to * @param smClient i18n support for current client's locale */ protected void list(PrintWriter writer, StringManager smClient) { if (debug >= 1) { log("list: Listing contexts for virtual host '" + host.getName() + "'"); } writer.println(smClient.getString("managerServlet.listed", host.getName())); Container[] contexts = host.findChildren(); for (Container container : contexts) { Context context = (Context) container; if (context != null) { String displayPath = context.getPath(); if (displayPath.equals("")) { displayPath = "/"; } List parts = null; if (context.getState().isAvailable()) { parts = Arrays.asList(displayPath, "running", "" + context.getManager().findSessions().length, context.getDocBase()); } else { parts = Arrays.asList(displayPath, "stopped", "0", context.getDocBase()); } writer.println(StringUtils.join(parts, ':')); } } } /** * Reload the web application at the specified context path. * * @param writer Writer to render to * @param cn Name of the application to be restarted * @param smClient i18n support for current client's locale */ protected void reload(PrintWriter writer, ContextName cn, StringManager smClient) { if (debug >= 1) { log("restart: Reloading web application '" + cn + "'"); } if (!validateContextName(cn, writer, smClient)) { return; } try { Context context = (Context) host.findChild(cn.getName()); if (context == null) { writer.println( smClient.getString("managerServlet.noContext", Escape.htmlElementContent(cn.getDisplayName()))); return; } // It isn't possible for the manager to reload itself if (context.getName().equals(this.context.getName())) { writer.println(smClient.getString("managerServlet.noSelf")); return; } context.reload(); writer.println(smClient.getString("managerServlet.reloaded", cn.getDisplayName())); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.reload", cn.getDisplayName()), t); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } /** * Render a list of available global JNDI resources. * * @param writer Writer to render to * @param type Fully qualified class name of the resource type of interest, or null to list * resources of all types * @param smClient i18n support for current client's locale */ protected void resources(PrintWriter writer, String type, StringManager smClient) { if (debug >= 1) { if (type != null) { log("resources: Listing resources of type " + type); } else { log("resources: Listing resources of all types"); } } // Is the global JNDI resources context available? if (global == null) { writer.println(smClient.getString("managerServlet.noGlobal")); return; } // Enumerate the global JNDI resources of the requested type if (type != null) { writer.println(smClient.getString("managerServlet.resourcesType", type)); } else { writer.println(smClient.getString("managerServlet.resourcesAll")); } printResources(writer, "", global, type, smClient); } /** * List the resources of the given context. * * @param writer Writer to render to * @param prefix Path for recursion * @param namingContext The naming context for lookups * @param type Fully qualified class name of the resource type of interest, or null to list * resources of all types * @param smClient i18n support for current client's locale */ protected void printResources(PrintWriter writer, String prefix, javax.naming.Context namingContext, String type, StringManager smClient) { try { NamingEnumeration items = namingContext.listBindings(""); while (items.hasMore()) { Binding item = items.next(); Object obj = item.getObject(); if (obj instanceof javax.naming.Context) { printResources(writer, prefix + item.getName() + "/", (javax.naming.Context) obj, type, smClient); } else { if (type != null && (obj == null || !IntrospectionUtils.isInstance(obj.getClass(), type))) { continue; } writer.print(prefix + item.getName()); writer.print(':'); writer.print(item.getClassName()); // Do we want a description if available? writer.println(); } } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.resources", type), t); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } /** * Writes System OS and JVM properties. * * @param writer Writer to render to * @param smClient i18n support for current client's locale */ protected void serverinfo(PrintWriter writer, StringManager smClient) { if (debug >= 1) { log("serverinfo"); } try { writer.println(smClient.getString("managerServlet.serverInfo", ServerInfo.getServerInfo(), System.getProperty("os.name"), System.getProperty("os.version"), System.getProperty("os.arch"), System.getProperty("java.runtime.version"), System.getProperty("java.vm.vendor"))); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.serverInfo"), t); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } /** * Session information for the web application at the specified context path. Displays a profile of session * thisAccessedTime listing number of sessions for each 10 minute interval up to 10 hours. * * @param writer Writer to render to * @param cn Name of the application to list session information for * @param idle Expire all sessions with idle time > idle for this context * @param smClient i18n support for current client's locale */ protected void sessions(PrintWriter writer, ContextName cn, int idle, StringManager smClient) { if (debug >= 1) { log("sessions: Session information for web application '" + cn + "'"); if (idle >= 0) { log("sessions: Session expiration for " + idle + " minutes '" + cn + "'"); } } if (!validateContextName(cn, writer, smClient)) { return; } String displayPath = cn.getDisplayName(); try { Context context = (Context) host.findChild(cn.getName()); if (context == null) { writer.println(smClient.getString("managerServlet.noContext", Escape.htmlElementContent(displayPath))); return; } Manager manager = context.getManager(); if (manager == null) { writer.println(smClient.getString("managerServlet.noManager", Escape.htmlElementContent(displayPath))); return; } int maxCount = 60; int histoInterval = 1; int maxInactiveInterval = context.getSessionTimeout(); if (maxInactiveInterval > 0) { histoInterval = maxInactiveInterval / maxCount; if (histoInterval * maxCount < maxInactiveInterval) { histoInterval++; } if (0 == histoInterval) { histoInterval = 1; } maxCount = maxInactiveInterval / histoInterval; if (histoInterval * maxCount < maxInactiveInterval) { maxCount++; } } writer.println(smClient.getString("managerServlet.sessions", displayPath)); writer.println(smClient.getString("managerServlet.sessiondefaultmax", "" + maxInactiveInterval)); Session[] sessions = manager.findSessions(); int[] timeout = new int[maxCount + 1]; int notimeout = 0; int expired = 0; for (Session session : sessions) { int time = (int) (session.getIdleTimeInternal() / 1000L); if (idle >= 0 && time >= idle * 60) { session.expire(); expired++; } time = time / 60 / histoInterval; if (time < 0) { notimeout++; } else if (time >= maxCount) { timeout[maxCount]++; } else { timeout[time]++; } } if (timeout[0] > 0) { writer.println( smClient.getString("managerServlet.sessiontimeout", "<" + histoInterval, "" + timeout[0])); } for (int i = 1; i < maxCount; i++) { if (timeout[i] > 0) { writer.println(smClient.getString("managerServlet.sessiontimeout", "" + i * histoInterval + " - <" + (i + 1) * histoInterval, "" + timeout[i])); } } if (timeout[maxCount] > 0) { writer.println(smClient.getString("managerServlet.sessiontimeout", ">=" + maxCount * histoInterval, "" + timeout[maxCount])); } if (notimeout > 0) { writer.println(smClient.getString("managerServlet.sessiontimeout.unlimited", "" + notimeout)); } if (idle >= 0) { writer.println(smClient.getString("managerServlet.sessiontimeout.expired", ">" + idle, "" + expired)); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.sessions", displayPath), t); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } /** * Extract the expiration request parameter * * @param writer Writer to render to * @param cn Name of the application to list session information for * @param req The Servlet request * @param smClient i18n support for current client's locale */ protected void expireSessions(PrintWriter writer, ContextName cn, HttpServletRequest req, StringManager smClient) { int idle = -1; String idleParam = req.getParameter("idle"); if (idleParam != null) { try { idle = Integer.parseInt(idleParam); } catch (NumberFormatException e) { log(sm.getString("managerServlet.error.idleParam", idleParam)); } } sessions(writer, cn, idle, smClient); } /** * Start the web application at the specified context path. * * @param writer Writer to render to * @param cn Name of the application to be started * @param smClient i18n support for current client's locale */ protected void start(PrintWriter writer, ContextName cn, StringManager smClient) { if (debug >= 1) { log("start: Starting web application '" + cn + "'"); } if (!validateContextName(cn, writer, smClient)) { return; } String displayPath = cn.getDisplayName(); try { Context context = (Context) host.findChild(cn.getName()); if (context == null) { writer.println(smClient.getString("managerServlet.noContext", Escape.htmlElementContent(displayPath))); return; } context.start(); if (context.getState().isAvailable()) { writer.println(smClient.getString("managerServlet.started", displayPath)); } else { writer.println(smClient.getString("managerServlet.startFailed", displayPath)); } } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.start", displayPath), t); writer.println(smClient.getString("managerServlet.startFailed", displayPath)); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } /** * Stop the web application at the specified context path. * * @param writer Writer to render to * @param cn Name of the application to be stopped * @param smClient i18n support for current client's locale */ protected void stop(PrintWriter writer, ContextName cn, StringManager smClient) { if (debug >= 1) { log("stop: Stopping web application '" + cn + "'"); } if (!validateContextName(cn, writer, smClient)) { return; } String displayPath = cn.getDisplayName(); try { Context context = (Context) host.findChild(cn.getName()); if (context == null) { writer.println(smClient.getString("managerServlet.noContext", Escape.htmlElementContent(displayPath))); return; } // It isn't possible for the manager to stop itself if (context.getName().equals(this.context.getName())) { writer.println(smClient.getString("managerServlet.noSelf")); return; } context.stop(); writer.println(smClient.getString("managerServlet.stopped", displayPath)); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.stop", displayPath), t); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } /** * Undeploy the web application at the specified context path. * * @param writer Writer to render to * @param cn Name of the application to be removed * @param smClient i18n support for current client's locale */ protected void undeploy(PrintWriter writer, ContextName cn, StringManager smClient) { if (debug >= 1) { log("undeploy: Undeploying web application at '" + cn + "'"); } if (!validateContextName(cn, writer, smClient)) { return; } String name = cn.getName(); String baseName = cn.getBaseName(); String displayPath = cn.getDisplayName(); try { // Validate the Context of the specified application Context context = (Context) host.findChild(name); if (context == null) { writer.println(smClient.getString("managerServlet.noContext", Escape.htmlElementContent(displayPath))); return; } if (!isDeployed(name)) { writer.println( smClient.getString("managerServlet.notDeployed", Escape.htmlElementContent(displayPath))); return; } if (tryAddServiced(name)) { try { // Try to stop the context first to be nicer context.stop(); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); } try { File war = new File(host.getAppBaseFile(), baseName + ".war"); File dir = new File(host.getAppBaseFile(), baseName); File xml = new File(configBase, baseName + ".xml"); if (war.exists() && !war.delete()) { writer.println(smClient.getString("managerServlet.deleteFail", war)); return; } else if (dir.exists() && !ExpandWar.delete(dir, false)) { writer.println(smClient.getString("managerServlet.deleteFail", dir)); return; } else if (xml.exists() && !xml.delete()) { writer.println(smClient.getString("managerServlet.deleteFail", xml)); return; } } finally { removeServiced(name); } // Perform new deployment check(name); } else { writer.println(smClient.getString("managerServlet.inService", displayPath)); } writer.println(smClient.getString("managerServlet.undeployed", displayPath)); } catch (Throwable t) { ExceptionUtils.handleThrowable(t); log(sm.getString("managerServlet.error.undeploy", displayPath), t); writer.println(smClient.getString("managerServlet.exception", t.toString())); } } // -------------------------------------------------------- Support Methods /** * Invoke the isDeployed method on the deployer. * * @param name The webapp name * * @return true if a webapp with that name is deployed * * @throws Exception Propagate JMX invocation error */ protected boolean isDeployed(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; Boolean result = (Boolean) mBeanServer.invoke(oname, "isDeployed", params, signature); return result.booleanValue(); } /** * Invoke the check method on the deployer. * * @param name The webapp name * * @throws Exception Propagate JMX invocation error */ protected void check(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; mBeanServer.invoke(oname, "check", params, signature); } /** * Attempt to mark a context as being serviced * * @param name The context name * * @return {@code true} if the application was marked as being serviced and {@code false} if the application was * already marked as being serviced * * @throws Exception Error invoking the deployer */ protected boolean tryAddServiced(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; Boolean result = (Boolean) mBeanServer.invoke(oname, "tryAddServiced", params, signature); return result.booleanValue(); } /** * Invoke the removeServiced method on the deployer. * * @param name The webapp name * * @throws Exception Propagate JMX invocation error */ protected void removeServiced(String name) throws Exception { String[] params = { name }; String[] signature = { "java.lang.String" }; mBeanServer.invoke(oname, "removeServiced", params, signature); } /** * Upload the WAR file included in this request, and store it at the specified file location. * * @param writer Writer to render to * @param request The servlet request we are processing * @param war The file into which we should store the uploaded WAR * @param smClient The StringManager used to construct i18n messages based on the Locale of the client * * @exception IOException if an I/O error occurs during processing */ protected void uploadWar(PrintWriter writer, HttpServletRequest request, File war, StringManager smClient) throws IOException { if (war.exists() && !war.delete()) { String msg = smClient.getString("managerServlet.deleteFail", war); throw new IOException(msg); } try (ServletInputStream istream = request.getInputStream(); OutputStream ostream = new FileOutputStream(war)) { IOTools.flow(istream, ostream); } catch (IOException e) { if (war.exists() && !war.delete()) { writer.println(smClient.getString("managerServlet.deleteFail", war)); } throw e; } } protected static boolean validateContextName(ContextName cn, PrintWriter writer, StringManager smClient) { // ContextName should be non-null with a path that is empty or starts // with / if (cn != null && (cn.getPath().startsWith("/") || cn.getPath().equals(""))) { return true; } String path = null; if (cn != null) { path = Escape.htmlElementContent(cn.getPath()); } writer.println(smClient.getString("managerServlet.invalidPath", path)); return false; } protected Map> getConnectorCiphers(StringManager smClient) { Map> result = new HashMap<>(); Connector connectors[] = getConnectors(); for (Connector connector : connectors) { if (Boolean.TRUE.equals(connector.getProperty("SSLEnabled"))) { SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs(); for (SSLHostConfig sslHostConfig : sslHostConfigs) { String name = connector.toString() + "-" + sslHostConfig.getHostName(); /* Add cipher list, keep order but remove duplicates */ result.put(name, new ArrayList<>(new LinkedHashSet<>(Arrays.asList(sslHostConfig.getEnabledCiphers())))); } } else { ArrayList cipherList = new ArrayList<>(1); cipherList.add(smClient.getString("managerServlet.notSslConnector")); result.put(connector.toString(), cipherList); } } return result; } protected Map> getConnectorCerts(StringManager smClient) { Map> result = new HashMap<>(); Connector connectors[] = getConnectors(); for (Connector connector : connectors) { if (Boolean.TRUE.equals(connector.getProperty("SSLEnabled"))) { SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs(); for (SSLHostConfig sslHostConfig : sslHostConfigs) { if (sslHostConfig.getOpenSslContext().longValue() == 0) { // Not set. Must be JSSE based. Set sslHostConfigCerts = sslHostConfig.getCertificates(); for (SSLHostConfigCertificate sslHostConfigCert : sslHostConfigCerts) { String name = connector.toString() + "-" + sslHostConfig.getHostName() + "-" + sslHostConfigCert.getType(); List certList = new ArrayList<>(); SSLContext sslContext = sslHostConfigCert.getSslContext(); String alias = sslHostConfigCert.getCertificateKeyAlias(); if (alias == null) { alias = SSLUtilBase.DEFAULT_KEY_ALIAS; } X509Certificate[] certs = sslContext.getCertificateChain(alias); if (certs == null) { certList.add(smClient.getString("managerServlet.certsNotAvailable")); } else { for (Certificate cert : certs) { certList.add(cert.toString()); } } result.put(name, certList); } } else { List certList = new ArrayList<>(); certList.add(smClient.getString("managerServlet.certsNotAvailable")); String name = connector.toString() + "-" + sslHostConfig.getHostName(); result.put(name, certList); } } } else { List certList = new ArrayList<>(1); certList.add(smClient.getString("managerServlet.notSslConnector")); result.put(connector.toString(), certList); } } return result; } protected Map> getConnectorTrustedCerts(StringManager smClient) { Map> result = new HashMap<>(); Connector connectors[] = getConnectors(); for (Connector connector : connectors) { if (Boolean.TRUE.equals(connector.getProperty("SSLEnabled"))) { SSLHostConfig[] sslHostConfigs = connector.getProtocolHandler().findSslHostConfigs(); for (SSLHostConfig sslHostConfig : sslHostConfigs) { String name = connector.toString() + "-" + sslHostConfig.getHostName(); List certList = new ArrayList<>(); if (sslHostConfig.getOpenSslContext().longValue() == 0) { // Not set. Must be JSSE based. SSLContext sslContext = sslHostConfig.getCertificates().iterator().next().getSslContext(); X509Certificate[] certs = sslContext.getAcceptedIssuers(); if (certs == null) { certList.add(smClient.getString("managerServlet.certsNotAvailable")); } else if (certs.length == 0) { certList.add(smClient.getString("managerServlet.trustedCertsNotConfigured")); } else { for (Certificate cert : certs) { certList.add(cert.toString()); } } } else { certList.add(smClient.getString("managerServlet.certsNotAvailable")); } result.put(name, certList); } } else { List certList = new ArrayList<>(1); certList.add(smClient.getString("managerServlet.notSslConnector")); result.put(connector.toString(), certList); } } return result; } private Connector[] getConnectors() { Engine e = (Engine) host.getParent(); Service s = e.getService(); return s.findConnectors(); } }




© 2015 - 2024 Weber Informatics LLC | Privacy Policy