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

/**
 * Processes the Strings passed to it by <code>TCPServer</code> but throws
 * all errors back up to <code>TCPServer</code> which can better handle
 * problems by elegantly closing connections.
 *
 * Also included are data structures and methods for storing and retrieving 
 * check date/time stamps for the FREE security system to provide persistence
 * between calls of <code>process()</code>.
 * 
 * @version 1.11 30 October 2001
 * @author Jason Kitcat
 */
public class ServerProtocol {

	private static Vector checkDates = new Vector(1000);
	private static Vector checkVotes = new Vector(1000);
	private static Vector checkAuthKey = new Vector(1000);
	
	private static Vector session_data = new Vector(1000);
	private static Vector client_hmac_keys = new Vector(1000);
	private static Vector server_hmac_keys = new Vector(1000);
	private static Vector session_count = new Vector(1000);
	/*
	 * Everything above is to keep the protocols neatly in this class
	 * and thus maintain persistence between calls of process()
	 * I feel this undoubtedly makes for clearer reading overall.
	 */
	 

	
	/**
	 * adds an entry to the checkVotes Vector for later use.
	 *
	 * @param  n  The index at which to insert
	 * @param  data  Packet message string to store
	 */
	 private static void setVotes(int n, String data) {
	 
	 	checkVotes.insertElementAt(data, n);
	 
	 } //EOF setVotes


	/**
	 * adds an entry to the checkDate Vector for later use.
	 *
	 * @param  n  The index at which to insert
	 * @param  data  Packet message string to store
	 */
	 private static void setCheck(int n, String data) {
	 
	 	checkDates.insertElementAt(data, n);
	 
	 } //EOF setCheck


	/**
	 * adds an entry to the checkAuthKey Vector for later verifications.
	 *
	 * @param akey   Key to insert
	 * @returns    The index of where the date was inserted
	 */
	 private static int setKey(String akey) {
	 		
	 	int r = checkAuthKey.size();

	 	checkAuthKey.insertElementAt(akey, r);
	 		 	
	 	return r;
	 
	 } //EOF setKey
	 
	 
	 /**
	  * tries to find a matching key for security check.
	  *
	  * @param akey   Key to find & match
	  *
	  * @returns   A string array with the result of the matching vote and the associated AuthKey
	  */
	  private static String[] getKey(String akey) {
	  
	  	int i=0;
	  	boolean test=false;
	  	String[] r = new String[] {"","",""};
	  	
		i = checkAuthKey.indexOf(akey);

		if (i==-1) {
			r[0] = "FALSE";
		} else {
			r[0] = checkVotes.elementAt(i).toString(); // collect vote
			r[1] = checkAuthKey.elementAt(i).toString(); // collect AuthKey
			r[2] = checkDates.elementAt(i).toString(); // collect Date
			checkDates.removeElementAt(i);  // clear data
 			checkAuthKey.removeElementAt(i);
 			checkVotes.removeElementAt(i);
 		}

	  	return r;
	  		
	} //EOF getKey
	  
	  
	 /**
	  * adds entries to Vectors to keep track of session_data
	  *
	  * @param session_key  The encryption session_key to index by
	  * @param client_hmac_key  Client HMAC key
	  * @param server_hmac_key  Server HMAC key
	  * @param session_counter  Packet counter
	  */
	  private static void setSession(String session_key, String client_hmac_key, String server_hmac_key, int session_counter) {
	  
	  	int r = session_data.size();
	  	
	  	session_data.insertElementAt(session_key, r);
	  	client_hmac_keys.insertElementAt(client_hmac_key, r);
	  	server_hmac_keys.insertElementAt(server_hmac_key, r);
	  	session_count.insertElementAt(new Integer(session_counter).toString(), r);
	  	
	  } //EOF setSession

	
	 /**
	  * tries to find matching session data.
	  *
	  * @param session_key
	  * @return  A string array with the results of the match
	  */
	  private static String[] getSession(String session_key) {
	  
	  	int i=0;
	  	String[] r = new String[] {"","",""};
	  	
		i = session_data.indexOf(session_key);

		if (i==-1) {
			r[0] = "FALSE";
		} else {
			r[0] = client_hmac_keys.elementAt(i).toString(); // collect data
			r[1] = server_hmac_keys.elementAt(i).toString();
			r[2] = session_count.elementAt(i).toString();
			session_data.removeElementAt(i);  // clear data
 			client_hmac_keys.removeElementAt(i);
 			server_hmac_keys.removeElementAt(i);
 			session_count.removeElementAt(i);
 		}

	  	return r;
	  
	  } //EOF getSession
	  

