/* * NOTE: This copyright does *not* cover user programs that use HQ * program services by normal system calls through the application * program interfaces provided as part of the Hyperic Plug-in Development * Kit or the Hyperic Client Development Kit - this is merely considered * normal use of the program, and does *not* fall under the heading of * "derived work". * * Copyright (C) [2004, 2005, 2006], Hyperic, Inc. * This file is part of HQ. * * HQ is free software; you can redistribute it and/or modify * it under the terms version 2 of the GNU General Public License as * published by the Free Software Foundation. This program is distributed * in the hope that it will be useful, but WITHOUT ANY WARRANTY; without * even the implied warranty of MERCHANTABILITY or FITNESS FOR A * PARTICULAR PURPOSE. See the GNU General Public License for more * details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 * USA. */ package org.hyperic.hq.bizapp.agent.client; import org.apache.commons.logging.Log; import org.apache.commons.logging.LogFactory; import org.apache.log4j.*; import org.hyperic.hq.agent.*; import org.hyperic.hq.agent.client.AgentCommandsClient; import org.hyperic.hq.agent.client.LegacyAgentCommandsClientImpl; import org.hyperic.hq.agent.server.AgentDaemon; import org.hyperic.hq.agent.server.AgentDaemon.RunnableAgent; import org.hyperic.hq.agent.server.LoggingOutputStream; import org.hyperic.hq.bizapp.agent.ProviderInfo; import org.hyperic.hq.bizapp.agent.commands.CreateToken_args; import org.hyperic.hq.bizapp.agent.commands.CreateToken_result; import org.hyperic.hq.bizapp.client.*; import org.hyperic.hq.common.shared.ProductProperties; import org.hyperic.sigar.*; import org.hyperic.util.PropertyEncryptionUtil; import org.hyperic.util.PropertyUtil; import org.hyperic.util.StringUtil; import org.hyperic.util.security.SecurityUtil; import org.tanukisoftware.wrapper.WrapperManager; import sun.misc.Signal; import sun.misc.SignalHandler; import javax.net.ssl.SSLPeerUnverifiedException; import java.io.*; import java.net.*; import java.util.HashMap; import java.util.Map; import java.util.Properties; /** * This class provides the command line entry point into dealing with * the agent. */ public class AgentClient { private static final PrintStream SYSTEM_ERR = System.err; private static final PrintStream SYSTEM_OUT = System.out; private static final String PRODUCT = "HQ"; // The following QPROP_* defines are properties which can be // placed in the agent properties file to perform automatic setup private static final String QPROP_PRE = "agent.setup."; public static final String QPROP_IPADDR = QPROP_PRE + "camIP"; public static final String QPROP_PORT = QPROP_PRE + "camPort"; public static final String QPROP_SSLPORT = QPROP_PRE + "camSSLPort"; public static final String QPROP_NEWTRANSPORT = QPROP_PRE + "newTransport"; public static final String QPROP_UNI = QPROP_PRE + "unidirectional"; public static final String QPROP_UNI_POLLING_FREQUENCY = QPROP_PRE + "uniPollingFrequency"; private static final String QPROP_SECURE = QPROP_PRE + "camSecure"; private static final String QPROP_LOGIN = QPROP_PRE + "camLogin"; private static final String QPROP_PWORD = QPROP_PRE + "camPword"; private static final String QPROP_AGENTIP = QPROP_PRE + "agentIP"; private static final String QPROP_AGENTPORT = QPROP_PRE + "agentPort"; private static final String QPROP_RESETUPTOK = QPROP_PRE + "resetupTokens"; private static final String QPROP_TIMEOUT = QPROP_PRE + "serverTimeout"; private static final String DEFAULT_LOG_LEVEL = "INFO"; private static final String LOG_PATTERN_LAYOUT = "%d %-5p [%t] [%c{1}] %m%n"; private static final int BUFFER_SIZE = 1024; private static final String MAX_FILE_SIZE = "5000KB"; private static final String MAX_FILES = "1"; private static final String PROP_LOGFILE = "agent.logFile"; private static final String PROP_STARTUP_TIMEOUT = "agent.startupTimeOut"; private static final String PROP_FQDN = "platform.fqdn"; private static final String AGENT_CLASS = "org.hyperic.hq.agent.server.AgentDaemon"; private static final int AGENT_STARTUP_TIMEOUT = (60 * 5) * 1000; // 5 min private static final int FORCE_SETUP = -42; private static final String JAAS_CONFIG = "jaas.config"; private final AgentCommandsClient agtCommands; private final CommandsClient camCommands; private final AgentConfig config; private String sslHandlerPkg; private final Log log; private boolean nuking; private boolean redirectedOutputs = false; private static Thread agentDaemonThread; private final static String PING = "ping"; private final static String DIE = "die"; private final static String START = "start"; private final static String STATUS = "status"; private final static String RESTART = "restart"; private final static String SETUP = "setup"; private final static String SETUP_IF_NO_PROVIDER = "setup-if-no-provider"; private final static String SET_PROPERTY = "set-property"; private AgentClient(AgentConfig config, SecureAgentConnection conn){ this.agtCommands = new LegacyAgentCommandsClientImpl(conn); this.camCommands = new CommandsClient(conn); this.config = config; this.log = LogFactory.getLog(AgentClient.class); this.nuking = false; } private long cmdPing(int numAttempts) throws AgentConnectionException, AgentRemoteException { AgentConnectionException lastExc; lastExc = new AgentConnectionException("Failed to connect to agent"); while(numAttempts-- != 0){ try { return this.agtCommands.ping(); } catch(AgentConnectionException exc){ // Loop around to the next attempt lastExc = exc; } try { if (numAttempts > 0) { Thread.sleep(1000); } } catch(InterruptedException exc){ throw new AgentConnectionException("Connection interrupted"); } } throw lastExc; }//cmdPing private void cmdStatus() throws AgentConnectionException, AgentRemoteException { ProviderInfo pInfo; String address; String currentAgentBundle; try { currentAgentBundle = this.agtCommands.getCurrentAgentBundle(); pInfo = this.camCommands.getProviderInfo(); } catch(AgentConnectionException exc){ SYSTEM_ERR.println("Unable to contact agent: " + exc.getMessage()); return; } catch(AgentRemoteException exc){ SYSTEM_ERR.println("Error executing remote method: " + exc.getMessage()); return; } SYSTEM_OUT.println("Current agent bundle: "+currentAgentBundle); if(pInfo == null || (address = pInfo.getProviderAddress()) == null){ SYSTEM_OUT.println("Agent not yet setup"); return; } try { String proto; URL url = new URL(address); SYSTEM_OUT.println("Server IP address: " + url.getHost()); proto = url.getProtocol(); if(proto.equalsIgnoreCase("https")) SYSTEM_OUT.print("Server (SSL) port: "); else SYSTEM_OUT.print("Server port: "); SYSTEM_OUT.println(url.getPort()); if (pInfo.isNewTransport()) { SYSTEM_OUT.println("Using new transport; unidirectional="+ pInfo.isUnidirectional()); } } catch(Exception exc){ SYSTEM_OUT.println("Unable to parse provider info (" + address + "): " + exc.getMessage()); } SYSTEM_OUT.println("Agent listen port: " + this.config.getListenPort()); if (this.config.isProxyServerSet()) { SYSTEM_OUT.println("Proxy server IP address: "+this.config.getProxyIp()); SYSTEM_OUT.println("Proxy server port: "+this.config.getProxyPort()); } } private void cmdDie(int waitTime) throws AgentConnectionException, AgentRemoteException { try { this.agtCommands.die(); } catch(AgentConnectionException exc){ return; // If we can't connect then we know the agent is dead } catch(AgentRemoteException exc){ throw new AgentRemoteException("Error making remote agent call: "+ exc.getMessage()); } // Loop waiting to see if it died before returning while(waitTime-- != 0){ try { this.agtCommands.ping(); } catch(AgentConnectionException exc){ return; // Success! } catch(AgentRemoteException exc){ exc.printStackTrace(SYSTEM_ERR); throw exc; // Something bizarro occurred } try { Thread.sleep(1000); } catch(InterruptedException exc){ throw new AgentConnectionException("Connection interrupted"); } } throw new AgentRemoteException("Unable to kill agent within timeout"); } private void cmdRestart() throws AgentConnectionException, AgentRemoteException { try { this.agtCommands.restart(); } catch(AgentConnectionException exc){ throw new AgentConnectionException("Unable to connect to agent: " + "already dead?"); } catch(AgentRemoteException exc){ throw new AgentRemoteException("Error making remote agent call: "+ exc.getMessage()); } } private class AutoQuestionException extends Exception { AutoQuestionException(String s){ super(s); } } private String askQuestion(String question, String def, boolean invis, String questionProp) throws IOException { BufferedReader in; String res, bootProp; bootProp = this.config.getBootProperties().getProperty(questionProp); while(true){ SYSTEM_OUT.print(question); if(def != null){ SYSTEM_OUT.print(" [default=" + def + "]"); } SYSTEM_OUT.print(": "); if(invis){ if(bootProp != null){ SYSTEM_OUT.println("**Not echoing value**"); return bootProp; } return Sigar.getPassword(""); } else { if(bootProp != null){ if(bootProp.equals("*default*") && def != null){ bootProp = def; } SYSTEM_OUT.println(bootProp); return bootProp; } in = new BufferedReader(new InputStreamReader(System.in)); if((res = in.readLine()) != null){ res = res.trim(); if(res.length() == 0){ res = null; } } if(res == null){ if(def != null){ return def; } } else { return res; } } } } private String askQuestion(String question, String def, String questionProp) throws IOException { return this.askQuestion(question, def, false, questionProp); } private boolean askYesNoQuestion(String question, boolean def, String questionProp) throws IOException, AutoQuestionException { boolean isAuto; isAuto = this.config.getBootProperties().getProperty(questionProp) != null; while(true){ String res; res = this.askQuestion(question, def ? "yes" : "no", questionProp); if(res.equalsIgnoreCase("yes") || res.equalsIgnoreCase("y")) { return true; } else if(res.equalsIgnoreCase("no") || res.equalsIgnoreCase("n")) { return false; } if(isAuto){ throw new AutoQuestionException("Property '" + questionProp + "' must be 'yes' or " + "'no'"); } SYSTEM_OUT.println("- Value must be 'yes' or 'no'"); } } private int askIntQuestion(String question, int def, String questionProp) throws IOException, AutoQuestionException { boolean isAuto; isAuto = this.config.getBootProperties().getProperty(questionProp) != null; while(true){ String res; int iVal; res = this.askQuestion(question, Integer.toString(def), questionProp); try { iVal = Integer.parseInt(res); return iVal; } catch(NumberFormatException exc){ if(isAuto){ throw new AutoQuestionException("Property '" + questionProp +"' must be a valid integer"); } SYSTEM_OUT.println("- Value must be an integer"); } } } private BizappCallbackClient testProvider(String provider, final boolean acceptUnverifiedCertificates) throws AgentCallbackClientException { StaticProviderFetcher fetcher; BizappCallbackClient res; fetcher = new StaticProviderFetcher(new ProviderInfo(provider, "no-auth")); res = new BizappCallbackClient(fetcher, config); res.bizappPing(acceptUnverifiedCertificates); return res; } /** * Test the connection information. */ private BizappCallbackClient getConnection(String provider, boolean secure) throws AutoQuestionException, AgentCallbackClientException { BizappCallbackClient bizapp; Properties bootP = this.config.getBootProperties(); long start = System.currentTimeMillis(); boolean acceptUnverifiedCertificates = secure; while (true) { String sec = secure ? "secure" : "insecure"; SYSTEM_OUT.print("- Testing " + sec + " connection ... "); try { log.info("test connection with accept unverified certificates flag set to "+acceptUnverifiedCertificates); bizapp = this.testProvider(provider, acceptUnverifiedCertificates); SYSTEM_OUT.println("Success"); return bizapp; } catch (AgentCallbackClientException exc) { // ...reset to false just to be safe... acceptUnverifiedCertificates = false; String msg = exc.getMessage(); // ...check if there's a SSL exception... if (exc.getExceptionOfType(SSLPeerUnverifiedException.class) != null) { SYSTEM_OUT.println(); SYSTEM_OUT.println(exc.getMessage()); log.error(exc,exc); String question = "Are you sure you want to continue connecting?"; try { if (askYesNoQuestion(question, false, "agent.setup.acceptUnverifiedCertificate")) { acceptUnverifiedCertificates = true; // try again continue; } } catch(IOException ioe) { log.debug(ioe.getMessage()); } } if (msg.indexOf("is still starting") != -1) { SYSTEM_ERR.println("HQ is still starting (retrying in 10 seconds)"); try { Thread.sleep(10 * 1000); } catch (InterruptedException e) {} // Try again continue; } // Check for configured server timeout String propTimeout = bootP.getProperty(QPROP_TIMEOUT); if (propTimeout != null) { long timeout; try { timeout = Integer.parseInt(propTimeout) * 1000; } catch(NumberFormatException nfe){ // If the timeout is improperly configured, // bail out throw new AutoQuestionException("Mis-configured" + QPROP_TIMEOUT + "property: " + propTimeout); } SYSTEM_ERR.println("Failure (retrying in 10 seconds)"); if (start + timeout > System.currentTimeMillis()) { try { Thread.sleep(10 * 1000); } catch (InterruptedException ie) {} // Try again continue; } } SYSTEM_ERR.println("Failure"); if (bootP.getProperty(QPROP_IPADDR) != null || bootP.getProperty(QPROP_PORT) != null || bootP.getProperty(QPROP_SSLPORT) != null) { throw new AutoQuestionException("Unable to connect to " + PRODUCT); } throw exc; } } } private static int getCpuCount () throws SigarException { Sigar sigar = new Sigar(); try { return sigar.getCpuInfoList().length; } finally { sigar.close(); } } private String getDefaultIpAddress() { String address; final String loopback = "127.0.0.1"; try { address = InetAddress.getLocalHost().getHostAddress(); if (!loopback.equals(address)) { return address; } } catch(UnknownHostException e) { //hostname not in DNS or /etc/hosts } Sigar sigar = new Sigar(); try { address = sigar.getNetInterfaceConfig().getAddress(); } catch (SigarException e) { address = loopback; } finally { sigar.close(); } return address; } private void cmdSetupIfNoProvider() throws AgentConnectionException, AgentRemoteException, IOException, AutoQuestionException { Properties bootProps = this.config.getBootProperties(); int timeout = getStartupTimeout(bootProps); // Sleep until the agent is started this.cmdPing(timeout / 1000); // Prompt the agent to setup if the provider info is not specified. ProviderInfo providerInfo = this.camCommands.getProviderInfo(); if (providerInfo == null) { this.cmdSetup(); } } private void cmdSetup() throws AgentConnectionException, AgentRemoteException, IOException, AutoQuestionException { BizappCallbackClient bizapp; InetAddress localHost; CreateToken_result tokenRes; ProviderInfo providerInfo; String provider, host, user, pword, agentToken, response; String agentIP; Properties bootP; int agentPort = -1; boolean isNewTransportAgent = false; boolean unidirectional = false; int unidirectionalPort = -1; bootP = this.config.getBootProperties(); try { this.cmdPing(1); } catch(AgentConnectionException exc){ SYSTEM_ERR.println("Unable to setup agent: " + exc.getMessage()); SYSTEM_ERR.println("The Agent must be running prior to running " + SETUP); return; } SYSTEM_OUT.println("[ Running agent setup ]"); // FIXME - For now we will only setup the new transport if the // unidirectional agent is available (since currently we don't // have a bidirectional agent). Once we have a bidirectional // agent, we should always ask if agent communications should // use the new transport. // isNewTransportAgent = askYesNoQuestion("Should Agent communications to " + // PRODUCT + " use the new transport ", // false, QPROP_NEWTRANSPORT); if (isUnidirectionalAgentSupported()) { unidirectional = askYesNoQuestion("Should Agent communications to " + PRODUCT + " be unidirectional", false, QPROP_UNI); // FIXME For now enable the new transport only if we have // a unidirectional agent. isNewTransportAgent = unidirectional; } boolean secure; int port; while(true){ host = this.askQuestion("What is the " + PRODUCT + " server IP address", null, QPROP_IPADDR); secure = askYesNoQuestion("Should Agent communications " + "to " + PRODUCT + " always " + "be secure", true, QPROP_SECURE); if (secure) { // Always secure. Ask for SSL port and verify port = this.askIntQuestion("What is the " + PRODUCT + " server SSL port", 7443, QPROP_SSLPORT); provider = AgentCallbackClient.getDefaultProviderURL(host, port, true); } else { // Never secure. Only ask for non-ssl port and verify port = this.askIntQuestion("What is the " + PRODUCT + " server port ", 7080, QPROP_PORT); provider = AgentCallbackClient.getDefaultProviderURL(host, port, false); } try { bizapp = getConnection(provider, secure); } catch (AgentCallbackClientException e) { continue; } break; } if (unidirectional) { // workaround to uniquely identify an agent based on host and port combination // we set the agent port to the hashcode of the FQDN if specified // note that this is not actually a valid port number but rather used as an identifier // during agent registration to support multiple unidirectional agents on same host String fqdn = bootP.getProperty(PROP_FQDN); if (fqdn != null) { agentPort = fqdn.hashCode(); } if (secure) { unidirectionalPort = port; } else { // unidirectional only uses secure communication. Ask for SSL port and verify while(true){ unidirectionalPort = this.askIntQuestion("What is the " + PRODUCT + " server SSL port for unidirectional communications", 7443, QPROP_SSLPORT); // The unidirectional transport is not hosted via the // Lather servlet, but this is ok, since the // undirectional servlet is in the same container as // the Lather servlet. We are just testing connectivity // to the servlet container here. String unidirectionalProvider = AgentCallbackClient.getDefaultProviderURL(host, unidirectionalPort, true); try { getConnection(unidirectionalProvider, true); } catch (AgentCallbackClientException e) { continue; } break; } } } while(true){ user = this.askQuestion("What is your " + PRODUCT + " login", "hqadmin", QPROP_LOGIN); pword = this.askQuestion("What is your " + PRODUCT + " password", null, true, QPROP_PWORD); try { if(bizapp.userIsValid(user, pword)) { break; } } catch(AgentCallbackClientException exc){ log.error(exc,exc); SYSTEM_ERR.println("Error validating user: " + exc); return; } SYSTEM_ERR.println("- Invalid username/password"); if (bootP.getProperty(QPROP_LOGIN) != null || bootP.getProperty(QPROP_PWORD) != null) { throw new AutoQuestionException("Invalid username/password"); } } // Get info about agent while(true){ String question; if (unidirectional) { question = "What is the agent IP address"; } else { question = "What IP should "+PRODUCT+" use to contact the agent"; } agentIP = this.askQuestion(question, getDefaultIpAddress(), QPROP_AGENTIP); // Attempt to resolve, as a safeguard try { localHost = InetAddress.getByName(agentIP); localHost.getHostAddress(); break; } catch(UnknownHostException exc){ SYSTEM_ERR.println("- Unable to resolve host"); } } if (!unidirectional) { while(true){ int listenPort = this.config.getListenPort(); agentPort = this.askIntQuestion("What port should " + PRODUCT + " use to contact the agent", listenPort, QPROP_AGENTPORT); if(agentPort < 1 || agentPort > 65535){ SYSTEM_ERR.println("- Invalid port"); } else { if (agentPort!= listenPort){ SYSTEM_ERR.println("- To setup agent port to "+ agentPort + "," + " Stop the agent," + " Update agent properties" + " for agent.listenPort and start" + " the agent again"); SYSTEM_OUT.println("- Now Agent uses the default port:" +listenPort); agentPort = listenPort; } break; } } } // The old agent token may be needed if re-registering an existing agent // but changing from non-unidirectional to unidirectional transport. // In this case, the agent port will change, so we will need to lookup // the agent by the old agent token instead of agent IP and port. String oldAgentToken = null; /* Check to see if this agent already has a setup for a server. If it does, allow the user to re-register with the new IP address */ if((providerInfo = this.camCommands.getProviderInfo()) != null && providerInfo.getProviderAddress() != null && providerInfo.getAgentToken() != null) { oldAgentToken = providerInfo.getAgentToken(); boolean setupTokens; SYSTEM_OUT.println("- Agent is already setup for " + PRODUCT + " @ " + providerInfo.getProviderAddress()); setupTokens = this.askYesNoQuestion("Would you like to re-setup the auth " + "tokens", false, QPROP_RESETUPTOK); if(setupTokens == false){ // Here we basically just need to inform the server that the // agent with a given AgentToken will re-use that, but // with a different IP address SYSTEM_OUT.println("- Informing " + PRODUCT + " about agent setup changes"); boolean acceptUnverifiedCertificates = false; while(true) { try { response = bizapp.updateAgent(providerInfo.getAgentToken(), user, pword, agentIP, agentPort, isNewTransportAgent, unidirectional, acceptUnverifiedCertificates); if (response != null) { if (response.contains("java.security.cert.CertificateException")) { String question = "The server to agent communication channel is using a self-signed certificate and can not be verified" + "\nAre you sure you want to continue connecting?"; try { if (askYesNoQuestion(question, false, "agent.setup.acceptUnverifiedCertificate")) { acceptUnverifiedCertificates = true; // try again continue; } } catch(IOException ioe) { log.debug(ioe.getMessage()); } } SYSTEM_ERR.println("- Error updating agent: " + response); } break; } catch(Exception exc){ SYSTEM_ERR.println("- Error updating agent: " + exc.getMessage()); return; } } if (providerInfo.isNewTransport()!=isNewTransportAgent || providerInfo.isUnidirectional()!=unidirectional) { ProviderInfo registeredProviderInfo = new ProviderInfo(provider, providerInfo.getAgentToken()); if (isNewTransportAgent) { registeredProviderInfo.setNewTransport(unidirectional, unidirectionalPort); } this.camCommands.setProviderInfo(registeredProviderInfo); } return; } } // Ask agent for a new connection token try { InetAddress.getByName(host); } catch(UnknownHostException exc){ SYSTEM_ERR.println("Unable to resolve provider (strange): " + exc.getMessage()); return; } tokenRes = this.camCommands.createToken(new CreateToken_args()); SYSTEM_OUT.println("- Received temporary auth token from agent"); // Ask server to verify agent SYSTEM_OUT.println("- Registering agent with " + PRODUCT); RegisterAgentResult result; boolean acceptUnverifiedCertificates = false; while(true) { try { result = bizapp.registerAgent(oldAgentToken, user, pword, tokenRes.getToken(), agentIP, agentPort, ProductProperties.getVersion(), getCpuCount(), isNewTransportAgent, unidirectional, acceptUnverifiedCertificates); response = result.response; if(!response.startsWith("token:")) { if (response.contains("java.security.cert.CertificateException")) { String question = "The server to agent communication channel is using a self-signed certificate and could not be verified" + "\nAre you sure you want to continue connecting?"; try { if (askYesNoQuestion(question, false, "agent.setup.acceptUnverifiedCertificate")) { acceptUnverifiedCertificates = true; // try again continue; } } catch(IOException ioe) { log.debug(ioe.getMessage()); } } SYSTEM_ERR.println("- Unable to register agent: " + response); return; } // Else the bizapp responds with the token that the agent needs // to use to contact it agentToken = response.substring("token:".length()); break; } catch(Exception exc){ exc.printStackTrace(SYSTEM_ERR); SYSTEM_ERR.println("- Error registering agent: "+exc.getMessage()); } } SYSTEM_OUT.println("- " + PRODUCT + " gave us the following agent token"); SYSTEM_OUT.println(" " + agentToken); SYSTEM_OUT.println("- Informing agent of new " + PRODUCT + " server"); ProviderInfo registeredProviderInfo = new ProviderInfo(provider, agentToken); if (isNewTransportAgent) { registeredProviderInfo.setNewTransport(unidirectional, unidirectionalPort); } this.camCommands.setProviderInfo(registeredProviderInfo); SYSTEM_OUT.println("- Validating"); providerInfo = this.camCommands.getProviderInfo(); if(providerInfo == null || providerInfo.getProviderAddress().equals(provider) == false || providerInfo.getAgentToken().equals(agentToken) == false) { if(providerInfo == null){ SYSTEM_ERR.println(" - Failure - Agent is reporting no " + "" + PRODUCT + " provider information"); } else { SYSTEM_ERR.println("- Failure - Agent is using " + PRODUCT + " server '" + providerInfo.getProviderAddress() + "' with token '" + providerInfo.getAgentToken() + "'"); } } else { SYSTEM_OUT.println("- Successfully setup agent"); if (providerInfo.isNewTransport()) { String unidirectionalPortString = ""; if (providerInfo.isUnidirectional()) { unidirectionalPortString = ", port="+ providerInfo.getUnidirectionalPort(); } SYSTEM_OUT.println("- Agent using new transport, unidirectional="+ providerInfo.isUnidirectional()+ unidirectionalPortString); } } redirectOutputs(bootP); //win32 } private boolean isUnidirectionalAgentSupported() { // TODO: Ideally, we should be able to check for the existence of a // .com class by calling TransportUtils.tryLoadUnidirectionalTransportPollerClient() // but there is some class loader issue with an EE agent. As a workaround, // in HQ 4.5, we will just check for the existence of an EE agent jar. boolean isUnidirectionalSupported = false; String libPath = AgentConfig.PROP_BUNDLEHOME[1] + "/lib"; try { File libDir = new File(libPath); if (libDir.isDirectory()) { File[] libFiles = libDir.listFiles(); for (int i=0; i<libFiles.length; i++) { String fileName = libFiles[i].getName().toLowerCase(); if (fileName.startsWith("hqee-agent") && fileName.endsWith(".jar")) { isUnidirectionalSupported = true; break; } } } } catch (Exception e) { log.info("Could not determine whether the agent supports " + "unidirectional transport: " + e.getMessage(), e); } return isUnidirectionalSupported; } private void verifyAgentRunning(ServerSocket startupSock) throws AgentInvokeException { try { Socket conn = startupSock.accept(); DataInputStream dIs = new DataInputStream(conn.getInputStream()); if(dIs.readInt() != 1){ throw new AgentInvokeException("Agent reported an error " + "while starting up"); } } catch(InterruptedIOException exc){ throw new AgentInvokeException("Timed out waiting for Agent " + "to report startup success"); } catch(IOException exc){ throw new AgentInvokeException("Agent failure while starting"); } finally { try { startupSock.close(); } catch(IOException exc){} } try { this.agtCommands.ping(); } catch(Exception exc){ throw new AgentInvokeException("Unable to ping agent: " + exc.getMessage()); } } private void nukeAgentAndDie(){ synchronized(this){ if(this.nuking){ return; } this.nuking = true; } try { SYSTEM_ERR.println("Received interrupt while starting. " + "Shutting agent down ..."); this.cmdDie(10); } catch (Exception e){ } System.exit(-1); } private void handleSIGINT() { try { Signal.handle(new Signal("INT"), new SignalHandler() { public void handle(Signal sig) { nukeAgentAndDie(); } }); } catch(Exception e) { // avoid "Signal already used by VM: SIGINT", e.g. ibm jdk } } private PrintStream newLogStream(String stream, Properties bootProps) throws AgentConfigException, IOException { Logger logger = Logger.getLogger(stream); Level level = Level.toLevel(bootProps.getProperty("agent.startup.logLevel." + stream, bootProps.getProperty("agent.logLevel." + stream, DEFAULT_LOG_LEVEL))); PatternLayout layout = new PatternLayout(bootProps.getProperty("agent.startup.ConversionPattern", LOG_PATTERN_LAYOUT)); RollingFileAppender fileAppender = new RollingFileAppender(layout, getStartupLogFile(bootProps), true); fileAppender.setImmediateFlush(true); fileAppender.setBufferedIO(false); fileAppender.setBufferSize(BUFFER_SIZE); fileAppender.setMaxFileSize(bootProps.getProperty("agent.startup.MaxFileSize", MAX_FILE_SIZE)); fileAppender.setMaxBackupIndex(Integer.parseInt(bootProps.getProperty("agent.startup.MaxBackupIndex", MAX_FILES))); logger.addAppender(fileAppender); logger.setAdditivity(false); return new PrintStream(new LoggingOutputStream(logger, level), true); } private void redirectOutputs(Properties bootProp) { if (this.redirectedOutputs) { return; } this.redirectedOutputs = true; try { System.setErr(newLogStream("SystemErr", bootProp)); System.setOut(newLogStream("SystemOut", bootProp)); } catch (Exception e) { e.printStackTrace(SYSTEM_ERR); } } public static Thread getAgentDaemonThread() { return agentDaemonThread; } private int cmdStart(boolean force) throws AgentInvokeException { ServerSocket startupSock; ProviderInfo providerInfo; Properties bootProps; // Try to ping the agent one time to see if the agent is already up try { this.cmdPing(1); SYSTEM_OUT.println("Agent already running"); return -1; } catch(AgentConnectionException exc){ // Normal operation } catch(AgentRemoteException exc){ // Very nearly a normal operation } bootProps = this.config.getBootProperties(); try { int iSleepTime = getStartupTimeout(bootProps); startupSock = new ServerSocket(0); startupSock.setSoTimeout(iSleepTime); } catch(IOException e){ AgentInvokeException ex = new AgentInvokeException("Unable to setup a socket to listen for Agent startup: " + e); ex.initCause(e); throw ex; } SYSTEM_OUT.println("- Invoking agent"); try { this.config.setNotifyUpPort(startupSock.getLocalPort()); } catch (AgentConfigException e) { throw new AgentInvokeException("Invalid notify up port: "+startupSock.getLocalPort()); } RunnableAgent runnableAgent = new AgentDaemon.RunnableAgent(this.config); agentDaemonThread = new Thread(runnableAgent); agentDaemonThread.setName("AgentDaemonMain"); AgentUpgradeManager.setAgentDaemonThread(agentDaemonThread); AgentUpgradeManager.setAgent(runnableAgent); agentDaemonThread.setDaemon(true); agentDaemonThread.start(); SYSTEM_OUT.println("- Agent thread running"); /* Now comes the painful task of figuring out if the agent started correctly. */ SYSTEM_OUT.println("- Verifying if agent is running..."); this.verifyAgentRunning(startupSock); SYSTEM_OUT.println("- Agent is running"); // Ask the agent if they have a server setup try { providerInfo = this.camCommands.getProviderInfo(); } catch(Exception exc){ // This should rarely (never) occur, since we just ensured things // were operational. throw new AgentInvokeException("Unexpected connection exception: "+ "agent is still running"); } SYSTEM_OUT.println("Agent successfully started"); // Only force a setup if we are not running the agent in Java Service Wrapper mode if(providerInfo == null && !WrapperManager.isControlledByNativeWrapper()){ SYSTEM_OUT.println(); return FORCE_SETUP; } else { redirectOutputs(bootProps); //win32 return 0; } }//cmdStart private static void cmdSetProp(String propKey, String propVal) throws AgentConfigException { ensurePropertiesEncryption(); try { String propEncKey = PropertyEncryptionUtil.getPropertyEncryptionKey(AgentConfig.DEFAULT_PROP_ENC_KEY_FILE); String propFile = System.getProperty(AgentConfig.PROP_PROPFILE,AgentConfig.DEFAULT_PROPFILE); Map<String,String> entriesToStore = new HashMap<String,String>(); entriesToStore.put(propKey, propVal); PropertyUtil.storeProperties(propFile, propEncKey, entriesToStore); } catch (Exception exc) { throw new AgentConfigException(exc); } } private String getStartupLogFile(Properties bootProps) throws AgentConfigException { String logFile; if((logFile = bootProps.getProperty(PROP_LOGFILE)) == null){ throw new AgentConfigException(PROP_LOGFILE + " is undefined"); } return logFile + ".startup"; } // returns the startup timeout in milliseconds private static int getStartupTimeout(Properties bootProps) { int iSleepTime = AGENT_STARTUP_TIMEOUT; String sleepTime = bootProps.getProperty(PROP_STARTUP_TIMEOUT); try { iSleepTime = Integer.parseInt(sleepTime) * 1000; } catch(NumberFormatException exc){ // do nothing - keep default } return iSleepTime; } private static int getUseTime(String val){ try { return Integer.parseInt(val); } catch(NumberFormatException exc){ return 1; } } /** * Initialize the AgentClient * * @param generateToken If set to true, generate the agent token, otherwise * wait until the tokens are available. * * @return An initialized AgentClient */ private static AgentClient initializeAgent(boolean generateToken) throws AgentConfigException { ensurePropertiesEncryption(); SecureAgentConnection conn; AgentConfig cfg; String connIp, listenIp, authToken; final String propFile = System.getProperty(AgentConfig.PROP_PROPFILE, AgentConfig.DEFAULT_PROPFILE); //console appender until we have configured logging. BasicConfigurator.configure(); try { cfg = AgentConfig.newInstance(propFile); } catch(IOException exc){ SYSTEM_ERR.println("Error: " + exc); return null; } catch(AgentConfigException exc){ SYSTEM_ERR.println("Agent Properties error: " + exc.getMessage()); return null; } //we wait until AgentConfig.newInstance has merged //all properties to configure logging. Properties bootProps = cfg.getBootProperties(); if (!checkCanWriteToLog(bootProps)) { return null; } PropertyConfigurator.configure(bootProps); FileWatcherThread watcherThread = FileWatcherThread.getInstance(); FileWatcher loggingWatcher = new FileWatcher(new Sigar()) { { File[] files = AgentConfig.getPropertyFiles(propFile); for (int i = 0; i < files.length; i++) { try { add(files[i]); } catch (SigarException e) { SYSTEM_ERR.println("Error adding watcher for " + files[i]); } } setInterval(60000); } @Override public void onChange(FileInfo fileInfo) { try { SYSTEM_OUT.println("Change detected in " + fileInfo.getName() + ", reloading logging configuration"); PropertyConfigurator.configure(AgentConfig.getProperties(propFile)); } catch (AgentConfigException e) { SYSTEM_ERR.println("Error reloading logging configuration: " + e.getMessage()); e.printStackTrace(SYSTEM_ERR); } } }; watcherThread.add(loggingWatcher); watcherThread.doStart(); listenIp = cfg.getListenIp(); try { if(listenIp.equals(AgentConfig.IP_GLOBAL)){ connIp = "127.0.0.1"; } else { connIp = InetAddress.getByName(listenIp).getHostAddress(); } } catch(UnknownHostException exc){ SYSTEM_ERR.println("Failed to lookup agent address '" + listenIp + "'"); return null; } AgentKeystoreConfig keystoreConfig =new AgentKeystoreConfig(); String tokenFile = cfg.getTokenFile(); if (generateToken) { try { authToken = AgentClientUtil.getLocalAuthToken(tokenFile); } catch(FileNotFoundException exc){ SYSTEM_ERR.print("- Unable to load agent token file. Generating" + " a new one ... "); try { String nToken = SecurityUtil.generateRandomToken(); AgentClientUtil.generateNewTokenFile(tokenFile, nToken); authToken = AgentClientUtil.getLocalAuthToken(tokenFile); } catch(IOException oexc){ SYSTEM_ERR.println("Unable to setup preliminary agent auth " + "tokens: " + exc.getMessage()); return null; } SYSTEM_ERR.println("Done"); } catch(IOException exc){ SYSTEM_ERR.println("Unable to get necessary authentication tokens"+ " to talk to agent: " + exc.getMessage()); return null; } conn = new SecureAgentConnection(connIp, cfg.getListenPort(), authToken, keystoreConfig, keystoreConfig.isAcceptUnverifiedCert()); // TODO need to figure out where the connection should be closed AND close it! }:^( return new AgentClient(cfg, conn); } else { // Not the main agent daemon process, wait for the token to become // available. We will only wait up to the configured agent.startupTimeOut long initializeStartTime = System.currentTimeMillis(); long startupTimeout = getStartupTimeout(bootProps); while (initializeStartTime > (System.currentTimeMillis() - startupTimeout)) { try { authToken = AgentClientUtil.getLocalAuthToken(tokenFile); conn = new SecureAgentConnection(connIp, cfg.getListenPort(), authToken, keystoreConfig, keystoreConfig.isAcceptUnverifiedCert()); // TODO need to figure out where the connection should be closed AND close it! }:^( return new AgentClient(cfg, conn); } catch(FileNotFoundException exc){ SYSTEM_ERR.println("- No token file found, waiting for " + "Agent to initialize"); try { Thread.sleep(2000); } catch (InterruptedException e) { SYSTEM_ERR.println("Interrupted! Shutting down"); return null; } } catch(IOException e) { SYSTEM_ERR.println("Unable to read preliminary agent auth " + "tokens, waiting for Agent to initialize " + "(error was: " + e.getMessage() + ")"); } } SYSTEM_ERR.println("Timeout waiting for token file"); return null; } } public static void main(String args[]) { if(args.length==3 && args[0].equals(SET_PROPERTY)){ try { cmdSetProp(args[1],args[2]); } catch (AgentConfigException e) { SYSTEM_ERR.println("Error: " + e.getMessage()); e.printStackTrace(SYSTEM_ERR); } return; } if(args.length < 1 || !(args[0].equals(PING) || args[0].equals(DIE) || args[0].equals(START) || args[0].equals(STATUS) || args[0].equals(RESTART) || args[0].equals(SETUP) || args[0].equals(SETUP_IF_NO_PROVIDER))) { SYSTEM_ERR.println("Syntax: program " + "<" + PING + " [numAttempts] | " + DIE + " [dieTime] | " + START + " | " + STATUS + " | " + RESTART + " | " + SETUP + " | " + SETUP_IF_NO_PROVIDER + " | " + SET_PROPERTY + " >"); return; } AgentClient client; try { if (args[0].equals(START)) { PropertyEncryptionUtil.unlock(true);//file state should be 'unlocked' when the agent starts. // Only generate tokens on agent startup. client = initializeAgent(true); } else { client = initializeAgent(false); } if (client == null) { return; } int nWait; if(args[0].equals(PING)){ if(args.length == 3){ nWait = getUseTime(args[2]); } else { nWait = 1; } client.cmdPing(nWait); } else if(args[0].equals(DIE)){ if(args.length == 2){ nWait = getUseTime(args[1]); } else { nWait = 1; } SYSTEM_OUT.println("Stopping agent ... "); try { client.cmdDie(nWait); SYSTEM_OUT.println("Success -- agent is stopped!"); } catch(Exception exc){ SYSTEM_OUT.println("Failed to stop agent: " + exc.getMessage()); } } else if(args[0].equals(START)){ int errVal = client.cmdStart(false); if(errVal == FORCE_SETUP){ errVal = 0; client.cmdSetupIfNoProvider(); } } else if(args[0].equals(STATUS)){ client.cmdStatus(); } else if(args[0].equals(SETUP)){ client.cmdSetup(); } else if(args[0].equals(SETUP_IF_NO_PROVIDER)) { client.cmdSetupIfNoProvider(); } else if(args[0].equals(RESTART)){ client.cmdRestart(); } else throw new IllegalStateException("Unhandled condition"); } catch(AutoQuestionException exc){ SYSTEM_ERR.println("Unable to automatically setup: " + exc.getMessage()); } catch(AgentInvokeException exc){ SYSTEM_ERR.println("Error invoking agent: " + exc.getMessage()); } catch(AgentConnectionException exc){ SYSTEM_ERR.println("Error contacting agent: " + exc.getMessage()); } catch(AgentRemoteException exc){ SYSTEM_ERR.println("Error executing remote method: " + exc); } catch(Exception exc){ SYSTEM_ERR.println("Error: " + exc.getMessage()); exc.printStackTrace(SYSTEM_ERR); } } private static void ensurePropertiesEncryption() throws AgentConfigException { // Get the name of the agent properties file. final String propFile = System.getProperty(AgentConfig.PROP_PROPFILE, AgentConfig.DEFAULT_PROPFILE); // Make sure the properties are encrypted. AgentConfig.ensurePropertiesEncryption(propFile); } private static boolean checkCanWriteToLog (Properties props) { String logFileName = props.getProperty("agent.logFile"); if (logFileName == null) { SYSTEM_ERR.println("agent.logFile not set. " + "\nCannot start HQ agent."); return false; } File logFile = new File(logFileName); File logDir = logFile.getParentFile(); if (!logDir.exists()) { if (!logDir.mkdirs()) { SYSTEM_ERR.println("Log directory does not exist and " + "could not be created: " + logDir.getAbsolutePath() + "\nCannot start HQ agent."); return false; } } if (!logDir.canWrite()) { SYSTEM_ERR.println("Cannot write to log directory: " + logDir.getAbsolutePath() + "\nMake sure this directory is owned by user '" + System.getProperty("user.name") + "' and is " + "not a read-only directory." + "\nCannot start HQ agent."); return false; } if (logFile.exists() && !logFile.canWrite()) { SYSTEM_ERR.println("Cannot write to log file: " + logFile.getAbsolutePath() + "\nMake sure this file is owned by user '" + System.getProperty("user.name") + "' and is " + "not a read-only file." + "\nCannot start HQ agent."); return false; } return true; } /** * @param s A string that might contain unix-style classpath separators. * @return The correct path for this platform (i.e, if win32, replace : with ;). */ private static String normalizeClassPath(String s) { return StringUtil.replace(s, ":", File.pathSeparator); } }