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