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

com.sun.enterprise.admin.servermgmt.cli.LocalServerCommand Maven / Gradle / Ivy

/*
 * DO NOT ALTER OR REMOVE COPYRIGHT NOTICES OR THIS HEADER.
 *
 * Copyright (c) 1997-2013 Oracle and/or its affiliates. All rights reserved.
 *
 * The contents of this file are subject to the terms of either the GNU
 * General Public License Version 2 only ("GPL") or the Common Development
 * and Distribution License("CDDL") (collectively, the "License").  You
 * may not use this file except in compliance with the License.  You can
 * obtain a copy of the License at
 * https://glassfish.dev.java.net/public/CDDL+GPL_1_1.html
 * or packager/legal/LICENSE.txt.  See the License for the specific
 * language governing permissions and limitations under the License.
 *
 * When distributing the software, include this License Header Notice in each
 * file and include the License file at packager/legal/LICENSE.txt.
 *
 * GPL Classpath Exception:
 * Oracle designates this particular file as subject to the "Classpath"
 * exception as provided by Oracle in the GPL Version 2 section of the License
 * file that accompanied this code.
 *
 * Modifications:
 * If applicable, add the following below the License Header, with the fields
 * enclosed by brackets [] replaced by your own identifying information:
 * "Portions Copyright [year] [name of copyright owner]"
 *
 * Contributor(s):
 * If you wish your version of this file to be governed by only the CDDL or
 * only the GPL Version 2, indicate your decision by adding "[Contributor]
 * elects to include this software in this distribution under the [CDDL or GPL
 * Version 2] license."  If you don't indicate a single choice of license, a
 * recipient has the option to distribute your version of this file under
 * either the CDDL, the GPL Version 2 or to extend the choice of license to
 * its licensees as provided above.  However, if you add GPL Version 2 code
 * and therefore, elected the GPL Version 2 license, then the option applies
 * only if the new code is made subject to such option by the copyright
 * holder.
 */
// Portions Copyright [2016-2023] [Payara Foundation and/or affiliates]

package com.sun.enterprise.admin.servermgmt.cli;

import com.sun.enterprise.admin.cli.CLICommand;
import com.sun.enterprise.admin.cli.CLIConstants;
import com.sun.enterprise.admin.cli.ProgramOptions;
import com.sun.enterprise.admin.cli.remote.RemoteCLICommand;
import com.sun.enterprise.admin.servermgmt.domain.DomainConstants;
import com.sun.enterprise.security.store.PasswordAdapter;
import com.sun.enterprise.universal.i18n.LocalStringsImpl;
import com.sun.enterprise.universal.io.SmartFile;
import com.sun.enterprise.universal.process.Jps;
import com.sun.enterprise.universal.process.ProcessUtils;
import com.sun.enterprise.universal.xml.MiniXmlParser;
import com.sun.enterprise.universal.xml.MiniXmlParserException;
import com.sun.enterprise.util.HostAndPort;
import com.sun.enterprise.util.StringUtils;
import com.sun.enterprise.util.SystemPropertyConstants;
import com.sun.enterprise.util.io.FileUtils;
import com.sun.enterprise.util.io.ServerDirs;
import org.glassfish.api.ActionReport;
import org.glassfish.api.admin.CommandException;
import org.w3c.dom.Document;
import org.w3c.dom.NodeList;
import org.xml.sax.SAXException;

import javax.xml.namespace.QName;
import javax.xml.parsers.DocumentBuilder;
import javax.xml.parsers.DocumentBuilderFactory;
import javax.xml.parsers.ParserConfigurationException;
import javax.xml.stream.XMLEventReader;
import javax.xml.stream.XMLInputFactory;
import javax.xml.stream.XMLStreamException;
import javax.xml.stream.events.Attribute;
import javax.xml.stream.events.XMLEvent;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.util.Arrays;
import java.util.HashMap;
import java.util.List;
import java.util.logging.Level;
import java.util.stream.Stream;

import static com.sun.enterprise.admin.servermgmt.domain.DomainConstants.MASTERPASSWORD_FILE;

