Compare commits

..

2 Commits

Author SHA1 Message Date
31d9f3cb4a Fix parsing of illegal uuids with spaces 2024-04-04 21:46:58 +02:00
56e43fb146 Add support for postgresql 2024-04-04 17:43:23 +02:00
11 changed files with 196 additions and 56 deletions

View File

@@ -60,13 +60,13 @@ Possible values: `Premium`, `Cracked`, `Unknown`
## Requirements
* Java 17+
* Java 17+ (Recommended)
* Server software in offlinemode:
* Spigot (or a fork e.g. Paper) 1.8.8+
* Protocol plugin:
* [ProtocolLib 5.0+](https://www.spigotmc.org/resources/protocollib.1997/) or
* [ProtocolLib 5.1+](https://www.spigotmc.org/resources/protocollib.1997/) or
* [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
* Latest BungeeCord (or a fork e.g. Waterfall)
* Latest BungeeCord (or a fork e.g. Waterfall) or Velocity
* An auth plugin.
### Supported auth plugins
@@ -117,10 +117,10 @@ Install the plugin on both platforms, that is proxy (BungeeCord or Velocity) and
4. Activate ip forwarding in your proxy config
5. Check your database settings in the config of FastLogin on your proxy
* The proxies only ship with a limited set of drivers where Spigot supports more. Therefore, these are supported:
* BungeeCord: `com.mysql.jdbc.Driver` for MySQL/MariaDB
* Velocity: `fastlogin.mariadb.jdbc.Driver` for MySQL/MariaDB
* BungeeCord: `com.mysql.jdbc.Driver` for MySQL/MariaDB/PostgreSQL
* Velocity: `fastlogin.mariadb.jdbc.Driver` for MySQL/MariaDB/PostgreSQL
* Note the embedded file storage SQLite is not available
* MySQL/MariaDB requires an external database server running. Check your server provider if there is one available
* MySQL/MariaDB/PostgreSQL requires an external database server running. Check your server provider if there is one available
or install one.
6. Set proxy and Spigot in offline mode by setting the value `onlinemode` in your `config.yml` to false
7. You should *always* configure the firewall for your Spigot server so that it's only accessible through your proxy

View File

@@ -39,7 +39,7 @@ public class BukkitScheduler extends AsyncScheduler {
public BukkitScheduler(Plugin plugin, Logger logger) {
super(logger, command -> Bukkit.getScheduler().runTaskAsynchronously(plugin, command));
syncExecutor = r -> Bukkit.getScheduler().runTask(plugin, r);
syncExecutor = task -> Bukkit.getScheduler().runTask(plugin, task);
}
public Executor getSyncExecutor() {

View File

@@ -177,7 +177,7 @@ public class ProtocolLibListener extends PacketAdapter {
ClientPublicKey clientPublicKey, byte[] expectedToken) {
try {
if (new MinecraftVersion(1, 19, 0).atOrAbove()
&& !(new MinecraftVersion(1, 19, 3).atOrAbove())) {
&& !new MinecraftVersion(1, 19, 3).atOrAbove()) {
Either<byte[], ?> either = packet.getSpecificModifier(Either.class).read(0);
if (clientPublicKey == null) {
Optional<byte[]> left = either.left();

View File

@@ -71,43 +71,25 @@ public class ConnectListener implements Listener {
private static final String UUID_FIELD_NAME = "uniqueId";
protected static final MethodHandle UNIQUE_ID_SETTER;
private static final String REWRITE_ID_NAME = "rewriteId";
protected static final MethodHandle REWRITE_ID_SETTER;
static {
MethodHandle uniqueIdHandle = null;
MethodHandle rewriterHandle = null;
MethodHandle setHandle = null;
try {
Lookup lookup = MethodHandles.lookup();
// test for implementation class availability
Class.forName("net.md_5.bungee.connection.InitialHandler");
uniqueIdHandle = getHandlerSetter(lookup, UUID_FIELD_NAME);
try {
rewriterHandle = getHandlerSetter(lookup, REWRITE_ID_NAME);
} catch (NoSuchFieldException noSuchFieldEx) {
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
logger.error(
"Rewrite field not found. Setting only legacy BungeeCord field"
);
}
Field uuidField = InitialHandler.class.getDeclaredField(UUID_FIELD_NAME);
uuidField.setAccessible(true);
setHandle = lookup.unreflectSetter(uuidField);
} catch (ReflectiveOperationException reflectiveOperationException) {
Logger logger = LoggerFactory.getLogger(ConnectListener.class);
logger.error(
"Cannot find Bungee UUID field implementation; Disabling premium UUID and skin won't work.",
"Cannot find Bungee initial handler; Disabling premium UUID and skin won't work.",
reflectiveOperationException
);
}
UNIQUE_ID_SETTER = uniqueIdHandle;
REWRITE_ID_SETTER = rewriterHandle;
}
private static MethodHandle getHandlerSetter(Lookup lookup, String fieldName)
throws NoSuchFieldException, IllegalAccessException {
Field uuidField = InitialHandler.class.getDeclaredField(fieldName);
uuidField.setAccessible(true);
return lookup.unreflectSetter(uuidField);
UNIQUE_ID_SETTER = setHandle;
}
private final FastLoginBungee plugin;
@@ -197,12 +179,6 @@ public class ConnectListener implements Listener {
// So we have to do it with reflection
UNIQUE_ID_SETTER.invokeExact(connection, offlineUUID);
// if available set rewrite id to forward the UUID for newer BungeeCord versions since
// https://github.com/SpigotMC/BungeeCord/commit/1be25b6c74ec2be4b15adf8ca53a0497f01e2afe
if (REWRITE_ID_SETTER != null) {
REWRITE_ID_SETTER.invokeExact(connection, offlineUUID);
}
String format = "Overridden UUID from {} to {} (based of {}) on {}";
plugin.getLog().info(format, oldPremiumId, offlineUUID, username, connection);
} catch (Exception ex) {

View File

@@ -107,7 +107,7 @@ public class TickingRateLimiter implements RateLimiter {
}
}
private static class TimeRecord implements Comparable<Long> {
private static class TimeRecord implements Comparable<TimeRecord> {
private final long firstMinuteRecord;
private final long expireTime;
@@ -131,9 +131,9 @@ public class TickingRateLimiter implements RateLimiter {
return firstMinuteRecord + expireTime <= now;
}
@Override
public int compareTo(Long other) {
public int compareTo(long other) {
if (other < firstMinuteRecord) {
// other is earlier
return -1;
}
@@ -143,5 +143,10 @@ public class TickingRateLimiter implements RateLimiter {
return 0;
}
@Override
public int compareTo(TimeRecord other) {
return compareTo(other.firstMinuteRecord);
}
}
}

View File

@@ -36,6 +36,7 @@ import com.github.games647.fastlogin.core.antibot.TickingRateLimiter;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
import com.github.games647.fastlogin.core.storage.PostgreSQLStorage;
import com.github.games647.fastlogin.core.storage.MySQLStorage;
import com.github.games647.fastlogin.core.storage.SQLStorage;
import com.github.games647.fastlogin.core.storage.SQLiteStorage;
@@ -230,6 +231,24 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
if (type.contains("sqlite")) {
storage = new SQLiteStorage(plugin, database, databaseConfig);
} else if (type.contains("postgresql")) {
String host = config.get("host", "");
int port = config.get("port", 3306);
boolean useSSL = config.get("useSSL", false);
if (useSSL) {
boolean publicKeyRetrieval = config.getBoolean("allowPublicKeyRetrieval", false);
String rsaPublicKeyFile = config.getString("ServerRSAPublicKeyFile");
String sslMode = config.getString("sslMode", "Required");
databaseConfig.addDataSourceProperty("allowPublicKeyRetrieval", publicKeyRetrieval);
databaseConfig.addDataSourceProperty("serverRSAPublicKeyFile", rsaPublicKeyFile);
databaseConfig.addDataSourceProperty("sslMode", sslMode);
}
databaseConfig.setUsername(config.get("username", ""));
databaseConfig.setPassword(config.getString("password"));
storage = new PostgreSQLStorage(plugin, type, host, port, database, databaseConfig, useSSL);
} else {
String host = config.get("host", "");
int port = config.get("port", 3306);

View File

@@ -33,6 +33,7 @@ import org.geysermc.floodgate.api.player.FloodgatePlayer;
import java.io.IOException;
import java.net.InetSocketAddress;
import java.util.Locale;
import java.util.Optional;
import java.util.UUID;
@@ -61,9 +62,9 @@ public abstract class FloodgateManagement<P extends C, C, L extends LoginSession
this.username = getName(player);
//load values from config.yml
autoLoginFloodgate = core.getConfig().get("autoLoginFloodgate").toString().toLowerCase();
autoRegisterFloodgate = core.getConfig().get("autoRegisterFloodgate").toString().toLowerCase();
allowNameConflict = core.getConfig().get("allowFloodgateNameConflict").toString().toLowerCase();
autoLoginFloodgate = core.getConfig().getString("autoLoginFloodgate").toLowerCase(Locale.ROOT);
autoRegisterFloodgate = core.getConfig().getString("autoRegisterFloodgate").toLowerCase(Locale.ROOT);
allowNameConflict = core.getConfig().getString("allowFloodgateNameConflict").toLowerCase(Locale.ROOT);
}
@Override

View File

@@ -61,6 +61,7 @@ public abstract class LoginSession {
}
/**
* Check if user needs registration once login is successful
* @return This value is always false if we authenticate the player with a cracked authentication
*/
public synchronized boolean needsRegistration() {

View File

@@ -0,0 +1,101 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2023 games647 and contributors
*
* Permission is hereby granted, free of charge, to any person obtaining a copy
* of this software and associated documentation files (the "Software"), to deal
* in the Software without restriction, including without limitation the rights
* to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
* copies of the Software, and to permit persons to whom the Software is
* furnished to do so, subject to the following conditions:
*
* The above copyright notice and this permission notice shall be included in all
* copies or substantial portions of the Software.
*
* THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
* IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
* FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
* AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
* LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.core.storage;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.zaxxer.hikari.HikariConfig;
public class PostgreSQLStorage extends SQLStorage {
private static final String JDBC_PROTOCOL = "jdbc:";
public PostgreSQLStorage(PlatformPlugin<?> plugin, String driver, String host, int port, String database,
HikariConfig config, boolean useSSL) {
super(plugin.getLog(), plugin.getName(), plugin.getThreadFactory(),
setParams(config, driver, host, port, database, useSSL));
}
private static HikariConfig setParams(HikariConfig config,
String driver, String host, int port, String database,
boolean useSSL) {
// Require SSL on the server if requested in config - this will also verify certificate
// Those values are deprecated in favor of sslMode
config.addDataSourceProperty("useSSL", useSSL);
config.addDataSourceProperty("requireSSL", useSSL);
// adding paranoid, hides hostname, username, version and so
// could be useful for hiding server details
config.addDataSourceProperty("paranoid", true);
config.setJdbcUrl(JDBC_PROTOCOL + buildJDBCUrl(driver, host, port, database));
return config;
}
private static String buildJDBCUrl(String driver, String host, int port, String database) {
return "postgresql://" + host + ':' + port + '/' + database;
}
@Override
protected String getCreateTableStmt() {
// PostgreSQL has a different syntax for id column
return CREATE_TABLE_STMT
.replace("`", "\"")
.replace("INTEGER PRIMARY KEY AUTO_INCREMENT", "SERIAL PRIMARY KEY");
}
@Override
protected String getAddFloodgateColumnStmt() {
// PostgreSQL has a different syntax
return ADD_FLOODGATE_COLUMN_STMT
.replace("`", "\"")
.replace("INTEGER(3)", "INTEGER");
}
@Override
protected String getLoadByNameStmt() {
return LOAD_BY_NAME_STMT
.replace("`", "\"");
}
@Override
protected String getLoadByUuidStmt() {
return LOAD_BY_UUID_STMT
.replace("`", "\"");
}
@Override
protected String getInsertProfileStmt() {
return INSERT_PROFILE_STMT
.replace("`", "\"");
}
@Override
protected String getUpdateProfileStmt() {
return UPDATE_PROFILE_STMT
.replace("`", "\"");
}
}

View File

@@ -61,14 +61,14 @@ public abstract class SQLStorage implements AuthStorage {
protected static final String ADD_FLOODGATE_COLUMN_STMT = "ALTER TABLE `" + PREMIUM_TABLE
+ "` ADD COLUMN `Floodgate` INTEGER(3)";
protected static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE
protected static final String LOAD_BY_NAME_STMT = "SELECT * FROM `" + PREMIUM_TABLE
+ "` WHERE `Name`=? LIMIT 1";
protected static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE
protected static final String LOAD_BY_UUID_STMT = "SELECT * FROM `" + PREMIUM_TABLE
+ "` WHERE `UUID`=? LIMIT 1";
protected static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
protected static final String INSERT_PROFILE_STMT = "INSERT INTO `" + PREMIUM_TABLE
+ "` (`UUID`, `Name`, `Premium`, `Floodgate`, `LastIp`) " + "VALUES (?, ?, ?, ?, ?) ";
// limit not necessary here, because it's unique
protected static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE
protected static final String UPDATE_PROFILE_STMT = "UPDATE `" + PREMIUM_TABLE
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `Floodgate`=?, `LastIp`=?, "
+ "`LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
@@ -97,7 +97,7 @@ public abstract class SQLStorage implements AuthStorage {
// add Floodgate column
DatabaseMetaData md = con.getMetaData();
if (isColumnMissing(md, "Floodgate")) {
stmt.executeUpdate(ADD_FLOODGATE_COLUMN_STMT);
stmt.executeUpdate(getAddFloodgateColumnStmt());
}
}
@@ -112,7 +112,7 @@ public abstract class SQLStorage implements AuthStorage {
@Override
public StoredProfile loadProfile(String name) {
try (Connection con = dataSource.getConnection();
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_NAME)
PreparedStatement loadStmt = con.prepareStatement(getLoadByNameStmt())
) {
loadStmt.setString(1, name);
@@ -130,7 +130,7 @@ public abstract class SQLStorage implements AuthStorage {
@Override
public StoredProfile loadProfile(UUID uuid) {
try (Connection con = dataSource.getConnection();
PreparedStatement loadStmt = con.prepareStatement(LOAD_BY_UUID)) {
PreparedStatement loadStmt = con.prepareStatement(getLoadByUuidStmt())) {
loadStmt.setString(1, UUIDAdapter.toMojangId(uuid));
try (ResultSet resultSet = loadStmt.executeQuery()) {
@@ -147,7 +147,10 @@ public abstract class SQLStorage implements AuthStorage {
if (resultSet.next()) {
long userId = resultSet.getInt("UserID");
UUID uuid = Optional.ofNullable(resultSet.getString("UUID")).map(UUIDAdapter::parseId).orElse(null);
UUID uuid = Optional.ofNullable(resultSet.getString("UUID"))
.map(String::trim)
.map(UUIDAdapter::parseId)
.orElse(null);
String name = resultSet.getString("Name");
boolean premium = resultSet.getBoolean("Premium");
@@ -177,7 +180,7 @@ public abstract class SQLStorage implements AuthStorage {
playerProfile.getSaveLock().lock();
try {
if (playerProfile.isSaved()) {
try (PreparedStatement saveStmt = con.prepareStatement(UPDATE_PROFILE)) {
try (PreparedStatement saveStmt = con.prepareStatement(getUpdateProfileStmt())) {
saveStmt.setString(1, uuid);
saveStmt.setString(2, playerProfile.getName());
saveStmt.setBoolean(3, playerProfile.isPremium());
@@ -188,7 +191,8 @@ public abstract class SQLStorage implements AuthStorage {
saveStmt.execute();
}
} else {
try (PreparedStatement saveStmt = con.prepareStatement(INSERT_PROFILE, RETURN_GENERATED_KEYS)) {
try (PreparedStatement saveStmt = con.prepareStatement(getInsertProfileStmt(),
RETURN_GENERATED_KEYS)) {
saveStmt.setString(1, uuid);
saveStmt.setString(2, playerProfile.getName());
@@ -214,13 +218,37 @@ public abstract class SQLStorage implements AuthStorage {
}
/**
* SQLite has a slightly different syntax, so this will be overridden by SQLiteStorage
* SQLite and PostgreSQL have a slightly different syntax, so this will be overridden by SQLiteStorage and so on...
* @return An SQL Statement to create the `premium` table
*/
protected String getCreateTableStmt() {
return CREATE_TABLE_STMT;
}
/**
* PostgreSQL has a slightly different syntax, so this will be overridden by PostgreSQLStorage
* @return An SQL Statement to create the `premium` table
*/
protected String getAddFloodgateColumnStmt() {
return ADD_FLOODGATE_COLUMN_STMT;
}
protected String getLoadByNameStmt() {
return LOAD_BY_NAME_STMT;
}
protected String getLoadByUuidStmt() {
return LOAD_BY_UUID_STMT;
}
protected String getInsertProfileStmt() {
return INSERT_PROFILE_STMT;
}
protected String getUpdateProfileStmt() {
return UPDATE_PROFILE_STMT;
}
@Override
public void close() {
dataSource.close();

View File

@@ -289,6 +289,15 @@ database: '{pluginDir}/FastLogin.db'
#username: 'myUser'
#password: 'myPassword'
# PostgreSQL
# If you want to enable it, uncomment only the lines below; this not this line.
#driver: 'postgresql'
#host: '127.0.0.1'
#port: 5432
#database: 'fastlogin'
#username: 'myUser'
#password: 'myPassword'
# Advanced Connection Pool settings in seconds
#timeout: 30
#lifetime: 30