/* * SPDX-License-Identifier: MIT * * The MIT License (MIT) * * Copyright (c) 2015-2021 * * 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.bungee.listener; import com.github.games647.craftapi.UUIDAdapter; import com.github.games647.fastlogin.bungee.BungeeLoginSession; import com.github.games647.fastlogin.bungee.FastLoginBungee; import com.github.games647.fastlogin.bungee.task.AsyncPremiumCheck; import com.github.games647.fastlogin.bungee.task.FloodgateAuthTask; import com.github.games647.fastlogin.bungee.task.ForceLoginTask; import com.github.games647.fastlogin.core.RateLimiter; import com.github.games647.fastlogin.core.StoredProfile; import com.github.games647.fastlogin.core.shared.LoginSession; import com.google.common.base.Throwables; import java.lang.invoke.MethodHandle; import java.lang.invoke.MethodHandles; import java.lang.invoke.MethodHandles.Lookup; import java.lang.reflect.Field; import java.nio.charset.StandardCharsets; import java.util.UUID; import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; import net.md_5.bungee.api.event.LoginEvent; import net.md_5.bungee.api.event.PlayerDisconnectEvent; import net.md_5.bungee.api.event.PreLoginEvent; import net.md_5.bungee.api.event.ServerConnectedEvent; import net.md_5.bungee.api.plugin.Listener; import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.connection.LoginResult; import net.md_5.bungee.connection.LoginResult.Property; import net.md_5.bungee.event.EventHandler; import net.md_5.bungee.event.EventPriority; import org.geysermc.floodgate.api.FloodgateApi; import org.geysermc.floodgate.api.player.FloodgatePlayer; import org.slf4j.Logger; import org.slf4j.LoggerFactory; /** * Enables online mode logins for specified users and sends plugin message to the Bukkit version of this plugin in * order to clear that the connection is online mode. */ public class ConnectListener implements Listener { private static final String UUID_FIELD_NAME = "uniqueId"; private static final MethodHandle uniqueIdSetter; static { MethodHandle setHandle = null; try { Lookup lookup = MethodHandles.lookup(); Class.forName("net.md_5.bungee.connection.InitialHandler"); Field uuidField = InitialHandler.class.getDeclaredField(UUID_FIELD_NAME); uuidField.setAccessible(true); setHandle = lookup.unreflectSetter(uuidField); } catch (ClassNotFoundException classNotFoundException) { Logger logger = LoggerFactory.getLogger(ConnectListener.class); logger.error( "Cannot find Bungee initial handler; Disabling premium UUID and skin won't work.", classNotFoundException ); } catch (ReflectiveOperationException reflectiveOperationException) { reflectiveOperationException.printStackTrace(); } uniqueIdSetter = setHandle; } private final FastLoginBungee plugin; private final RateLimiter rateLimiter; private final Property[] emptyProperties = {}; public ConnectListener(FastLoginBungee plugin, RateLimiter rateLimiter) { this.plugin = plugin; this.rateLimiter = rateLimiter; } @EventHandler public void onPreLogin(PreLoginEvent preLoginEvent) { PendingConnection connection = preLoginEvent.getConnection(); if (preLoginEvent.isCancelled()) { return; } if (!rateLimiter.tryAcquire()) { plugin.getLog().warn("Simple Anti-Bot join limit - Ignoring {}", connection); return; } String username = connection.getName(); plugin.getLog().info("Incoming login request for {} from {}", username, connection.getSocketAddress()); preLoginEvent.registerIntent(plugin); Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection, username); plugin.getScheduler().runAsync(asyncPremiumCheck); } @EventHandler(priority = EventPriority.LOWEST) public void onLogin(LoginEvent loginEvent) { if (loginEvent.isCancelled()) { return; } //use the login event instead of the post login event in order to send the login success packet to the client //with the offline uuid this makes it possible to set the skin then PendingConnection connection = loginEvent.getConnection(); if (connection.isOnlineMode()) { LoginSession session = plugin.getSession().get(connection); UUID verifiedUUID = connection.getUniqueId(); String verifiedUsername = connection.getName(); session.setUuid(verifiedUUID); session.setVerifiedUsername(verifiedUsername); StoredProfile playerProfile = session.getProfile(); playerProfile.setId(verifiedUUID); // bungeecord will do this automatically so override it on disabled option if (uniqueIdSetter != null) { InitialHandler initialHandler = (InitialHandler) connection; if (!plugin.getCore().getConfig().get("premiumUuid", true)) { setOfflineId(initialHandler, verifiedUsername); } if (!plugin.getCore().getConfig().get("forwardSkin", true)) { // this is null on offline mode LoginResult loginProfile = initialHandler.getLoginProfile(); loginProfile.setProperties(emptyProperties); } } } // LimeLogin start //start of Govindas code to auto-add "-" character for offline mode users differentiation from online mode else { if (!(connection.getName().charAt(0) == '-')) { InitialHandler initialHandler = (InitialHandler) loginEvent.getConnection(); //TODO add bedrock check of player and don't trigger this for bedrock users LoginSession session = plugin.getSession().get(connection); String username = connection.getName(); //for bedrock support, this opens up vulnerability, so cracked players with * can join without auth //but that isn't a problem as we're handling * in LimeworkProxy instead //but it would be better to handle it here, just need to debug it further //debug, we can remove after UUID checkUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + connection.getName()).getBytes(StandardCharsets.UTF_8)); //System.out.println("Profile uuid: " + session.getProfile().getId()); //System.out.println("Session uuid: " + session.getUuid()); //System.out.println("Offline uuid: " + checkUUID); if (username.charAt(0)== '*') { if (checkUUID.equals(session.getProfile().getId())) { System.out.println("IT EQUALS!"); } return; } if (username.length() >= 16) { username = username.substring(0, 15); } session.setVerifiedUsername("-" + username); initialHandler.getLoginRequest().setData("-" + username); UUID uuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + connection.getName()).getBytes(StandardCharsets.UTF_8)); plugin.getLog().info("Govindas system has converted username to contain - character: " + username); try { uniqueIdSetter.invokeExact(initialHandler, uuid); } catch (Throwable throwable) { throwable.printStackTrace(); } session.getProfile().setPremium(false); session.getProfile().setPlayerName(connection.getName()); session.setUuid(uuid); session.getProfile().setId(uuid); } } //end of Govindas code // LimeLogin end } private void setOfflineId(InitialHandler connection, String username) { try { final UUID oldPremiumId = connection.getUniqueId(); final UUID offlineUUID = UUIDAdapter.generateOfflineId(username); // BungeeCord only allows setting the UUID in PreLogin events and before requesting online mode // However if online mode is requested, it will override previous values // So we have to do it with reflection uniqueIdSetter.invokeExact(connection, offlineUUID); String format = "Overridden UUID from {} to {} (based of {}) on {}"; plugin.getLog().info(format, oldPremiumId, offlineUUID, username, connection); } catch (Exception ex) { plugin.getLog().error("Failed to set offline uuid of {}", username, ex); } catch (Throwable throwable) { // throw remaining exceptions like "out of memory" that we shouldn't handle our self's Throwables.throwIfUnchecked(throwable); } } @EventHandler public void onServerConnected(ServerConnectedEvent serverConnectedEvent) { ProxiedPlayer player = serverConnectedEvent.getPlayer(); Server server = serverConnectedEvent.getServer(); if (plugin.isPluginInstalled("floodgate")) { FloodgatePlayer floodgatePlayer = FloodgateApi.getInstance().getPlayer(player.getUniqueId()); if (floodgatePlayer != null) { Runnable floodgateAuthTask = new FloodgateAuthTask(plugin.getCore(), player, floodgatePlayer, server); plugin.getScheduler().runAsync(floodgateAuthTask); return; } } BungeeLoginSession session = plugin.getSession().get(player.getPendingConnection()); if (session == null) { 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); } @EventHandler public void onDisconnect(PlayerDisconnectEvent disconnectEvent) { ProxiedPlayer player = disconnectEvent.getPlayer(); plugin.getSession().remove(player.getPendingConnection()); plugin.getCore().getPendingConfirms().remove(player.getUniqueId()); } }