/*
 * 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 Free;

import java.io.*;
import java.sql.*;
import java.util.*;
//#ifdef INSTALL
import org.hsql.*;

import cryptix.provider.key.RawSecretKey;
import cryptix.provider.padding.OneAndZeroes;
import cryptix.provider.cipher.Blowfish;
import cryptix.util.core.Hex;

import xjava.security.Cipher;
import xjava.security.FeedbackCipher;
import xjava.security.SecretKey;

import java.security.*;

import Free.util.*;
import Free.DBPool.*;

/**
 * DBase creates databases, tables and interacts with these through JDBC and SQL.
 *
 * Implementation of the databases is with 
 * <a href="http://hsql.oron.ch">Hypersonic SQL</a> which is written
 * totally in Java. The logic for calculating totals is also performed in this class.
 * 
 * @version 1.8  27 October 2001
 * @author Jason Kitcat
 */
public class DBase {

//#ifdef INSTALL
	private static final String username = "sa";
//#ifdef INSTALL
	private static final String password = "";
	//USER SUPPLIED SESSION WORD
	protected static String cryptword = "";
	

	/**
	 * initialises the Hypersonic SQL database.
	 *
	 */
	protected static void init() {
	
		try {

//#ifdef INSTALL
			Class.forName("org.hsql.jdbcDriver"); // Load the Hypersonic SQL JDBC driver

//#ifdef INSTALL
			new JDCConnectionDriver("org.hsql.jdbcDriver","jdbc:HypersonicSQL:rtserver",username, password);
			
			/* Get a connection from the pool */
			Connection freeConn = getConnection();
			Statement freeStat = freeConn.createStatement();
			
			/* make table for storing votes */
			if (RTServer.serverType == 'R') {  // regional mode
				try {
					ResultSet r = freeStat.executeQuery("SELECT choice FROM Votes"); //check for the table
				} catch (SQLException sqle) {
					if(RTServer.DEV.isDebugEnabled()) {
						RTServer.DEV.debug("No Votes table so creating");
					}
					freeStat.execute("CREATE TABLE Votes(choice VARCHAR(255))");
					if(RTServer.DEV.isDebugEnabled()) {
						RTServer.DEV.debug("No AuthKeys table so creating");
					}
					freeStat.execute("CREATE TABLE AuthKeys(ak VARCHAR(255))");
					if(RTServer.DEV.isDebugEnabled()) {
						RTServer.DEV.debug("No Test_Keys table so creating");
					}
					freeStat.execute("CREATE TABLE Test_Keys(ak VARCHAR(255))");
					// now import keys from file
					importKeys();
				}
			} else {						   // totaller mode
				if (RTServer.vote_system.equals("fptp-default")) {  // first past the post default implementation
					try {
						ResultSet r = freeStat.executeQuery("SELECT choice FROM Totals"); //check for the table
					} catch (SQLException sqle) {
						if(RTServer.DEV.isDebugEnabled()) {
							RTServer.DEV.debug("No Totals table so creating");
						}
						freeStat.execute("CREATE TABLE Totals(choice VARCHAR(255), total VARCHAR(12))");
					}
				} else if (RTServer.vote_system.equals("borda-default")) {  // borda system default implentation
					try {
						ResultSet r = freeStat.executeQuery("SELECT choice FROM Totals"); //check for the table
					} catch (SQLException sqle) {
						if(RTServer.DEV.isDebugEnabled()) {
							RTServer.DEV.debug("No Totals table so creating");
						}
						freeStat.execute("CREATE TABLE Totals(choice VARCHAR(255), total VARCHAR(12))");
					}
				} else {  // unrecognised voting system
					RTServer.NORM.fatal("DBase error: Unrecognised voting system specified - " + RTServer.vote_system);
					RTServer.NORM.fatal("The system will not continue successfully - no tables created.");
				}
			}
			
			DatabaseMetaData dbMetaData = freeConn.getMetaData();
			
			freeStat.close();
			
			RTServer.NORM.info("Database initialised.");
			RTServer.DEV.debug("DB Using: "  + dbMetaData.getDatabaseProductName() + " " + dbMetaData.getDatabaseProductVersion());
			
			// initialise crypto
		 	try {
				java.security.Security.addProvider(new cryptix.provider.Cryptix());
			} catch(Exception e) {
				RTServer.NORM.error("DBase crypto initialistion error: " + e.toString());
			}


		} catch(Exception e) {
			RTServer.NORM.error("Database error: " + e.toString());
		}
			
	} //EOF init()


