org.hyperic.hq.bizapp.agent.client.AgentClient.java Source code

Java tutorial

Introduction

Here is the source code for org.hyperic.hq.bizapp.agent.client.AgentClient.java

Source

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