/*
 * GNU.FREE 2002
 *
 * Copyright (c) 1999, 2000, 2001, 2002
 * The Free Software Foundation (www.fsf.org)
 *
 * GNU.FREE Co-ordinator: Jason Kitcat <jeep@free-project.org>
 *
 * GNU site: http://www.gnu.org/software/free/
 * 
 * FREE e-democracy site: http://www.free-project.org
 *
 * This program is free software; you can redistribute it and/or
 * modify it under the terms of the GNU General Public License
 * as published by the Free Software Foundation; either version 2
 * of the License, or (at your option) any later version.
 *
 * This program is distributed in the hope that it will be useful,
 * but WITHOUT ANY WARRANTY; without even the implied warranty of
 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 * GNU General Public License for more details.
 * 
 * You should have received a copy of the GNU General Public License
 * along with this program (COPYING); if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
 *
 */

package FreeInstall;

import java.security.*;
import java.awt.*;
import java.io.*;
import java.util.*;
import Free.awt.*;

import cryptix.provider.rsa.RawRSAPrivateKey;
import cryptix.provider.rsa.RawRSAPublicKey;
import cryptix.util.core.Hex;

import org.apache.log4j.*;

 /**
  * <code>Install</code> provides a quick graphical way of customising GNU.FREE for a vote.
  *
  * AWT frames guide the user through entering details before having the changes done.
  * Source code is backed up to .bak files while changes are made, these are only deleted
  * after successful alterations. Additionally all changes are logged to <code>FreeInstall.log</code>
  *
  * @version 1.3 1 December 2001
  * @author Jason Kitcat, Rajagopal C.V
  * @since 1.2.1
  */
public class Install {

	private static String[] installStrings;
	private static int installCounter;
	
	private static char quoteMark = '"';

	// logging categories
	protected static Category NORM;
	protected static Category DEV;

	// AWT frames
	protected static RTDBFrame frame8;
	protected static ERDBFrame frame7;
	protected static DBChoiceFrame frame6;
	protected static KeyInstallFrame frame5;
	protected static AuthInstallFrame frame4;
	protected static VoteInstallFrame frame3;
	protected static InstallFrame frame2;
	protected static StatusFrame frame;
	protected static FileFrame frame9;
	
	// Install variables
	protected static String regional_name;
	protected static String totaller_name;
	protected static String er_name;
	protected static String freePort;
	protected static String freeRTPort;
	protected static String party1;
	protected static String party2;
	protected static String party3;
	protected static String party4;
	protected static String passphrase;
	protected static String secret_key1;
	protected static String secret_key2;
	protected static String MAC_key;
	protected static String er_dbuser;
	protected static String er_dbpass;
	protected static int er_dbtype;
	protected static String er_dbname;
	protected static String er_dbhostname;
	protected static String rt_dbuser;
	protected static String rt_dbpass;
	protected static int rt_dbtype;
	protected static String rt_dbname;
	protected static String rt_dbhostname;
	protected static String rt_private_key;
	protected static String er_private_key;
	protected static String fc_private_key;
	protected static String pm_private_key;
	protected static String rt_public_key;
	protected static String er_public_key;
	protected static String fc_public_key;
	protected static String pm_public_key;
	
	// EDL variables
	protected static String config_filename;
	protected static String edl_name;
	protected static String edl_jurisdiction;
	protected static String edl_contact_email;
	protected static String edl_website;
	protected static String[] edl_jnlp_attr;
	protected static String edl_auth_system;
	protected static String[] edl_auth_system_attr;
	protected static String edl_vote_system;
	protected static String[] edl_vote_system_attr;
	protected static String edl_count_system;
	protected static String[] edl_count_system_attr;
	protected static String edl_write_in;
	protected static int edl_number_of_choices;
	protected static String[] edl_choice;
	protected static String[][] edl_choice_attr;

	public Install() {
		
	}