	/**
	 * Provides a database connection from the pool.
	 *
	 * @returns  A working connection
	 */
	private static Connection getConnection() throws SQLException {
        return DriverManager.getConnection("jdbc:jdc:jdcpool");
    }
    
	
	/**
	 * Encrypts string data before being inserted into database
	 *
	 * @param data  The string to encrypt
	 * @returns  Encrypted string
	 */
	 private static String encrypt(String data) throws Exception {
	 
	 	RawSecretKey key;
	 	byte[] ect;
		Cipher blowalg = Cipher.getInstance(new Blowfish(),null,new OneAndZeroes());
	 	
		/* build a Blowfish (16-round) key using ECB */
		key = new RawSecretKey("Blowfish", Hex.fromString(cryptword));
        
		blowalg.initEncrypt(key);
		ect = blowalg.crypt(StringByteTools.asciiGetBytes(data));

		return Hex.toString(ect);

	} //eof encrypt()


	/**
	 * Decrypts data pulled from the database returning normal strings.
	 *
	 * @param data  The encrypted string
	 * @returns  Decrypted string
	 */
	protected static String decrypt(String data) throws Exception {

		RawSecretKey key;
	 	byte[] dect;
		Cipher blowalg = Cipher.getInstance(new Blowfish(),null,new OneAndZeroes());
	 	
		/* build a Blowfish (16-round) key using ECB */
		key = new RawSecretKey("Blowfish", Hex.fromString(cryptword));
        
		blowalg.initDecrypt(key);
		dect = blowalg.crypt(Hex.fromString(data));

		return new String(dect);

	} //eof decrypt()


