Compare commits

...

8 Commits
main ... 2.0

Author SHA1 Message Date
games647
ad815777d4 Send login command from proxy immediately 2024-05-21 21:55:23 +02:00
games647
9a400f5ae1 Only print premium warning on command sender site 2024-05-21 21:53:30 +02:00
games647
c7254034bb Modularize authentication backends 2024-05-21 17:07:09 +02:00
games647
7a8e5a59d0 Restore older Java compatibility 2024-05-21 17:04:41 +02:00
games647
d04e421927 Migrate to new HTTP client 2024-05-21 15:09:45 +02:00
games647
6005f0db44 Minor clean up 2024-05-21 15:09:28 +02:00
games647
f50004f50d Drop configuration for outgoing IP addresses 2024-05-21 15:08:12 +02:00
games647
b6b95d50e5 Update HikariCP 2024-05-21 14:47:34 +02:00
51 changed files with 655 additions and 642 deletions

View File

@@ -25,8 +25,3 @@ updates:
# Plugin require special evaluation about their compatibility
- "com.lenis0012.bukkit:loginsecurity"
- "com.comphenix.protocol:ProtocolLib"
ignore:
# HikariCP dropped Java 8 support with 5.0
- dependency-name: "com.zaxxer:HikariCP"
update-types: ["version-update:semver-major"]

View File

@@ -25,7 +25,7 @@ jobs:
if: ${{ github.event.workflow_run.conclusion == 'success' }}
permissions:
# Only allow write for security, then all others default to read only
# Only allow 'write' permission for security, then all others default to read only
security-events: write
strategy:

View File

@@ -1,3 +1,29 @@
# 2.0
## Major changes
* Bumped minimum Java version to 11 make use of modern Java performance features
* Report back if really still need the old versions
* Then we could make use of versioned code, but that requires more coding effort
## Added
* Support for HTTP/2 for contacting Mojang
## Changed
* Updated many dependencies
## Removed
Dropped some features listed below. Please contact us if you still need them
* Dropped Java support < 11
* Removed configuration option to add multiple outgoing IPv4 towards Mojang
* Dropped support for ProtocolSupport seems to unsupported
[...] A lot of changes
### 1.11
* TODO: Replace reflection with methodhandles

View File

@@ -26,7 +26,7 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import org.jetbrains.annotations.Nullable;

View File

@@ -25,23 +25,21 @@
*/
package com.github.games647.fastlogin.bukkit;
import com.comphenix.protocol.ProtocolLibrary;
import com.github.games647.fastlogin.bukkit.auth.AuthenticationBackend;
import com.github.games647.fastlogin.bukkit.auth.ConnectionListener;
import com.github.games647.fastlogin.bukkit.auth.protocollib.ProtocolAuthentication;
import com.github.games647.fastlogin.bukkit.auth.proxy.ProxyAuthentication;
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
import com.github.games647.fastlogin.bukkit.listener.PaperCacheListener;
import com.github.games647.fastlogin.bukkit.listener.protocollib.ProtocolLibListener;
import com.github.games647.fastlogin.bukkit.listener.protocollib.SkinApplyListener;
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook;
import com.github.games647.fastlogin.bukkit.hook.DelayedAuthHook;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.antibot.AntiBotService;
import com.github.games647.fastlogin.core.hooks.bedrock.BedrockService;
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
import com.github.games647.fastlogin.core.hooks.bedrock.GeyserService;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import lombok.Getter;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -55,6 +53,8 @@ import org.slf4j.Logger;
import java.net.InetSocketAddress;
import java.nio.file.Path;
import java.time.Duration;
import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Optional;
import java.util.UUID;
@@ -71,18 +71,30 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
Duration.ofMinutes(1), -1
);
@Getter
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
private final Logger logger;
private boolean serverStarted;
private BungeeManager bungeeManager;
private final BukkitScheduler scheduler;
@Getter
private final Collection<UUID> pendingConfirms = new HashSet<>();
@Getter
private FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
@Getter
private FloodgateService floodgateService;
private GeyserService geyserService;
private PremiumPlaceholder premiumPlaceholder;
@Getter
private AuthenticationBackend backend;
@Getter
private boolean initialized;
public FastLoginBukkit() {
this.logger = CommonUtil.initializeLoggerService(getLogger());
this.scheduler = new BukkitScheduler(this, logger);
@@ -104,51 +116,42 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
setEnabled(false);
}
bungeeManager = new BungeeManager(this);
bungeeManager.initialize();
PluginManager pluginManager = getServer().getPluginManager();
if (bungeeManager.isEnabled()) {
markInitialized();
} else {
if (!core.setupDatabase()) {
setEnabled(false);
return;
}
AntiBotService antiBotService = core.getAntiBotService();
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
pluginManager.registerEvents(new ProtocolSupportListener(this, antiBotService), this);
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
ProtocolLibListener.register(this, antiBotService, core.getConfig().getBoolean("verifyClientKeys"));
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
if (!isPaper() && getConfig().getBoolean("forwardSkin")) {
pluginManager.registerEvents(new SkinApplyListener(this), this);
}
} else {
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
setEnabled(false);
return;
}
backend = initializeAuthenticationBackend();
if (backend == null) {
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
setEnabled(false);
return;
}
//delay dependency setup because we load the plugin very early where plugins are initialized yet
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
backend.init(getServer().getPluginManager());
PluginManager pluginManager = getServer().getPluginManager();
pluginManager.registerEvents(new ConnectionListener(this), this);
//if server is using paper - we need to add one more listener to correct the user cache usage
if (isPaper()) {
pluginManager.registerEvents(new PaperCacheListener(this), this);
}
registerCommands();
if (pluginManager.isPluginEnabled("PlaceholderAPI")) {
premiumPlaceholder = new PremiumPlaceholder(this);
premiumPlaceholder.register();
}
// delay dependency setup because we load the plugin very early where plugins are initialized yet
getServer().getScheduler().runTaskLater(this, new DelayedAuthHook(this), 5L);
}
private AuthenticationBackend initializeAuthenticationBackend() {
AuthenticationBackend proxyVerifier = new ProxyAuthentication(this);
if (proxyVerifier.isAvailable()) {
return proxyVerifier;
}
logger.warn("Disabling Minecraft proxy configuration. Assuming direct connections from now on.");
AuthenticationBackend protocolAuthentication = new ProtocolAuthentication(this);
if (protocolAuthentication.isAvailable()) {
return protocolAuthentication;
}
return null;
}
private void registerCommands() {
@@ -182,25 +185,13 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
core.close();
}
if (bungeeManager != null) {
bungeeManager.cleanup();
if (backend != null) {
backend.stop();
}
if (premiumPlaceholder != null && getServer().getPluginManager().isPluginEnabled("PlaceholderAPI")) {
try {
premiumPlaceholder.unregister();
} catch (Exception | NoSuchMethodError exception) {
logger.error("Failed to unregister placeholder", exception);
}
premiumPlaceholder.unregister();
}
if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().unregisterAsyncHandlers(this);
}
}
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
return core;
}
/**
@@ -232,10 +223,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
loginSession.remove(id);
}
public Map<UUID, PremiumStatus> getPremiumPlayers() {
return premiumPlayers;
}
/**
* Fetches the premium status of an online player.
* {@snippet :
@@ -263,24 +250,6 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN);
}
/**
* Wait before the server is fully started. This is workaround, because connections right on startup are not
* injected by ProtocolLib
*
* @return true if ProtocolLib can now intercept packets
*/
public boolean isServerFullyStarted() {
return serverStarted;
}
public void markInitialized() {
this.serverStarted = true;
}
public BungeeManager getBungeeManager() {
return bungeeManager;
}
@Override
public Path getPluginFolder() {
return getDataFolder().toPath();
@@ -313,12 +282,21 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return Bukkit.getServer().getPluginManager().getPlugin(name) != null;
}
public FloodgateService getFloodgateService() {
return floodgateService;
public void setInitialized(boolean hookFound) {
if (backend instanceof ProxyAuthentication) {
logger.info("BungeeCord setting detected. No auth plugin is required");
} else if (!hookFound) {
logger.warn("No auth plugin were found by this plugin "
+ "(other plugins could hook into this after the initialization of this plugin)"
+ "and BungeeCord is deactivated. "
+ "Either one or both of the checks have to pass in order to use this plugin");
}
initialized = true;
}
public GeyserService getGeyserService() {
return geyserService;
public ProxyAuthentication getBungeeManager() {
return (ProxyAuthentication) backend;
}
@Override
@@ -326,19 +304,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
if (floodgateService != null) {
return floodgateService;
}
return geyserService;
}
private boolean isPaper() {
return isClassAvailable("com.destroystokyo.paper.PaperConfig").isPresent()
|| isClassAvailable("io.papermc.paper.configuration.Configuration").isPresent();
}
private Optional<Class<?>> isClassAvailable(String clazzName) {
try {
return Optional.of(Class.forName(clazzName));
} catch (ClassNotFoundException e) {
return Optional.empty();
}
}
}

