diff --git a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java index 53f04a9f..a59c4ff1 100644 --- a/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java +++ b/NCPCommons/src/main/java/fr/neatmonster/nocheatplus/utilities/ds/prefixtree/CharPrefixTree.java @@ -106,6 +106,20 @@ public class CharPrefixTree, L extends CharLookupEntry> return false; } + /** + * Test hasPrefixWords for each given argument. + * @param inputs + * @return true if hasPrefixWords returns ture for any of the inputs, false otherwise. + */ + public boolean hasAnyPrefixWords(final String... inputs){ + for (int i = 0; i < inputs.length; i++){ + if (hasPrefixWords(inputs[i])){ + return true; + } + } + return false; + } + public boolean isPrefix(final char[] chars){ return isPrefix(toCharacterList(chars)); } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java index 9b3280b2..a7b42687 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatConfig.java @@ -129,7 +129,7 @@ public class ChatConfig extends AsyncCheckConfig { public final String loginsKickMessage; public final long loginsStartupDelay; - public final boolean opInConsoleOnly; + public final boolean consoleOnlyCheck; public final boolean relogCheck; @@ -223,7 +223,7 @@ public class ChatConfig extends AsyncCheckConfig { relogWarningTimeout = config.getLong(ConfPaths.CHAT_RELOG_WARNING_TIMEOUT); relogActions = config.getOptimizedActionList(ConfPaths.CHAT_RELOG_ACTIONS, Permissions.CHAT_RELOG); - opInConsoleOnly = config.getBoolean(ConfPaths.MISCELLANEOUS_OPINCONSOLEONLY); + consoleOnlyCheck = config.getBoolean(ConfPaths.PROTECT_COMMANDS_CONSOLEONLY_ACTIVE); } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java index f8dc7ee3..23935c07 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/checks/chat/ChatListener.java @@ -1,6 +1,9 @@ package fr.neatmonster.nocheatplus.checks.chat; +import java.util.Collection; + import org.bukkit.ChatColor; +import org.bukkit.command.Command; import org.bukkit.entity.Player; import org.bukkit.event.EventHandler; import org.bukkit.event.EventPriority; @@ -63,7 +66,10 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe private final SimpleCharPrefixTree commandExclusions = new SimpleCharPrefixTree(); /** Commands to be handled as chat. */ - private final SimpleCharPrefixTree chatCommands = new SimpleCharPrefixTree(); + private final SimpleCharPrefixTree chatCommands = new SimpleCharPrefixTree(); + + /** Commands not to be executed in-game. */ + private final SimpleCharPrefixTree consoleOnlyCommands = new SimpleCharPrefixTree(); public ChatListener(){ super(CheckType.CHAT); @@ -72,11 +78,35 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe // (text inits in constructor.) } + /** + * Clear tree and feed lower case. Add versions with "/" if missing. + * @param tree + * @param inputs + */ + private void feedCommands(SimpleCharPrefixTree tree, Collection inputs){ + tree.clear(); + tree.feedAll(inputs, false, true); + for (String input : inputs){ + if (!input.trim().startsWith("/")){ + tree.feed("/" + input.trim().toLowerCase()); + } + } + } + + /** + * Read string list from config and call feedCommands(tree, list). + * @param tree + * @param config + * @param configPath + */ + private void feedCommands(SimpleCharPrefixTree tree, ConfigFile config, String configPath){ + feedCommands(tree, config.getStringList(configPath)); + } + private void initFilters(ConfigFile config) { - commandExclusions.clear(); - commandExclusions.feedAll(config.getStringList(ConfPaths.CHAT_COMMANDS_EXCLUSIONS), false, true); - chatCommands.clear(); - chatCommands.feedAll(config.getStringList(ConfPaths.CHAT_COMMANDS_HANDLEASCHAT), false, true); + feedCommands(consoleOnlyCommands, config, ConfPaths.PROTECT_COMMANDS_CONSOLEONLY_CMDS); + feedCommands(chatCommands, config, ConfPaths.CHAT_COMMANDS_HANDLEASCHAT); + feedCommands(commandExclusions, config, ConfPaths.CHAT_COMMANDS_EXCLUSIONS); } @EventHandler(priority=EventPriority.MONITOR) @@ -142,43 +172,54 @@ public class ChatListener extends CheckListener implements INotifyReload, JoinLe // Tell TickTask to update cached permissions. TickTask.requestPermissionUpdate(player.getName(), CheckType.CHAT); - - // Trim is necessary because the server accepts leading spaces with commands. - String lcMessage = event.getMessage().trim().toLowerCase(); - final String alias = lcMessage.split(" ")[0].substring(1); - final String commandLabel = CommandUtil.getCommandLabel(alias, false); final ChatConfig cc = ChatConfig.getConfig(player); - - // Prevent /op and /deop commands from being used by players. - if (cc.opInConsoleOnly && (commandLabel.equals("op") || commandLabel.equals("deop"))) { - player.sendMessage(ChatColor.RED + "I'm sorry, but this command can't be executed in chat. Use the console instead!"); - event.setCancelled(true); - return; - } - - // First the color check. - if (color.isEnabled(player)) event.setMessage(color.check(player, event.getMessage(), true)); + + // Checks that replace parts of the message (color). + if (color.isEnabled(player)){ + event.setMessage(color.check(player, event.getMessage(), true)); + } - // Reset lcMessage (might be canged by color check). - lcMessage = event.getMessage().trim().toLowerCase(); - final boolean handleAsChat = chatCommands.hasPrefixWords(lcMessage); + // Trim is necessary because the server accepts leading spaces with commands. + final String message = event.getMessage(); + final String lcMessage = message.trim().toLowerCase(); + final String[] split = lcMessage.split(" ", 2); + final String alias = split[0].substring(1); + final Command command = CommandUtil.getCommand(alias); + final String lcAltMessage; + if (command != null){ + lcAltMessage = "/" + command.getLabel().toLowerCase() + (split.length > 1 ? (" " + split[1]) : ""); + } + else{ + lcAltMessage = lcMessage; + } + + // Prevent /op and /deop commands from being used by players. + if (cc.consoleOnlyCheck && consoleOnlyCommands.hasAnyPrefixWords(lcMessage, lcAltMessage)) { + if (command == null || command.testPermission(player)){ + player.sendMessage(ChatColor.RED + "I'm sorry, but this command can't be executed in chat. Use the console instead!"); + } + event.setCancelled(true); + return; + } - // Then the no pwnage check. + // Handle as chat or command. + final boolean handleAsChat = chatCommands.hasAnyPrefixWords(lcMessage, lcAltMessage); if (handleAsChat){ // Treat as chat. - if (text.isEnabled(player) && text.check(player, event.getMessage(), captcha, true)) + // TODO: At least cut off the command (!). + if (text.isEnabled(player) && text.check(player, message, captcha, true)) event.setCancelled(true); } - else if (!commandExclusions.hasPrefixWords(lcMessage)){ + else if (!commandExclusions.hasAnyPrefixWords(lcMessage, lcAltMessage)){ // Treat as command. - if (commands.isEnabled(player) && commands.check(player, event.getMessage(), captcha)) + if (commands.isEnabled(player) && commands.check(player, message, captcha)) event.setCancelled(true); } } - /** + /** * We listen to this type of events to prevent spambots from login to the server. * * @param event diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java index 570f9d29..c0da5f23 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/ConfPaths.java @@ -70,7 +70,6 @@ public abstract class ConfPaths { */ @GlobalConfig private static final String MISCELLANEOUS = "miscellaneous."; - public static final String MISCELLANEOUS_OPINCONSOLEONLY = MISCELLANEOUS + "opinconsoleonly"; public static final String MISCELLANEOUS_CHECKFORUPDATES = MISCELLANEOUS + "checkforupdates"; public static final String MISCELLANEOUS_UPDATETIMEOUT = MISCELLANEOUS + "updatetimeout"; public static final String MISCELLANEOUS_REPORTTOMETRICS = MISCELLANEOUS + "reporttometrics"; @@ -107,6 +106,12 @@ public abstract class ConfPaths { private static final String PROTECT_CLIENTS_MOTD = PROTECT_CLIENTS + "motd."; public static final String PROTECT_CLIENTS_MOTD_ACTIVE = PROTECT_CLIENTS_MOTD + "active"; public static final String PROTECT_CLIENTS_MOTD_ALLOWALL = PROTECT_CLIENTS_MOTD + "allowall"; + // Other commands settings + @GlobalConfig + private static final String PROTECT_COMMANDS = PROTECT + "commands."; + private static final String PROTECT_COMMANDS_CONSOLEONLY = PROTECT_COMMANDS + "consoleonly."; + public static final String PROTECT_COMMANDS_CONSOLEONLY_ACTIVE = PROTECT_COMMANDS_CONSOLEONLY + "active"; + public static final String PROTECT_COMMANDS_CONSOLEONLY_CMDS = PROTECT_COMMANDS_CONSOLEONLY + "commands"; // Plugins settings. private static final String PROTECT_PLUGINS = PROTECT + "plugins."; @GlobalConfig @@ -118,7 +123,8 @@ public abstract class ConfPaths { private static final String PROTECT_PLUGINS_HIDE_NOPERMISSION = PROTECT_PLUGINS_HIDE + "nopermission."; public static final String PROTECT_PLUGINS_HIDE_NOPERMISSION_MSG = PROTECT_PLUGINS_HIDE_NOPERMISSION + "message"; public static final String PROTECT_PLUGINS_HIDE_NOPERMISSION_CMDS = PROTECT_PLUGINS_HIDE_NOPERMISSION + "commands"; - + + // Checks! private static final String CHECKS = "checks."; /** Debug flag to debug all checks (!), individual sections debug flags override this, if present. */ public static final String CHECKS_DEBUG = CHECKS + SUB_DEBUG; @@ -669,4 +675,6 @@ public abstract class ConfPaths { public static final String PROTECT_PLUGINS_HIDE_MSG_NOCOMMAND = "protection.plugins.hide.messages.unknowncommand"; @Moved(newPath = PROTECT_PLUGINS_HIDE_NOPERMISSION_MSG) public static final String PROTECT_PLUGINS_HIDE_MSG_NOPERMISSION = "protection.plugins.hide.messages.nopermission"; + @Moved(newPath = PROTECT_COMMANDS_CONSOLEONLY_ACTIVE) + public static final String MISCELLANEOUS_OPINCONSOLEONLY = "miscellaneous.opinconsoleonly"; } diff --git a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java index 798f8b94..badfd278 100644 --- a/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java +++ b/NCPCore/src/main/java/fr/neatmonster/nocheatplus/config/DefaultConfig.java @@ -72,7 +72,6 @@ public class DefaultConfig extends ConfigFile { * d8b Y8b Y8b 888 Y88D Y888 , 888 , 888 888 ,ee 888 888 888 888 , Y888 888P Y888 888P Y88D * d888b Y8b Y8b 888 d,dP "88,e8' "YeeP" 888 888 "88 888 888 888 "YeeP" "88 88" "88 88" d,dP */ - set(ConfPaths.MISCELLANEOUS_OPINCONSOLEONLY, false); set(ConfPaths.MISCELLANEOUS_MANAGELISTENERS, false); // set(ConfPaths.MISCELLANEOUS_CHECKFORUPDATES, true); set(ConfPaths.MISCELLANEOUS_REPORTTOMETRICS, true); @@ -98,6 +97,9 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.PROTECT_PLUGINS_HIDE_NOPERMISSION_CMDS, Arrays.asList("plugins", "version", "icanhasbukkit")); set(ConfPaths.PROTECT_PLUGINS_HIDE_NOCOMMAND_MSG, "Unknown command. Type \"/help\" for help."); set(ConfPaths.PROTECT_PLUGINS_HIDE_NOCOMMAND_CMDS, new LinkedList()); + // Commands (other). + set(ConfPaths.PROTECT_COMMANDS_CONSOLEONLY_ACTIVE, false); + set(ConfPaths.PROTECT_COMMANDS_CONSOLEONLY_CMDS, Arrays.asList("op", "deop")); // Client motd. set(ConfPaths.PROTECT_CLIENTS_MOTD_ACTIVE, true); set(ConfPaths.PROTECT_CLIENTS_MOTD_ALLOWALL, false); @@ -202,7 +204,7 @@ public class DefaultConfig extends ConfigFile { set(ConfPaths.CHAT_COMMANDS_CHECK, true); set(ConfPaths.CHAT_COMMANDS_EXCLUSIONS, new ArrayList()); - set(ConfPaths.CHAT_COMMANDS_HANDLEASCHAT, Arrays.asList("/me")); + set(ConfPaths.CHAT_COMMANDS_HANDLEASCHAT, Arrays.asList("me")); set(ConfPaths.CHAT_COMMANDS_LEVEL, 10); set(ConfPaths.CHAT_COMMANDS_SHORTTERM_TICKS, 18); set(ConfPaths.CHAT_COMMANDS_SHORTTERM_LEVEL, 3);