	/**
	 * 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) throws Exception {
	 
	 	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;
	 			case '?': ok = true; break;
	 			default:  ok = false; break;
	 		} //eof case
	 		
		 } //eof for
	 
	 	return ok;
	 
	 } //eof isSafe


	/**
	 * adds a vote entry to the database.
	 *
	 * @param data  The string representing the party to insert
	 */
	protected static void storeVote(String data) throws Exception {
	
		int result = 0;
	
		/* boundary check the data first */
		// only if the data is alphanumeric, punctuation or spaces do we carry on
		if (isSafe(data)) {

			/* Get a connection from the pool */
			Connection freeConn = getConnection();
			Statement freeStat = freeConn.createStatement();
			
			/* store the vote */
			result = freeStat.executeUpdate("INSERT INTO Votes VALUES ('" + encrypt(data) + "')");
			freeStat.close();
			
		} else {
		
			throw new Exception("storeVote - invalid character(s) used");

		}

	} //EOF storeVote()
	
	
	/**
	 * adds a total entry to the database.
	 *
	 * @param data  The string representing the total data to inser
	 */
	protected static void storeTotal(Packet total_input) throws Exception {
	
		int result = 0;
		int i = 0;
	
		/* Get a connection from the pool */
		Connection freeConn = getConnection();
		Statement freeStat = freeConn.createStatement();
		
		/* first past the post default implementation */			
		if (RTServer.vote_system.equals("fptp-default")) {
		
			String[] ppx = total_input.splitMessage();

			/* boundary check the data first */
			// only if the data is alphanumeric, punctuation or spaces do we carry on
			if (isSafe(ppx[0])&&isSafe(ppx[1])) {

				/* store the data */
				result = freeStat.executeUpdate("INSERT INTO Totals VALUES ('" + encrypt(ppx[0]) + "', '" + encrypt(ppx[1]) + "')");
				freeStat.close();

			} else {
				freeStat.close();
				throw new Exception("storeTotal - invalid character(s) used");
			}

		/* borda system default implentation */			
		} else if (RTServer.vote_system.equals("borda-default")) {
		
			String[] ppx = total_input.splitMessage();

			/* boundary check the data first */
			// only if the data is alphanumeric, punctuation or spaces do we carry on
			if (isSafe(ppx[0])&&isSafe(ppx[1])) {

				/* store the data */
				result = freeStat.executeUpdate("INSERT INTO Totals VALUES ('" + encrypt(ppx[0]) + "', '" + encrypt(ppx[1]) + "')");
				freeStat.close();

			} else {
				freeStat.close();
				throw new Exception("storeTotal - invalid character(s) used");
			}

		} else {
			freeStat.close();
			throw new Exception("storeTotal - Unrecognised voting system. Quit and check configuration.");
		}
	} //EOF storeTotal()
	
	
	/**
	 * analyses the totals to create the final results for the vote.
	 *
	 */
	 protected static void calcResult() throws Exception {
	 
		ResultSet results, subtotal;
		Vector parties = new Vector(1);
		Vector totalData = new Vector(1);
		String temp = "";
		long turnout = 0;
		long totalpoints = 0;
		long t = 0;
		float p;
		
		RTServer.NORM.info("Calculating final results...");
		RTServer.NORM.info("Vote system is: " + RTServer.vote_system);
		RTServer.NORM.info("Count system is: " + RTServer.count_system);

		/* Get a connection from the pool */
		Connection freeConn = getConnection();
		Statement freeStat = freeConn.createStatement();
			
		/* first past the post default implementation */			
		if (RTServer.vote_system.equals("fptp-default")) {

			/* count turnout */
			results = freeStat.executeQuery("SELECT total FROM Totals");
		
			while(results.next()) {
				temp = decrypt(results.getString(1));
				turnout += new Long(temp).longValue();
			}
		
			RTServer.NORM.info("Turnout: " + turnout + " votes were made.");
		
			/* get the choices voted for */
			results = freeStat.executeQuery("SELECT DISTINCT choice FROM Totals");
		
			/* find the distinct parties */
			while(results.next()) {
				parties.addElement(new String(decrypt(results.getString(1))));
			}
		
			/* count the votes for each party */
			int i = 0;
			do {
				temp = (String) parties.elementAt(i);
				subtotal = freeStat.executeQuery("SELECT total FROM Totals WHERE choice = '" + encrypt(temp) + "'");
				while(subtotal.next()) {
					t += new Long(decrypt(subtotal.getString(1))).longValue();
				}
				p = (new Float(t).floatValue() / new Float(turnout).floatValue()) * 100;
				RTServer.NORM.info(parties.elementAt(i) + " received " + t + " votes. This is " + p + "% of the vote.");
				t = 0;
				i++;
			} while (i < parties.size());
		
		/* borda system default implementation */			
		} else if (RTServer.vote_system.equals("borda-default")) {

			/* get turnout */
			results = freeStat.executeQuery("SELECT total FROM Totals WHERE choice='" + encrypt("Turnout") + "'");
			while(results.next()) {
					t += new Long(decrypt(results.getString(1))).longValue();
				}
			RTServer.NORM.info("Turnout: " + t + " votes were made.");
			t=0;
			
			/* remove turnout before we continue */
			results = freeStat.executeQuery("DELETE FROM Totals WHERE choice='" + encrypt("Turnout") + "'");

			/* count total points */
			results = freeStat.executeQuery("SELECT total FROM Totals");
		
			while(results.next()) {
				temp = decrypt(results.getString(1));
				totalpoints += new Long(temp).longValue();
			}
		
			/* get the choices voted for */
			results = freeStat.executeQuery("SELECT DISTINCT choice FROM Totals");
		
			/* find the distinct parties */
			while(results.next()) {
				parties.addElement(new String(decrypt(results.getString(1))));
			}
		
			/* count the votes for each party */
			int i = 0;
			do {
				temp = (String) parties.elementAt(i);
				subtotal = freeStat.executeQuery("SELECT total FROM Totals WHERE choice = '" + encrypt(temp) + "'");
				while(subtotal.next()) {
					t += new Long(decrypt(subtotal.getString(1))).longValue();
				}
				p = (new Float(t).floatValue() / new Float(totalpoints).floatValue()) * 100;
				RTServer.NORM.info(parties.elementAt(i) + " received " + t + " points. This is " + p + "% of the total points cast.");
				t = 0;
				i++;
			} while (i < parties.size());
		
		} else {
			freeStat.close();
			throw new Exception("calcResult - Unrecognised voting system. Quit and check configuration.");
		}
		
		RTServer.NORM.info("**** EOF Results.");
		freeStat.close();
	 
	 } //EOF calcResult()
	
	
	/**
	 * analyses votes to create data for total packets.
	 *
	 * @returns  Vector holding total data in format "PARTY-noOfVotes"
	 */
	protected static Vector getTotals() throws Exception {

		ResultSet results, subtotal;
		Vector parties = new Vector(10);
		Vector count = new Vector(10);
		Vector totalData = new Vector(10);
		String temp = "";
	
		/* Get a connection from the pool */
		Connection freeConn = getConnection();
		Statement freeStat = freeConn.createStatement();

		/* first past the post default implementation */			
		if (RTServer.vote_system.equals("fptp-default")) {
		
			/* get the choices voted for */
			results = freeStat.executeQuery("SELECT DISTINCT choice FROM Votes");
		
			/* find the distinct parties */
			while(results.next()) {
				parties.addElement(new String(decrypt(results.getString(1))));
			}
		
			/* count the votes for each party */
			for (int i=0; i<parties.size(); i++) {
				temp = (String) parties.elementAt(i);
				subtotal = freeStat.executeQuery("SELECT COUNT(choice) FROM Votes WHERE choice = '" + encrypt(temp) + "'");
				while(subtotal.next()) {  // formats results for packets
					totalData.addElement(new String(parties.elementAt(i) + "-" + subtotal.getString(1)));
				}
			}

		/* borda system default implentation */			
		} else if (RTServer.vote_system.equals("borda-default")) {
		
			Packet p;
			String[] pp;
			
			/* get the turnount */
			subtotal = freeStat.executeQuery("SELECT COUNT(choice) FROM Votes");
			subtotal.next();
			totalData.addElement(new String("Turnout-" + subtotal.getString(1)));
			
			/* get the votes */
			results = freeStat.executeQuery("SELECT choice FROM Votes");
			
			/* split votes, get points and add to vector */
			while(results.next()) {
				// data is in format "CHOICE-points-CHOICE2-points2-CHOICE3-points3" etc.
				p = new Packet("Z|" + new String(decrypt(results.getString(1))) + "|XXXX"); // split data
				pp = p.splitMessage();
				for (int z = 0; z < RTServer.number_of_choices; z++) {
					if (parties.indexOf(pp[z*2]) != -1) {  // party already in list
						count.setElementAt(new Integer(new Integer(count.elementAt(parties.indexOf(pp[z*2])).toString()).intValue() + new Integer(pp[(z*2)+1]).intValue()), parties.indexOf(pp[z*2])); // add points to sub-total
					} else { // party not in list
						parties.addElement(pp[z*2]); // insert party
						count.addElement(pp[(z*2)+1]); // insert points
					}
				}
				
				// deal with write-in if we need to...
				if ((RTServer.write_in.equals("yes")) && (!pp[RTServer.number_of_choices*2].equals(""))) {
					if (parties.indexOf(pp[RTServer.number_of_choices*2]) != -1) { // party already in list
						count.setElementAt(new Integer(new Integer(count.elementAt(new Integer(pp[RTServer.number_of_choices*2]).intValue()).toString()).intValue() + new Integer(pp[(RTServer.number_of_choices*2)+1]).intValue()), parties.indexOf(pp[RTServer.number_of_choices*2])); // add points to sub-total
					} else { // party not in list
						parties.addElement(pp[RTServer.number_of_choices*2]); // insert party
						count.addElement(pp[(RTServer.number_of_choices*2)+1]); // insert points
					}
				}
			}
			
			/* format and store for return */
			for (int i=0; i<parties.size(); i++) {
			System.out.println("=="+new String("td-" + new String(parties.elementAt(i) + "-" + count.elementAt(i)))); //XXXX
				totalData.addElement(new String(parties.elementAt(i) + "-" + count.elementAt(i)));
			}
				
		} else {
			RTServer.NORM.warn("DBase.getTotals: Unrecognised voting system. Quit and check configuration.");
		}
		
		freeStat.close();
		
		return totalData;

	} //EOF getTotals()
	