	/**
	 * <code>process</code> analyses Strings sent from <code>TCPServer</code>
	 * and decides whether the data is in fact valid packets.
	 *
	 * If so then the appropriate replies are formulated and returned to 
	 * <code>TCPServer</code> for sending to the client.
	 *
	 * @param inputData   Contains the contents of a received packet
	 * @param session_key  The session key for encryption
	 * @returns   String with a packet to return to client
	 */
	protected static String process(String inputData, String session_key) throws Exception {

		String outputData="";
		
		if (inputData.charAt(0) == 'C') {
		
			/* vote or total confirm */
			Packet p = new Packet(inputData);
			
			String[] sess_data = getSession(session_key);
			
			if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check
			
				// parse message
				String[] pp = p.splitMessage();
				String FCAuthKey = pp[0];
				String check = pp[1];
				
				String[] t = getKey(FCAuthKey); //compare second time stamp
			
				/* respond according to regional or totaller mode */
				if (RTServer.serverType == 'R') {
					if (!t[0].equals("FALSE")&&check.equals(t[2])) {  //date security ok (FALSE indicated no matches, second term checks if dates match)
							/* register vote */														
							try {
								// AuthKey check, if ok store vote
								int kcResult = DBase.authKeyCheck(t[1]);
								if (kcResult==1) {
									/* Normal vote */
									// now store the vote							
									DBase.storeVote(t[0]);
									RTServer.NORM.info("Vote stored in database");
									// confirm vote to user
									outputData = new Packet('C',"OK",session_key,"",sess_data[1],(new Integer(sess_data[2]).intValue()+2)).toCryptString(false);
									setSession(session_key,sess_data[0],sess_data[1],(new Integer(sess_data[2]).intValue()+2));
								} else if (kcResult==2) {
									/* Test Ballot */
									RTServer.NORM.info("TEST BALLOT RECEIVED - Value: " + t[0]);
									// confirm vote to user
									outputData = new Packet('C',"OK",session_key,"",sess_data[1],(new Integer(sess_data[2]).intValue()+2)).toCryptString(false);
									setSession(session_key,sess_data[0],sess_data[1],(new Integer(sess_data[2]).intValue()+2));
								} else {
									/* we have an error */
									RTServer.NORM.error("AuthKey Check failed!");
									outputData = "ERROR";
								}
								
							} catch (Exception e) {
								RTServer.NORM.error("Vote registration error: " + e.getMessage());
								outputData = "ERROR";
							}
					} else {
							RTServer.NORM.warn("Vote security failure");
							outputData = "ERROR";
					}
				} else { // Totaller server
					if (!t[0].equals("FALSE")&&check.equals(t[2])) {  //date security ok
							/* register total */
							try {
								Packet px = new Packet("X|"+t[0]+"|XXX"); // dummy packet w/info
								DBase.storeTotal(px); // store
								RTServer.NORM.info("Total stored in database");
								outputData = new Packet('C',"OK",session_key,"",sess_data[1],(new Integer(sess_data[2]).intValue()+2)).toCryptString(false); //confirm total
								setSession(session_key,sess_data[0],sess_data[1],(new Integer(sess_data[2]).intValue()+2));
							} catch (Exception e) {
								RTServer.NORM.error("Totals registration error: " + e.getMessage());
								outputData = "ERROR";
							}
					} else {
							RTServer.NORM.warn("Totals security failure.");
							outputData = "ERROR";
					}
				}
				
			} else {
				RTServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}
			
		} else if (inputData.charAt(0) == 'D') {
		
			/* diagnostic */
			Packet p = new Packet(inputData);
			
			String[] sess_data = getSession(session_key);
			
			if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check
			
				RTServer.NORM.info("Diagnostic packet received");
			
				if (p.getMessage().equals("TEST")) {
					outputData = new Packet('D',"OK",session_key,"",sess_data[1],(new Integer(sess_data[2]).intValue()+2)).toCryptString(false);
					setSession(session_key,sess_data[0],sess_data[1],(new Integer(sess_data[2]).intValue()+2));
				} else {
					RTServer.NORM.warn("Unrecognized diagnostic command.");
					outputData = "ERROR";
				}
			
			} else {
				RTServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}
			
		} else if (inputData.charAt(0) == 'I') {
		
			/* initialisation packet */
			Packet p = new Packet(inputData);
			
			String[] sess_data = getSession(session_key);
			
			if (AuthSys.checkDigest(p)) {  // tamper check
			
				String client_key = AuthSys.seedToKey(p.getMessage());  // build keys
				String server_seed = RSAEncrypt.makeSessionKey();
				String server_key = AuthSys.seedToKey(server_seed);

				setSession(session_key,client_key,server_key,1);  // store session information
				
				outputData = new Packet('I',server_seed + "-" + AuthSys.makeHMAC("I|" + p.getMessage(),client_key,0),session_key,"",server_key,1).toCryptString(false);
			} else {
				RTServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}
			
		} else if (inputData.charAt(0) == 'T') {
		
			/* total for party */
			Packet p = new Packet(inputData);
			
			String[] sess_data = getSession(session_key);
			
			if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check
			
				if (p.getMessage().equals("DONE")) {
					RTServer.rServersDone = RTServer.rServersDone + 1;
					RTServer.NORM.info("Received " + RTServer.rServersDone + "/" + RTServer.rServers + " totals.");
					if (RTServer.rServersDone == RTServer.rServers) { // if all results in
						DBase.calcResult();
					}
					outputData = new Packet('C',"DONE",session_key,"",sess_data[1],(new Integer(sess_data[2]).intValue()+2)).toCryptString(false); // confirm
					setSession(session_key,sess_data[0],sess_data[1],(new Integer(sess_data[2]).intValue()+2));
				} else {
					// commence security check
					
					// split packet message
					String[] pp = p.splitMessage();
					String tkey = pp[0];
					String check = pp[1];
					String total = pp[2] + "-" + pp[3];

					if (total.equals("")|total.equals(" ")) {  // check to see if there is a total in the data
						RTServer.NORM.warn("No total data in packet");
						outputData = "ERROR";
					} else {
						// commence security check						
						//store time stamp, and total packet
						int checkIndex = setKey(tkey);
						setVotes(checkIndex, total);
						setCheck(checkIndex, check);
						outputData = new Packet('C',"STAMP",session_key,"",sess_data[1],(new Integer(sess_data[2]).intValue()+2)).toCryptString(false); //ask for time stamp check
						setSession(session_key,sess_data[0],sess_data[1],(new Integer(sess_data[2]).intValue()+2));
					}
				}
			} else {
				RTServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}

		} else if (inputData.charAt(0) == 'V') {
		
			/* normal vote */
			if (inputData.length() > 120) {  // simple validity check to save work
			
				Packet p = new Packet(inputData);
			
				String[] sess_data = getSession(session_key);
			
				if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check

					// split packet message into vote, AuthKey and time stamp					
					String[] pp = p.splitMessage();
					String check = pp[0];
					String FCAuthKey = pp[1];
					String vote = p.getMessage().substring(new String(check+"-"+FCAuthKey+"-").length());
						
					if (vote.equals("")|vote.equals(" ")) {  // check to see if there is a party in the data
						RTServer.NORM.warn("No vote data in packet");
						outputData = "ERROR";
					} else {
						// commence security check						
						//store time stamp, AuthKey and vote packet
						int checkIndex = setKey(FCAuthKey);
						setVotes(checkIndex, vote);
						setCheck(checkIndex, check);
						outputData = new Packet('C',"STAMP",session_key,"",sess_data[1],(new Integer(sess_data[2]).intValue()+2)).toCryptString(false); // ask for time stamp check
						setSession(session_key,sess_data[0],sess_data[1],(new Integer(sess_data[2]).intValue()+2));
					}
				} else {
					RTServer.NORM.warn("Packet corrupted or altered!");
					outputData = "ERROR";
				}
			} else {
				RTServer.NORM.warn("Bad packet!");
				outputData = "ERROR";
			}
			
		} else if (inputData.charAt(0) == 'X') {
		
			/* end communication*/
			Packet p = new Packet(inputData);
			
			String[] sess_data = getSession(session_key);
			
			if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check
			
				outputData = "DONE";
			} else {
				RTServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}

		} else {
				RTServer.NORM.warn("Unrecognized packet received.");
				outputData = "ERROR";
		}
		
		return outputData;

	}  //EOF process


} //EOF Class
