Package erki.talk.server

Source Code of erki.talk.server.ErkiTalkServer$Client

/*
* (c) Copyright 2007-2008 by Edgar Kalkowski (eMail@edgar-kalkowski.de)
*
* The ErkiTalk Chat Server 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.
*
* 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, see <http://www.gnu.org/licenses/>.
*/

package erki.talk.server;

import java.io.BufferedReader;
import java.io.FileInputStream;
import java.io.FileNotFoundException;
import java.io.FileOutputStream;
import java.io.IOException;
import java.io.InputStreamReader;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.io.OutputStreamWriter;
import java.io.PrintStream;
import java.io.PrintWriter;
import java.io.UnsupportedEncodingException;
import java.net.ServerSocket;
import java.net.Socket;
import java.security.NoSuchAlgorithmException;
import java.util.Calendar;
import java.util.LinkedList;
import java.util.List;
import java.util.TreeMap;

import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;

import erki.api.util.Logger;
import erki.talk.Crypto;
import erki.talk.DHKeyExchange;

/**
* This class is a tcp talk server.
*
* @author Edgar Kalkowski
*/
public class ErkiTalkServer implements Runnable {
   
    /** All the log messages go to this file. */
    private static String logFile = "ErkiTalkServer.log";
   
    /** The version string of the server. */
    private static final String VERSION = "v3.1.0";
   
    /** The file containing the md5's of all registered user's passwords. */
    private static final String PWD_FILE = "passwd";
   
    /** The timestamp when this server was started. */
    private final long startTimestamp;
   
    /** The timestamp when the topic was set. */
    private long topicTimestamp = System.currentTimeMillis();
   
    /** The user who set the current topic. */
    private String topicUser = "ErkiTalkServer";
   
    /** The port the server is running on. */
    private int port;
   
    /** The current topic of this chat. */
    private String topic = "cd /pub; more beer";
   
    /** The list of all clients. */
    private List<Client> clients = new LinkedList<Client>();
   
    /**
     * Contains the hex strings of the sha-512's of the passwords of all
     * registered users.
     */
    private TreeMap<String, String> pwds;
   
    /** This static block initializes the log file. */
    static {
       
        try {
            Logger.setHander(new PrintStream(
                    new FileOutputStream(logFile, true), true, "UTF-8"));
        } catch (UnsupportedEncodingException e) {
            e.printStackTrace();
            System.exit(-1);
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.exit(-1);
        }
       
        Logger.print(ErkiTalkServer.class, "This is Erki der Loony's talk "
                + "server " + VERSION + ". Please have fun!");
    }
   
    /**
     * Initializes a new instance of this talk server listening on a specific
     * port.
     *
     * @param port
     *        The port the server shall be running on.
     */
    public ErkiTalkServer(int port) {
        this.port = port;
        this.startTimestamp = System.currentTimeMillis();
        loadPwds();
       
        // Start a ping thread
        new Thread() {
           
            @Override
            public void run() {
                super.run();
               
                while (true) {
                    Client[] cArray;
                   
                    synchronized (clients) {
                        cArray = clients.toArray(new Client[0]);
                    }
                   
                    for (Client c : cArray) {
                       
                        if (c.ping == false) {
                            c.send("PING");
                            c.ping = true;
                        } else {
                            c.kill("PING timeout");
                        }
                    }
                   
                    try {
                        Thread.sleep(300000);
                    } catch (InterruptedException e) {
                    }
                }
            }
           
        }.start();
    }
   