/**
 * A class that's supposed to capture all the behavior common to operation on a
 * "local" server. It's getting fairly complicated thus the "section headers"
 * comments. This class plays two roles,
 * 
    *
  • a place for putting common code - which are final methods. A parent class * that is communicating with its own unknown sub-classes. These are non-final * methods * * @author Byron Nevins */ public abstract class LocalServerCommand extends CLICommand { //////////////////////////////////////////////////////////////// /// Section: private variables //////////////////////////////////////////////////////////////// private ServerDirs serverDirs; private static final LocalStringsImpl STRINGS = new LocalStringsImpl(LocalDomainCommand.class); private final static int IS_RUNNING_DEFAULT_TIMEOUT = 2000; //////////////////////////////////////////////////////////////// /// Section: protected variables //////////////////////////////////////////////////////////////// protected static final String DEFAULT_MASTER_PASSWORD = "changeit"; //////////////////////////////////////////////////////////////// /// Section: protected methods that are OK to override //////////////////////////////////////////////////////////////// /** * Override this method and return false to turn-off the file validation. E.g. * it demands that config/domain.xml be present. In special cases like * Synchronization -- this is how you turn off the testing. * * @return true - do the checks, false - don't do the checks */ protected boolean checkForSpecialFiles() { return true; } //////////////////////////////////////////////////////////////// /// Section: protected methods that are notOK to override. //////////////////////////////////////////////////////////////// /** * Returns the admin address of the local domain. Note that this method should * be called only when you own the domain that is available on an accessible * file system. * * @return HostAndPort object with admin server address * @throws CommandException in case of parsing errors */ protected final HostAndPort getAdminAddress() throws CommandException { // default: DAS which always has the name "server" return getAdminAddress("server"); } /** * Returns the admin address of a particular server. Note that this method * should be called only when you own the server that is available on an * accessible file system. * * @param serverName the server name * @return HostAndPort object with admin server address * @throws CommandException in case of parsing errors */ protected final HostAndPort getAdminAddress(String serverName) throws CommandException { try { MiniXmlParser parser = new MiniXmlParser(getDomainXml(), serverName); List addrSet = parser.getAdminAddresses(); if (!addrSet.isEmpty()) { return addrSet.get(0); } else { throw new CommandException(STRINGS.get("NoAdminPort")); } } catch (MiniXmlParserException ex) { throw new CommandException(STRINGS.get("NoAdminPortEx", ex), ex); } } protected final void setServerDirs(ServerDirs sd) { serverDirs = sd; } protected final boolean isLocal() { return serverDirs != null && serverDirs.getServerName() != null; } protected final boolean isRemote() { return !isLocal(); } private void resetLocalPassword() throws IOException { resetServerDirs(); setLocalPassword(); } protected final void setLocalPassword() { String pw = serverDirs == null ? null : serverDirs.getLocalPassword(); if (ok(pw)) { programOpts.setPassword(pw != null ? pw.toCharArray() : null, ProgramOptions.PasswordLocation.LOCAL_PASSWORD); logger.finer("Using local password"); } else logger.finer("Not using local password"); } protected final void unsetLocalPassword() { programOpts.setPassword(null, ProgramOptions.PasswordLocation.LOCAL_PASSWORD); } protected final void resetServerDirs() throws IOException { if (serverDirs == null) throw new RuntimeException(Strings.get("NoServerDirs")); serverDirs = serverDirs.refresh(); } protected final ServerDirs getServerDirs() { return serverDirs; } protected final File getDomainXml() { if (serverDirs == null) throw new RuntimeException(Strings.get("NoServerDirs")); return serverDirs.getDomainXml(); } /** * Checks if the create-domain was created using --savemasterpassword flag which * obtains security by obfuscation! Returns null in case of failure of any kind. * * @return String representing the password from the JCEKS store named * master-password in domain folder */ protected final String readFromMasterPasswordFile() { File mpf = getMasterPasswordFile(); if (mpf == null) return null; // no master password saved try { PasswordAdapter pw = new PasswordAdapter(mpf.getAbsolutePath(), MASTERPASSWORD_FILE.toCharArray()); // fixed // key return pw.getPasswordForAlias(MASTERPASSWORD_FILE); } catch (Exception e) { logger.log(Level.FINER, "master password file reading error: {0}", e.getMessage()); return null; } } protected final boolean verifyMasterPassword(String mpv) { // issue : 14971, should ideally use javax.net.ssl.keyStore and // javax.net.ssl.keyStoreType system props here but they are // unavailable to asadmin start-domain hence falling back to // cacerts.p12 instead of keystore.p12. Since the truststore // is less-likely to be Non-JKS return loadAndVerifyKeystore(getKeyStoreFile(), mpv); } protected boolean loadAndVerifyKeystore(File jks, String mpv) { try { // try to load the keystore with the provided keystore password KeyStore.getInstance(jks, mpv.toCharArray()); return true; } catch (Exception e) { if (logger.isLoggable(Level.FINER)) logger.finer(e.getMessage()); return false; } } /** * Get the master password, either from a password file or by asking the user. * * @return the actual master password */ protected final String getMasterPassword() throws CommandException { // Sets the password into the launcher info. // Yes, returning master password as a string is not right ... final int RETRIES = 3; long t0 = now(); String mpv = passwords.get(CLIConstants.MASTER_PASSWORD); if (mpv == null) { // not specified in the password file mpv = DEFAULT_MASTER_PASSWORD; // optimization for the default case -- see 9592 if (!verifyMasterPassword(mpv)) { mpv = readFromMasterPasswordFile(); if (!verifyMasterPassword(mpv)) { mpv = retry(RETRIES); } } } else { // the passwordfile contains AS_ADMIN_MASTERPASSWORD, use it if (!verifyMasterPassword(mpv)) mpv = retry(RETRIES); } long t1 = now(); logger.log(Level.FINER, "Time spent in master password extraction: {0} msec", (t1 - t0)); return mpv; } /** * See if the server is alive and is the one at the specified directory. * * @param ourDir the directory to check if the server is alive agains * @param directoryKey the key for the directory * @return true if it's the DAS at this domain directory */ protected final boolean isThisServer(File ourDir, String directoryKey) { if (!ok(directoryKey)) throw new NullPointerException(); ourDir = getUniquePath(ourDir); logger.log(Level.FINER, "Check if server is at location {0}", ourDir); try { programOpts.setHostAndPort(getAdminAddress()); RemoteCLICommand cmd = new RemoteCLICommand("__locations", programOpts, env); ActionReport report = cmd.executeAndReturnActionReport("__locations"); String theirDirPath = report.findProperty(directoryKey); logger.log(Level.FINER, "Remote server has root directory {0}", theirDirPath); if (ok(theirDirPath)) { File theirDir = getUniquePath(new File(theirDirPath)); return theirDir.equals(ourDir); } return false; } catch (Exception ex) { return false; } } protected final int getServerPid() { try { return Integer.parseInt(new RemoteCLICommand("__locations", programOpts, env) .executeAndReturnActionReport("__locations").findProperty("Pid")); } catch (Exception e) { return -1; } } /** * There is sometimes a need for subclasses to know if a * local domain is running.An example of such a command is * change-master-password command.The stop-domain command also needs to know if * a domain is running without having to provide user name and password * on command line (this is the case when I own a domain that has non-default * admin user and password) and want to stop it without providing it. *

    * In such cases, we need to know if the domain is running and this method * provides a way to do that. * * @param host the host to check * @param port the port to check agains * @return boolean indicating whether the server is running */ protected final boolean isRunning(String host, int port) { try (Socket server = new Socket()) { if (host == null) { host = InetAddress.getByName(null).getHostName(); } server.connect(new InetSocketAddress(host, port), IS_RUNNING_DEFAULT_TIMEOUT); return true; } catch (Exception ex) { logger.log(Level.FINER, "\nisRunning got exception: {0}", ex); return false; } } /** * Is the server still running? This is only called when we're hanging around * waiting for the server to die. Byron Nevins, Nov 7, 2010 - Check to see if * the process itself is still running We use OS tools to figure this out. See * ProcessUtils for details. Failover to the JPS check if necessary *

    * bnevins, May 2013 * http://serverfault.com/questions/181015/how-do-you-free-up-a-port-being-held-open-by-dead-process * In WIndows the admin port may be held open for a while -- if there happens to * be an attached running child process. This is the key message from the url: *

    * If your program spawned any processes while it was running, try killing them. * That should cause its process record to be freed and the TCP port to be * cleaned up. Apparently windows does this when the record is released not when * the process exits as I would have expected. * * @return */ protected boolean isRunning() { int pp = getPrevPid(); if (pp < 0) { return isRunningByCheckingForPidFile(); } Boolean b = ProcessUtils.isProcessRunning(pp); if (b == null) { // this means it couldn't find out! return isRunningUsingJps(); } else { return b; } } protected final void waitForRestart(final int oldServerPid) throws CommandException { waitForRestart(oldServerPid, CLIConstants.WAIT_FOR_DAS_TIME_MS); } /** * Byron Nevins Says: We have quite a historical assortment of ways to determine * if a server has restarted. There are little teeny timing issues with all of * them. I'm confident that this new technique will clear them all up. Here we * are just monitoring the PID of the new server and comparing it to the pid of * the old server. The oldServerPid is guaranteed to be either the PID of the * "old" server or -1 if we couldn't get it -- or it isn't running. If it is -1 * then we make the assumption that once we DO get a valid pid that the server * has started. If the old pid is valid we simply poll until we get a different * pid. Notice that we will never get a valid pid back unless the server is * officially up and running and "STARTED" Created April 2013 * * @param oldServerPid The pid of the server which is being restarted. * @throws CommandException if we time out. */ protected final void waitForRestart(final int oldServerPid, long timeout) throws CommandException { long end = getEndTime(timeout); while (now() < end) { try { if (isLocal()) resetLocalPassword(); int newServerPid = getServerPid(); if (newServerPid > 0 && newServerPid != oldServerPid) { logger.log(Level.FINER, "oldserver-pid, newserver-pid = {0} --- {1}", new Object[]{oldServerPid, newServerPid}); return; } Thread.sleep(CLIConstants.RESTART_CHECK_INTERVAL_MSEC); } catch (Exception e) { // continue } } // if we get here -- we timed out throw new CommandException(STRINGS.get("restartDomain.noGFStart")); } // todo move prevpid to ServerDirs ??? protected final int getPrevPid() { try { File prevPidFile = new File(getServerDirs().getPidFile().getPath() + ".prev"); if (!prevPidFile.canRead()) return -1; String pids = FileUtils.readSmallFile(prevPidFile).trim(); return Integer.parseInt(pids); } catch (Exception ex) { return -1; } } /** * Is the server still running? This is only called when we're hanging around * waiting for the server to die. Byron Nevins, Nov 7, 2010 - Check to see if * the process itself is still running We use jps to check If there are any * problems fall back to the previous implementation of isRunning() which looks * for the pidfile to get deleted */ private boolean isRunningUsingJps() { int pp = getPrevPid(); if (pp < 0) return isRunningByCheckingForPidFile(); return Jps.isPid(pp); } /** * Is the server still running? This is only called when we're hanging around * waiting for the server to die. */ private boolean isRunningByCheckingForPidFile() { File pf = getServerDirs().getPidFile(); if (pf != null) { return pf.exists(); } else return isRunning(programOpts.getHost(), // remote case programOpts.getPort()); } /** * Get uptime from the server. * * @return uptime in milliseconds * @throws CommandException if the server is not running */ protected final long getUptime() throws CommandException { RemoteCLICommand cmd = new RemoteCLICommand("uptime", programOpts, env); String up = cmd.executeAndReturnOutput("uptime", "--milliseconds").trim(); long uptimeMillis = parseUptime(up); if (uptimeMillis <= 0) { throw new CommandException(STRINGS.get("restart.dasNotRunning")); } logger.log(Level.FINER, "server uptime: {0}", uptimeMillis); return uptimeMillis; } /** * See if the server is restartable As of March 2011 -- this only returns false * if a passwordfile argument was given when the server started -- but it is no * longer available - i.e.the user deleted it or made it unreadable. * * @return true if the server is restartable * @throws CommandException */ protected final boolean isRestartable() throws CommandException { // false negative is worse than false positive. // there is one and only one case where we return false RemoteCLICommand cmd = new RemoteCLICommand("_get-runtime-info", programOpts, env); ActionReport report = cmd.executeAndReturnActionReport("_get-runtime-info"); if (report != null) { String val = report.findProperty("restartable_value"); return !ok(val) || !val.equals("false"); } return true; } //////////////////////////////////////////////////////////////// /// Section: private methods //////////////////////////////////////////////////////////////// /** * The remote uptime command returns a string like: Uptime: 10 minutes, 53 * seconds, Total milliseconds: 653859\n We find that last number and extract * it. XXX - this is pretty gross, and fragile */ private long parseUptime(String up) { try { return Long.parseLong(up); } catch (Exception e) { return 0; } } /** * Load KeyStore. By default, it is cacerts.p12. If not found, search for cacerts.jks. * * @return File of keystore (p12 or jks) in config directory, null if none is found */ protected File getKeyStoreFile() { if (serverDirs == null) { return null; } File configDir = serverDirs.getConfigDir(); File mp = Stream.of(new File(configDir, DomainConstants.TRUSTSTORE_FILE), new File(configDir, DomainConstants.TRUSTSTORE_JKS_FILE)) .filter(f -> f.canRead()) .findFirst() .orElse(null); return mp; } protected File getMasterPasswordFile() { if (serverDirs == null) return null; File mp = new File(serverDirs.getConfigDir(), MASTERPASSWORD_FILE); if (!mp.canRead()) return null; return mp; } private String retry(int times) throws CommandException { String mpv; // prompt times times for (int i = 0; i < times; i++) { // XXX - I18N String prompt = STRINGS.get("mp.prompt", (times - i)); char[] mpvArr = super.readPassword(prompt); mpv = mpvArr != null ? new String(mpvArr) : null; if (mpv == null) throw new CommandException(STRINGS.get("no.console")); // ignore retries :) if (verifyMasterPassword(mpv)) return mpv; if (i < (times - 1)) logger.info(STRINGS.get("retry.mp")); // make them pay for typos? // Thread.currentThread().sleep((i+1)*10000); } throw new CommandException(STRINGS.get("mp.giveup", times)); } private File getUniquePath(File f) { try { f = f.getCanonicalFile(); } catch (IOException ioex) { f = SmartFile.sanitize(f); } return f; } private long now() { // it's just *so* ugly to call this directly! return System.currentTimeMillis(); } private long getEndTime(long timeout) { return timeout + now(); } protected boolean dataGridEncryptionEnabled() throws IOException, XMLStreamException { // We can't access config beans from this invocation due to it being CLI vs. // ASAdmin command - it's not // executing against a running server. This means we need to read directly from // the domain.xml. XMLEventReader xmlReader = XMLInputFactory.newInstance() .createXMLEventReader(new FileInputStream(getDomainXml())); while (xmlReader.hasNext()) { XMLEvent event = xmlReader.nextEvent(); if (event.isStartElement() && event.asStartElement().getName().getLocalPart().equals("hazelcast-runtime-configuration")) { Attribute attribute = event.asStartElement() .getAttributeByName(new QName("datagrid-encryption-enabled")); if (attribute == null) { return false; } return Boolean.parseBoolean(attribute.getValue()); } } logger.warning("Could not determine if data grid encryption is enabled - " + "you will need to regenerate the encryption key if it is"); return false; } /** * Gets the GlassFish installation root (using property * com.sun.aas.installRoot), first from asenv.conf. If that's not available, * then from java.lang.System. * * @return path of GlassFish install root * @throws CommandException if the GlassFish install root is not found */ protected String getInstallRootPath() throws CommandException { String installRootPath = getSystemProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY); if (!StringUtils.ok(installRootPath)) { installRootPath = System.getProperty(SystemPropertyConstants.INSTALL_ROOT_PROPERTY); } if (!StringUtils.ok(installRootPath)) { throw new CommandException("noInstallDirPath"); } return installRootPath; } protected void checkAdditionalTrustAndKeyStores() throws IOException, XMLStreamException { HashMap additionalTrustandKeyStores = new HashMap<>(); try { DocumentBuilderFactory documentBuilderFactory = DocumentBuilderFactory.newInstance(); DocumentBuilder documentBuilder = documentBuilderFactory.newDocumentBuilder(); Document document = documentBuilder.parse(getDomainXml()); NodeList jvmOptionsNodes = document.getElementsByTagName("jvm-options"); for (int i = 0; i < jvmOptionsNodes.getLength(); i++) { String jvmOption = jvmOptionsNodes.item(i).getTextContent(); if (jvmOption.startsWith("-Dfish.payara.ssl.additionalKeyStores")) { String additionalKeyStores = jvmOption.split("=")[1]; additionalTrustandKeyStores.compute("additionalKeyStores", (key, value) -> value == null ? additionalKeyStores : value.concat(", " + additionalKeyStores)); continue; } if (jvmOption.startsWith("-Dfish.payara.ssl.additionalTrustStores")) { String additionalTrustStores = jvmOption.split("=")[1]; additionalTrustandKeyStores.compute("additionalTrustStores", (key, value) -> value == null ? additionalTrustStores : value.concat(", " + additionalTrustStores)); continue; } } if (additionalTrustandKeyStores.containsKey("additionalKeyStores")) { logger.log(Level.INFO, "The passwords of additional KeyStores {0} have not been changed - please update these manually to continue using them.", Arrays.toString(additionalTrustandKeyStores.get("additionalKeyStores").split(File.pathSeparator))); } if (additionalTrustandKeyStores.containsKey("additionalTrustStores")) { logger.log(Level.INFO, "The passwords of additional TrustStores {0} have not been changed - please update these manually to continue using them.", Arrays.toString(additionalTrustandKeyStores.get("additionalTrustStores").split(File.pathSeparator))); } } catch (ParserConfigurationException | SAXException exception) { logger.warning( "Could not determine if there were additional Key Stores or Trust stores, if the master-password has been updated, the password for the additional stores need updating in order to continue using them."); } } }





© 2015 - 2025 Weber Informatics LLC | Privacy Policy