/* 
 * The contents of this file are subject to the Mozilla Public
 * License Version 1.1 (the "License"); you may not use this file
 * except in compliance with the License. You may obtain a copy of
 * the License at http://www.mozilla.org/MPL/
 * 
 * Software distributed under the License is distributed on an "AS
 * IS" basis, WITHOUT WARRANTY OF ANY KIND, either express or
 * implied. See the License for the specific language governing
 * rights and limitations under the License.
 * 
 * The Original Code is the Netscape Security Services for Java.
 * 
 * The Initial Developer of the Original Code is Netscape
 * Communications Corporation.  Portions created by Netscape are 
 * Copyright (C) 1998-2000 Netscape Communications Corporation.  All
 * Rights Reserved.
 * 
 * Contributor(s):
 * 
 * Alternatively, the contents of this file may be used under the
 * terms of the GNU General Public License Version 2 or later (the
 * "GPL"), in which case the provisions of the GPL are applicable 
 * instead of those above.  If you wish to allow use of your 
 * version of this file only under the terms of the GPL and not to
 * allow others to use your version of this file under the MPL,
 * indicate your decision by deleting the provisions above and
 * replace them with the notice and other provisions required by
 * the GPL.  If you do not delete the provisions above, a recipient
 * may use your version of this file under either the MPL or the
 * GPL.
 */

// THIS FILE NEEDS CLEANING UP

// This program demonstrates SSL server-side support. It expects
// (HTTP) clients to connect to it, and will respond to HTTP
// GET requests 
//

// tabstops: 4

import org.mozilla.jss.ssl.*;

import java.io.*;
import java.util.*;
import org.mozilla.jss.*;

/**
 * Parameters supported by this socket test:
 * 
 * filename 	file to be read from https server (default: /index.html)
 * port 	port to connect to (default: 443)
 * clientauth 	do client-auth or not (default: no client-auth)
 *
 * The following parameters are used for regression testing, so
 * we can print success or failure of the test.
 * 
 * filesize 	size of file to be read
 * status 	security status of connection - this has to be an integer
 * 
 */

public class SSLServer 
{
    boolean     handshakeEventHappened = false;
    boolean     doClientAuth = false;
    Hashtable   args;
    PrintStream results;
    String      versionStr;

    String argNames[] = {
	"filename",
	"port",
	"filesize",
	"clientauth",
	"nickname"
    };

    String values[] = {
	"data1k.txt",           // filename
	"2929",			// port
	"1024",			// filesize
	"true",	        // request client auth 
	"Server-Cert cert-meatpie"             // nickname of cert to use
    };

    private static String htmlHeader = "SSL Server Tester";
    private static String htmlTail   = "\n";

    /* simple helper functions */
    private boolean isInvalid(String s) {
	return (s == null) || s.equals("");
    }

    private String getArgument(String key) {
	return (String) args.get(key);
    }

    String okay = "okay";
    String failed = "FAILED";

    /*
     * return "okay" or "FAILED" based on equality of
     * the argument strings
     */
    private String cmp(String s1, String s2) {
	if(s1 == s2) return okay;
	if(s1 == null) return failed;
	if(s1.equals(s2)) return okay;
	return failed;
    }

    private String cmp(String s1, int s2) {
	return cmp(s1, new Integer(s2).toString());
    }


    public void run() 
    {
	try {
	    SSLServerSocket l;
	    SSLSocket       s;
	    int             port;
	    String          tmpStr;
	    String          portStr;
	    String          nickname;

	    results.println(htmlHeader);

	    portStr = getArgument("port");
	    if(isInvalid(portStr)) {
		port = 2929;
	    } else {
		port = Integer.valueOf(portStr).intValue();
	    }
	    
	    results.println("here");
	    tmpStr = getArgument("clientauth");
	    if(!isInvalid(tmpStr)) {
		tmpStr       = tmpStr.toLowerCase();
		doClientAuth = !(tmpStr.equals("off") ||
				 tmpStr.equals("false") ||
				 tmpStr.equals("0"));
	    }

	    l = new SSLServerSocket(port);
	    results.println("Listening " + l.toString());

	    // select the server's cert/key
	    nickname = getArgument("nickname");
	    results.println("Getting Cert:"+nickname+"");
	    l.setServerCertNickname(nickname);

	    if (doClientAuth) {
	    	l.setNeedClientAuth(true);
	    }

	    // wait for and accept a connection.
            s = (SSLSocket)l.accept();
	    results.println("Accepted.");

	    handleConnection(s);
	    // handleConnection will close s, as appropriate.
	    s = null;

	    l.close();
	    l = null;

	} catch(Exception e) {
	    results.println("***** TEST FAILED *****");
	    e.printStackTrace(results);
	    results.println(
"If there is no stack trace, try disabling the JIT and trying again.");
	}
	results.println("END OF TEST");
    }

