/*
* 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.server;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.security.KeyStore;
import java.security.KeyStoreException;
import java.util.Properties;
import javax.net.ssl.KeyManager;
import javax.net.ssl.KeyManagerFactory;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.agent.AgentAPIInfo;
import org.hyperic.hq.agent.AgentConfig;
import org.hyperic.hq.agent.AgentConfigException;
import org.hyperic.hq.agent.AgentKeystoreConfig;
import org.hyperic.hq.agent.AgentRemoteException;
import org.hyperic.hq.agent.AgentRemoteValue;
import org.hyperic.hq.agent.AgentStartupCallback;
import org.hyperic.hq.agent.server.AgentDaemon;
import org.hyperic.hq.agent.server.AgentNotificationHandler;
import org.hyperic.hq.agent.server.AgentServerHandler;
import org.hyperic.hq.agent.server.AgentStartException;
import org.hyperic.hq.agent.server.AgentStorageException;
import org.hyperic.hq.agent.server.AgentStorageProvider;
import org.hyperic.hq.bizapp.agent.CommandsAPIInfo;
import org.hyperic.hq.bizapp.agent.ProviderInfo;
import org.hyperic.hq.bizapp.agent.TokenData;
import org.hyperic.hq.bizapp.agent.TokenManager;
import org.hyperic.hq.bizapp.agent.TokenStorer;
import org.hyperic.hq.bizapp.agent.commands.CreateToken_args;
import org.hyperic.hq.bizapp.agent.commands.CreateToken_result;
import org.hyperic.hq.bizapp.agent.commands.GetServer_args;
import org.hyperic.hq.bizapp.agent.commands.GetServer_result;
import org.hyperic.hq.bizapp.agent.commands.SetServer_args;
import org.hyperic.hq.bizapp.agent.commands.SetServer_result;
import org.hyperic.util.security.KeystoreConfig;
import org.hyperic.util.security.KeystoreManager;
import org.hyperic.util.security.SecurityUtil;
public class CommandsServer
implements AgentServerHandler, TokenStorer, AgentNotificationHandler
{
private static final long TOKEN_TIMEOUT = 20 * 1000; // 20 seconds
private CommandsAPIInfo verAPI;
private Log log;
private TokenManager tokenManager;
private AgentStorageProvider storage;
private AgentDaemon agent;
private Socket startupSock;
private String tokenFile;
private String keyAlg;
private AgentStartupCallback agentStartupCallback;
public CommandsServer(){
this.verAPI = new CommandsAPIInfo();
this.log = LogFactory.getLog(CommandsServer.class);
this.tokenManager = null;
this.storage = null;
this.agent = null;
this.startupSock = null;
this.keyAlg = null;
this.agentStartupCallback = null;
}
public AgentAPIInfo getAPIInfo(){
return this.verAPI;
}
public String[] getCommandSet(){
return CommandsAPIInfo.commandSet;
}
public AgentRemoteValue dispatchCommand(String cmd, AgentRemoteValue args,
InputStream in, OutputStream out)
throws AgentRemoteException
{
if(cmd.equals(CommandsAPIInfo.command_createToken)){
return this.createToken(new CreateToken_args(args));
} else if(cmd.equals(CommandsAPIInfo.command_getServer)){
return this.getServer(new GetServer_args(args));
} else if(cmd.equals(CommandsAPIInfo.command_setServer)){
return this.setServer(new SetServer_args(args));
} else {
throw new AgentRemoteException("Unknown command: " + cmd);
}
}
private String getTokenFile() {
return this.tokenFile;
}
public OutputStream getTokenStoreStream()
throws IOException
{
return new FileOutputStream(getTokenFile());
}
private void storeTokens()
throws IOException
{
try {
this.tokenManager.store();
} catch(IOException exc){
this.log.error("Failed to store token data to '" +
getTokenFile() +
"': " + exc.getMessage());
throw exc;
}
}
private CreateToken_result createToken(CreateToken_args args){
CreateToken_result res;
TokenData newToken;
newToken = new TokenData(SecurityUtil.generateRandomToken(),
System.currentTimeMillis(), false);
res = new CreateToken_result();
res.setToken(newToken.getToken());
this.tokenManager.addToken(newToken);
try {
this.storeTokens();
} catch(IOException exc){
// ignore, since there is nothing we can do
}
return res;
}
private SetServer_result setServer(SetServer_args args){
SetServer_result res;
ProviderInfo provider;
provider = args.getProvider();
String providerMsg = "Setting the HQ server to: "+provider.getProviderAddress();
if (provider.isNewTransport()) {
providerMsg+="; unidirectional="+provider.isUnidirectional();
}
this.log.info(providerMsg);
CommandsAPIInfo.setProvider(this.storage, provider);
try {
this.storage.flush();
} catch(AgentStorageException exc){
this.log.error("Error flushing storage", exc);
}
res = new SetServer_result();
this.agent.sendNotification(CommandsAPIInfo.NOTIFY_SERVER_SET,
provider.getProviderAddress());
return res;
}
private GetServer_result getServer(GetServer_args args){
GetServer_result res;
ProviderInfo provider;
res = new GetServer_result();
res.setProvider(CommandsAPIInfo.getProvider(this.storage));
return res;
}
private KeyStore getKeyStoreInstance()
throws AgentStartException
{
final String KEYSTORE_TYPE = "JKS";
try {
return KeyStore.getInstance(KEYSTORE_TYPE);
} catch(KeyStoreException exc){
throw new AgentStartException("Unable to get keystore instance " +
"for " + KEYSTORE_TYPE);
}
}
private KeyManager[] getKeyManagers(KeyStore useStore,String filePass)
throws AgentStartException
{
KeyManagerFactory res;
String alg;
alg = KeyManagerFactory.getDefaultAlgorithm();
try {
res = KeyManagerFactory.getInstance(alg);
res.init(useStore, filePass.toCharArray());
} catch(Exception exc){
throw new AgentStartException("Unable to get default key " +
"manager: " + exc.getMessage());
}
return res.getKeyManagers();
}
private TokenData generateLocalToken(AgentConfig cfg)
throws AgentStartException
{
InetAddress addr;
try {
if((addr = cfg.getListenIpAsAddr()) == null){
//XXX InetAddress.getByAddress is only in jdk 1.4+
addr = InetAddress.getByName("127.0.0.1");
}
} catch(Exception exc){
throw new AgentStartException("Unable to get listen IP as addr: " +
exc.getMessage());
}
return new TokenData(SecurityUtil.generateRandomToken(),
System.currentTimeMillis(), true);
}
private void setupTokenManager(AgentConfig cfg)
throws AgentStartException
{
String tokenFile = getTokenFile();
this.tokenManager = new TokenManager(TOKEN_TIMEOUT, this);
try {
this.tokenManager.load(new FileInputStream(tokenFile));
} catch(IOException exc){
this.log.warn("Unable to load " + tokenFile + ": generating " +
"new file: " + exc.getMessage());
this.tokenManager.addToken(this.generateLocalToken(cfg));
try {
this.storeTokens();
} catch(IOException eExc){
final String msg = "Unable to store " + tokenFile + ": " +
eExc.getMessage();
throw new AgentStartException(msg);
}
}
}
public void handleNotification(String msgClass, String msg){
this.log.debug("handling notification: "+msgClass+":"+ msg);
if (this.agentStartupCallback != null) {
this.log.debug("calling back to agent");
if(msgClass.equals(AgentDaemon.NOTIFY_AGENT_UP)){
this.agentStartupCallback.onAgentStartup(true);
} else if (msgClass.equals(AgentDaemon.NOTIFY_AGENT_FAILED_START)) {
this.agentStartupCallback.onAgentStartup(false);
}
}
}
public void startup(AgentDaemon agent)
throws AgentStartException
{
try {
this.agentStartupCallback = new AgentStartupCallback(agent.getBootConfig());
agent.registerNotifyHandler(this, AgentDaemon.NOTIFY_AGENT_UP);
agent.registerNotifyHandler(this, AgentDaemon.NOTIFY_AGENT_FAILED_START);
}
catch (AgentConfigException e) {
log.warn("Failure to find startup reporting port in sys properties: " + e);
log.debug(e,e);
}
catch (IOException e) {
throw new AgentStartException("Wrapped Exception", e);
}
this.agent = agent;
try {
SSLConnectionListener listener;
KeyManager[] keyManagers;
AgentConfig cfg;
Properties bootConfig;
KeyStore keystore;
cfg = agent.getBootConfig();
this.tokenFile = cfg.getTokenFile();
this.setupTokenManager(cfg);
bootConfig = cfg.getBootProperties();
this.storage = agent.getStorageProvider();
this.keyAlg =
bootConfig.getProperty("agent.keyalg", "RSA");
KeystoreConfig keystoreConfig = new AgentKeystoreConfig();
keystore = KeystoreManager.getKeystoreManager().getKeyStore(keystoreConfig );
keyManagers = this.getKeyManagers(keystore,keystoreConfig.getFilePassword());
listener = new SSLConnectionListener(cfg, this.tokenManager);
agent.setConnectionListener(listener);
} catch(Exception exc){
//This catch is intended to catch AgentRunningException, KeyStoreException, IOException
throw new AgentStartException("Unable to initialize SSL: " + exc.getMessage(), exc);
}
this.log.info("Commands Server started up");
}
public final void postInitActions() throws AgentStartException { /*do nothing*/ }//EOM
public void shutdown(){
this.log.info("Commands Server shut down");
}
}