	// Main entry point
	static public void main(String[] args) {
		FreePanel.showSplash(4000);

		// init logging system
		ScreenAppender A1 = new ScreenAppender();
		PropertyConfigurator.configure("log4j.install.properties");			
		NORM = Category.getInstance("NORM");
		NORM.addAppender(A1);
		/* comment out the DEV portion to reduce log detail */
		DEV = Category.getInstance("DEV");
		DEV.addAppender(A1);

		// default vals
		er_dbtype=3;
		rt_dbtype=3;
		installCounter = 0;

		edl_jnlp_attr = new String[5];
		edl_auth_system_attr = new String[5];
		edl_vote_system_attr = new String[5];
		edl_count_system_attr = new String[5];
		edl_choice = new String[50];
		edl_choice_attr = new String[50][5];

		installStrings = new String[100];
		
		try {
			frame = new StatusFrame();
			frame.initComponents();
			frame2 = new InstallFrame();
			frame2.initComponents();
			frame3 = new VoteInstallFrame();
			frame3.initComponents();
			frame4 = new AuthInstallFrame();
			frame4.initComponents();
			frame5 = new KeyInstallFrame();
			frame5.initComponents();
			frame6 = new DBChoiceFrame();
			frame6.initComponents();
			frame7 = new ERDBFrame();
			frame7.initComponents();
			frame8 = new RTDBFrame();
			frame8.initComponents();
			frame9 = new FileFrame();
			frame9.initComponents();
			frame9.setVisible(true);

		} catch (Exception e) {
			NORM.error("Startup Error: " + e.getMessage());
		}

	} //eof main
	
	/**
	 * Creates Public and Private key set for RSA encryption.
	 *
	 * @returns A string array with 0 being private and 1 being the public key, hex encoded.
	 */
	 private static String[] buildRSAKeyPair() {
	 
	 	String[] reskeys = new String[] {"", ""};

        try {  // Cryptix likes to throw exceptions. We'll catch them all.
        
            
            // Let's force it to the Cryptix provider. You don't trust Sun to
            // do that for you, right?
            KeyPairGenerator kpg = KeyPairGenerator.getInstance("RSA","Cryptix");
            
            // No. of bits in the key.
            kpg.initialize(2048);
            
            // Coffee time
            NORM.info("Creating keypair. This may take a while. Get some coffee.");
            KeyPair pair = kpg.generateKeyPair();

            // Separate the twins
            RawRSAPrivateKey seckey = (RawRSAPrivateKey)pair.getPrivate();
            RawRSAPublicKey  pubkey = (RawRSAPublicKey) pair.getPublic ();
            
			/* convert to hex */
            reskeys[0] = Hex.toString(seckey.getEncoded());
            reskeys[1] = Hex.toString(pubkey.getEncoded());
            
        } catch (Exception e) {
			NORM.error("buildRSAKeyPair error: " + e.getMessage());
        }
        
		return reskeys;
        
	} //EOF builRSAKeyPair
     

	/**
	 * Creates a key from a string input using a message digest and also
	 * a date as well as a secure random.
	 *
	 * @param skey  The password to seed the key as a string
	 * @returns  A string of the hex value of key
	 */
	 private static String buildKey(String pass) {
	 
	 	String result="";
	 
	 	try {
			String data = new Date().toString();
		
			SecureRandom sr = new SecureRandom();
			byte[] pseudoRandom = new byte[100];
			sr.nextBytes(pseudoRandom);

		 	MessageDigest md = MessageDigest.getInstance("SHA-1");
	 	
		 	byte dataBytes[] = data.getBytes();
		 	byte passBytes[] = pass.getBytes();
	 		md.update(passBytes);
		 	md.update(dataBytes);
		 	md.update(pseudoRandom);
	 		byte digest1[] = md.digest();
	 	
		 	md.update(passBytes);
		 	md.update(digest1);
	 		byte mac[] = md.digest();
	 
			/* convert to hex */
		 	result = Hex.toString(mac);
		 	
		 } catch (Exception e) {
			NORM.error("buildKey error: " + e.getMessage());
		}

	 	return result;
	 
	 } //eof buildKey


