diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..615d3af --- /dev/null +++ b/.gitignore @@ -0,0 +1,5 @@ +/.classpath +/.project +/.settings +/bin +/target \ No newline at end of file diff --git a/README.md b/README.md new file mode 100644 index 0000000..f22d23b --- /dev/null +++ b/README.md @@ -0,0 +1 @@ +Will write this when I have time. \ No newline at end of file diff --git a/pom.xml b/pom.xml new file mode 100644 index 0000000..b161649 --- /dev/null +++ b/pom.xml @@ -0,0 +1,29 @@ + + 4.0.0 + xAuth + xAuth + 0.0.1-SNAPSHOT + + + org.bukkit + bukkit + 0.0.1-SNAPSHOT + jar + compile + + + org.bukkit + craftbukkit + 0.0.1-SNAPSHOT + jar + compile + + + com.nijikokun.bukkit + Permissions + 2.7 + jar + compile + + + \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/CommandHandler.java b/src/main/java/com/cypherx/xauth/CommandHandler.java new file mode 100644 index 0000000..c158194 --- /dev/null +++ b/src/main/java/com/cypherx/xauth/CommandHandler.java @@ -0,0 +1,361 @@ +package com.cypherx.xauth; + +import org.bukkit.Server; +import org.bukkit.command.Command; +import org.bukkit.entity.Player; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.craftbukkit.CraftServer; + +public class CommandHandler +{ + private final xAuth plugin; + private final PluginDescriptionFile pdfFile; + + public CommandHandler(final xAuth instance) + { + plugin = instance; + pdfFile = plugin.getDescription(); + } + + public void handlePlayerCommand(Player player, Command cmd, String[] args) + { + if (cmd.getName().equalsIgnoreCase("register")) + { + if (args.length != 1) + player.sendMessage(xAuth.strings.getString("register.usage")); + else if (!xAuth.settings.getBool("registration.enabled")) + player.sendMessage(xAuth.strings.getString("register.err.disabled")); + else if (plugin.isRegistered(player.getName())) + player.sendMessage(xAuth.strings.getString("register.err.registered")); + else if (!plugin.isValidPass(args[0])) + player.sendMessage(xAuth.strings.getString("password.invalid", xAuth.settings.getInt("password.min-length"))); + else + { + plugin.addAuth(player.getName(), args[0]); + plugin.login(player); + player.sendMessage(xAuth.strings.getString("register.success1")); + player.sendMessage(xAuth.strings.getString("register.success2", args[0])); + System.out.println("[" + pdfFile.getName() + "] Player '" + player.getName() + "' has registered"); + } + } + else if (cmd.getName().equalsIgnoreCase("login")) + { + if (args.length != 1) + player.sendMessage(xAuth.strings.getString("login.usage")); + else if (!plugin.isRegistered(player.getName())) + player.sendMessage(xAuth.strings.getString("login.err.registered")); + else if (plugin.sessionExists(player.getName())) + player.sendMessage(xAuth.strings.getString("login.err.logged")); + else + { + if (plugin.checkPass(player, args[0])) + { + if (xAuth.settings.getBool("login.strikes.enabled")) + plugin.clearStrikes(player); + + plugin.login(player); + player.sendMessage(xAuth.strings.getString("login.success")); + System.out.println("[" + pdfFile.getName() + "] Player '" + player.getName() + "' has authenticated"); + } + else + { + player.sendMessage(xAuth.strings.getString("login.err.password")); + + if (xAuth.settings.getBool("login.strikes.enabled")) + { + plugin.addStrike(player); + + if (plugin.getStrikes(player) >= xAuth.settings.getInt("login.strikes.amount")) + { + String addr = player.getAddress().getAddress().getHostAddress(); + Server server = plugin.getServer(); + server.dispatchCommand(((CraftServer)server).getServer().console, "ban-ip " + addr); + player.kickPlayer(xAuth.strings.getString("login.err.kick")); + plugin.clearStrikes(player); + System.out.println("[" + pdfFile.getName() + "] " + addr + " banned by Strike system"); + } + } + } + } + } + else if (cmd.getName().equalsIgnoreCase("changepw")) + { + if (plugin.canUseCommand(player, "xauth.admin.changepw")) + { + if (args.length == 1) + { + if (!plugin.sessionExists(player.getName())) + player.sendMessage(xAuth.strings.getString("changepw.err.login")); + else if (!xAuth.settings.getBool("misc.allow-changepw")) + player.sendMessage(xAuth.strings.getString("changepw.err.disabled")); + else if (!plugin.isValidPass(args[0])) + player.sendMessage(xAuth.strings.getString("password.invalid", xAuth.settings.getInt("password.min-length"))); + else + { + plugin.changePass(player.getName(), args[0]); + player.sendMessage(xAuth.strings.getString("changepw.success.self", args[0])); + System.out.println("[" + pdfFile.getName() + "] Player '" + player.getName() + "' has changed their password"); + } + } + else if (args.length == 2) + { + if (!plugin.isRegistered(args[0])) + player.sendMessage(xAuth.strings.getString("changepw.err.registered")); + else + { + plugin.changePass(args[0], args[1]); + player.sendMessage(xAuth.strings.getString("changepw.success.other")); + System.out.println("[" + pdfFile.getName() + "] " + player.getName() + " has changed " + args[0] + "'s password"); + } + } + else + player.sendMessage(xAuth.strings.getString("changepw.usage2")); + } + else + { + if (args.length != 1) + player.sendMessage(xAuth.strings.getString("changepw.usage1")); + else if (!plugin.sessionExists(player.getName())) + player.sendMessage(xAuth.strings.getString("changepw.err.login")); + else if (!xAuth.settings.getBool("misc.allow-changepw")) + player.sendMessage(xAuth.strings.getString("changepw.err.disabled")); + else if (!plugin.isValidPass(args[0])) + player.sendMessage(xAuth.strings.getString("password.invalid", xAuth.settings.getInt("password.min-length"))); + else + { + plugin.changePass(player.getName(), args[0]); + player.sendMessage(xAuth.strings.getString("changepw.success.self", args[0])); + System.out.println("[" + pdfFile.getName() + "] Player '" + player.getName() + "' has changed their password"); + } + } + } + else if (cmd.getName().equalsIgnoreCase("unregister")) + { + if (plugin.canUseCommand(player, "xauth.admin.unregister")) + { + if (args.length != 1) + player.sendMessage(xAuth.strings.getString("unregister.usage")); + else if (!plugin.isRegistered(args[0])) + player.sendMessage(xAuth.strings.getString("changepw.err.registered")); + else + { + plugin.removeAuth(args[0]); + Player target = plugin.getServer().getPlayer(args[0]); + + if (target != null) + { + if (plugin.mustRegister(target)) + plugin.saveInventory(target); + target.sendMessage(xAuth.strings.getString("unregister.target")); + } + + player.sendMessage(xAuth.strings.getString("unregister.success", args[0])); + System.out.println("[" + pdfFile.getName() + "] " + player.getName() + " has unregistered " + args[0]); + } + } + } + else if (cmd.getName().equalsIgnoreCase("authreload")) + { + if (plugin.canUseCommand(player, "xauth.admin.reload")) + { + plugin.reload(); + player.sendMessage(xAuth.strings.getString("reload.success")); + } + } + else if (cmd.getName().equalsIgnoreCase("toggle")) + { + if (plugin.canUseCommand(player, "xauth.admin.toggle")) + { + String node = null; + + if (args.length < 1) + { + player.sendMessage(xAuth.strings.getString("toggle.usage")); + return; + } + + if (args[0].equalsIgnoreCase("reg")) + node = "registration.enabled"; + else if (args[0].equalsIgnoreCase("changepw")) + node = "misc.allow-changepw"; + else if (args[0].equalsIgnoreCase("autosave")) + node = "misc.autosave"; + else if (args[0].equalsIgnoreCase("filter")) + node = "filter.enabled"; + else if (args[0].equalsIgnoreCase("blankname")) + node = "filter.blankname"; + else if (args[0].equalsIgnoreCase("verifyip")) + node = "session.verifyip"; + else if (args[0].equalsIgnoreCase("strike")) + node = "login.strikes.enabled"; + else if (args[0].equalsIgnoreCase("forcereg")) + node = "registration.forced"; + else + { + player.sendMessage(xAuth.strings.getString("toggle.usage")); + return; + } + + Boolean b = xAuth.settings.getBool(node); + xAuth.settings.updateValue(node, (b ? false : true)); + player.sendMessage(xAuth.strings.getString("toggle.success", + (b ? xAuth.strings.getString("misc.disabled") : xAuth.strings.getString("misc.enabled")))); + } + } + else if (cmd.getName().equalsIgnoreCase("logout")) + { + //logout current player + if (args.length == 0) + plugin.killSession(player); + //logout other player + else + { + if (plugin.canUseCommand(player, "xauth.admin.logout")) + { + String target = buildString(args); + if (!plugin.sessionExists(target)) + player.sendMessage(xAuth.strings.getString("logout.err.session")); + else + { + Player pTarget = plugin.getServer().getPlayer(target); + plugin.removeSession(target); + + if (pTarget != null) + { + plugin.saveInventory(pTarget); + pTarget.sendMessage(xAuth.strings.getString("logout.success.ended")); + } + + player.sendMessage(xAuth.strings.getString("logout.success.other", target)); + } + } + } + } + } + + public void handleConsoleCommand(Command cmd, String[] args) + { + if (cmd.getName().equalsIgnoreCase("register")) + { + if (args.length != 2) + System.out.println("Correct Usage: /register "); + else if (plugin.isRegistered(args[0])) + System.out.println("Player '" + args[0] + "' is already registered"); + else + { + plugin.addAuth(args[0], args[1]); + System.out.println(args[0] + " has been registered with password: " + args[1]); + } + } + else if (cmd.getName().equalsIgnoreCase("changepw")) + { + if (args.length != 2) + System.out.println("Correct Usage: /changepw "); + else if (!plugin.isRegistered(args[0])) + System.out.println("Player '" + args[0] + "' is not registered"); + else + { + plugin.changePass(args[0], args[1]); + System.out.println(args[0] + "'s password has been changed to: " + args[1]); + } + } + else if (cmd.getName().equalsIgnoreCase("unregister")) + { + if (args.length != 1) + System.out.println("Correct Usage: /unregister "); + else if (!plugin.isRegistered(args[0])) + System.out.println("Player '" + args[0] + "' is not registered"); + else + { + plugin.removeAuth(args[0]); + Player target = plugin.getServer().getPlayer(args[0]); + + if (target != null) + { + if (plugin.mustRegister(target)) + plugin.saveInventory(target); + target.sendMessage(xAuth.strings.getString("unregister.target")); + } + + System.out.println(args[0] + " has been unregistered"); + } + } + else if (cmd.getName().equalsIgnoreCase("authreload")) + plugin.reload(); + else if (cmd.getName().equalsIgnoreCase("toggle")) + { + String node = null; + + if (args.length < 1) + { + System.out.println("[" + pdfFile.getName() + "] Correct Usage: /toggle "); + return; + } + + if (args[0].equalsIgnoreCase("reg")) + node = "registration.enabled"; + else if (args[0].equalsIgnoreCase("changepw")) + node = "misc.allow-changepw"; + else if (args[0].equalsIgnoreCase("autosave")) + node = "misc.autosave"; + else if (args[0].equalsIgnoreCase("filter")) + node = "filter.enabled"; + else if (args[0].equalsIgnoreCase("blankname")) + node = "filter.blankname"; + else if (args[0].equalsIgnoreCase("verifyip")) + node = "session.verifyip"; + else if (args[0].equalsIgnoreCase("strike")) + node = "login.strikes.enabled"; + else if (args[0].equalsIgnoreCase("forcereg")) + node = "registration.forced"; + else + { + System.out.println("[" + pdfFile.getName() + "] Correct Usage: /toggle "); + return; + } + + Boolean b = xAuth.settings.getBool(node); + xAuth.settings.updateValue(node, (b ? false : true)); + System.out.println("[" + pdfFile.getName() + "] Node " + (b ? "disabled" : "enabled")); + } + else if (cmd.getName().equalsIgnoreCase("logout")) + { + if (args.length < 1) + System.out.println("Correct Usage: /logout "); + else + { + String target = buildString(args); + if (!plugin.sessionExists(target)) + System.out.println("[" + pdfFile.getName() + "] This player does not have an active session."); + else + { + Player pTarget = plugin.getServer().getPlayer(target); + plugin.removeSession(target); + + if (pTarget != null) + { + plugin.saveInventory(pTarget); + pTarget.sendMessage(xAuth.strings.getString("logout.success.ended")); + } + + System.out.println("[" + pdfFile.getName() + "] " + target + "'s session has been terminated"); + } + } + } + } + + private String buildString(String[] args) + { + String s = ""; + + for (int i = 0; i < args.length; i++) + { + if (args[i] == null) + continue; + + s += args[i] + " "; + } + + return s.trim(); + } +} \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/Session.java b/src/main/java/com/cypherx/xauth/Session.java new file mode 100644 index 0000000..9679bfd --- /dev/null +++ b/src/main/java/com/cypherx/xauth/Session.java @@ -0,0 +1,39 @@ +package com.cypherx.xauth; + +import java.util.Date; + +import org.bukkit.entity.Player; + +public class Session +{ + private Player player; + private Date loginTime; + private String addr; + + public Session(Player player) + { + this.player = player; + loginTime = new Date(); + addr = player.getAddress().getAddress().getHostAddress(); + } + + public Boolean isExpired(Date timeoutTime) + { + if (timeoutTime.compareTo(new Date()) < 0) + return true; + + return false; + } + + public Boolean isValidAddr(String testAddr) + { + if (addr.equals(testAddr)) + return true; + + return false; + } + + public Player getPlayer() { return player; } + + public Long getLoginTime() { return loginTime.getTime(); } +} \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/Settings.java b/src/main/java/com/cypherx/xauth/Settings.java new file mode 100644 index 0000000..1bb92bb --- /dev/null +++ b/src/main/java/com/cypherx/xauth/Settings.java @@ -0,0 +1,185 @@ +package com.cypherx.xauth; + +import java.io.File; +import java.util.Arrays; +import java.util.List; +import java.util.concurrent.ConcurrentHashMap; + +import org.bukkit.util.config.Configuration; + +public class Settings +{ + private static String[] keys = + { + "registration.enabled", + "registration.forced", + "misc.allow-changepw", + "misc.autosave", + "session.timeout", + "session.verifyip", + "notify.limit", + "misc.allowed-cmds", + "login.strikes.enabled", + "login.strikes.amount", + "filter.enabled", + "filter.allowed", + "filter.block-blankname", + "password.min-length", + "password.complexity.enabled", + "password.complexity.lowercase", + "password.complexity.uppercase", + "password.complexity.numbers", + "password.complexity.symbols" + }; + + private static final String[][] keyUpdates = + { + {"misc.allow-change-pw", "misc.allow-changepw"}, + {"misc.save-on-change", "misc.autosave"}, + {"registration.pw-min-length", "password.min-length"}, + {"security.filter.enabled", "filter.enabled"}, + {"security.filter.allowed", "filter.allowed"} + }; + + private static final String[] keyRemovals = + { + "security" + }; + + private final File file; + private static Configuration config; + private static final ConcurrentHashMap defaults = new ConcurrentHashMap(); + private static final ConcurrentHashMap settings = new ConcurrentHashMap(); + + public Settings(File f) + { + file = f; + config = new Configuration(file); + config.load(); + fillDefaults(); + + if (file.exists()) + { + updateKeys(); + removeKeys(); + } + + load(); + config.save(); + } + + public void fillDefaults() + { + defaults.put("registration.enabled", true); + defaults.put("registration.forced", true); + defaults.put("session.timeout", 3600); + defaults.put("session.verifyip", true); + defaults.put("notify.limit", 5); + defaults.put("misc.allow-changepw", true); + defaults.put("misc.allowed-cmds", Arrays.asList(new String[]{"/register", "/login"})); + defaults.put("misc.autosave", true); + defaults.put("login.strikes.enabled", true); + defaults.put("login.strikes.amount", 5); + defaults.put("filter.enabled", true); + defaults.put("filter.allowed", "abcdefghijklmnopqrstuvwxyz0123456789_- ()[]{}"); + defaults.put("filter.block-blankname", true); + defaults.put("password.min-length", 3); + defaults.put("password.complexity.enabled", false); + defaults.put("password.complexity.lowercase", false); + defaults.put("password.complexity.uppercase", false); + defaults.put("password.complexity.numbers", false); + defaults.put("password.complexity.symbols", false); + } + + public void updateKeys() + { + String fromKey, toKey; + Object holder; + for (String[] update : keyUpdates) + { + fromKey = update[0]; + if (config.getProperty(fromKey) != null) + { + toKey = update[1]; + holder = config.getProperty(fromKey); + config.removeProperty(fromKey); + config.setProperty(toKey, holder); + } + } + } + + private void removeKeys() + { + for (String key : keyRemovals) + { + if (config.getProperty(key) != null) + config.removeProperty(key); + } + } + + public void load() + { + for (String key : keys) + { + if (config.getProperty(key) == null) + config.setProperty(key, defaults.get(key)); + settings.put(key, config.getProperty(key)); + } + + //clear defaults to free memory + defaults.clear(); + } + + public void updateValue(String key, Object value) + { + settings.replace(key, value); + config.setProperty(key, value); + config.save(); + } + + public Boolean getBool(String key) + { + Object value = settings.get(key); + + if (value instanceof String) + return Boolean.parseBoolean((String)value); + + return (Boolean)settings.get(key); + } + + public int getInt(String key) + { + return (Integer)settings.get(key); + } + + public String getStr(String key) + { + return (String)settings.get(key); + } + + @SuppressWarnings("unchecked") + public List getStrList(String key) + { + //COMMAND_PREPROCESS error debugging + if (!(settings.get(key) instanceof List)) + { + System.out.println("[xAuth] COMMAND_PREPROCESS Error: Report this in the xAuth thread."); + System.out.println("[xAuth] Value:" + settings.get(key)); + System.out.println("[xAuth] Attempting to autocorrect.."); + xAuth.settings = new Settings(file); + } + + /*Object value = settings.get(key); + + if (value instanceof String[]) + System.out.println("string array"); + else if (value instanceof String) + System.out.println("string"); + else if (value instanceof List) + System.out.println("list"); + + System.out.println(value);*/ + + return (List)settings.get(key); + } +} \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/Strings.java b/src/main/java/com/cypherx/xauth/Strings.java new file mode 100644 index 0000000..91c1f29 --- /dev/null +++ b/src/main/java/com/cypherx/xauth/Strings.java @@ -0,0 +1,148 @@ +package com.cypherx.xauth; + +import java.util.concurrent.ConcurrentHashMap; +import java.io.File; + +import org.bukkit.util.config.Configuration; + +public class Strings +{ + private static String[] keys = + { + "register.login", "register.usage", "register.err.disabled", "register.err.registered", "register.success1", + "register.success2", "login.login", "login.usage", "login.err.registered", "login.err.logged", + "login.err.password", "login.err.kick", "login.success", "changepw.usage1", "changepw.usage2", "changepw.err.login", + "changepw.err.disabled", "changepw.err.registered", "changepw.success.self", "changepw.success.other", + "unregister.usage", "unregister.target", "unregister.success", "reload.success", "toggle.usage", "toggle.success", + "logout.err.session", "logout.success.ended", "logout.success.other", "password.invalid", "misc.illegal", "misc.reloaded", + "misc.enabled", "misc.disabled", "misc.filterkickmsg", "misc.blankkickmsg" + }; + + private static final String[][] keyUpdates = + { + {"register.err.password", "password.invalid"} + }; + + private static final String[] keyRemovals = + { + "toggle.err", + "toggle.success", + }; + + private static Configuration config; + private static final ConcurrentHashMap defaults = new ConcurrentHashMap(); + private static final ConcurrentHashMap strings = new ConcurrentHashMap(); + + public Strings(File file) + { + config = new Configuration(file); + config.load(); + fillDefaults(); + + if (file.exists()) + { + updateKeys(); + removeKeys(); + } + + load(); + config.save(); + } + + private void fillDefaults() + { + defaults.put("register.login", "&cYou are not registered. Please register using /register ."); + defaults.put("register.usage", "&cCorrect Usage: /register "); + defaults.put("register.err.disabled", "&cRegistrations are currently disabled."); + defaults.put("register.err.registered", "&cYou are already registered."); + defaults.put("register.success1", "&aYou have successfully registered!"); + defaults.put("register.success2", "&aYour password is: &f%1"); + + defaults.put("login.login", "&cPlease log in using /login ."); + defaults.put("login.usage", "&cCorrect Usage: /login "); + defaults.put("login.err.registered", "&cYou are not registered."); + defaults.put("login.err.logged", "&cYou are already logged in."); + defaults.put("login.err.password", "&cIncorrect password!"); + defaults.put("login.err.kick", "Too many incorrect passwords!"); + defaults.put("login.success", "&aYou are now logged in."); + + defaults.put("changepw.usage1", "&cCorrect Usage: /changepw "); + defaults.put("changepw.usage2", "&cCorrect Usage: /changepw [player] "); + defaults.put("changepw.err.login", "&cYou must login before changing your password!"); + defaults.put("changepw.err.disabled", "&cPassword changes are currently disabled."); + defaults.put("changepw.err.registered", "&cThis player is not registered!"); + defaults.put("changepw.success.self", "&aYour password has been changed to: &f%1"); + defaults.put("changepw.success.other", "&aPassword changed."); + + defaults.put("unregister.usage", "&cCorrect Usage: /unregister "); + defaults.put("unregister.target", "&cYou have been unregistered."); + defaults.put("unregister.success", "&a%1 has been unregistered."); + + defaults.put("reload.success", "&e[xAuth] Configuration and Accounts reloaded"); + + defaults.put("toggle.usage", "&cCorrect Usage: /toggle "); + defaults.put("toggle.success", "&e[xAuth] Node %1."); + + defaults.put("logout.err.session", "&cThis player does not have an active session."); + defaults.put("logout.success.ended", "&cYour session has been terminated. You must log in again."); + defaults.put("logout.success.other", "&a%1's session has been terminated."); + + defaults.put("password.invalid", "&cYour password must contain %1 or more characters."); + + defaults.put("misc.illegal", "&7You must be logged in to do that!"); + defaults.put("misc.reloaded", "&cServer reloaded! You must log in again."); + defaults.put("misc.enabled", "enabled"); + defaults.put("misc.disabled", "disabled"); + defaults.put("misc.filterkickmsg", "Your name contains one or more illegal characters."); + defaults.put("misc.blankkickmsg", "Blank names are not allowed."); + } + + private void updateKeys() + { + String fromKey, toKey, holder; + for (String[] update : keyUpdates) + { + fromKey = update[0]; + if (config.getProperty(fromKey) != null) + { + toKey = update[1]; + holder = config.getString(fromKey); + config.removeProperty(fromKey); + if (!toKey.equals("")) + config.setProperty(toKey, holder); + } + } + } + + private void removeKeys() + { + for (String key : keyRemovals) + { + if (config.getProperty(key) != null) + config.removeProperty(key); + } + } + + private void load() + { + for (String key : keys) + { + if (config.getProperty(key) == null) + config.setProperty(key, defaults.get(key)); + strings.put(key, config.getString(key).replace("&", "\u00a7")); + } + + //clear defaults to free memory + defaults.clear(); + } + + public String getString(String key) + { + return strings.get(key); + } + + public String getString(String key, Object replacement) + { + return strings.get(key).replace("%1", replacement.toString()); + } +} \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/Whirlpool.java b/src/main/java/com/cypherx/xauth/Whirlpool.java new file mode 100644 index 0000000..de10657 --- /dev/null +++ b/src/main/java/com/cypherx/xauth/Whirlpool.java @@ -0,0 +1,559 @@ +package com.cypherx.xauth; + +/** + * The Whirlpool hashing function. + * + *

+ * References + * + *

+ * The Whirlpool algorithm was developed by + * Paulo S. L. M. Barreto and + * Vincent Rijmen. + * + * See + * P.S.L.M. Barreto, V. Rijmen, + * ``The Whirlpool hashing function,'' + * First NESSIE workshop, 2000 (tweaked version, 2003), + * + * + * @author Paulo S.L.M. Barreto + * @author Vincent Rijmen. + * + * @version 3.0 (2003.03.12) + * + * ============================================================================= + * + * Differences from version 2.1: + * + * - Suboptimal diffusion matrix replaced by cir(1, 1, 4, 1, 8, 5, 2, 9). + * + * ============================================================================= + * + * Differences from version 2.0: + * + * - Generation of ISO/IEC 10118-3 test vectors. + * - Bug fix: nonzero carry was ignored when tallying the data length + * (this bug apparently only manifested itself when feeding data + * in pieces rather than in a single chunk at once). + * + * Differences from version 1.0: + * + * - Original S-box replaced by the tweaked, hardware-efficient version. + * + * ============================================================================= + * + * THIS SOFTWARE IS PROVIDED BY THE AUTHORS ''AS IS'' AND ANY EXPRESS + * OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED + * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE + * ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHORS OR CONTRIBUTORS BE + * LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR + * CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF + * SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR + * BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, + * WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE + * OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, + * EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. + * + */ + +import java.util.Arrays; + +class Whirlpool { + + /** + * The message digest size (in bits) + */ + public static final int DIGESTBITS = 512; + + /** + * The message digest size (in bytes) + */ + public static final int DIGESTBYTES = DIGESTBITS >>> 3; + + /** + * The number of rounds of the internal dedicated block cipher. + */ + protected static final int R = 10; + + /** + * The substitution box. + */ + private static final String sbox = + "\u1823\uc6E8\u87B8\u014F\u36A6\ud2F5\u796F\u9152" + + "\u60Bc\u9B8E\uA30c\u7B35\u1dE0\ud7c2\u2E4B\uFE57" + + "\u1577\u37E5\u9FF0\u4AdA\u58c9\u290A\uB1A0\u6B85" + + "\uBd5d\u10F4\ucB3E\u0567\uE427\u418B\uA77d\u95d8" + + "\uFBEE\u7c66\udd17\u479E\ucA2d\uBF07\uAd5A\u8333" + + "\u6302\uAA71\uc819\u49d9\uF2E3\u5B88\u9A26\u32B0" + + "\uE90F\ud580\uBEcd\u3448\uFF7A\u905F\u2068\u1AAE" + + "\uB454\u9322\u64F1\u7312\u4008\uc3Ec\udBA1\u8d3d" + + "\u9700\ucF2B\u7682\ud61B\uB5AF\u6A50\u45F3\u30EF" + + "\u3F55\uA2EA\u65BA\u2Fc0\udE1c\uFd4d\u9275\u068A" + + "\uB2E6\u0E1F\u62d4\uA896\uF9c5\u2559\u8472\u394c" + + "\u5E78\u388c\ud1A5\uE261\uB321\u9c1E\u43c7\uFc04" + + "\u5199\u6d0d\uFAdF\u7E24\u3BAB\ucE11\u8F4E\uB7EB" + + "\u3c81\u94F7\uB913\u2cd3\uE76E\uc403\u5644\u7FA9" + + "\u2ABB\uc153\udc0B\u9d6c\u3174\uF646\uAc89\u14E1" + + "\u163A\u6909\u70B6\ud0Ed\ucc42\u98A4\u285c\uF886"; + + private static long[][] C = new long[8][256]; + private static long[] rc = new long[R + 1]; + + static { + for (int x = 0; x < 256; x++) { + char c = sbox.charAt(x/2); + long v1 = ((x & 1) == 0) ? c >>> 8 : c & 0xff; + long v2 = v1 << 1; + if (v2 >= 0x100L) { + v2 ^= 0x11dL; + } + long v4 = v2 << 1; + if (v4 >= 0x100L) { + v4 ^= 0x11dL; + } + long v5 = v4 ^ v1; + long v8 = v4 << 1; + if (v8 >= 0x100L) { + v8 ^= 0x11dL; + } + long v9 = v8 ^ v1; + /* + * build the circulant table C[0][x] = S[x].[1, 1, 4, 1, 8, 5, 2, 9]: + */ + C[0][x] = + (v1 << 56) | (v1 << 48) | (v4 << 40) | (v1 << 32) | + (v8 << 24) | (v5 << 16) | (v2 << 8) | (v9 ); + /* + * build the remaining circulant tables C[t][x] = C[0][x] rotr t + */ + for (int t = 1; t < 8; t++) { + C[t][x] = (C[t - 1][x] >>> 8) | ((C[t - 1][x] << 56)); + } + } + /* + for (int t = 0; t < 8; t++) { + System.out.println("static const u64 C" + t + "[256] = {"); + for (int i = 0; i < 64; i++) { + System.out.print(" "); + for (int j = 0; j < 4; j++) { + String v = Long.toHexString(C[t][4*i + j]); + while (v.length() < 16) { + v = "0" + v; + } + System.out.print(" LL(0x" + v + "),"); + } + System.out.println(); + } + System.out.println("};"); + System.out.println(); + } + System.out.println(); + //*/ + + /* + * build the round constants: + */ + rc[0] = 0L; /* not used (assigment kept only to properly initialize all variables) */ + for (int r = 1; r <= R; r++) { + int i = 8*(r - 1); + rc[r] = + (C[0][i ] & 0xff00000000000000L) ^ + (C[1][i + 1] & 0x00ff000000000000L) ^ + (C[2][i + 2] & 0x0000ff0000000000L) ^ + (C[3][i + 3] & 0x000000ff00000000L) ^ + (C[4][i + 4] & 0x00000000ff000000L) ^ + (C[5][i + 5] & 0x0000000000ff0000L) ^ + (C[6][i + 6] & 0x000000000000ff00L) ^ + (C[7][i + 7] & 0x00000000000000ffL); + } + /* + System.out.println("static const u64 rc[R + 1] = {"); + for (int r = 0; r <= R; r++) { + String v = Long.toHexString(rc[r]); + while (v.length() < 16) { + v = "0" + v; + } + System.out.println(" LL(0x" + v + "),"); + } + System.out.println("};"); + System.out.println(); + //*/ + } + + /** + * Global number of hashed bits (256-bit counter). + */ + protected byte[] bitLength = new byte[32]; + + /** + * Buffer of data to hash. + */ + protected byte[] buffer = new byte[64]; + + /** + * Current number of bits on the buffer. + */ + protected int bufferBits = 0; + + /** + * Current (possibly incomplete) byte slot on the buffer. + */ + protected int bufferPos = 0; + + /** + * The hashing state. + */ + protected long[] hash = new long[8]; + protected long[] K = new long[8]; // the round key + protected long[] L = new long[8]; + protected long[] block = new long[8]; // mu(buffer) + protected long[] state = new long[8]; // the cipher state + + public Whirlpool() { + } + + /** + * The core Whirlpool transform. + */ + protected void processBuffer() { + /* + * map the buffer to a block: + */ + for (int i = 0, j = 0; i < 8; i++, j += 8) { + block[i] = + (((long)buffer[j ] ) << 56) ^ + (((long)buffer[j + 1] & 0xffL) << 48) ^ + (((long)buffer[j + 2] & 0xffL) << 40) ^ + (((long)buffer[j + 3] & 0xffL) << 32) ^ + (((long)buffer[j + 4] & 0xffL) << 24) ^ + (((long)buffer[j + 5] & 0xffL) << 16) ^ + (((long)buffer[j + 6] & 0xffL) << 8) ^ + (((long)buffer[j + 7] & 0xffL) ); + } + /* + * compute and apply K^0 to the cipher state: + */ + for (int i = 0; i < 8; i++) { + state[i] = block[i] ^ (K[i] = hash[i]); + } + /* + * iterate over all rounds: + */ + for (int r = 1; r <= R; r++) { + /* + * compute K^r from K^{r-1}: + */ + for (int i = 0; i < 8; i++) { + L[i] = 0L; + for (int t = 0, s = 56; t < 8; t++, s -= 8) { + L[i] ^= C[t][(int)(K[(i - t) & 7] >>> s) & 0xff]; + } + } + for (int i = 0; i < 8; i++) { + K[i] = L[i]; + } + K[0] ^= rc[r]; + /* + * apply the r-th round transformation: + */ + for (int i = 0; i < 8; i++) { + L[i] = K[i]; + for (int t = 0, s = 56; t < 8; t++, s -= 8) { + L[i] ^= C[t][(int)(state[(i - t) & 7] >>> s) & 0xff]; + } + } + for (int i = 0; i < 8; i++) { + state[i] = L[i]; + } + } + /* + * apply the Miyaguchi-Preneel compression function: + */ + for (int i = 0; i < 8; i++) { + hash[i] ^= state[i] ^ block[i]; + } + } + + /** + * Initialize the hashing state. + */ + public void NESSIEinit() { + Arrays.fill(bitLength, (byte)0); + bufferBits = bufferPos = 0; + buffer[0] = 0; // it's only necessary to cleanup buffer[bufferPos]. + Arrays.fill(hash, 0L); // initial value + } + + /** + * Delivers input data to the hashing algorithm. + * + * @param source plaintext data to hash. + * @param sourceBits how many bits of plaintext to process. + * + * This method maintains the invariant: bufferBits < 512 + */ + public void NESSIEadd(byte[] source, long sourceBits) { + /* + sourcePos + | + +-------+-------+------- + ||||||||||||||||||||| source + +-------+-------+------- + +-------+-------+-------+-------+-------+------- + |||||||||||||||||||||| buffer + +-------+-------+-------+-------+-------+------- + | + bufferPos + */ + int sourcePos = 0; // index of leftmost source byte containing data (1 to 8 bits). + int sourceGap = (8 - ((int)sourceBits & 7)) & 7; // space on source[sourcePos]. + int bufferRem = bufferBits & 7; // occupied bits on buffer[bufferPos]. + int b; + // tally the length of the added data: + long value = sourceBits; + for (int i = 31, carry = 0; i >= 0; i--) { + carry += (bitLength[i] & 0xff) + ((int)value & 0xff); + bitLength[i] = (byte)carry; + carry >>>= 8; + value >>>= 8; + } + // process data in chunks of 8 bits: + while (sourceBits > 8) { // at least source[sourcePos] and source[sourcePos+1] contain data. + // take a byte from the source: + b = ((source[sourcePos] << sourceGap) & 0xff) | + ((source[sourcePos + 1] & 0xff) >>> (8 - sourceGap)); + if (b < 0 || b >= 256) { + throw new RuntimeException("LOGIC ERROR"); + } + // process this byte: + buffer[bufferPos++] |= b >>> bufferRem; + bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos; + if (bufferBits == 512) { + // process data block: + processBuffer(); + // reset buffer: + bufferBits = bufferPos = 0; + } + buffer[bufferPos] = (byte)((b << (8 - bufferRem)) & 0xff); + bufferBits += bufferRem; + // proceed to remaining data: + sourceBits -= 8; + sourcePos++; + } + // now 0 <= sourceBits <= 8; + // furthermore, all data (if any is left) is in source[sourcePos]. + if (sourceBits > 0) { + b = (source[sourcePos] << sourceGap) & 0xff; // bits are left-justified on b. + // process the remaining bits: + buffer[bufferPos] |= b >>> bufferRem; + } else { + b = 0; + } + if (bufferRem + sourceBits < 8) { + // all remaining data fits on buffer[bufferPos], and there still remains some space. + bufferBits += sourceBits; + } else { + // buffer[bufferPos] is full: + bufferPos++; + bufferBits += 8 - bufferRem; // bufferBits = 8*bufferPos; + sourceBits -= 8 - bufferRem; + // now 0 <= sourceBits < 8; furthermore, all data is in source[sourcePos]. + if (bufferBits == 512) { + // process data block: + processBuffer(); + // reset buffer: + bufferBits = bufferPos = 0; + } + buffer[bufferPos] = (byte)((b << (8 - bufferRem)) & 0xff); + bufferBits += (int)sourceBits; + } + } + + /** + * Get the hash value from the hashing state. + * + * This method uses the invariant: bufferBits < 512 + */ + public void NESSIEfinalize(byte[] digest) { + // append a '1'-bit: + buffer[bufferPos] |= 0x80 >>> (bufferBits & 7); + bufferPos++; // all remaining bits on the current byte are set to zero. + // pad with zero bits to complete 512N + 256 bits: + if (bufferPos > 32) { + while (bufferPos < 64) { + buffer[bufferPos++] = 0; + } + // process data block: + processBuffer(); + // reset buffer: + bufferPos = 0; + } + while (bufferPos < 32) { + buffer[bufferPos++] = 0; + } + // append bit length of hashed data: + System.arraycopy(bitLength, 0, buffer, 32, 32); + // process data block: + processBuffer(); + // return the completed message digest: + for (int i = 0, j = 0; i < 8; i++, j += 8) { + long h = hash[i]; + digest[j ] = (byte)(h >>> 56); + digest[j + 1] = (byte)(h >>> 48); + digest[j + 2] = (byte)(h >>> 40); + digest[j + 3] = (byte)(h >>> 32); + digest[j + 4] = (byte)(h >>> 24); + digest[j + 5] = (byte)(h >>> 16); + digest[j + 6] = (byte)(h >>> 8); + digest[j + 7] = (byte)(h ); + } + } + + /** + * Delivers string input data to the hashing algorithm. + * + * @param source plaintext data to hash (ASCII text string). + * + * This method maintains the invariant: bufferBits < 512 + */ + public void NESSIEadd(String source) { + if (source.length() > 0) { + byte[] data = new byte[source.length()]; + for (int i = 0; i < source.length(); i++) { + data[i] = (byte)source.charAt(i); + } + NESSIEadd(data, 8*data.length); + } + } + + static String display(byte[] array) { + char[] val = new char[2*array.length]; + String hex = "0123456789ABCDEF"; + for (int i = 0; i < array.length; i++) { + int b = array[i] & 0xff; + val[2*i] = hex.charAt(b >>> 4); + val[2*i + 1] = hex.charAt(b & 15); + } + return String.valueOf(val); + } + + private static final int LONG_ITERATION = 100000000; + + /** + * Generate the NESSIE test vector set for Whirlpool. + * + * The test consists of: + * 1. hashing all bit strings containing only zero bits + * for all lengths from 0 to 1023; + * 2. hashing all 512-bit strings containing a single set bit; + * 3. the iterated hashing of the 512-bit string of zero bits a large number of times. + */ + public static void makeNESSIETestVectors() { + Whirlpool w = new Whirlpool(); + byte[] digest = new byte[64]; + byte[] data = new byte[128]; + Arrays.fill(data, (byte)0); + System.out.println("Message digests of strings of 0-bits and length L:"); + for (int i = 0; i < 1024; i++) { + w.NESSIEinit(); + w.NESSIEadd(data, i); + w.NESSIEfinalize(digest); + String s = Integer.toString(i); + s = " ".substring(s.length()) + s; + System.out.println(" L =" + s + ": " + display(digest)); + } + System.out.println("Message digests of all 512-bit strings S containing a single 1-bit:"); + data = new byte[512/8]; + Arrays.fill(data, (byte)0); + for (int i = 0; i < 512; i++) { + // set bit i: + data[i/8] |= 0x80 >>> (i % 8); + w.NESSIEinit(); + w.NESSIEadd(data, 512); + w.NESSIEfinalize(digest); + System.out.println(" S = " + display(data) + ": " + display(digest)); + // reset bit i: + data[i/8] = 0; + } + for (int i = 0; i < digest.length; i++) { + digest[i] = 0; + } + for (int i = 0; i < LONG_ITERATION; i++) { + w.NESSIEinit(); + w.NESSIEadd(digest, 512); + w.NESSIEfinalize(digest); + } + System.out.println("Iterated message digest computation (" + LONG_ITERATION + " times): " + display(digest)); + } + + /** + * Generate the ISO/IEC 10118-3 test vector set for Whirlpool. + */ + public static void makeISOTestVectors() { + Whirlpool w = new Whirlpool(); + byte[] digest = new byte[DIGESTBYTES]; + byte[] data = new byte[1000000]; + + Arrays.fill(data, (byte)0); + + System.out.println("1. In this example the data-string is the empty string, i.e. the string of length zero.\n"); + w.NESSIEinit(); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + System.out.println("2. In this example the data-string consists of a single byte, namely the ASCII-coded version of the letter 'a'.\n"); + w.NESSIEinit(); + w.NESSIEadd("a"); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + System.out.println("3. In this example the data-string is the three-byte string consisting of the ASCII-coded version of 'abc'.\n"); + w.NESSIEinit(); + w.NESSIEadd("abc"); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + System.out.println("4. In this example the data-string is the 14-byte string consisting of the ASCII-coded version of 'message digest'.\n"); + w.NESSIEinit(); + w.NESSIEadd("message digest"); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + System.out.println("5. In this example the data-string is the 26-byte string consisting of the ASCII-coded version of 'abcdefghijklmnopqrstuvwxyz'.\n"); + w.NESSIEinit(); + w.NESSIEadd("abcdefghijklmnopqrstuvwxyz"); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + System.out.println("6. In this example the data-string is the 62-byte string consisting of the ASCII-coded version of 'ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789'.\n"); + w.NESSIEinit(); + w.NESSIEadd("ABCDEFGHIJKLMNOPQRSTUVWXYZabcdefghijklmnopqrstuvwxyz0123456789"); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + System.out.println("7. In this example the data-string is the 80-byte string consisting of the ASCII-coded version of eight repetitions of '1234567890'.\n"); + w.NESSIEinit(); + w.NESSIEadd("12345678901234567890123456789012345678901234567890123456789012345678901234567890"); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + System.out.println("8. In this example the data-string is the 32-byte string consisting of the ASCII-coded version of 'abcdbcdecdefdefgefghfghighijhijk'.\n"); + w.NESSIEinit(); + w.NESSIEadd("abcdbcdecdefdefgefghfghighijhijk"); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + + Arrays.fill(data, (byte)'a'); + System.out.println("9. In this example the data-string is the 1000000-byte string consisting of the ASCII-coded version of 'a' repeated 10^6 times.\n"); + w.NESSIEinit(); + w.NESSIEadd(data, 8*1000000); + w.NESSIEfinalize(digest); + System.out.println("The hash-code is the following 512-bit string.\n\n" + display(digest) + "\n"); + } + + public static void main(String[] args) { + //makeNESSIETestVectors(); + makeISOTestVectors(); + } +} diff --git a/src/main/java/com/cypherx/xauth/xAuth.java b/src/main/java/com/cypherx/xauth/xAuth.java new file mode 100644 index 0000000..89f8c91 --- /dev/null +++ b/src/main/java/com/cypherx/xauth/xAuth.java @@ -0,0 +1,600 @@ +//xAuth 1.2.2 +//Built against Bukkit #653, CraftBukkit #674, and Permissions v2.7 + +package com.cypherx.xauth; + +import java.io.*; +import java.math.BigInteger; +import java.security.MessageDigest; +import java.util.concurrent.ConcurrentHashMap; +import java.util.ArrayList; +import java.util.Date; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +import net.minecraft.server.PropertyManager; +import org.bukkit.command.Command; +import org.bukkit.command.CommandSender; +import org.bukkit.command.ConsoleCommandSender; +import org.bukkit.entity.Player; +import org.bukkit.event.Cancellable; +import org.bukkit.event.Event; +import org.bukkit.event.Event.Priority; +import org.bukkit.inventory.ItemStack; +import org.bukkit.inventory.PlayerInventory; +import org.bukkit.plugin.java.JavaPlugin; +import org.bukkit.plugin.Plugin; +import org.bukkit.plugin.PluginDescriptionFile; +import org.bukkit.plugin.PluginManager; + +import com.nijiko.permissions.PermissionHandler; +import com.nijikokun.bukkit.Permissions.Permissions; + +/** + * xAuth for Bukkit + * + * @author CypherX + */ +public class xAuth extends JavaPlugin +{ + private final xAuthPlayerListener playerListener = new xAuthPlayerListener(this); + private final xAuthBlockListener blockListener = new xAuthBlockListener(this); + private final xAuthEntityListener entityListener = new xAuthEntityListener(this); + private static PluginDescriptionFile pdfFile; + + private static final String DIR = "plugins" + File.separator + "xAuth" + File.separator; + private static final String CONFIG_FILE = "config.yml"; + private static final String STRINGS_FILE = "strings.yml"; + private static final String AUTH_FILE = "auths.txt"; + + public static Settings settings; + public static Strings strings; + public static PermissionHandler Permissions; + + //autosave test code + private static Boolean fullyEnabled; + + private ConcurrentHashMap auths = new ConcurrentHashMap(); + private ConcurrentHashMap inventory = new ConcurrentHashMap(); + private ConcurrentHashMap armor = new ConcurrentHashMap(); + private ConcurrentHashMap sessions = new ConcurrentHashMap(); + private ConcurrentHashMap lastNotifyTimes = new ConcurrentHashMap(); + private ConcurrentHashMap strikes = new ConcurrentHashMap(); + private ArrayList illegalNames = new ArrayList(); + + public void onEnable() + { + fullyEnabled = false; + pdfFile = this.getDescription(); + + PropertyManager props = new PropertyManager(new File("server.properties")); + if (props.a("online-mode", true)) + { + System.out.println("[" + pdfFile.getName() + "] Stopping - Server is running in online-mode"); + this.setEnabled(false); + this.getServer().getPluginManager().disablePlugin(this); + return; + } + + File fDir = new File(DIR); + + if (!fDir.exists()) + fDir.mkdir(); + + try + { + File fAuths = new File(DIR + AUTH_FILE); + + if (!fAuths.exists()) + fAuths.createNewFile(); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + } + + settings = new Settings(new File(DIR + CONFIG_FILE)); + strings = new Strings(new File(DIR + STRINGS_FILE)); + getAuths(); + setupPermissions(); + + //Hide inventory of any players online while server is starting (means /reload was used) + Player[] players = getServer().getOnlinePlayers(); + if (players.length > 0) + { + for (Player player : players) + { + if (isRegistered(player.getName())) + { + saveInventory(player); + player.sendMessage(strings.getString("misc.reloaded")); + } + } + } + + PluginManager pm = getServer().getPluginManager(); + pm.registerEvent(Event.Type.PLAYER_CHAT, playerListener, Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_COMMAND_PREPROCESS, playerListener, Event.Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_DROP_ITEM, playerListener, Event.Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_PICKUP_ITEM, playerListener, Event.Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_INTERACT, playerListener, Event.Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_JOIN, playerListener, Event.Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_LOGIN, playerListener, Event.Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_MOVE, playerListener, Event.Priority.Lowest, this); + pm.registerEvent(Event.Type.PLAYER_QUIT, playerListener, Event.Priority.Lowest, this); + + pm.registerEvent(Event.Type.BLOCK_BREAK, blockListener, Priority.Lowest, this); + pm.registerEvent(Event.Type.BLOCK_PLACE, blockListener, Priority.Lowest, this); + + pm.registerEvent(Event.Type.ENTITY_DAMAGE, entityListener, Priority.Lowest, this); + pm.registerEvent(Event.Type.ENTITY_TARGET, entityListener, Priority.Lowest, this); + + System.out.println("[" + pdfFile.getName() + "]" + " v" + pdfFile.getVersion() + " Enabled!"); + + //autosave stuff + fullyEnabled = true; + } + + public void getAuths() + { + System.out.println("[" + pdfFile.getName() + "] Loading player accounts.."); + + try + { + BufferedReader authReader = new BufferedReader(new FileReader(DIR + AUTH_FILE)); + + String line; + while ((line = authReader.readLine()) != null) + { + String[] split = line.split(":"); + auths.put(split[0], line); + } + authReader.close(); + System.out.print("[" + pdfFile.getName() + "] Done! Loaded " + auths.size() + " Accounts!"); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + } + } + + public void onDisable() + { + getServer().getScheduler().cancelAllTasks(); + + //Restore players inventories so they are not lost + Player[] players = getServer().getOnlinePlayers(); + if (players.length > 0) + { + for (Player player : players) + if (!sessionExists(player.getName())) + restoreInventory(player); + } + + if (fullyEnabled) + updateAuthFile(); + + System.out.println("[" + pdfFile.getName() + "]" + " v" + pdfFile.getVersion() + " Disabled"); + } + + public boolean onCommand(CommandSender sender, Command cmd, String commandLabel, String[] args) + { + if (!this.isEnabled()) + return false; + + CommandHandler cmdHandler = new CommandHandler(this); + + if (sender instanceof Player) + cmdHandler.handlePlayerCommand((Player)sender, cmd, args); + else if (sender instanceof ConsoleCommandSender) + cmdHandler.handleConsoleCommand(cmd, args); + + return true; + } + + //AUTH / REGISTER FUNCTIONS + public void addAuth(String pName, String pass) + { + String hash = whirlpool(pass); + auths.put(pName.toLowerCase(), pName.toLowerCase() + ":" + hash); + + if (settings.getBool("misc.autosave")) + updateAuthFile(); + } + + public Boolean isRegistered(String pName) + { + if (auths.containsKey(pName.toLowerCase())) + return true; + + return false; + } + + public void changePass(String pName, String pass) + { + String hash = whirlpool(pass); + + auths.remove(pName.toLowerCase()); + auths.put(pName.toLowerCase(), pName.toLowerCase() + ":" + hash); + + if (settings.getBool("misc.autosave")) + updateAuthFile(); + } + + public void removeAuth(String pName) + { + pName = pName.toLowerCase(); + auths.remove(pName); + + if (sessionExists(pName)) + removeSession(pName); + + if (settings.getBool("misc.autosave")) + updateAuthFile(); + } + + public void updateAuthFile() + { + try + { + BufferedWriter authWriter = new BufferedWriter(new FileWriter(DIR + AUTH_FILE)); + for (String key: auths.keySet()) + { + authWriter.write(auths.get(key)); + authWriter.newLine(); + } + authWriter.close(); + } + catch (Exception e) + { + System.out.println(e.getMessage()); + } + } + + public Boolean isValidPass(String pass) + { + if (!settings.getBool("password.complexity.enabled")) + { + if (pass.length() < settings.getInt("password.min-length")) + return false; + + return true; + } + + String pattern = "("; + + if (settings.getBool("password.complexity.numbers")) + pattern += "(?=.*\\d)"; + + if (settings.getBool("password.complexity.lowercase")) + pattern += "(?=.*[a-z])"; + + if (settings.getBool("password.complexity.uppercase")) + pattern += "(?=.*[A-Z])"; + + if (settings.getBool("password.complexity.symbols")) + pattern += "(?=.*\\W)"; + + pattern += ".{" + settings.getInt("password.min-length") + ",})"; + + //String pattern = "((?=.*\\d)(?=.*[a-z])(?=.*[A-Z])(?=.*\\W).{3,})"; + Pattern p = Pattern.compile(pattern); + Matcher matcher = p.matcher(pass); + return matcher.matches(); + } + + //LOGIN / LOGOUT FUNCTIONS + public void login(Player player) + { + startSession(player); + restoreInventory(player); + } + + public Boolean checkPass(Player player, String pass) + { + String account = auths.get(player.getName().toLowerCase()); + int md5Length = player.getName().length() + 33; + String hash; + + if (account.length() == md5Length) + hash = md5(pass); + else + hash = whirlpool(pass); + + if (account.equals(player.getName().toLowerCase() + ":" + hash)) + { + //change pass to whirlpool if md5 + if (hash.length() == 32) + changePass(player.getName(), pass); + + return true; + } + else + return false; + } + + public void logout(Player player) + { + String pName = player.getName(); + + if (sessionExists(pName)) + { + Session session = sessions.get(pName.toLowerCase()); + + if (session.isExpired(new Date(session.getLoginTime() + (settings.getInt("session.timeout") * 1000)))) + removeSession(pName); + } + else + restoreInventory(player); + } + + public void addStrike(Player player) + { + String addr = player.getAddress().getAddress().getHostAddress(); + int newCount = 1; + if (strikes.containsKey(addr)) + { + newCount = strikes.get(addr) + 1; + strikes.remove(addr); + } + + strikes.put(addr, newCount); + } + + public int getStrikes(Player player) + { + String addr = player.getAddress().getAddress().getHostAddress(); + + if (!strikes.containsKey(addr)) + return 0; + + return strikes.get(addr); + } + + public void clearStrikes(Player player) + { + String addr = player.getAddress().getAddress().getHostAddress(); + strikes.remove(addr); + } + + //NOTIFY FUNCTIONS + public void handleEvent(Player player, Cancellable event) + { + if (!isRegistered(player.getName()) && !mustRegister(player)) + return; + + if (!sessionExists(player.getName())) + { + event.setCancelled(true); + + if (canNotify(player)) + notifyPlayer(player); + } + } + + public Boolean isCmdAllowed(String cmd) + { + if (settings.getStrList("misc.allowed-cmds").contains(cmd)) + return true; + + return false; + } + + public Boolean canNotify(Player player) + { + if (lastNotifyTimes.get(player) == null) + return true; + + Date nextNotifyTime = new Date(lastNotifyTimes.get(player).getTime() + (settings.getInt("notify.limit") * 1000)); + if (nextNotifyTime.compareTo(new Date()) < 0) + return true; + + return false; + } + + public void notifyPlayer(Player player) + { + player.sendMessage(strings.getString("misc.illegal")); + updateNotifyTime(player, new Date()); + } + + public void updateNotifyTime(Player player, Date date) + { + lastNotifyTimes.remove(player); + lastNotifyTimes.put(player, date); + } + + //INVENTORY FUNCTIONS + public void saveInventory(Player player) + { + PlayerInventory playerInv = player.getInventory(); + inventory.put(player, playerInv.getContents()); + playerInv.clear(); + armor.put(player, playerInv.getArmorContents()); + playerInv.setHelmet(null); + playerInv.setChestplate(null); + playerInv.setLeggings(null); + playerInv.setBoots(null); + } + + public void restoreInventory(Player player) + { + PlayerInventory playerInv = player.getInventory(); + + if (inventory.containsKey(player)) + { + playerInv.setContents(inventory.get(player)); + inventory.remove(player); + } + + if (armor.containsKey(player)) + { + playerInv.setBoots(armor.get(player)[0].getTypeId() == 0 ? null : armor.get(player)[0]); + playerInv.setLeggings(armor.get(player)[1].getTypeId() == 0 ? null : armor.get(player)[1]); + playerInv.setChestplate(armor.get(player)[2].getTypeId() == 0 ? null : armor.get(player)[2]); + playerInv.setHelmet(armor.get(player)[3].getTypeId() == 0 ? null : armor.get(player)[3]); + armor.remove(player); + } + + player.saveData(); + } + + //SESSION FUNCTIONS + public void startSession(Player player) + { + sessions.put(player.getName().toLowerCase(), new Session(player)); + } + + public Boolean sessionExists(String pName) + { + if (sessions.containsKey(pName.toLowerCase())) + return true; + + return false; + } + + public Boolean isLoggedIn(Player player) + { + if (sessionExists(player.getName())) + { + if (isSessionValid(player)) + return true; + + removeSession(player.getName()); + } + + return false; + } + + public Boolean isSessionValid(Player player) + { + Session session = sessions.get(player.getName().toLowerCase()); + if (session.isExpired(new Date(session.getLoginTime() + (settings.getInt("session.timeout") * 1000)))) + return false; + + if (settings.getBool("session.verifyip") && !session.isValidAddr(player.getAddress().getAddress().getHostAddress())) + return false; + + return true; + } + + public void removeSession(String pName) + { + pName = pName.toLowerCase(); + if (sessionExists(pName)) + sessions.remove(pName); + } + + public void killSession(Player player) + { + String pName = player.getName(); + removeSession(pName); + + if (player != null) + { + saveInventory(player); + player.sendMessage(strings.getString("logout.success.ended")); + } + } + + //MISC FUNCTIONS + private void setupPermissions() + { + Plugin test = this.getServer().getPluginManager().getPlugin("Permissions"); + + if (xAuth.Permissions == null) + { + if (test != null) + xAuth.Permissions = ((Permissions)test).getHandler(); + else + System.out.println("[" + pdfFile.getName() + "] Permissions plugin not detected, defaulting to ops.txt"); + } + } + + public String md5(String str) + { + try + { + MessageDigest md = MessageDigest.getInstance("MD5"); + byte[] messageDigest = md.digest(str.getBytes()); + BigInteger number = new BigInteger(1, messageDigest); + String hashtext = number.toString(16); + while (hashtext.length() < 32) + hashtext = "0" + hashtext; + + return hashtext; + } + catch (Exception e) + { + System.out.println(e.getMessage()); + } + + return null; + } + + public String whirlpool(String str) + { + Whirlpool w = new Whirlpool(); + byte[] digest = new byte[Whirlpool.DIGESTBYTES]; + w.NESSIEinit(); + w.NESSIEadd(str); + w.NESSIEfinalize(digest); + return Whirlpool.display(digest); + } + + public boolean canUseCommand(Player player, String node) + { + if (xAuth.Permissions == null) + { + if (!player.isOp()) + return false; + + return true; + } + + if (!xAuth.Permissions.has(player, node)) + return false; + + return true; + } + + public Boolean isNameLegal(String pName) + { + pName = pName.toLowerCase(); + + if (illegalNames.contains(pName)) + return false; + + String allowed = settings.getStr("filter.allowed"); + + for(int i = 0; i < pName.length(); i++) + if (allowed.indexOf(pName.charAt(i)) == -1) + { + illegalNames.add(pName); + return false; + } + + return true; + } + + public void reload() + { + updateAuthFile(); + settings = new Settings(new File(DIR + CONFIG_FILE)); + strings = new Strings(new File(DIR + STRINGS_FILE)); + getAuths(); + System.out.println("[" + pdfFile.getName() + "] Configuration & Accounts reloaded"); + } + + public Boolean mustRegister(Player player) + { + if (!settings.getBool("registration.forced")) + return false; + + if (xAuth.Permissions != null) + if (xAuth.Permissions.has(player, "xauth.exclude")) + return false; + + return true; + } +} \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/xAuthBlockListener.java b/src/main/java/com/cypherx/xauth/xAuthBlockListener.java new file mode 100644 index 0000000..18e7937 --- /dev/null +++ b/src/main/java/com/cypherx/xauth/xAuthBlockListener.java @@ -0,0 +1,38 @@ +package com.cypherx.xauth; + +import org.bukkit.entity.Player; +import org.bukkit.event.block.*; + +/** + * Handle events for all Block related events + * @author CypherX + */ +public class xAuthBlockListener extends BlockListener +{ + private final xAuth plugin; + + public xAuthBlockListener(final xAuth plugin) + { + this.plugin = plugin; + } + + //Prevents players from breaking blocks + public void onBlockBreak(BlockBreakEvent event) + { + if (event.isCancelled()) + return; + + Player player = event.getPlayer(); + plugin.handleEvent(player, event); + } + + //Prevents player from placing blocks + public void onBlockPlace(BlockPlaceEvent event) + { + if (event.isCancelled()) + return; + + Player player = event.getPlayer(); + plugin.handleEvent(player, event); + } +} \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/xAuthEntityListener.java b/src/main/java/com/cypherx/xauth/xAuthEntityListener.java new file mode 100644 index 0000000..a008cec --- /dev/null +++ b/src/main/java/com/cypherx/xauth/xAuthEntityListener.java @@ -0,0 +1,51 @@ +package com.cypherx.xauth; + +import org.bukkit.entity.*; +import org.bukkit.event.entity.*; + +/** + * Handle events for all Entity related events + * @author CypherX + */ +public class xAuthEntityListener extends EntityListener +{ + private final xAuth plugin; + + public xAuthEntityListener(final xAuth plugin) + { + this.plugin = plugin; + } + + //Prevents player from taking damage or giving damage + public void onEntityDamage(EntityDamageEvent event) + { + if (event.isCancelled()) + return; + + Entity entity = event.getEntity(); + + //Player taking damage + if (entity instanceof Player) + plugin.handleEvent((Player)entity, event); + //Player dealing damage to other entity + else if (event instanceof EntityDamageByEntityEvent) + { + EntityDamageByEntityEvent edbeEvent = (EntityDamageByEntityEvent)event; + Entity damager = edbeEvent.getDamager(); + + if (damager instanceof Player) + plugin.handleEvent((Player)damager, event); + } + } + + //Prevents monsters from attacking player + public void onEntityTarget(EntityTargetEvent event) + { + if (event.isCancelled()) + return; + + Entity entity = event.getTarget(); + if (entity instanceof Player) + plugin.handleEvent((Player)entity, event); + } +} \ No newline at end of file diff --git a/src/main/java/com/cypherx/xauth/xAuthPlayerListener.java b/src/main/java/com/cypherx/xauth/xAuthPlayerListener.java new file mode 100644 index 0000000..7905a69 --- /dev/null +++ b/src/main/java/com/cypherx/xauth/xAuthPlayerListener.java @@ -0,0 +1,146 @@ +package com.cypherx.xauth; + +import org.bukkit.Location; +import org.bukkit.entity.Player; +import org.bukkit.event.player.*; + +/** + * Handle events for all Player related events + * @author CypherX + */ +public class xAuthPlayerListener extends PlayerListener +{ + private final xAuth plugin; + + public xAuthPlayerListener(final xAuth instance) + { + plugin = instance; + } + + public void onPlayerLogin(PlayerLoginEvent event) + { + Player player = event.getPlayer(); + + if (player.isOnline()) + event.disallow(PlayerLoginEvent.Result.KICK_OTHER, "A player with this name is already online."); + + if (xAuth.settings.getBool("filter.enabled") && !plugin.isNameLegal(player.getName())) + event.disallow(PlayerLoginEvent.Result.KICK_OTHER, xAuth.strings.getString("misc.filterkickmsg")); + + if (xAuth.settings.getBool("filter.block-blankname") && player.getName().trim().equals("")) + event.disallow(PlayerLoginEvent.Result.KICK_OTHER, xAuth.strings.getString("misc.blankkickmsg")); + } + + public void onPlayerJoin(PlayerJoinEvent event) + { + final Player player = event.getPlayer(); + + if (!plugin.isLoggedIn(player)) + { + if (!plugin.isRegistered(player.getName())) + { + if (!plugin.mustRegister(player)) + return; + + plugin.saveInventory(player); + plugin.getServer().getScheduler().scheduleAsyncDelayedTask(plugin, new Runnable() { + public void run() { + player.sendMessage(xAuth.strings.getString("register.login")); + } + }, 5); + } + else + { + plugin.saveInventory(player); + plugin.getServer().getScheduler().scheduleSyncDelayedTask(plugin, new Runnable() { + public void run() { + player.sendMessage(xAuth.strings.getString("login.login")); + } + }, 5); + } + } + } + + public void onPlayerQuit(PlayerQuitEvent event) + { + plugin.logout(event.getPlayer()); + } + + //Prevents players from executing commands + public void onPlayerCommandPreprocess(PlayerCommandPreprocessEvent event) + { + if (event.isCancelled()) + return; + + Player player = event.getPlayer(); + String[] msg = event.getMessage().split(" "); + + if (!plugin.isCmdAllowed(msg[0])) + plugin.handleEvent(player, event); + + if (event.isCancelled()) + event.setMessage("/"); + } + + //Prevents player from being able to chat + public void onPlayerChat(PlayerChatEvent event) + { + if (event.isCancelled()) + return; + + Player player = event.getPlayer(); + plugin.handleEvent(player, event); + } + + //Prevents player from being able to drop an item (inventory should be empty anyway) + public void onPlayerDropItem(PlayerDropItemEvent event) + { + if (event.isCancelled()) + return; + + Player player = event.getPlayer(); + plugin.handleEvent(player, event); + } + + public void onPlayerInteract(PlayerInteractEvent event) + { + if (event.isCancelled()) + return; + + Player player = event.getPlayer(); + plugin.handleEvent(player, event); + } + + //Prevents player from moving + public void onPlayerMove(PlayerMoveEvent event) + { + if (event.isCancelled()) + return; + + Location from = event.getFrom(); + Location to = event.getTo(); + + if (from.getX() == to.getX() && from.getZ() == to.getZ()) + { + if (from.getY() > to.getY()) + return; + } + + Player player = event.getPlayer(); + plugin.handleEvent(player, event); + + if (event.isCancelled()) + player.teleport(event.getFrom()); + //player.teleportTo(event.getFrom()); + } + + //Prevents player from picking up items + public void onPlayerPickupItem(PlayerPickupItemEvent event) + { + if (event.isCancelled()) + return; + + Player player = event.getPlayer(); + plugin.handleEvent(player, event); + } +} \ No newline at end of file diff --git a/src/main/resources/plugin.yml b/src/main/resources/plugin.yml new file mode 100644 index 0000000..e682d11 --- /dev/null +++ b/src/main/resources/plugin.yml @@ -0,0 +1,29 @@ +name: xAuth +main: com.cypherx.xauth.xAuth +version: 1.2.2 +description: Allows players to register and maintain an account while the server is in offline-mode. +authors: + - CypherX + +commands: + register: + description: Register your player name + usage: /register + login: + description: Authenticate yourself + usage: /login + changepw: + description: Change your or another players password + usage: /changepw [player] + unregister: + description: Remove a players registration + usage: /unregister + authreload: + description: Reload the account, configuration, and string files + usage: /authreload + toggle: + description: Toggle various commands on/off + usage: /toggle + logout: + description: End a players session and force them to re-authenticate + usage: /logout [player] \ No newline at end of file