	/**
	 * Removes key from database once used for security + vote confirmation
	 *
	 * @param akey  The key to remove
	 * @returns  True if operation was successful
	 */
	protected static boolean removeKey(String akey) {
	
		ResultSet results;
		boolean removeOK = true;
	
		try {
			/* boundary check the data first */
			// only if the data is alphanumeric, punctuation or spaces do we carry on
			if (isSafe(akey)) {
			
				/* Get a connection from the pool */
				Connection freeConn = getConnection();
				Statement freeStat = freeConn.createStatement();
	
				/* replace our key with a string */
				results = freeStat.executeQuery("UPDATE AuthKeys SET ak='" + encrypt("USED") + "' WHERE ak='" + encrypt(akey) + "'"); //normal vote
				results = freeStat.executeQuery("UPDATE Test_Keys SET ak='" + encrypt("USED") + "' WHERE ak='" + encrypt(akey) + "'"); //test ballot
				
			} else {
			
				throw new Exception("removeKey - invalid character(s) used");
			
			}
			
		} catch (Exception e) {		
			removeOK = false;
			RTServer.NORM.error("DBase.removeKey error:" + e.getMessage());
		}

		return removeOK;

	} //EOF removeKey()


	/**
	 * This method retrieves stored <code>AuthKeys</code> (encrypted with secret key 1)
	 * decrypts them and then compares with the decrypted <code>AuthKey</code> delivered with
	 * the vote (originally encrypted with secret key 2).
	 *
	 * If a match is found then the AuthKey encrypted with secret key 1 is sent to the
	 * ERServer. On confirmation it is removed from the database here.
	 *
	 * This impacts performance - it could have been simply done as a SELECT statement if the
	 * keys weren't stored encrypted, however I feel this is a necessary security precaution 
	 * especially when the security of the database is not known in our db independent setup.
	 *
	 * Note that since 1.5 all db data is encrypted. Thus keys are twice encrypted.
	 *
	 * @param data  The string representing the party to insert
	 * @returns  1 if normal ok, 2 if test ballot ok, 3 if failed (no match)
	 */
	protected static int authKeyCheck(String key2) throws Exception {

		ResultSet result;
		boolean keyOK=false;
		int end_result = 3;
		String decKey1, decKey2;
	
		/* Get a connection from the pool */
		Connection freeConn = getConnection();
		Statement freeStat = freeConn.createStatement();

		/* prepare key2 by decrypting it */
		decKey2 = AuthKey.decrypt(key2,2);
			
		/* list the data */
		result = freeStat.executeQuery("SELECT ak FROM AuthKeys WHERE ak <> '" + encrypt("USED") + "' ORDER BY ak;");
		
		/* check answer by going through all keys */
		while (result.next()&&!keyOK) {
			decKey1 = AuthKey.decrypt(decrypt(result.getString(1)),1);
			
			if (decKey1.equals(decKey2)) {
				// send the key to ERServer
				RTServer.comms.sendKey(decrypt(result.getString(1)));
				// comms will remove key from database if all ok
				keyOK = true; // break out
				end_result = 1;
			}
		}
		
		if (!keyOK) {  // if match not found check if it's a test ballot
			/* list the data */
			result = freeStat.executeQuery("SELECT ak FROM Test_Keys WHERE ak <> '" + encrypt("USED") + "' ORDER BY ak;");
		
			/* check answer by going through all keys */
			while (result.next()&&!keyOK) {
				decKey1 = AuthKey.decrypt(decrypt(result.getString(1)),1);
			
				if (decKey1.equals(decKey2)) {
					// send the key to ERServer
					RTServer.comms.sendKey(decrypt(result.getString(1)));
					// comms will remove key from database if all ok
					keyOK = true; // break out
					end_result = 2;
				}
			}
		}

		freeStat.close();
				
		return end_result;
		
	} //EOF authKeyCheck()


