Fork of Poseidon providing Bukkit #1060 to older Beta versions (b1.0-b1.7.3)
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}