All Downloads are FREE. Search and download functionalities are using the official Maven repository.

ml.karmaconfigs.remote.messaging.worker.ssl.SSLClient Maven / Gradle / Ivy

package ml.karmaconfigs.remote.messaging.worker.ssl;

/*
 * GNU LESSER GENERAL PUBLIC LICENSE
 * Version 2.1, February 1999
 * 

* Copyright (C) 1991, 1999 Free Software Foundation, Inc. * 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA * Everyone is permitted to copy and distribute verbatim copies * of this license document, but changing it is not allowed. *

* [This is the first released version of the Lesser GPL. It also counts * as the successor of the GNU Library Public License, version 2, hence * the version number 2.1.] */ import ml.karmaconfigs.api.common.Console; import ml.karmaconfigs.api.common.timer.SourceSecondsTimer; import ml.karmaconfigs.api.common.timer.SourceSimpleTimer; import ml.karmaconfigs.api.common.timer.scheduler.LateScheduler; import ml.karmaconfigs.api.common.timer.scheduler.SimpleScheduler; import ml.karmaconfigs.api.common.timer.scheduler.worker.AsyncLateScheduler; import ml.karmaconfigs.api.common.utils.PrefixConsoleData; import ml.karmaconfigs.api.common.utils.enums.Level; import ml.karmaconfigs.api.common.utils.string.StringUtils; import ml.karmaconfigs.remote.messaging.listener.RemoteListener; import ml.karmaconfigs.remote.messaging.listener.event.client.ServerConnectEvent; import ml.karmaconfigs.remote.messaging.listener.event.client.ServerDisconnectEvent; import ml.karmaconfigs.remote.messaging.listener.event.client.ServerMessageEvent; import ml.karmaconfigs.remote.messaging.platform.SecureClient; import ml.karmaconfigs.remote.messaging.remote.RemoteServer; import ml.karmaconfigs.remote.messaging.util.WorkLevel; import ml.karmaconfigs.remote.messaging.util.message.*; import ml.karmaconfigs.remote.messaging.util.message.type.MergeType; import ml.karmaconfigs.remote.messaging.worker.ssl.remote.SSLRemoteServer; import javax.net.ssl.*; import java.io.*; import java.net.InetAddress; import java.net.NetworkInterface; import java.nio.ByteBuffer; import java.nio.charset.StandardCharsets; import java.nio.file.Files; import java.nio.file.Path; import java.security.*; import java.util.*; import java.util.concurrent.ConcurrentHashMap; /** * Remote message client interface */ public final class SSLClient extends SecureClient { private final Set data_queue = Collections.newSetFromMap(new ConcurrentHashMap<>()); private RemoteServer remote = null; private String client_name = "client_" + new Random().nextInt(Integer.MAX_VALUE); private String server = "127.0.0.1"; private String key = ""; private int sv_port = 49305; private int client = 49300; private int disconnect = 10; private boolean disconnecting = false; private boolean debug = false; private boolean operative = false; private boolean instant_close = false; private boolean award_connection = false; private boolean tryingConnect = true; private boolean waitingResponse = false; private boolean preventStatusChange = false; private boolean fullyConnected = false; private SSLSocket socket; private final String password; private final String name; private final String extension; private final String type; private final Console console = new Console(this); private String protocol = "TLSv1.3"; private Path parent = getDataPath().resolve("certs"); /** * Initialize a default client that * will connect to local server at * default port 49305 */ public SSLClient(final String pwd, final String nm, final String ext, final String tp) { PrefixConsoleData data = console.getData(); data.setOkPrefix("&3[SSL Client (&aOK&3)]&b "); data.setInfoPrefix("&3[SSL Client (&7INFO&3)]&b "); data.setWarnPrefix("&3[SSL Client (&eWARNING&3)]&b "); data.setGravePrefix("&3[SSL Client (&4ERROR&3)]&b "); password = pwd; name = nm; extension = ext; type = tp; } /** * Initialize a client that will connect * to the specified server at specified port * * @param server_host the server * @param server_port the server port */ public SSLClient(final String pwd, final String nm, final String ext, final String tp, final String server_host, final int server_port) { server = server_host; sv_port = server_port; PrefixConsoleData data = console.getData(); data.setOkPrefix("&3[SSL Client (&aOK&3)]&b "); data.setInfoPrefix("&3[SSL Client (&7INFO&3)]&b "); data.setWarnPrefix("&3[SSL Client (&eWARNING&3)]&b "); data.setGravePrefix("&3[SSL Client (&4ERROR&3)]&b "); password = pwd; name = nm; extension = ext; type = tp; } /** * Initialize a client with a custom port * that will connect to the specified server at the * specified port * * @param client_port the client port * @param server_host the server * @param server_port the server port */ public SSLClient(final String pwd, final String nm, final String ext, final String tp, final int client_port, final String server_host, final int server_port) { client = client_port; server = server_host; sv_port = server_port; PrefixConsoleData data = console.getData(); data.setOkPrefix("&3[SSL Client (&aOK&3)]&b "); data.setInfoPrefix("&3[SSL Client (&7INFO&3)]&b "); data.setWarnPrefix("&3[SSL Client (&eWARNING&3)]&b "); data.setGravePrefix("&3[SSL Client (&4ERROR&3)]&b "); password = pwd; name = nm; extension = ext; type = tp; } /** * Set the client debug status * * @param status the client debug status * @return this instance */ @Override public SecureClient debug(final boolean status) { debug = status; return this; } /** * Set the current protocol * * @param p the protocol * @return this instance */ @Override public SecureClient protocol(final String p) { protocol = p; return this; } /** * Set the certificates location * * @param location the certificates location * @return the certificates location */ @Override public SecureClient certsLocation(final Path location) { parent = location; return this; } /** * Try to connect to the server * * @return a completable future when the client connects */ @Override public LateScheduler connect() { if (!operative) { tryingConnect = true; disconnecting = false; LateScheduler result = new AsyncLateScheduler<>(); Thread thread = new Thread(() -> { try { if (debug) { console.send("Initializing the connection with the server", Level.INFO); } try { if (!Files.exists(parent)) Files.createDirectories(parent); Path serverKeyStore = parent.resolve(name + "." + extension); Path trustedKeyStore = parent.resolve(name + "_trusted." + extension); InputStream internalStorage = new FileInputStream(serverKeyStore.toFile()); InputStream internalTruster = new FileInputStream(trustedKeyStore.toFile()); KeyStore keyStore = KeyStore.getInstance(type); keyStore.load(internalStorage, password.toCharArray()); KeyManagerFactory kmf = KeyManagerFactory.getInstance(KeyManagerFactory.getDefaultAlgorithm()); kmf.init(keyStore, password.toCharArray()); KeyStore trustedStore = KeyStore.getInstance(type); trustedStore.load(internalTruster, password.toCharArray()); TrustManagerFactory tmf = TrustManagerFactory.getInstance(TrustManagerFactory.getDefaultAlgorithm()); tmf.init(trustedStore); SSLContext sc = SSLContext.getInstance(protocol); TrustManager[] trustManagers = tmf.getTrustManagers(); KeyManager[] keyManagers = kmf.getKeyManagers(); sc.init(keyManagers, trustManagers, null); SSLSocketFactory ssf = sc.getSocketFactory(); try { socket = (SSLSocket) ssf.createSocket(server, sv_port, InetAddress.getLocalHost(), client); } catch (Throwable ex) { socket = (SSLSocket) ssf.createSocket(server, sv_port, InetAddress.getLoopbackAddress(), client); } socket.startHandshake(); } catch (Throwable ex) { result.complete(false, ex); return; } award_connection = true; if (debug) { console.send("The connection has been established but the client is still waiting for server confirmation, data can be started to be sent", Level.WARNING, server, sv_port); } try { while (award_connection) { if (instant_close) { tryingConnect = false; award_connection = false; operative = false; if (socket != null) { try { socket.close(); } catch (Throwable ignored) { } } socket = null; } if (tryingConnect) { MessageOutput output = new MessageDataOutput(); output.write("MAC", getMAC()); output.write("COMMAND_ENABLED", true); output.write("COMMAND", "connect"); output.write("ARGUMENT", client_name); if (!StringUtils.isNullOrEmpty(key)) { output.write("ACCESS_KEY", key); } byte[] compile = output.compile(); PrintWriter writer = new PrintWriter(socket.getOutputStream()); writer.println(new String(compile, StandardCharsets.UTF_8)); writer.flush(); } if (!operative) { if (socket != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); ByteBuffer readBuffer = ByteBuffer.wrap(reader.readLine().getBytes(StandardCharsets.UTF_8)); MessageInput input = new MessageDataInput(readBuffer.array()); if (input.getBoolean("COMMAND_ENABLED")) { String sequence = input.getString("COMMAND"); String mac = input.getString("MAC"); if (sequence != null && mac != null) { if (sequence.equalsIgnoreCase("accept")) { remote = new SSLRemoteServer(mac, InetAddress.getByName(server), sv_port, socket); if (debug) { console.send("Connection has been validated by the server", Level.OK); } for (byte[] data : data_queue) { ByteBuffer tmp = ByteBuffer.wrap(data); socket.getChannel().write(tmp); data_queue.remove(data); } award_connection = false; operative = true; fullyConnected = true; ServerConnectEvent event = new ServerConnectEvent(remote, this); RemoteListener.callClientEvent(event); } else { String argument = input.getString("ARGUMENT"); if (argument != null) { if (argument.equalsIgnoreCase("connect")) { instant_close = true; result.complete(false); String reason = input.getString("COMMAND_ARGUMENT"); if (reason != null) { console.send("Connection has been declined by the server ({0})", Level.GRAVE, reason); } } } } } else { console.send("Server {0}:{1} tried to send message with invalid {2}", Level.GRAVE, server, sv_port, (mac == null ? "mac address" : "command sequence")); } } } } } } catch (Throwable ex) { ex.printStackTrace(); } result.complete(true); try { while (operative) { if (socket != null) { if (!data_queue.isEmpty()) { //We won't read requests from server until he processes all our requests for (byte[] waiting : data_queue) { PrintWriter writer = new PrintWriter(socket.getOutputStream()); writer.println(new String(waiting, StandardCharsets.UTF_8)); writer.flush(); data_queue.remove(waiting); } } else { if (remote != null) { BufferedReader reader = new BufferedReader(new InputStreamReader(socket.getInputStream(), StandardCharsets.UTF_8)); ByteBuffer readBuffer = ByteBuffer.wrap(reader.readLine().getBytes(StandardCharsets.UTF_8)); MessageInput input = new MessageDataInput(readBuffer.array()); String mac = input.getString("MAC"); boolean isCommand = input.getBoolean("COMMAND_ENABLED"); if (remote.getMAC().equals(mac)) { if (isCommand) { String command = input.getString("COMMAND"); String argument = input.getString("ARGUMENT"); if (command != null && argument != null) { String data; switch (command.toLowerCase()) { case "success": switch (argument.toLowerCase()) { case "rename": client_name = input.getString("ARGUMENT_DATA"); if (client_name != null && debug) { console.send("Server accepted the new client name: {0}", Level.OK, client_name); } break; case "message": data = input.getString("ARGUMENT_DATA"); if (data != null && debug) { console.send("{0} to server: {1}", Level.INFO, data, new String(readBuffer.array())); } break; case "unknown": data = input.getString("ARGUMENT_DATA"); if (data != null && debug) { String[] arg_data = data.split(","); console.send("{0} ran custom command: {1} ( {2} )", Level.WARNING, arg_data[0], arg_data[1], arg_data[2]); } break; default: if (debug) { console.send("Unknown command from server: {0} ( {1} )", Level.GRAVE, command, argument); } break; } break; case "failed": switch (argument.toLowerCase()) { case "connect": data = input.getString("ARGUMENT_DATA"); if (data != null) { String[] connect_data = data.split(","); String name = connect_data[0]; String reason = connect_data[1]; console.send("Server declined connection as {0}, because: {1}", Level.GRAVE, name, reason); ServerDisconnectEvent connectEvent = new ServerDisconnectEvent(remote, this, reason); RemoteListener.callClientEvent(connectEvent); } break; case "rename": data = input.getString("ARGUMENT_DATA"); if (data != null) { String[] rename_data = data.split(","); console.send("Failed to change client name to {0}: {1}", Level.GRAVE, rename_data[0], rename_data[1]); } break; case "disconnect": data = input.getString("ARGUMENT_DATA"); if (data != null) { console.send("Failed while trying to disconnect the server ( you've been disconnected anyway ): {0}", Level.GRAVE, data); ServerDisconnectEvent disconnectEvent = new ServerDisconnectEvent(remote, this, "no server reason..."); RemoteListener.callClientEvent(disconnectEvent); } break; case "message": data = input.getString("ARGUMENT_DATA"); if (data != null) { console.send("Failed while trying to send a message to server: {0}", Level.GRAVE, data); } break; case "unknown": data = input.getString("ARGUMENT_DATA"); if (data != null) { String[] unknown_data = data.split(","); console.send("Failed while trying to execute custom command {0} with argument {1}: {2}", Level.GRAVE, unknown_data[0], unknown_data[1], unknown_data[2]); } break; default: if (debug) { console.send("Unknown command from server: {0} ( {1} )", Level.WARNING, command, argument); } break; } break; case "disconnect": String reason = input.getString("ARGUMENT_DATA"); if (reason != null) { console.send("Connection killed by server: {0}", Level.GRAVE, reason); } ServerDisconnectEvent event = new ServerDisconnectEvent(remote, this, reason); RemoteListener.callClientEvent(event); operative = false; award_connection = false; tryingConnect = true; socket.close(); socket = null; disconnecting = false; break; case "hello": fullyConnected = true; preventStatusChange = true; break; } } } else { ServerMessageEvent event = new ServerMessageEvent(remote, this, input); RemoteListener.callClientEvent(event); } } } } } } } catch (Throwable ex) { logger().scheduleLog(Level.GRAVE, ex); operative = false; award_connection = false; tryingConnect = false; try { socket.close(); } catch (Throwable ignored) { } socket = null; disconnecting = false; console.send("Connection killed by server: Server disconnected unexpectedly", Level.GRAVE); } } catch (Throwable ex) { result.complete(false, ex); } }); thread.start(); return result; } return null; } /** * Try to connect to the server * * @param accessKey the server access key * @return a completable future when the client connects */ @Override public LateScheduler connect(final String accessKey) { if (!operative) { key = accessKey; return connect(); } return null; } /** * Get the client name * * @return the client name */ @Override public String getName() { return client_name; } /** * Get the client MAC address * * @return the client MAC address */ @Override public String getMAC() { try { NetworkInterface network = NetworkInterface.getByInetAddress(InetAddress.getLocalHost()); byte[] macArray = network.getHardwareAddress(); StringBuilder str = new StringBuilder(); for (int i = 0; i < macArray.length; i++) { str.append(String.format("%02X%s", macArray[i], (i < macArray.length - 1) ? ":" : "")); } return str.toString(); } catch (Throwable ex) { System.out.println("Failed to locate MAC address..."); System.exit(1); return null; } } /** * Get the client address * * @return the client address */ @Override public InetAddress getHost() { return InetAddress.getLoopbackAddress(); } /** * Get the client port * * @return the client port */ @Override public int getPort() { return client; } /** * Send a message to the client * * @param message the message to send * @return if the message could be sent */ @Override public boolean sendMessage(MessageOutput message) { return false; } /** * Get the connected remote server * * @return the connected remote server */ @Override public RemoteServer getServer() { return remote; } /** * Get the client work level * * @return the client work level */ @Override public WorkLevel getWorkLevel() { return WorkLevel.TCP; } /** * Get if the client is trying to connect to the * server * * @return if the client is trying to connect to * the server */ @Override public boolean isConnecting() { return tryingConnect || award_connection; } /** * Get if the client is completely connected * to the server * * @return if the client is connected */ @Override public boolean isConnected() { if (operative) { try { MessageOutput output = new MessageDataOutput(); output.write("MAC", getMAC()); output.write("COMMAND_ENABLED", true); output.write("COMMAND", "HELLO"); output.write("ARGUMENT", ""); if (!StringUtils.isNullOrEmpty(key)) { output.write("ACCESS_KEY", key); } byte[] compile = output.compile(); PrintWriter writer = new PrintWriter(socket.getOutputStream()); writer.println(new String(compile, StandardCharsets.UTF_8)); writer.flush(); if (!waitingResponse) { waitingResponse = true; SimpleScheduler timer = new SourceSecondsTimer(this, 30, false).multiThreading(false); timer.secondChangeAction((sec) -> { if (preventStatusChange) { timer.cancel(); preventStatusChange = false; } }).endAction(() -> { fullyConnected = false; waitingResponse = false; close(); }).cancelAction((sec) -> waitingResponse = false); timer.start(); } return fullyConnected; } catch (Throwable ex) { return false; } } return false; } /** * Rename the client on the server interface * * @param name the client name */ @Override public void rename(final String name) { if (award_connection || operative) { client_name = name; MessageOutput output = new MessageDataOutput(); output.write("MAC", getMAC()); output.write("COMMAND_ENABLED", true); output.write("COMMAND", "rename"); output.write("ARGUMENT", client_name); try { if (debug) { console.send("Trying to inform the server about the name change request to {0}", Level.INFO, name); } byte[] compile = output.compile(); PrintWriter writer = new PrintWriter(socket.getOutputStream()); writer.println(new String(compile, StandardCharsets.UTF_8)); writer.flush(); } catch (Throwable ex) { data_queue.add(output.compile()); } } } /** * Send data to the server * * @param data the data to send */ @Override public void send(final byte[] data) { if (award_connection || operative) { MessageOutput output = new MessageDataOutput(data, MergeType.DIFFERENCE); output.write("MAC", getMAC()); output.write("COMMAND_ENABLED", false); try { byte[] compile = output.compile(); PrintWriter writer = new PrintWriter(socket.getOutputStream()); writer.println(new String(compile, StandardCharsets.UTF_8)); writer.flush(); } catch (Throwable ex) { data_queue.add(output.compile()); } } } /** * Close the connection */ @Override public void close() { if (operative) { if (!disconnecting) { try { MessageOutput output = new MessageDataOutput(); output.write("MAC", getMAC()); output.write("COMMAND_ENABLED", true); output.write("COMMAND", "disconnect"); output.write("ARGUMENT", "Client disconnect request"); byte[] compile = output.compile(); PrintWriter writer = new PrintWriter(socket.getOutputStream()); writer.println(new String(compile, StandardCharsets.UTF_8)); writer.flush(); if (debug) { console.send("Trying to inform the server about the disconnect request. The client will wait for server response. If no response is given in 10 seconds the client will disconnect anyway", Level.INFO); } disconnect = 10; disconnecting = true; SimpleScheduler scheduler = new SourceSimpleTimer(this, 1, true).multiThreading(false); scheduler.restartAction(() -> { if (disconnecting) { if (disconnect == 0) { operative = false; award_connection = false; tryingConnect = true; try { socket.close(); } catch (Throwable ignored) { } socket = null; disconnecting = false; } else { disconnect--; } } else { scheduler.cancel(); } }); } catch (Throwable ex) { ex.printStackTrace(); } } } else { instant_close = true; } } /** * Karma source name * * @return the source name */ @Override public String name() { return "SSL Client"; } /** * Karma source version * * @return the source version */ @Override public String version() { return "0"; } /** * Karma source description * * @return the source description */ @Override public String description() { return "TCP client to connect to a TCP server that has been created with RemoteMessaging API"; } /** * Karma source authors * * @return the source authors */ @Override public String[] authors() { return new String[]{"KarmaDev"}; } /** * Karma source update URL * * @return the source update URL */ @Override public String updateURL() { return null; } }





© 2015 - 2024 Weber Informatics LLC | Privacy Policy