Jriver/
Jriver/src/org/jriver/core/
Jriver/src/org/jriver/core/interactive/
Jriver/src/org/jriver/lib/
Jriver/src/org/jriver/lib/daemon/
Jriver/src/org/jriver/lib/events/
Jriver/src/org/jriver/lib/interfaces/
Jriver/src/org/jriver/lib/std/
Jriver/src/org/jriver/telnet/
Jriver/src/org/jriver/telnet/.svn/
Jriver/src/org/jriver/telnet/.svn/prop-base/
Jriver/src/org/jriver/telnet/.svn/text-base/
/*
 * Copyright 2007 Kevin Roe, Daniel Mccarney
 * This file is part of Jriver.
 *
 * Jriver 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 3 of the License, or
 * (at your option) any later version.
 *
 * Jriver 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.  If not, see <http://www.gnu.org/licenses/>.
 */
 
 package org.jriver.lib;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileInputStream;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.PrintWriter;
import java.net.Socket;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;

import org.jriver.core.HeartBeat;
import org.jriver.core.MudDriver;
import org.jriver.core.interactive.Interactive;
import org.jriver.lib.events.ConnectionEvent.ConnectionState;
import org.jriver.lib.interfaces.Talkable.MessageLevel;
import org.jriver.telnet.TelnetProtocol;

public class Authenticator implements Runnable, Interactive 
{

	private boolean destroy = false;
	private Socket boundSocket = null;
	private BufferedReader in = null;
	private PrintWriter out = null;
	private MudDriver driver = MudDriver.getMudDriver();
	
	public static String md5Hash(String input)
	{
		byte inputBytes[] = input.getBytes();
		String hash = null;
		
		try
		{
			MessageDigest md5Digest = MessageDigest.getInstance("MD5");
			md5Digest.reset();
			md5Digest.update(inputBytes);
			byte messageDigest[] = md5Digest.digest();
			
			// Doing a toString() on a primitive causes an autobox to occur
			// at which point the toString returns the objects internal reference
			// String has a constructor that allows it to build a string from
			// a byte array
			hash = new String(messageDigest);
		} catch(NoSuchAlgorithmException e){
			System.err.println("Unable to compute md5 hash.");
			e.printStackTrace();
		}
		
		return hash;
	}

	public boolean isLinkDead() 
	{
		//An Authenticator is never LD. If it goes LD it nullifies itself.
		return false;
	}

	public void linkDeath() 
	{
		cleanUp();
		destroy = true;
	}

	public void recoverLink(Socket socket) 
	{
		throw new UnsupportedOperationException("Authenticators do not implement linkdeath recovery");
	}