View File

@@ -23,44 +23,15 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
package com.github.games647.fastlogin.bukkit.auth;
import com.github.games647.fastlogin.core.shared.LoginSource;
import protocolsupport.api.events.PlayerLoginStartEvent;
import org.bukkit.plugin.PluginManager;
import java.net.InetSocketAddress;
public interface AuthenticationBackend {
public class ProtocolLoginSource implements LoginSource {
boolean isAvailable();
private final PlayerLoginStartEvent loginStartEvent;
void init(PluginManager pluginManager);
public ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
this.loginStartEvent = loginStartEvent;
}
@Override
public void enableOnlinemode() {
loginStartEvent.setOnlineMode(true);
}
@Override
public void kick(String message) {
loginStartEvent.denyLogin(message);
}
@Override
public InetSocketAddress getAddress() {
return loginStartEvent.getConnection().getRawAddress();
}
public PlayerLoginStartEvent getLoginStartEvent() {
return loginStartEvent;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '{'
+ "loginStartEvent=" + loginStartEvent
+ '}';
}
void stop();
}

View File

@@ -23,23 +23,17 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener;
package com.github.games647.fastlogin.bukkit.auth;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.task.FloodgateAuthTask;
import com.github.games647.fastlogin.bukkit.task.ForceLoginTask;
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.metadata.Metadatable;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
/**
@@ -56,14 +50,6 @@ public class ConnectionListener implements Listener {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
removeBlockedStatus(loginEvent.getPlayer());
if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) {
loginEvent.disallow(Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
}
}
@EventHandler(ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
Player player = joinEvent.getPlayer();
@@ -102,21 +88,13 @@ public class ConnectionListener implements Listener {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
plugin.getBungeeManager().markJoinEventFired(player);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
removeBlockedStatus(player);
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
plugin.getPendingConfirms().remove(player.getUniqueId());
plugin.getPremiumPlayers().remove(player.getUniqueId());
plugin.getBungeeManager().cleanup(player);
}
private void removeBlockedStatus(Metadatable player) {
player.removeMetadata(plugin.getName(), plugin);
}
}

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.task;
package com.github.games647.fastlogin.bukkit.auth;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.task;
package com.github.games647.fastlogin.bukkit.auth;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
@@ -80,7 +80,7 @@ public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender,
@Override
public void onForceActionSuccess(LoginSession session) {
if (core.getPlugin().getBungeeManager().isEnabled()) {
if (core.getPlugin().getBungeeManager().isAvailable()) {
core.getPlugin().getBungeeManager().sendPluginMessage(player, new SuccessMessage());
}
}

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit;
package com.github.games647.fastlogin.bukkit.auth;
import java.net.InetAddress;

View File

@@ -0,0 +1,70 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2024 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.auth;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.auth.protocollib.SkinApplyListener;
import org.bukkit.plugin.PluginManager;
import java.util.Optional;
public abstract class LocalAuthentication implements AuthenticationBackend {
protected final FastLoginBukkit plugin;
protected LocalAuthentication(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@Override
public void init(PluginManager pluginManager) {
if (!plugin.getCore().setupDatabase()) {
plugin.setEnabled(false);
return;
}
// if server is using paper - we need to add one more listener to correct the user cache usage
if (isPaper()) {
pluginManager.registerEvents(new PaperCacheListener(plugin), plugin);
} else if (plugin.getConfig().getBoolean("forwardSkin")) {
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
pluginManager.registerEvents(new SkinApplyListener(plugin), plugin);
}
}
private boolean isPaper() {
return isClassAvailable("com.destroystokyo.paper.PaperConfig").isPresent()
|| isClassAvailable("io.papermc.paper.configuration.Configuration").isPresent();
}
private Optional<Class<?>> isClassAvailable(String clazzName) {
try {
return Optional.of(Class.forName(clazzName));
} catch (ClassNotFoundException e) {
return Optional.empty();
}
}
}

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener;
package com.github.games647.fastlogin.bukkit.auth;
import com.destroystokyo.paper.profile.ProfileProperty;
import com.github.games647.craftapi.model.skin.Textures;

View File

@@ -23,10 +23,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import com.google.common.hash.Hasher;
import com.google.common.hash.Hashing;
import com.google.common.io.Resources;

View File

@@ -23,14 +23,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketEvent;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;

View File

@@ -0,0 +1,72 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2024 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.auth.LocalAuthentication;
import com.github.games647.fastlogin.core.antibot.AntiBotService;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.plugin.PluginManager;
public class ProtocolAuthentication extends LocalAuthentication implements Listener {
public ProtocolAuthentication(FastLoginBukkit plugin) {
super(plugin);
}
@Override
public boolean isAvailable() {
return plugin.getServer().getPluginManager().isPluginEnabled("ProtocolLib");
}
@Override
public void init(PluginManager pluginManager) {
pluginManager.registerEvents(this, plugin);
FastLoginCore<Player, CommandSender, FastLoginBukkit> core = plugin.getCore();
AntiBotService antiBotService = core.getAntiBotService();
ProtocolLibListener.register(plugin, antiBotService, core.getConfig().getBoolean("verifyClientKeys"));
}
@Override
public void stop() {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().unregisterAsyncHandlers(plugin);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() == PlayerLoginEvent.Result.ALLOWED && !plugin.isInitialized()) {
loginEvent.disallow(PlayerLoginEvent.Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
}
}
}

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.PacketType;
import com.comphenix.protocol.ProtocolLibrary;
@@ -42,7 +42,7 @@ import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.core.antibot.AntiBotService;
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
import com.mojang.datafixers.util.Either;
@@ -110,7 +110,7 @@ public class ProtocolLibListener extends PacketAdapter {
public void onPacketReceiving(PacketEvent packetEvent) {
if (packetEvent.isCancelled()
|| plugin.getCore().getAuthPluginHook() == null
|| !plugin.isServerFullyStarted()) {
|| !plugin.isInitialized()) {
return;
}

View File

@@ -23,14 +23,14 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.reflect.StructureModifier;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.core.shared.LoginSource;
import org.bukkit.entity.Player;

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketContainer;
@@ -47,8 +47,8 @@ import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.InetUtils;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.auth.InetUtils;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import lombok.val;
import org.bukkit.entity.Player;

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib.packet;
package com.github.games647.fastlogin.bukkit.auth.protocollib.packet;
import lombok.Value;
import lombok.experimental.Accessors;

View File

@@ -23,9 +23,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit;
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.listener.BungeeListener;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.auth.AuthenticationBackend;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
@@ -33,105 +34,65 @@ import com.google.common.io.ByteArrayDataOutput;
import com.google.common.io.ByteStreams;
import org.bukkit.Bukkit;
import org.bukkit.Server;
import org.bukkit.entity.Player;
import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
import java.io.IOException;
import java.lang.reflect.Field;
import java.lang.reflect.InvocationTargetException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;
import static com.github.games647.fastlogin.core.message.ChangePremiumMessage.CHANGE_CHANNEL;
import static com.github.games647.fastlogin.core.message.SuccessMessage.SUCCESS_CHANNEL;
import static java.util.stream.Collectors.toSet;
public class BungeeManager {
private static final String LEGACY_FILE_NAME = "proxy-whitelist.txt";
private static final String FILE_NAME = "allowed-proxies.txt";
//null if proxies allowed list is empty so bungeecord support is disabled
private Set<UUID> proxyIds;
public class ProxyAuthentication implements AuthenticationBackend {
private final FastLoginBukkit plugin;
private boolean enabled;
private ProxyVerifier verifier;
private final Collection<UUID> firedJoinEvents = new HashSet<>();
public BungeeManager(FastLoginBukkit plugin) {
public ProxyAuthentication(FastLoginBukkit plugin) {
this.plugin = plugin;
}
public void cleanup() {
//remove old blocked status
Bukkit.getOnlinePlayers().forEach(player -> player.removeMetadata(plugin.getName(), plugin));
@Override
public boolean isAvailable() {
return detectProxy();
}
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
if (player != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
message.writeTo(dataOutput);
@Override
public void init(PluginManager pluginManager) {
verifier = new ProxyVerifier(plugin);
verifier.loadSecrets();
NamespaceKey channel = new NamespaceKey(plugin.getName(), message.getChannelName());
player.sendPluginMessage(plugin, channel.getCombinedName(), dataOutput.toByteArray());
registerPluginChannels();
pluginManager.registerEvents(new ProxyConnectionListener(plugin, verifier), plugin);
plugin.getLog().info("Found enabled proxy configuration");
plugin.getLog().info("Remember to follow the proxy guide to complete your setup");
}
private void registerPluginChannels() {
Server server = Bukkit.getServer();
// check for incoming messages from the bungeecord version of this plugin
String groupId = plugin.getName();
String forceChannel = NamespaceKey.getCombined(groupId, LoginActionMessage.FORCE_CHANNEL);
server.getMessenger().registerIncomingPluginChannel(plugin, forceChannel, new ProxyListener(plugin, verifier));
// outgoing
String successChannel = new NamespaceKey(groupId, SUCCESS_CHANNEL).getCombinedName();
String changeChannel = new NamespaceKey(groupId, CHANGE_CHANNEL).getCombinedName();
server.getMessenger().registerOutgoingPluginChannel(plugin, successChannel);
server.getMessenger().registerOutgoingPluginChannel(plugin, changeChannel);
}
@Override
public void stop() {
if (verifier != null) {
verifier.cleanup();
}
}
public boolean isEnabled() {
return enabled;
}
public void initialize() {
enabled = detectProxy();
if (enabled) {
proxyIds = loadBungeeCordIds();
if (proxyIds.isEmpty()) {
plugin.getLog().info("No valid IDs found. Minecraft proxy support cannot work in the current state");
}
registerPluginChannels();
plugin.getLog().info("Found enabled proxy configuration");
plugin.getLog().info("Remember to follow the proxy guide to complete your setup");
} else {
plugin.getLog().warn("Disabling Minecraft proxy configuration. Assuming direct connections from now on.");
}
}
private boolean isProxySupported(String className, String fieldName)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
return Class.forName(className).getDeclaredField(fieldName).getBoolean(null);
}
private boolean isVelocityEnabled()
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException,
NoSuchMethodException, InvocationTargetException {
try {
Class<?> globalConfig = Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
Object global = globalConfig.getDeclaredMethod("get").invoke(null);
Object proxiesConfiguration = global.getClass().getDeclaredField("proxies").get(global);
Field velocitySectionField = proxiesConfiguration.getClass().getDeclaredField("velocity");
Object velocityConfig = velocitySectionField.get(proxiesConfiguration);
return velocityConfig.getClass().getDeclaredField("enabled").getBoolean(velocityConfig);
} catch (ClassNotFoundException classNotFoundException) {
// try again using the older Paper configuration, because the old class file still exists in newer versions
if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) {
return true;
}
}
return false;
}
private boolean detectProxy() {
try {
if (isProxySupported("org.spigotmc.SpigotConfig", "bungee")) {
@@ -155,82 +116,40 @@ public class BungeeManager {
return false;
}
private void registerPluginChannels() {
Server server = Bukkit.getServer();
// check for incoming messages from the bungeecord version of this plugin
String groupId = plugin.getName();
String forceChannel = NamespaceKey.getCombined(groupId, LoginActionMessage.FORCE_CHANNEL);
server.getMessenger().registerIncomingPluginChannel(plugin, forceChannel, new BungeeListener(plugin));
// outgoing
String successChannel = new NamespaceKey(groupId, SUCCESS_CHANNEL).getCombinedName();
String changeChannel = new NamespaceKey(groupId, CHANGE_CHANNEL).getCombinedName();
server.getMessenger().registerOutgoingPluginChannel(plugin, successChannel);
server.getMessenger().registerOutgoingPluginChannel(plugin, changeChannel);
private boolean isProxySupported(String className, String fieldName)
throws ClassNotFoundException, NoSuchFieldException, IllegalAccessException {
return Class.forName(className).getDeclaredField(fieldName).getBoolean(null);
}
private Set<UUID> loadBungeeCordIds() {
Path proxiesFile = plugin.getPluginFolder().resolve(FILE_NAME);
Path legacyFile = plugin.getPluginFolder().resolve(LEGACY_FILE_NAME);
private boolean isVelocityEnabled()
throws NoSuchFieldException, IllegalArgumentException, IllegalAccessException, ClassNotFoundException,
NoSuchMethodException, InvocationTargetException {
try {
if (Files.notExists(proxiesFile)) {
if (Files.exists(legacyFile)) {
Files.move(legacyFile, proxiesFile);
}
Class<?> globalConfig = Class.forName("io.papermc.paper.configuration.GlobalConfiguration");
Object global = globalConfig.getDeclaredMethod("get").invoke(null);
Object proxiesConfiguration = global.getClass().getDeclaredField("proxies").get(global);
if (Files.notExists(legacyFile)) {
Files.createFile(proxiesFile);
}
}
Field velocitySectionField = proxiesConfiguration.getClass().getDeclaredField("velocity");
Object velocityConfig = velocitySectionField.get(proxiesConfiguration);
Files.deleteIfExists(legacyFile);
try (Stream<String> lines = Files.lines(proxiesFile)) {
return lines.map(String::trim).map(UUID::fromString).collect(toSet());
return velocityConfig.getClass().getDeclaredField("enabled").getBoolean(velocityConfig);
} catch (ClassNotFoundException classNotFoundException) {
// try again using the older Paper configuration, because the old class file still exists in newer versions
if (isProxySupported("com.destroystokyo.paper.PaperConfig", "velocitySupport")) {
return true;
}
} catch (IOException ex) {
plugin.getLog().error("Failed to read proxies", ex);
} catch (Exception ex) {
plugin.getLog().error("Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
}
return Collections.emptySet();
return false;
}
public boolean isProxyAllowed(UUID proxyId) {
return proxyIds != null && proxyIds.contains(proxyId);
}
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
if (player != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
message.writeTo(dataOutput);
/**
* Mark the event to be fired including the task delay.
*
* @param player joining player
*/
public synchronized void markJoinEventFired(Player player) {
firedJoinEvents.add(player.getUniqueId());
}
/**
* Check if the event fired including with the task delay. This necessary to restore the order of processing the
* BungeeCord messages after the PlayerJoinEvent fires including the delay.
* <p>
* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login
* session. If not fired, we can start a new force login task. This will still match the requirement that we wait
* a certain time after the player join event fired.
*
* @param player joining player
* @return event fired including delay
*/
public synchronized boolean didJoinEventFired(Player player) {
return firedJoinEvents.contains(player.getUniqueId());
}
/**
* Player quit clean up
*
* @param player joining player
*/
public synchronized void cleanup(Player player) {
firedJoinEvents.remove(player.getUniqueId());
NamespaceKey channel = new NamespaceKey(plugin.getName(), message.getChannelName());
player.sendPluginMessage(plugin, channel.getCombinedName(), dataOutput.toByteArray());
}
}
}