	/**
	 * checks String input to make sure it only contains safe characters.
	 * It's not pretty but regular expression packages tried all were far
	 * too slow.
	 *
	 * @param input  The String to check
	 * @returns  True if the string is ok
	 */
	 protected static boolean isSafe(String input) {
	 
	 	boolean ok = true;
	 	 
		for(int i = 0; (i<input.length())&&(ok==true); i++) {
	 
	 		switch (input.charAt(i)) {
	 			case 'a': ok = true; break;
	 			case 'b': ok = true; break;
	 			case 'c': ok = true; break;
	 			case 'd': ok = true; break;
	 			case 'e': ok = true; break;
	 			case 'f': ok = true; break;
	 			case 'g': ok = true; break;
	 			case 'h': ok = true; break;
	 			case 'i': ok = true; break;
	 			case 'j': ok = true; break;
	 			case 'k': ok = true; break;
	 			case 'l': ok = true; break;
	 			case 'm': ok = true; break;
	 			case 'n': ok = true; break;
	 			case 'o': ok = true; break;
	 			case 'p': ok = true; break;
	 			case 'q': ok = true; break;
	 			case 'r': ok = true; break;
	 			case 's': ok = true; break;
	 			case 't': ok = true; break;
	 			case 'u': ok = true; break;
	 			case 'v': ok = true; break;
	 			case 'w': ok = true; break;
	 			case 'x': ok = true; break;
	 			case 'y': ok = true; break;
	 			case 'z': ok = true; break;
	 			case 'A': ok = true; break;
	 			case 'B': ok = true; break;
	 			case 'C': ok = true; break;
	 			case 'D': ok = true; break;
	 			case 'E': ok = true; break;
	 			case 'F': ok = true; break;
	 			case 'G': ok = true; break;
	 			case 'H': ok = true; break;
	 			case 'I': ok = true; break;
	 			case 'J': ok = true; break;
	 			case 'K': ok = true; break;
	 			case 'L': ok = true; break;
	 			case 'M': ok = true; break;
	 			case 'N': ok = true; break;
	 			case 'O': ok = true; break;
	 			case 'P': ok = true; break;
	 			case 'Q': ok = true; break;
	 			case 'R': ok = true; break;
	 			case 'S': ok = true; break;
	 			case 'T': ok = true; break;
	 			case 'U': ok = true; break;
	 			case 'V': ok = true; break;
	 			case 'W': ok = true; break;
	 			case 'X': ok = true; break;
	 			case 'Y': ok = true; break;
	 			case 'Z': ok = true; break;
	 			case '1': ok = true; break;
	 			case '2': ok = true; break;
	 			case '3': ok = true; break;
	 			case '4': ok = true; break;
	 			case '5': ok = true; break;
	 			case '6': ok = true; break;
	 			case '7': ok = true; break;
	 			case '8': ok = true; break;
	 			case '9': ok = true; break;
	 			case '0': ok = true; break;
	 			case ';': ok = true; break;
	 			case ')': ok = true; break;
	 			case '(': ok = true; break;
	 			case '*': ok = true; break;
	 			case ' ': ok = true; break;
	 			case '=': ok = true; break;
	 			case '-': ok = true; break;
	 			case '_': ok = true; break;
	 			case '+': ok = true; break;
	 			case '@': ok = true; break;
	 			case ',': ok = true; break;
	 			case '.': ok = true; break;
	 			case '<': ok = true; break;
	 			case '>': ok = true; break;
	 			case '?': ok = true; break;
	 			default:  ok = false; break;
	 		} //eof case
	 		
		 } //eof for
	 
	 	return ok;
	 
	 } //eof isSafe


