package com.dbxml.labrador.http;
/*
* The dbXML Labrador Software License, Version 1.0
*
*
* Copyright (c) 2003 The dbXML Group, L.L.C. All rights reserved.
*
* Redistribution and use in source and binary forms, with or without
* modification, are permitted provided that the following conditions
* are met:
*
* 1. Redistributions of source code must retain the above copyright
* notice, this list of conditions and the following disclaimer.
*
* 2. Redistributions in binary form must reproduce the above copyright
* notice, this list of conditions and the following disclaimer in
* the documentation and/or other materials provided with the
* distribution.
*
* 3. The end-user documentation included with the redistribution,
* if any, must include the following acknowledgment:
* "This product includes software developed by The
* dbXML Group, L.L.C. (http://www.dbxml.com/)."
* Alternately, this acknowledgment may appear in the software
* itself, if and wherever such third-party acknowledgments normally
* appear.
*
* 4. The names "Labrador" and "dbXML Group" must not be used to
* endorse or promote products derived from this software without
* prior written permission. For written permission, please contact
* info@dbxml.com
*
* 5. Products derived from this software may not be called "Labrador",
* nor may "Labrador" appear in their name, without prior written
* permission of The dbXML Group, L.L.C..
*
* THIS SOFTWARE IS PROVIDED ``AS IS'' AND ANY EXPRESSED OR IMPLIED
* WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES
* OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
* DISCLAIMED. IN NO EVENT SHALL THE DBXML GROUP, L.L.C. OR ITS
* CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
* SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT
* LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF
* USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT
* OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF
* SUCH DAMAGE.
* ====================================================================
*
* $Id: SecureHTTPServer.java,v 1.14 2004/02/27 16:20:36 bradford Exp $
*/
import javax.net.ssl.*;
import com.dbxml.labrador.broker.Broker;
import com.sun.net.ssl.internal.ssl.Provider;
import java.io.FileInputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.net.InetAddress;
import java.net.Socket;
import java.net.SocketException;
import java.net.URL;
import java.security.KeyStore;
import java.security.SecureRandom;
import java.security.Security;
import java.util.Stack;
/**
* SecureHTTPServer is an incredibly simple HTTP Server implementation with
* the sole purpose of handling Labrador requests. No static content, no
* virtual servers, no bells, no whistles. Because Java 1.4.1 still doesn't
* support non-blocking SSL, this code is based on the older implementation.
* <br /><br />
* This server is implemented as a Daemon Thread, so in order to keep the
* VM alive, you'll have to either call setDaemon(false) or you'll need to
* have some other Thread keeping the VM alive.
* <br /><br />
* The Default port for this Secure HTTP Server is 7643.
*/
public final class SecureHTTPServer extends HTTPServerBase {
private static final int ACCEPT_BACKLOG = 5;
private static final String KEYSTORE_TYPE = "JKS";
private static final String SSL_PROTOCOL = "TLS";
private static final String CERT_ALGORITHM = "SunX509";
public static final Object PROP_SSL_SESSION = new Object();
private SSLServerSocket sock = null;
private Stack workers = new Stack(); // of HTTPSWorker
private String keyFile;
private String keyPass;
private String trustFile;
private String trustPass;
private boolean clientAuth;
/**
* main starts the SecureHTTPServer in standalone mode
*/
public static void main(String[] args) {
try {
SecureHTTPServer server = new SecureHTTPServer();
Broker.println(server.getName());
server.setDaemon(false);
// Start the server, and wait until it's ready
synchronized ( server ) {
server.start();
server.wait();
}
Broker.println("Listening on " + server.getHostName() + " at port " + server.getPort());
}
catch ( Exception e ) {
e.printStackTrace(System.err);
System.exit(1);
}
}
public SecureHTTPServer() {
}
public SecureHTTPServer(String url) {
super(url);
}
public SecureHTTPServer(URL url) {
super(url);
}
protected String getThreadName() {
return "Labrador Secure HTTP Server";
}
protected String getProtocol() {
return "https";
}
protected int getDefaultPort() {
return 7643;
}
/**
* setKeyFile sets the name of the file that stores the Server's
* keys.
*
* @param keyFile The Server Key file
*/
public void setKeyFile(String keyFile) {
this.keyFile = keyFile;
}
/**
* getKeyFile returns the name of the file that stores the Server's
* keys.
*
* @return The Server Key file
*/
public String getKeyFile() {
return keyFile;
}
/**
* setKeyPass sets the pass phrase that will be used to decrypt
* the Server's keys.
*
* @param keyPass The pass phrase
*/
public void setKeyPass(String keyPass) {
this.keyPass = keyPass;
}
/**
* getKeyPass returns the pass phrase that will be used to decrypt
* the Server's keys.
*
* @return The pass phrase
*/
public String getKeyPass() {
return keyPass;
}
/**
* setTrustFile sets the name of the file that stores the Server's
* trusted certs.
*
* @param trustFile The Server Trust file
*/
public void setTrustFile(String trustFile) {
this.trustFile = trustFile;
}
/**
* getTrustFile returns the name of the file that stores the Server's
* trusted certs.
*
* @return The Server Trust file
*/
public String getTrustFile() {
return trustFile;
}
/**
* setTrustPass sets the pass phrase that will be used to decrypt
* the Server's trusted certs.
*
* @param trustPass The pass phrase
*/
public void setTrustPass(String trustPass) {
this.trustPass = trustPass;
}
/**
* getTrustPass returns the pass phrase that will be used to decrypt
* the Server's trusted certs.
*
* @return The pass phrase
*/
public String getTrustPass() {
return trustPass;
}
/**
* setClientAuth sets whether or not SSL client authentication will
* be used for this server.
*
* @param clientAuth The client authentication setting
*/
public void setClientAuth(boolean clientAuth) {
this.clientAuth = clientAuth;
}
/**
* getClientAuth returns whether or not SSL client authentication will
* be used for this server.
*
* @return The client authentication setting
*/
public boolean getClientAuth() {
return clientAuth;
}
public void shutdown() {
try {
sock.close();
}
catch ( Exception e ) {
Broker.printwarning("Error Closing Secure HTTP server socket");
e.printStackTrace(System.err);
}
}
protected void createServerSocket(InetAddress bindaddr) throws Exception {
KeyManager[] kms = null;
TrustManager[] tms = null;
if ( keyFile != null && keyFile.length() > 0 ) {
char[] pc = null;
if ( keyPass != null )
pc = keyPass.toCharArray();
KeyStore ks = loadKeyStore(keyFile, pc);
// Retrieve The Key Managers
KeyManagerFactory kmf = KeyManagerFactory.getInstance(CERT_ALGORITHM);
kmf.init(ks, pc);
kms = kmf.getKeyManagers();
}
if ( trustFile != null && trustFile.length() > 0 ) {
char[] pc = null;
if ( trustPass != null )
pc = trustPass.toCharArray();
KeyStore ks = loadKeyStore(trustFile, pc);
// Retrieve The Trust Managers
TrustManagerFactory tmf = TrustManagerFactory.getInstance(CERT_ALGORITHM);
tmf.init(ks);
tms = tmf.getTrustManagers();
}
// Add the SSL Provider (if needed)
try {
Security.addProvider(new Provider());
}
catch ( Throwable t ) {
t.printStackTrace(System.err);
}
// Create the SSL context
SSLContext ctx = SSLContext.getInstance(SSL_PROTOCOL);
// Initialize the context with key managers
ctx.init(kms, tms, new SecureRandom());
SSLServerSocketFactory ssf = ctx.getServerSocketFactory();
if ( bindaddr != null )
sock = (SSLServerSocket)ssf.createServerSocket(port, ACCEPT_BACKLOG, bindaddr);
else
sock = (SSLServerSocket)ssf.createServerSocket(port, ACCEPT_BACKLOG);
sock.setNeedClientAuth(clientAuth);
}
private KeyStore loadKeyStore(String filename, char[] pass) throws Exception {
KeyStore ks = KeyStore.getInstance(KEYSTORE_TYPE);
FileInputStream fis = new FileInputStream(filename);
ks.load(fis, pass);
fis.close();
return ks;
}
private void prepareSocket(Socket socket) {
try {
socket.setTcpNoDelay(true);
socket.setSoLinger(true, linger);
socket.setSoTimeout(timeout);
}
catch ( Exception e ) {
}
}
public void acceptConnections() {
try {
while ( true ) {
Socket socket = sock.accept();
prepareSocket(socket);
if ( !workers.isEmpty() ) {
HTTPSWorker worker = (HTTPSWorker)workers.pop();
worker.setSocket(socket);
synchronized ( worker ) {
worker.notify();
}
}
else
new HTTPSWorker(socket).start();
}
}
catch ( SocketException e ) {
// If we get an exception here, it's probably because
// the server is being shut down
}
catch ( Exception e ) {
Broker.printerr("Error in Labrador Secure HTTP Server");
e.printStackTrace(System.err);
}
}
/**
* HTTPSWorker
*/
private class HTTPSWorker extends Worker {
private Socket sock;
private boolean keepalive = true;
private boolean connected = true;
public HTTPSWorker(Socket sock) {
setSocket(sock);
}
protected String getThreadName() {
return "Labrador Secure HTTP Worker";
}
public void handleConnection() {
while ( true ) {
do {
remoteHost = sock.getInetAddress().getHostAddress();
initRequest();
try {
InputStream is = sock.getInputStream();
readRequest(is);
processRequest();
}
catch ( Exception e ) {
keepalive = false;
connected = false;
}
}
while ( connected && keepalive );
try {
sock.close();
}
catch ( Exception e ) {
Broker.printwarning("Error Closing HTTP client socket");
}
synchronized ( this ) {
workers.push(this);
try {
wait();
}
catch ( InterruptedException e ) {
e.printStackTrace(System.err);
}
}
}
}
private void setSocket(Socket sock) {
this.sock = sock;
if ( sock instanceof SSLSocket )
properties.put(PROP_SSL_SESSION, ((SSLSocket)sock).getSession());
}
public void send(int code, String message) {
try {
OutputStream os = sock.getOutputStream();
send(os, code, message);
}
catch ( Exception e ) {
keepalive = false;
connected = false;
}
}
}
}