View File

@@ -0,0 +1,114 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2024 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.auth.FloodgateAuthTask;
import com.github.games647.fastlogin.bukkit.auth.ForceLoginTask;
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerQuitEvent;
import org.bukkit.metadata.Metadatable;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
public class ProxyConnectionListener implements Listener {
private static final long DELAY_LOGIN = 20L / 2;
private final FastLoginBukkit plugin;
private final ProxyVerifier verifier;
public ProxyConnectionListener(FastLoginBukkit plugin, ProxyVerifier verifier) {
this.plugin = plugin;
this.verifier = verifier;
}
private void removeBlockedStatus(Metadatable player) {
player.removeMetadata(plugin.getName(), plugin);
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
removeBlockedStatus(loginEvent.getPlayer());
}
@EventHandler(ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
Player player = joinEvent.getPlayer();
Bukkit.getScheduler().runTaskLater(plugin, () -> {
delayForceLogin(player);
// delay the login process to let auth plugins initialize the player
// Magic number however as there is no direct event from those plugins
}, DELAY_LOGIN);
}
private void delayForceLogin(Player player) {
// session exists so the player is ready for force login
// cases: Paper (firing BungeeCord message before PlayerJoinEvent) or not running BungeeCord and already
// having the login session from the login process
BukkitLoginSession session = plugin.getSession(player.spigot().getRawAddress());
if (session == null) {
// Floodgate players usually don't have a session at this point
// exception: if force login by bungee message had been delayed
FloodgateService floodgateService = plugin.getFloodgateService();
if (floodgateService != null) {
FloodgatePlayer floodgatePlayer = floodgateService.getBedrockPlayer(player.getUniqueId());
if (floodgatePlayer != null) {
Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer);
Bukkit.getScheduler().runTaskAsynchronously(plugin, floodgateAuthTask);
return;
}
}
String sessionId = plugin.getSessionId(player.spigot().getRawAddress());
plugin.getLog().info("No on-going login session for player: {} with ID {}. ", player, sessionId);
plugin.getLog().info("Setups using Minecraft proxies will start delayed "
+ "when the command from the proxy is received");
} else {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);
Bukkit.getScheduler().runTaskAsynchronously(plugin, forceLoginTask);
}
verifier.markJoinEventFired(player);
}
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
removeBlockedStatus(player);
verifier.cleanup(player);
}
}

