 /*
  * (C) 2003-2004 Columbia University Departments of Computer Science and
  * Electrical Engineering
  *
  * Redistribution and use in source and binary forms, with or without
  * modification, is permitted provided the original authors give
  * written consent and 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.
  *
  * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ``AS IS'' AND
  * ANY EXPRESS 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 AUTHORS
  * 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.
  */

package server;

import settings.Settings;
import util.Debug;
import java.io.*;
import java.net.*;
import javax.net.*;
import javax.net.ssl.*;
import java.security.KeyStore;
import javax.security.cert.X509Certificate;
import java.util.Map;


/** 
 * SSLSocketServer.java
 *
 * SSLSocketServer opens a Socket connection to the proxy and forwards the requests from the SSLSocket
 * to the proxy
 *
 */
public class SSLSocketServer
{

    private SSLServerSocket _serverSocket = null;  // SSL server socket
    private Map _settings = Settings.instance();
    private boolean _isListening = true;
    private boolean _isDebugMode = Debug.isEnabled(SSLSocketServer.class.getName());
    private SocketFactory csf;

    public SSLSocketServer()
    {
		createServerSocket();
    }


    // initializes the SSL server socket and SSL socket factory based on configuration
    private void createServerSocket()
    {
		try {
			// set up key manager to do server authentication
			SSLContext ctx = SSLContext.getInstance((String)_settings.get(Settings.SSL_CONTEXT_PROTOCOL));
			KeyManagerFactory kmf = KeyManagerFactory.getInstance((String)_settings.get(Settings.KEY_MANAGER_FACTORY_ALGORITHM));
			TrustManagerFactory tmf = TrustManagerFactory.getInstance((String)_settings.get(Settings.TRUST_MANAGER_FACTORY_ALGORITHM));
			KeyStore ks = KeyStore.getInstance((String)_settings.get(Settings.KEY_STORE_TYPE));
			char[] passphrase = ((String)_settings.get(Settings.KEY_PASSWORD)).toCharArray();
	    
			ks.load(new FileInputStream((String)_settings.get(Settings.KEY_FILE_DIRECTORY)), ((String)_settings.get(Settings.KEY_STORE_PASSWORD)).toCharArray());
			tmf.init(ks);
			kmf.init(ks, passphrase );
			ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null );
            csf = ctx.getSocketFactory();
			ServerSocketFactory ssf = ctx.getServerSocketFactory();
	    
			_serverSocket = (SSLServerSocket)ssf.createServerSocket(((Integer)_settings.get(Settings.SERVER_PORT)).intValue());
			_serverSocket.setNeedClientAuth(true);
			if (_isDebugMode) {
				Debug.writeln(getClass().getName() + ": server socket ready from server side on port " + ((Integer)_settings.get(Settings.SERVER_PORT)).intValue() + " - start client.");
			}

			// listen for connections
			startListening();
		} 
		catch(Exception e) {
			if (_isDebugMode) {
				Debug.writeln(getClass().getName() + ": Error - could not initialize ssl server socket - " + e.getMessage());
			}	    
		}

    }

    
    // begin listening for incoming requests from SSLSocketClient
    private void startListening()
    {
		while (_isListening) {
			try {

				// creating a new ssl socket
				final SSLSocket socket = (SSLSocket)_serverSocket.accept();
			    
				if (_isDebugMode) {
					Debug.writeln(getClass().getName() + ": creating a new thread to take care of requests from the client");
				}	    
				Thread thread = new Thread(new Runnable() {
						public void run() {
							try {
								// Create streams to securely send and receive data to the server
								BufferedReader inFromClient = new BufferedReader(new InputStreamReader(socket.getInputStream())); 
								DataOutputStream outToClient = new DataOutputStream(socket.getOutputStream()); 
								//SSLSocket proxySocket = (SSLSocket)sslFact.createSocket(next.hostname, 18080);
								SSLSocket proxySocket = (SSLSocket)csf.createSocket((String)_settings.get(Settings.PROXY_HOSTNAME), ((Integer)_settings.get(Settings.PROXY_PORT)).intValue());
				
								DataOutputStream outToProxy = new DataOutputStream(proxySocket.getOutputStream());
								DataInputStream inFromProxy = new DataInputStream(proxySocket.getInputStream());
								String request = null;	
								request = inFromClient.readLine().trim();

								if (_isDebugMode) {
									Debug.writeln(getClass().getName() + ": created a socket to forward requests to proxy on port " + proxySocket.getPort() + " and host " + (String)_settings.get(Settings.PROXY_HOSTNAME));
									Debug.writeln(getClass().getName() + ": received request: " + request);
								}	    

								// forwarding the request to proxy
								outToProxy.writeBytes(request + "\n\n");
								outToProxy.flush();		
								byte[] buffer = new byte[1024];
								int len;
								while ((len = inFromProxy.read(buffer)) != -1  ) {
									outToClient.write(buffer, 0, len);
									if (_isDebugMode) {
										Debug.writeln(getClass().getName() + ": received reply from proxy: " + new String(buffer));
									}
								}
								outToClient.flush();
								proxySocket.setSoLinger(true,10);
								inFromProxy.close();
								outToProxy.close();
								proxySocket.close();
								//socket.setSoLinger(true,20);
								//inFromClient.close();
								//outToClient.close();
								//socket.close();
							}
							catch (Exception e) {
								if (_isDebugMode) {
									Debug.writeln(getClass().getName() + ": Error - " + e.getMessage());
								}
							}
							if (_isDebugMode) {
								Debug.write(getClass().getName() + ": exited server thread");
							}
						}
					});
				thread.start();
			}
			catch (Exception e){
				if (_isDebugMode) {
					Debug.writeln(getClass().getName() + ": Error - " + e.getMessage());
				}
			}
		}

		try {
			_serverSocket.close();
		}
		catch (Exception e) {
			if (_isDebugMode) {
				Debug.writeln(getClass().getName() + ": Error - closing server socket - " + e.getMessage());
			}
		}
    }


    public void stopListening()
    {
		_isListening = false;
    }


    public static void main(String[] args_)
    {
		SSLSocketServer socketServer = new SSLSocketServer();
    }
}


