package fr.xephi.authme; import java.io.BufferedReader; import java.io.InputStreamReader; import java.net.URL; import java.net.URLConnection; import java.util.Calendar; import java.util.Collection; import java.util.Date; import java.util.List; import java.util.concurrent.ConcurrentHashMap; import java.util.logging.Logger; import org.apache.logging.log4j.LogManager; import org.bukkit.Bukkit; import org.bukkit.Location; import org.bukkit.Server; import org.bukkit.World; import org.bukkit.command.CommandSender; import org.bukkit.entity.Player; import org.bukkit.plugin.PluginManager; import org.bukkit.plugin.RegisteredServiceProvider; import org.bukkit.plugin.java.JavaPlugin; import org.bukkit.scheduler.BukkitTask; import com.earth2me.essentials.Essentials; import fr.xephi.authme.api.API; import fr.xephi.authme.api.NewAPI; import fr.xephi.authme.cache.auth.PlayerAuth; import fr.xephi.authme.cache.auth.PlayerCache; import fr.xephi.authme.cache.backup.JsonCache; import fr.xephi.authme.cache.limbo.LimboCache; import fr.xephi.authme.cache.limbo.LimboPlayer; import fr.xephi.authme.commands.AdminCommand; import fr.xephi.authme.commands.CaptchaCommand; import fr.xephi.authme.commands.ChangePasswordCommand; import fr.xephi.authme.commands.ConverterCommand; import fr.xephi.authme.commands.LoginCommand; import fr.xephi.authme.commands.LogoutCommand; import fr.xephi.authme.commands.RegisterCommand; import fr.xephi.authme.commands.UnregisterCommand; import fr.xephi.authme.converter.Converter; import fr.xephi.authme.converter.ForceFlatToSqlite; import fr.xephi.authme.datasource.CacheDataSource; import fr.xephi.authme.datasource.DataSource; import fr.xephi.authme.datasource.DatabaseCalls; import fr.xephi.authme.datasource.FlatFile; import fr.xephi.authme.datasource.MySQL; import fr.xephi.authme.datasource.SQLite; import fr.xephi.authme.datasource.SQLite_HIKARI; import fr.xephi.authme.listener.AuthMeBlockListener; import fr.xephi.authme.listener.AuthMeEntityListener; import fr.xephi.authme.listener.AuthMeInventoryPacketAdapter; import fr.xephi.authme.listener.AuthMePlayerListener; import fr.xephi.authme.listener.AuthMePlayerListener16; import fr.xephi.authme.listener.AuthMePlayerListener18; import fr.xephi.authme.listener.AuthMeServerListener; import fr.xephi.authme.modules.ModuleManager; import fr.xephi.authme.plugin.manager.BungeeCordMessage; import fr.xephi.authme.plugin.manager.EssSpawn; import fr.xephi.authme.process.Management; import fr.xephi.authme.settings.Messages; import fr.xephi.authme.settings.OtherAccounts; import fr.xephi.authme.settings.Settings; import fr.xephi.authme.settings.Spawn; import net.milkbowl.vault.permission.Permission; public class AuthMe extends JavaPlugin { private static AuthMe authme; private static Server server; public boolean antibotMod = false; public NewAPI api; public ConcurrentHashMap cap = new ConcurrentHashMap<>(); public ConcurrentHashMap captcha = new ConcurrentHashMap<>(); public DataSource database; public DataManager dataManager; public boolean delayedAntiBot = true; public Essentials ess; public Location essentialsSpawn; public AuthMeInventoryPacketAdapter inventoryProtector; public Management management; public OtherAccounts otherAccounts; // Hooks TODO: move into modules public Permission permission; public ConcurrentHashMap realIp = new ConcurrentHashMap<>(); // TODO: Create Manager for fields below public ConcurrentHashMap sessions = new ConcurrentHashMap<>(); private Logger authmeLogger; private Messages m; // Module manager private ModuleManager moduleManager; private JsonCache playerBackup; private Settings settings; public static AuthMe getInstance() { return authme; } public boolean authmePermissible(final CommandSender sender, final String perm) { if (sender.hasPermission(perm)) { return true; } else if (permission != null) { return permission.has(sender, perm); } return false; } // Check if a player/command sender have a permission public boolean authmePermissible(final Player player, final String perm) { if (player.hasPermission(perm)) { return true; } else if (permission != null) { return permission.playerHas(player, perm); } return false; } // Get the Essentials plugin public void checkEssentials() { if (server.getPluginManager().isPluginEnabled("Essentials")) { try { ess = (Essentials) server.getPluginManager().getPlugin("Essentials"); ConsoleLogger.info("发现 Essentials 启用相关功能..."); } catch (Exception | NoClassDefFoundError ingnored) { ess = null; } } else { ess = null; } if (server.getPluginManager().isPluginEnabled("EssentialsSpawn")) { try { essentialsSpawn = new EssSpawn().getLocation(); ConsoleLogger.info("发现 EssentialsSpawn 读取重生点..."); } catch (final Exception e) { essentialsSpawn = null; ConsoleLogger.showError("无法读取文件 /plugins/Essentials/spawn.yml !"); } } else { essentialsSpawn = null; } } // Check the presence of the ProtocolLib plugin public void checkProtocolLib() { if (Settings.protectInventoryBeforeLogInEnabled) { if (server.getPluginManager().isPluginEnabled("ProtocolLib")) { inventoryProtector = new AuthMeInventoryPacketAdapter(this); inventoryProtector.register(); } else { ConsoleLogger.showError("警告!!! 保护背包功能必须启用 ProtocolLib 插件 但是未找到! 关闭该功能..."); Settings.protectInventoryBeforeLogInEnabled = false; } } } // Check the presence of the Vault plugin and a permissions provider public void checkVault() { if (server.getPluginManager().isPluginEnabled("Vault")) { final RegisteredServiceProvider permissionProvider = server.getServicesManager().getRegistration(net.milkbowl.vault.permission.Permission.class); if (permissionProvider != null) { permission = permissionProvider.getProvider(); ConsoleLogger.info("发现 Vault 使用经济系统: " + permission.getName()); } else { ConsoleLogger.showError("发现 Vault, 但是 Vault 未找到权限系统..."); } } else { permission = null; } } // Select the player to kick when a vip player join the server when full public Player generateKickPlayer(final Collection collection) { Player player = null; for (final Player p : collection) { if (!(authmePermissible(p, "authme.vip"))) { player = p; break; } } return player; } @Deprecated public String getCountryCode(final String ip) { return Utils.getCountryCode(ip); } @Deprecated public String getCountryName(final String ip) { return Utils.getCountryName(ip); } public String getIP(final Player player) { final String name = player.getName().toLowerCase(); String ip = player.getAddress().getAddress().getHostAddress(); if (Settings.bungee) { if (realIp.containsKey(name)) { ip = realIp.get(name); } } if (Settings.checkVeryGames) { if (getVeryGamesIP(player) != null) { ip = getVeryGamesIP(player); } } return ip; } public Messages getMessages() { return m; } public ModuleManager getModuleManager() { return moduleManager; } public Settings getSettings() { return settings; } // Return the spawn location of a player public Location getSpawnLocation(final Player player) { final World world = player.getWorld(); final String[] spawnPriority = Settings.spawnPriority.split(","); Location spawnLoc = world.getSpawnLocation(); for (int i = spawnPriority.length - 1; i >= 0; i--) { final String s = spawnPriority[i]; if (s.equalsIgnoreCase("default") && getDefaultSpawn(world) != null) { spawnLoc = getDefaultSpawn(world); } if (s.equalsIgnoreCase("essentials") && getEssentialsSpawn() != null) { spawnLoc = getEssentialsSpawn(); } if (s.equalsIgnoreCase("authme") && getAuthMeSpawn(player) != null) { spawnLoc = getAuthMeSpawn(player); } } if (spawnLoc == null) { spawnLoc = world.getSpawnLocation(); } return spawnLoc; } /** * Get Player real IP through VeryGames method * * @param player * player */ @Deprecated public String getVeryGamesIP(final Player player) { String realIP = player.getAddress().getAddress().getHostAddress(); String sUrl = "http://monitor-1.verygames.net/api/?action=ipclean-real-ip&out=raw&ip=%IP%&port=%PORT%"; sUrl = sUrl.replace("%IP%", player.getAddress().getAddress().getHostAddress()).replace("%PORT%", "" + player.getAddress().getPort()); try { final URL url = new URL(sUrl); final URLConnection urlc = url.openConnection(); final BufferedReader in = new BufferedReader(new InputStreamReader(urlc.getInputStream())); final String inputLine = in.readLine(); if (inputLine != null && !inputLine.isEmpty() && !inputLine.equalsIgnoreCase("error") && !inputLine.contains("error")) { realIP = inputLine; } } catch (final Exception ignored) { } return realIP; } public boolean hasJoinedIp(final String name, final String ip) { int count = 0; for (final Player player : Utils.getOnlinePlayers()) { if (ip.equalsIgnoreCase(getIP(player)) && !player.getName().equalsIgnoreCase(name)) { count++; } } return count >= Settings.getMaxJoinPerIp; } public boolean isLoggedIp(final String name, final String ip) { int count = 0; for (final Player player : Utils.getOnlinePlayers()) { if (ip.equalsIgnoreCase(getIP(player)) && database.isLogged(player.getName().toLowerCase()) && !player.getName().equalsIgnoreCase(name)) { count++; } } return count >= Settings.getMaxLoginPerIp; } @Override public void onDisable() { // Save player data final Collection players = Utils.getOnlinePlayers(); if (players != null) { for (final Player player : players) { this.savePlayer(player); } } // Close the database if (database != null) { database.close(); } // Do backup on stop if enabled if (Settings.isBackupActivated && Settings.isBackupOnStop) { final boolean Backup = new PerformBackup(this).doBackup(); if (Backup) { ConsoleLogger.info("数据文件备份完成."); } else { ConsoleLogger.showError("Error while performing the backup!"); } } // Unload modules moduleManager.unloadModules(); // Disabled correctly ConsoleLogger.info("AuthMe " + this.getDescription().getVersion() + " 已卸载!"); } @SuppressWarnings("deprecation") @Override public void onEnable() { // Set the Instance server = getServer(); authmeLogger = Logger.getLogger("AuthMe"); authme = this; // TODO: split the plugin in more modules moduleManager = new ModuleManager(this); @SuppressWarnings("unused") final int loaded = moduleManager.loadModules(); // TODO: remove vault as hard dependency final PluginManager pm = server.getPluginManager(); // Setup the Logger if (authmeLogger == null) { authmeLogger = this.getLogger(); } else { authmeLogger.setParent(this.getLogger()); } // Load settings and custom configurations // TODO: new configuration style (more files) try { settings = new Settings(this); Settings.reload(); } catch (final Exception e) { ConsoleLogger.writeStackTrace(e); ConsoleLogger.showError("无法载入配置文件... 某些配置是错误的, 为了安全考虑 服务器即将关闭!"); server.shutdown(); return; } // Setup otherAccounts file otherAccounts = OtherAccounts.getInstance(); // Setup messages m = Messages.getInstance(); // Set Console Filter if (Settings.removePassword) { final ConsoleFilter filter = new ConsoleFilter(); this.getLogger().setFilter(filter); Bukkit.getLogger().setFilter(filter); Logger.getLogger("Minecraft").setFilter(filter); authmeLogger.setFilter(filter); // Set Log4J Filter try { Class.forName("org.apache.logging.log4j.core.Filter"); setLog4JFilter(); } catch (ClassNotFoundException | NoClassDefFoundError e) { ConsoleLogger.info("You're using Minecraft 1.6.x or older, Log4J support will be disabled"); } } // AntiBot delay if (Settings.enableAntiBot) { Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { @Override public void run() { delayedAntiBot = false; } }, 2400); } // Download GeoIp.dat file Utils.checkGeoIP(); // Find Permissions checkVault(); // Check Essentials checkEssentials(); // Check if the protocollib is available. If so we could listen for // inventory protection checkProtocolLib(); // Do backup on start if enabled if (Settings.isBackupActivated && Settings.isBackupOnStart) { // Do backup and check return value! if (new PerformBackup(this).doBackup()) { ConsoleLogger.info("数据备份完成..."); } else { ConsoleLogger.showError("Error while performing the backup!"); } } // Connect to the database and setup tables try { setupDatabase(); } catch (final Exception e) { ConsoleLogger.writeStackTrace(e); ConsoleLogger.showError(e.getMessage()); ConsoleLogger.showError("连接数据库期间发生错误! Authme 初始化 中断!"); stopOrUnload(); return; } // Setup the inventory backup playerBackup = new JsonCache(); // Set the DataManager dataManager = new DataManager(this); // Setup the new API api = new NewAPI(this); // Setup the old deprecated API new API(this); // Setup Management management = new Management(this); // Bungeecord hook if (Settings.bungee) { Bukkit.getMessenger().registerOutgoingPluginChannel(this, "BungeeCord"); Bukkit.getMessenger().registerIncomingPluginChannel(this, "BungeeCord", new BungeeCordMessage(this)); } // Reload support hook if (Settings.reloadSupport) { if (database != null) { final int playersOnline = Utils.getOnlinePlayers().size(); if (playersOnline < 1) { database.purgeLogged(); } else { for (final PlayerAuth auth : database.getLoggedPlayers()) { if (auth == null) { continue; } auth.setLastLogin(new Date().getTime()); database.updateSession(auth); PlayerCache.getInstance().addPlayer(auth); } } } } // Register events pm.registerEvents(new AuthMePlayerListener(this), this); // Try to register 1.6 player listeners try { Class.forName("org.bukkit.event.player.PlayerEditBookEvent"); pm.registerEvents(new AuthMePlayerListener16(this), this); } catch (final ClassNotFoundException ignore) { } // Try to register 1.8 player listeners try { Class.forName("org.bukkit.event.player.PlayerInteractAtEntityEvent"); pm.registerEvents(new AuthMePlayerListener18(this), this); } catch (final ClassNotFoundException ignore) { } pm.registerEvents(new AuthMeBlockListener(this), this); pm.registerEvents(new AuthMeEntityListener(this), this); pm.registerEvents(new AuthMeServerListener(this), this); // Register commands getCommand("authme").setExecutor(new AdminCommand(this)); getCommand("register").setExecutor(new RegisterCommand(this)); getCommand("login").setExecutor(new LoginCommand(this)); getCommand("changepassword").setExecutor(new ChangePasswordCommand(this)); getCommand("logout").setExecutor(new LogoutCommand(this)); getCommand("unregister").setExecutor(new UnregisterCommand(this)); getCommand("captcha").setExecutor(new CaptchaCommand(this)); getCommand("converter").setExecutor(new ConverterCommand(this)); // Purge on start if enabled autoPurge(); // Start Email recall task if needed recallEmail(); // Configuration Security Warnings if (!Settings.isForceSingleSessionEnabled) { ConsoleLogger.showError("WARNING!!! By disabling ForceSingleSession, your server protection is inadequate!"); } if (Settings.getSessionTimeout == 0 && Settings.isSessionsEnabled) { ConsoleLogger.showError("WARNING!!! You set session timeout to 0, this may cause security issues!"); } // Sponsor messages ConsoleLogger.info("AuthMe hooks perfectly with the VERYGAMES server hosting!"); ConsoleLogger.info("Development builds are available on our jenkins, thanks to f14stelt."); ConsoleLogger.info("Do you want a good gameserver? Look at our sponsor GameHosting.it leader in Italy as Game Server Provider!"); // Successful message ConsoleLogger.info("AuthMe " + this.getDescription().getVersion() + " correctly enabled!"); } public String replaceAllInfos(String message, final Player player) { final int playersOnline = Utils.getOnlinePlayers().size(); message = message.replace("&", "§"); message = message.replace("{PLAYER}", player.getName()); message = message.replace("{ONLINE}", "" + playersOnline); message = message.replace("{MAXPLAYERS}", "" + server.getMaxPlayers()); message = message.replace("{IP}", getIP(player)); message = message.replace("{LOGINS}", "" + PlayerCache.getInstance().getLogged()); message = message.replace("{WORLD}", player.getWorld().getName()); message = message.replace("{SERVER}", server.getServerName()); message = message.replace("{VERSION}", server.getBukkitVersion()); message = message.replace("{COUNTRY}", Utils.getCountryName(getIP(player))); return message; } // Save Player Data public void savePlayer(final Player player) { if ((Utils.isNPC(player)) || (Utils.isUnrestricted(player))) { return; } final String name = player.getName().toLowerCase(); if (PlayerCache.getInstance().isAuthenticated(name) && !player.isDead() && Settings.isSaveQuitLocationEnabled) { final PlayerAuth auth = new PlayerAuth(player.getName().toLowerCase(), player.getLocation().getX(), player.getLocation().getY(), player.getLocation().getZ(), player .getWorld() .getName(), player.getName()); database.updateQuitLoc(auth); } if (LimboCache.getInstance().hasLimboPlayer(name)) { final LimboPlayer limbo = LimboCache.getInstance().getLimboPlayer(name); if (!Settings.noTeleport) { player.teleport(limbo.getLoc()); } Utils.addNormal(player, limbo.getGroup()); player.setOp(limbo.getOperator()); limbo.getTimeoutTaskId().cancel(); LimboCache.getInstance().deleteLimboPlayer(name); if (this.playerBackup.doesCacheExist(player)) { this.playerBackup.removeCache(player); } } PlayerCache.getInstance().removePlayer(name); database.setUnlogged(name); player.saveData(); } public void setMessages(final Messages m) { this.m = m; } // Initialize and setup the database public void setupDatabase() throws Exception { if (database != null) { database.close(); } // Backend MYSQL - FILE - SQLITE - SQLITEHIKARI boolean isSQLite = false; switch (Settings.getDataSource) { case FILE: database = new FlatFile(); break; case MYSQL: database = new MySQL(); break; case SQLITE: database = new SQLite(); isSQLite = true; break; case SQLITEHIKARI: database = new SQLite_HIKARI(); isSQLite = true; break; } if (isSQLite) { server.getScheduler().runTaskAsynchronously(this, new Runnable() { @Override public void run() { final int accounts = database.getAccountsRegistered(); if (accounts >= 4000) { ConsoleLogger.showError("您使用 SQLite 存储了超过 " + accounts + " 个账户, 为了获得更好的性能, 建议使用MySQL!!!"); } } }); } if (Settings.isCachingEnabled) { database = new CacheDataSource(this, database); } else { database = new DatabaseCalls(database); } if (Settings.getDataSource == DataSource.DataSourceType.FILE) { final Converter converter = new ForceFlatToSqlite(database, this); server.getScheduler().runTaskAsynchronously(this, converter); ConsoleLogger.info("+================================================"); ConsoleLogger.info("| 数据格式 FlatFile 已经过时并且被弃用..."); ConsoleLogger.info("| 下一次启动服务器, 数据格式将被转换为 SQLite..."); ConsoleLogger.info("| 转换过程将会在后台异步进行..."); ConsoleLogger.info("| 这不会影响您的服务器性能..."); ConsoleLogger.info("| 如果你想继续使用 FlatFile 格式 请修改配置文件..."); ConsoleLogger.info("| 但是下次重启 插件仍然会进行此操作..."); ConsoleLogger.info("+================================================"); } } // Stop/unload the server/plugin as defined in the configuration public void stopOrUnload() { if (Settings.isStopEnabled) { ConsoleLogger.showError("插件载入发生错误 根据您的配置 服务器即将关闭..."); server.shutdown(); } else { server.getPluginManager().disablePlugin(AuthMe.getInstance()); } } // Show the exception message and stop/unload the server/plugin as defined // in the configuration public void stopOrUnload(final Exception e) { ConsoleLogger.showError(e.getMessage()); stopOrUnload(); } public void switchAntiBotMod(final boolean mode) { this.antibotMod = mode; Settings.switchAntiBotMod(mode); } // Purge inactive players from the database, as defined in the configuration private void autoPurge() { if (!Settings.usePurge) { return; } final Calendar calendar = Calendar.getInstance(); calendar.add(Calendar.DATE, -(Settings.purgeDelay)); final long until = calendar.getTimeInMillis(); final List cleared = database.autoPurgeDatabase(until); if (cleared == null) { return; } if (cleared.isEmpty()) { return; } ConsoleLogger.info("AutoPurging the Database: " + cleared.size() + " accounts removed!"); if (Settings.purgeEssentialsFile && this.ess != null) { dataManager.purgeEssentials(cleared); // name to UUID convertion } // needed with latest versions if (Settings.purgePlayerDat) { dataManager.purgeDat(cleared); // name to UUID convertion needed } // with latest versions of MC if (Settings.purgeLimitedCreative) { dataManager.purgeLimitedCreative(cleared); } if (Settings.purgeAntiXray) { dataManager.purgeAntiXray(cleared); // IDK if it uses UUID or } // names... (Actually it purges // only names!) if (Settings.purgePermissions) { dataManager.purgePermissions(cleared, permission); } } // Return the authme soawnpoint private Location getAuthMeSpawn(final Player player) { if ((!database.isAuthAvailable(player.getName().toLowerCase()) || !player.hasPlayedBefore()) && (Spawn.getInstance().getFirstSpawn() != null)) { return Spawn.getInstance().getFirstSpawn(); } if (Spawn.getInstance().getSpawn() != null) { return Spawn.getInstance().getSpawn(); } return player.getWorld().getSpawnLocation(); } // Return the default spawnpoint of a world private Location getDefaultSpawn(final World world) { return world.getSpawnLocation(); } // Return the essentials spawnpoint private Location getEssentialsSpawn() { if (essentialsSpawn != null) { return essentialsSpawn; } return null; } private void recallEmail() { if (!Settings.recallEmail) { return; } Bukkit.getScheduler().scheduleSyncRepeatingTask(this, new Runnable() { @Override public void run() { for (final Player player : Utils.getOnlinePlayers()) { if (player.isOnline()) { final String name = player.getName().toLowerCase(); if (database.isAuthAvailable(name)) { if (PlayerCache.getInstance().isAuthenticated(name)) { final String email = database.getAuth(name).getEmail(); if (email == null || email.isEmpty() || email.equalsIgnoreCase("your@email.com")) { m.send(player, "add_email"); } } } } } } }, 1, 1200 * Settings.delayRecall); } // Set the console filter to remove the passwords private void setLog4JFilter() { Bukkit.getScheduler().scheduleSyncDelayedTask(this, new Runnable() { @Override public void run() { final org.apache.logging.log4j.core.Logger coreLogger = (org.apache.logging.log4j.core.Logger) LogManager.getRootLogger(); coreLogger.addFilter(new Log4JFilter()); } }); } }