package servlet;

import com.mortbay.Base.*;					// Jetty server
import com.mortbay.Util.*;
import com.mortbay.HTTP.*;
import com.mortbay.HTTP.Handler.*;
import com.mortbay.HTTP.Configure.*;

import COM.claymoresystems.cert.*;			// SSL
import COM.claymoresystems.ptls.*;
import COM.claymoresystems.sslg.*;

import java.io.*;
import java.net.*;
import javax.servlet.*;
import java.util.*;

import Tools.*;

/**
 * A Jetty Configuration class for an SSL HTTP server.
 * It is not hard to combine this server with a regular
 * HTTP server in a single process; see {@link #fourServers}.
 */
public class SSLServerConfig
	extends com.mortbay.HTTP.Configure.BaseConfiguration
	implements SSLConfiguration {
    
	/**
	 * Using the command line options in <code>opts</code>, configure
	 * the SSL server handler.
	 */
    public SSLServerConfig(Options opts)
         throws IOException {

        // Listen at a single port on the localhost
        addresses=new InetAddrPort[1];
        addresses[0]=new InetAddrPort(null,opts.getInt("port"));

        // Create single stack of HttpHandlers at "/"
        httpHandlersMap=new PathMap();
        HttpHandler[] httpHandlers = new HttpHandler[5];
        httpHandlersMap.put("/",httpHandlers);
        int h=0;

        // Parameter handler
        httpHandlers[h++] = new SessionHandler();
        
        // Parameter handler
        httpHandlers[h++] = new ParamHandler();

		// establish ssl context
		if ("SSL".equals(opts.get("socket"))) {
			setSSLContext(opts);
			listenerClassPrototype = SSLListener.class;
		} else {
			listenerClassPrototype = NaglessListener.class;
		}

        Properties properties;

        // File Handler
        properties = new Properties();
		properties.put("FILES.root.DIRECTORY",
			opts.get("fileBase"));
		properties.put("FILES.root.PATHS", "/");
		httpHandlers[h++] = new FileHandler(properties);

        // NotFound Handler
        httpHandlers[h++] = new NotFoundHandler();
    }

	/**
	 * Listen with an SSLListener so the sockets are SSL sockets (To
	 * extend this config to support both Http and SSL, listen on two
	 * ports with different handlers.)
	 */
	public Class[] listenerClasses() {
		if (listenerClasses==null) {
			listenerClasses = new Class[addresses.length];
			for (int i=0; i<listenerClasses.length; i++) {
				listenerClasses[i] = listenerClassPrototype;
			}
		}
		return listenerClasses;
	}

	Class listenerClassPrototype;
	SSLContext context;

	/**
	 * Implements SSLConfiguration by supplying the requested
	 * SSLContext object.<p>
	 *
	 * I'd use the getAttributes() mechanism in
	 * HttpServer/HttpConfiguration, but it is deprecated for
	 * getProperties.	I'd use the getProperties() mechanism, but
	 * it only returns strings.
	 */
	public SSLContext getSSLContext() {
		return context;
	}

	/**
	 * Establish an SSLContext according to the options.
	 */
	public void setSSLContext(Options opts) {
		try {
			context = new SSLContext();
	
			// set up the policy (local administrative preferences)
			SSLPolicyInt policy = new SSLPolicyInt();
			policy.acceptUnverifiableCertificates(
				opts.getBoolean("acceptUnverifiableCertificates"));
			context.setPolicy(policy);
		
			// ctx.loadRootCertificates("/u/jonh/puretls-0.9b1/root.pem");
			context.loadRootCertificates(opts.get("rootCertificate"));
			context.loadEAYKeyFile(opts.get("serverKey"),
				opts.get("serverPassword"));
	
			context.seedRNG(null);
				// "fakeseed" TODO bad bad bad random number seeding
		} catch (FileNotFoundException ex) {
			context = null;
			throw new RuntimeException(ex.toString());
		} catch (IOException ex) {
			context = null;
			throw new RuntimeException(ex.toString());
		}
	}

	/**
	 * Configure an SSL server from the command line.
	 */
    public static void main(String args[]) {
		Options opts = new Options(args) {
			public void defineOptions() {
				programName = "SecureServerConfig";
				defineOption(
		"fourServers", "set up four standard servers", "false");
				defineOption(
		"serverType", "Jetty or simple", "Jetty");
				defineOption(
		"port", "Port number server listens on", "4443");
				defineOption(
		"socket", "Socket type to listen with (plain or SSL)", "SSL");
				defineOption(
		"fileBase", "location in filesystem of root of URL hierarchy",
			"/usr/local/apache/htdocs/");
				defineOption(
		"acceptUnverifiableCertificates", "", "false");
				defineOption(
		"rootCertificate", "location of root certificate file",
			"/usr/local/apache/conf/ssl.crt/snakeoil-ca-rsa.crt");
				defineOption(
		"serverKey", "location of server key file",
			"/usr/local/apache/conf/ssl.key/server.key+cert");
		// TODO dumb thing to put on command line ...
				defineOption(
		"serverPassword", "server password", "password");
				defineOption(
		"bg", "run server in background (meaningless from cmd line)", "false");
			}
		};
		if (opts.getBoolean("fourServers")) {
			fourServers();
		} else {
			Runnable runnable = new ServletRunner(opts);
			if (opts.getBoolean("bg")) {
				Thread thread = new Thread(runnable);
				thread.start();
			} else {
				runnable.run();
			}
		}
    }

	static class ServletRunner implements Runnable {
		Options opts;
		public ServletRunner(Options opts) {
			this.opts = opts;
		}
		public void run() {
			try {
				System.out.println("Options for this server:");
				opts.dumpOptions();
           		SSLServerConfig serverConfig;
				if (opts.get("serverType").equals("simple")) {
					serverConfig = new SSLServerConfig(opts);
					serverConfig.runSimpleServer(opts);
				} else {
            		serverConfig = new SSLServerConfig(opts);
   					HttpServer httpServer = new HttpServer(serverConfig);
  					httpServer.join();
				}
			} catch (Exception ex) {
				ex.printStackTrace();
			}
		}
	}

	/**
	 * Configure a set of four servers for use with {@link
	 * timingexp.HttpExp}.  Two are SSL, two are plain; two use Jetty,
	 * two provide simple HTTP service using the inner class
	 * {@link SSLServerConfig.SimpleServer}.
	 */
	public static void fourServers() {
		String argsets[][] = {
		{"-bg=true", "-port=8180", "-serverType=simple", "-socket=plain"},
		{"-bg=true", "-port=8280", "-serverType=Jetty", "-socket=plain"},
		{"-bg=true", "-port=8143", "-serverType=simple", "-socket=SSL"},
		{"-bg=true", "-port=8243", "-serverType=Jetty", "-socket=SSL"}
			};
		// -join causes main to return immediately.
		for (int i=0; i<argsets.length; i++) {
			main(argsets[i]);
		}
		// now join on last server?
	}

	/**
	 * Instantiate and run a simple server using the specified option
	 * array.
	 */
	public void runSimpleServer(Options opts) {
		(new SimpleServer()).run(opts);
	}

	/**
	 * A very simple Java HTTP server. This gives us a baseline for the
	 * overhead associated with Jetty's sophisticated request and
	 * stream handling.
	 */
	public class SimpleServer {
		/**
		 * Configure the server using the options, and loop, handling
		 * one request at a time.
		 */
		public void run(Options opts) {
			// SSLDebug.setDebug(SSLDebug.DEBUG_CODEC);

			ServerSocket serverSocket;
			try {
				if (opts.get("socket").equals("SSL")) {
					setSSLContext(opts);
					serverSocket = new SSLServerSocket(context,
						opts.getInt("port"));
				} else {
					serverSocket = new ServerSocket(opts.getInt("port"));
				}
			} catch (IOException ex) {
				ex.printStackTrace();
				return;
			}

			File rootDir = new File(opts.get("fileBase"));

			System.out.println("Simple server ready.");
			while (true) {
				Socket socket = null;
				try {
					socket = serverSocket.accept();
					socket.setTcpNoDelay(true);
					BufferedReader br = new BufferedReader(
						new InputStreamReader(socket.getInputStream()));
					String firstLine = null;
					while (true) {
						String requestLine = br.readLine();
						if (firstLine==null) {
							firstLine = requestLine;
						}
						if (requestLine.length()==0) {
							break;
						}
						char c0 = requestLine.charAt(0);
						if (c0=='\n' || c0=='\r') {
							break;
						}
					}
	
					if (firstLine==null) {
						throw new IOException("Bad request");
					}

					int i0=-1, i1=-1;
					i0 = firstLine.indexOf(' ');
					if (i0>=0) {
						i1 = firstLine.indexOf(' ', i0+1);
					}
					if (i0<0 || i1<0) {
						throw new IOException("Unparseable request");
					}
					String resource = firstLine.substring(i0+1, i1);
					if (resource.charAt(0)=='/') {
						resource = resource.substring(1);
					}
					if (resource.equals("")) {
						resource = ".";
					}

					// vet path to ensure it doesn't have any /'s in it
					if (resource.indexOf("..")>=0) {
						throw new IOException("Tainted path");
					}
					if (resource.startsWith("/")) {
						throw new IOException("Tainted path");
					}

					// make sure it's a file
					File f = new File(rootDir, resource);
					if (!f.exists() || !f.isFile()) {
						throw new IOException("File not found");
					}

					OutputStream os = socket.getOutputStream();
					PrintWriter wr = new PrintWriter(
						new OutputStreamWriter(os));
					wr.println("HTTP/1.0 200 Yahooo!");
					wr.println("Content-type: text/plain");
					wr.println("Content-length: "+f.length());
					wr.println();
					wr.flush();
					CopyStream.copyStream(new FileInputStream(f), os);
					os.flush();
	
					socket.close();
					// go get another request
				} catch (IOException ex) {
					ex.printStackTrace();
					try {
						OutputStream os = socket.getOutputStream();
						os.write(("HTTP/1.0 500 Crud.\n"
							+"Content-type: text/plain\n\nServer error:\n")
							.getBytes());
						PrintStream ps = new PrintStream(os);
						ex.printStackTrace(ps);
						ps.close();
					} catch (IOException ex2) {}
					try {
						socket.close();
					} catch (IOException ex2) {}
				}
			}
		}
	}
};