    @SuppressWarnings("unchecked")
    private void loadPwds() {
       
        try {
            ObjectInputStream objectIn = new ObjectInputStream(
                    new FileInputStream(PWD_FILE));
            pwds = (TreeMap<String, String>) objectIn.readObject();
            objectIn.close();
        } catch (FileNotFoundException e) {
            // Do nothing. A pwd-file will be generated if needed.
            pwds = new TreeMap<String, String>();
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        } catch (ClassNotFoundException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
   
    private void savePwds() {
       
        try {
            ObjectOutputStream objectOut = new ObjectOutputStream(
                    new FileOutputStream(PWD_FILE));
            objectOut.writeObject(pwds);
            objectOut.close();
        } catch (FileNotFoundException e) {
            e.printStackTrace();
            System.exit(-1);
        } catch (IOException e) {
            e.printStackTrace();
            System.exit(-1);
        }
    }
   
    /**
     * Main server method that listens for clients and creates a new thread for
     * each one.
     */
    @Override
    public void run() {
        ServerSocket socket = null;
        Socket clientSocket;
       
        try {
            socket = new ServerSocket(port);
        } catch (IOException e) {
            Logger.error(this, "Could not aquire socket!");
            System.exit(-1);
        }
       
        while (true) {
           
            try {
                clientSocket = socket.accept();
            } catch (IOException e) {
                Logger.error(this, "Error while waiting for clients! Server "
                        + "will restart in 5 min.");
               
                try {
                    socket.close();
                } catch (IOException e1) {
                }
               
                try {
                    Thread.sleep(300000);
                } catch (InterruptedException ex) {
                }
               
                break;
            }
           
            Client client = null;
           
            try {
                client = new Client(clientSocket);
            } catch (IOException e) {
                Logger.error(this, "Could not create new client instance!");
               
                try {
                    clientSocket.close();
                } catch (IOException e1) {
                }
            }
           
            Logger.info(this, "A new client has connected from "
                    + clientSocket.getInetAddress().getCanonicalHostName()
                    + ".");
            client.start();
        }
    }
   
    /** A specified client sends text to all connected clients. */
    public void sendText(Client client, String line) {
        String msg = "TEXT " + client.getNick() + ": " + line;
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send(msg);
        }
    }
   
    /** Send an information to a specific client. */
    public void sendInfo(String line, Client client) {
        client.send("INFO " + line);
    }
   
    /** Notify all clients that the topic has changed. */
    public void sendTopicChange(Client client, String newTopic) {
        String msg = "NEWTOPIC " + client.getNick() + ": " + newTopic;
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send(msg);
        }
    }
   
