/*
 * 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 ERServer;
 
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.
 *
 * The source code is very readable and best explains the logic.
 *
 * @version 1.3 8 September 2001
 * @author Jason Kitcat
 */
public class ERServerProtocol {

	  
	private static Vector checkDates = new Vector(1000);
	private static Vector checkUsers = 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 Users Vector for later use.
	 *
	 * @param  n  The index at which to insert
	 * @param  data  Packet message string to store
	 */
	 private static void setUsers(int n, String data) {
	 
	 	checkUsers.insertElementAt(data, n);
	 
	 } //EOF Users


	/**
	 * adds an entry to the checkDate Vector for later use.
	 *
	 * @param cdate   Date to insert
	 * @returns    The index of where the date was inserted
	 */
	 private static int setCheck(String cdate) {
	 
 	 	int r = checkDates.size();

	 	checkDates.insertElementAt(cdate, r);
	 		 	
	 	return r;

	 } //EOF setCheck
	 
	 
	 /**
	  * tries to find a matching user for security check.
	  *
	  * @param user   User to find & match
	  *
	  * @returns   A string array with the result of the matching date and the associated user
	  */
	  private static String[] getUsers(String user) {
	  
	  	int i=0;
	  	String[] r = new String[] {"","",""};
	  	
		i = checkUsers.indexOf(user);

		if (i==-1) {
			r[0] = "FALSE";
		} else {
			r[0] = checkUsers.elementAt(i).toString(); // collect user
			r[1] = checkDates.elementAt(i).toString(); // collect Date
			checkDates.removeElementAt(i);  // clear data
 			checkUsers.removeElementAt(i);
 		}

	  	return r;
	  		
	} //EOF getUsers
	  