    public void handleConnection(SSLSocket s) throws Exception
    {
	SSLHandshakeCompletedListener listener = null;

	listener = new ServerHandshakeCB(this);
	s.addHandshakeCompletedListener(listener);

	results.println("Connected to " + s.toString());

	InputStream in         = s.getInputStream();
	byte[]      bytes      = new byte[4096];
	int         totalBytes = 0;
	int         numReads   = 0;

	// now try to read data from the SSL connection
	try {
	    boolean endFound = false;
	    while(! endFound && totalBytes < bytes.length) {
		results.println("Calling Read.");
		int n = in.read(bytes, totalBytes, 
				bytes.length - totalBytes);
		if(n == -1) {
		    results.println("EOF found.");
		    break;
		}

		if(n == 0) {
		    results.println("Zero bytes read?");
		    break;
		}
		numReads++;

		results.println("Read " + n + " bytes of data");
		totalBytes += n;
		for (int i = 0; i + 3 < bytes.length; ++i) {
		    if (bytes[i] == 0x0D &&
		        bytes[i+1] == 0x0A &&
		        bytes[i+2] == 0x0D &&
		        bytes[i+3] == 0x0A )
		    {
			results.println("Empty line found.");
		    	endFound = true;
			break;
		    }
		}
	    }
	} catch (IOException e) {
	    results.println(
		"IOException while reading from pipe?  Actually got " + 
		totalBytes + " bytes total");
	    e.printStackTrace(results);
	    results.println("");
	    throw e;
	} 

	results.println("Number of read() calls: " + numReads );
	results.println("Total bytes read:       " + totalBytes );
	String msg = null;

	if (totalBytes > 0) {
	    msg = new String(bytes, 0, totalBytes, "8859_1");
	    results.println("Request received:");
	    results.println(msg);
	    results.println("");
	}


	SSLSecurityStatus status = s.getStatus();
	results.println("Security status of session:");
	results.println(status.toString());

	results.println("Handshake callback event happened: " +
			((handshakeEventHappened) ? okay : failed));

	OutputStream            os  = s.getOutputStream();
	PrintOutputStreamWriter out = new PrintOutputStreamWriter(os);
	os = null;	// don't keep this reference lying around

	String reply =  "HTTP/1.0 200 OK\r\n" +
			"Server: Netscape-Enterprise/2.0a\r\n" +
			"Date: Tue, 01 Apr 1998 22:10:05 GMT\r\n" +
			"Content-type: text/plain\r\n" +
			"\r\n" +
			msg;
	out.println(reply);

	// Got here, so no exception thrown above.
	// Try to clean up.
	in.close();
	in = null;

	if (listener != null) {
	    s.removeHandshakeCompletedListener(listener);
	    listener = null;
	}

	out.close();
	out = null;

	s.close();
	s = null;
    }

    /**
     * given an input string, convert less-than, greater-than, and ampersand
     * from raw characters to escaped characters
     * (&lt; becomes `&amp;lt;', etc.)
     */
    private String escapeHTML(String s) 
    {
	StringBuffer result = new StringBuffer();

	// this is inefficient, but I don't care
	for(int i=0; i<s.length(); i++) {
	    char c = s.charAt(i);
	    switch(c) {
	    case '<':	result.append("&lt;");	break;
	    case '>':	result.append("&gt;");	break;
	    case '&':	result.append("&amp;"); break;
	    default:	result.append(c);	break;
	    }
	}

	return result.toString();
    }

    public SSLServer( PrintStream ps, String verStr)
    {
	this.args       = new Hashtable();
	this.results    = ps;
	this.versionStr = verStr;

	for(int i=0; i<argNames.length; i++) {
	    String value = values[i];
	    if(value != null)
		this.args.put(argNames[i], value);
	}
    }

    static final int cipherSuites[] = {
    	SSLSocket.SSL3_RSA_WITH_RC4_128_MD5,
	SSLSocket.SSL3_RSA_WITH_3DES_EDE_CBC_SHA,
	SSLSocket.SSL3_RSA_WITH_DES_CBC_SHA,
	SSLSocket.SSL3_RSA_EXPORT_WITH_RC4_40_MD5,
	SSLSocket.SSL3_RSA_EXPORT_WITH_RC2_CBC_40_MD5,
	SSLSocket.SSL3_RSA_WITH_NULL_MD5,
	0
    };

    public static void main(String argv[]) 
    {
	int i;

        System.out.println("SSLServer started\n");

	try {
		NSSInit.initialize("secmod.db", "key3.db", "cert7.db");
	}
	catch (KeyDatabaseException kdbe) {
          System.out.println("Couldn't open the key database\n");
	  return;
	}
	catch (CertDatabaseException cdbe) {
	  System.out.println("Couldn't open the certificate database");
	  return;
	}
    catch (org.mozilla.jss.crypto.AlreadyInitializedException aie) {
      System.out.println("CryptoManager already initialized???");
      return;
    }
	  
	SSLServerSocket.configServerSessionIDCache(10, 0, 0, null);

	/* enable all the SSL2 cipher suites */
	for (i = SSLSocket.SSL2_RC4_128_WITH_MD5;
	     i <= SSLSocket.SSL2_DES_192_EDE3_CBC_WITH_MD5; ++i) {
//	    SSLSocket.setPermittedByPolicy(i, SSLSocket.SSL_ALLOWED);
	    SSLSocket.setCipherPreference( i, true);
	}

	/* enable all the SSL3 cipher suites */
	for (i = 0; cipherSuites[i] != 0;  ++i) {
//	    SSLSocket.setPermittedByPolicy(cipherSuites[i], SSLSocket.SSL_ALLOWED);
	    SSLSocket.setCipherPreference( cipherSuites[i], true);
	}

	SSLServer x = new SSLServer(System.out, "Stand alone Ver 0.01");
	x.run();
    }

}


class ServerHandshakeCB implements SSLHandshakeCompletedListener {
    SSLServer sc;

    ServerHandshakeCB(SSLServer sc) {
	this.sc = sc;
    }

    public void handshakeCompleted(SSLHandshakeCompletedEvent event) {
	sc.handshakeEventHappened = true;
	sc.results.println("handshake happened");
    }
}

