diff --git a/COPYRIGHT b/COPYRIGHT new file mode 100644 index 0000000..3813d85 --- /dev/null +++ b/COPYRIGHT @@ -0,0 +1,15 @@ +ConsoleChat, a simple chat that can run on any machine with a Java JRE. +Copyright (C) 2014 Nicolás A. Ortega + +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 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 . diff --git a/WARRANTY b/WARRANTY new file mode 100644 index 0000000..3360453 --- /dev/null +++ b/WARRANTY @@ -0,0 +1,18 @@ +THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY +APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT +HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY +OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, +THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR +PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM +IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF +ALL NECESSARY SERVICING, REPAIR OR CORRECTION. + +IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING +WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MODIFIES AND/OR CONVEYS +THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY +GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE +USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF +DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD +PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), +EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF +SUCH DAMAGES. diff --git a/src-client/Client.class b/src-client/Client.class new file mode 100644 index 0000000..ef60375 Binary files /dev/null and b/src-client/Client.class differ diff --git a/src-client/Client.java b/src-client/Client.java new file mode 100644 index 0000000..7ce0169 --- /dev/null +++ b/src-client/Client.java @@ -0,0 +1,105 @@ +import java.net.*; +import java.io.*; +import java.lang.*; + +/** + * @author Nicolás A. Ortega + * @license GNU GPLv3 + * @year 2014 + * + * For details on the copyright, look at the COPYRIGHT file that came with + * this program. + * + */ +public class Client implements Runnable { + private String version = "v1.0"; + private Socket socket = null; + private ClientThread cThread = null; + private DataOutputStream streamOut = null; + private BufferedReader console = null; + private Thread thread = null; + + public static void main(String[] args) { + if(args.length != 2) { + System.out.println("Usage: java Client [server] [port]"); + } else { + Client client = new Client(args[0], Integer.parseInt(args[1])); + } + } + + public Client(String server, int port) { + System.out.println("ConsoleChat client " + version + " Copyright (C) 2014 Nicolás A. Ortega\n" + + "This program comes with ABSOLUTELY NO WARRANTY; details in WARRANTY file.\n" + + "This is free software, and you are welcome to redistribute it\n" + + "under certain conditions; details in LICENSE file.\n"); + + try { + // Create a new socket connection + System.out.println("Connecting to server..."); + socket = new Socket(server, port); + System.out.println("Connected!"); + start(); + } catch(UnknownHostException uhe) { + System.out.println("Host unknown: " + uhe.getMessage()); + } catch(IOException e) { + System.out.println("Unknown exception: " + e.getMessage()); + } + } + + public void run() { + while(thread != null) { + try { + streamOut.writeUTF(console.readLine()); + streamOut.flush(); + } catch(IOException e) { + System.out.println("Sending error: " + e.getMessage()); + stop(); + } + } + } + + public void handle(String msg) { + if(msg.equals("/quit")) { + System.out.println("Goodbye bye. Press RETURN to exit..."); + stop(); + } else { + System.out.println(msg); + if(msg.length() > 6 && msg.substring(0, 5).equals("Kick:")) { + stop(); + } + } + } + + private void start() throws IOException { + console = new BufferedReader(new InputStreamReader(System.in)); + streamOut = new DataOutputStream(socket.getOutputStream()); + + if(thread == null) { + cThread = new ClientThread(this, socket); + thread = new Thread(this); + thread.start(); + } + } + + public void stop() { + if(thread != null) { + thread.interrupt(); + thread = null; + } + + try { + if(console != null) { console.close(); } + if(streamOut != null) { streamOut.close(); } + if(socket != null) { socket.close(); } + } catch(IOException e) { + System.out.println("Error closing..."); + } + + try { + cThread.close(); + } catch(IOException e) { + System.out.println("Error closing the thread: " + e); + } + cThread.interrupt(); + } +} \ No newline at end of file diff --git a/src-client/ClientThread.class b/src-client/ClientThread.class new file mode 100644 index 0000000..1f3a7a5 Binary files /dev/null and b/src-client/ClientThread.class differ diff --git a/src-client/ClientThread.java b/src-client/ClientThread.java new file mode 100644 index 0000000..247c68d --- /dev/null +++ b/src-client/ClientThread.java @@ -0,0 +1,51 @@ +import java.io.*; +import java.net.*; + +/** + * @author Nicolás A. Ortega + * @license GNU GPLv3 + * @year 2014 + * + * For details on the copyright, look at the COPYRIGHT file that came with + * this program. + * + */ +public class ClientThread extends Thread { + private Socket socket = null; + private Client client = null; + private DataInputStream streamIn = null; + private boolean run = false; + + public ClientThread(Client _client, Socket _socket) { + client = _client; + socket = _socket; + open(); + start(); + } + + public void open() { + try { + streamIn = new DataInputStream(socket.getInputStream()); + } catch(IOException e) { + System.out.println("Error getting input stream: " + e); + client.stop(); + } + run = true; + } + + public void close() throws IOException { + if(streamIn != null) { streamIn.close(); } + run = false; + } + + public void run() { + while(run) { + try { + client.handle(streamIn.readUTF()); + } catch(IOException e) { + System.out.println("Listening error: " + e.getMessage()); + client.stop(); + } + } + } +} \ No newline at end of file diff --git a/src-server/Server.class b/src-server/Server.class new file mode 100644 index 0000000..3d0bc40 Binary files /dev/null and b/src-server/Server.class differ diff --git a/src-server/Server.java b/src-server/Server.java new file mode 100644 index 0000000..2881e5f --- /dev/null +++ b/src-server/Server.java @@ -0,0 +1,171 @@ +import java.net.*; +import java.io.*; +import java.util.*; + +/** + * @author Nicolás A. Ortega + * @license GNU GPLv3 + * @year 2014 + * + * For details on the copyright, look at the COPYRIGHT file that came with + * this program. + * + */ +public class Server implements Runnable { + private String version = "v1.0"; + private ArrayList clients = new ArrayList(); + private ServerSocket sSocket = null; + private Thread thread = null; + private int clientCount = 0; + private String passwd = "admin"; + + public static void main(String[] args) { + if(args.length != 1) { + System.out.println("You are using the program incorrectly."); + } else { + Server server = new Server(Integer.parseInt(args[0])); + } + } + + public Server(int port) { + System.out.println("ConsoleChat server " + version + " Copyright (C) 2014 Nicolás A. Ortega\n" + + "This program comes with ABSOLUTELY NO WARRANTY; details in WARRANTY file.\n" + + "This is free software, and you are welcome to redistribute it\n" + + "under certain conditions; details in LICENSE file.\n"); + try { + System.out.println("Binding to port " + port + "..."); + // Try to open a port on the specified port number + sSocket = new ServerSocket(port); + System.out.println("Server started: " + sSocket); + start(); + } catch(IOException e) { + System.out.println(e); + } + } + + public void run() { + while(thread != null) { + try { + System.out.println("Waiting for clients..."); + addThread(sSocket.accept()); + } catch(IOException e) { + System.out.println("Acceptance error: " + e); + stop(); + } + } + } + + public void start() { + if(thread == null) { + thread = new Thread(this); + thread.start(); + } + } + + public void stop() { + if(thread != null) { + thread.interrupt(); + thread = null; + } + + for(int i = 0; i < clients.size(); i++) { + try { + clients.get(i).close(); + } catch(IOException e) { + System.out.println("Error closing thread: " + e); + } + } + } + + public int findClient(int id) { + for(int i = 0; i < clientCount; i++) { + if(clients.get(i).getID() == id) { + return i; + } + } + + return -1; + } + + public synchronized void handle(int id, String username, String input) { + if(input.startsWith("/")) { + if(input.equals("/quit")) { + clients.get(findClient(id)).send("/quit"); + remove(id); + } else if(input.equals("/list")) { + int pos = findClient(id); + for(int i = 0; i < clients.size(); i++) { + String list = Integer.toString(clients.get(i).getID()) + " - " + clients.get(i).getUsername() + " |"; + if(pos == i) { list += " (you)"; } + if(clients.get(i).isAdmin()) { list += " (admin)"; } + clients.get(pos).send(list); + } + clients.get(pos).send("\n"); + } else if(input.substring(0, 3).equals("/pm")) { + int pos = findClient(Integer.parseInt(input.substring(4, 9))); + clients.get(pos).send("<" + username + ">: " + input.substring(10)); + } else if(input.length() > 12 && input.substring(0, 5).equals("/kick")) { + if(clients.get(findClient(id)).isAdmin()) { + int kID = Integer.parseInt(input.substring(6, 11)); + String reason = input.substring(12); + clients.get(findClient(kID)).send("Kick: " + reason); + remove(kID); + } else { + clients.get(findClient(id)).send("You are not admin."); + } + } else if(input.length() > 10 && input.substring(0, 10).equals("/giveadmin")) { + if(clients.get(findClient(id)).isAdmin()) { + int aID = Integer.parseInt(input.substring(11)); + clients.get(findClient(aID)).setAdmin(true); + clients.get(findClient(aID)).send("You have been made admin.\n"); + } else { + clients.get(findClient(id)).send("You are not admin."); + } + } else { + clients.get(findClient(id)).runCommand(input); + } + } else { + for(int i = 0; i < clientCount; i++) { + if(clients.get(i).getID() != id) { + clients.get(i).send(username + ": " + input); + } + } + } + } + + public synchronized void remove(int id) { + int pos = findClient(id); + if(pos >= 0) { + ServerThread toTerminate = clients.get(pos); + System.out.println("Remove client thread: " + id + " at " + pos); + try { + clients.get(pos).close(); + } catch(IOException e) { + System.out.println("Error closing thread: " + e); + } + clients.remove(pos); + clientCount--; + try { + toTerminate.close(); + } catch(IOException e) { + System.out.println("Error closing thread: " + e); + } + toTerminate.interrupt(); + } + } + + public void addThread(Socket socket) { + clients.add(new ServerThread(this, socket)); + try { + clients.get(clientCount).open(); + clients.get(clientCount).start(); + clientCount++; + } catch(IOException e) { + System.out.println("Error opening thread: " + e); + } + } + + public String getPasswd() { return passwd; } + + public void setPasswd(String npasswd) { this.passwd = npasswd; } +} \ No newline at end of file diff --git a/src-server/ServerThread.class b/src-server/ServerThread.class new file mode 100644 index 0000000..f5d0a53 Binary files /dev/null and b/src-server/ServerThread.class differ diff --git a/src-server/ServerThread.java b/src-server/ServerThread.java new file mode 100644 index 0000000..a927841 --- /dev/null +++ b/src-server/ServerThread.java @@ -0,0 +1,118 @@ +import java.net.*; +import java.io.*; + +/** + * @author Nicolás A. Ortega + * @license GNU GPLv3 + * @year 2014 + * + * For details on the copyright, look at the COPYRIGHT file that came with + * this program. + * + */ +public class ServerThread extends Thread { + private Server server = null; + private Socket socket = null; + private int id = -1; + private String username = "User"; + private DataInputStream streamIn = null; + private DataOutputStream streamOut = null; + private boolean run = false; + private boolean admin = false; + + public ServerThread(Server _server, Socket _socket) { + super(); + this.socket = _socket; + this.server = _server; + id = socket.getPort(); + } + + public void send(String msg) { + try { + streamOut.writeUTF(msg); + streamOut.flush(); + } catch(IOException e) { + System.out.println(id + " Error sending: " + e.getMessage()); + server.remove(id); + interrupt(); + } + } + + public void run() { + System.out.println("Server thread " + id + " running."); + while(run) { + try { + server.handle(id, username, streamIn.readUTF()); + } catch(IOException e) { + System.out.println(id + " error reading: " + e.getMessage()); + server.remove(id); + interrupt(); + } + } + } + + public void runCommand(String command) { + if(command.equals("/help")) { + send(" - /admin [passwd] -- Gain admin privileges.\n" + + " - /list -- Returns all the user's ID's and usernames.\n" + + " - /myID -- Returns your ID.\n" + + " - /myUserName -- Returns your username.\n" + + " - /pm [id] [msg] -- Sends a message to only one of the clients.\n" + + " - /setUserName [newusername] -- Change your username.\n" + + " - /help -- Show this information.\n" + + " - /quit -- Quit.\n"); + if(admin) { + send("Admin commands:\n" + + " - /chgpasswd [newpasswd] -- Change the server admin password.\n" + + " - /giveadmin [id] -- Give another client admin as well.\n" + + " - /kick [id] [reason] -- Kick out a user and state a reason for the kick.\n"); + } + } else if(command.length() > 6 && command.substring(0, 6).equals("/admin")) { + if(command.substring(7).equals(server.getPasswd())) { + admin = true; + send("You are now admin.\n"); + } else { + send("Wrong password.\n"); + } + } else if(command.equals("/myID")) { + send(Integer.toString(id) + "\n"); + } else if(command.equals("/myUserName")) { + send(username + "\n"); + } else if(command.length() > 12 && command.substring(0, 12).equals("/setUserName")) { + if(command.length() <= 13) { + send("You did not enter a new username."); + } else { + username = command.substring(13); + send("Your username is now: " + username + "\n"); + } + } else if(admin && command.length() > 10 && command.substring(0, 10).equals("/chgpasswd")) { + if(command.length() > 11) { + server.setPasswd(command.substring(11)); + } else { + send("You did not enter a new password.\n"); + } + } else { + send("You have either entered an invalid command, or used an improper form.\n"); + } + } + + public void open() throws IOException { + streamIn = new DataInputStream(new BufferedInputStream(socket.getInputStream())); + streamOut = new DataOutputStream(new BufferedOutputStream(socket.getOutputStream())); + + run = true; + } + + public void close() throws IOException { + if(socket != null) { socket.close(); } + if(streamIn != null) { streamIn.close(); } + if(streamOut != null) { streamOut.close(); } + run = false; + } + + public int getID() { return id; } + public String getUsername() { return username; } + public boolean isAdmin() { return admin; } + + public void setAdmin(boolean a) { this.admin = a; } +} \ No newline at end of file