diff --git a/src/main/java/com/loohp/limbo/Limbo.java b/src/main/java/com/loohp/limbo/Limbo.java index c2f0d6e..9ccf1f1 100644 --- a/src/main/java/com/loohp/limbo/Limbo.java +++ b/src/main/java/com/loohp/limbo/Limbo.java @@ -432,6 +432,29 @@ public class Limbo { public ServerProperties getServerProperties() { return properties; } + + public void reloadAllowlist() { + properties.reloadAllowlist(); + } + + public boolean uuidIsAllowed(UUID requestedUuid) { + if (!properties.isEnforceAllowlist()) { + return true; + } + + if (properties.uuidIsAllowed(requestedUuid)) { + if(!properties.isReducedDebugInfo()) { + Limbo.getInstance().getConsole().sendMessage(String.format("allowlist: %s allowed", requestedUuid.toString())); + } + return true; + } + + if(!properties.isReducedDebugInfo()) { + Limbo.getInstance().getConsole().sendMessage(String.format("allowlist: %s not allowed", requestedUuid.toString())); + } + + return false; + } public ServerConnection getServerConnection() { return server; diff --git a/src/main/java/com/loohp/limbo/commands/DefaultCommands.java b/src/main/java/com/loohp/limbo/commands/DefaultCommands.java index ec6f170..c7def11 100644 --- a/src/main/java/com/loohp/limbo/commands/DefaultCommands.java +++ b/src/main/java/com/loohp/limbo/commands/DefaultCommands.java @@ -165,6 +165,20 @@ public class DefaultCommands implements CommandExecutor, TabCompletor { } return; } + if (args[0].equalsIgnoreCase("allowlist")) { + if (sender.hasPermission("limboserver.allowlist")) { + if (args.length != 2) { + sender.sendMessage(ChatColor.RED + "Invalid usage!"); + } else if (!args[1].equalsIgnoreCase("reload")) { + sender.sendMessage(ChatColor.RED + "Invalid usage!"); + } else { + Limbo.getInstance().reloadAllowlist(); + } + } else { + sender.sendMessage(ChatColor.RED + "You do not have permission to use that command!"); + } + return; + } } @Override diff --git a/src/main/java/com/loohp/limbo/file/ServerProperties.java b/src/main/java/com/loohp/limbo/file/ServerProperties.java index 8f10271..8446494 100644 --- a/src/main/java/com/loohp/limbo/file/ServerProperties.java +++ b/src/main/java/com/loohp/limbo/file/ServerProperties.java @@ -22,20 +22,32 @@ package com.loohp.limbo.file; import java.awt.image.BufferedImage; import java.io.File; import java.io.FileInputStream; +import java.io.FileNotFoundException; import java.io.FileOutputStream; +import java.io.FileReader; import java.io.IOException; import java.io.InputStreamReader; import java.io.OutputStreamWriter; import java.io.PrintWriter; import java.nio.charset.StandardCharsets; +import java.util.ArrayList; +import java.util.HashMap; +import java.util.Iterator; import java.util.List; import java.util.Map.Entry; import java.util.Optional; import java.util.Properties; +import java.util.UUID; import javax.imageio.ImageIO; +import org.json.simple.JSONArray; +import org.json.simple.JSONObject; +import org.json.simple.parser.JSONParser; +import org.json.simple.parser.ParseException; + import com.google.common.collect.Lists; +import com.loohp.limbo.Console; import com.loohp.limbo.Limbo; import com.loohp.limbo.location.Location; import com.loohp.limbo.utils.GameMode; @@ -72,6 +84,8 @@ public class ServerProperties { private int viewDistance; private double ticksPerSecond; private boolean handshakeVerbose; + private boolean enforceAllowlist; + private HashMap allowlist; private String resourcePackSHA1; private String resourcePackLink; @@ -184,9 +198,65 @@ public class ServerProperties { favicon = Optional.empty(); } + enforceAllowlist = Boolean.parseBoolean(prop.getProperty("enforce-allowlist")); + if (enforceAllowlist) { + reloadAllowlist(); + } + Limbo.getInstance().getConsole().sendMessage("Loaded server.properties"); } - + + public void reloadAllowlist() { + Console console = Limbo.getInstance().getConsole(); + + allowlist = new HashMap(); + try { + JSONParser parser = new JSONParser(); + Object obj = parser.parse(new FileReader("allowlist.json")); + + if (!(obj instanceof JSONArray)) { + console.sendMessage("allowlist: expected [] got {}"); + return; + } + + JSONArray array = (JSONArray) obj; + + Iterator iter = array.iterator(); + while (iter.hasNext()) { + Object o = iter.next(); + if (!(o instanceof JSONObject)) { + console.sendMessage("allowlist: array element is not an object"); + continue; + } + + JSONObject element = (JSONObject) o; + o = element.get("uuid"); + if (o == null) { + console.sendMessage("allowlist: missing uuid attribute"); + continue; + } + if (!(o instanceof String)) { + console.sendMessage("allowlist: uuid is not a string"); + continue; + } + + String uuidStr = (String) o; + UUID allowedUuid = UUID.fromString(uuidStr); + allowlist.put(allowedUuid, true); + } + } catch (IllegalArgumentException e) { + console.sendMessage(e.toString()); + } catch (FileNotFoundException e) { + console.sendMessage(String.format("allowlist: %s", e.toString())); + } catch (IOException e) { + console.sendMessage(String.format("allowlist: %s", e.toString())); + } catch (ParseException e) { + console.sendMessage(String.format(" allowlist: parse: %s", e.toString())); + } + + console.sendMessage("allowlist: reloaded"); + } + public String getServerImplementationVersion() { return Limbo.getInstance().SERVER_IMPLEMENTATION_VERSION; } @@ -294,6 +364,17 @@ public class ServerProperties { public boolean handshakeVerboseEnabled() { return handshakeVerbose; } + + public boolean isEnforceAllowlist() { + return enforceAllowlist; + } + + public boolean uuidIsAllowed(UUID requestedUuid) { + if (allowlist.containsKey(requestedUuid)) { + return true; + } + return false; + } public String getResourcePackLink() { return resourcePackLink; diff --git a/src/main/java/com/loohp/limbo/network/ClientConnection.java b/src/main/java/com/loohp/limbo/network/ClientConnection.java index 52f0078..95d56b6 100644 --- a/src/main/java/com/loohp/limbo/network/ClientConnection.java +++ b/src/main/java/com/loohp/limbo/network/ClientConnection.java @@ -88,6 +88,7 @@ import com.loohp.limbo.utils.MojangAPIUtils.SkinResponse; import com.loohp.limbo.utils.NamespacedKey; import com.loohp.limbo.world.BlockPosition; import com.loohp.limbo.world.World; + import net.kyori.adventure.text.Component; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.chat.BaseComponent; @@ -360,9 +361,9 @@ public class ClientConnection extends Thread { break; case LOGIN: state = ClientState.LOGIN; + ServerProperties properties = Limbo.getInstance().getServerProperties(); if (isBungeecord || isBungeeGuard) { - ServerProperties properties = Limbo.getInstance().getServerProperties(); try { String[] data = bungeeForwarding.split("\\x00"); String host = ""; @@ -469,6 +470,11 @@ public class ClientConnection extends Thread { UUID uuid = isBungeecord || isBungeeGuard ? bungeeUUID : UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(StandardCharsets.UTF_8)); + if (!Limbo.getInstance().uuidIsAllowed(uuid)) { + disconnectDuringLogin(TextComponent.fromLegacyText("You are not invited to this server")); + break; + } + PacketLoginOutLoginSuccess success = new PacketLoginOutLoginSuccess(uuid, username); sendPacket(success); diff --git a/src/main/resources/server.properties b/src/main/resources/server.properties index 112e235..367a1e1 100644 --- a/src/main/resources/server.properties +++ b/src/main/resources/server.properties @@ -79,4 +79,8 @@ resource-pack-sha1= required-resource-pack=false #JSON formatted text to show when prompting the player to install the resource pack (May be left blank) -resource-pack-prompt={"text":"","extra":[{"text":"Install server resource pack!","color":"yellow"}]} \ No newline at end of file +resource-pack-prompt={"text":"","extra":[{"text":"Install server resource pack!","color":"yellow"}]} + +#Whether to enforce the player allowlist. If true, loads and enforces the allowlist from 'allowlist.json' +#in the same format as vanilla minecraft servers +enforce-allowlist=false \ No newline at end of file