    /**
     * Notify all clients that someone has changed the nick (and remind everyone
     * that the person with the new nick is not authed).
     */
    public void sendNickChange(String oldNick, String newNick) {
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send("ISAUTHED " + oldNick + ": FALSE");
            c.send("NEWNICK " + oldNick + ": " + newNick);
        }
    }
   
    /** Notify all clients that someone joined the chat. */
    public void sendJoin(Client client) {
        String msg = "JOIN " + client.getNick();
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send(msg);
        }
    }
   
    /** Notify all clients that someone has left the chat. */
    public void sendQuit(Client client, String reason) {
        String msg;
        Client[] cArray;
       
        if (reason != null) {
            msg = "QUIT " + client.getNick() + ": " + reason;
        } else {
            msg = "QUIT " + client.getNick();
        }
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send(msg);
        }
    }
   
    /** Notify a client that he has successfully registered an account. */
    public void sendRegistered(Client client) {
        client.send("REGISTERED");
    }
   
    /** Notify a client that his password was successfully deleted. */
    void sendDeregistered(Client client) {
        client.send("DEREGISTERED");
    }
   
    /**
     * Notify a client that all further communication will be encrypted. The
     * number transmitted allows the client to calculate the secret key. This is
     * the last message to be transmitted unencrypted. All other uses are
     * informed that this user switched to an encrypted connection.
     */
    public void sendEncrypt(Client client, String pubkey) {
        client.send("ENCRYPT " + pubkey);
    }
   
    /**
     * Notify all the clients that someone switched to an encrypted connection.
     * The client that is now encrypted also receives this notice (but of course
     * already encrypted).
     */
    public void sendEncryptInfo(Client client) {
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send("ISENCRYPTED " + client.getNick() + ": TRUE");
        }
    }
   
    /**
     * Notify a client that his authentification was successful and all others
     * that the client has authed.
     */
    public void sendAuth(Client client) {
        client.send("AUTHED");
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send("ISAUTHED " + client.getNick() + ": TRUE");
        }
    }
   
    /** Notify a client if another client uses an encrypted connection. */
    public void sendIsEncrypted(Client client, Client encClient) {
       
        if (encClient.encrypt) {
            client.send("ISENCRYPTED " + encClient.getNick() + ": TRUE");
        } else {
            client.send("ISENCRYPTED " + encClient.getNick() + ": FALSE");
        }
    }
   
    /** Notify a client if another client is authed at the server. */
    public void sendIsAuthed(Client client, Client authedClient) {
       
        if (authedClient.authed) {
            client.send("ISAUTHED " + authedClient.getNick() + ": TRUE");
        } else {
            client.send("ISAUTHED " + authedClient.getNick() + ": FALSE");
        }
    }
   
    /**
     * Notify a client that all further communication will be sent in plain text
     * and not encrypted. This message itself is the last one transmitted
     * encrypted. Notify all other clients that this client switched back to
     * plain text and no longer uses an encrypted connection.
     */
    public void sendEndEncrypt(Client client) {
        client.send("PLAIN");
    }
   
    /**
     * Notify all clients that someone canceled encryption and sends plain text
     * again. The client that is now sending plain text again also receives this
     * notice (of course in plain text).
     */
    public void sendEndEncryptInfo(Client client) {
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send("ISENCRYPTED " + client.getNick() + ": FALSE");
        }
    }
   
    /** Send an error message to a specific client. */
    public void sendError(String line, Client client) {
        client.send("ERROR " + line);
    }
   
    /** Notify a client that the chosen nick is already in use. */
    public void sendNickInUse(Client client, String nick) {
        client.send("NICKINUSE " + nick);
    }
   
    /** A specific client sends text to another client. */
    public void sendPM(Client from, String line, Client to) {
        to.send("PM " + from.getNick() + ": " + line);
    }
   
    /** A specific client sends indirect speech to all other clients. */
    public void sendIndirect(Client client, String line) {
        String msg = "INDIRECT " + client.getNick() + ": " + line;
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            c.send(msg);
        }
    }
   
    /** Send the user list to a specific client. */
    public void sendList(Client client) {
        client.send("BEGINUSERS");
        Client[] cArray;
       
        synchronized (clients) {
            cArray = clients.toArray(new Client[0]);
        }
       
        for (Client c : cArray) {
            client.send("USER " + (c.authed ? "T" : "F")
                    + (c.encrypt ? "T" : "F") + " " + c.getNick());
        }
       
        client.send("ENDUSERS");
    }
   
    /** Responds to a client's ping request. */
    public void sendPong(Client client) {
        client.send("PONG");
    }
   
    /** Sends the time for which the server is running to a client. */
    public void sendUptime(Client client) {
        client.send("UPTIME " + getTime(startTimestamp));
    }
   
    /** Send the chat topic to a specific client. */
    public void sendTopic(Client client) {
       
        synchronized (topic) {
            client.send("TOPIC " + topicUser + ": " + getTime(topicTimestamp)
                    + ": " + topic);
        }
    }
   
    /** Removes a client e.g. when it has disconnected. */
    public void remove(Client client, String reason) {
       
        synchronized (clients) {
           
            if (clients.contains(client)) {
                clients.remove(client);
                sendQuit(client, reason);
            } else {
                return;
            }
        }
    }
   
    /** Indicates wether or not a nick is currently in use. */
    public boolean isInUse(String nick) {
       
        synchronized (clients) {
           
            for (Client c : clients) {
               
                if (c.getNick() != null && c.getNick().equals(nick)) {
                    return true;
                }
            }
        }
       
        return false;
    }
   
    /** @return The current topic of the chat. */
    public String getTopic() {
       
        synchronized (topic) {
            return topic;
        }
    }
   
    /** Changes the topic to a new one. */
    public void setTopic(String topic) {
       
        synchronized (topic) {
            this.topic = topic;
        }
    }
   
    /**
     * @return A nice string representation of a given unix timestamp. The
     *         format is "dd.mm.yyyy hh:mm".
     */
    public static String getTime(long timestamp) {
        String result = "";
        Calendar calendar = Calendar.getInstance();
        calendar.setTimeInMillis(timestamp);
        int day = calendar.get(Calendar.DAY_OF_MONTH);
        result += day < 10 ? "0" + day + "." : day + ".";
        int month = calendar.get(Calendar.MONTH) + 1;
        result += month < 10 ? "0" + month + "." : month + ".";
        int year = calendar.get(Calendar.YEAR);
        result += year + " ";
        int hours = calendar.get(Calendar.HOUR_OF_DAY);
        result += hours < 10 ? "0" + hours + ":" : hours + ":";
        int mins = calendar.get(Calendar.MINUTE);
        result += mins < 10 ? "0" + mins : mins;
        return result;
    }
   
    /**
     * @return A nice string representation of a time difference. The format is
     *         "[d ]hh:mm:ss".
     */
    public static String getTimeDiff(long startTimestamp, long endTimestamp) {
        String result = "";
        long diff = endTimestamp - startTimestamp;
        long sec = diff / 1000;
        long min = sec / 60;
        sec %= 60;
        long hours = min / 60;
        min = min %= 60;
        long days = hours / 24;
        hours %= 24;
        result += days + "d ";
        result += hours < 10 ? "0" + hours + ":" : hours + ":";
        result += min < 10 ? "0" + min + ":" : min + ":";
        result += sec < 10 ? "0" + sec : sec;
        return result;
    }
   
    /**
     * Represents a client and stores the client's socket.
     *
     * @author Edgar Kalkowski
     */
    class Client extends Thread {
       
        /** This client's socket. */
        private Socket socket;
       
        /** The nickname of this client. */
        private String name = null;
       
        /** The output stream of this client's socket. */
        private PrintWriter socketOut;
       
        /** The input stream of this client's socket. */
        private BufferedReader socketIn;
       
        /** Indicates whether this client's thread shall exit itself. */
        private boolean killed = false;
       
        /** Indicates whether this client must pong until the next ping cycle. */
        private boolean ping = false;
       
        /**
         * Indicates whether this client uses an encrypted connection. If
         * {@code true} all text received from this client will be passed
         * through {@link Crypto#decrypt(String)} before interpreting and all
         * text send to this client will be passed through
         * {@link Crypto#encrypt(String)} before send to the client.
         */
        private boolean encrypt = false;
       
        /**
         * The {@link Crypto} object used by this client to encrypt messages if
         * {@link encrypt} ist {@code true}.
         */
        private Crypto crypto = null;
       
        /**
         * The {@link DHKeyExchange} object used for this client to generate
         * public and private keys.
         */
        private DHKeyExchange keyExchange = null;
       
        /**
         * Indicates whether this client has authed itself with a password
         * submitted via an encrypted channel.
         */
        private boolean authed = false;
       
        /**
         * Creates a new talk server client.
         *
         * @param socket
         *        The client socket.
         * @throws IOException
         */
        public Client(Socket socket) throws IOException {
            this.socket = socket;
            name = socket.getInetAddress().getCanonicalHostName();
            socketOut = new PrintWriter(new OutputStreamWriter(socket
                    .getOutputStream(), "UTF-8"), true);
            socketIn = new BufferedReader(new InputStreamReader(socket
                    .getInputStream(), "UTF-8"));
        }
       
        /**
         * Send a line of text to this client.
         *
         * @param line
         *        The line of text to send. This {@code String} must not be
         *        terminated by a "\r" or "\n".
         */
        public synchronized void send(String line) {
           
            if (encrypt) {
               
                try {
                    line = crypto.encrypt(line);
                } catch (IllegalBlockSizeException e) {
                    e.printStackTrace();
                    System.exit(-1);
                } catch (BadPaddingException e) {
                    e.printStackTrace();
                    System.exit(-1);
                }
            }
           
            socketOut.println(line);
        }
       
        /** Gives access to this client's nick. */
        public String getNick() {
            return name;
        }
       
        /**
         * Set the ping flag that indicated if this client must pong until the
         * next ping cycle.
         */
        public void setPing(boolean ping) {
            this.ping = ping;
        }
       
        /** Quits this client for a reason. */
        public void quit(String reason) {
            sendInfo("Thanks for the visit!", this);
            sendInfo("Bye!", this);
            kill(reason);
        }
       
        /** Kill this client if e.g. it's socket is dead. */
        public void kill(String reason) {
            killed = true;
           
            if (reason == null) {
                Logger.info(this, getNick() + " has left.");
            } else {
                Logger.info(this, getNick() + " has left (" + reason + ").");
            }
           
            remove(this, reason);
           
            try {
                socket.close();
            } catch (IOException e) {
            }
        }
       
        /** Handles all messages from this client. */
        @Override
        public void run() {
           
            try {
                String line;
                send("PING");
               
                if ((line = socketIn.readLine()) != null) {
                   
                    if (!line.toUpperCase().equals("PONG")) {
                        send("I don't like you! Go away!");
                        Logger.info(this, "The new client does not speak my "
                                + "language, so I'll kick it.");
                        kill("Does not speak ErkiTalk properly");
                        return;
                    }
                   
                } else {
                    kill("Lost connection");
                    return;
                }
               
                while (true) {
                   
                    if ((line = socketIn.readLine()) != null) {
                       
                        if (line.toUpperCase().startsWith("NICK ")) {
                            line = line.substring("NICK ".length());
                           
                            if (line.equals("")) {
                                sendError("The empty nick is not allowed!",
                                        this);
                                continue;
                            } else if (line.contains(":")) {
                                sendError("Your nick must not contain colons "
                                        + "(»:«)!", this);
                                continue;
                            } else if (isInUse(line)) {
                                sendNickInUse(this, line);
                                continue;
                            } else {
                                name = line;
                                Logger.info(this, "The new Client chose the "
                                        + "nickname »" + name + "«.");
                                sendJoin(this);
                               
                                synchronized (clients) {
                                    clients.add(this);
                                }
                               
                                break;
                            }
                           
                        } else {
                            sendError("Please submit your nickname!", this);
                            continue;
                        }
                       
                    } else {
                        kill("Lost connection");
                        return;
                    }
                }
               
                sendInfo("This is Erki der Loony's talk server " + VERSION
                        + ". Please have fun!", this);
                sendInfo("The server is running since "
                        + getTime(startTimestamp)
                        + " for "
                        + getTimeDiff(startTimestamp, System
                                .currentTimeMillis()) + ".", this);
                sendInfo("The current chat topic is: " + getTopic(), this);
                sendInfo("The topic was set on " + getTime(topicTimestamp)
                        + " by " + topicUser + ".", this);
                String users = "";
               
                synchronized (clients) {
                   
                    for (Client c : clients) {
                        users += c.getNick() + ", ";
                    }
                }
               
                if (users.equals("")) {
                    users = "keine, ";
                }
               
                sendInfo("The following users are currently online: "
                        + users.substring(0, users.length() - 2) + ".", this);
               
                while (!killed) {
                   
                    if ((line = socketIn.readLine()) != null) {
                       
                        if (encrypt) {
                           
                            try {
                                line = crypto.decrypt(line);
                            } catch (NumberFormatException e) {
                                sendError("Your encryption is flawed!", this);
                                sendInfo("I received " + line
                                        + " which is not a "
                                        + "correctly encrypted line!", this);
                            } catch (IllegalBlockSizeException e) {
                                sendError("Your encryption is flawed!", this);
                                sendInfo("I received " + line
                                        + " which is not a "
                                        + "correctly encrypted line!", this);
                            } catch (BadPaddingException e) {
                                sendError("Your encryption is flawed!", this);
                                sendInfo("I received " + line
                                        + " which is not a "
                                        + "correctly encrypted line!", this);
                            }
                        }
                       
                        if (line.toUpperCase().startsWith("AUTH ")) {
                            String pwd = line.substring("AUTH ".length());
                           
                            if (!encrypt) {
                                Logger.info(this, getNick() + " failed to "
                                        + "auth (No encrypted connection).");
                                sendError("For your own safety you have to "
                                        + "transmit your password via an "
                                        + "encrypted connection!", this);
                                sendInfo("You can initialize an encrypted "
                                        + "connection via the »ENCRYPT« "
                                        + "command.", this);
                                continue;
                            }
                           
                            if (!pwds.containsKey(getNick())) {
                                Logger.info(this, getNick() + " failed to "
                                        + "auth (Not registered).");
                                sendError("Please register at first via "
                                        + "»REGISTER«!", this);
                                continue;
                            }
                           
                            try {
                               
                                if (!pwds.get(getNick()).equals(
                                        Crypto.toSHA(pwd))) {
                                    Logger.info(this, getNick() + " failed "
                                            + "to auth (Wrong password).");
                                    sendError("Wrong password!", this);
                                    continue;
                                }
                               
                            } catch (NoSuchAlgorithmException e) {
                                e.printStackTrace();
                                System.exit(-1);
                            }
                           
                            Logger.info(this, getNick()
                                    + " successfully authed.");
                            authed = true;
                            sendAuth(this);
                        } else if (line.toUpperCase().equals("AUTH")) {
                            sendError("You have to provide your password as "
                                    + "argument!", this);
                        } else if (line.toUpperCase().equals("DEREGISTER")) {
                           
                            if (!authed) {
                                sendError("You have to be authed to delete "
                                        + "you password!", this);
                                Logger.info(this, getNick() + " failed to "
                                        + "deregister his account (Not "
                                        + "authed).");
                                continue;
                            }
                           
                            if (!pwds.containsKey(getNick())) {
                                sendError("There is no password registered "
                                        + "for your nick!", this);
                                Logger.info(this, getNick() + " failed to "
                                        + "deregister his account (No account "
                                        + "registered).");
                                continue;
                            }
                           
                            Logger.info(this, getNick() + " successfully "
                                    + "deregistered his account.");
                            pwds.remove(getNick());
                            savePwds();
                            sendDeregistered(this);
                        } else if (line.toUpperCase().startsWith("ENCRYPT ")) {
                            String pubKey = line.substring("ENCRYPT ".length());
                            keyExchange = new DHKeyExchange();
                            String publicKey = keyExchange.getPublicKey(pubKey);
                            sendEncrypt(this, publicKey);
                            Logger.info(this, getNick() + " initiated an "
                                    + "encrypted connection.");
                            crypto = new Crypto(keyExchange.getSecretKey());
                            encrypt = true;
                            sendEncryptInfo(this);
                        } else if (line.toUpperCase().equals("ENCRYPT")) {
                            sendError("You have to provide your public key "
                                    + "as argument!", this);
                        } else if (line.toUpperCase().equals("HELP")) {
                            sendInfo("This server understands the following "
                                    + "commands:", this);
                            sendInfo("AUTH ENCRYPT HELP ISAUTHED ISENCRYPTED"
                                    + " LIST ME NICK TEXT "
                                    + "TOPIC PING PLAIN PONG PM QUIT REGISTER "
                                    + "UPTIME", this);
                            sendInfo("Send HELP <cmd> to get info about the "
                                    + "command <cmd>.", this);
                            Logger.info(this, getNick() + " needs help.");
                        } else if (line.toUpperCase().startsWith("HELP ")) {
                            String cmd = line.substring("HELP ".length())
                                    .toUpperCase().trim();
                           
                            if (cmd.toUpperCase().equals("AUTH")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about AUTH.");
                                sendInfo("AUTH <pwd>", this);
                                sendInfo("Auth at server. You must use an "
                                        + "encrypted connection (see HELP "
                                        + "ENCRYPT) for authing.", this);
                            } else if (cmd.toUpperCase().equals("ENCRYPT")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about ENCRYPT.");
                                sendInfo("ENCRYPT <pubkey>", this);
                                sendInfo("Initialize an encrypted "
                                        + "connection. <pubkey> is the public "
                                        + "key of the client.", this);
                            } else if (cmd.toUpperCase().equals("HELP")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about HELP.");
                                sendInfo("HELP[ <cmd>]", this);
                                sendInfo("If <cmd> is omitted print the "
                                        + "available commands. If <cmd> is "
                                        + "given print information about the "
                                        + "command <cmd>.", this);
                            } else if (cmd.toUpperCase().equals("INDIRECT")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about INDIRECT.");
                                sendInfo("INDIRECT <text>", this);
                                sendInfo("Say <text> indirectly to the chat "
                                        + "like »Bob scratches his head«.",
                                        this);
                            } else if (cmd.toUpperCase().equals("ISAUTHED")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about ISAUTHED.");
                                sendInfo("ISAUTHED <nick>", this);
                                sendInfo("Request if user <nick> is authed "
                                        + "at server.", this);
                            } else if (cmd.toUpperCase().equals("ISENCRYPTED")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about ISENCRYPTED.");
                                sendInfo("ISENCRYPTED <nick>", this);
                                sendInfo("Request if user <nick> uses an "
                                        + "encrypted connection.", this);
                            } else if (cmd.toUpperCase().equals("LIST")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about LIST.");
                                sendInfo("LIST", this);
                                sendInfo("Request the user list.", this);
                            } else if (cmd.toUpperCase().equals("NICK")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about NICK.");
                                sendInfo("NICK <nick>", this);
                                sendInfo("Change your nick to <nick>.", this);
                            } else if (cmd.toUpperCase().equals("TEXT")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about TEXT.");
                                sendInfo("TEXT <text>", this);
                                sendInfo("Say <text> to the chat.", this);
                            } else if (cmd.toUpperCase().equals("TOPIC")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about TOPIC.");
                                sendInfo("TOPIC[ <topic>]", this);
                                sendInfo("If <topic> is omitted request the "
                                        + "current topic. If <topic> is given "
                                        + "change the topic of the chat to "
                                        + "<topic>.", this);
                            } else if (cmd.toUpperCase().equals("PING")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about PING.");
                                sendInfo("PING", this);
                                sendInfo("Ask the server if it's still there. "
                                        + "Will be responded by PONG.", this);
                            } else if (cmd.toUpperCase().equals("PLAIN")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about PLAIN.");
                                sendInfo("PLAIN", this);
                                sendInfo("Ask the server to transmit "
                                        + "unencrypted again. This command "
                                        + "must be sent encrypted.", this);
                            } else if (cmd.toUpperCase().equals("PONG")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about PONG.");
                                sendInfo("PONG", this);
                                sendInfo("This should be the reply to PINGs "
                                        + "from the server.", this);
                            } else if (cmd.toUpperCase().equals("PM")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about PM.");
                                sendInfo("PM <nick>: <text>", this);
                                sendInfo("Send private message <text> only to "
                                        + "user <nick>.", this);
                            } else if (cmd.toUpperCase().equals("QUIT")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about QUIT.");
                                sendInfo("QUIT[ <msg>]", this);
                                sendInfo("Leave the chat. If <msg> is given "
                                        + "leave <msg> as quit message.", this);
                            } else if (cmd.toUpperCase().equals("REGISTER")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about REGISTER.");
                                sendInfo("REGISTER <pwd>", this);
                                sendInfo("Register your current nick with "
                                        + "password <pwd>. This must be done "
                                        + "using an encrypted connection (see "
                                        + "HELP ENCRYPT for details).", this);
                            } else if (cmd.toUpperCase().equals("UPTIME")) {
                                Logger.info(this, getNick() + " needs help "
                                        + "about UPTIME.");
                                sendInfo("UPTIME", this);
                                sendInfo("Ask how long the server is already "
                                        + "running.", this);
                            } else {
                                Logger.info(this, getNick() + " asks help "
                                        + "about unknown command " + cmd + ".");
                                sendError("Unknown command: " + cmd, this);
                            }
                           
                        } else if (line.toUpperCase().startsWith("INDIRECT ")) {
                            sendIndirect(this, line.substring("INDIRECT "
                                    .length()));
                            Logger.print(this, getNick() + " "
                                    + line.substring("INDIRECT ".length()));
                        } else if (line.toUpperCase().equals("INDIRECT")) {
                            sendError("You have to provide the text you want "
                                    + "to say indirectly as argument!", this);
                        } else if (line.toUpperCase().startsWith("ISAUTHED ")) {
                            String nick = line.substring("ISAUTHED ".length());
                            Client[] cArray = clients.toArray(new Client[0]);
                            boolean found = false;
                           
                            for (Client c : cArray) {
                               
                                if (c.getNick().equals(nick)) {
                                    Logger.info(this, getNick()
                                            + " wants to know if " + nick
                                            + " is authed.");
                                    sendIsAuthed(this, c);
                                    found = true;
                                    break;
                                }
                            }
                           
                            if (!found) {
                                sendError("There is no user " + nick
                                        + " online!", this);
                                Logger.info(this, getNick()
                                        + " wants to know if " + nick
                                        + " is authed but there is no " + nick
                                        + " online.");
                            }
                           
                        } else if (line.toUpperCase().equals("ISAUTHED")) {
                            sendError("You have to provide a user's nickname "
                                    + "as argument!", this);
                        } else if (line.toUpperCase()
                                .startsWith("ISENCRYPTED ")) {
                            String nick = line.substring("ISENCRYPTED "
                                    .length());
                            Client[] cArray = clients.toArray(new Client[0]);
                            boolean found = false;
                           
                            for (Client c : cArray) {
                               
                                if (c.getNick().equals(nick)) {
                                    Logger.info(this, getNick()
                                            + " wants to know if " + nick
                                            + " uses an encrypted connection.");
                                    sendIsEncrypted(this, c);
                                    found = true;
                                    break;
                                }
                            }
                           
                            if (!found) {
                                Logger.info(this, getNick()
                                        + " wants to know if " + nick
                                        + " uses an encrypted "
                                        + "connection but there is no " + nick
                                        + " online.");
                                sendError("There is no user " + nick
                                        + " online!", this);
                            }
                           
                        } else if (line.toUpperCase().equals("ISENCRYPTED")) {
                            sendError("You have to provide a user's nickname "
                                    + "as argument!", this);
                        } else if (line.toUpperCase().equals("LIST")) {
                            Logger.info(this, getNick() + " requests the "
                                    + "user list.");
                            sendList(this);
                        } else if (line.toUpperCase().startsWith("NICK ")) {
                            String newNick = line.substring(5);
                           
                            if (newNick.equals("")) {
                                Logger.info(this, getNick() + " tried to "
                                        + "change his nick to the empty "
                                        + "string.");
                                sendError("The empty nick is not allowed! "
                                        + "Please choose a different nick.",
                                        this);
                                continue;
                            }
                           
                            if (newNick.contains(":")) {
                                Logger.info(this, getNick() + " tried to "
                                        + "change the "
                                        + "nick to a string containing a "
                                        + "colon which is forbidden.");
                                sendError("Your nick must not contain any "
                                        + "colons (\":\")! Please choose a "
                                        + "different nick.", this);
                                continue;
                            }
                           
                            if (!isInUse(newNick)) {
                                this.authed = false;
                                Logger.info(this, getNick() + " is now known "
                                        + "as " + newNick + ".");
                                sendNickChange(getNick(), newNick);
                                this.name = newNick;
                            } else {
                                Logger.info(this, getNick()
                                        + " tried to change the nick to "
                                        + newNick
                                        + " but that nick is already in use.");
                                sendNickInUse(this, newNick);
                            }
                           
                        } else if (line.toUpperCase().equals("NICK")) {
                            sendError("You have to provide your new nickname "
                                    + "as argument!", this);
                        } else if (line.toUpperCase().startsWith("TEXT ")) {
                            Logger.print(this, getNick() + ": "
                                    + line.substring(5));
                            sendText(this, line.substring(5));
                        } else if (line.toUpperCase().equals("TEXT")) {
                            sendError("You have to provide the text you want "
                                    + "to say as argument!", this);
                        } else if (line.toUpperCase().startsWith("TOPIC ")) {
                            String newTopic = line.substring(6);
                            Logger.info(this, getNick()
                                    + " changed the topic to »" + newTopic
                                    + "«");
                            sendTopicChange(this, newTopic);
                            setTopic(newTopic);
                            topicUser = getNick();
                            topicTimestamp = System.currentTimeMillis();
                        } else if (line.toUpperCase().equals("TOPIC")) {
                            Logger.info(this, getNick() + " wants to know "
                                    + "the topic.");
                            sendTopic(this);
                        } else if (line.toUpperCase().equals("PING")) {
                            sendPong(this);
                        } else if (line.toUpperCase().equals("PLAIN")) {
                            Logger.info(this, getNick()
                                    + " cancels his encrypted "
                                    + "connection and switches back to plain "
                                    + "text.");
                            sendEndEncrypt(this);
                            encrypt = false;
                            crypto = null;
                            keyExchange = null;
                            sendEndEncryptInfo(this);
                        } else if (line.toUpperCase().equals("PONG")) {
                            ping = false;
                        } else if (line.toUpperCase().startsWith("PM ")) {
                            line = line.substring(3);
                            Client[] cArray;
                            boolean userFound = false;
                            String nick = line.substring(0, line.indexOf(':'));
                            String text = line.substring(line.indexOf(':') + 2,
                                    line.length());
                           
                            synchronized (clients) {
                                cArray = clients.toArray(new Client[0]);
                            }
                           
                            for (Client c : cArray) {
                               
                                if (c.getNick().equals(nick)) {
                                    sendPM(this, text, c);
                                    Logger.info(this, getNick()
                                            + " talks privately to "
                                            + c.getNick() + ".");
                                    userFound = true;
                                    break;
                                }
                            }
                           
                            if (!userFound) {
                                Logger.info(this, getNick()
                                        + " tries to talk privately to " + nick
                                        + " but there is no user  online.");
                                sendError("There is no user »" + nick
                                        + "« online.", this);
                            }
                           
                        } else if (line.toUpperCase().equals("PM")) {
                            sendError("Syntax error! Send HELP PM for more "
                                    + "information!", this);
                        } else if (line.toUpperCase().startsWith("QUIT ")) {
                            quit(line.substring(5));
                        } else if (line.toUpperCase().equals("QUIT")) {
                            quit(null);
                        } else if (line.toUpperCase().startsWith("REGISTER ")) {
                           
                            if (!encrypt) {
                                Logger.info(this, getNick()
                                        + " could not register "
                                        + "(connection not encrypted).");
                                sendError("For your own safety you have to "
                                        + "transmit your password via an "
                                        + "encrypted connection!", this);
                                sendInfo("You can initialize an encrypted "
                                        + "connection via the »ENCRYPT« "
                                        + "command.", this);
                                continue;
                            }
                           
                            if (pwds.containsKey(getNick())) {
                                Logger.info(this, getNick()
                                        + " could not register "
                                        + "(already registered).");
                                sendError("There already exists a password "
                                        + "for your nick!", this);
                                sendInfo("Use DEREGISTER to delete your "
                                        + "account.", this);
                                continue;
                            }
                           
                            try {
                                pwds.put(getNick(), Crypto.toSHA(line
                                        .substring("REGISTER ".length())));
                            } catch (NoSuchAlgorithmException e) {
                                e.printStackTrace();
                                System.exit(-1);
                            }
                           
                            Logger.info(this, getNick()
                                    + " registered successfully.");
                            authed = true;
                            savePwds();
                            sendRegistered(this);
                        } else if (line.toUpperCase().equals("REGISTER")) {
                            sendError("You have to provide a password as "
                                    + "argument!", this);
                        } else if (line.toUpperCase().equals("UPTIME")) {
                            Logger.info(this, getNick()
                                    + " wants to know how long the "
                                    + "server is already running.");
                            sendUptime(this);
                        } else {
                            Logger.info(this, getNick() + " sent the unknown "
                                    + "command " + line + ".");
                            sendError("Unknown command: " + line, this);
                        }
                       
                    } else {
                        kill("Lost connection");
                    }
                }
               
            } catch (IOException e) {
               
                synchronized (clients) {
                   
                    if (clients.contains(this)) {
                        Logger.error(this, "Could not read from client socket!"
                                + " The client " + getNick()
                                + " is kicked from the server now.");
                        kill("Lost connection");
                    }
                }
            }
        }
    }
   
    /**
     * Creates a new talk server listening on port 7886 if no different port is
     * specified on the command line.
     *
     * @param args
     *        One may specify the port number the server shall run on.
     */
    public static void main(String[] args) {
        int port = 7886;
       
        for (String arg : args) {
           
            try {
                port = Integer.parseInt(arg);
                Logger.info(ErkiTalkServer.class, "Using port recognized on "
                        + "the command line: " + port + ".");
            } catch (NumberFormatException e) {
                port = 7886;
                Logger.warning(ErkiTalkServer.class, "Could not parse "
                        + "command line argument »" + arg + "«!");
            }
        }
       
        try {
            new Thread(new ErkiTalkServer(port)).start();
        } catch (Throwable e) {
            Logger.error(ErkiTalkServer.class, e);
            System.exit(-1);
        }
    }
}
TOP

Related Classes of erki.talk.server.ErkiTalkServer$Client

TOP
Copyright © 2018 www.massapi.com. All rights reserved.
All source code are property of their respective owners. Java is a trademark of Sun Microsystems, Inc and owned by ORACLE Inc. Contact coftware#gmail.com.