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

import settings.Settings;
import util.Utilities;
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;


/**
 * SSLSocketClient.java
 *
 * SSLSocketClient opens an SSLSocket connection to SSLSocketServer and tunnels the requests from 
 * the browser to the SSLSocketServer via the secure tunnel
 *
 * Copyright Columbia University. Computer Science Department
 */
public class SSLSocketClient {

	private Map _settings = Settings.instance();
	private SSLSocketFactory _socketFactory = null;
	private boolean _isDebugMode =
		Debug.isEnabled(SSLSocketClient.class.getName());

	public SSLSocketClient() {
		createSocket();

	}
	// initializes the SSL socket factory based on configuration
	private void createSocket() {
		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();
			URL keyurl =
				new	URL	((String) _settings.get(Settings.KEY_URL));
			ks.load(new BufferedInputStream(keyurl.openStream()),
					((String) _settings.get(Settings.KEY_STORE_PASSWORD)).
					toCharArray());
			tmf.init(ks);
			kmf.init(ks, passphrase);
			ctx.init(kmf.getKeyManagers(), tmf.getTrustManagers(), null);

			_socketFactory = ctx.getSocketFactory();

		}
		catch(Exception e) {
			if (_isDebugMode) {
				Debug.writeln(getClass().getName() +
							  ": Error - could not initialize ssl socket factory - "
							  + e.getMessage());
			}
		}
	}


	/*
	 * Accepts a new browser request, creates the new SSLSocket connection and forwards the data
	 * from the input stream to the tunnel, and from the tunnel back to the output stream.
	 */
	public void sendRequest(final DataInputStream inFromBrowser_,
							final DataOutputStream outToBrowser_) {
		try {

			if (_isDebugMode) {
				Debug.writeln(getClass().getName() + ": sendRequest()");
			}
			// creating a new Socket with Encryption SSLSocket
			SSLSocket socket = 
				(SSLSocket)_socketFactory.createSocket((String)_settings.get(Settings.SERVER_HOSTNAME), 
													   ((Integer)_settings.get(Settings.SERVER_PORT)).intValue());
			//This is a Socket without encryption
			/*final Socket socket =
				new Socket((String) _settings.get(Settings.PROXY_HOSTNAME),
						   ((Integer) _settings.get(Settings.PROXY_PORT)).
						   intValue());*/
			// do the ssl handshake
			socket.startHandshake();
			
			DataOutputStream outToServer = new DataOutputStream(socket.getOutputStream());
			DataInputStream inFromServer = new DataInputStream(socket.getInputStream());

			// initial browser request
			String request =
				(new BufferedReader(new InputStreamReader(inFromBrowser_))).readLine();

			// forwarding the request to the server
			outToServer.writeBytes(request.trim() + "\n\n");
			outToServer.flush();
			if (_isDebugMode) {
				Debug.writeln(getClass().getName() +
							  ": sending initial browser request to server - "
							  + request);
			}
			int len = 0;
			byte[]buffer = new byte[1024];
			if (inFromBrowser_ != null && outToBrowser_ != null) {	//Test if Sockets are valid
				boolean done_listening = false, done_reading = false;
				while (!done_reading && !done_listening ) {
					buffer = new byte[1024];
					// reading the response from server and forwarding it to the browser
					while ((len = inFromServer.read(buffer)) != -1) {
						done_reading = false;
						outToBrowser_.write(buffer, 0, len);
						if (_isDebugMode) {
							Debug.write(getClass().getName() +
										": received reply from server - " +
										new String(buffer));
						}
					}
					done_reading= true;
					outToBrowser_.flush();
					buffer = new byte[1024];
					// reading the response from the browser and forwarding it to the server
					while ((len = inFromBrowser_.read(buffer)) != -1) {
						done_listening = false;
						outToServer.write(buffer, 0, len);
						if (_isDebugMode) {
							Debug.write(getClass().getName() +
										": received from browser - " +
										new String(buffer));
						}
					}
					done_listening = true;
					outToServer.flush();
				}
			} else {
				buffer = new byte[1024];
				outToServer.writeBytes("GET www.cs.columbia.edu HTTP/1.0\n\n");	// testing from main
				outToServer.flush();
				while ((len = inFromServer.read(buffer)) != -1) {
					if (_isDebugMode) {
						Debug.write(getClass().getName() +
									": TESTING: received reply from server - "
									+ new String(buffer));
					}
				}
			}
			if (_isDebugMode) {
				Debug.write(getClass().getName() +
							"\n Server Socket Closed \n");
			}
			socket.close();

			if (_isDebugMode) {
				Debug.write(getClass().getName() +
							": exited client thread");
			}
		}
		catch(Exception ex) {
			if (_isDebugMode) {
				Debug.writeln(getClass().getName() + ": " +
							  ex.getMessage());
			}
		}
	}


	public static void main(String[]args_) {
		SSLSocketClient socketClient = new SSLSocketClient();
		socketClient.sendRequest(null, null);
	}

}