	/**
	 * Imports keys from the <code>ERServer</code>, decrypts them and then inserts them into
	 * <code>AuthKeys</code> table.
	 *
	 */
	 protected static void importKeys() throws Exception {
	
		String inData = "";
		int line = 0;
		ResultSet result;
		boolean res;

		RTServer.NORM.info("Importing keys from rtserver.keys");
	
		BufferedReader in = new BufferedReader(new FileReader("rtserver.keys"));
		
		inData = in.readLine();

		while (inData!=null) {
			line++;
			/* boundary check the data first */
			// only if the data is alphanumeric, punctuation or spaces do we carry on
			if (isSafe(inData)) {
					/* search DB for duplicates */
					
					// Get a connection from the pool
					Connection freeConn = getConnection();
					Statement freeStat = freeConn.createStatement();
					res = freeStat.execute("SELECT ak FROM AuthKeys WHERE ak = '" + encrypt(AuthKey.decrypt(inData,1)) + "'");
					result = freeStat.getResultSet();
		
					// check answer 
					res = result.next();
					if (!res) {
						doSQL("INSERT INTO AuthKeys VALUES ('" + encrypt(AuthKey.decrypt(inData,1)) + "');");
					} else {
						// we have a duplicate so inform and skip
						RTServer.NORM.info("importKeys - record on line " + line + " already exists in DB.");
					}

					freeStat.close();

					inData = in.readLine();
			} else {
				throw new Exception("importKeys - invalid character(s) used on line " + line);
			}
		}
	 
	 	in.close();
	 	
		line = 0;

		try {
			BufferedReader t_in = new BufferedReader(new FileReader("rtserver.test.keys"));
		
			inData = t_in.readLine();

			while (inData!=null) {
				line++;
				/* boundary check the data first */
				// only if the data is alphanumeric, punctuation or spaces do we carry on
				if (isSafe(inData)) {
					/* search DB for duplicates */
					
					// Get a connection from the pool
					Connection freeConn = getConnection();
					Statement freeStat = freeConn.createStatement();
					res = freeStat.execute("SELECT ak FROM Test_Keys WHERE ak = '" + encrypt(AuthKey.decrypt(inData,1)) + "');");
					result = freeStat.getResultSet();
		
					// check answer 
					res = result.next();
					if (!res) {
					doSQL("INSERT INTO Test_Keys VALUES ('" + encrypt(AuthKey.decrypt(inData,1)) + "');");
					} else {
						// we have a duplicate so inform and skip
						RTServer.NORM.info("importKeys - record on line " + line + " already exists in DB.");
					}

					freeStat.close();

					inData = t_in.readLine();
				} else {
					throw new Exception("importKeys - invalid character(s) used on line " + line);
				}
			}
	 
		 	t_in.close();
		 } catch (FileNotFoundException fnfe) {
		 	RTServer.NORM.info("No rtserver.test.keys found so skipping.");
		 }

		RTServer.NORM.info("Import complete.");
		
	 } //eof importKeys

	
	/**
	 * Checks the number of votes registered against what ERServer reported.
	 *
	 * @param ERtotal  What the ERServer returned
	 */
	protected static void verCheck(int ERtotal) throws Exception {
	
		ResultSet result;
		boolean res;
		int count;
		
		/* Get a connection from the pool */
		Connection freeConn = getConnection();
		Statement freeStat = freeConn.createStatement();
			
		/* store the vote */
		result = freeStat.executeQuery("SELECT COUNT(choice) FROM Votes");
			
		/* check answer */
		res = result.next();
		if (!res) {
			count = 0;
		} else {
			count = new Integer(result.getString(1)).intValue();
		}
		
		freeStat.close();
		
		if (count==ERtotal) {
			RTServer.NORM.info("ERServer and RTServer both report " + count + " votes/voters registered.");
		} else {
			RTServer.NORM.warn("ERServer reports " + ERtotal + " as having voted, but RTServer has registered " + count + " votes.");
		}
		
	} //EOF verCheck()
	

