diff --git a/.gradle/8.5/checksums/checksums.lock b/.gradle/8.5/checksums/checksums.lock index 29fb249..2295aaf 100644 Binary files a/.gradle/8.5/checksums/checksums.lock and b/.gradle/8.5/checksums/checksums.lock differ diff --git a/.gradle/8.5/executionHistory/executionHistory.bin b/.gradle/8.5/executionHistory/executionHistory.bin index a0635de..51dfd1b 100644 Binary files a/.gradle/8.5/executionHistory/executionHistory.bin and b/.gradle/8.5/executionHistory/executionHistory.bin differ diff --git a/.gradle/8.5/executionHistory/executionHistory.lock b/.gradle/8.5/executionHistory/executionHistory.lock index 67ffac5..498ba86 100644 Binary files a/.gradle/8.5/executionHistory/executionHistory.lock and b/.gradle/8.5/executionHistory/executionHistory.lock differ diff --git a/.gradle/8.5/fileHashes/fileHashes.bin b/.gradle/8.5/fileHashes/fileHashes.bin index 7dd8368..c7df9ba 100644 Binary files a/.gradle/8.5/fileHashes/fileHashes.bin and b/.gradle/8.5/fileHashes/fileHashes.bin differ diff --git a/.gradle/8.5/fileHashes/fileHashes.lock b/.gradle/8.5/fileHashes/fileHashes.lock index 232f0be..a21d685 100644 Binary files a/.gradle/8.5/fileHashes/fileHashes.lock and b/.gradle/8.5/fileHashes/fileHashes.lock differ diff --git a/.gradle/8.5/fileHashes/resourceHashesCache.bin b/.gradle/8.5/fileHashes/resourceHashesCache.bin index 17f9c0a..fc71764 100644 Binary files a/.gradle/8.5/fileHashes/resourceHashesCache.bin and b/.gradle/8.5/fileHashes/resourceHashesCache.bin differ diff --git a/.gradle/buildOutputCleanup/buildOutputCleanup.lock b/.gradle/buildOutputCleanup/buildOutputCleanup.lock index be90103..beec8ca 100644 Binary files a/.gradle/buildOutputCleanup/buildOutputCleanup.lock and b/.gradle/buildOutputCleanup/buildOutputCleanup.lock differ diff --git a/.gradle/file-system.probe b/.gradle/file-system.probe index 2fe393a..cf440c9 100644 Binary files a/.gradle/file-system.probe and b/.gradle/file-system.probe differ diff --git a/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig$1.class b/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig$1.class index d4dce8a..d222ac0 100644 Binary files a/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig$1.class and b/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig$1.class differ diff --git a/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig.class b/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig.class index cd3031b..b82cb5b 100644 Binary files a/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig.class and b/build/classes/java/main/me/trouper/sentinel/data/config/AdvancedConfig.class differ diff --git a/build/classes/java/main/me/trouper/sentinel/data/config/NBTConfig.class b/build/classes/java/main/me/trouper/sentinel/data/config/NBTConfig.class index a6c710f..122a7d0 100644 Binary files a/build/classes/java/main/me/trouper/sentinel/data/config/NBTConfig.class and b/build/classes/java/main/me/trouper/sentinel/data/config/NBTConfig.class differ diff --git a/build/resources/main/plugin.yml b/build/resources/main/plugin.yml index 5612acc..306f6e5 100644 --- a/build/resources/main/plugin.yml +++ b/build/resources/main/plugin.yml @@ -12,6 +12,7 @@ softdepend: - ViaBackwards - ViaRewind - Geyser-Spigot + - NoChatReports load: POSTWORLD permissions: sentinel.admin: diff --git a/build/tmp/compileJava/previous-compilation-data.bin b/build/tmp/compileJava/previous-compilation-data.bin index 1552d39..0d8cb3c 100644 Binary files a/build/tmp/compileJava/previous-compilation-data.bin and b/build/tmp/compileJava/previous-compilation-data.bin differ diff --git a/src/main/java/me/trouper/sentinel/data/config/NBTConfig.java b/src/main/java/me/trouper/sentinel/data/config/NBTConfig.java index 368cc6f..df7d690 100644 --- a/src/main/java/me/trouper/sentinel/data/config/NBTConfig.java +++ b/src/main/java/me/trouper/sentinel/data/config/NBTConfig.java @@ -4,6 +4,7 @@ import io.github.itzispyder.pdk.utils.misc.config.JsonSerializable; import me.trouper.sentinel.Sentinel; import java.io.File; +import java.util.List; public class NBTConfig implements JsonSerializable { @Override @@ -13,6 +14,16 @@ public class NBTConfig implements JsonSerializable { return file; } + public RateLimit rateLimit = new RateLimit(); + + public class RateLimit { + public int rateLimitBytes = 16348; + public int byteDecay = 1024; + public int rateLimitItems = 10; + public int itemDecay = 2; + public List punishmentCommands = List.of("kick %player% Internal Exception: io.netty.handler.codec.DecoderException: java.lang.RuntimeException: Tried to read NBT tag that was too big; tried to allocate 28391038bytes where max allowed: 16348"); + } + public boolean allowName = true; public boolean allowLore = true; public boolean allowAttributes = false; @@ -21,6 +32,8 @@ public class NBTConfig implements JsonSerializable { public boolean allowCustomTools = false; public boolean allowBooks = false; public boolean allowRecursion = true; + public int maxCustomData = 64; + public int globalMaxEnchant = 5; public int maxMending = 1; public int maxUnbreaking = 3; diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/players/CreativeHotbar.java b/src/main/java/me/trouper/sentinel/server/events/violations/players/CreativeHotbar.java index efb8a69..e155559 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/players/CreativeHotbar.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/players/CreativeHotbar.java @@ -1,11 +1,13 @@ package me.trouper.sentinel.server.events.violations.players; import io.github.itzispyder.pdk.plugin.gui.CustomGui; +import io.github.itzispyder.pdk.utils.misc.Pair; import me.trouper.sentinel.Sentinel; import me.trouper.sentinel.data.storage.NBTStorage; import me.trouper.sentinel.server.events.violations.AbstractViolation; import me.trouper.sentinel.server.functions.helpers.ActionConfiguration; import me.trouper.sentinel.server.functions.itemchecks.ItemCheck; +import me.trouper.sentinel.server.functions.itemchecks.RateLimitCheck; import me.trouper.sentinel.server.gui.Items; import me.trouper.sentinel.server.gui.MainGUI; import me.trouper.sentinel.server.gui.config.AntiNukeGUI; @@ -28,18 +30,36 @@ public class CreativeHotbar extends AbstractViolation { private void onNBTPull(InventoryCreativeEvent e) { //ServerUtils.verbose("NBT: Detected creative mode action"); if (!Sentinel.getInstance().getDirector().io.violationConfig.creativeHotbarAction.enabled) return; - ServerUtils.verbose("NBT: Enabled"); + //ServerUtils.verbose("NBT: Enabled"); if (!(e.getWhoClicked() instanceof Player p)) return; - ServerUtils.verbose("NBT: Clicker is a player"); + //ServerUtils.verbose("NBT: Clicker is a player"); if (e.getCursor() == null) return; // Well it threw an exception during testing, so it isn't always false! - ServerUtils.verbose("NBT: Cursor isn't null"); + //ServerUtils.verbose("NBT: Cursor isn't null"); ItemStack i = e.getCursor(); if (PlayerUtils.isTrusted(p)) return; - ServerUtils.verbose("NBT: Not trusted"); + //ServerUtils.verbose("NBT: Not trusted"); if (e.getCursor().getItemMeta() == null) return; - ServerUtils.verbose("NBT: Cursor has meta"); + //ServerUtils.verbose("NBT: Cursor has meta"); if (!(i.hasItemMeta() && i.getItemMeta() != null)) return; - ServerUtils.verbose("NBT: Item has meta"); + if (!new RateLimitCheck().passes(new Pair<>(p,i))) { + ServerUtils.verbose("Player flags rate limit, performing action"); + ActionConfiguration.Builder config = new ActionConfiguration.Builder() + .setEvent(e) + .setPlayer(p) + .cancel(true) + .punish(true) + .deop(Sentinel.getInstance().getDirector().io.violationConfig.creativeHotbarAction.deop) + .setPunishmentCommands(Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.punishmentCommands); + + runActions( + Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.grab, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.nbtItem), + Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.rootNameFormatPlayer.formatted(p.getName(), Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.grab, Sentinel.getInstance().getDirector().io.lang.violations.protections.rootName.nbtItem), + generatePlayerInfo(p), + config + ); + + return; + } if (new ItemCheck().passes(i)) return; ServerUtils.verbose("NBT: Item doesn't pass, performing action"); diff --git a/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java b/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java index 4f2c593..40bebc3 100644 --- a/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java +++ b/src/main/java/me/trouper/sentinel/server/events/violations/players/PluginCloakingPacket.java @@ -62,12 +62,6 @@ public class PluginCloakingPacket implements PacketListener { } } } - case PacketType.Play.Client.CHAT_COMMAND, PacketType.Play.Client.CHAT_COMMAND_UNSIGNED -> { - WrapperPlayClientChatCommandUnsigned wrapper = new WrapperPlayClientChatCommandUnsigned(event); - WrapperPlayClientChatCommand wrappers = new WrapperPlayClientChatCommand(event); - wrapper.getCommand(); - wrappers.getCommand(); - } default -> {} } } diff --git a/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java b/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java index fb0bb3d..582121f 100644 --- a/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java +++ b/src/main/java/me/trouper/sentinel/server/functions/helpers/ActionConfiguration.java @@ -45,7 +45,6 @@ public class ActionConfiguration { this.punishmentCommands = builder.punishmentCommands; this.logToDiscord = builder.logToDiscord; this.actionNode = builder.actionNode; - // Removed the actions being run here to prevent double execution } public Player getPlayer() { diff --git a/src/main/java/me/trouper/sentinel/server/functions/itemchecks/ItemCheck.java b/src/main/java/me/trouper/sentinel/server/functions/itemchecks/ItemCheck.java index 3860338..6c405ea 100644 --- a/src/main/java/me/trouper/sentinel/server/functions/itemchecks/ItemCheck.java +++ b/src/main/java/me/trouper/sentinel/server/functions/itemchecks/ItemCheck.java @@ -2,6 +2,7 @@ package me.trouper.sentinel.server.functions.itemchecks; import de.tr7zw.changeme.nbtapi.NBT; import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.data.config.NBTConfig; import me.trouper.sentinel.utils.InventoryUtils; import me.trouper.sentinel.utils.ServerUtils; import org.bukkit.Material; @@ -17,6 +18,7 @@ public class ItemCheck extends AbstractCheck { @Override public boolean passes(ItemStack item) { ServerUtils.verbose("Checking item: " + item.getType().name()); + NBTConfig config = Sentinel.getInstance().getDirector().io.nbtConfig; // No metadata? Nothing to check. if (item.getItemMeta() == null) { @@ -38,11 +40,11 @@ public class ItemCheck extends AbstractCheck { // NBT-based checks (e.g. custom consumables/tools). var nbt = NBT.itemStackToNBT(item); var components = nbt.getCompound("components"); - if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowCustomConsumables && components.getCompound("minecraft:consumable") != null) { + if (!config.allowCustomConsumables && components.getCompound("minecraft:consumable") != null) { ServerUtils.verbose("Item is consumable and not allowed."); return false; } - if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowCustomTools && components.getCompound("minecraft:tool") != null) { + if (!config.allowCustomTools && components.getCompound("minecraft:tool") != null) { ServerUtils.verbose("Item is custom tool and not allowed."); return false; } @@ -148,32 +150,32 @@ public class ItemCheck extends AbstractCheck { } // Name, lore, potion, attribute and enchantment checks. - if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowName && meta.hasDisplayName()) { + if (!config.allowName && meta.hasDisplayName()) { ServerUtils.verbose("Custom names not allowed."); return false; } - if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowLore && meta.hasLore()) { + if (!config.allowLore && meta.hasLore()) { ServerUtils.verbose("Custom lore not allowed."); return false; } - if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowPotions && + if (!config.allowPotions && (item.getType().equals(Material.POTION) || item.getType().equals(Material.SPLASH_POTION) || item.getType().equals(Material.LINGERING_POTION))) { ServerUtils.verbose("Potions not allowed."); return false; } - if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowAttributes && meta.hasAttributeModifiers()) { + if (!config.allowAttributes && meta.hasAttributeModifiers()) { ServerUtils.verbose("Attribute modifiers not allowed."); return false; } - if (Sentinel.getInstance().getDirector().io.nbtConfig.globalMaxEnchant != 0 && new EnchantmentCheck().hasIllegalEnchants(item)) { + if (config.globalMaxEnchant != 0 && new EnchantmentCheck().hasIllegalEnchants(item)) { ServerUtils.verbose("Illegal enchantments found."); return false; } // Recursion check for use-remainder items. if (meta.hasUseRemainder()) { - if (!Sentinel.getInstance().getDirector().io.nbtConfig.allowRecursion) { + if (!config.allowRecursion) { ServerUtils.verbose("Recursion not allowed."); return false; } diff --git a/src/main/java/me/trouper/sentinel/server/functions/itemchecks/RateLimitCheck.java b/src/main/java/me/trouper/sentinel/server/functions/itemchecks/RateLimitCheck.java new file mode 100644 index 0000000..afda1cf --- /dev/null +++ b/src/main/java/me/trouper/sentinel/server/functions/itemchecks/RateLimitCheck.java @@ -0,0 +1,85 @@ +package me.trouper.sentinel.server.functions.itemchecks; + +import de.tr7zw.changeme.nbtapi.NBTItem; +import io.github.itzispyder.pdk.utils.misc.Pair; +import me.trouper.sentinel.Sentinel; +import me.trouper.sentinel.utils.ServerUtils; +import org.bukkit.entity.Player; +import org.bukkit.inventory.ItemStack; +import org.bukkit.scheduler.BukkitTask; + +import java.io.ByteArrayOutputStream; +import java.io.IOException; +import java.io.ObjectOutputStream; +import java.util.Arrays; +import java.util.HashMap; +import java.util.Map; +import java.util.UUID; + +public class RateLimitCheck extends AbstractCheck> { + + + public static Map dataUsed = new HashMap<>(); + public static Map itemsUsed = new HashMap<>(); + + @Override + public boolean passes(Pair input) { + Player player = input.left; + UUID uuid = player.getUniqueId(); + ItemStack item = input.right; + + return itemLimit(player,uuid,item) && dataLimit(player,uuid,item); + } + + private boolean itemLimit(Player player, UUID uuid, ItemStack item) { + int currentUsed = itemsUsed.getOrDefault(uuid,0); + ServerUtils.verbose("Current Player used items: " + currentUsed); + currentUsed++; + itemsUsed.put(uuid,currentUsed); + return currentUsed <= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.rateLimitItems; + } + + + private boolean dataLimit(Player player, UUID uuid, ItemStack item) { + int itemData = 0; + int currentData = dataUsed.getOrDefault(uuid,0); + + ServerUtils.verbose("Current Player used data: " + currentData); + try { + NBTItem nbt = new NBTItem(item); + itemData = nbt.toString().length(); + ServerUtils.verbose("Item data: " + itemData); + currentData += itemData; + } catch (Exception e) { + Sentinel.getInstance().getLogger().warning("Could not determine size of item. Blocking."); + Sentinel.getInstance().getLogger().warning(Arrays.toString(e.getStackTrace())); + return false; + } + + dataUsed.put(uuid,currentData); + + ServerUtils.verbose("New Player used data: " + currentData); + + return currentData <= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.rateLimitBytes; + } + + public static void decayData() { + for (UUID uuid : dataUsed.keySet()) { + int currentData = dataUsed.get(uuid); + if (currentData > 0) { + currentData -= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.byteDecay; + dataUsed.put(uuid, Math.max(0, currentData)); + } + } + } + + public static void decayItems() { + for (UUID uuid : itemsUsed.keySet()) { + int currentItems = itemsUsed.get(uuid); + if (currentItems > 0) { + currentItems -= Sentinel.getInstance().getDirector().io.nbtConfig.rateLimit.itemDecay; + itemsUsed.put(uuid, Math.max(0, currentItems)); + } + } + } +} diff --git a/src/main/java/me/trouper/sentinel/startup/drm/Loader.java b/src/main/java/me/trouper/sentinel/startup/drm/Loader.java index 340e0b0..40741c7 100644 --- a/src/main/java/me/trouper/sentinel/startup/drm/Loader.java +++ b/src/main/java/me/trouper/sentinel/startup/drm/Loader.java @@ -28,6 +28,7 @@ import me.trouper.sentinel.server.events.violations.entities.CommandMinecartPlac import me.trouper.sentinel.server.events.violations.entities.CommandMinecartUse; import me.trouper.sentinel.server.functions.chatfilter.profanity.ProfanityFilter; import me.trouper.sentinel.server.functions.chatfilter.spam.SpamFilter; +import me.trouper.sentinel.server.functions.itemchecks.RateLimitCheck; import me.trouper.sentinel.utils.Text; import org.bukkit.Bukkit; @@ -183,6 +184,8 @@ public final class Loader { Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), SpamFilter::decayHeat,0, 20); Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), ProfanityFilter::decayScore,0,1200); Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), WandEvents::handleDisplay,0,1); + Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), RateLimitCheck::decayData,0,20*60); + Bukkit.getScheduler().runTaskTimer(Sentinel.getInstance(), RateLimitCheck::decayItems,0,200); if (Sentinel.getInstance().getDirector().io.mainConfig.backdoorDetection.enabled) Sentinel.getInstance().getDirector().backdoorDetection.init();