Add a 500ms rate limit to disguise commands to try prevent possible crashes. Fixes #551

This commit is contained in:
libraryaddict 2021-05-26 06:31:08 +12:00
parent 135b0fdeaa
commit 54b9490604
7 changed files with 63 additions and 27 deletions

View File

@ -1,6 +1,8 @@
package me.libraryaddict.disguise.commands; package me.libraryaddict.disguise.commands;
import com.comphenix.protocol.ProtocolLibrary; import com.comphenix.protocol.ProtocolLibrary;
import com.google.common.cache.Cache;
import com.google.common.cache.CacheBuilder;
import me.libraryaddict.disguise.DisguiseConfig; import me.libraryaddict.disguise.DisguiseConfig;
import me.libraryaddict.disguise.commands.disguise.DisguiseCommand; import me.libraryaddict.disguise.commands.disguise.DisguiseCommand;
import me.libraryaddict.disguise.commands.disguise.DisguiseEntityCommand; import me.libraryaddict.disguise.commands.disguise.DisguiseEntityCommand;
@ -19,6 +21,7 @@ import me.libraryaddict.disguise.utilities.parser.DisguiseParser;
import me.libraryaddict.disguise.utilities.parser.DisguisePerm; import me.libraryaddict.disguise.utilities.parser.DisguisePerm;
import me.libraryaddict.disguise.utilities.parser.DisguisePermissions; import me.libraryaddict.disguise.utilities.parser.DisguisePermissions;
import me.libraryaddict.disguise.utilities.parser.WatcherMethod; import me.libraryaddict.disguise.utilities.parser.WatcherMethod;
import me.libraryaddict.disguise.utilities.translations.LibsMsg;
import org.bukkit.Bukkit; import org.bukkit.Bukkit;
import org.bukkit.ChatColor; import org.bukkit.ChatColor;
import org.bukkit.command.CommandExecutor; import org.bukkit.command.CommandExecutor;
@ -27,12 +30,14 @@ import org.bukkit.entity.Player;
import org.bukkit.inventory.ItemStack; import org.bukkit.inventory.ItemStack;
import java.util.*; import java.util.*;
import java.util.concurrent.TimeUnit;
/** /**
* @author libraryaddict * @author libraryaddict
*/ */
public abstract class DisguiseBaseCommand implements CommandExecutor { public abstract class DisguiseBaseCommand implements CommandExecutor {
private static final Map<Class<? extends DisguiseBaseCommand>, String> disguiseCommands; private static final Map<Class<? extends DisguiseBaseCommand>, String> disguiseCommands;
private final Cache<UUID, Long> rateLimit = CacheBuilder.newBuilder().expireAfterWrite(500, TimeUnit.MILLISECONDS).build();
static { static {
HashMap<Class<? extends DisguiseBaseCommand>, String> map = new HashMap<>(); HashMap<Class<? extends DisguiseBaseCommand>, String> map = new HashMap<>();
@ -49,6 +54,20 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
disguiseCommands = map; disguiseCommands = map;
} }
protected boolean hasHitRateLimit(CommandSender sender) {
if (sender.isOp() || !(sender instanceof Player) || sender.hasPermission("libsdisguises.ratelimitbypass")) {
return false;
}
if (rateLimit.getIfPresent(((Player) sender).getUniqueId()) != null) {
LibsMsg.TOO_FAST.send(sender);
return true;
}
rateLimit.put(((Player) sender).getUniqueId(), System.currentTimeMillis());
return false;
}
protected boolean isNotPremium(CommandSender sender) { protected boolean isNotPremium(CommandSender sender) {
String requiredProtocolLib = DisguiseUtilities.getProtocolLibRequiredVersion(); String requiredProtocolLib = DisguiseUtilities.getProtocolLibRequiredVersion();
String version = ProtocolLibrary.getPlugin().getDescription().getVersion(); String version = ProtocolLibrary.getPlugin().getDescription().getVersion();
@ -59,8 +78,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
if (sender instanceof Player && !sender.isOp() && if (sender instanceof Player && !sender.isOp() &&
(!LibsPremium.isPremium() || LibsPremium.getPaidInformation() == LibsPremium.getPluginInformation())) { (!LibsPremium.isPremium() || LibsPremium.getPaidInformation() == LibsPremium.getPluginInformation())) {
sender.sendMessage(ChatColor.RED + sender.sendMessage(ChatColor.RED + "This is the free version of Lib's Disguises, player commands are limited to console and " +
"This is the free version of Lib's Disguises, player commands are limited to console and " +
"Operators only! Purchase the plugin for non-admin usage!"); "Operators only! Purchase the plugin for non-admin usage!");
return true; return true;
} }
@ -68,8 +86,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
return false; return false;
} }
protected List<String> getTabDisguiseTypes(CommandSender sender, DisguisePermissions perms, String[] allArgs, protected List<String> getTabDisguiseTypes(CommandSender sender, DisguisePermissions perms, String[] allArgs, int startsAt, String currentArg) {
int startsAt, String currentArg) {
// If not enough arguments to get current disguise type // If not enough arguments to get current disguise type
if (allArgs.length <= startsAt) { if (allArgs.length <= startsAt) {
return getAllowedDisguises(perms); return getAllowedDisguises(perms);
@ -85,8 +102,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
// If current argument is just after the disguise type, and disguise type is a player which is not a custom // If current argument is just after the disguise type, and disguise type is a player which is not a custom
// disguise // disguise
if (allArgs.length == startsAt + 1 && disguiseType.getType() == DisguiseType.PLAYER && if (allArgs.length == startsAt + 1 && disguiseType.getType() == DisguiseType.PLAYER && !disguiseType.isCustomDisguise()) {
!disguiseType.isCustomDisguise()) {
ArrayList<String> tabs = new ArrayList<>(); ArrayList<String> tabs = new ArrayList<>();
// Add all player names to tab list // Add all player names to tab list
@ -103,8 +119,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
return tabs; return tabs;
} }
return getTabDisguiseOptions(sender, perms, disguiseType, allArgs, startsAt + (disguiseType.isPlayer() ? 2 : 1), return getTabDisguiseOptions(sender, perms, disguiseType, allArgs, startsAt + (disguiseType.isPlayer() ? 2 : 1), currentArg);
currentArg);
} }
/** /**
@ -114,8 +129,8 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
* @param startsAt What index this starts at * @param startsAt What index this starts at
* @return a list of viable disguise options * @return a list of viable disguise options
*/ */
protected List<String> getTabDisguiseOptions(CommandSender commandSender, DisguisePermissions perms, protected List<String> getTabDisguiseOptions(CommandSender commandSender, DisguisePermissions perms, DisguisePerm disguisePerm, String[] allArgs,
DisguisePerm disguisePerm, String[] allArgs, int startsAt, String currentArg) { int startsAt, String currentArg) {
ArrayList<String> usedOptions = new ArrayList<>(); ArrayList<String> usedOptions = new ArrayList<>();
WatcherMethod[] methods = ParamInfoManager.getDisguiseWatcherMethods(disguisePerm.getWatcherClass()); WatcherMethod[] methods = ParamInfoManager.getDisguiseWatcherMethods(disguisePerm.getWatcherClass());
@ -142,8 +157,8 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
return getTabDisguiseSubOptions(commandSender, perms, disguisePerm, allArgs, startsAt, currentArg); return getTabDisguiseSubOptions(commandSender, perms, disguisePerm, allArgs, startsAt, currentArg);
} }
protected List<String> getTabDisguiseSubOptions(CommandSender commandSender, DisguisePermissions perms, protected List<String> getTabDisguiseSubOptions(CommandSender commandSender, DisguisePermissions perms, DisguisePerm disguisePerm, String[] allArgs,
DisguisePerm disguisePerm, String[] allArgs, int startsAt, String currentArg) { int startsAt, String currentArg) {
boolean addMethods = true; boolean addMethods = true;
List<String> tabs = new ArrayList<>(); List<String> tabs = new ArrayList<>();
@ -199,8 +214,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
} }
protected List<String> filterTabs(List<String> list, String[] origArgs) { protected List<String> filterTabs(List<String> list, String[] origArgs) {
if (origArgs.length == 0) if (origArgs.length == 0) {
return list; return list;
}
Iterator<String> itel = list.iterator(); Iterator<String> itel = list.iterator();
String label = origArgs[origArgs.length - 1].toLowerCase(Locale.ENGLISH); String label = origArgs[origArgs.length - 1].toLowerCase(Locale.ENGLISH);
@ -208,8 +224,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
while (itel.hasNext()) { while (itel.hasNext()) {
String name = itel.next(); String name = itel.next();
if (name.toLowerCase(Locale.ENGLISH).startsWith(label)) if (name.toLowerCase(Locale.ENGLISH).startsWith(label)) {
continue; continue;
}
itel.remove(); itel.remove();
} }
@ -231,8 +248,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
ArrayList<String> allowedDisguises = new ArrayList<>(); ArrayList<String> allowedDisguises = new ArrayList<>();
for (DisguisePerm type : permissions.getAllowed()) { for (DisguisePerm type : permissions.getAllowed()) {
if (type.isUnknown()) if (type.isUnknown()) {
continue; continue;
}
allowedDisguises.add(type.toReadable().replaceAll(" ", "_")); allowedDisguises.add(type.toReadable().replaceAll(" ", "_"));
} }
@ -250,8 +268,9 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
for (int i = 0; i < args.length - 1; i++) { for (int i = 0; i < args.length - 1; i++) {
String s = args[i]; String s = args[i];
if (s.trim().isEmpty()) if (s.trim().isEmpty()) {
continue; continue;
}
newArgs.add(s); newArgs.add(s);
} }
@ -289,8 +308,7 @@ public abstract class DisguiseBaseCommand implements CommandExecutor {
try { try {
Integer.parseInt(string); Integer.parseInt(string);
return true; return true;
} } catch (Exception ex) {
catch (Exception ex) {
return false; return false;
} }
} }

View File

@ -37,20 +37,22 @@ public class DisguiseCommand extends DisguiseBaseCommand implements TabCompleter
return true; return true;
} }
if (hasHitRateLimit(sender)) {
return true;
}
Disguise disguise; Disguise disguise;
try { try {
disguise = DisguiseParser.parseDisguise(sender, (Entity) sender, getPermNode(), disguise = DisguiseParser
DisguiseUtilities.split(StringUtils.join(args, " ")), getPermissions(sender)); .parseDisguise(sender, (Entity) sender, getPermNode(), DisguiseUtilities.split(StringUtils.join(args, " ")), getPermissions(sender));
} } catch (DisguiseParseException ex) {
catch (DisguiseParseException ex) {
if (ex.getMessage() != null) { if (ex.getMessage() != null) {
DisguiseUtilities.sendMessage(sender, ex.getMessage()); DisguiseUtilities.sendMessage(sender, ex.getMessage());
} }
return true; return true;
} } catch (Throwable ex) {
catch (Throwable ex) {
ex.printStackTrace(); ex.printStackTrace();
return true; return true;
} }
@ -69,10 +71,10 @@ public class DisguiseCommand extends DisguiseBaseCommand implements TabCompleter
if (!setViewDisguise(args)) { if (!setViewDisguise(args)) {
// They prefer to have the opposite of whatever the view disguises option is // They prefer to have the opposite of whatever the view disguises option is
if (DisguiseAPI.hasSelfDisguisePreference(disguise.getEntity()) && if (DisguiseAPI.hasSelfDisguisePreference(disguise.getEntity()) && disguise.isSelfDisguiseVisible() == DisguiseConfig.isViewDisguises()) {
disguise.isSelfDisguiseVisible() == DisguiseConfig.isViewDisguises())
disguise.setViewSelfDisguise(!disguise.isSelfDisguiseVisible()); disguise.setViewSelfDisguise(!disguise.isSelfDisguiseVisible());
} }
}
if (!DisguiseAPI.isActionBarShown(disguise.getEntity())) { if (!DisguiseAPI.isActionBarShown(disguise.getEntity())) {
disguise.setNotifyBar(DisguiseConfig.NotifyBar.NONE); disguise.setNotifyBar(DisguiseConfig.NotifyBar.NONE);
@ -91,8 +93,9 @@ public class DisguiseCommand extends DisguiseBaseCommand implements TabCompleter
private boolean setViewDisguise(String[] strings) { private boolean setViewDisguise(String[] strings) {
for (String string : strings) { for (String string : strings) {
if (!string.equalsIgnoreCase("setSelfDisguiseVisible")) if (!string.equalsIgnoreCase("setSelfDisguiseVisible")) {
continue; continue;
}
return true; return true;
} }

View File

@ -41,6 +41,10 @@ public class DisguiseEntityCommand extends DisguiseBaseCommand implements TabCom
return true; return true;
} }
if (hasHitRateLimit(sender)) {
return true;
}
String[] disguiseArgs = DisguiseUtilities.split(StringUtils.join(args, " ")); String[] disguiseArgs = DisguiseUtilities.split(StringUtils.join(args, " "));
Disguise testDisguise; Disguise testDisguise;

View File

@ -48,6 +48,10 @@ public class DisguisePlayerCommand extends DisguiseBaseCommand implements TabCom
return true; return true;
} }
if (hasHitRateLimit(sender)) {
return true;
}
Entity entityTarget = Bukkit.getPlayer(args[0]); Entity entityTarget = Bukkit.getPlayer(args[0]);
if (entityTarget == null) { if (entityTarget == null) {

View File

@ -66,6 +66,10 @@ public class DisguiseRadiusCommand extends DisguiseBaseCommand implements TabCom
return true; return true;
} }
if (hasHitRateLimit(sender)) {
return true;
}
if (args[0].equalsIgnoreCase(TranslateType.DISGUISES.get("EntityType")) || args[0].equalsIgnoreCase(TranslateType.DISGUISES.get("EntityType") + "s")) { if (args[0].equalsIgnoreCase(TranslateType.DISGUISES.get("EntityType")) || args[0].equalsIgnoreCase(TranslateType.DISGUISES.get("EntityType") + "s")) {
ArrayList<String> classes = new ArrayList<>(); ArrayList<String> classes = new ArrayList<>();

View File

@ -125,6 +125,7 @@ public enum LibsMsg {
MADE_REF(ChatColor.RED + "Constructed a %s disguise! Your reference is %s"), MADE_REF(ChatColor.RED + "Constructed a %s disguise! Your reference is %s"),
MADE_REF_EXAMPLE(ChatColor.RED + "Example usage: /disguise %s"), MADE_REF_EXAMPLE(ChatColor.RED + "Example usage: /disguise %s"),
NO_CONSOLE(ChatColor.RED + "You may not use this command from the console!"), NO_CONSOLE(ChatColor.RED + "You may not use this command from the console!"),
TOO_FAST(ChatColor.RED + "You are using the disguise command too fast!"),
NO_MODS(ChatColor.RED + "%s is not using any mods!"), NO_MODS(ChatColor.RED + "%s is not using any mods!"),
MODS_LIST(ChatColor.DARK_GREEN + "%s has the mods:" + ChatColor.AQUA + " %s"), MODS_LIST(ChatColor.DARK_GREEN + "%s has the mods:" + ChatColor.AQUA + " %s"),
NO_PERM(ChatColor.RED + "You are forbidden to use this command."), NO_PERM(ChatColor.RED + "You are forbidden to use this command."),

View File

@ -137,6 +137,8 @@ permissions:
description: Allows the command user to set names on different heights description: Allows the command user to set names on different heights
libsdisguises.grabhead: libsdisguises.grabhead:
description: Allows the command user to use /grabhead description: Allows the command user to use /grabhead
libsdisguises.ratelimitbypass:
description: Allows a living player to bypass the 500ms rate limit on disguise commands, used to prevent crashes
libsdisguises.seecmd: libsdisguises.seecmd:
description: See all commands in tab-completion description: See all commands in tab-completion
default: true default: true