diff --git a/.github/workflows/codeql-analysis.yml b/.github/workflows/codeql-analysis.yml new file mode 100644 index 00000000..d25b94bc --- /dev/null +++ b/.github/workflows/codeql-analysis.yml @@ -0,0 +1,70 @@ +# For most projects, this workflow file will not need changing; you simply need +# to commit it to your repository. +# +# You may wish to alter this file to override the set of languages analyzed, +# or to provide custom queries or build logic. +# +# ******** NOTE ******** +# We have attempted to detect the languages in your repository. Please check +# the `language` matrix defined below to confirm you have the correct set of +# supported CodeQL languages. +# +name: "CodeQL" + +on: + push: + branches: [ master, dev ] + pull_request: + # The branches below must be a subset of the branches above + branches: [ master, dev ] + schedule: + - cron: '28 4 * * 0' + +jobs: + analyze: + name: Analyze + runs-on: ubuntu-latest + + strategy: + fail-fast: false + matrix: + language: [ 'java' ] + # CodeQL supports [ 'cpp', 'csharp', 'go', 'java', 'javascript', 'python' ] + # Learn more: + # https://docs.github.com/en/free-pro-team@latest/github/finding-security-vulnerabilities-and-errors-in-your-code/configuring-code-scanning#changing-the-languages-that-are-analyzed + + steps: + - name: Checkout repository + uses: actions/checkout@v2 + + # Initializes the CodeQL tools for scanning. + - name: Initialize CodeQL + uses: github/codeql-action/init@v1 + with: + languages: ${{ matrix.language }} + # If you wish to specify custom queries, you can do so here or in a config file. + # By default, queries listed here will override any specified in a config file. + # Prefix the list here with "+" to use these queries and those in the config file. + # queries: ./path/to/local/query, your-org/your-repo/queries@main + - name: Set up JDK 11 + uses: actions/setup-java@v1 + with: + java-version: 11 + # Autobuild attempts to build any compiled languages (C/C++, C#, or Java). + # If this step fails, then you should remove it and run the build manually (see below) + - name: Autobuild + uses: github/codeql-action/autobuild@v1 + + # ℹ️ Command-line programs to run using the OS shell. + # 📚 https://git.io/JvXDl + + # ✏️ If the Autobuild fails above, remove it and uncomment the following three lines + # and modify them (or add more) to build your code if your project + # uses a compiled language + + #- run: | + # make bootstrap + # make release + + - name: Perform CodeQL Analysis + uses: github/codeql-action/analyze@v1 diff --git a/Dockerfile b/Dockerfile new file mode 100644 index 00000000..b2095924 --- /dev/null +++ b/Dockerfile @@ -0,0 +1,17 @@ +FROM archlinux/base +RUN pacman -Sy --noconfirm jdk11-openjdk unzip && pacman -Scc --noconfirm +ADD https://download2.gluonhq.com/openjfx/11.0.2/openjfx-11.0.2_linux-x64_bin-jmods.zip . +RUN unzip openjfx-11.0.2_linux-x64_bin-jmods.zip && mv javafx-jmods-11.0.2/* /usr/lib/jvm/java-11-openjdk/jmods/ && rmdir javafx-jmods-11.0.2 && rm openjfx-11.0.2_linux-x64_bin-jmods.zip +RUN mkdir ./libraries ./launcher-libraries ./launcher-libraries-compile +COPY ./LaunchServer/build/libs/LaunchServer.jar . +COPY ./LaunchServer/build/libs/libraries ./libraries +COPY ./LaunchServer/build/libs/launcher-libraries ./launcher-libraries +COPY ./LaunchServer/build/libs/launcher-libraries-compile ./launcher-libraries-compile +RUN mkdir ./compat/ +COPY ./compat/authlib/authlib-clean.jar ./compat +COPY ./LauncherAuthlib/build/libs/* ./compat/ +COPY ./ServerWrapper/build/libs/ServerWrapper.jar ./compat/ +RUN mkdir ./compat/modules +COPY ./modules/*_module/build/libs/* ./compat/modules/ +COPY ./modules/*_lmodule/build/libs/* ./compat/modules/ +CMD java -javaagent:LaunchServer.jar -jar LaunchServer.jar diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java index eaa40fb1..b74d8de7 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServer.java @@ -10,6 +10,7 @@ import pro.gravit.launcher.modules.events.ClosePhase; import pro.gravit.launcher.profiles.ClientProfile; import pro.gravit.launchserver.auth.AuthProviderPair; +import pro.gravit.launchserver.auth.session.MemorySessionStorage; import pro.gravit.launchserver.binary.*; import pro.gravit.launchserver.config.LaunchServerConfig; import pro.gravit.launchserver.config.LaunchServerRuntimeConfig; @@ -80,6 +81,7 @@ public final class LaunchServer implements Runnable, AutoCloseable, Reconfigurab public final ReconfigurableManager reconfigurableManager; public final ConfigManager configManager; public final PingServerManager pingServerManager; + public final FeaturesManager featuresManager; // HWID ban + anti-brutforce public final CertificateManager certificateManager; public final ProguardConf proguardConf; @@ -128,6 +130,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La runtime.verify(); config.verify(); + if(config.sessions == null) config.sessions = new MemorySessionStorage(); if (config.components != null) { LogHelper.debug("PreInit components"); config.components.forEach((k, v) -> { @@ -139,15 +142,17 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La // build hooks, anti-brutforce and other proguardConf = new ProguardConf(this); - sessionManager = new SessionManager(); + sessionManager = new SessionManager(this); mirrorManager = new MirrorManager(); reconfigurableManager = new ReconfigurableManager(); authHookManager = new AuthHookManager(); configManager = new ConfigManager(); pingServerManager = new PingServerManager(this); + featuresManager = new FeaturesManager(this); //Generate or set new Certificate API certificateManager.orgName = config.projectName; - if (config.certificate != null && config.certificate.enabled) { + /* + if (false) { if (IOHelper.isFile(caCertFile) && IOHelper.isFile(caKeyFile)) { certificateManager.ca = certificateManager.readCertificate(caCertFile); certificateManager.caKey = certificateManager.readPrivateKey(caKeyFile); @@ -175,6 +180,7 @@ public LaunchServer(LaunchServerDirectories directories, LaunchServerEnv env, La } } } + */ config.init(ReloadType.FULL); registerObject("launchServer", this); GarbageManager.registerNeedGC(sessionManager); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java index 7e012dfe..8c49e94b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/LaunchServerStarter.java @@ -10,6 +10,7 @@ import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider; import pro.gravit.launchserver.auth.provider.AuthProvider; +import pro.gravit.launchserver.auth.session.SessionStorage; import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.components.Component; import pro.gravit.launchserver.config.LaunchServerConfig; @@ -215,6 +216,7 @@ public static void registerAll() { AuthRequest.registerProviders(); HWIDProvider.registerProviders(); OptionalAction.registerProviders(); + SessionStorage.registerProviders(); } public static void generateConfigIfNotExists(Path configFile, CommandHandler commandHandler, LaunchServer.LaunchServerEnv env) throws IOException { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/RequiredDAO.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/RequiredDAO.java new file mode 100644 index 00000000..b543e653 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/RequiredDAO.java @@ -0,0 +1,4 @@ +package pro.gravit.launchserver.auth; + +public interface RequiredDAO { +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/handler/HibernateAuthHandler.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/handler/HibernateAuthHandler.java index 62cf5fd6..0491e7c6 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/handler/HibernateAuthHandler.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/handler/HibernateAuthHandler.java @@ -1,10 +1,11 @@ package pro.gravit.launchserver.auth.handler; +import pro.gravit.launchserver.auth.RequiredDAO; import pro.gravit.launchserver.dao.User; import java.util.UUID; -public class HibernateAuthHandler extends CachedAuthHandler { +public class HibernateAuthHandler extends CachedAuthHandler implements RequiredDAO { @Override protected Entry fetchEntry(String username) { User user = srv.config.dao.userDAO.findByUsername(username); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/hwid/MemoryHWIDProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/hwid/MemoryHWIDProvider.java index a4d679c7..5cc24c5b 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/hwid/MemoryHWIDProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/protect/hwid/MemoryHWIDProvider.java @@ -84,11 +84,11 @@ public boolean addPublicKeyToHardwareInfo(HardwareReportRequest.HardwareInfo har for (MemoryHWIDEntity e : db) { HardwareInfoCompareResult result = compareHardwareInfo(e.hardware, hardwareInfo); if (warningSpoofingLevel > 0 && result.firstSpoofingLevel > warningSpoofingLevel && !isAlreadyWarning) { - LogHelper.warning("HardwareInfo spoofing level too high: %d", result.firstSpoofingLevel); + LogHelper.warning("HardwareInfo spoofing level too high: %f", result.firstSpoofingLevel); isAlreadyWarning = true; } if (result.compareLevel > criticalCompareLevel) { - LogHelper.debug("HardwareInfo publicKey change: compareLevel %d", result.compareLevel); + LogHelper.debug("HardwareInfo publicKey change: compareLevel %f", result.compareLevel); if (e.banned) throw new HWIDException("You HWID banned"); e.publicKey = publicKey; return true; diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/provider/HibernateAuthProvider.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/provider/HibernateAuthProvider.java index 05cd046c..7e7b3c65 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/provider/HibernateAuthProvider.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/provider/HibernateAuthProvider.java @@ -3,11 +3,12 @@ import pro.gravit.launcher.request.auth.AuthRequest; import pro.gravit.launcher.request.auth.password.AuthPlainPassword; import pro.gravit.launchserver.auth.AuthException; +import pro.gravit.launchserver.auth.RequiredDAO; import pro.gravit.launchserver.dao.User; import pro.gravit.launchserver.manangers.hook.AuthHookManager; import pro.gravit.utils.helper.SecurityHelper; -public class HibernateAuthProvider extends AuthProvider { +public class HibernateAuthProvider extends AuthProvider implements RequiredDAO { public boolean autoReg; @Override diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/MemorySessionStorage.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/MemorySessionStorage.java new file mode 100644 index 00000000..7676bdab --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/MemorySessionStorage.java @@ -0,0 +1,128 @@ +package pro.gravit.launchserver.auth.session; + +import pro.gravit.launcher.NeedGarbageCollection; +import pro.gravit.launchserver.manangers.SessionManager; + +import java.util.*; +import java.util.concurrent.ConcurrentHashMap; +import java.util.stream.Stream; + +public class MemorySessionStorage extends SessionStorage implements NeedGarbageCollection { + + private final Map clientSet = new ConcurrentHashMap<>(128); + private final Map> uuidIndex = new ConcurrentHashMap<>(32); + + @Override + public byte[] getSessionData(UUID session) { + + Entry e = clientSet.get(session); + if(e == null) return null; + return e.data; + } + + @Override + public Stream getSessionsFromUserUUID(UUID userUUID) { + Set set = uuidIndex.get(userUUID); + if(set != null) return set.stream().map((e) -> e.sessionUuid); + return null; + } + + @Override + public boolean writeSession(UUID userUUID, UUID sessionUUID, byte[] data) { + deleteSession(sessionUUID); + Entry e = new Entry(data, sessionUUID); + clientSet.put(sessionUUID, e); + if(userUUID != null) { + Set uuidSet = uuidIndex.computeIfAbsent(userUUID, k -> ConcurrentHashMap.newKeySet()); + uuidSet.add(e); + } + return false; + } + + @Override + public boolean deleteSession(UUID sessionUUID) { + Entry e =clientSet.remove(sessionUUID); + if(e != null) { + Set set = uuidIndex.get(sessionUUID); + if(set != null) { + removeUuidFromIndexSet(set, e, sessionUUID); + } + return true; + } + return false; + } + + @Override + public boolean deleteSessionsByUserUUID(UUID userUUID) { + Set set = uuidIndex.get(userUUID); + if(set != null) { + for(Entry e : set) { + clientSet.remove(e.sessionUuid); + } + set.clear(); + uuidIndex.remove(userUUID); + } + return true; + } + + @Override + public void clear() { + clientSet.clear(); + uuidIndex.clear(); + } + + @Override + public void lockSession(UUID sessionUUID) { + + } + + @Override + public void lockUser(UUID userUUID) { + + } + + @Override + public void unlockSession(UUID sessionUUID) { + + } + + @Override + public void unlockUser(UUID userUUID) { + + } + + private void removeUuidFromIndexSet(Set set, Entry e, UUID session) { + set.remove(e); + if(set.isEmpty()) { + uuidIndex.remove(session); + } + } + + @Override + public void garbageCollection() { + long time = System.currentTimeMillis(); + long session_timeout = server.config.netty.performance.sessionLifetimeMs; + Set to_delete = new HashSet<>(32); + clientSet.forEach((uuid, entry) -> { + long timestamp = entry.timestamp; + if(timestamp + session_timeout < time) + to_delete.add(uuid); + }); + for(UUID session : to_delete) { + deleteSession(session); + } + to_delete.clear(); + } + + private static class Entry { + public byte[] data; + public UUID sessionUuid; + public long timestamp; + + public Entry(byte[] data, UUID sessionUuid) { + this.data = data; + this.sessionUuid = sessionUuid; + this.timestamp = System.currentTimeMillis(); + } + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/SessionStorage.java b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/SessionStorage.java new file mode 100644 index 00000000..a1ac65e2 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/auth/session/SessionStorage.java @@ -0,0 +1,37 @@ +package pro.gravit.launchserver.auth.session; + +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.utils.ProviderMap; + +import java.util.List; +import java.util.UUID; +import java.util.stream.Stream; + +public abstract class SessionStorage { + protected transient LaunchServer server; + public static ProviderMap providers = new ProviderMap<>(); + private static boolean registeredProviders = false; + public abstract byte[] getSessionData(UUID session); + public abstract Stream getSessionsFromUserUUID(UUID userUUID); + public abstract boolean writeSession(UUID userUUID, UUID sessionUUID, byte[] data); + public abstract boolean deleteSession(UUID sessionUUID); + public boolean deleteSessionsByUserUUID(UUID userUUID) { + getSessionsFromUserUUID(userUUID).forEach(this::deleteSession); + return true; + } + public abstract void clear(); + public abstract void lockSession(UUID sessionUUID); + public abstract void lockUser(UUID userUUID); + public abstract void unlockSession(UUID sessionUUID); + public abstract void unlockUser(UUID userUUID); + public void init(LaunchServer server) + { + this.server = server; + } + public static void registerProviders() { + if(!registeredProviders) { + providers.register("memory", MemorySessionStorage.class); + registeredProviders = true; + } + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/dump/DumpSessionsCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/dump/DumpSessionsCommand.java deleted file mode 100644 index 4eff81ff..00000000 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/dump/DumpSessionsCommand.java +++ /dev/null @@ -1,67 +0,0 @@ -package pro.gravit.launchserver.command.dump; - -import com.google.gson.reflect.TypeToken; -import pro.gravit.launcher.Launcher; -import pro.gravit.launchserver.LaunchServer; -import pro.gravit.launchserver.command.Command; -import pro.gravit.launchserver.socket.Client; -import pro.gravit.utils.command.SubCommand; -import pro.gravit.utils.helper.IOHelper; -import pro.gravit.utils.helper.LogHelper; - -import java.io.Reader; -import java.io.Writer; -import java.lang.reflect.Type; -import java.nio.file.Paths; -import java.util.Collection; -import java.util.HashSet; -import java.util.Set; - -public class DumpSessionsCommand extends Command { - public DumpSessionsCommand(LaunchServer server) { - super(server); - childCommands.put("load", new SubCommand() { - @Override - public void invoke(String... args) throws Exception { - verifyArgs(args, 1); - LogHelper.info("Sessions read from %s", args[0]); - int size; - try (Reader reader = IOHelper.newReader(Paths.get(args[0]))) { - Type setType = new TypeToken>() { - }.getType(); - Set clientSet = Launcher.gsonManager.configGson.fromJson(reader, setType); - size = clientSet.size(); - server.sessionManager.loadSessions(clientSet); - } - LogHelper.subInfo("Readed %d sessions", size); - } - }); - childCommands.put("unload", new SubCommand() { - @Override - public void invoke(String... args) throws Exception { - verifyArgs(args, 1); - LogHelper.info("Sessions write to %s", args[0]); - Collection clientSet = server.sessionManager.getSessions(); - try (Writer writer = IOHelper.newWriter(Paths.get(args[0]))) { - Launcher.gsonManager.configGson.toJson(clientSet, writer); - } - LogHelper.subInfo("Write %d sessions", clientSet.size()); - } - }); - } - - @Override - public String getArgsDescription() { - return "[load/unload] [filename]"; - } - - @Override - public String getUsageDescription() { - return "Load or unload sessions"; - } - - @Override - public void invoke(String... args) throws Exception { - invokeSubcommands(args); - } -} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java index 78b75119..d001f466 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/handler/CommandHandler.java @@ -5,7 +5,6 @@ import pro.gravit.launchserver.command.auth.UUIDToUsernameCommand; import pro.gravit.launchserver.command.auth.UsernameToUUIDCommand; import pro.gravit.launchserver.command.basic.*; -import pro.gravit.launchserver.command.dump.DumpSessionsCommand; import pro.gravit.launchserver.command.hash.*; import pro.gravit.launchserver.command.install.CheckInstallCommand; import pro.gravit.launchserver.command.install.MultiCommand; @@ -61,12 +60,6 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand Category authCategory = new Category(auth, "auth", "User Management"); handler.registerCategory(authCategory); - //Register dump commands - BaseCommandCategory dump = new BaseCommandCategory(); - dump.registerCommand("dumpSessions", new DumpSessionsCommand(server)); - Category dumpCategory = new Category(dump, "dump", "Dump runtime data"); - handler.registerCategory(dumpCategory); - //Register service commands BaseCommandCategory service = new BaseCommandCategory(); service.registerCommand("config", new ConfigCommand(server)); @@ -78,6 +71,7 @@ public static void registerCommands(pro.gravit.utils.command.CommandHandler hand service.registerCommand("clients", new ClientsCommand(server)); service.registerCommand("signJar", new SignJarCommand(server)); service.registerCommand("signDir", new SignDirCommand(server)); + service.registerCommand("pingServers", new PingServersCommand(server)); service.registerCommand("securitycheck", new SecurityCheckCommand(server)); Category serviceCategory = new Category(service, "service", "Managing LaunchServer Components"); handler.registerCategory(serviceCategory); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ClientsCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ClientsCommand.java index 63e68d1b..d3e004e6 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ClientsCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ClientsCommand.java @@ -8,6 +8,8 @@ import pro.gravit.utils.helper.IOHelper; import pro.gravit.utils.helper.LogHelper; +import java.util.Base64; + public class ClientsCommand extends Command { public ClientsCommand(LaunchServer server) { super(server); @@ -34,8 +36,12 @@ public void invoke(String... args) { LogHelper.info("Channel %s | connectUUID %s | checkSign %s", ip, frameHandler.getConnectUUID(), client.checkSign ? "true" : "false"); else { LogHelper.info("Client name %s | ip %s | connectUUID %s", client.username == null ? "null" : client.username, ip, frameHandler.getConnectUUID()); + LogHelper.subInfo("userUUID: %s | session %s", client.uuid == null ? "null" : client.uuid.toString(), client.session == null ? "null" : client.session); LogHelper.subInfo("Data: checkSign %s | auth_id %s", client.checkSign ? "true" : "false", client.auth_id); + if(client.trustLevel != null) { + LogHelper.subInfo("trustLevel | key %s | pubkey %s", client.trustLevel.keyChecked ? "checked" : "unchecked", client.trustLevel.publicKey == null ? "null" : Base64.getEncoder().encode(client.trustLevel.publicKey)); + } LogHelper.subInfo("Permissions: %s (permissions %d | flags %d)", client.permissions == null ? "null" : client.permissions.toString(), client.permissions == null ? 0 : client.permissions.permissions, client.permissions == null ? 0 : client.permissions.flags); } })); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/PingServersCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/PingServersCommand.java new file mode 100644 index 00000000..1181391b --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/PingServersCommand.java @@ -0,0 +1,36 @@ +package pro.gravit.launchserver.command.service; + +import pro.gravit.launcher.request.management.PingServerReportRequest; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.command.Command; +import pro.gravit.utils.helper.LogHelper; + +public class PingServersCommand extends Command { + public PingServersCommand(LaunchServer server) { + super(server); + } + + @Override + public String getArgsDescription() { + return "[]"; + } + + @Override + public String getUsageDescription() { + return "show modern pings status"; + } + + @Override + public void invoke(String... args) throws Exception { + server.pingServerManager.map.forEach((name, data) -> { + LogHelper.info("[%s] online %d / %d", name, data.lastReport == null ? -1 : data.lastReport.playersOnline, data.lastReport == null ? -1 : data.lastReport.maxPlayers); + if(data.lastReport != null && data.lastReport.users != null) + { + for(PingServerReportRequest.PingServerReport.UsernameInfo user : data.lastReport.users) + { + LogHelper.subInfo("User %s", user.username == null ? "null" : user.username); + } + } + }); + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java index fea875b8..30b83662 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/SecurityCheckCommand.java @@ -158,8 +158,10 @@ public void invoke(String... args) throws Exception { if (tokenizer.hasMoreTokens() && tokenizer.nextToken().equals("mods")) { String nextToken = tokenizer.nextToken(); if (!tokenizer.hasMoreTokens()) { - printCheckResult(LogHelper.Level.INFO, profileModuleName, String.format("updateExclusions %s not safe. Cheats may be injected very easy!", exc), false); - bad = true; + if(!exc.endsWith("/")) { + printCheckResult(LogHelper.Level.INFO, profileModuleName, String.format("updateExclusions %s not safe. Cheats may be injected very easy!", exc), false); + bad = true; + } } else { if (nextToken.equals("memory_repo") || nextToken.equals(profile.getVersion().name)) { printCheckResult(LogHelper.Level.INFO, profileModuleName, String.format("updateExclusions %s not safe. Cheats may be injected very easy!", exc), false); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ServerStatusCommand.java b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ServerStatusCommand.java index f7c1b9cd..bc0aef04 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ServerStatusCommand.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/command/service/ServerStatusCommand.java @@ -38,7 +38,7 @@ public void invoke(String... args) { for (CommandHandler.Category category : server.commandHandler.getCategories()) { commands += category.category.commandsMap().size(); } - LogHelper.info("Sessions: %d | Commands: %d(%d categories)", server.sessionManager.getSessions().size(), commands, server.commandHandler.getCategories().size() + 1); + LogHelper.info("Commands: %d(%d categories)", commands, server.commandHandler.getCategories().size() + 1); for (AuthProviderPair pair : server.config.auth.values()) { if (pair.handler instanceof CachedAuthHandler) { LogHelper.info("AuthHandler %s: EntryCache: %d | usernameCache: %d", pair.name, ((CachedAuthHandler) pair.handler).getEntryCache().size(), ((CachedAuthHandler) pair.handler).getUsernamesCache().size()); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java b/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java index bc450975..170cc4c4 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/config/LaunchServerConfig.java @@ -10,6 +10,8 @@ import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.StdProtectHandler; import pro.gravit.launchserver.auth.provider.RejectAuthProvider; +import pro.gravit.launchserver.auth.session.MemorySessionStorage; +import pro.gravit.launchserver.auth.session.SessionStorage; import pro.gravit.launchserver.auth.texture.RequestTextureProvider; import pro.gravit.launchserver.binary.tasks.exe.Launch4JTask; import pro.gravit.launchserver.components.AuthLimiterComponent; @@ -33,6 +35,7 @@ public final class LaunchServerConfig { public LauncherConfig.LauncherEnvironment env; public Map auth; public DaoProvider dao; + public SessionStorage sessions; // Handlers & Providers public ProtectHandler protectHandler; @@ -40,7 +43,6 @@ public final class LaunchServerConfig { public ExeConf launch4j; public NettyConfig netty; public LauncherConf launcher; - public CertificateConf certificate; public JarSignerConf sign; public String startScript; private transient LaunchServer server = null; @@ -71,6 +73,7 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) { a.displayName = "Default"; newConfig.auth.put("std", a); newConfig.protectHandler = new StdProtectHandler(); + newConfig.sessions = new MemorySessionStorage(); newConfig.binaryName = "Launcher"; newConfig.netty = new NettyConfig(); @@ -97,8 +100,6 @@ public static LaunchServerConfig getDefault(LaunchServer.LaunchServerEnv env) { newConfig.launcher.stripLineNumbers = true; newConfig.launcher.proguardGenMappings = true; - newConfig.certificate = new LaunchServerConfig.CertificateConf(); - newConfig.certificate.enabled = false; newConfig.sign = new JarSignerConf(); newConfig.components = new HashMap<>(); @@ -187,6 +188,10 @@ public void init(LaunchServer.ReloadType type) { protectHandler.init(server); protectHandler.checkLaunchServerLicense(); } + if(sessions != null) { + sessions.init(server); + server.registerObject("sessions", sessions); + } if (components != null) { components.forEach((k, v) -> server.registerObject("component.".concat(k), v)); } @@ -229,6 +234,9 @@ public void close(LaunchServer.ReloadType type) { server.unregisterObject("protectHandler", protectHandler); protectHandler.close(); } + if(sessions != null) { + server.unregisterObject("sessions", sessions); + } if (dao != null) { server.unregisterObject("dao", dao); if (dao instanceof AutoCloseable) { @@ -260,10 +268,6 @@ public static class ExeConf { public String txtProductVersion; } - public static class CertificateConf { - public boolean enabled; - } - public static class JarSignerConf { public boolean enabled = false; public String keyStore = "pathToKey"; @@ -312,6 +316,8 @@ public static class NettyPerformanceConfig { public boolean usingEpoll; public int bossThread; public int workerThread; + public long sessionLifetimeMs = 24 * 60 * 60 * 1000; + public int maxWebSocketRequestBytes = 1024 * 1024; } public static class NettyBindAddress { diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/FeaturesManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/FeaturesManager.java new file mode 100644 index 00000000..da1d15c8 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/FeaturesManager.java @@ -0,0 +1,35 @@ +package pro.gravit.launchserver.manangers; + +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.utils.Version; + +import java.util.HashMap; +import java.util.Map; + +public class FeaturesManager { + private transient LaunchServer server; + private Map map; + + public FeaturesManager(LaunchServer server) { + this.server = server; + map = new HashMap<>(); + addFeatureInfo("version", Version.getVersion().getVersionString()); + + } + + public Map getMap() { + return map; + } + + public String getFeatureInfo(String name) { + return map.get(name); + } + + public String addFeatureInfo(String name, String featureInfo) { + return map.put(name, featureInfo); + } + + public String removeFeatureInfo(String name) { + return map.remove(name); + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java index b4d3d9c8..1cdf41e1 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/LaunchServerGsonManager.java @@ -11,6 +11,7 @@ import pro.gravit.launchserver.auth.protect.ProtectHandler; import pro.gravit.launchserver.auth.protect.hwid.HWIDProvider; import pro.gravit.launchserver.auth.provider.AuthProvider; +import pro.gravit.launchserver.auth.session.SessionStorage; import pro.gravit.launchserver.auth.texture.TextureProvider; import pro.gravit.launchserver.components.Component; import pro.gravit.launchserver.dao.provider.DaoProvider; @@ -41,6 +42,7 @@ public void registerAdapters(GsonBuilder builder) { builder.registerTypeAdapter(AuthRequest.AuthPasswordInterface.class, new UniversalJsonAdapter<>(AuthRequest.providers)); builder.registerTypeAdapter(HWIDProvider.class, new UniversalJsonAdapter<>(HWIDProvider.providers)); builder.registerTypeAdapter(OptionalAction.class, new UniversalJsonAdapter<>(OptionalAction.providers)); + builder.registerTypeAdapter(SessionStorage.class, new UniversalJsonAdapter<>(SessionStorage.providers)); modulesManager.invokeEvent(new PreGsonPhase(builder)); //ClientWebSocketService.appendTypeAdapters(builder); } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java index 268e9196..92c2a7d2 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/manangers/SessionManager.java @@ -1,63 +1,112 @@ package pro.gravit.launchserver.manangers; +import pro.gravit.launcher.Launcher; import pro.gravit.launcher.NeedGarbageCollection; +import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.auth.RequiredDAO; import pro.gravit.launchserver.socket.Client; +import pro.gravit.utils.HookSet; +import pro.gravit.utils.helper.LogHelper; +import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.function.Function; -import java.util.stream.Collectors; +import java.util.concurrent.ConcurrentHashMap; +import java.util.concurrent.atomic.AtomicInteger; +import java.util.stream.Stream; public class SessionManager implements NeedGarbageCollection { - public static final long SESSION_TIMEOUT = 3 * 60 * 60 * 1000; // 3 часа - private final Map clientSet = new HashMap<>(128); + private final LaunchServer server; + public HookSet clientRestoreHook = new HookSet<>(); + + public SessionManager(LaunchServer server) { + this.server = server; + } public boolean addClient(Client client) { - clientSet.put(client.session, client); - return true; + if(client == null || client.session == null) return false; + return server.config.sessions.writeSession(client.uuid, client.session, compressClient(client)); + } + + public Stream findSessionsByUUID(UUID uuid) { + return server.config.sessions.getSessionsFromUserUUID(uuid); + } + + public boolean removeByUUID(UUID uuid) { + return server.config.sessions.deleteSessionsByUserUUID(uuid); + } + + @Deprecated + public Set getSavedUUIDs() + { + throw new UnsupportedOperationException(); + } + + public void clear() { + server.config.sessions.clear(); + } + + private byte[] compressClient(Client client) { + return Launcher.gsonManager.gson.toJson(client).getBytes(StandardCharsets.UTF_8); //Compress using later + } + + private Client decompressClient(byte[] client) { + return Launcher.gsonManager.gson.fromJson(new String(client, StandardCharsets.UTF_8), Client.class); //Compress using later + } + private Client restoreFromString(byte[] data) { + Client result = decompressClient(data); + result.updateAuth(server); + if(result.auth != null && (result.username != null)) { + if(result.auth.handler instanceof RequiredDAO || result.auth.provider instanceof RequiredDAO || result.auth.textureProvider instanceof RequiredDAO) { + result.daoObject = server.config.dao.userDAO.findByUsername(result.username); + } + } + if(result.refCount == null) result.refCount = new AtomicInteger(1); + clientRestoreHook.hook(result); + return result; } @Override public void garbageCollection() { - long time = System.currentTimeMillis(); - clientSet.entrySet().removeIf(entry -> { - Client c = entry.getValue(); - return (c.timestamp + SESSION_TIMEOUT < time); - }); } public Client getClient(UUID session) { - return clientSet.get(session); + if(session == null) return null; + byte[] data = server.config.sessions.getSessionData(session); + if(data == null) return null; + return restoreFromString(data); } public Client getOrNewClient(UUID session) { - return clientSet.computeIfAbsent(session, Client::new); + Client client = getClient(session); + return client == null ? new Client(session) : client; } - public Client removeClient(UUID session) { - return clientSet.remove(session); + public boolean remove(UUID session) { + return server.config.sessions.deleteSession(session); } + @Deprecated + public void removeClient(UUID session) { + remove(session); + } + @Deprecated public void updateClient(UUID session) { - Client c = clientSet.get(session); - if (c != null) { - c.up(); - return; - } - Client newClient = new Client(session); - clientSet.put(session, newClient); + LogHelper.warning("Using deprecated method: sessionManager.updateClient"); } + @Deprecated public Set getSessions() { - // TODO: removeme - return new HashSet<>(clientSet.values()); + LogHelper.warning("Using deprecated method: sessionManager.getSession"); + return new HashSet<>(); } - + @Deprecated public void loadSessions(Set set) { - clientSet.putAll(set.stream().collect(Collectors.toMap(c -> c.session, Function.identity()))); + LogHelper.warning("Using deprecated method: sessionManager.loadSessions"); + //clientSet.putAll(set.stream().collect(Collectors.toMap(c -> c.session, Function.identity()))); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java index 1b5639d5..57113f14 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/Client.java @@ -11,6 +11,7 @@ import java.util.HashMap; import java.util.Map; import java.util.UUID; +import java.util.concurrent.atomic.AtomicInteger; public class Client { public UUID session; @@ -22,6 +23,7 @@ public class Client { public boolean checkSign; public ClientPermissions permissions; public String username; + public UUID uuid; public TrustLevel trustLevel; public transient AuthProviderPair auth; @@ -30,6 +32,8 @@ public class Client { public transient Map properties; + public transient AtomicInteger refCount = new AtomicInteger(1); + public Client(UUID session) { this.session = session; timestamp = System.currentTimeMillis(); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java index 668df90d..6042d2d2 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/LauncherNettyServer.java @@ -56,11 +56,11 @@ public void initChannel(SocketChannel ch) { NettyConnectContext context = new NettyConnectContext(); //p.addLast(new LoggingHandler(LogLevel.INFO)); pipeline.addLast("http-codec", new HttpServerCodec()); - pipeline.addLast("http-codec-compressor", new HttpObjectAggregator(65536)); + pipeline.addLast("http-codec-compressor", new HttpObjectAggregator(server.config.netty.performance.maxWebSocketRequestBytes)); if (server.config.netty.ipForwarding) pipeline.addLast("forward-http", new NettyIpForwardHandler(context)); pipeline.addLast("websock-comp", new WebSocketServerCompressionHandler()); - pipeline.addLast("websock-codec", new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true)); + pipeline.addLast("websock-codec", new WebSocketServerProtocolHandler(WEBSOCKET_PATH, null, true, server.config.netty.performance.maxWebSocketRequestBytes)); if (!server.config.netty.disableWebApiInterface) pipeline.addLast("webapi", new NettyWebAPIHandler(context)); if (server.config.netty.fileServerEnabled) diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/WebSocketService.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/WebSocketService.java index 41588e9e..abe90139 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/WebSocketService.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/WebSocketService.java @@ -11,12 +11,15 @@ import pro.gravit.launcher.events.ExceptionEvent; import pro.gravit.launcher.events.RequestEvent; import pro.gravit.launcher.events.request.ErrorRequestEvent; +import pro.gravit.launcher.events.request.ExitRequestEvent; import pro.gravit.launcher.request.WebSocketEvent; import pro.gravit.launchserver.LaunchServer; +import pro.gravit.launchserver.dao.User; import pro.gravit.launchserver.socket.handlers.WebSocketFrameHandler; import pro.gravit.launchserver.socket.response.SimpleResponse; import pro.gravit.launchserver.socket.response.WebSocketServerResponse; import pro.gravit.launchserver.socket.response.auth.*; +import pro.gravit.launchserver.socket.response.management.FeaturesResponse; import pro.gravit.launchserver.socket.response.management.PingServerReportResponse; import pro.gravit.launchserver.socket.response.management.PingServerResponse; import pro.gravit.launchserver.socket.response.management.ServerStatusResponse; @@ -36,8 +39,10 @@ import pro.gravit.utils.helper.LogHelper; import java.lang.reflect.Type; +import java.util.UUID; import java.util.concurrent.atomic.AtomicLong; import java.util.function.BiConsumer; +import java.util.function.Consumer; public class WebSocketService { public static final ProviderMap providers = new ProviderMap<>(); @@ -68,12 +73,12 @@ public WebSocketService(ChannelGroup channels, LaunchServer server) { } public void forEachActiveChannels(BiConsumer callback) { - channels.forEach((channel) -> { - if (channel == null || channel.pipeline() == null) return; + for(Channel channel : channels) { + if (channel == null || channel.pipeline() == null) continue; WebSocketFrameHandler wsHandler = channel.pipeline().get(WebSocketFrameHandler.class); - if (wsHandler == null) return; + if (wsHandler == null) continue; callback.accept(channel, wsHandler); - }); + }; } public static void registerResponses() { @@ -99,6 +104,7 @@ public static void registerResponses() { providers.register("pingServerReport", PingServerReportResponse.class); providers.register("pingServer", PingServerResponse.class); providers.register("currentUser", CurrentUserResponse.class); + providers.register("features", FeaturesResponse.class); } public void process(ChannelHandlerContext ctx, TextWebSocketFrame frame, Client client, String ip) { @@ -208,6 +214,88 @@ public void sendObjectAll(Object obj, Type type) { } } + public void sendObjectToUUID(UUID userUuid, Object obj, Type type) { + for (Channel ch : channels) { + if (ch == null || ch.pipeline() == null) continue; + WebSocketFrameHandler wsHandler = ch.pipeline().get(WebSocketFrameHandler.class); + if (wsHandler == null) continue; + Client client = wsHandler.getClient(); + if(client == null || !userUuid.equals(client.uuid)) continue; + ch.writeAndFlush(new TextWebSocketFrame(gson.toJson(obj, type)), ch.voidPromise()); + } + } + + public void updateDaoObject(UUID userUuid, User daoObject, Consumer callback) { + for(Channel ch : channels) { + if (ch == null || ch.pipeline() == null) continue; + WebSocketFrameHandler wsHandler = ch.pipeline().get(WebSocketFrameHandler.class); + if (wsHandler == null) continue; + Client client = wsHandler.getClient(); + if(client == null || client.daoObject == null || !userUuid.equals(client.uuid)) continue; + client.daoObject = daoObject; + if(callback != null) callback.accept(ch); + } + } + + public Channel getChannelFromConnectUUID(UUID connectUuid) { + for(Channel ch : channels) { + if (ch == null || ch.pipeline() == null) continue; + WebSocketFrameHandler wsHandler = ch.pipeline().get(WebSocketFrameHandler.class); + if (wsHandler == null) continue; + if(connectUuid.equals(wsHandler.getConnectUUID())) { + return ch; + } + } + return null; + } + + public boolean kickByUserUUID(UUID userUuid, boolean isClose) { + boolean result = false; + for(Channel ch : channels) { + if (ch == null || ch.pipeline() == null) continue; + WebSocketFrameHandler wsHandler = ch.pipeline().get(WebSocketFrameHandler.class); + if (wsHandler == null) continue; + Client client = wsHandler.getClient(); + if(client == null || client.daoObject == null || !userUuid.equals(client.uuid)) continue; + ExitResponse.exit(server, wsHandler, ch, ExitRequestEvent.ExitReason.SERVER); + if(isClose) ch.close(); + result = true; + } + return result; + } + + public boolean kickByConnectUUID(UUID connectUuid, boolean isClose) { + for(Channel ch : channels) { + if (ch == null || ch.pipeline() == null) continue; + WebSocketFrameHandler wsHandler = ch.pipeline().get(WebSocketFrameHandler.class); + if (wsHandler == null) continue; + if(connectUuid.equals(wsHandler.getConnectUUID())) { + ExitResponse.exit(server, wsHandler, ch, ExitRequestEvent.ExitReason.SERVER); + if(isClose) ch.close(); + return true; + } + } + return false; + } + + public boolean kickByIP(String ip, boolean isClose) { + boolean result = false; + for(Channel ch : channels) { + if (ch == null || ch.pipeline() == null) continue; + WebSocketFrameHandler wsHandler = ch.pipeline().get(WebSocketFrameHandler.class); + if(wsHandler == null) continue; + String clientIp; + if(wsHandler.context != null && wsHandler.context.ip != null) clientIp = wsHandler.context.ip; + else clientIp = IOHelper.getIP(ch.remoteAddress()); + if(ip.equals(clientIp)) { + ExitResponse.exit(server, wsHandler, ch, ExitRequestEvent.ExitReason.SERVER); + if(isClose) ch.close(); + result = true; + } + } + return result; + } + public void sendObjectAndClose(ChannelHandlerContext ctx, Object obj) { ctx.writeAndFlush(new TextWebSocketFrame(gson.toJson(obj, WebSocketEvent.class))).addListener(ChannelFutureListener.CLOSE); } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/WebSocketFrameHandler.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/WebSocketFrameHandler.java index b8948f25..4406b39f 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/WebSocketFrameHandler.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/WebSocketFrameHandler.java @@ -37,7 +37,9 @@ public Client getClient() { } public void setClient(Client client) { + if(this.client != null) this.client.refCount.decrementAndGet(); this.client = client; + if(client != null) client.refCount.incrementAndGet(); } public final UUID getConnectUUID() { @@ -85,6 +87,7 @@ protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) { } else if ((frame instanceof PongWebSocketFrame)) { LogHelper.dev("WebSocket Client received pong"); } else if ((frame instanceof CloseWebSocketFrame)) { + int statusCode = ((CloseWebSocketFrame) frame).statusCode(); ctx.channel().close(); } else { String message = "unsupported frame type: " + frame.getClass().getName(); @@ -95,6 +98,18 @@ protected void channelRead0(ChannelHandlerContext ctx, WebSocketFrame frame) { @Override public void channelInactive(ChannelHandlerContext ctx) throws Exception { if (future != null) future.cancel(true); + if(LogHelper.isDevEnabled()) { + LogHelper.dev("Client %s disconnected", IOHelper.getIP(ctx.channel().remoteAddress())); + } + int refCount = client.refCount.decrementAndGet(); + if(client.session != null) { + if(refCount == 0) { + srv.sessionManager.addClient(client); + } + else if(refCount < 0) { + LogHelper.warning("Client session %s reference counter invalid - %d", client.session, refCount); + } + } super.channelInactive(ctx); } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java index 54c5cee4..cf547058 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/handlers/fileserver/FileServerHandler.java @@ -7,6 +7,7 @@ import io.netty.handler.stream.ChunkedFile; import io.netty.util.CharsetUtil; import pro.gravit.launchserver.socket.handlers.ContentType; +import pro.gravit.utils.helper.LogHelper; import pro.gravit.utils.helper.VerifyHelper; import java.io.File; @@ -24,6 +25,7 @@ import java.time.temporal.ChronoField; import java.time.temporal.ChronoUnit; import java.time.temporal.TemporalAccessor; +import java.time.temporal.UnsupportedTemporalTypeException; import java.util.Arrays; import java.util.Locale; import java.util.Objects; @@ -220,11 +222,17 @@ public void channelRead0(ChannelHandlerContext ctx, FullHttpRequest request) thr // Only compare up to the second because the datetime format we send to the client // does not have milliseconds - long ifModifiedSinceDateSeconds = ifModifiedSinceDate.get(ChronoField.INSTANT_SECONDS); - long fileLastModifiedSeconds = file.lastModified() / 1000; - if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { - sendNotModified(ctx); - return; + try { + long ifModifiedSinceDateSeconds = ifModifiedSinceDate.getLong(ChronoField.INSTANT_SECONDS); + long fileLastModifiedSeconds = file.lastModified() / 1000; + if (ifModifiedSinceDateSeconds == fileLastModifiedSeconds) { + sendNotModified(ctx); + return; + } + } catch (UnsupportedTemporalTypeException e) { + if(LogHelper.isDebugEnabled()) { + LogHelper.warning("Request access If-Modifed-Since: %s not parsed correctly", ifModifiedSince); + } } } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/AuthResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/AuthResponse.java index 6465617a..6fd35b41 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/AuthResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/AuthResponse.java @@ -100,21 +100,21 @@ public void execute(ChannelHandlerContext ctx, Client clientData) throws Excepti if (getSession) { if (clientData.session == null) { clientData.session = UUID.randomUUID(); - server.sessionManager.addClient(clientData); + //server.sessionManager.addClient(clientData); } result.session = clientData.session; } - UUID uuid; if (authType == ConnectTypes.CLIENT && server.config.protectHandler.allowGetAccessToken(context)) { - uuid = pair.handler.auth(aresult); + clientData.uuid = pair.handler.auth(aresult); if (LogHelper.isDebugEnabled()) { - LogHelper.debug("Auth: %s accessToken %s uuid: %s", login, result.accessToken, uuid.toString()); + LogHelper.debug("Auth: %s accessToken %s uuid: %s", login, result.accessToken, clientData.uuid.toString()); } } else { - uuid = pair.handler.usernameToUUID(aresult.username); + clientData.uuid = pair.handler.usernameToUUID(aresult.username); result.accessToken = null; } - result.playerProfile = ProfileByUUIDResponse.getProfile(uuid, aresult.username, client, clientData.auth.textureProvider); + + result.playerProfile = ProfileByUUIDResponse.getProfile(clientData.uuid, aresult.username, client, clientData.auth.textureProvider); clientData.type = authType; sendResult(result); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/CurrentUserResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/CurrentUserResponse.java index 6d5cfd33..5aa7514c 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/CurrentUserResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/CurrentUserResponse.java @@ -23,7 +23,7 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception { public static CurrentUserRequestEvent.UserInfo collectUserInfoFromClient(Client client) throws IOException { CurrentUserRequestEvent.UserInfo result = new CurrentUserRequestEvent.UserInfo(); if (client.auth != null && client.isAuth && client.username != null) { - UUID uuid = client.auth.handler.usernameToUUID(client.username); + UUID uuid = client.uuid != null ? client.uuid : client.auth.handler.usernameToUUID(client.username); if (uuid != null) { result.playerProfile = ProfileByUUIDResponse.getProfile(uuid, client.username, client.profile == null ? null : client.profile.getTitle(), client.auth.textureProvider); } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ExitResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ExitResponse.java index 66d3cec1..1a32823e 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ExitResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/ExitResponse.java @@ -38,7 +38,7 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception { Client newClient = new Client(null); newClient.checkSign = client.checkSign; handler.setClient(newClient); - if (client.session != null) server.sessionManager.removeClient(client.session); + if (client.session != null) server.sessionManager.remove(client.session); if (exitAll) { service.forEachActiveChannels(((channel, webSocketFrameHandler) -> { Client client1 = webSocketFrameHandler.getClient(); @@ -68,7 +68,7 @@ public static void exit(LaunchServer server, WebSocketFrameHandler wsHandler, Ch Client newCusClient = new Client(null); newCusClient.checkSign = chClient.checkSign; wsHandler.setClient(newCusClient); - if (chClient.session != null) server.sessionManager.removeClient(chClient.session); + if (chClient.session != null) server.sessionManager.remove(chClient.session); ExitRequestEvent event = new ExitRequestEvent(reason); event.requestUUID = RequestEvent.eventUUID; wsHandler.service.sendObject(channel, event); diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreSessionResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreSessionResponse.java index f68aefb6..aed2765c 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreSessionResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/auth/RestoreSessionResponse.java @@ -21,15 +21,28 @@ public String getType() { @Override public void execute(ChannelHandlerContext ctx, Client client) throws Exception { - Client rClient = server.sessionManager.getClient(session); - if (rClient == null) { + if(session == null) { + sendError("Session invalid"); + return; + } + final Client[] rClient = {null}; + service.forEachActiveChannels((channel, handler) -> { + Client c = handler.getClient(); + if(c != null && session.equals(c.session)) { + rClient[0] = c; + } + }); + if(rClient[0] == null) { + rClient[0] = server.sessionManager.getClient(session); + } + if (rClient[0] == null) { sendError("Session invalid"); return; } WebSocketFrameHandler frameHandler = ctx.pipeline().get(WebSocketFrameHandler.class); - frameHandler.setClient(rClient); + frameHandler.setClient(rClient[0]); if (needUserInfo) { - sendResult(new RestoreSessionRequestEvent(CurrentUserResponse.collectUserInfoFromClient(rClient))); + sendResult(new RestoreSessionRequestEvent(CurrentUserResponse.collectUserInfoFromClient(rClient[0]))); } else { sendResult(new RestoreSessionRequestEvent()); } diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/management/FeaturesResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/management/FeaturesResponse.java new file mode 100644 index 00000000..09eb4118 --- /dev/null +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/management/FeaturesResponse.java @@ -0,0 +1,18 @@ +package pro.gravit.launchserver.socket.response.management; + +import io.netty.channel.ChannelHandlerContext; +import pro.gravit.launcher.events.request.FeaturesRequestEvent; +import pro.gravit.launchserver.socket.Client; +import pro.gravit.launchserver.socket.response.SimpleResponse; + +public class FeaturesResponse extends SimpleResponse { + @Override + public String getType() { + return "features"; + } + + @Override + public void execute(ChannelHandlerContext ctx, Client client) throws Exception { + sendResult(new FeaturesRequestEvent(server.featuresManager.getMap())); + } +} diff --git a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/profile/ProfileByUUIDResponse.java b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/profile/ProfileByUUIDResponse.java index 816c5920..b468c12d 100644 --- a/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/profile/ProfileByUUIDResponse.java +++ b/LaunchServer/src/main/java/pro/gravit/launchserver/socket/response/profile/ProfileByUUIDResponse.java @@ -63,6 +63,6 @@ public void execute(ChannelHandlerContext ctx, Client client) throws Exception { sendError(String.format("ProfileByUUIDResponse: User with uuid %s not found or AuthProvider#uuidToUsername returned null", uuid)); return; } - sendResult(new ProfileByUUIDRequestEvent(getProfile(uuid, username, this.client, client.auth.textureProvider))); + sendResult(new ProfileByUUIDRequestEvent(getProfile(uuid, username, this.client, pair.textureProvider))); } } diff --git a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java index b4d8550f..00001648 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java +++ b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherEntryPoint.java @@ -159,8 +159,7 @@ public static void main(String[] args) throws Throwable { verifyHDir(clientDir, params.clientHDir, clientMatcher, digest); if (javaWatcher != null) verifyHDir(javaDir, params.javaHDir, null, digest); - if (params.javaHDir != null) - LauncherEngine.modulesManager.invokeEvent(new ClientProcessLaunchEvent(engine, params)); + LauncherEngine.modulesManager.invokeEvent(new ClientProcessLaunchEvent(engine, params)); launch(profile, params); } } diff --git a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java index 136b7400..8b05806a 100644 --- a/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java +++ b/Launcher/src/main/java/pro/gravit/launcher/client/ClientLauncherProcess.java @@ -103,7 +103,6 @@ private void applyClientProfile() { this.systemEnv.put("JAVA_HOME", javaDir.toString()); Collections.addAll(this.systemClassPath, this.params.profile.getAlternativeClassPath()); if (params.ram > 0) { - this.jvmArgs.add("-Xms" + params.ram + 'M'); this.jvmArgs.add("-Xmx" + params.ram + 'M'); } this.params.session = Request.getSession(); diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/events/request/FeaturesRequestEvent.java b/LauncherAPI/src/main/java/pro/gravit/launcher/events/request/FeaturesRequestEvent.java new file mode 100644 index 00000000..d6151b19 --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/events/request/FeaturesRequestEvent.java @@ -0,0 +1,21 @@ +package pro.gravit.launcher.events.request; + +import pro.gravit.launcher.events.RequestEvent; + +import java.util.Map; + +public class FeaturesRequestEvent extends RequestEvent { + public Map features; + + public FeaturesRequestEvent() { + } + + public FeaturesRequestEvent(Map features) { + this.features = features; + } + + @Override + public String getType() { + return "features"; + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java index 7695fb7d..1c0f8e99 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/profiles/ClientProfile.java @@ -352,8 +352,6 @@ public void verify() { // Client VerifyHelper.verify(getTitle(), VerifyHelper.NOT_EMPTY, "Profile title can't be empty"); VerifyHelper.verify(getInfo(), VerifyHelper.NOT_EMPTY, "Profile info can't be empty"); - VerifyHelper.verify(getServerAddress(), VerifyHelper.NOT_EMPTY, "Server address can't be empty"); - VerifyHelper.verifyInt(getServerPort(), VerifyHelper.range(0, 65535), "Illegal server port: " + getServerPort()); // Client launcher VerifyHelper.verify(getTitle(), VerifyHelper.NOT_EMPTY, "Main class can't be empty"); @@ -428,7 +426,8 @@ public enum Version { MC1152("1.15.2", 578), MC1161("1.16.1", 736), MC1162("1.16.2", 751), - MC1163("1.16.3", 753); + MC1163("1.16.3", 753), + MC1164("1.16.4", 754); private static final Map VERSIONS; static { diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/request/management/FeaturesRequest.java b/LauncherAPI/src/main/java/pro/gravit/launcher/request/management/FeaturesRequest.java new file mode 100644 index 00000000..d203cdff --- /dev/null +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/request/management/FeaturesRequest.java @@ -0,0 +1,11 @@ +package pro.gravit.launcher.request.management; + +import pro.gravit.launcher.events.request.FeaturesRequestEvent; +import pro.gravit.launcher.request.Request; + +public class FeaturesRequest extends Request { + @Override + public String getType() { + return "features"; + } +} diff --git a/LauncherAPI/src/main/java/pro/gravit/launcher/request/uuid/BatchProfileByUsernameRequest.java b/LauncherAPI/src/main/java/pro/gravit/launcher/request/uuid/BatchProfileByUsernameRequest.java index f1038f48..b120341c 100644 --- a/LauncherAPI/src/main/java/pro/gravit/launcher/request/uuid/BatchProfileByUsernameRequest.java +++ b/LauncherAPI/src/main/java/pro/gravit/launcher/request/uuid/BatchProfileByUsernameRequest.java @@ -16,6 +16,7 @@ public final class BatchProfileByUsernameRequest extends Request { public ClassLoader loader; public ClientPermissions permissions; public ClientProfile profile; + public PlayerProfile playerProfile; + public ClientProfile.ServerProfile serverProfile; public ServerWrapper(Type type, Path configPath) { super(type, configPath); @@ -70,20 +74,28 @@ public boolean auth() { try { Launcher.getConfig(); AuthRequest request = new AuthRequest(config.login, config.password, config.auth_id, AuthRequest.ConnectTypes.API); - permissions = request.request().permissions; + AuthRequestEvent authResult = request.request(); + permissions = authResult.permissions; + playerProfile = authResult.playerProfile; ProfilesRequestEvent result = new ProfilesRequest().request(); for (ClientProfile p : result.profiles) { LogHelper.debug("Get profile: %s", p.getTitle()); - if (p.getTitle().equals(config.title)) { - profile = p; - Launcher.profile = p; - LogHelper.debug("Found profile: %s", Launcher.profile.getTitle()); - break; + boolean isFound = false; + for(ClientProfile.ServerProfile srv : p.getServers()) + { + if(srv != null && srv.name.equals(config.serverName)) { + this.serverProfile = srv; + this.profile = p; + Launcher.profile = p; + LogHelper.debug("Found profile: %s", Launcher.profile.getTitle()); + isFound = true; + break; + } } + if(isFound) break; } if (profile == null) { - LogHelper.error("Your profile not found"); - if (config.stopOnError) System.exit(-1); + LogHelper.warning("Not connected to ServerProfile. May be serverName incorrect?"); } return true; } catch (Throwable e) { @@ -183,7 +195,7 @@ public void run(String... args) throws Throwable { } auth(); }; - LogHelper.info("ServerWrapper: Project %s, LaunchServer address: %s. Title: %s", config.projectname, config.address, config.title); + LogHelper.info("ServerWrapper: Project %s, LaunchServer address: %s. Title: %s", config.projectname, config.address, Launcher.profile != null ? Launcher.profile.getTitle() : "unknown"); LogHelper.info("Minecraft Version (for profile): %s", wrapper.profile == null ? "unknown" : wrapper.profile.getVersion().name); LogHelper.info("Start Minecraft Server"); LogHelper.debug("Invoke main method %s", mainClass.getName()); @@ -228,7 +240,7 @@ public void setConfig(Config config) { @Override public Config getDefaultConfig() { Config newConfig = new Config(); - newConfig.title = "Your profile title"; + newConfig.serverName = "your server name"; newConfig.projectname = "MineCraft"; newConfig.login = "login"; newConfig.password = "password"; @@ -244,9 +256,11 @@ public Config getDefaultConfig() { } public static final class Config { + @Deprecated public String title; public String projectname; public String address; + public String serverName; public WebSocketConf websocket; public int reconnectCount; public int reconnectSleep; diff --git a/ServerWrapper/src/main/java/pro/gravit/launcher/server/setup/ServerWrapperSetup.java b/ServerWrapper/src/main/java/pro/gravit/launcher/server/setup/ServerWrapperSetup.java index 70e30e85..9bcd7118 100644 --- a/ServerWrapper/src/main/java/pro/gravit/launcher/server/setup/ServerWrapperSetup.java +++ b/ServerWrapper/src/main/java/pro/gravit/launcher/server/setup/ServerWrapperSetup.java @@ -46,6 +46,8 @@ public void run() throws IOException { } } LogHelper.info("Found MainClass %s", mainClassName); + System.out.println("Print your server name:"); + wrapper.config.serverName = commands.commandHandler.readLine(); System.out.println("Print launchserver websocket host( ws://host:port/api ):"); String address = commands.commandHandler.readLine(); wrapper.config.mainclass = mainClassName; @@ -56,14 +58,10 @@ public void run() throws IOException { String login = commands.commandHandler.readLine(); System.out.println("Print server account password:"); String password = commands.commandHandler.readLine(); - System.out.println("Print profile title:"); - String title = commands.commandHandler.readLine(); wrapper.config.login = login; wrapper.config.password = password; - wrapper.config.title = title; wrapper.config.stopOnError = false; wrapper.updateLauncherConfig(); - if (wrapper.auth()) { break; } else { diff --git a/build.gradle b/build.gradle index 01c64481..915ab739 100644 --- a/build.gradle +++ b/build.gradle @@ -5,7 +5,7 @@ id 'org.openjfx.javafxplugin' version '0.0.8' apply false } group = 'pro.gravit.launcher' -version = '5.1.8' +version = '5.1.9' apply from: 'props.gradle' diff --git a/modules b/modules index 71c224e4..5c4f6850 160000 --- a/modules +++ b/modules @@ -1 +1 @@ -Subproject commit 71c224e4d7a0950d3b5599ba4dec8d6fd04ededd +Subproject commit 5c4f6850bd4feeee0caff5561564b7e54bb94774