	public void run() 
	{
		String inputLine = "";
		String username = null;
		String password = null;
		Player oldPlayer = null, newPlayer = null;
		
		try
		{
			out.print("By what name are you known? ");
			out.flush();

            while (!destroy && (inputLine = in.readLine()) != null && inputLine.equals(""))
            	out.println("You must enter a name.\r");
            
            if(inputLine == null) 
            {
            	//They went LD
            	cleanUp();
                return; //End this thread
            }            	

            inputLine = Player.capString(TelnetProtocol.sanitize(inputLine));
            username = inputLine;
            
            //Is this player already connected with another player ob?
            //i.e. LD?
            if((oldPlayer = driver.findPlayer(username)) != null)
            {
            	out.print("Password: ");
            	out.flush();
            	inputLine = in.readLine();
            	
            	if(inputLine == null)
            	{
            		cleanUp(); //They went LD
            		return;
            	}
            	
            	if(!Authenticator.md5Hash(inputLine).equals(oldPlayer.queryPassword())) 
            	{
            		out.println("Sorry. Wrong password...");
                    in.close();
                    out.close();
                    boundSocket.close();
                    return;
            	}
            	
				if(!oldPlayer.isLinkDead()) 
				{
					out.println("You are already in the game.");
					out.print("Would you like to replace the other version? [yes/no] ");
					out.flush();
					inputLine = in.readLine();
					if(inputLine == null || (!inputLine.equals("yes") && !inputLine.equals("y"))) 
					{
						if(inputLine != null)
							out.println("Goodbye then!");
						cleanUp();
						return;
					}
				}
				// else System.out.println("Player IS linkdead");
				
				String saveDir = driver.configuration.getProperty("playerSaveDir");
				String fileName = saveDir + oldPlayer.queryName() + ".o";
				
				oldPlayer.catchTell("You have been replaced", MessageLevel.INFO, null);
				System.out.println("done");
				oldPlayer.savePlayer(new File(fileName));
				if(!oldPlayer.isLinkDead()) {
					oldPlayer.getSocket().close();
				}
								
				newPlayer = loadPlayer(new File(fileName));
				
				if(newPlayer == null)
				{
					out.println("Unable to reload your player object. Goodbye!");
					destroy = true;
					return;
				}
				
				newPlayer.recoverLink(boundSocket); //Replace their socket with the new one
				
	        	Thread t = new Thread(newPlayer);
			newPlayer.setName(username);
	        	t.setName(username);
	        	t.start(); 
	        	destroy = true;
	        	newPlayer.move(driver.loginRoom.getRootNode());
                	oldPlayer.getRootNode().removeFromParent();
			driver.removePlayer(oldPlayer);
                	HeartBeat.getHeartBeat().removeLiving((Living) oldPlayer);

	        	return; //End this thread
            }
            
            //At this point we know they are a newly logging player
            out.print("Please enter a password: ");
            out.flush();

			while (!destroy && (inputLine = in.readLine()) != null && inputLine.equals(""))
			{
				out.print("Please enter a password: ");
				out.flush();
			}
	
			if(inputLine == null) 
			{
				cleanUp();
				return;
			}
			
			password = Authenticator.md5Hash(inputLine);	
			newPlayer = new Player();
			
			Thread t = new Thread(newPlayer);
			t.setName(username);
			
			newPlayer.setName(username);
			newPlayer.username = username;
			newPlayer.setPassword(password);
			newPlayer.shortDesc = username+" (player)";
			newPlayer.move(driver.loginRoom.getRootNode());
			Thread.currentThread().setName(username);
                        
            out.println("Welcome " + username + "\r");
            System.out.println("User "+ username +" logged in ["+this.toString()+"]");
            
            newPlayer.shout("CONNECT", MessageLevel.INFO);
            
            newPlayer.fireConnectionEvent(ConnectionState.CONNECTED, username + " logged in.");
            
            HeartBeat.getHeartBeat().addLiving((Living) newPlayer);
            newPlayer.showPrompt(out);
            
            newPlayer.setInteractivity(boundSocket);
            t.run();
            return; //We're all done! Kill the thread
		} catch(IOException e) {
			e.printStackTrace();
		}
	}
	
	public void setInteractivity(Socket socket) 
	{
		try
		{
			boundSocket = socket;
			in = new BufferedReader(new InputStreamReader(boundSocket.getInputStream()));
			out = new PrintWriter(boundSocket.getOutputStream(), true);
			new Thread(this).run();
		} catch(IOException e) {
			e.printStackTrace();
		}
	}
	
	private void cleanUp()
	{
		try
		{
			in.close();
			out.close();
			boundSocket.close();
		} catch(IOException e) {
			e.printStackTrace();
		}
	}
	
	public Player loadPlayer(File saveFile)
	{
		Player p = null;
		try
		{
			FileInputStream fileIn = new FileInputStream(saveFile);
			ObjectInputStream objOut = new ObjectInputStream(fileIn);
			p = (Player) objOut.readObject();
			objOut.close();
			fileIn.close();
		} catch(IOException e) {
			System.err.println("Unable to load player from: "+
					saveFile.getAbsolutePath());
			e.printStackTrace();
		} catch(ClassNotFoundException e) {
			System.err.println("Unable to determine class to load player to.");
			e.printStackTrace();
		} catch(ClassCastException e) {
			System.err.println("Unable to cast returned object to a Player");
			e.printStackTrace();
		}
		return p;
	}
}