--- a/net/minecraft/server/network/LoginListener.java
+++ b/net/minecraft/server/network/LoginListener.java
@@ -40,6 +40,13 @@
 import org.apache.commons.lang3.Validate;
 import org.slf4j.Logger;
 
+// CraftBukkit start
+import net.minecraft.server.level.EntityPlayer;
+import org.bukkit.craftbukkit.util.Waitable;
+import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
+import org.bukkit.event.player.PlayerPreLoginEvent;
+// CraftBukkit end
+
 public class LoginListener implements PacketLoginInListener, TickablePacketListener {
 
     private static final AtomicInteger UNIQUE_THREAD_ID = new AtomicInteger(0);
@@ -56,6 +63,7 @@
     @Nullable
     private GameProfile authenticatedProfile;
     private final String serverId;
+    private EntityPlayer player; // CraftBukkit
 
     public LoginListener(MinecraftServer minecraftserver, NetworkManager networkmanager) {
         this.state = LoginListener.EnumProtocolState.HELLO;
@@ -81,6 +89,13 @@
 
     }
 
+    // CraftBukkit start
+    @Deprecated
+    public void disconnect(String s) {
+        disconnect(IChatBaseComponent.literal(s));
+    }
+    // CraftBukkit end
+
     @Override
     public boolean isAcceptingMessages() {
         return this.connection.isConnected();
@@ -139,10 +154,12 @@
 
     private void verifyLoginAndFinishConnectionSetup(GameProfile gameprofile) {
         PlayerList playerlist = this.server.getPlayerList();
-        IChatBaseComponent ichatbasecomponent = playerlist.canPlayerLogin(this.connection.getRemoteAddress(), gameprofile);
+        // CraftBukkit start - fire PlayerLoginEvent
+        this.player = playerlist.canPlayerLogin(this, gameprofile); // CraftBukkit
 
-        if (ichatbasecomponent != null) {
-            this.disconnect(ichatbasecomponent);
+        if (this.player == null) {
+            // this.disconnect(ichatbasecomponent);
+            // CraftBukkit end
         } else {
             if (this.server.getCompressionThreshold() >= 0 && !this.connection.isMemoryConnection()) {
                 this.connection.send(new PacketLoginOutSetCompression(this.server.getCompressionThreshold()), PacketSendListener.thenRun(() -> {
@@ -150,7 +167,7 @@
                 }));
             }
 
-            boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile);
+            boolean flag = playerlist.disconnectAllPlayersWithProfile(gameprofile, this.player); // CraftBukkit - add player reference
 
             if (flag) {
                 this.state = LoginListener.EnumProtocolState.WAITING_FOR_DUPE_DISCONNECT;
@@ -200,6 +217,43 @@
                     if (profileresult != null) {
                         GameProfile gameprofile = profileresult.profile();
 
+                        // CraftBukkit start - fire PlayerPreLoginEvent
+                        if (!connection.isConnected()) {
+                            return;
+                        }
+
+                        String playerName = gameprofile.getName();
+                        java.net.InetAddress address = ((java.net.InetSocketAddress) connection.getRemoteAddress()).getAddress();
+                        java.util.UUID uniqueId = gameprofile.getId();
+                        final org.bukkit.craftbukkit.CraftServer server = LoginListener.this.server.server;
+
+                        AsyncPlayerPreLoginEvent asyncEvent = new AsyncPlayerPreLoginEvent(playerName, address, uniqueId);
+                        server.getPluginManager().callEvent(asyncEvent);
+
+                        if (PlayerPreLoginEvent.getHandlerList().getRegisteredListeners().length != 0) {
+                            final PlayerPreLoginEvent event = new PlayerPreLoginEvent(playerName, address, uniqueId);
+                            if (asyncEvent.getResult() != PlayerPreLoginEvent.Result.ALLOWED) {
+                                event.disallow(asyncEvent.getResult(), asyncEvent.getKickMessage());
+                            }
+                            Waitable<PlayerPreLoginEvent.Result> waitable = new Waitable<PlayerPreLoginEvent.Result>() {
+                                @Override
+                                protected PlayerPreLoginEvent.Result evaluate() {
+                                    server.getPluginManager().callEvent(event);
+                                    return event.getResult();
+                                }};
+
+                            LoginListener.this.server.processQueue.add(waitable);
+                            if (waitable.get() != PlayerPreLoginEvent.Result.ALLOWED) {
+                                disconnect(event.getKickMessage());
+                                return;
+                            }
+                        } else {
+                            if (asyncEvent.getLoginResult() != AsyncPlayerPreLoginEvent.Result.ALLOWED) {
+                                disconnect(asyncEvent.getKickMessage());
+                                return;
+                            }
+                        }
+                        // CraftBukkit end
                         LoginListener.LOGGER.info("UUID of player {} is {}", gameprofile.getName(), gameprofile.getId());
                         LoginListener.this.startClientVerification(gameprofile);
                     } else if (LoginListener.this.server.isSingleplayer()) {
@@ -217,6 +271,11 @@
                         LoginListener.this.disconnect(IChatBaseComponent.translatable("multiplayer.disconnect.authservers_down"));
                         LoginListener.LOGGER.error("Couldn't verify username because servers are unavailable");
                     }
+                    // CraftBukkit start - catch all exceptions
+                } catch (Exception exception) {
+                    disconnect("Failed to verify username!");
+                    server.server.getLogger().log(java.util.logging.Level.WARNING, "Exception verifying " + s1, exception);
+                    // CraftBukkit end
                 }
 
             }
@@ -242,7 +301,7 @@
     public void handleLoginAcknowledgement(ServerboundLoginAcknowledgedPacket serverboundloginacknowledgedpacket) {
         Validate.validState(this.state == LoginListener.EnumProtocolState.PROTOCOL_SWITCHING, "Unexpected login acknowledgement packet", new Object[0]);
         CommonListenerCookie commonlistenercookie = CommonListenerCookie.createInitial((GameProfile) Objects.requireNonNull(this.authenticatedProfile));
-        ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie);
+        ServerConfigurationPacketListenerImpl serverconfigurationpacketlistenerimpl = new ServerConfigurationPacketListenerImpl(this.server, this.connection, commonlistenercookie, this.player); // CraftBukkit
 
         this.connection.setListener(serverconfigurationpacketlistenerimpl);
         serverconfigurationpacketlistenerimpl.startConfiguration();
