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