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