diff --git a/LaunchServer/source/auth/handler/FileAuthHandler.java b/LaunchServer/source/auth/handler/FileAuthHandler.java index 05e4908..337dac5 100644 --- a/LaunchServer/source/auth/handler/FileAuthHandler.java +++ b/LaunchServer/source/auth/handler/FileAuthHandler.java @@ -198,10 +198,10 @@ public Auth(String username, String accessToken, String serverID) { this(username); if (accessToken == null && serverID != null) { - throw new IllegalArgumentException("Can't set accessToken while serverID is null"); + throw new IllegalArgumentException("Can't set access token while server ID is null"); } - // Set and verify accessToken + // Set and verify access token this.accessToken = accessToken == null ? null : SecurityHelper.verifyToken(accessToken); this.serverID = serverID == null ? diff --git a/LaunchServer/source/response/PingResponse.java b/LaunchServer/source/response/PingResponse.java index 1a35844..549c6c8 100644 --- a/LaunchServer/source/response/PingResponse.java +++ b/LaunchServer/source/response/PingResponse.java @@ -8,8 +8,8 @@ import launchserver.LaunchServer; public final class PingResponse extends Response { - public PingResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public PingResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override diff --git a/LaunchServer/source/response/Response.java b/LaunchServer/source/response/Response.java index dad62b9..60e5d5b 100644 --- a/LaunchServer/source/response/Response.java +++ b/LaunchServer/source/response/Response.java @@ -3,18 +3,21 @@ import java.io.IOException; import launcher.LauncherAPI; +import launcher.helper.LogHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launchserver.LaunchServer; public abstract class Response { @LauncherAPI protected final LaunchServer server; + @LauncherAPI protected final int id; @LauncherAPI protected final HInput input; @LauncherAPI protected final HOutput output; @LauncherAPI - protected Response(LaunchServer server, HInput input, HOutput output) { + protected Response(LaunchServer server, int id, HInput input, HOutput output) { this.server = server; + this.id = id; this.input = input; this.output = output; } @@ -23,6 +26,16 @@ public abstract void reply() throws Exception; @LauncherAPI + protected final void debug(String message) { + LogHelper.subDebug("#%d %s", id, message); + } + + @LauncherAPI + protected final void debug(String message, Object... args) { + debug(String.format(message, args)); + } + + @LauncherAPI protected final void writeNoError(HOutput output) throws IOException { output.writeString("", 0); } @@ -30,6 +43,6 @@ @FunctionalInterface public interface Factory { @LauncherAPI - Response newResponse(LaunchServer server, HInput input, HOutput output); + Response newResponse(LaunchServer server, int id, HInput input, HOutput output); } } diff --git a/LaunchServer/source/response/ResponseThread.java b/LaunchServer/source/response/ResponseThread.java index bfcf87f..d90f5bb 100644 --- a/LaunchServer/source/response/ResponseThread.java +++ b/LaunchServer/source/response/ResponseThread.java @@ -1,8 +1,6 @@ package launchserver.response; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.math.BigInteger; import java.net.Socket; import java.net.SocketException; @@ -32,104 +30,120 @@ // Instance private final LaunchServer server; + private final int id; private final Socket socket; - private boolean handshakePassed = false; - public ResponseThread(LaunchServer server, Socket socket) throws SocketException { + public ResponseThread(LaunchServer server, int id, Socket socket) throws SocketException { this.server = server; + this.id = id; this.socket = socket; + + // Fix socket flags IOHelper.setSocketFlags(socket); } @Override public void run() { - LogHelper.debug("Connection from %s", IOHelper.getIP(socket.getRemoteSocketAddress())); - try (InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); - HInput input = new HInput(is); HOutput output = new HOutput(os)) { - readHandshake(input, output); + boolean cancelled = false; + Exception savedError = null; + + // Process connection + LogHelper.debug("Connection #%d from %s", id, + IOHelper.getIP(socket.getRemoteSocketAddress())); + try (HInput input = new HInput(socket.getInputStream()); + HOutput output = new HOutput(socket.getOutputStream())) { + Request.Type type = readHandshake(input, output); + if (type == null) { // Not accepted + cancelled = true; + return; + } // Start response try { - respond(input, output); + respond(type, input, output); } catch (RequestException e) { - output.writeString(e.toString(), 0); + LogHelper.debug(String.format("#%d Request error: %s", id, e.getMessage())); + output.writeString(e.getMessage(), 0); } } catch (Exception e) { + savedError = e; LogHelper.error(e); } finally { IOHelper.close(socket); - - // Invoke disconnect listener - if (handshakePassed) { - server.serverSocketHandler.onDisconnected(socket); + if (!cancelled) { + server.serverSocketHandler.onDisconnect(id, savedError); } } } - private void readHandshake(HInput input, HOutput output) throws IOException { + private Request.Type readHandshake(HInput input, HOutput output) throws IOException { // Verify magic number int magicNumber = input.readInt(); if (magicNumber != Launcher.PROTOCOL_MAGIC) { output.writeBoolean(false); - throw new IOException("Protocol magic mismatch"); + throw new IOException(String.format("#%d Protocol magic mismatch", id)); } // Verify key modulus BigInteger keyModulus = input.readBigInteger(SecurityHelper.RSA_KEY_LENGTH + 1); if (!keyModulus.equals(server.getPrivateKey().getModulus())) { output.writeBoolean(false); - throw new IOException("Key modulus mismatch"); + throw new IOException(String.format("#%d Key modulus mismatch", id)); + } + + // Read request type + Request.Type type = Request.Type.read(input); + if (!server.serverSocketHandler.onHandshake(id, type)) { + output.writeBoolean(false); + output.flush(); + return null; } // Protocol successfully verified output.writeBoolean(true); output.flush(); + return type; } - private void respond(HInput input, HOutput output) throws Exception { - Request.Type type = Request.Type.read(input); + private void respond(Request.Type type, HInput input, HOutput output) throws Exception { if (LOG_CONNECTIONS) { LogHelper.info("Connection from %s: %s", IOHelper.getIP(socket.getRemoteSocketAddress()), type.name()); } else { - LogHelper.subDebug("Type: " + type.name()); + LogHelper.subDebug("#%d Type: %s", id, type.name()); } - // Invoke connect listener - server.serverSocketHandler.onConnected(socket, type); - handshakePassed = true; - // Choose response based on type Response response; switch (type) { case PING: - response = new PingResponse(server, input, output); + response = new PingResponse(server, id, input, output); break; case AUTH: - response = new AuthResponse(server, input, output); + response = new AuthResponse(server, id, input, output); break; case JOIN_SERVER: - response = new JoinServerResponse(server, input, output); + response = new JoinServerResponse(server, id, input, output); break; case CHECK_SERVER: - response = new CheckServerResponse(server, input, output); + response = new CheckServerResponse(server, id, input, output); break; case LAUNCHER: - response = new LauncherResponse(server, input, output); + response = new LauncherResponse(server, id, input, output); break; case UPDATE: - response = new UpdateResponse(server, input, output); + response = new UpdateResponse(server, id, input, output); break; case UPDATE_LIST: - response = new UpdateListResponse(server, input, output); + response = new UpdateListResponse(server, id, input, output); break; case PROFILE_BY_USERNAME: - response = new ProfileByUsernameResponse(server, input, output); + response = new ProfileByUsernameResponse(server, id, input, output); break; case PROFILE_BY_UUID: - response = new ProfileByUUIDResponse(server, input, output); + response = new ProfileByUUIDResponse(server, id, input, output); break; case BATCH_PROFILE_BY_USERNAME: - response = new BatchProfileByUsernameResponse(server, input, output); + response = new BatchProfileByUsernameResponse(server, id, input, output); break; case CUSTOM: String name = VerifyHelper.verifyIDName(input.readASCII(255)); @@ -141,6 +155,6 @@ // Reply response.reply(); - LogHelper.subDebug("Successfully replied"); + LogHelper.subDebug("#%d Replied", id); } } diff --git a/LaunchServer/source/response/ServerSocketHandler.java b/LaunchServer/source/response/ServerSocketHandler.java index ee801e4..c7b8c62 100644 --- a/LaunchServer/source/response/ServerSocketHandler.java +++ b/LaunchServer/source/response/ServerSocketHandler.java @@ -1,6 +1,7 @@ package launchserver.response; import java.io.IOException; +import java.net.InetAddress; import java.net.ServerSocket; import java.net.Socket; import java.util.Map; @@ -9,10 +10,8 @@ import java.util.concurrent.ExecutorService; import java.util.concurrent.Executors; import java.util.concurrent.ThreadFactory; +import java.util.concurrent.atomic.AtomicInteger; import java.util.concurrent.atomic.AtomicReference; -import java.util.function.BiConsumer; -import java.util.function.Consumer; -import java.util.function.Predicate; import launcher.LauncherAPI; import launcher.helper.CommonHelper; @@ -34,9 +33,8 @@ // API private final Map customResponses = new ConcurrentHashMap<>(2); - private volatile BiConsumer connectListener; - private volatile Predicate preConnectListener; - private volatile Consumer disconnectListener; + private final AtomicInteger idCounter = new AtomicInteger(0); + private volatile Listener listener; public ServerSocketHandler(LaunchServer server) { this.server = server; @@ -73,11 +71,15 @@ // Listen for incoming connections while (serverSocket.isBound()) { Socket socket = serverSocket.accept(); - if (preConnectListener != null && !preConnectListener.test(socket)) { - IOHelper.close(socket); - continue; + + // Invoke pre-connect listener + int id = idCounter.incrementAndGet(); + if (listener != null && !listener.onConnect(id, socket.getInetAddress())) { + continue; // Listener didn't accepted this connection } - threadPool.execute(new ResponseThread(server, socket)); + + // Reply in separate thread + threadPool.execute(new ResponseThread(server, id, socket)); } } catch (IOException e) { // Ignore error after close/rebind @@ -102,29 +104,28 @@ } @LauncherAPI - public void setConnectListener(BiConsumer connectListener) { - this.connectListener = connectListener; + public void setListener(Listener listener) { + this.listener = listener; } - @LauncherAPI - public void setDisconnectListener(Consumer disconnectListener) { - this.disconnectListener = disconnectListener; - } - - @LauncherAPI - public void setPreConnectListener(Predicate preConnectListener) { - this.preConnectListener = preConnectListener; - } - - /*package*/ void onConnected(Socket socket, Request.Type type) { - if (connectListener != null) { - connectListener.accept(socket, type); + /*package*/ void onDisconnect(int id, Exception e) { + if (listener != null) { + listener.onDisconnect(id, e); } } - /*package*/ void onDisconnected(Socket socket) { - if (disconnectListener != null) { - disconnectListener.accept(socket); - } + /*package*/ boolean onHandshake(int id, Request.Type type) { + return listener == null || listener.onHandshake(id, type); + } + + public interface Listener { + @LauncherAPI + boolean onConnect(int id, InetAddress address); + + @LauncherAPI + void onDisconnect(int id, Exception e); + + @LauncherAPI + boolean onHandshake(int id, Request.Type type); } } diff --git a/LaunchServer/source/response/auth/AuthResponse.java b/LaunchServer/source/response/auth/AuthResponse.java index b6741e1..bc2ef38 100644 --- a/LaunchServer/source/response/auth/AuthResponse.java +++ b/LaunchServer/source/response/auth/AuthResponse.java @@ -18,8 +18,8 @@ import launchserver.response.profile.ProfileByUUIDResponse; public final class AuthResponse extends Response { - public AuthResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public AuthResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override @@ -36,8 +36,8 @@ } // Authenticate + debug("Login: '%s', Password: '%s'", id, login, echo(password.length())); LaunchServer.Config config = server.getConfig(); - LogHelper.subDebug("Login: '%s', password: '%s'", login, echo(password.length())); String username; try { username = VerifyHelper.verifyUsername(config.authProvider.auth(login, password)); @@ -47,7 +47,7 @@ LogHelper.error(e); throw new RequestException("Internal auth error", e); } - LogHelper.subDebug("Auth: '%s' -> '%s'", login, username); + debug("Auth: '%s' -> '%s'", id, login, username); // Authenticate on server (and get UUID) String accessToken = SecurityHelper.randomStringToken(); diff --git a/LaunchServer/source/response/auth/CheckServerResponse.java b/LaunchServer/source/response/auth/CheckServerResponse.java index 9026ea2..6ee5754 100644 --- a/LaunchServer/source/response/auth/CheckServerResponse.java +++ b/LaunchServer/source/response/auth/CheckServerResponse.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.util.UUID; -import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launcher.request.auth.JoinServerRequest; import launcher.serialize.HInput; @@ -13,17 +12,15 @@ import launchserver.response.profile.ProfileByUUIDResponse; public final class CheckServerResponse extends Response { - public CheckServerResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public CheckServerResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override public void reply() throws IOException { String username = VerifyHelper.verifyUsername(input.readASCII(16)); String serverID = JoinServerRequest.verifyServerID(input.readASCII(41)); // With minus sign - - // Debug print message - LogHelper.subDebug("Username: '%s', server ID: %s", username, serverID); + debug("Username: %s, Server ID: %s", id, username, serverID); // Check server UUID uuid = server.getConfig().authHandler.checkServer(username, serverID); diff --git a/LaunchServer/source/response/auth/JoinServerResponse.java b/LaunchServer/source/response/auth/JoinServerResponse.java index 89fbfbd..16c8073 100644 --- a/LaunchServer/source/response/auth/JoinServerResponse.java +++ b/LaunchServer/source/response/auth/JoinServerResponse.java @@ -2,7 +2,6 @@ import java.io.IOException; -import launcher.helper.LogHelper; import launcher.helper.SecurityHelper; import launcher.helper.VerifyHelper; import launcher.request.auth.JoinServerRequest; @@ -12,8 +11,8 @@ import launchserver.response.Response; public final class JoinServerResponse extends Response { - public JoinServerResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public JoinServerResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override @@ -22,10 +21,8 @@ String accessToken = SecurityHelper.verifyToken(input.readASCII(-SecurityHelper.TOKEN_STRING_LENGTH)); String serverID = JoinServerRequest.verifyServerID(input.readASCII(41)); // With minus sign - // Debug print message - LogHelper.subDebug("Username: '%s', access token: %s, server ID: %s", username, accessToken, serverID); - // Try join server with auth manager + debug("Username: '%s', Access token: %s, Server ID: %s", username, accessToken, serverID); output.writeBoolean(server.getConfig().authHandler.joinServer(username, accessToken, serverID)); } } diff --git a/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java b/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java index de8a1c5..3bfab02 100644 --- a/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java +++ b/LaunchServer/source/response/profile/BatchProfileByUsernameResponse.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.util.Arrays; -import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launcher.request.uuid.BatchProfileByUsernameRequest; import launcher.serialize.HInput; @@ -12,8 +11,8 @@ import launchserver.response.Response; public final class BatchProfileByUsernameResponse extends Response { - public BatchProfileByUsernameResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public BatchProfileByUsernameResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override @@ -22,7 +21,7 @@ for (int i = 0; i < usernames.length; i++) { usernames[i] = VerifyHelper.verifyUsername(input.readASCII(16)); } - LogHelper.subDebug("Usernames: " + Arrays.toString(usernames)); + debug("Usernames: " + Arrays.toString(usernames)); // Respond with profiles array for (String username : usernames) { diff --git a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java index 513e7ad..0071666 100644 --- a/LaunchServer/source/response/profile/ProfileByUUIDResponse.java +++ b/LaunchServer/source/response/profile/ProfileByUUIDResponse.java @@ -4,21 +4,20 @@ import java.util.UUID; import launcher.client.PlayerProfile; -import launcher.helper.LogHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; import launchserver.LaunchServer; import launchserver.response.Response; public final class ProfileByUUIDResponse extends Response { - public ProfileByUUIDResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public ProfileByUUIDResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override public void reply() throws IOException { UUID uuid = input.readUUID(); - LogHelper.subDebug("UUID: " + uuid); + debug("UUID: " + uuid); // Verify has such profile String username = server.getConfig().authHandler.uuidToUsername(uuid); diff --git a/LaunchServer/source/response/profile/ProfileByUsernameResponse.java b/LaunchServer/source/response/profile/ProfileByUsernameResponse.java index 69a3e4e..fa7947b 100644 --- a/LaunchServer/source/response/profile/ProfileByUsernameResponse.java +++ b/LaunchServer/source/response/profile/ProfileByUsernameResponse.java @@ -3,7 +3,6 @@ import java.io.IOException; import java.util.UUID; -import launcher.helper.LogHelper; import launcher.helper.VerifyHelper; import launcher.serialize.HInput; import launcher.serialize.HOutput; @@ -11,14 +10,16 @@ import launchserver.response.Response; public final class ProfileByUsernameResponse extends Response { - public ProfileByUsernameResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public ProfileByUsernameResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override public void reply() throws IOException { String username = VerifyHelper.verifyUsername(input.readASCII(16)); - LogHelper.subDebug("Username: " + username); + debug("Username: " + username); + + // Write response writeProfile(server, output, username); } diff --git a/LaunchServer/source/response/update/LauncherResponse.java b/LaunchServer/source/response/update/LauncherResponse.java index a2d1ea0..546b32f 100644 --- a/LaunchServer/source/response/update/LauncherResponse.java +++ b/LaunchServer/source/response/update/LauncherResponse.java @@ -14,8 +14,8 @@ import launchserver.response.Response; public final class LauncherResponse extends Response { - public LauncherResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public LauncherResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override diff --git a/LaunchServer/source/response/update/UpdateListResponse.java b/LaunchServer/source/response/update/UpdateListResponse.java index 28e252e..cd0b794 100644 --- a/LaunchServer/source/response/update/UpdateListResponse.java +++ b/LaunchServer/source/response/update/UpdateListResponse.java @@ -11,8 +11,8 @@ import launchserver.response.Response; public final class UpdateListResponse extends Response { - public UpdateListResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public UpdateListResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override diff --git a/LaunchServer/source/response/update/UpdateResponse.java b/LaunchServer/source/response/update/UpdateResponse.java index 06dbefe..502fb9f 100644 --- a/LaunchServer/source/response/update/UpdateResponse.java +++ b/LaunchServer/source/response/update/UpdateResponse.java @@ -9,7 +9,6 @@ import launcher.hasher.HashedDir; import launcher.hasher.HashedEntry; import launcher.helper.IOHelper; -import launcher.helper.LogHelper; import launcher.request.RequestException; import launcher.request.update.UpdateRequest; import launcher.serialize.HInput; @@ -19,8 +18,8 @@ import launchserver.response.Response; public final class UpdateResponse extends Response { - public UpdateResponse(LaunchServer server, HInput input, HOutput output) { - super(server, input, output); + public UpdateResponse(LaunchServer server, int id, HInput input, HOutput output) { + super(server, id, input, output); } @Override @@ -34,7 +33,7 @@ writeNoError(output); // Write update hdir - LogHelper.subDebug("Update dir: '%s'", updateDirName); + debug("Update dir: '%s'", updateDirName); hdir.write(output); output.flush(); @@ -58,7 +57,7 @@ UpdateRequest.Action action = actionsSlice[i]; switch (action.type) { case CD: - LogHelper.subDebug("CD '%s'", action.name); + debug("CD '%s'", action.name); // Get hashed dir (for validation) HashedEntry hSubdir = dirStack.getLast().getEntry(action.name); @@ -71,7 +70,7 @@ dir = dir.resolve(action.name); break; case GET: - LogHelper.subDebug("GET '%s'", action.name); + debug("GET '%s'", action.name); // Get hashed file (for validation) HashedEntry hFile = dirStack.getLast().getEntry(action.name); @@ -86,7 +85,7 @@ } break; case CD_BACK: - LogHelper.subDebug("CD .."); + debug("CD .."); // Remove from hashed dir stack dirStack.removeLast(); diff --git a/Launcher/source/request/Request.java b/Launcher/source/request/Request.java index 66a723d..fe64518 100644 --- a/Launcher/source/request/Request.java +++ b/Launcher/source/request/Request.java @@ -1,8 +1,6 @@ package launcher.request; import java.io.IOException; -import java.io.InputStream; -import java.io.OutputStream; import java.net.Socket; import java.util.concurrent.atomic.AtomicBoolean; @@ -41,8 +39,8 @@ // Make request to LaunchServer try (Socket socket = IOHelper.newSocket()) { socket.connect(IOHelper.resolve(config.address)); - try (InputStream is = socket.getInputStream(); OutputStream os = socket.getOutputStream(); - HInput input = new HInput(is); HOutput output = new HOutput(os)) { + try (HInput input = new HInput(socket.getInputStream()); + HOutput output = new HOutput(socket.getOutputStream())) { writeHandshake(input, output); // Start request diff --git a/Launcher/source/request/auth/AuthRequest.java b/Launcher/source/request/auth/AuthRequest.java index 02b3ea2..70362fe 100644 --- a/Launcher/source/request/auth/AuthRequest.java +++ b/Launcher/source/request/auth/AuthRequest.java @@ -40,7 +40,7 @@ output.writeByteArray(encryptedPassword, IOHelper.BUFFER_SIZE); output.flush(); - // Read UUID and accessToken + // Read UUID and access token readError(input); PlayerProfile pp = new PlayerProfile(input); String accessToken = input.readASCII(-SecurityHelper.TOKEN_STRING_LENGTH);