	/**
	 * executes the SQL command sent as a parameter and returns the result to DB Console.
	 *
	 * @param  sqlCommand  A string containing SQL
	 */
	protected static void doSQL(String sqlCommand) {
	
		ResultSet results;
		boolean res;
		int count;
		
		try {
			/* Is it a custom command? Denoted by a '~' XXXX */
			if (sqlCommand.charAt(0) == '~') {
				sqlCommand = "INSERT INTO Votes VALUES ('" + encrypt(sqlCommand.substring(2)) + "');";
			} else if (sqlCommand.charAt(0) == '+') {
				sqlCommand = "INSERT INTO Totals VALUES ('" + encrypt(sqlCommand.substring(3)) + "','" + encrypt(sqlCommand.substring(1,2)) + "');";
			} else if (sqlCommand.charAt(0) == '*') {
				sqlCommand = sqlCommand.substring(2);
				RTServer.DEV.info("Executing: " + sqlCommand);
		
				/* Get a connection from the pool */
				Connection freeConn = getConnection();
				Statement freeStat = freeConn.createStatement();
			
				/* execute SQL */
				res = freeStat.execute(sqlCommand);
										
				/* display results */
				count = freeStat.getUpdateCount();
				results = freeStat.getResultSet();
				if (!res) {  // if no result data records
					RTServer.frame3.showInfo(count + " record(s) affected.");
					RTServer.frame3.showInfo("-DONE-");
				} else {  // otherwise show results
					while(results.next()) {
							RTServer.frame3.showInfo(results.getString(1));
					}  //FIXME: Improve flexibility as assumes fixed no. of columns
					RTServer.frame3.showInfo("-DONE-");
				}
      
				freeStat.close();
			} //XXXX
			/* boundary check the data first */
			// only if the data is alphanumeric, punctuation or spaces do we carry on
			if (isSafe(sqlCommand)) {
			
				RTServer.DEV.info("Executing: " + sqlCommand);
		
				/* Get a connection from the pool */
				Connection freeConn = getConnection();
				Statement freeStat = freeConn.createStatement();
			
				/* execute SQL */
				res = freeStat.execute(sqlCommand);
										
				/* display results */
				count = freeStat.getUpdateCount();
				results = freeStat.getResultSet();
				if (!res) {  // if no result data records
					RTServer.frame3.showInfo(count + " record(s) affected.");
					RTServer.frame3.showInfo("-DONE-");
				} else {  // otherwise show results
					while(results.next()) {
						if (RTServer.serverType == 'R') {  // regional mode
							RTServer.frame3.showInfo(decrypt(results.getString(1)));
						} else { // totaller mode
							RTServer.frame3.showInfo(decrypt(results.getString(1)) + " " + decrypt(results.getString(2)));
						}
					}  //FIXME: Improve flexibility as assumes fixed no. of columns
					RTServer.frame3.showInfo("-DONE-");
				}
      
				freeStat.close();
				
			} else {
			
				throw new Exception("doSQL - invalid charcter(s) used");
			
			}
			
		} catch(Exception sqle) {
			RTServer.frame3.showError("Database error: " + sqle.toString());
			RTServer.NORM.error("Database error: " + sqle.toString());
		}
		
	} //EOF doSQL()
	


} //EOF Class