Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
at develop 321 lines 14 kB view raw
1package com.projectposeidon.johnymuffin; 2 3import com.legacyminecraft.poseidon.PoseidonConfig; 4import com.legacyminecraft.poseidon.PoseidonPlugin; 5import com.legacyminecraft.poseidon.util.CrackedAllowlist; 6import com.legacyminecraft.poseidon.uuid.ThreadUUIDFetcher; 7import net.minecraft.server.NetLoginHandler; 8import net.minecraft.server.Packet1Login; 9import net.minecraft.server.ThreadLoginVerifier; 10import org.bukkit.Bukkit; 11import org.bukkit.craftbukkit.CraftServer; 12import org.bukkit.entity.Player; 13import org.bukkit.event.player.PlayerConnectionInitializationEvent; 14import org.bukkit.event.player.PlayerPreLoginEvent; 15import org.bukkit.plugin.Plugin; 16 17import java.net.InetSocketAddress; 18import java.util.ArrayList; 19import java.util.HashSet; 20import java.util.UUID; 21 22public class LoginProcessHandler { 23 24 private NetLoginHandler netLoginHandler; 25 private final Packet1Login packet1Login; 26 private CraftServer server; 27 private boolean onlineMode; 28 private boolean loginCancelled = false; 29 private LoginProcessHandler loginProcessHandler; 30 private boolean loginSuccessful = false; 31 private long startTime; 32 33 private HashSet<ConnectionPause> connectionPauses = new HashSet<ConnectionPause>(); 34 35 private final String msgKickAlreadyOnline; 36 37 public LoginProcessHandler(NetLoginHandler netloginhandler, Packet1Login packet1login, CraftServer server, boolean onlineMode) { 38 this.loginProcessHandler = this; 39 this.netLoginHandler = netloginhandler; 40 this.packet1Login = packet1login; 41 this.server = server; 42 this.onlineMode = onlineMode; 43 44 this.msgKickAlreadyOnline = PoseidonConfig.getInstance().getConfigString("message.kick.already-online"); 45 46 processAuthentication(); 47 48 long connectionStartTime = System.currentTimeMillis() / 1000L; 49 runLoginTimer(connectionStartTime); 50 51 } 52 53 private void runLoginTimer(long connectionStartTime) { 54 Bukkit.getScheduler().scheduleAsyncDelayedTask(new PoseidonPlugin(), () -> { 55 int currentRunningTime = (int) (System.currentTimeMillis() / 1000L - connectionStartTime); 56 if (!loginSuccessful && !loginCancelled) { 57 //This if statement shouldn't be needed, but this is here just in case a players login fails but the appropriate variables aren't changed 58 System.out.println("[Poseidon] The login process for " + packet1Login.name + " is still running. It has been running for " + currentRunningTime + " seconds. The following plugins are still currently pausing the login process: " + getConnectionPauseNames(true)); 59 60 //Cancel the login process if it has been running for more than 20 seconds. 61 if (currentRunningTime >= 20) { 62 cancelLoginProcess("Login Process Handler Timeout"); 63 System.out.println("[Poseidon] LoginProcessHandler for user " + packet1Login.name + " has failed to respond after 20 seconds. And future calls to this class will result in error"); 64 System.out.println("[Poseidon] Plugin Pauses: " + getConnectionPauseNames(true)); 65 } 66 67 if (currentRunningTime < 60) { 68 runLoginTimer(connectionStartTime); 69 } 70 } 71 }, 20 * 5); 72 } 73 74 private void processAuthentication() { 75 PlayerConnectionInitializationEvent event = new PlayerConnectionInitializationEvent(this.packet1Login.name, this.netLoginHandler.getSocket().getInetAddress(), loginProcessHandler); 76 this.server.getPluginManager().callEvent(event); 77 if (loginCancelled) { 78 return; 79 } 80 81 // Account for cracked allowlist 82 if (onlineMode && !CrackedAllowlist.get().contains(this.packet1Login.name)) { 83 //Server is running online mode 84 verifyMojangSession(); 85 } else { 86 //Server is not running online mode 87 getUserUUID(); 88 } 89 } 90 91 private void getUserUUID() { 92 //UUID uuid = UUIDManager.getInstance().getUUIDFromUsername(packet1Login.name, true); 93 long unixTime = (System.currentTimeMillis() / 1000L); 94 UUID uuid = UUIDManager.getInstance().getUUIDFromUsername(packet1Login.name, true, unixTime); 95 if (uuid == null) { 96 boolean useGetMethod = PoseidonConfig.getInstance().getString("settings.uuid-fetcher.method.value", "POST").equalsIgnoreCase("GET"); 97 (new ThreadUUIDFetcher(packet1Login, this, useGetMethod)).start(); 98 } else { 99 System.out.println("[Poseidon] Fetched UUID from Cache for " + packet1Login.name + " - " + uuid.toString()); 100 connectPlayer(uuid); 101 } 102 103 104 } 105 106 public synchronized void userUUIDReceived(UUID uuid, boolean onlineMode) { 107 if (!onlineMode) { 108 if (Boolean.valueOf(String.valueOf(PoseidonConfig.getInstance().getConfigOption("settings.check-username-validity.enabled", true))) && !isUsernameValid()) { 109 //Username is invalid, and is a cracked user 110 return; 111 } 112 } 113 114 115 long unixTime = (System.currentTimeMillis() / 1000L) + 1382400; 116 UUIDManager.getInstance().receivedUUID(packet1Login.name, uuid, unixTime, onlineMode); 117 connectPlayer(uuid); 118 119 } 120 121 //This function is only run if the user is cracked 122 public boolean isUsernameValid() { 123 String username = this.packet1Login.name; 124 if (username.isEmpty()) { 125 cancelLoginProcess("Sorry, you don't have a username, messing with MC?????"); 126 return false; 127 } 128 String regex = String.valueOf(PoseidonConfig.getInstance().getConfigOption("settings.check-username-validity.regex", "[a-zA-Z0-9_?]*")); 129 int minimumLength = Integer.valueOf(String.valueOf(PoseidonConfig.getInstance().getConfigOption("settings.check-username-validity.min-length", 3))); 130 int maximumLength = Integer.valueOf(String.valueOf(PoseidonConfig.getInstance().getConfigOption("settings.check-username-validity.max-length", 16))); 131 132 if (username.length() > maximumLength) { 133 cancelLoginProcess("Sorry, your username is too long. The maximum length is: " + maximumLength); 134 return false; 135 } 136 if (username.length() < minimumLength) { 137 cancelLoginProcess("Sorry, your username is too short. The minimum length is: " + minimumLength); 138 return false; 139 } 140 if (!username.matches(regex)) { 141 cancelLoginProcess("Sorry, your username is invalid, allowed characters: " + regex); 142 return false; 143 } 144 145 return true; 146 } 147 148 149 private void verifyMojangSession() { 150 if (!loginSuccessful & !loginCancelled) { 151 (new ThreadLoginVerifier(this, netLoginHandler, this.packet1Login, this.server)).start(); // CraftBukkit 152 } 153 } 154 155 public synchronized void userMojangSessionVerified() { 156 if (!loginSuccessful & !loginCancelled) { 157 getUserUUID(); 158 } 159 } 160 161 private void connectPlayer(UUID uuid) { 162 String username = packet1Login.name; 163 //Check if a player with the same UUID or Username is already online which is mainly an issue in Offline Mode servers. 164 for (Player p : server.getOnlinePlayers()) { 165 if (p.getName().equalsIgnoreCase(username) || p.getUniqueId().equals(uuid)) { 166 cancelLoginProcess(this.msgKickAlreadyOnline); 167 System.out.println("[Poseidon] User " + username + " has been blocked from connecting as they share a username or UUID with a user who is already online called " + p.getName() + "\nMost likely the user has changed their UUID or the server is running in offline mode and someone has attempted to connect with their name"); 168 } 169 } 170 171 172 if (!loginSuccessful && !loginCancelled) { 173 174 //Bukkit Login Event Start 175 if (this.netLoginHandler.getSocket() == null) { 176 return; 177 } 178 179 180 PlayerPreLoginEvent event = new PlayerPreLoginEvent(this.packet1Login.name, ((InetSocketAddress) netLoginHandler.networkManager.getSocketAddress()).getAddress(), loginProcessHandler); 181 this.server.getPluginManager().callEvent(event); 182 if (event.getResult() != PlayerPreLoginEvent.Result.ALLOWED) { 183 cancelLoginProcess(event.getKickMessage()); 184 return; 185 } 186 //Bukkit Login Event End 187 if (isPlayerConnectionPaused()) { 188 startTime = System.currentTimeMillis() / 1000L; 189 } else { 190 loginSuccessful = true; 191 NetLoginHandler.a(netLoginHandler, packet1Login); 192 } 193 } 194 } 195 196 /** 197 * Cancel a players login before join or login events 198 */ 199 public void cancelLoginProcess(String s) { 200 if (!loginCancelled && !loginSuccessful) { 201 loginCancelled = true; 202 netLoginHandler.disconnect(s); 203 } 204 } 205 206 /** 207 * Set a pause for your plugin 208 * Connection pauses are for fetching data for a player before they MIGHT be allowed to join 209 * 210 * @param plugin Instance of plugin 211 * @param connectionPauseName Name of connection pause (Ensure no duplicates) 212 * @return ConnectionPause Object, used to remove a connection pause 213 */ 214 public ConnectionPause addConnectionInterrupt(Plugin plugin, String connectionPauseName) { 215 final ConnectionPause connectionPause = new ConnectionPause(plugin.getDescription().getName(), connectionPauseName, loginProcessHandler); 216 connectionPauses.add(connectionPause); 217 return connectionPause; 218 } 219 220 @Deprecated 221 public void removeConnectionPause(ConnectionPause connectionPause) { 222 removeConnectionInterrupt(connectionPause); 223 } 224 225 /** 226 * Remove a connection pause 227 * 228 * @param connectionPause ConnectionPause object 229 */ 230 public void removeConnectionInterrupt(ConnectionPause connectionPause) { 231 //Check if the connection pause is registered and active 232 if (!connectionPauses.contains(connectionPause)) { 233 System.out.println("[Poseidon] A plugin has tried to remove a connection pause from the player " + packet1Login.name + " called " + connectionPause.getConnectionPauseName() + " from the plugin " + connectionPause.getPluginName() + ". Please contact the plugin author and get them to check their logic as this is a duplicate remove, or a pause for another player."); 234 return; 235 } 236 //Handle the completion of the pause 237 connectionPause.setActive(false); 238 //If there are no more pauses, connect the player 239 if (!isPlayerConnectionPaused()) { 240 long endTime = System.currentTimeMillis() / 1000L; 241 int timeTaken = (int) (endTime - startTime); 242 243 //If a pause has cancelled the login, don't connect the player 244 if (loginCancelled) { 245 System.out.println("[Poseidon] Player " + loginProcessHandler.packet1Login.name + " was not allowed to join after being on hold for " + timeTaken + " seconds by the following plugins: " + getConnectionPauseNames(false)); 246 return; 247 } 248 249 this.setLoginSuccessful(true); 250 System.out.println("[Poseidon] Player " + loginProcessHandler.packet1Login.name + " has been allowed to join after being on hold for " + timeTaken + " seconds by the following plugins: " + getConnectionPauseNames(false)); 251 NetLoginHandler.a(netLoginHandler, packet1Login); 252 } 253 } 254 255 256 public ConnectionPause[] getActiveConnectionPauses() { 257 HashSet<ConnectionPause> activePauses = new HashSet<>(); 258 for (ConnectionPause connectionPause : connectionPauses) { 259 if (connectionPause.isActive()) { 260 activePauses.add(connectionPause); 261 } 262 } 263 return activePauses.toArray(new ConnectionPause[activePauses.size()]); 264 } 265 266 public String getConnectionPauseNames(boolean activeOnly) { 267 268 StringBuilder pauseNames = new StringBuilder(); 269 for (ConnectionPause connectionPause : connectionPauses) { 270 String pluginName = connectionPause.getPluginName(); 271 String pauseName = connectionPause.getConnectionPauseName(); 272 boolean isActive = connectionPause.isActive(); 273 int time = connectionPause.getRunningTime(); 274 if (activeOnly) { 275 if (connectionPause.isActive()) { 276 pauseNames.append(pluginName).append(":").append(pauseName).append(":").append(isActive ? "Running" : "Complete").append(":").append(time).append("-Seconds, "); 277 } 278 } else { 279 pauseNames.append(pluginName).append(":").append(pauseName).append(":").append(isActive ? "Running" : "Complete").append(":").append(time).append("-Seconds, "); 280 } 281 } 282 return pauseNames.toString(); 283 } 284 285 286 private ConnectionPause legacyConnectionPause; 287 288 /** 289 * Set a pause for your plugin 290 * Connection pauses are for fetching data for a player before they MIGHT be allowed to join 291 */ 292 @Deprecated 293 public void addConnectionPause(Plugin plugin) throws Exception { 294 System.out.println("[Poseidon] " + plugin.getDescription().getName() + " is using the deprecated connection pause system which will be removed in the future. Contact the plugin author to get an updated version."); 295 legacyConnectionPause = addConnectionInterrupt(plugin, "Legacy-Connection-Pause"); 296 } 297 298 /** 299 * Remove a pause for your plugin 300 */ 301 @Deprecated 302 public void removeConnectionPause(Plugin plugin) { 303 if (legacyConnectionPause != null) { 304 removeConnectionInterrupt(legacyConnectionPause); 305 return; 306 } 307 System.out.println("[Poseidon] " + plugin.getDescription().getName() + " Attempted to remove a legacy (deprecated) connection pause that was never added. Please contact the plugin author and get them to check their logic and update to the new connection pause system."); 308 } 309 310 /** 311 * See if the players connection currently paused 312 */ 313 public boolean isPlayerConnectionPaused() { 314 return getActiveConnectionPauses().length > 0; 315 } 316 317 318 private void setLoginSuccessful(boolean loginSuccessful) { 319 this.loginSuccessful = loginSuccessful; 320 } 321}