diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java index 751557b4..93d0b2c8 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/FastLoginBukkit.java @@ -32,6 +32,8 @@ import org.bukkit.plugin.java.JavaPlugin; * This plugin checks if a player has a paid account and if so tries to skip offline mode authentication. */ public class FastLoginBukkit extends JavaPlugin { + + private static final int WORKER_THREADS = 5; public static UUID parseId(String withoutDashes) { return UUID.fromString(withoutDashes.substring(0, 8) @@ -44,7 +46,6 @@ public class FastLoginBukkit extends JavaPlugin { //provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic private final KeyPair keyPair = EncryptionUtil.generateKeyPair(); - private static final int WORKER_THREADS = 5; private boolean bungeeCord; private Storage storage; diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java index 79bf2aad..fe50b2ec 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/ProtocolSupportListener.java @@ -37,7 +37,7 @@ public class ProtocolSupportListener implements Listener { PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true); if (playerProfile != null) { //user not exists in the db - if (playerProfile.getUserId() == -1) { + if (!playerProfile.isPremium() && playerProfile.getUserId() == -1) { BukkitAuthPlugin authPlugin = plugin.getAuthPlugin(); if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) { UUID premiumUUID = plugin.getApiConnector().getPremiumUUID(username); diff --git a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/StartPacketListener.java b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/StartPacketListener.java index 63a6c845..bc85b67d 100644 --- a/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/StartPacketListener.java +++ b/bukkit/src/main/java/com/github/games647/fastlogin/bukkit/listener/StartPacketListener.java @@ -78,7 +78,7 @@ public class StartPacketListener extends PacketAdapter { PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true); if (playerProfile != null) { //user not exists in the db - if (playerProfile.getUserId() == -1) { + if (!playerProfile.isPremium() && playerProfile.getUserId() == -1) { BukkitAuthPlugin authPlugin = plugin.getAuthPlugin(); if (plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) { UUID premiumUUID = plugin.getApiConnector().getPremiumUUID(username); diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java index 707030c9..18288657 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/FastLoginBungee.java @@ -2,23 +2,70 @@ package com.github.games647.fastlogin.bungee; import com.github.games647.fastlogin.bungee.hooks.BungeeAuthHook; import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin; -import com.google.common.collect.Sets; -import java.util.Set; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.nio.file.Files; +import java.util.UUID; +import java.util.logging.Level; import net.md_5.bungee.api.plugin.Plugin; +import net.md_5.bungee.config.Configuration; +import net.md_5.bungee.config.ConfigurationProvider; +import net.md_5.bungee.config.YamlConfiguration; /** - * BungeeCord version of FastLogin. This plugin keeps track - * on online mode connections. + * BungeeCord version of FastLogin. This plugin keeps track on online mode connections. */ public class FastLoginBungee extends Plugin { - private final Set enabledPremium = Sets.newConcurrentHashSet(); + public static UUID parseId(String withoutDashes) { + return UUID.fromString(withoutDashes.substring(0, 8) + + "-" + withoutDashes.substring(8, 12) + + "-" + withoutDashes.substring(12, 16) + + "-" + withoutDashes.substring(16, 20) + + "-" + withoutDashes.substring(20, 32)); + } + private BungeeAuthPlugin bungeeAuthPlugin; + private Storage storage; + private Configuration configuration; @Override public void onEnable() { + File configFile = new File(getDataFolder(), "config.yml"); + if (!configFile.exists()) { + try (InputStream in = getResourceAsStream("config.yml")) { + Files.copy(in, configFile.toPath()); + } catch (IOException ioExc) { + getLogger().log(Level.SEVERE, "Error saving default config", ioExc); + } + } + + try { + configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile); + + String driver = configuration.getString("storage.driver"); + String host = configuration.getString("storage.host", ""); + int port = configuration.getInt("storage.port", 3306); + String database = configuration.getString("storage.database"); + + String username = configuration.getString("storage.username", ""); + String password = configuration.getString("storage.password", ""); + storage = new Storage(this, driver, host, port, database, username, password); + try { + storage.createTables(); + } catch (Exception ex) { + getLogger().log(Level.SEVERE, "Failed to setup database. Disabling plugin...", ex); + return; + } + + } catch (IOException ioExc) { + getLogger().log(Level.SEVERE, "Error loading config. Disabling plugin...", ioExc); + return; + } + //events getProxy().getPluginManager().registerListener(this, new PlayerConnectionListener(this)); @@ -28,13 +75,19 @@ public class FastLoginBungee extends Plugin { registerHook(); } - /** - * A set of players who want to use fastlogin - * - * @return all player which want to be in onlinemode - */ - public Set getEnabledPremium() { - return enabledPremium; + @Override + public void onDisable() { + if (storage != null) { + storage.close(); + } + } + + public Configuration getConfiguration() { + return configuration; + } + + public Storage getStorage() { + return storage; } /** diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/PlayerConnectionListener.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/PlayerConnectionListener.java index e96a86e0..d502709f 100644 --- a/bungee/src/main/java/com/github/games647/fastlogin/bungee/PlayerConnectionListener.java +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/PlayerConnectionListener.java @@ -6,10 +6,8 @@ import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; import java.util.UUID; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.chat.BaseComponent; -import net.md_5.bungee.api.chat.TextComponent; +import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.connection.Server; @@ -26,7 +24,7 @@ import net.md_5.bungee.event.EventHandler; */ public class PlayerConnectionListener implements Listener { - private final FastLoginBungee plugin; + protected final FastLoginBungee plugin; public PlayerConnectionListener(FastLoginBungee plugin) { this.plugin = plugin; @@ -41,8 +39,22 @@ public class PlayerConnectionListener implements Listener { PendingConnection connection = preLoginEvent.getConnection(); String username = connection.getName(); //just enable it for activated users - if (plugin.getEnabledPremium().contains(username)) { - connection.setOnlineMode(true); + + PlayerProfile playerProfile = plugin.getStorage().getProfile(username, true); + if (playerProfile != null) { + //user not exists in the db + if (!playerProfile.isPremium() && playerProfile.getUserId() == -1) { +// BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin(); +// if (plugin.getConfiguration().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) { +// UUID premiumUUID = plugin.getApiConnector().getPremiumUUID(username); +// if (premiumUUID != null) { +// plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username); +// connection.setOnlineMode(true); +// } +// } + } else if (playerProfile.isPremium()) { + connection.setOnlineMode(true); + } } } @@ -70,9 +82,6 @@ public class PlayerConnectionListener implements Listener { BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin(); if (authPlugin != null) { authPlugin.forceLogin(player); - BaseComponent loginMessage = new TextComponent("Auto login"); - loginMessage.setColor(ChatColor.DARK_GREEN); - player.sendMessage(loginMessage); } } } @@ -94,11 +103,29 @@ public class PlayerConnectionListener implements Listener { ByteArrayDataInput dataInput = ByteStreams.newDataInput(data); String subchannel = dataInput.readUTF(); if ("ON".equals(subchannel)) { - ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver(); - plugin.getEnabledPremium().add(forPlayer.getName()); + final ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver(); + + ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() { + @Override + public void run() { + PlayerProfile playerProfile = plugin.getStorage().getProfile(forPlayer.getName(), true); + playerProfile.setPremium(true); + //todo: set uuid + plugin.getStorage().save(playerProfile); + } + }); } else if ("OFF".equals(subchannel)) { - ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver(); - plugin.getEnabledPremium().remove(forPlayer.getName()); + final ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver(); + ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() { + @Override + public void run() { + PlayerProfile playerProfile = plugin.getStorage().getProfile(forPlayer.getName(), true); + playerProfile.setPremium(false); + playerProfile.setUuid(null); + //todo: set uuid + plugin.getStorage().save(playerProfile); + } + }); } } } diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/PlayerProfile.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/PlayerProfile.java new file mode 100644 index 00000000..65d67d28 --- /dev/null +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/PlayerProfile.java @@ -0,0 +1,78 @@ +package com.github.games647.fastlogin.bungee; + +import java.util.UUID; + +public class PlayerProfile { + + private final String playerName; + + private long userId; + + private UUID uuid; + private boolean premium; + private String lastIp; + private long lastLogin; + + public PlayerProfile(long userId, UUID uuid, String playerName, boolean premium + , String lastIp, long lastLogin) { + this.userId = userId; + this.uuid = uuid; + this.playerName = playerName; + this.premium = premium; + this.lastIp = lastIp; + this.lastLogin = lastLogin; + } + + public PlayerProfile(UUID uuid, String playerName, boolean premium, String lastIp) { + this.userId = -1; + + this.uuid = uuid; + this.playerName = playerName; + this.premium = premium; + this.lastIp = lastIp; + } + + public String getPlayerName() { + return playerName; + } + + public synchronized long getUserId() { + return userId; + } + + protected synchronized void setUserId(long generatedId) { + this.userId = generatedId; + } + + public synchronized UUID getUuid() { + return uuid; + } + + public synchronized void setUuid(UUID uuid) { + this.uuid = uuid; + } + + public synchronized boolean isPremium() { + return premium; + } + + public synchronized void setPremium(boolean premium) { + this.premium = premium; + } + + public synchronized String getLastIp() { + return lastIp; + } + + public synchronized void setLastIp(String lastIp) { + this.lastIp = lastIp; + } + + public long getLastLogin() { + return lastLogin; + } + + public synchronized void setLastLogin(long lastLogin) { + this.lastLogin = lastLogin; + } +} diff --git a/bungee/src/main/java/com/github/games647/fastlogin/bungee/Storage.java b/bungee/src/main/java/com/github/games647/fastlogin/bungee/Storage.java new file mode 100644 index 00000000..e6647e7f --- /dev/null +++ b/bungee/src/main/java/com/github/games647/fastlogin/bungee/Storage.java @@ -0,0 +1,204 @@ +package com.github.games647.fastlogin.bungee; + +import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.zaxxer.hikari.HikariConfig; +import com.zaxxer.hikari.HikariDataSource; + +import java.sql.Connection; +import java.sql.PreparedStatement; +import java.sql.ResultSet; +import java.sql.SQLException; +import java.sql.Statement; +import java.util.UUID; +import java.util.concurrent.ConcurrentMap; +import java.util.concurrent.TimeUnit; +import java.util.logging.Level; + +public class Storage { + + private static final String PREMIUM_TABLE = "premium"; + + private final ConcurrentMap profileCache = CacheBuilder + .newBuilder() + .concurrencyLevel(20) + .expireAfterAccess(30, TimeUnit.MINUTES) + .build(new CacheLoader() { + @Override + public PlayerProfile load(String key) throws Exception { + //should be fetched manually + throw new UnsupportedOperationException("Not supported yet."); + } + }).asMap(); + + private final HikariDataSource dataSource; + private final FastLoginBungee plugin; + + public Storage(FastLoginBungee plugin, String driver, String host, int port, String databasePath + , String user, String pass) { + this.plugin = plugin; + + HikariConfig databaseConfig = new HikariConfig(); + databaseConfig.setUsername(user); + databaseConfig.setPassword(pass); + databaseConfig.setDriverClassName(driver); + + databasePath = databasePath.replace("{pluginDir}", plugin.getDataFolder().getAbsolutePath()); + + String jdbcUrl = "jdbc:"; + if (driver.contains("sqlite")) { + jdbcUrl += "sqlite" + "://" + databasePath; + databaseConfig.setConnectionTestQuery("SELECT 1"); + } else { + jdbcUrl += "mysql" + "://" + host + ':' + port + '/' + databasePath; + } + + databaseConfig.setJdbcUrl(jdbcUrl); + this.dataSource = new HikariDataSource(databaseConfig); + } + + public void createTables() throws SQLException { + Connection con = null; + try { + con = dataSource.getConnection(); + Statement statement = con.createStatement(); + String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " (" + + "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, " + + "`UUID` CHAR(36), " + + "`Name` VARCHAR(16) NOT NULL, " + + "`Premium` BOOLEAN NOT NULL, " + + "`LastIp` VARCHAR(255) NOT NULL, " + + "`LastLogin` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, " + + "UNIQUE (`UUID`), " + //the premium shouldn't steal the cracked account by changing the name + + "UNIQUE (`Name`) " + + ")"; + + if (dataSource.getJdbcUrl().contains("sqlite")) { + createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT"); + } + + statement.executeUpdate(createDataStmt); + } finally { + closeQuietly(con); + } + } + + public PlayerProfile getProfile(String name, boolean fetch) { + if (profileCache.containsKey(name)) { + return profileCache.get(name); + } else if (fetch) { + Connection con = null; + try { + con = dataSource.getConnection(); + PreparedStatement loadStatement = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + + " WHERE `Name`=? LIMIT 1"); + loadStatement.setString(1, name); + + ResultSet resultSet = loadStatement.executeQuery(); + if (resultSet.next()) { + long userId = resultSet.getInt(1); + + String unparsedUUID = resultSet.getString(2); + UUID uuid; + if (unparsedUUID == null) { + uuid = null; + } else { + uuid = FastLoginBungee.parseId(unparsedUUID); + } + +// String name = resultSet.getString(3); + boolean premium = resultSet.getBoolean(4); + String lastIp = resultSet.getString(5); + long lastLogin = resultSet.getTimestamp(6).getTime(); + PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin); + profileCache.put(name, playerProfile); + return playerProfile; + } else { + PlayerProfile crackedProfile = new PlayerProfile(null, name, false, ""); + profileCache.put(name, crackedProfile); + return crackedProfile; + } + } catch (SQLException sqlEx) { + plugin.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx); + } finally { + closeQuietly(con); + } + } + + return null; + } +// public PlayerProfile getProfile(UUID uuid, boolean fetch) { +//todo +// } + + public boolean save(PlayerProfile playerProfile) { + Connection con = null; + try { + con = dataSource.getConnection(); + + UUID uuid = playerProfile.getUuid(); + + if (playerProfile.getUserId() == -1) { + PreparedStatement saveStatement = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE + + " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?)", Statement.RETURN_GENERATED_KEYS); + + if (uuid == null) { + saveStatement.setString(1, null); + } else { + saveStatement.setString(1, uuid.toString().replace("-", "")); + } + + saveStatement.setString(2, playerProfile.getPlayerName()); + saveStatement.setBoolean(3, playerProfile.isPremium()); + saveStatement.setString(4, playerProfile.getLastIp()); + saveStatement.execute(); + + ResultSet generatedKeys = saveStatement.getGeneratedKeys(); + if (generatedKeys != null && generatedKeys.next()) { + playerProfile.setUserId(generatedKeys.getInt(1)); + } + } else { + PreparedStatement saveStatement = con.prepareStatement("UPDATE " + PREMIUM_TABLE + + " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?"); + + if (uuid == null) { + saveStatement.setString(1, null); + } else { + saveStatement.setString(1, uuid.toString().replace("-", "")); + } + + saveStatement.setString(2, playerProfile.getPlayerName()); + saveStatement.setBoolean(3, playerProfile.isPremium()); + saveStatement.setString(4, playerProfile.getLastIp()); +// saveStatement.setTimestamp(5, new Timestamp(playerProfile.getLastLogin())); + + saveStatement.setLong(5, playerProfile.getUserId()); + saveStatement.execute(); + } + + return true; + } catch (SQLException ex) { + plugin.getLogger().log(Level.SEVERE, "Failed to save playerProfile", ex); + } finally { + closeQuietly(con); + } + + return false; + } + + public void close() { + dataSource.close(); + profileCache.clear(); + } + + private void closeQuietly(Connection con) { + if (con != null) { + try { + con.close(); + } catch (SQLException sqlEx) { + plugin.getLogger().log(Level.SEVERE, "Failed to close connection", sqlEx); + } + } + } +}