	/**
	 * Sets the installation strings and then processes the files.
	 *
	 */
	protected static void doInstallation() {
	
		NORM.info("GNU.FREE Install 0.8");
		
		/* Read in XML config */
		NORM.info("Reading XML configuration file...");
		ElectionDefinition install_ed = new ElectionDefinition(config_filename);
		NORM.info("File read complete!");
		
	 	try {
			java.security.Security.addProvider(new cryptix.provider.Cryptix());
		} catch(Exception e) {
			NORM.error("Cryptix initialistion error: " + e.toString());
		}
		
		/* Build keys - this takes a while... */
		NORM.info("Please wait... building Authorisation keys.");
		secret_key1 = buildKey(secret_key1);
		secret_key2 = buildKey(secret_key2);
		NORM.info("Keys built.");
		
		/* In fact we only build keys for servers, but room there for expansion */
		NORM.info("Please wait... building RSA keys.");
		String temp[] = new String[] {"",""};
		temp = buildRSAKeyPair();
		rt_private_key = temp[0];
		rt_public_key = temp[1];
		temp = buildRSAKeyPair();
		er_private_key = temp[0];
		er_public_key = temp[1];
		NORM.info("Keys built.");
	
		/* FreeClient.Comms install strings */
		installStrings[0] = "protected static final String R_address = " + quoteMark + regional_name +  quoteMark + ";";
		installStrings[1] = "protected static final String ER_address = " +  quoteMark + er_name + quoteMark +  ";";
		installStrings[2] = "protected static final int freePort = " + freePort + ";";
		installStrings[3] = "protected static final int freeRTPort = " + freeRTPort + ";";
		installStrings[4] = "protected static final String rt_public_key = " + quoteMark + rt_public_key + quoteMark + ";";
		installStrings[5] = "protected static final String er_public_key = " + quoteMark + er_public_key + quoteMark + ";";
		installStrings[6] = "protected static final String fc_private_key = " + quoteMark + fc_private_key + quoteMark + ";";
		/* ERServer.ERServer install strings */
		installStrings[7] = "private static final int freePort = " + freePort + ";";
		installStrings[8] = "protected static final String rt_public_key = " + quoteMark + rt_public_key + quoteMark + ";";
		installStrings[9] = "protected static final String fc_public_key = " + quoteMark + fc_public_key + quoteMark + ";";
		installStrings[10] = "protected static final String pm_public_key = " + quoteMark + pm_public_key + quoteMark + ";";
		installStrings[11] = "protected static final String er_private_key = " + quoteMark + er_private_key + quoteMark + ";";
		/* ERServer.AuthKey install strings */
		installStrings[12] = "private static final String skey1 = " + quoteMark + secret_key1 + quoteMark + ";";
		installStrings[13] = "private static final String skey2 = " + quoteMark + secret_key2 + quoteMark + ";";
		installStrings[14] = "private static final String MACpass = " + quoteMark + MAC_key + quoteMark + ";";
		/* ERServer.DBase install strings */
		if (er_dbtype==3) {
			installStrings[15] = "import org.hsql.*;";
		} else {
			installStrings[15] = "";
		}
		installStrings[16] = "private static final String username = " + quoteMark + er_dbuser + quoteMark + ";";
		installStrings[17] = "private static final String password = " + quoteMark + er_dbpass + quoteMark + ";";
		if (er_dbtype==1) {
			installStrings[18] = "Class.forName(" + quoteMark + "org.gjt.mm.mysql.Driver" + quoteMark + ").newInstance(); //Load the MySQL JDBC driver";
		} else if (er_dbtype==2) {
			installStrings[18] = "Class.forName(" + quoteMark + "org.postgresql.Driver" + quoteMark + "); //Load the PostgreSQL JDBC driver";
		} else {
			installStrings[18] = "Class.forName(" + quoteMark + "org.hsql.jdbcDriver" + quoteMark + "); //Load the Hypersonic SQL JDBC driver";
		}
		if (er_dbtype==1) {
			installStrings[19] = "new JDCConnectionDriver(" + quoteMark + "org.gjt.mm.mysql.Driver" + quoteMark + "," + quoteMark + "jdbc:mysql://" + er_dbhostname + "/" + er_dbname + quoteMark + ",username, password);";
		} else if (er_dbtype==2) {
			installStrings[19] = "new JDCConnectionDriver(" + quoteMark + "org.postgresql.Driver" + quoteMark + "," + quoteMark + "jdbc:postgresql://" + er_dbhostname + "/" + er_dbname + quoteMark + ",username, password);";
		} else {
			installStrings[19] = "new JDCConnectionDriver(" + quoteMark + "org.hsql.jdbcDriver" + quoteMark + "," + quoteMark + "jdbc:HypersonicSQL:erserver" + quoteMark + "," + quoteMark + "sa" + quoteMark + "," + quoteMark + quoteMark + ");";
		}
		/* Free.DBase install strings */
		if (rt_dbtype==3) {
			installStrings[20] = "import org.hsql.*;";
		} else {
			installStrings[20] = "";
		}
		installStrings[21] = "private static final String username = " + quoteMark + rt_dbuser + quoteMark + ";";
		installStrings[22] = "private static final String password = " + quoteMark + rt_dbpass + quoteMark + ";";
		if (rt_dbtype==1) {
			installStrings[23] = "Class.forName(" + quoteMark + "org.gjt.mm.mysql.Driver" + quoteMark + ").newInstance(); //Load the MySQL JDBC driver";
		} else if (rt_dbtype==2) {
			installStrings[23] = "Class.forName(" + quoteMark + "org.postgresql.Driver" + quoteMark + "); //Load the PostgreSQL JDBC driver";
		} else {
			installStrings[23] = "Class.forName(" + quoteMark + "org.hsql.jdbcDriver" + quoteMark + "); //Load the Hypersonic SQL JDBC driver";
		}
		if (rt_dbtype==1) {
			installStrings[24] = "new JDCConnectionDriver(" + quoteMark + "org.gjt.mm.mysql.Driver" + quoteMark + "," + quoteMark + "jdbc:mysql://" + rt_dbhostname + "/" + rt_dbname + quoteMark + ",username, password);";
		} else if (rt_dbtype==2) {
			installStrings[24] = "new JDCConnectionDriver(" + quoteMark + "org.postgresql.Driver" + quoteMark + "," + quoteMark + "jdbc:postgresql://" + rt_dbhostname + "/" + rt_dbname + quoteMark + ",username, password);";
		} else {
			installStrings[24] = "new JDCConnectionDriver(" + quoteMark + "org.hsql.jdbcDriver" + quoteMark + "," + quoteMark + "jdbc:HypersonicSQL:erserver" + quoteMark + "," + quoteMark + "sa" + quoteMark + "," + quoteMark + quoteMark + ");";
		}
		/* Free.AuthKey install strings */
		installStrings[25] = "private static final String skey1 = " + quoteMark + secret_key1 + quoteMark + ";";
		installStrings[26] = "private static final String skey2 = " + quoteMark + secret_key2 + quoteMark + ";";
		installStrings[27] = "private static final String MACpass = " + quoteMark + MAC_key + quoteMark + ";";
		/* Free.Comms install strings */
		installStrings[28] = "protected static final String T_address = " + quoteMark + totaller_name + quoteMark + ";";
		installStrings[29] = "protected static final String ER_address = " + quoteMark + er_name + quoteMark + ";";
		installStrings[30] = "protected static final int freePort = " + freePort + ";";
		installStrings[31] = "protected static final int freeRTPort = " + freeRTPort + ";";
		/* Free.RTServer install strings */
		installStrings[32] = "protected static final String er_public_key = " + quoteMark + er_public_key + quoteMark + ";";
		installStrings[33] = "protected static final String fc_public_key = " + quoteMark + fc_public_key + quoteMark + ";";
		installStrings[34] = "protected static final String rt_private_key = " + quoteMark +  rt_private_key + quoteMark + ";";
		installStrings[35] = "protected static final String rt_public_key = " + quoteMark + rt_public_key + quoteMark + ";";
		installStrings[36] = "protected static final String vote_system = " + quoteMark + edl_vote_system_attr[0] + "-" + edl_vote_system_attr[1] + quoteMark + ";";
		installStrings[37] = "protected static final String count_system = " + quoteMark + edl_count_system_attr[0] + quoteMark + ";";
		installStrings[38] = "protected static final int number_of_choices = " + edl_number_of_choices + ";";
		installStrings[39] = "protected static final String write_in = " + quoteMark + edl_write_in + quoteMark + ";";
		/* Free.util.AuthSys install strings */
		installStrings[40] = "private static final String phrase = " + quoteMark + passphrase + quoteMark + ";";
		/* PollManager.Comms install strings */
		installStrings[41] = "protected static final String ER_address = " + quoteMark + er_name + quoteMark + ";";
		installStrings[42] = "protected static final int freePort = " + freePort + ";";
		installStrings[43] = "protected static final String er_public_key = " + quoteMark + er_public_key + quoteMark + ";";
		installStrings[44] = "protected static final String pm_private_key = " + quoteMark + pm_private_key + quoteMark + ";";
		/* FreeClient.FClient install strings */
		installStrings[45] = "protected static final String vote_system = " + quoteMark + edl_vote_system_attr[0] + "-" + edl_vote_system_attr[1] + quoteMark + ";";
		installStrings[46] = "protected static final int number_of_choices = " + edl_number_of_choices + ";";
		installStrings[47] = "protected static final String write_in = " + quoteMark + edl_write_in + quoteMark + ";";
		/* FreeClient.VoteFrame install strings */
		installStrings[48] = "private final String cand1name = " + quoteMark + edl_choice[0] + quoteMark + ";";
		installStrings[49] = "private final String cand1code = " + quoteMark + edl_choice_attr[0][0] + quoteMark + ";";
		installStrings[50] = "private final String cand2name = " + quoteMark + edl_choice[1] + quoteMark + ";";
		installStrings[51] = "private final String cand2code = " + quoteMark + edl_choice_attr[1][0] + quoteMark + ";";
		installStrings[52] = "private final String cand3name = " + quoteMark + edl_choice[2] + quoteMark + ";";
		installStrings[53] = "private final String cand3code = " + quoteMark + edl_choice_attr[2][0] + quoteMark + ";";
		installStrings[54] = "private final String cand4name = " + quoteMark + edl_choice[3] + quoteMark + ";";
		installStrings[55] = "private final String cand4code = " + quoteMark + edl_choice_attr[3][0] + quoteMark + ";";

		
		boolean t = false;
		
		t = processFile("FreeClient/Comms.java");
		t = processFile("ERServer/ERServer.java");
		t = processFile("ERServer/AuthKey.java");
		t = processFile("ERServer/DBase.java");
		t = processFile("Free/DBase.java");
		t = processFile("Free/AuthKey.java");
		t = processFile("Free/Comms.java");
		t = processFile("Free/RTServer.java");
		t = processFile("Free/util/AuthSys.java");
		t = processFile("PollManager/Comms.java");
		t = processFile("FreeClient/FClient.java");
		t = processFile("FreeClient/VoteFrame.java");
		
		NORM.info("INSTALLATION COMPLETE!");
		
		frame.jButton1.setVisible(true);		
	
	} //EOF doInstallation()



