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("YOU'RE USING THE SQLITE DATABASE WITH " + accounts + "+ ACCOUNTS, FOR BETTER PERFORMANCES, PLEASE UPGRADE TO 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.showError( "FlatFile backend has been detected and is now deprecated, next time server starts up, it will be changed to SQLite... Conversion will be started Asynchronously, it will not drop down your performance !"); ConsoleLogger.showError("If you want to keep FlatFile, set file again into config at backend, but this message and this change will appear again at the next restart"); } } // 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("mc@mc.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()); } }); } }