	 /**
	  * 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 code to return to client
	 */
	protected static String process(String inputData, String session_key) throws Exception {

		String outputData="";
		
		if (inputData.charAt(0) == 'C') {
		
			/* set as voted 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
			
				// split packet message
				String[] pp = p.splitMessage();
				String check = pp[0];
				String name = pp[1];
				String code = pp[2];
				String pword = pp[3];
				
				String[] t = getUsers(name + "-" + code + "-" + pword); //compare second time stamp
			
				if (!t[0].equals("FALSE")&&check.equals(t[1])) {  //date security ok (FALSE indicated no matches, second term checks if dates match)
						/* register vote */														
						try {
							DBase.PMconfirmVoted(name, code, pword); //set as voted
							outputData = new Packet('S',"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));
						} catch (Exception e) {
							ERServer.NORM.error("Voted registration error: " + e.getMessage());
							outputData = "ERROR";
						}
				} else {
						ERServer.NORM.warn("Voted security failure");
						outputData = "ERROR";
				}
				
			} else {
				ERServer.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
			
				ERServer.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 {
					ERServer.NORM.warn("Unrecognized diagnostic command.");
					outputData = "ERROR";
				}
			
			} else {
				ERServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}
			
		} else if (inputData.charAt(0) == 'E') {
		
			/* ER check */
			Packet p = new Packet(inputData);
			
			/* NOTE: If the Auth System is changed from the default code
			 * provide with GNU.FREE then the number below will also need to be
			 * changed to the MINUMUM number of characters long an Auth packet
			 * can be in your system.
			 * Remember the MAC is always 31 characters long!
			 */
			if (inputData.length() >= 47) {  // simple validity check to save work
			
				String[] sess_data = getSession(session_key);

				if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check
				
					try {

						// parse to get seperate data fields
						String[] pp = p.splitMessage();
						String name = pp[0];
						String code = pp[1];
						String pword = pp[2];
						
						// another simple validity check to save time
						if (code.equals("")|code.equals(" ")) {
							ERServer.NORM.warn("Bad auth code data in packet.");
							outputData = "ERROR";
						} else if (pword.equals("")|pword.equals(" ")) {
							ERServer.NORM.warn("Bad auth pword data in packet.");
							outputData = "ERROR";
						} else if (name.equals("")|name.equals(" ")) {
							ERServer.NORM.warn("Bad auth name data in packet.");
							outputData = "ERROR";
						} else {
							/* data seems ok so do commit to doing database work */
							try {
								if (DBase.checkER(name, code, pword)) {
									// before sending confirmation make auth key
									String ak = AuthKey.build(name,code,pword,2);

									outputData = new Packet('A',"TRUE-" + ak,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));
									ERServer.NORM.info("Voter authorised!");
								} else {
									// datbase check didn't match or user has already voted
									outputData = new Packet('A',"FALSE-NOKEY",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));
									ERServer.NORM.info("Voter authorisation failed!");
								}
							} catch (Exception e) {
								outputData = "ERROR";
								ERServer.NORM.error("Auth dbase/auth key exception: " + e.getMessage());
							}
								
						}
						
					} catch (IndexOutOfBoundsException e) {
						ERServer.NORM.error("Unrecognized or bad packet received.");
						outputData = "ERROR";
					}

				} else {
					ERServer.NORM.error("Packet corrupted or altered!");
					outputData = "ERROR";
				}

			} else {
				ERServer.NORM.warn("Bad Packet!");
				outputData = "ERROR";
			}

		} else if (inputData.charAt(0) == 'I') {
		
			/* initialisation packet */
			Packet p = new Packet(inputData);
			
			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 {
				ERServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}
			
		} else if (inputData.charAt(0) == 'K') {
		
			/* auth key packet */
			ERServer.NORM.info("Auth Key packet received");
			
			Packet p = new Packet(inputData);
			
			if (inputData.length() >= 88) {  // simple validity check to save work
			
				String[] sess_data = getSession(session_key);
				
				if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check
					if (DBase.confirmVoted(p.getMessage())) {
						// we have a match
						outputData = new Packet('K',p.getMessage() + "-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 {
						// no match found
						ERServer.NORM.warn("No Key match: " + p.getMessage());
						outputData = new Packet('K',p.getMessage() + "-FALSE",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 {
					ERServer.NORM.warn("Packet corrupted or altered!");
					outputData = "ERROR";
				}
				
			} else {
				ERServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}

		} else if (inputData.charAt(0) == 'P') {
		
			/* ERServer ER Check */
			ERServer.NORM.info("PMERCheck packet received");
			
			Packet p = new Packet(inputData);
			
			if (inputData.length() >= 47) {  // simple validity check to save work
			
				String[] sess_data = getSession(session_key);
			
				if (AuthSys.checkHMAC(p,sess_data[0],new Integer(sess_data[2]).intValue()+1)) {  // tamper check
					try {

						// parse to get seperate data fields
						String[] pp = p.splitMessage();
						String name = pp[0];
						String code = pp[1];
						String pword = pp[2];
						
						// another simple validity check to save time
						if (code.equals("")|code.equals(" ")) {
							ERServer.NORM.warn("Bad auth code data in packet.");
							outputData = "ERROR";
						} else if (pword.equals("")|pword.equals(" ")) {
							ERServer.NORM.warn("Bad auth pword data in packet.");
							outputData = "ERROR";
						} else if (name.equals("")|name.equals(" ")) {
							ERServer.NORM.warn("Bad auth name data in packet.");
							outputData = "ERROR";
						} else {
							/* data seems ok so do commit to doing database work */
							try {
								String[] pmer = DBase.PMcheckER(name, code, pword);
								if (pmer[0].equals("ERROR")) {
									// datbase check didn't match
									outputData = new Packet('P',"FALSE-FALSE",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));
									ERServer.NORM.info("No PMERCheck match.");
								} else {
									outputData = new Packet('P',pmer[0] + "-" + pmer[1],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));
									ERServer.NORM.info("PMERCheck match.");
								}
							} catch (Exception e) {
								outputData = "ERROR";
								ERServer.NORM.error("PMERChecl dbase exception: " + e.getMessage());
							}
								
						}
						
					} catch (IndexOutOfBoundsException e) {
						ERServer.NORM.error("Unrecognized or bad packet received.");
						outputData = "ERROR";
					}
				} else {
					ERServer.NORM.warn("Packet corrupted or altered!");
					outputData = "ERROR";
				}
				
			} else {
				ERServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}

		} else if (inputData.charAt(0) == 'Q') {
		
			/* verification query */
			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("TOTALVOTED")) {
					int total = DBase.usersVoted();
					outputData = new Packet('Q',Integer.toString(total),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));
					ERServer.NORM.info("Total users voted calculated and sent.");
				} else {
					ERServer.NORM.warn("Unrecognized verification query.");
					outputData = "ERROR";
				}
			} else {
				ERServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}
			
		} else if (inputData.charAt(0) == 'S') {
		
			/* Set as voted */
			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
				String[] pp = p.splitMessage();
				String check = pp[0];
				String name = pp[1];
				String code = pp[2];
				String pword = pp[3];
						
				if (check.equals("")|check.equals(" ")) {  // check to see if there is a party in the data
					ERServer.NORM.warn("No data in packet");
					outputData = "ERROR";
				} else {
					// commence security check						
					//store time stamp, AuthKey and ER data packet
					int checkIndex = setCheck(check);
					setUsers(checkIndex, name+"-"+code+"-"+pword);
					outputData = new Packet('C',"STAMP",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 {
				ERServer.NORM.warn("Packet corrupted or altered!");
				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 {
				ERServer.NORM.warn("Packet corrupted or altered!");
				outputData = "ERROR";
			}
			
		} else {
				ERServer.NORM.warn("Unrecognized packet received.");
				outputData = "ERROR";
		}
		
		return outputData;

	}  //EOF process


} //EOF Class