	/**
	 * This code is based on <code>CodeSwitcher</code> from the Hypersonic SQL
	 * distribution.
	 *
	 * @param name	Filename to check for tags and alter
	 * @returns		Boolean of success or failure
	 */
	private static boolean processFile(String name) {
	
		File f=new File(name);
		File fnew=new File(name+".new");
		int state=0; // 0=normal 1=inside_if 2=inside_else
		boolean working=false;
		try {
			LineNumberReader read=new LineNumberReader(new FileReader(f));
			FileWriter write=new FileWriter(fnew);
			NORM.info("Opened " + name + " for editing.");
			int i=1;
			while(true) {
				String line=read.readLine();
				if(line==null) {
					break;
				}
				if(working) {
					if(line.equals("/*") || line.equals("*/")) {
						continue;
					}
				}
				if(!line.startsWith("//#")) { //if no token reprint line
					if (working) {
						write.write(installStrings[installCounter] + "\r\n");
						DEV.info("Wrote: " + installStrings[installCounter]);
						installCounter++;
						working = false;
						state = 0;
					} else {
						write.write(line+"\r\n");
					}
				} else {  //otherwise we've hit a token
					int t=0;
					if(line.startsWith("//#ifdef ")) {
						if(state!=0) {
							printError("'#ifdef' not allowed inside '#ifdef'");
							return false;
						}
						write.write(line+"\r\n");
						state=1;
						String s=line.substring(9);
						if(s.equals("INSTALL")) {
							working=true;
						}
					}
				}
			}
		
			read.close();
			write.flush();
			write.close();
			File fbak=new File(name+".bak");
			fbak.delete();
			f.renameTo(fbak);
			File fcopy=new File(name);
			fnew.renameTo(fcopy);
			fbak.delete();
			NORM.info(name + " edited successfully!");
			return true;
		} catch(Exception e) {
			printError(e.getMessage());
			return false;
		}
		
	} //EOF processFile()
	
	
	static void printError(String e) {

		NORM.error("Problem with: " + e);
  
 	} //EOF printError()


}  //EOF Install
