 /*
  * (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.
  */
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.*;

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

/*
---------------------------------------------------
 * Angelos Stavrou 09/03
 * Columbia University Computer Science Department
 * All Rights Reserved
---------------------------------------------------
*/

public class SSLSocketServer
{

    private SSLServerSocket _serverSocket = null;  // SSL server socket
    private boolean _isListening = true;
    private boolean _isDebugMode = false;
	public  ShortcutRouter parent;
    public	int proxyPort,port;
	public	String httpVersion,hostname;
	public SSLSocketFactory sslFact;

    public SSLSocketServer(int port, ShortcutRouter r)
    {
		parent = r;
        proxyPort = port;
		String KEYSTORE = "server.key";
        char[] KEYSTOREPW = "test12345".toCharArray();
        char[] KEYPW = "test12345".toCharArray();
        System.setProperty("javax.net.ssl.trustStore", "server.key");
        System.setProperty("javax.net.ssl.trustStorePassword", "test12345");
        System.setProperty("javax.net.ssl.keyStore", "server.key");
        System.setProperty("javax.net.ssl.keyStorePassword", "test12345");
  	    sslFact=(SSLSocketFactory)SSLSocketFactory.getDefault();
		createServerSocket();

    }
	
	public NextDestination nextHop(String dest, int hops){
		try{
			/* convert dest into an ip address */
			InetAddress destAddr = InetAddress.getByName(dest);
			dest = destAddr.getHostAddress();
		}
		catch (Exception e) {
			System.err.println("sosProxy: error in getting ip address: " +e);
		}
		System.err.println("nextHop(" + dest + ")");
		return parent.getNextHop(dest);
	}

	public NextDestination oldNextHop(String dest, int hops){

		NextDestination ret;
		String nextAddr = "";
		int nextPort = 10;
		boolean connectDirect = false;

		try{
			/* convert dest into an ip address */
			InetAddress destAddr = InetAddress.getByName(dest);
			String destIP = destAddr.getHostAddress();

			if (nextAddr.equals(destIP)) {
				connectDirect = true;
			}

		}
		catch (Exception e) {
			e.printStackTrace();
		}

		/* need to compare dest and actual destination */
		ret = new NextDestination(nextAddr, nextPort, connectDirect);

		return ret;
	}

    // 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("SSLv3");
			KeyManagerFactory kmf = KeyManagerFactory.getInstance("SunX509");
			TrustManagerFactory tmf = TrustManagerFactory.getInstance("SunX509");
			KeyStore ks = KeyStore.getInstance("JKS");
			char[] passphrase = "test12345".toCharArray();
			ks.load(new FileInputStream("server.key"), "test12345".toCharArray());
			tmf.init(ks);
			kmf.init(ks, passphrase );
			ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null );
			ServerSocketFactory ssf = ctx.getServerSocketFactory();
	    
			_serverSocket = (SSLServerSocket)ssf.createServerSocket(proxyPort);
			_serverSocket.setNeedClientAuth(true);
			if (_isDebugMode) {
				System.out.println("server socket ready from server side on port " + proxyPort  + " - start client.");
			}

			// listen for connections
			startListening();
		} 
		catch(Exception e) {
			if (_isDebugMode) {
				System.out.println ("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();
			   	socket.setSoTimeout(10000); 
				if (_isDebugMode) {
					System.out.println("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()); 
								String request = null;	
								request = inFromClient.readLine().trim();
								System.err.println("REQUEST:"+request);
								StringTokenizer st = new StringTokenizer(request, " ");
								String method = st.nextToken();
								if ((method.equals("GET"))) {
									System.out.println("GET REQUEST");
									String destRaw = st.nextToken();
									httpVersion = st.nextToken();
									StringTokenizer st2 = new StringTokenizer(destRaw, "//");
									String strtmp=st2.nextToken();
									String Path = st2.nextToken();
									StringTokenizer st3 = new StringTokenizer(Path, "/");
									hostname = st3.nextToken();
									System.out.println("Hostname : "+hostname);
									port = 80;
								}
			
								else {
									String destRaw = st.nextToken();
									httpVersion = st.nextToken();
									StringTokenizer st2 = new StringTokenizer(destRaw, ":");
									hostname = st2.nextToken();
									port = 0;
									try {
										port = Integer.parseInt(st2.nextToken());
									}
									catch (Exception e) {
										System.err.println("Invalid Request");
										return;
									}
								}
								if (true)
									System.err.println(method + "," + hostname + "," + port +
													   " " + httpVersion);

								/* determine the next step */
								// use method from sosProxyWorker
								NextDestination next = nextHop(hostname, 1);
								if (next == null) {
									System.err.println("could not find host");
									return;
								}

								/* We need to connect through a proxy */
								if (!next.direct) {
									// open ssl socket to next proxy
									System.out.println("NOT DIRECT: "+ next.direct + " " + next.hostname);
									SSLSocket proxySocket = (SSLSocket)sslFact.createSocket(next.hostname, 18080);
									proxySocket.setSoTimeout(10000);
									DataOutputStream outToProxy = new DataOutputStream(proxySocket.getOutputStream());
									DataInputStream inFromProxy = new DataInputStream(proxySocket.getInputStream());
									
									if (_isDebugMode) {
										System.out.println(": created a socket to forward requests to proxy on port " + proxySocket.getPort() + " and host " );
										System.out.println(": received request: " + request);
									}	    
						
									//outToProxy.writeBytes("GET HTTP://www.intel.com/ HTTP/1.0" + "\n\n\n");
									outToProxy.writeBytes(request + "\n\n\n");
									outToProxy.flush();		
									byte[] buffer = new byte[1024];
									int len;
									while ((len = inFromProxy.read(buffer)) != -1  ) {
										outToClient.write(buffer, 0, len);
										if (_isDebugMode) {
											System.out.println(": received reply from proxy: " + new String(buffer));
										}
									}
									outToClient.flush();
									System.err.println("Done with client");
								}
		   

								/* connect direct */
								else {
									Socket proxySocket = new Socket(hostname,80);
									DataOutputStream outToProxy = new DataOutputStream(proxySocket.getOutputStream());
									DataInputStream inFromProxy = new DataInputStream(proxySocket.getInputStream());

									if (_isDebugMode) {
										System.out.println(": created a socket to directly connect to " + proxySocket.getPort() + " and host " +hostname );
										System.out.println(": received request: " + request);
									}	    

									
									// forwarding the request to REGULAR host
									outToProxy.writeBytes(request.substring(0,(request.length()-1)) + "0" + "\n\n\n");
									//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) {
											System.out.println(": received reply from proxy: " + new String(buffer));
										}
									}
									outToClient.flush();
								}
							}
							catch (Exception e) {
								if (_isDebugMode) {
									System.out.println(": Error - " + e.getMessage());
								}
							}
							if (_isDebugMode) {
								System.out.println(": exited server thread");
							}
						}
					}
										   );
				thread.start();
			}
			catch (Exception e){
				if (_isDebugMode) {
					System.out.println(": Error - " + e.getMessage());
				}
			}
		}

		try {
			_serverSocket.close();
		}
		catch (Exception e) {
			if (_isDebugMode) {
				System.out.println(": Error - closing server socket - " + e.getMessage());
			}
		}
	}

	public void stopListening()
	{
		_isListening = false;
	}
}