View File

@@ -23,11 +23,11 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener;
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.task.ForceLoginTask;
import com.github.games647.fastlogin.bukkit.auth.ForceLoginTask;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
@@ -47,12 +47,14 @@ import java.util.UUID;
* This class also receives the plugin message from the bungeecord version of this plugin in order to get notified if
* the connection is in online mode.
*/
public class BungeeListener implements PluginMessageListener {
public class ProxyListener implements PluginMessageListener {
private final FastLoginBukkit plugin;
private final ProxyVerifier verifier;
public BungeeListener(FastLoginBukkit plugin) {
public ProxyListener(FastLoginBukkit plugin, ProxyVerifier verifier) {
this.plugin = plugin;
this.verifier = verifier;
}
@Override
@@ -79,7 +81,7 @@ public class BungeeListener implements PluginMessageListener {
plugin.getLog().warn("Received message {} from a blocked player {}", loginMessage, targetPlayer);
} else {
UUID sourceId = loginMessage.getProxyId();
if (plugin.getBungeeManager().isProxyAllowed(sourceId)) {
if (verifier.isProxyAllowed(sourceId)) {
readMessage(targetPlayer, loginMessage);
} else {
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy file", sourceId);
@@ -127,7 +129,7 @@ public class BungeeListener implements PluginMessageListener {
plugin.putSession(player.spigot().getRawAddress(), session);
// only start a new login task if the join event fired earlier. This event then didn't
boolean result = plugin.getBungeeManager().didJoinEventFired(player);
boolean result = verifier.didJoinEventFired(player);
plugin.getLog().info("Delaying force login until join event fired?: {}", result);
if (result) {
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player, session);

View File

@@ -0,0 +1,135 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2024 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.auth.proxy;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import java.io.IOException;
import java.nio.file.Files;
import java.nio.file.Path;
import java.util.Collection;
import java.util.Collections;
import java.util.HashSet;
import java.util.Set;
import java.util.UUID;
import java.util.stream.Stream;
import static java.util.stream.Collectors.toSet;
public class ProxyVerifier {
private static final String LEGACY_FILE_NAME = "proxy-whitelist.txt";
private static final String FILE_NAME = "allowed-proxies.txt";
//null if proxies allowed list is empty so bungeecord support is disabled
private Set<UUID> proxyIds;
private final FastLoginBukkit plugin;
private final Collection<UUID> firedJoinEvents = new HashSet<>();
public ProxyVerifier(FastLoginBukkit plugin) {
this.plugin = plugin;
}
public void cleanup() {
//remove old blocked status
Bukkit.getOnlinePlayers().forEach(player -> player.removeMetadata(plugin.getName(), plugin));
}
public void loadSecrets() {
proxyIds = loadBungeeCordIds();
if (proxyIds.isEmpty()) {
plugin.getLog().info("No valid IDs found. Minecraft proxy support cannot work in the current state");
}
}
private Set<UUID> loadBungeeCordIds() {
Path proxiesFile = plugin.getPluginFolder().resolve(FILE_NAME);
Path legacyFile = plugin.getPluginFolder().resolve(LEGACY_FILE_NAME);
try {
if (Files.notExists(proxiesFile)) {
if (Files.exists(legacyFile)) {
Files.move(legacyFile, proxiesFile);
}
if (Files.notExists(legacyFile)) {
Files.createFile(proxiesFile);
}
}
Files.deleteIfExists(legacyFile);
try (Stream<String> lines = Files.lines(proxiesFile)) {
return lines.map(String::trim).map(UUID::fromString).collect(toSet());
}
} catch (IOException ex) {
plugin.getLog().error("Failed to read proxies", ex);
} catch (Exception ex) {
plugin.getLog().error("Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
}
return Collections.emptySet();
}
public boolean isProxyAllowed(UUID proxyId) {
return proxyIds != null && proxyIds.contains(proxyId);
}
/**
* Mark the event to be fired including the task delay.
*
* @param player joining player
*/
public synchronized void markJoinEventFired(Player player) {
firedJoinEvents.add(player.getUniqueId());
}
/**
* Check if the event fired including with the task delay. This necessary to restore the order of processing the
* BungeeCord messages after the PlayerJoinEvent fires including the delay.
* <p>
* If the join event fired, the delay exceeded, but it ran earlier and couldn't find the recently started login
* session. If not fired, we can start a new force login task. This will still match the requirement that we wait
* a certain time after the player join event fired.
*
* @param player joining player
* @return event fired including delay
*/
public synchronized boolean didJoinEventFired(Player player) {
return firedJoinEvents.contains(player.getUniqueId());
}
/**
* Player quit clean up
*
* @param player joining player
*/
public synchronized void cleanup(Player player) {
firedJoinEvents.remove(player.getUniqueId());
}
}

View File

@@ -64,18 +64,18 @@ public class PremiumCommand extends ToggleCommand {
return;
}
UUID id = ((Player) sender).getUniqueId();
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getPendingConfirms().contains(id)) {
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
plugin.getPendingConfirms().add(id);
return;
}
if (forwardPremiumCommand(sender, sender.getName())) {
return;
}
UUID id = ((Player) sender).getUniqueId();
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getCore().getPendingConfirms().contains(id)) {
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
plugin.getCore().getPendingConfirms().add(id);
return;
}
plugin.getCore().getPendingConfirms().remove(id);
plugin.getPendingConfirms().remove(id);
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isOnlinemodePreferred()) {

View File

@@ -26,6 +26,7 @@
package com.github.games647.fastlogin.bukkit.command;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.auth.proxy.ProxyAuthentication;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import org.bukkit.Bukkit;
@@ -55,7 +56,7 @@ public abstract class ToggleCommand implements CommandExecutor {
}
protected boolean forwardBungeeCommand(CommandSender sender, String target, boolean activate) {
if (plugin.getBungeeManager().isEnabled()) {
if (plugin.getBackend() instanceof ProxyAuthentication) {
sendBungeeActivateMessage(sender, target, activate);
plugin.getCore().sendLocaleMessage("wait-on-proxy", sender);
return true;

View File

@@ -23,15 +23,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.task;
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.hook.AuthMeHook;
import com.github.games647.fastlogin.bukkit.hook.CrazyLoginHook;
import com.github.games647.fastlogin.bukkit.hook.LogItHook;
import com.github.games647.fastlogin.bukkit.hook.LoginSecurityHook;
import com.github.games647.fastlogin.bukkit.hook.UltraAuthHook;
import com.github.games647.fastlogin.bukkit.hook.XAuthHook;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
@@ -51,19 +45,7 @@ public class DelayedAuthHook implements Runnable {
@Override
public void run() {
boolean hookFound = isHookFound();
if (plugin.getBungeeManager().isEnabled()) {
plugin.getLog().info("BungeeCord setting detected. No auth plugin is required");
} else if (!hookFound) {
plugin.getLog().warn("No auth plugin were found by this plugin "
+ "(other plugins could hook into this after the initialization of this plugin)"
+ "and BungeeCord is deactivated. "
+ "Either one or both of the checks have to pass in order to use this plugin");
}
if (hookFound) {
plugin.markInitialized();
}
plugin.setInitialized(isHookFound());
}
private boolean isHookFound() {

View File

@@ -1,139 +0,0 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2024 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.antibot.AntiBotService;
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import protocolsupport.api.events.ConnectionCloseEvent;
import protocolsupport.api.events.PlayerLoginStartEvent;
import protocolsupport.api.events.PlayerProfileCompleteEvent;
import java.net.InetSocketAddress;
import java.util.Optional;
public class ProtocolSupportListener extends JoinManagement<Player, CommandSender, ProtocolLoginSource>
implements Listener {
private final FastLoginBukkit plugin;
private final AntiBotService antiBotService;
public ProtocolSupportListener(FastLoginBukkit plugin, AntiBotService antiBotService) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook(), plugin.getBedrockService());
this.plugin = plugin;
this.antiBotService = antiBotService;
}
@EventHandler
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
return;
}
String username = loginStartEvent.getConnection().getProfile().getName();
InetSocketAddress address = loginStartEvent.getConnection().getRawAddress();
plugin.getLog().info("Incoming login request for {} from {}", username, address);
Action action = antiBotService.onIncomingConnection(address, username);
switch (action) {
case Ignore:
// just ignore
return;
case Block:
String message = plugin.getCore().getMessage("kick-antibot");
loginStartEvent.denyLogin(message);
break;
case Continue:
default:
//remove old data every time on a new login in order to keep the session only for one person
plugin.removeSession(address);
ProtocolLoginSource source = new ProtocolLoginSource(loginStartEvent);
super.onLogin(username, source);
break;
}
}
@EventHandler
public void onConnectionClosed(ConnectionCloseEvent closeEvent) {
InetSocketAddress address = closeEvent.getConnection().getRawAddress();
plugin.removeSession(address);
}
@EventHandler
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
InetSocketAddress address = profileCompleteEvent.getConnection().getRawAddress();
BukkitLoginSession session = plugin.getSession(address);
if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) {
session.setVerifiedPremium(true);
if (!plugin.getConfig().getBoolean("premiumUuid")) {
String username = Optional.ofNullable(profileCompleteEvent.getForcedName())
.orElse(profileCompleteEvent.getConnection().getProfile().getName());
profileCompleteEvent.setForcedUUID(UUIDAdapter.generateOfflineId(username));
}
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLoginSource source,
StoredProfile profile) {
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
plugin.getServer().getPluginManager().callEvent(event);
return event;
}
@Override
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username,
boolean registered) {
source.enableOnlinemode();
String ip = source.getAddress().getAddress().getHostAddress();
plugin.getCore().addLoginAttempt(ip, username);
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
plugin.putSession(source.getAddress(), playerSession);
}
@Override
public void startCrackedSession(ProtocolLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.putSession(source.getAddress(), loginSession);
}
}

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.google.gson.TypeAdapter;
import com.google.gson.stream.JsonReader;

View File

@@ -23,10 +23,10 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.github.games647.fastlogin.bukkit.listener.protocollib.SignatureTestData.SignatureData;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.auth.protocollib.SignatureTestData.SignatureData;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import com.google.common.hash.Hashing;
import lombok.val;
import org.junit.jupiter.api.Test;
@@ -60,7 +60,7 @@ class EncryptionUtilTest {
@Test
void testVerifyToken() {
val random = ThreadLocalRandom.current();
@SuppressWarnings("SharedThreadLocalRandom") val random = ThreadLocalRandom.current();
byte[] token = EncryptionUtil.generateVerifyToken(random);
assertAll(
@@ -88,7 +88,7 @@ class EncryptionUtilTest {
@Test
void testExpiredClientKey() throws Exception {
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
// Client expires at the exact second mentioned, so use it for verification
val expiredTimestamp = clientKey.expiry();
@@ -105,7 +105,7 @@ class EncryptionUtilTest {
"client_keys/invalid_wrong_signature.json"
})
void testInvalidClientKey(String clientKeySource) throws Exception {
val clientKey = ResourceLoader.loadClientKey(clientKeySource);
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey(clientKeySource);
Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp, null));
@@ -113,7 +113,7 @@ class EncryptionUtilTest {
@Test
void testValidClientKey() throws Exception {
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, null));
@@ -121,7 +121,7 @@ class EncryptionUtilTest {
@Test
void testValid191ClientKey() throws Exception {
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
val ownerPremiumId = UUID.fromString("0aaa2c13-922a-411b-b655-9b8c08404695");
@@ -130,7 +130,7 @@ class EncryptionUtilTest {
@Test
void testIncorrect191ClientOwner() throws Exception {
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
val clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
val ownerPremiumId = UUID.fromString("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6");
@@ -170,7 +170,7 @@ class EncryptionUtilTest {
void testServerIdHash() throws Exception {
val serverId = "";
val sharedSecret = generateSharedKey();
val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
val serverPK = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
assertEquals(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), sessionHash);
@@ -200,7 +200,7 @@ class EncryptionUtilTest {
void testServerIdHashWrongSecret() throws Exception {
val serverId = "";
val sharedSecret = generateSharedKey();
val serverPK = ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
val serverPK = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json").key();
String sessionHash = getServerHash(serverId, sharedSecret, serverPK);
assertNotEquals(EncryptionUtil.getServerIdHashString("", generateSharedKey(), serverPK), sessionHash);
@@ -219,7 +219,7 @@ class EncryptionUtilTest {
@Test
void testValidSignedNonce() throws Exception {
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
ClientPublicKey clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json");
assertTrue(verifySignedNonce(testData, clientKey));
}
@@ -231,7 +231,7 @@ class EncryptionUtilTest {
"signature/incorrect_signature.json",
})
void testIncorrectNonce(String signatureSource) throws Exception {
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
ClientPublicKey clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
SignatureTestData testData = SignatureTestData.fromResource(signatureSource);
assertFalse(verifySignedNonce(testData, clientKey));
}
@@ -239,7 +239,7 @@ class EncryptionUtilTest {
@Test
void testWrongPublicKeySigned() throws Exception {
// load a different public key
ClientPublicKey clientKey = ResourceLoader.loadClientKey("client_keys/invalid_wrong_key.json");
ClientPublicKey clientKey = com.github.games647.fastlogin.bukkit.auth.protocollib.ResourceLoader.loadClientKey("client_keys/invalid_wrong_key.json");
SignatureTestData testData = SignatureTestData.fromResource("signature/valid_signature.json");
assertFalse(verifySignedNonce(testData, clientKey));
}

View File

@@ -23,9 +23,9 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
import com.github.games647.fastlogin.bukkit.auth.protocollib.packet.ClientPublicKey;
import com.google.common.io.Resources;
import com.google.gson.Gson;
import com.google.gson.JsonObject;
@@ -50,6 +50,10 @@ import java.util.Base64;
public class ResourceLoader {
private ResourceLoader() {
// Utility
}
public static RSAPrivateKey parsePrivateKey(String keySpec)
throws IOException, NoSuchAlgorithmException, InvalidKeySpecException {
try (

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.google.common.io.Resources;
import com.google.gson.Gson;

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
package com.github.games647.fastlogin.bukkit.auth.protocollib;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.reflect.accessors.Accessors;

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.bukkit.task;
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import lombok.val;

View File

@@ -233,9 +233,6 @@ public class ConnectListener implements Listener {
return;
}
// delay sending force command, because Paper will process the login event asynchronously
// In this case it means that the force command (plugin message) is already received and processed while
// player is still in the login phase and reported to be offline.
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
plugin.getScheduler().runAsync(loginTask);
}
@@ -244,6 +241,5 @@ public class ConnectListener implements Listener {
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
ProxiedPlayer player = disconnectEvent.getPlayer();
plugin.getSession().remove(player.getPendingConnection());
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
}
}

View File

@@ -37,7 +37,6 @@ import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PluginMessageEvent;
@@ -96,15 +95,6 @@ public class PluginMessageListener implements Listener {
String playerName = changeMessage.getPlayerName();
boolean isSourceInvoker = changeMessage.isSourceInvoker();
if (changeMessage.shouldEnable()) {
if (playerName.equals(forPlayer.getName()) && plugin.getCore().getConfig().get("premium-warning", true)
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
String message = core.getMessage("premium-warning");
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
core.getPendingConfirms().add(forPlayer.getUniqueId());
return;
}
core.getPendingConfirms().remove(forPlayer.getUniqueId());
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
plugin.getScheduler().runAsync(task);
} else {

View File

@@ -125,7 +125,7 @@
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>4.0.3</version>
<version>5.1.0</version>
<exclusions>
<!-- HikariCP uses an old version of this API that has a typo in the service interface -->
<!-- We will use the api provided by the jdk14 dependency -->
@@ -203,7 +203,7 @@
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>craftapi</artifactId>
<version>0.8.1</version>
<version>1.0-SNAPSHOT</version>
</dependency>
<!-- Database driver included in Spigot -->

View File

@@ -29,8 +29,6 @@ import com.github.games647.craftapi.model.auth.Verification;
import com.github.games647.craftapi.resolver.MojangResolver;
import java.io.IOException;
import java.io.InputStream;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.util.Optional;
@@ -44,38 +42,9 @@ import java.util.Optional;
*/
public class ProxyAgnosticMojangResolver extends MojangResolver {
private static final String HOST = "sessionserver.mojang.com";
/**
* A formatting string containing a URL used to call the {@code hasJoined} method on mojang session servers.
* <p>
* Formatting parameters:
* 1. The username of the player in question
* 2. The serverId of this server
*/
public static final String ENDPOINT = "https://" + HOST + "/session/minecraft/hasJoined?username=%s&serverId=%s";
@Override
public Optional<Verification> hasJoined(String username, String serverHash, InetAddress hostIp)
throws IOException {
String url = String.format(ENDPOINT, username, serverHash);
HttpURLConnection conn = this.getConnection(url);
int responseCode = conn.getResponseCode();
Verification verification = null;
// Mojang session servers send HTTP 204 (NO CONTENT) when the authentication seems invalid
// If that's not our case, the authentication is valid, and so we can parse the response.
if (responseCode != HttpURLConnection.HTTP_NO_CONTENT) {
verification = this.parseRequest(conn, this::parseVerification);
}
return Optional.ofNullable(verification);
}
// Functional implementation of InputStreamAction, used in hasJoined method in parseRequest call
protected Verification parseVerification(InputStream input) throws IOException {
return this.readJson(input, Verification.class);
return super.hasJoined(username, serverHash, null);
}
}

View File

@@ -61,7 +61,6 @@ import java.util.HashSet;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -83,7 +82,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
Duration.ofMinutes(5), -1
);
private final Collection<UUID> pendingConfirms = new HashSet<>();
private final T plugin;
private MojangResolver resolver;
@@ -123,7 +121,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
// Initialize the resolver based on the config parameter
this.resolver = this.config.getBoolean("useProxyAgnosticResolver", false)
? new ProxyAgnosticMojangResolver() : new MojangResolver();
? new ProxyAgnosticMojangResolver() : new MojangResolver();
antiBot = createAntiBotService(config.getSection("anti-bot"));
Set<Proxy> proxies = config.getStringList("proxies")
@@ -144,7 +142,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
resolver.setMaxNameRequests(config.getInt("mojang-request-limit"));
resolver.setProxySelector(new RotatingProxySelector(proxies));
resolver.setOutgoingAddresses(addresses);
}
private AntiBotService createAntiBotService(Configuration botSection) {
@@ -164,12 +161,12 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
Action action = Action.Ignore;
switch (botSection.getString("action", "ignore")) {
case "ignore":
action = Action.Ignore;
break;
case "block":
action = Action.Block;
break;
case "ignore":
action = Action.Ignore;
break;
default:
plugin.getLog().warn("Invalid anti bot action - defaulting to ignore");
}
@@ -288,10 +285,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
return pendingLogin.remove(ip + username) != null;
}
public Collection<UUID> getPendingConfirms() {
return pendingConfirms;
}
public AuthPlugin<P> getAuthPluginHook() {
return authPlugin;
}

View File

@@ -109,7 +109,7 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
premiumUUID = core.getResolver().findProfile(username);
}
if (!premiumUUID.isPresent()
if (premiumUUID.isPresent()
|| (!isNameChanged(source, username, premiumUUID.get())
&& !isUsernameAvailable(source, username, profile))) {
//nothing detected the player as premium -> start a cracked session

View File

@@ -29,7 +29,7 @@ import java.net.InetSocketAddress;
public interface LoginSource {
void enableOnlinemode() throws Exception;
void enableOnlinemode();
void kick(String message);

View File

@@ -32,9 +32,12 @@ import org.slf4j.Logger;
import java.nio.file.Path;
import java.util.concurrent.ThreadFactory;
import java.util.regex.Pattern;
public interface PlatformPlugin<C> {
Pattern PATTERN = Pattern.compile("%nl%");
String getName();
Path getPluginFolder();
@@ -48,7 +51,7 @@ public interface PlatformPlugin<C> {
boolean isPluginInstalled(String name);
default void sendMultiLineMessage(C receiver, String message) {
for (String line : message.split("%nl%")) {
for (String line : PATTERN.split(message)) {
sendMessage(receiver, line);
}
}

View File

@@ -163,17 +163,16 @@ public class StoredProfile extends Profile {
}
@Override
public synchronized boolean equals(Object o) {
if (this == o) {
public synchronized boolean equals(Object other) {
if (this == other) {
return true;
}
if (!(o instanceof StoredProfile)) {
if (!(other instanceof StoredProfile)) {
return false;
}
StoredProfile that = (StoredProfile) o;
if (!super.equals(o)) {
StoredProfile that = (StoredProfile) other;
if (!super.equals(other)) {
return false;
}

View File

@@ -173,22 +173,6 @@ premium-warning: true
# every environment is different, and so it might not work for you as it did for me.
useProxyAgnosticResolver: false
# If you have autoRegister or nameChangeCheck enabled, you could be rate-limited by Mojang.
# The requests of the both options will be only made by FastLogin if the username is unknown to the server
# You are allowed to make 600 requests per 10-minutes (60 per minute)
# If you own a big server this value could be too low
# Once the limit is reached, new players are always logged in as cracked until the rate-limit is expired.
# (to the next ten minutes)
#
# The limit is IP-wide. If you have multiple IPv4-addresses you specify them here. FastLogin will then use it in
# rotating order --> 5 different IP-addresses 5 * 600 per 10 minutes
# If this list is empty only the default one will be used
#
# Lists are created like this:
#ip-addresses:
# - 192-168-0-2
ip-addresses: []
# How many requests should be established to the Mojang API for Name -> UUID requests. Some other plugins as well
# as the head Minecraft block make such requests as well. Using this option you can limit the amount requests this
# plugin should make.

View File

@@ -27,13 +27,13 @@ package com.github.games647.fastlogin.core.hooks;
import org.junit.jupiter.api.Test;
import static org.junit.jupiter.api.Assertions.assertTrue;
import static org.junit.jupiter.api.Assertions.assertEquals;
class DefaultPasswordGeneratorTest {
@Test
void smokeTestPassword() {
PasswordGenerator<?> passwordGenerator = new DefaultPasswordGenerator<>();
assertTrue(passwordGenerator.getRandomPassword(null).length() == 8);
assertEquals(8, passwordGenerator.getRandomPassword(null).length());
}
}

View File

@@ -48,7 +48,7 @@
<!-- Set default for non-git clones -->
<git.commit.id>Unknown</git.commit.id>
<maven.compiler.release>8</maven.compiler.release>
<maven.compiler.release>11</maven.compiler.release>
<lombook.version>1.18.32</lombook.version>

View File

@@ -27,6 +27,7 @@ package com.github.games647.fastlogin.velocity;
import com.github.games647.fastlogin.core.shared.LoginSource;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.proxy.InboundConnection;
import net.kyori.adventure.text.Component;
import net.kyori.adventure.text.format.NamedTextColor;
@@ -46,17 +47,17 @@ public class VelocityLoginSource implements LoginSource {
@Override
public void enableOnlinemode() {
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.forceOnlineMode());
preLoginEvent.setResult(PreLoginComponentResult.forceOnlineMode());
}
@Override
public void kick(String message) {
if (message == null) {
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
preLoginEvent.setResult(PreLoginComponentResult.denied(
Component.text("Kicked").color(NamedTextColor.WHITE))
);
} else {
preLoginEvent.setResult(PreLoginEvent.PreLoginComponentResult.denied(
preLoginEvent.setResult(PreLoginComponentResult.denied(
LegacyComponentSerializer.legacyAmpersand().deserialize(message))
);
}

View File

@@ -29,11 +29,12 @@ import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.storage.StoredProfile;
import com.velocitypowered.api.event.ResultedEvent;
import com.velocitypowered.api.event.ResultedEvent.GenericResult;
import java.util.Objects;
public class VelocityFastLoginAutoLoginEvent
implements FastLoginAutoLoginEvent, ResultedEvent<ResultedEvent.GenericResult> {
implements FastLoginAutoLoginEvent, ResultedEvent<GenericResult> {
private final LoginSession session;
private final StoredProfile profile;

View File

@@ -38,7 +38,6 @@ import com.github.games647.fastlogin.velocity.task.FloodgateAuthTask;
import com.github.games647.fastlogin.velocity.task.ForceLoginTask;
import com.velocitypowered.api.event.EventTask;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.DisconnectEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent;
import com.velocitypowered.api.event.connection.PreLoginEvent.PreLoginComponentResult;
import com.velocitypowered.api.event.player.GameProfileRequestEvent;
@@ -46,14 +45,12 @@ import com.velocitypowered.api.event.player.ServerConnectedEvent;
import com.velocitypowered.api.proxy.InboundConnection;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import com.velocitypowered.api.util.GameProfile;
import com.velocitypowered.api.util.GameProfile.Property;
import net.kyori.adventure.text.TextComponent;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import java.net.InetSocketAddress;
import java.time.Duration;
import java.util.ArrayList;
import java.util.Collection;
import java.util.List;
@@ -130,9 +127,9 @@ public class ConnectListener {
}
}
private List<GameProfile.Property> removeSkin(Collection<Property> oldProperties) {
List<GameProfile.Property> newProperties = new ArrayList<>(oldProperties.size());
for (GameProfile.Property property : oldProperties) {
private List<Property> removeSkin(Collection<Property> oldProperties) {
List<Property> newProperties = new ArrayList<>(oldProperties.size());
for (Property property : oldProperties) {
if (!"textures".equals(property.getName())) {
newProperties.add(property);
}
@@ -169,12 +166,6 @@ public class ConnectListener {
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server, session);
// Delay at least one second, otherwise the login command can be missed
plugin.getScheduler().runAsyncDelayed(loginTask, Duration.ofSeconds(1));
}
@Subscribe
public void onDisconnect(DisconnectEvent disconnectEvent) {
Player player = disconnectEvent.getPlayer();
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
plugin.getScheduler().runAsync(loginTask);
}
}

View File

@@ -38,10 +38,10 @@ import com.google.common.io.ByteStreams;
import com.velocitypowered.api.command.CommandSource;
import com.velocitypowered.api.event.Subscribe;
import com.velocitypowered.api.event.connection.PluginMessageEvent;
import com.velocitypowered.api.event.connection.PluginMessageEvent.ForwardResult;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ServerConnection;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import net.kyori.adventure.text.serializer.legacy.LegacyComponentSerializer;
import java.util.Arrays;
@@ -69,7 +69,7 @@ public class PluginMessageListener {
//the client shouldn't be able to read the messages in order to know something about server internal states
//moreover the client shouldn't be able to fake a running premium check by sending the result message
pluginMessageEvent.setResult(PluginMessageEvent.ForwardResult.handled());
pluginMessageEvent.setResult(ForwardResult.handled());
if (!(pluginMessageEvent.getSource() instanceof ServerConnection)) {
//check if the message is sent from the server
@@ -96,16 +96,6 @@ public class PluginMessageListener {
String playerName = changeMessage.getPlayerName();
boolean isSourceInvoker = changeMessage.isSourceInvoker();
if (changeMessage.shouldEnable()) {
Boolean premiumWarning = plugin.getCore().getConfig().get("premium-warning", true);
if (playerName.equals(forPlayer.getUsername()) && premiumWarning
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
String message = core.getMessage("premium-warning");
forPlayer.sendMessage(LegacyComponentSerializer.legacyAmpersand().deserialize(message));
core.getPendingConfirms().add(forPlayer.getUniqueId());
return;
}
core.getPendingConfirms().remove(forPlayer.getUniqueId());
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
plugin.getScheduler().runAsync(task);
} else {

View File

@@ -55,8 +55,8 @@ public class FloodgateAuthTask
core.getPlugin().getSession().put(player.getRemoteAddress(), session);
// enable auto login based on the value of 'autoLoginFloodgate' in config.yml
boolean forcedOnlineMode = autoLoginFloodgate.equals("true")
|| (autoLoginFloodgate.equals("linked") && isLinked);
boolean forcedOnlineMode = "true".equals(autoLoginFloodgate)
|| ("linked".equals(autoLoginFloodgate) && isLinked);
// delay sending force command, because Paper will process the login event asynchronously
// In this case it means that the force command (plugin message) is already received and processed while