/**
Copyright (C) 2012 Delcyon, Inc.
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
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, see <http://www.gnu.org/licenses/>.
*/
package com.delcyon.capo.server;
import java.io.BufferedInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.PrintStream;
import java.math.BigInteger;
import java.net.ServerSocket;
import java.net.Socket;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.KeyStore;
import java.security.KeyStore.PrivateKeyEntry;
import java.security.KeyStore.TrustedCertificateEntry;
import java.security.Security;
import java.security.Signature;
import java.security.cert.Certificate;
import java.security.cert.X509Certificate;
import java.util.Arrays;
import java.util.Calendar;
import java.util.Date;
import java.util.HashMap;
import java.util.concurrent.RejectedExecutionException;
import java.util.concurrent.SynchronousQueue;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import java.util.logging.Level;
import javax.net.ssl.KeyManagerFactory;
import javax.net.ssl.SSLContext;
import javax.net.ssl.SSLException;
import javax.net.ssl.SSLSocket;
import javax.net.ssl.SSLSocketFactory;
import javax.net.ssl.TrustManagerFactory;
import javax.xml.bind.DatatypeConverter;
import javax.xml.transform.OutputKeys;
import javax.xml.transform.Transformer;
import javax.xml.transform.TransformerFactory;
import javax.xml.transform.dom.DOMSource;
import javax.xml.transform.stream.StreamResult;
import org.bouncycastle.asn1.x500.X500NameBuilder;
import org.bouncycastle.asn1.x500.style.BCStyle;
import org.bouncycastle.cert.X509v3CertificateBuilder;
import org.bouncycastle.cert.jcajce.JcaX509CertificateConverter;
import org.bouncycastle.cert.jcajce.JcaX509v3CertificateBuilder;
import org.bouncycastle.jce.provider.BouncyCastleProvider;
import org.bouncycastle.operator.ContentSigner;
import org.bouncycastle.operator.jcajce.JcaContentSignerBuilder;
import org.tanukisoftware.wrapper.WrapperManager;
import org.w3c.dom.Document;
import org.w3c.dom.Element;
import com.delcyon.capo.CapoApplication;
import com.delcyon.capo.CapoThreadFactory;
import com.delcyon.capo.Configuration;
import com.delcyon.capo.Configuration.PREFERENCE;
import com.delcyon.capo.controller.LocalRequestProcessor;
import com.delcyon.capo.datastream.BufferedSocket;
import com.delcyon.capo.datastream.ConsoleOutputStreamFilter;
import com.delcyon.capo.datastream.SocketFinalizer;
import com.delcyon.capo.datastream.StreamFinalizer;
import com.delcyon.capo.datastream.StreamHandler;
import com.delcyon.capo.datastream.StreamProcessor;
import com.delcyon.capo.datastream.StreamUtil;
import com.delcyon.capo.preferences.Preference;
import com.delcyon.capo.preferences.PreferenceInfo;
import com.delcyon.capo.preferences.PreferenceInfoHelper;
import com.delcyon.capo.preferences.PreferenceProvider;
import com.delcyon.capo.protocol.client.CapoConnection.ConnectionResponses;
import com.delcyon.capo.protocol.client.CapoConnection.ConnectionTypes;
import com.delcyon.capo.protocol.server.ClientRequestProcessorSessionManager;
import com.delcyon.capo.resourcemanager.CapoDataManager;
import com.delcyon.capo.resourcemanager.ResourceDescriptor;
import com.delcyon.capo.resourcemanager.ResourceDescriptor.Action;
import com.delcyon.capo.resourcemanager.ResourceDescriptor.LifeCycle;
import com.delcyon.capo.resourcemanager.ResourceParameter;
import com.delcyon.capo.resourcemanager.types.FileResourceType;
import com.delcyon.capo.server.jackrabbit.CapoJcrServer;
import com.delcyon.capo.server.jetty.CapoJettyServer;
import com.delcyon.capo.tasks.TaskManagerThread;
import com.delcyon.capo.xml.XPath;
/**
* @author jeremiah
*/
@PreferenceProvider(preferences=CapoServer.Preferences.class)
public class CapoServer extends CapoApplication
{
public enum Preferences implements Preference
{
@PreferenceInfo(arguments={"sec"}, defaultValue="15", description="The number of seconds to tell a client to wait before attempting to connect again", longOption="INITIAL_CLIENT_RETRY_TIME", option="INITIAL_CLIENT_RETRY_TIME")
INITIAL_CLIENT_RETRY_TIME,
@PreferenceInfo(arguments={"ms"}, defaultValue="60000", description="The number of ms to keep an idle thread alive", longOption="THREAD_IDLE_TIME", option="THREAD_IDLE_TIME")
THREAD_IDLE_TIME,
@PreferenceInfo(arguments={"ms"}, defaultValue="0", description="The number of ms to keep an socket alive", longOption="SOCKET_IDLE_TIME", option="SOCKET_IDLE_TIME")
SOCKET_IDLE_TIME,
@PreferenceInfo(arguments={"int"}, defaultValue="30", description="The number of concurrent connection to allow", longOption="MAX_THREADPOOL_SIZE", option="MAX_THREADPOOL_SIZE")
MAX_THREADPOOL_SIZE,
@PreferenceInfo(arguments={"int"}, defaultValue="10", description="The number of server threads to start with", longOption="START_THREADPOOL_SIZE", option="START_THREADPOOL_SIZE")
START_THREADPOOL_SIZE,
@PreferenceInfo(arguments={"string"}, defaultValue="capo.server.0", description="ID that this server will use when communicating with clients", longOption="SERVER_ID", option="SERVER_ID")
SERVER_ID,
@PreferenceInfo(arguments={"int"}, defaultValue="1024", description="Encryption key size", longOption="KEY_SIZE", option="KEY_SIZE")
KEY_SIZE,
@PreferenceInfo(arguments={"months"}, defaultValue="36", description="Number of Months before key expires", longOption="KEY_MONTHS_VALID", option="KEY_MONTHS_VALID")
KEY_MONTHS_VALID,
@PreferenceInfo(arguments={"dir"}, defaultValue="clients", description="resource where client information is stored", longOption="CLIENTS_DIR", option="CLIENTS_DIR")
CLIENTS_DIR,
@PreferenceInfo(arguments={"webPort"}, defaultValue="8443", description="port to start webserver on", longOption="WEB_PORT", option="WEB_PORT")
WEB_PORT,
@PreferenceInfo(arguments={"webSessionTimeout"}, defaultValue="1800", description="number of seconds before an inactive web session is logged out automatically", longOption="WEB_SESSION_TIMEOUT", option="WEB_SESSION_TIMEOUT")
WEB_SESSION_TIMEOUT,
@PreferenceInfo(arguments={"disableRepo"}, defaultValue="false", description="prevent JCR repository from starting automatically", longOption="DISABLE_REPO", option="DISABLE_REPO")
DISABLE_REPO,
@PreferenceInfo(arguments={"disableWebServer"}, defaultValue="false", description="prevent webserver from starting automatically", longOption="DISABLE_WEBSERVER", option="DISABLE_WEBSERVER")
DISABLE_WEBSERVER;
@Override
public String[] getArguments()
{
return PreferenceInfoHelper.getInfo(this).arguments();
}
@Override
public String getDefaultValue()
{
return java.util.prefs.Preferences.systemNodeForPackage(CapoApplication.getApplication().getClass()).get(getLongOption(), PreferenceInfoHelper.getInfo(this).defaultValue());
}
@Override
public String getDescription()
{
return PreferenceInfoHelper.getInfo(this).description();
}
@Override
public String getLongOption()
{
return PreferenceInfoHelper.getInfo(this).longOption();
}
@Override
public String getOption()
{
return PreferenceInfoHelper.getInfo(this).option();
}
@Override
public Location getLocation()
{
return PreferenceInfoHelper.getInfo(this).location();
}
}
private static final String BC = org.bouncycastle.jce.provider.BouncyCastleProvider.PROVIDER_NAME;
private static final String APPLICATION_DIRECTORY_NAME = "server";
private ServerSocket serverSocket;
private boolean attemptSSL = true;
//private boolean isReady = false;
//private boolean isShutdown = false;
private ThreadPoolExecutor threadPoolExecutor;
private ClientRequestProcessorSessionManager clientRequestProcessorSessionManager;
private ThreadGroup threadPoolGroup;
private TrustManagerFactory trustManagerFactory;
private KeyManagerFactory keyManagerFactory;
//private SecureSocketListener secureSocketListener;
//private SocketListener socketListener;
private CapoJettyServer capoJettyServer;
private CapoJcrServer capoJcrServer;
public static final PrintStream sysout = System.out;
public static final PrintStream syserr = System.err;
public static ConsoleOutputStreamFilter outConsole = null;
public static ConsoleOutputStreamFilter errConsole = null;
/**
* This is so that we can steal the output streams as quickly as possible
*/
static
{
outConsole = new ConsoleOutputStreamFilter(System.out);
System.setOut(new PrintStream(outConsole));
errConsole = new ConsoleOutputStreamFilter(System.err);
System.setErr(new PrintStream(errConsole));
}
public CapoServer() throws Exception
{
super();
}
@Override
public Integer start(String[] programArgs)
{
try
{
init(programArgs);
startup(programArgs);
} catch (Exception e)
{
e.printStackTrace();
return 1;
}
return null;
}
/**
* @param args
*/
public static void main(String[] args)
{
try
{
WrapperManager.start( new CapoServer(), args );
// CapoServer capoServer = new CapoServer();
// capoServer.init(args);
// capoServer.startup(args);
} catch (Exception e)
{
e.printStackTrace();
System.exit(1);
}
}
/**
* @param programArgs
* @throws Exception
* @throws SecurityException
*/
public void init(String[] programArgs) throws SecurityException, Exception
{
setApplicationState(ApplicationState.INITIALIZING);
setConfiguration(new Configuration(programArgs));
if (getConfiguration().hasOption(PREFERENCE.HELP))
{
getConfiguration().printHelp();
System.exit(0);
}
clientRequestProcessorSessionManager = new ClientRequestProcessorSessionManager();
clientRequestProcessorSessionManager.start();
//setup resource manager
setDataManager(CapoDataManager.loadDataManager(getConfiguration().getValue(PREFERENCE.RESOURCE_MANAGER)));
getDataManager().init(true);
if(getConfiguration().getBooleanValue(Preferences.DISABLE_REPO) != true)
{
capoJcrServer = new CapoJcrServer();
capoJcrServer.start();
setSession(CapoJcrServer.createSession());
}
getDataManager().init(false);
//change over to using the repo by default once were up and running
//getDataManager().setDefaultResourceTypeScheme("repo");
TaskManagerThread.startTaskManagerThread();
Security.addProvider(new BouncyCastleProvider());
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
ResourceDescriptor keystoreFile = getDataManager().getResourceDescriptor(null, getConfiguration().getValue(PREFERENCE.KEYSTORE));
keystoreFile.addResourceParameters(null,new ResourceParameter(FileResourceType.Parameters.PARENT_PROVIDED_DIRECTORY,PREFERENCE.CONFIG_DIR));
char[] password = getConfiguration().getValue(PREFERENCE.KEYSTORE_PASSWORD).toCharArray();
if (keystoreFile.getResourceMetaData(null).exists() == false)
{
keyStore = buildKeyStore();
}
else
{
keystoreFile.open(null);
InputStream keyStoreFileInputStream = keystoreFile.getInputStream(null);
keyStore.load(keyStoreFileInputStream, password);
keyStoreFileInputStream.close();
keystoreFile.close(null);
}
setKeyStore(keyStore);
trustManagerFactory = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm());
trustManagerFactory.init(getKeyStore());
keyManagerFactory = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm());
keyManagerFactory.init(getKeyStore(), getConfiguration().getValue(PREFERENCE.KEYSTORE_PASSWORD).toCharArray());
setSslSocketFactory(getLocalSslSocketFactory());
serverSocket = new ServerSocket(getConfiguration().getIntValue(PREFERENCE.PORT));
logger.log(Level.INFO, "Listening on "+serverSocket);
try
{
runStartupScript("repo:"+getConfiguration().getValue(PREFERENCE.STARTUP_SCRIPT));
} catch (Exception exception)
{
exception.printStackTrace();
}
if(getConfiguration().getBooleanValue(Preferences.DISABLE_WEBSERVER) != true)
{
capoJettyServer = new CapoJettyServer(keyStore, getConfiguration().getValue(PREFERENCE.KEYSTORE_PASSWORD), getConfiguration().getIntValue(Preferences.WEB_PORT));
capoJettyServer.start();
}
setApplicationState(ApplicationState.INITIALIZED);
}
private void runStartupScript(String startupScriptName) throws Exception
{
ResourceDescriptor startupScriptResourceDescriptor = getDataManager().getResourceDescriptor(null,startupScriptName);
startupScriptResourceDescriptor.addResourceParameters(null,new ResourceParameter(FileResourceType.Parameters.PARENT_PROVIDED_DIRECTORY,PREFERENCE.CONTROLLER_DIR));
startupScriptResourceDescriptor.init(null, null, LifeCycle.EXPLICIT, false);
if (startupScriptResourceDescriptor.getResourceMetaData(null).exists() == false)
{
startupScriptResourceDescriptor.performAction(null, Action.CREATE);
// startupScriptResourceDescriptor.close(null);
// startupScriptResourceDescriptor.open(null);
Document startupDocument = CapoApplication.getDefaultDocument("server_startup.xml");
OutputStream startupFileOutputStream = startupScriptResourceDescriptor.getOutputStream(null);
TransformerFactory tFactory = TransformerFactory.newInstance();
Transformer transformer = tFactory.newTransformer();
transformer.setOutputProperty(OutputKeys.INDENT, "yes");
transformer.transform(new DOMSource(startupDocument), new StreamResult(startupFileOutputStream));
startupFileOutputStream.close();
}
LocalRequestProcessor localRequestProcessor = new LocalRequestProcessor();
localRequestProcessor.process(CapoApplication.getDocumentBuilder().parse(startupScriptResourceDescriptor.getInputStream(null)));
startupScriptResourceDescriptor.close(null);
}
public void shutdown() throws Exception
{
long incrementalWaitTime = 2000;
long maxWaitTime = 30000;
long totalWaitTime = 0;
logger.log(Level.INFO, "Shuting Down Server");
capoJettyServer.shutdown();
while(getApplicationState().ordinal() < ApplicationState.READY.ordinal())
{
logger.log(Level.INFO, "Waiting for final shutdown...");
Thread.sleep(incrementalWaitTime);
}
setApplicationState(ApplicationState.STOPPING);
if (serverSocket != null)
{
logger.log(Level.INFO, "Closing Socket");
serverSocket.close();
}
if (threadPoolExecutor != null)
{
logger.log(Level.INFO, "Stopping thread pool");
threadPoolExecutor.shutdown();
while(threadPoolExecutor.isTerminated() == false)
{
logger.log(Level.INFO, "Waiting for thread pool to shutdown...");
sleep(incrementalWaitTime);
totalWaitTime += incrementalWaitTime;
if (totalWaitTime >= maxWaitTime)
{
logger.log(Level.INFO, "Forcing thread pool to shutdown... ");
threadPoolExecutor.shutdownNow();
}
}
}
if (TaskManagerThread.getTaskManagerThread() != null)
{
logger.log(Level.INFO, "Stopping Task Manager");
TaskManagerThread.getTaskManagerThread().interrupt();
while(TaskManagerThread.getTaskManagerThread().getTaskManagerState() != ApplicationState.STOPPED)
{
sleep(incrementalWaitTime);
}
logger.log(Level.INFO, "Done Waiting for Task Manager to shutdown...");
}
if (clientRequestProcessorSessionManager != null)
{
logger.log(Level.INFO, "Stopping Session Manager");
clientRequestProcessorSessionManager.shutdown();
while(clientRequestProcessorSessionManager.isAlive())
{
logger.log(Level.INFO, "Waiting for Session Manager to shutdown...");
sleep(incrementalWaitTime);
}
}
logger.log(Level.INFO, "Releaseing Resource Manager");
getDataManager().release();
logger.log(Level.INFO, "Removing Resource Manager");
setDataManager(null);
logger.log(Level.INFO, "Shutting Down JCR");
capoJcrServer.shutdown();
logger.log(Level.INFO, "Server Shutdown");
setApplicationState(ApplicationState.STOPPED);
}
/**
* Start Server Listening
*
* @param programArgs
* @throws Exception
*/
public void startup(String[] programArgs) throws Exception
{
setApplicationState(ApplicationState.STARTING);
SynchronousQueue<Runnable> synchronousQueue = new SynchronousQueue<Runnable>();
threadPoolGroup = new ThreadGroup("Thread Pool Group");
threadPoolExecutor = new ThreadPoolExecutor(getConfiguration().getIntValue(Preferences.START_THREADPOOL_SIZE), getConfiguration().getIntValue(Preferences.MAX_THREADPOOL_SIZE), getConfiguration().getIntValue(Preferences.THREAD_IDLE_TIME), TimeUnit.MILLISECONDS, synchronousQueue);
threadPoolExecutor.setThreadFactory(new CapoThreadFactory(threadPoolGroup));
start();
}
private SSLSocketFactory getLocalSslSocketFactory() throws Exception
{
SSLContext sslContext = SSLContext.getInstance("SSL");
sslContext.init(keyManagerFactory.getKeyManagers(), trustManagerFactory.getTrustManagers(), new java.security.SecureRandom());
return sslContext.getSocketFactory();
}
@Override
public void run()
{
setApplicationState(ApplicationState.READY);
try
{
while (true)
{
Socket socket = null;
try
{
logger.log(Level.FINER, "waiting for connection");
try
{
socket = serverSocket.accept();
socket.setTcpNoDelay(true);
socket.setSoTimeout(getConfiguration().getIntValue(Preferences.SOCKET_IDLE_TIME));
} catch (SocketException socketException)
{
if (getApplicationState().ordinal() > ApplicationState.READY.ordinal() && serverSocket.isClosed())
{
logger.log(Level.INFO, "Shutting down server");
//isReady = false;
return;
}
}
logger.log(Level.FINE, "got connection: "+socket);
socket = new BufferedSocket(socket);
BufferedInputStream inputStream = (BufferedInputStream) socket.getInputStream();
//figure out what kind of socket this is
inputStream.mark(getConfiguration().getIntValue(PREFERENCE.BUFFER_SIZE));
byte[] buffer = new byte[getConfiguration().getIntValue(PREFERENCE.BUFFER_SIZE)];
inputStream.read(buffer);
inputStream.reset();
String message = new String(buffer).trim();
if (message.matches(ConnectionTypes.CAPO_REQUEST.toString()))
{
if (threadPoolExecutor.getActiveCount() < threadPoolExecutor.getMaximumPoolSize())
{
writeOKMessage(inputStream, socket, message, buffer);
}
else
{
writeBusyMessage(socket);
continue;
}
}
StreamProcessor streamProcessor = StreamHandler.getStreamProcessor(buffer);
String clientID = null;
HashMap<String, String> sessionHashMap = new HashMap<String, String>();
//if the header is an unknown type assume its SSL and pass it on, since we can't read anything from it
if (streamProcessor == null && attemptSSL == true)
{
try
{
socket = getSslSocketFactory().createSocket(socket, socket.getLocalAddress().getHostAddress(), socket.getLocalPort(), true);
}
catch (Exception exception)
{
CapoApplication.logger.log(Level.WARNING, "Unknown Stream Type: "+exception.getMessage());
socket.close();
continue;
}
//keep the ssl socket so that we can use it's session id for validation
SSLSocket sslSocket = (SSLSocket) socket;
sslSocket.setUseClientMode(false);
//sslSocket.setReuseAddress(false); it's too late for this here, just left as a note
sslSocket.setSendBufferSize((CapoApplication.getConfiguration().getIntValue(PREFERENCE.BUFFER_SIZE)*2)+728);
//we effectively have a brand new socket, so we have to wrap it as well, so we can reset of checking it's content
socket = new BufferedSocket(socket);
inputStream = (BufferedInputStream) socket.getInputStream();
inputStream.mark(getConfiguration().getIntValue(PREFERENCE.BUFFER_SIZE));
try
{
inputStream.read(buffer);
}
catch (SSLException sslException)
{
CapoApplication.logger.log(Level.WARNING, "Unknown SSLException Type: "+sslException.getMessage());
socket.close();
continue;
}
String authMessage = new String(buffer).trim();
if (authMessage.matches("AUTH:CID=capo\\.(client|server)\\.\\d+:SIG=[A-F0-9]+.*:.*"))
{
logger.fine("SSL SID:"+DatatypeConverter.printHexBinary(sslSocket.getSession().getId()));
if (isValidAuthMessage(authMessage,sslSocket.getSession().getId()) == false)
{
CapoApplication.logger.log(Level.WARNING, "Invalid AUTH attempt "+authMessage+" from: "+socket);
socket.close();
continue;
}
else //got verified AUTH message
{
//the client is waiting for a response so send any single value;
socket.getOutputStream().write(0);
socket.getOutputStream().flush();
//reset to the beginning
inputStream.reset();
//skip ahead to the end of our AUTH message
inputStream.skip(authMessage.length());
//mark the spot
inputStream.mark(getConfiguration().getIntValue(PREFERENCE.BUFFER_SIZE));
//not re-read the buffer from our newly reset and marked location
Arrays.fill(buffer, (byte)0);
StreamUtil.fullyReadIntoBufferUntilPattern(inputStream, buffer, ConnectionTypes.CAPO_REQUEST.toString().getBytes());
//store client id
clientID = authMessage.replaceFirst("AUTH:CID=(capo\\.(client|server)\\.\\d+):SIG=[A-F0-9]+.*:.*", "$1");
sessionHashMap.put("clientID", clientID);
//check for a local client directory
ResourceDescriptor clientResourceDescriptor = getDataManager().getResourceDescriptor(null, "clients:"+clientID);
if (clientResourceDescriptor.getResourceMetaData(null).exists() == false)
{
logger.log(Level.INFO, "Creating new clients resource for "+clientID);
clientResourceDescriptor.performAction(null, Action.CREATE,new ResourceParameter(ResourceDescriptor.DefaultParameters.CONTAINER, "true"));
clientResourceDescriptor.close(null);
clientResourceDescriptor.open(null);
}
//check for a client resource directory
ResourceDescriptor clientResourcesResourceDescriptor = clientResourceDescriptor.getChildResourceDescriptor(null,getConfiguration().getValue(PREFERENCE.RESOURCE_DIR));
if (clientResourcesResourceDescriptor.getResourceMetaData(null).exists() == false)
{
logger.log(Level.INFO, "Creating new resource dir for "+clientID);
clientResourcesResourceDescriptor.performAction(null, Action.CREATE,new ResourceParameter(ResourceDescriptor.DefaultParameters.CONTAINER, "true"));
clientResourcesResourceDescriptor.close(null);
clientResourcesResourceDescriptor.open(null);
}
//check for a client tasks directory
ResourceDescriptor clientTasksResourceDescriptor = clientResourceDescriptor.getChildResourceDescriptor(null,getConfiguration().getValue(TaskManagerThread.Preferences.TASK_DIR));
if (clientTasksResourceDescriptor.getResourceMetaData(null).exists() == false)
{
logger.log(Level.INFO, "Creating new tasks dir for "+clientID);
clientTasksResourceDescriptor.performAction(null, Action.CREATE,new ResourceParameter(ResourceDescriptor.DefaultParameters.CONTAINER, "true"));
clientTasksResourceDescriptor.close(null);
clientTasksResourceDescriptor.open(null);
}
//update status information
ResourceDescriptor statusResourceDescriptor = clientResourceDescriptor.getChildResourceDescriptor(null,"status.xml");
Element statusRootElement = null;
if (statusResourceDescriptor.getResourceMetaData(null).exists() == false)
{
statusResourceDescriptor.performAction(null, Action.CREATE);
statusRootElement = CapoApplication.getDefaultDocument("status.xml").getDocumentElement();
}
else
{
statusRootElement = getDocumentBuilder().parse(statusResourceDescriptor.getInputStream(null)).getDocumentElement();
}
statusRootElement.setAttribute("lastConnectTime", System.currentTimeMillis()+"");
XPath.dumpNode(statusRootElement, statusResourceDescriptor.getOutputStream(null));
statusResourceDescriptor.close(null);
}
inputStream.reset();
message = new String(buffer).trim();
if (message.matches(ConnectionTypes.CAPO_REQUEST.toString()))
{
if (threadPoolExecutor.getActiveCount() < threadPoolExecutor.getMaximumPoolSize())
{
writeOKMessage(inputStream, socket, message, buffer);
}
else
{
writeBusyMessage(socket);
continue;
}
}
}
streamProcessor = StreamHandler.getStreamProcessor(buffer);
//reset the buffer
inputStream.reset();
}
logger.log(Level.FINE, "Request Buffer: '" + new String(buffer) +"'");
//we should have a stream handler by this point
if (streamProcessor == null)
{
int initailSocketTimeout = socket.getSoTimeout();
try
{
socket.setSoTimeout(10000);
//keep trying to read enough into the buffer to make a determination;
while(streamProcessor == null)
{
int count = inputStream.read(buffer);
if(count > 0)
{
inputStream.reset();
inputStream.read(buffer);
}
streamProcessor = StreamHandler.getStreamProcessor(buffer);
}
}
catch (SocketTimeoutException socketTimeoutException)
{
//one final try on the buffer
streamProcessor = StreamHandler.getStreamProcessor(buffer);
}
//still didn't find anything after at least 10 seconds
if(streamProcessor == null)
{
CapoApplication.logger.log(Level.WARNING, "Unknown Stream Type from "+socket.getRemoteSocketAddress());
CapoApplication.logger.log(Level.WARNING, "Unknown Stream Type: "+new String(buffer));
socket.close();
continue;
}
//reset the buffer, since we've been beating the crap out of it.
inputStream.reset();
//reset the socket timeout
socket.setSoTimeout(initailSocketTimeout);
}
StreamHandler streamHandler = new StreamHandler(streamProcessor);
StreamFinalizer streamFinalizer = new SocketFinalizer(socket);
streamHandler.add(streamFinalizer);
streamHandler.init(inputStream,socket.getOutputStream(),sessionHashMap);
logger.log(Level.FINE, "Starting a "+streamProcessor.getClass().getSimpleName()+" Stream Handler for "+clientID+"@"+socket);
try
{
threadPoolExecutor.execute(streamHandler);
}
catch (RejectedExecutionException e)
{
writeBusyMessage(socket);
}
}
catch (SocketTimeoutException socketTimeoutException)
{
socket.close();
socketTimeoutException.printStackTrace();
}
}
}
catch (Exception exception)
{
CapoApplication.logger.log(Level.SEVERE, "Exiting due to uncaught exception.",exception);
}
}
private void writeOKMessage(InputStream inputStream,Socket socket, String message,byte[] buffer) throws Exception
{
inputStream.skip(message.length());
inputStream.mark(getConfiguration().getIntValue(PREFERENCE.BUFFER_SIZE));
socket.getOutputStream().write(ConnectionResponses.OK.toString().getBytes());
socket.getOutputStream().write(0);
socket.getOutputStream().flush();
Arrays.fill(buffer, (byte)0);
inputStream.read(buffer);
inputStream.reset();
}
private void writeBusyMessage(Socket socket) throws Exception
{
String busyString = new String(ConnectionResponses.BUSY+" "+(getConfiguration().getIntValue(Preferences.INITIAL_CLIENT_RETRY_TIME)*1000));
logger.log(Level.WARNING, "Rejecting a request from "+socket.getRemoteSocketAddress()+" with "+busyString);
socket.getOutputStream().write(busyString.getBytes());
socket.getOutputStream().write(0);
socket.getOutputStream().flush();
socket.close();
}
private boolean isValidAuthMessage(String authMessage, byte[] sessionID)
{
if (authMessage == null)
{
return false;
}
if (authMessage.matches("AUTH:CID=capo\\.(client|server)\\.\\d+:SIG=[A-F0-9]+.*:.*") == false)
{
return false;
}
String[] splitAuthMessage = authMessage.split(":|=");
if (splitAuthMessage.length != 5)
{
return false;
}
String clientID = splitAuthMessage[2];
byte[] encodedSignature = DatatypeConverter.parseHexBinary(splitAuthMessage[4]);
try
{
Certificate certificate = getKeyStore().getCertificate(clientID+".cert");
if (certificate == null)
{
return false;
}
Signature signature = Signature.getInstance("SHA256withRSA");
signature.initVerify(certificate);
signature.update(clientID.getBytes());
signature.update(sessionID);
if (signature.verify(encodedSignature) == true)
{
return true;
}
} catch (Exception e)
{
// TODO Auto-generated catch block
e.printStackTrace();
}
return false;
}
@Override
public String getApplicationDirectoryName()
{
return APPLICATION_DIRECTORY_NAME;
}
/**
* This loads an xml file, starts he server, and sends the document as a
* request, then returns an array of [requestDocument,responseDocument]
* TESTING ONLY
*
* @param filename
* @return
* @throws Exception
*/
private KeyStore buildKeyStore() throws Exception
{
KeyStore keyStore = KeyStore.getInstance(KeyStore.getDefaultType());
char[] password = getConfiguration().getValue(PREFERENCE.KEYSTORE_PASSWORD).toCharArray();
//generate keys
KeyPairGenerator rsakeyPairGenerator = KeyPairGenerator.getInstance("RSA");
rsakeyPairGenerator.initialize(getConfiguration().getIntValue(Preferences.KEY_SIZE));
KeyPair rsaKeyPair = rsakeyPairGenerator.generateKeyPair();
//begin bouncy castle crap
X500NameBuilder x500NameBuilder = new X500NameBuilder(BCStyle.INSTANCE);
x500NameBuilder.addRDN(BCStyle.CN,getConfiguration().getValue(Preferences.SERVER_ID));
ContentSigner contentSigner = new JcaContentSignerBuilder("SHA256WithRSAEncryption").setProvider(BC).build(rsaKeyPair.getPrivate());
Calendar calendar = Calendar.getInstance();
calendar.add(Calendar.MONTH, getConfiguration().getIntValue(Preferences.KEY_MONTHS_VALID));
X509v3CertificateBuilder certificateBuilder = new JcaX509v3CertificateBuilder(x500NameBuilder.build(), BigInteger.valueOf(System.currentTimeMillis()), new Date(System.currentTimeMillis() - 50000),calendar.getTime(),x500NameBuilder.build(), rsaKeyPair.getPublic());
X509Certificate certificate = new JcaX509CertificateConverter().setProvider(BC).getCertificate(certificateBuilder.build(contentSigner));
//end bouncy castle crap
keyStore.load(null, password);
KeyStore.TrustedCertificateEntry trustedCertificateEntry = new TrustedCertificateEntry(certificate);
keyStore.setEntry(getConfiguration().getValue(Preferences.SERVER_ID), trustedCertificateEntry,null);
keyStore.setEntry("capo.server.cert", trustedCertificateEntry,null);
KeyStore.PrivateKeyEntry privateKeyEntry = new PrivateKeyEntry(rsaKeyPair.getPrivate(), new Certificate[]{certificate});
keyStore.setEntry(getConfiguration().getValue(Preferences.SERVER_ID)+".private", privateKeyEntry,new KeyStore.PasswordProtection(password));
writeKeyStore(keyStore);
return keyStore;
}
public synchronized void writeKeyStore(KeyStore keyStore) throws Exception
{
ResourceDescriptor keystoreFile = getDataManager().getResourceDescriptor(null,getConfiguration().getValue(PREFERENCE.KEYSTORE));
keystoreFile.addResourceParameters(null,new ResourceParameter(FileResourceType.Parameters.PARENT_PROVIDED_DIRECTORY,PREFERENCE.CONFIG_DIR));
char[] password = getConfiguration().getValue(PREFERENCE.KEYSTORE_PASSWORD).toCharArray();
//save keystore
OutputStream keyStoreFileOutputStream = keystoreFile.getOutputStream(null);
keyStore.store(keyStoreFileOutputStream, password);
keyStoreFileOutputStream.close();
keystoreFile.close(null);
}
}