AuthMe/src/main/java/fr/xephi/authme/AuthMe.java

739 lines
23 KiB
Java
Raw Normal View History

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<String, String> cap = new ConcurrentHashMap<>();
public ConcurrentHashMap<String, Integer> 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<String, String> realIp = new ConcurrentHashMap<>();
// TODO: Create Manager for fields below
public ConcurrentHashMap<String, BukkitTask> 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<Permission> 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<? extends Player> 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<? extends Player> 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<String> 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());
}
});
}
}