/** * @(#)gnsd.java 1.0.1 98/12/20 *

* Copyright (C) 1998 David E. Wexler *

* 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. See * license.txt for a full copy of the GNU GPL. *

* 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; if not, write to the Free Software * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, * USA. *

* To Contact the author send e-mail to vagabond@netdragon.com or send * snailmail to: 511 Bobcat Ct, Punta Gorda, FL 3398. */ import java.net.*; import java.io.*; import java.util.*; import logger; import gnsd; import Group; /** * User - Stores user data and provides the user with an connection * to interact with. *

* Each users is given it's own thread to run in. If the user fails to * send data to the server in 5 mins the connection will automatically close. * This will handle all the commands sent from the client, and handle * sending all the data from the server back to the client. * * @author David Wexler (vagabond@netdragons.com) * @version 1.0.1 * @since gnsd.1.0.1 */ public class User extends Thread { /** * Open socket connecting the client and server. This is used for all * incoming and out going traffic. */ private Socket soc; /** * Allows the server to communicate with the client. */ private DataInputStream in; /** * Allows the client to communicate with the server. */ private DataOutputStream out; /** * Used to log major commands to PATH/log/access_log. This * will log @connect, @login, @join, @joinnew, @list, @part, and @logout. * Logs are timestamped via time_t, information about the user's host, * command, and other useful information is given. */ private logger log; /** * The users ID number. This is set to -1 if the user is not authenticated. */ private int id = -1; /** * The users name or handle. This will be null until the user has * authenticated. */ private String name; /** * The game or client ID that the user is using. This will be null until the * user has authenticated. Used so a number of different clients can connet * to the server without them conflicting. */ private int gameID; /** * Used to hold information set by @setmode. This can store information * to be transmited on joining the network, this can be used for control * reasons, or just to transmit extra data. */ private String mode = ""; /** * Type of incoming data. Used to see if there is a leading @ or #. */ private char type; /** * Instance of the server so we can perform server level actions. This * includes joining groups, getting group lists, getting user counts, etc. */ private gnsd server; /** * Instnace of the current users group. This is so the user can send * data to the group, can leave the group, etc. This will be null until * the user has joined a channel. */ private Group myGroup; /** * Indicated if the players connection is active. */ private boolean active = false; /** * Creates a new instance of a User. Sets up the input stream and output * stream, through buffers, from the client's socket. Also sets the timeout * to 5 minutes. * * @param s the client's socket. * @param l Instance of logger, use to log commands. * @param g Instance of the server so we can perform server * level actions. * @since gnsd.1.0.1 */ public User (Socket s, logger l, gnsd g) throws IOException { soc = s; in = new DataInputStream(new BufferedInputStream(s.getInputStream())); out = new DataOutputStream(new BufferedOutputStream(s.getOutputStream())); log = l; server = g; active = true; } /** * Handles the execution of the users thread. We sit in an infinate loop * and read data from the input stream. The thread is only borken when * the client's connection is broken. Server is hardcoded to read UTF * data only. * * @since gnsd.1.0.1 */ public void run () { try { while (true) { handle(in.readUTF()); } } catch (IOException ex) { // No need to do anything! } finally { if (active) logout("Connection Timed Out."); } } /** * Sends a message to the client. Data is sent to the client in UTF format. * * @param msg the message to send. * @since gnsd.1.0.1 */ public void send(String msg) { try { out.writeUTF(msg); out.flush(); } catch (IOException ex) { if (log.debug()) ex.printStackTrace(); } } /** * Allows the user to join a group. A User can only be in one group at a * time, so the server is carefully crafted to make sure part is called * before we join a group. * * @param g the group to join. * @set #part() * @since gnsd.1.0.1 */ public void join(Group g) { myGroup = g; send("@|chanjoin|" + g.getTitle() + "`" + g.getMode()); send("@|who|" + myGroup.getUserList()); myGroup.addUser(this); } /** * Gets the users name. * * @return the name of the user. * @since gnsd.1.0.1 */ public String getUserName() { return name; } /** * Gets the host address of the client's socket. This is used for logging. * Later versions of the server may allow a @whois command so direct client * to client communication can be used. * * @return the host address of the client. * @since gnsd.1.0.1 */ public String getAddress() { if (soc != null) return soc.getInetAddress().getHostName(); else return ""; } /** * Gets the users ID number. * * @return the users ID number. * @since gnsd.1.0.1 */ public int getID() { return id; } /** * Gets the users mode. * * @return the users mode. * @since gnsd.1.0.2 */ public String getMode() { return mode; } /** * Gets the users Game/Client ID number. * * @return the users game/client ID number. * @since gnsd.1.0.1 */ public int getGameID() { return gameID; } /** * Handles input from the client when it's presented with data. The * String needs to begin with @ (a command) or # (broadcast) for the * server to handle the information. Depending on which type of data * is sent the proper handling function is called. If the String does * not start with a known char, an error is returned to the client. * * @see #handleCommand(java.lang.String) * @see #handleData(java.lang.String) * @since gnsd.1.0.1 */ private void handle(String message) { type = message.charAt(0); if (type == '#') handleData(new String(message.toCharArray(), 1, message.length()-1)); else if (type == '@') handleCommand(new String(message.toCharArray(), 1, message.length()-1)); else send("@|ERROR|Malformed Protocol \"" + message + "\""); } /** * Handles all the commands the client could posible send. For a list of * all clients visit http://www.netdragons.com/games/24/gns/commands/. If * the command isn't understood, an error is returned to the client. * * @param data data given from the client. * @since gnsd.1.0.1 */ private void handleCommand(String data) { StringTokenizer Token = new StringTokenizer(data, "|", false); String cmd = Token.nextToken(); if (cmd.compareTo("gameCmd") == 0) sendGameCommand(Token.nextToken()); else if (cmd.compareTo("privmsg") == 0 && myGroup != null) sendPrivMessage(Token.nextToken()); else if (cmd.compareTo("login") == 0) login(Token.nextToken()); else if (cmd.compareTo("part") == 0) part(); else if (cmd.compareTo("setmode") == 0) mode = Token.nextToken(); else if (cmd.compareTo("join") == 0) join(toInt(Token.nextToken())); else if (cmd.compareTo("joinnew") == 0) joinNew(Token.nextToken()); else if (cmd.compareTo("list") == 0) server.getList(gameID, this); else if (cmd.compareTo("who") == 0 && myGroup != null) send("@|who|" + myGroup.getUserList()); else if (cmd.compareTo("logout") == 0) logout("User Quit"); else if (cmd.compareTo("count") == 0) send("@|count|" + server.getUserCount()); else if (cmd.compareTo("oneway") == 0 && myGroup != null) myGroup.setBroadcastType(false); else if (cmd.compareTo("twoway") == 0 && myGroup != null) myGroup.setBroadcastType(true); else if (cmd.compareTo("transid") == 0 && myGroup != null) myGroup.useName = false; else if (cmd.compareTo("transname") == 0 && myGroup != null) myGroup.useName = true; else send("@|ERROR|Unknown Command"); } /** * Handles broadcasting of data to all members of a group. If the * user is not authenticated, or is not in a group, and error is returned * to the client. * * @param data the data to broadcast. * @since gnsd.1.0.1 */ private void handleData(String data) { if (id == -1) send("@|ERROR|User Not Authenticated"); else if (myGroup == null) send("@|ERROR|Cannot send message, not in a group!"); else if (myGroup.useName) myGroup.sendAll("#|" + name + "|" + data, id); else myGroup.sendAll("#|" + id + "|" + data, id); } /** * Broadcast game information to all clients in a group. This is used * to transmit informational data, such as a ship type, weapon change, etc. * most data will be send through handleData. * * @param data the data to broadcast. * @see #handleData(java.lang.String) * @since gnsd.1.0.1 */ private void sendGameCommand(String data) { if (myGroup == null || id == -1) send("@|ERROR|Cannot send Game Command!"); else myGroup.sendAll("@|gameCmd|" + name + "`" + data, -1); } /** * Sends a private message to a user. This only works when the user is * in the same group that you are. * * @param data the data to private message * @since gnsd.1.0.1 */ private void sendPrivMessage(String data) { StringTokenizer Token = new StringTokenizer(data, "`", false); myGroup.sendPriv(Token.nextToken(), Token.nextToken()); } /** * Uses the server level commands to join a group. If the user is not * authenticated, an error is returned to the client. If the group * does not exist, an error is returned to the client. To make sure * the user is not in a group, the part() function is called. * * @param cID group ID to join. * @see #part() * @since gnsd.1.0.1 */ private void join(int cID) { if (id == -1) send("@|ERROR|Cannot join channel until Authenticated"); else if (server.checkGroup(cID)) { part(); server.join(cID, this); } else send("@|ERROR|Cannot join unformed channel with that command."); } /** * Allows the user to make a new group. This works much the same * way as the join command, and returns errors the same. To make * sure we don't create two group with the same ID, we check to * make sure the group doesn't exist. If it does already exist, it is * just like using join(id); * * @param data new channel information * @see #join(int) * @since gnsd.1.0.1 */ private void joinNew(String data) { StringTokenizer Token = new StringTokenizer(data, "`", false); int cID = toInt(Token.nextToken()); if (id == -1) send("@|ERROR|Cannot join channel until Authenticated"); if (server.checkGroup(cID)) { part(); server.join(cID, this); } else { part(); server.joinNew(cID, gameID, Token.nextToken(), Token.nextToken(), this); } } /** * Allows a user to leave a channel. Makes sure that the user is in a group * to begin with, and if they are, they are removed from it. myGroup is * set to null to finish the operation. * * @see #myGroup * @since gnsd.1.0.1 */ private synchronized void part() { if (myGroup != null) { log.print(getAddress() + " @part("+ myGroup.getID() + ") " + id + " 100"); myGroup.removeUser(this); myGroup = null; } } /** * Allows the user to Login to the network and become authinticated. Uses * a server level command, and checks to make sure the name is valid, i.e. * not "logout". If authintication fails, an error is returned to the * client and they are disconnected. If the login work, id, gameID, and * and name are all set. * * @param data user data, "userID`password`gameID" * @see id * @see gameID * @see name * @since gnsd.1.0.1 */ private void login(String data) { StringTokenizer Token = new StringTokenizer(data, "`", false); name = server.authenticate (id = toInt(Token.nextToken()), Token.nextToken()); if (name.compareTo("logout") == 0) { id = -1; logout("User Authentication Failed!"); } else { gameID = toInt(Token.nextToken()); send("@|login|" + gameID); server.userCount(+1); log.print(getAddress() + " @login(" + gameID + ") 100"); server.join(gameID, this); } } /** * Causes the client connection to be closed. If posible the client * will be notified that the connection is being closed. * * @param msg Message to send to client. * @since gnsd.1.0.1 */ private void logout(String msg) { if (id == -1) log.print(getAddress() + " @logout 100"); else { server.userCount(-1); part(); log.print(getAddress() + " @logout " + id + " 100"); } send("@|logout|" + msg); try { soc.close(); soc = null; } catch (IOException ex) { if (log.debug()) ex.printStackTrace(); } active = false; stop(); } /** * Converts a String to an int; * * @param s The String to be converted * @return an int value from s * @since gnsd.1.0.1 */ private int toInt(String s) { Integer i = new Integer(s); return i.intValue(); } }