Compare commits

..

7 Commits

Author SHA1 Message Date
games647
9249c1ea8c Fix equality null test 2022-07-28 13:30:42 +02:00
games647
fd33523189 Perform equality check 2022-07-28 13:25:13 +02:00
games647
5af43170da Try to skip filtering 2022-07-28 13:16:25 +02:00
games647
eb51f0c83c Debug sending fake packet out 2022-07-28 13:05:46 +02:00
games647
5bdf2f4c9e Move debug message up 2022-07-27 15:08:03 +02:00
games647
155c98d601 Fix using asynchronous manager in synchronous mode 2022-07-27 14:56:59 +02:00
games647
a8f3155fc5 Add experimental synchronous processing 2022-07-27 14:56:59 +02:00
42 changed files with 627 additions and 499 deletions

View File

@@ -1,6 +1,6 @@
name: 🐞 Bug Report
description: Something isn't working, broken, not expected behavior
labels: [ bug ]
labels: [bug]
body:
- type: markdown
attributes:

View File

@@ -8,23 +8,18 @@ assignees: ''
---
[//]: # (Lines in this format are considered as comments and will not be displayed.)
[//]: # (Before reporting make sure you're running the **latest build** of the plugin and checked for existing issues!)
[//]: # (This ticket is about suggestions for a feature or particular enhancement)
### Is your feature request related to a problem? Please describe.
[//]: # (A clear and concise description of what the problem is. Ex. I'm always frustrated when [...])
### Describe the solution you'd like
[//]: # (A clear and concise description of what you want to happen.)
### Describe alternatives you've considered
[//]: # (A clear and concise description of any alternative solutions or features you've considered.)
### Additional context
[//]: # (Add any other context or screenshots about the feature request here.)

View File

@@ -1,11 +1,8 @@
[//]: # (Lines in this format are considered as comments and will not be displayed.)
[//]: # (If your work is in progress, please consider making a draft pull request.)
### Summary of your change
[//]: # (Example: motivation, enhancement)
### Related issue
[//]: # (Reference it using '#NUMBER'. Ex: Fixes/Related #...)

View File

@@ -4,57 +4,63 @@
name: "CodeQL"
on:
# Scan only for push on the primary branch for now
push:
branches: [ main ]
pull_request:
branches: [ main ]
# Scan only for push on the primary branch for now
push:
branches: [ main ]
pull_request:
branches: [ main ]
jobs:
# job i
analyze:
# job i
analyze:
# Display name
name: Analyze
# Display name
name: Analyze
# Environment
runs-on: ubuntu-latest
# Environment
runs-on: ubuntu-latest
permissions:
actions: read
contents: read
security-events: write
permissions:
actions: read
contents: read
security-events: write
strategy:
fail-fast: true
matrix:
# Languages to scan
language: [ 'java' ]
strategy:
fail-fast: false
matrix:
# Languages to scan
language: [ 'java' ]
steps:
- name: Checkout repository
uses: actions/checkout@v3
steps:
- name: Checkout repository
uses: actions/checkout@v3
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 19
cache: 'maven'
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 18
cache: 'maven'
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# Initializes the CodeQL tools for scanning.
- name: Initialize CodeQL
uses: github/codeql-action/init@v2
with:
languages: ${{ matrix.language }}
# Auto build attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
# Cache build process too like in the maven config
- uses: actions/cache@v3.0.4
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2
with:
category: "/language:${{matrix.language}}"
# Auto build attempts to build any compiled languages (C/C++, C#, or Java).
# If this step fails, then you should remove it and run the build manually (see below)
- name: Autobuild
uses: github/codeql-action/autobuild@v2
- name: Perform CodeQL Analysis
uses: github/codeql-action/analyze@v2

View File

@@ -2,42 +2,41 @@
# including making pull requests review easier
# Human-readable name in the actions tab
name: Maven Build
name: Java CI
# Build on every pull request regardless of the branch
# Wiki: https://help.github.com/en/actions/reference/events-that-trigger-workflows
on:
push:
branches:
- main
pull_request:
branches:
- main
push:
branches:
- main
pull_request:
branches:
- main
jobs:
# job id
build_and_test:
# job id
build_and_test:
# Environment image - always use the newest OS
runs-on: ubuntu-latest
permissions:
contents: read
# Environment image - always use the newest OS
runs-on: ubuntu-latest
# Run steps
steps:
# Pull changes
- uses: actions/checkout@v3
# Run steps
steps:
# Pull changes
- uses: actions/checkout@v3
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 19
cache: 'maven'
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v3
with:
distribution: 'temurin'
java-version: 18
cache: 'maven'
# Build and test (included in package)
- name: Build with Maven and test
# Run non-interactive, package (with compile+test),
# ignore snapshot updates, because they are likely to have breaking changes, enforce checksums
run: mvn test --batch-mode --threads 2.0C --no-snapshot-updates --strict-checksums --file pom.xml
# Build and test (included in package)
- name: Build with Maven and test
# Run non-interactive, package (with compile+test),
# ignore snapshot updates, because they are likely to have breaking changes, enforce checksums to validate
# possible errors in dependencies
run: mvn test --batch-mode --threads 2.0C --no-snapshot-updates --strict-checksums --file pom.xml

View File

@@ -112,7 +112,7 @@ Install the plugin on both platforms, that is proxy (BungeeCord or Velocity) and
* This is often found in `spigot.yml` or `paper.yml`
2. Restart the backend server
3. Now there is `allowed-proxies.txt` file in the FastLogin folder of the restarted server
* BungeeCord: Put your `stats`-id from the BungeeCord config into this file
* BungeeCord: Put your `stats-id` from the BungeeCord config into this file
* Velocity: On plugin startup the plugin generates a `proxyId.txt` inside the plugins folder of the proxy
4. Activate ip forwarding in your proxy config
5. Check your database settings in the config of FastLogin on your proxy

View File

@@ -25,12 +25,12 @@
SOFTWARE.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<properties>
<nettyVersion>4.1.79.Final</nettyVersion>
<nettyVersion>4.1.77.Final</nettyVersion>
</properties>
<parent>
@@ -50,7 +50,7 @@
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<version>3.3.0</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
@@ -86,8 +86,7 @@
<!-- Rename the service file too to let SLF4J api find our own relocated jdk logger -->
<!-- Located in META-INF/services -->
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<minimizeJar>true</minimizeJar>
<filters>
@@ -165,7 +164,7 @@
<dependency>
<groupId>io.papermc.paper</groupId>
<artifactId>paper-api</artifactId>
<version>1.19.4-R0.1-SNAPSHOT</version>
<version>1.19-R0.1-SNAPSHOT</version>
<scope>provided</scope>
<!-- Use our own newer api version -->
<exclusions>
@@ -180,7 +179,7 @@
<dependency>
<groupId>io.papermc</groupId>
<artifactId>paperlib</artifactId>
<version>1.0.8</version>
<version>1.0.7</version>
</dependency>
<dependency>
@@ -229,11 +228,15 @@
<dependency>
<groupId>org.geysermc.floodgate</groupId>
<artifactId>api</artifactId>
<version>${floodgate.version}</version>
<version>2.2.0-SNAPSHOT</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.geysermc.cumulus</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
@@ -241,7 +244,7 @@
<!-- Bedrock player bridge -->
<dependency>
<groupId>org.geysermc.geyser</groupId>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>${geyser.version}</version>
<scope>provided</scope>
@@ -255,23 +258,17 @@
<!-- We need the API, but it was excluded above -->
<dependency>
<groupId>org.geysermc.geyser</groupId>
<artifactId>api</artifactId>
<groupId>org.geysermc</groupId>
<artifactId>geyser-api</artifactId>
<version>${geyser.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Provide premium placeholders-->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.11.3</version>
<version>2.11.2</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
@@ -300,7 +297,7 @@
<dependency>
<groupId>com.lenis0012.bukkit</groupId>
<artifactId>loginsecurity</artifactId>
<version>3.1.1</version>
<version>3.1</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
@@ -371,7 +368,7 @@
<dependency>
<groupId>org.bouncycastle</groupId>
<artifactId>bcprov-jdk18on</artifactId>
<version>1.72</version>
<version>1.71</version>
<scope>test</scope>
</dependency>
@@ -379,9 +376,9 @@
<groupId>io.netty</groupId>
<artifactId>netty-transport</artifactId>
<version>${nettyVersion}</version>
<scope>provided</scope>
</dependency>
<scope>test</scope>
</dependency>
<dependency>
<groupId>io.netty</groupId>
<artifactId>netty-codec</artifactId>

View File

@@ -32,11 +32,11 @@ import com.github.games647.fastlogin.core.shared.LoginSession;
import java.util.Optional;
import org.jetbrains.annotations.Nullable;
import javax.annotation.Nullable;
/**
* Represents a client connecting to the server.
* <p>
*
* This session is invalid if the player disconnects or the login was successful
*/
public class BukkitLoginSession extends LoginSession {
@@ -76,7 +76,7 @@ public class BukkitLoginSession extends LoginSession {
/**
* Gets the verify-token the server sent to the client.
* <p>
*
* Empty if it's a BungeeCord connection
*
* @return verify token from the server

View File

@@ -25,11 +25,11 @@
*/
package com.github.games647.fastlogin.bukkit;
import com.comphenix.protocol.ProtocolLibrary;
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
import com.github.games647.fastlogin.bukkit.listener.PaperCacheListener;
import com.github.games647.fastlogin.bukkit.listener.protocollib.ManualNameChange;
import com.github.games647.fastlogin.bukkit.listener.protocollib.ProtocolLibListener;
import com.github.games647.fastlogin.bukkit.listener.protocollib.SkinApplyListener;
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
@@ -59,7 +59,6 @@ import org.bukkit.plugin.PluginManager;
import org.bukkit.plugin.java.JavaPlugin;
import org.geysermc.floodgate.api.FloodgateApi;
import org.geysermc.geyser.GeyserImpl;
import org.jetbrains.annotations.NotNull;
import org.slf4j.Logger;
/**
@@ -119,6 +118,10 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
ProtocolLibListener.register(this, core.getAntiBot(), core.getConfig().getBoolean("verifyClientKeys"));
if (isPluginInstalled("floodgate")) {
printFloodgateWarning();
}
//if server is using paper - we need to set the skin at pre login anyway, so no need for this listener
if (!PaperLib.isPaper() && getConfig().getBoolean("forwardSkin")) {
pluginManager.registerEvents(new SkinApplyListener(this), this);
@@ -148,6 +151,23 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
premiumPlaceholder = new PremiumPlaceholder(this);
premiumPlaceholder.register();
}
dependencyWarnings();
}
private void printFloodgateWarning() {
if (getConfig().getBoolean("floodgatePrefixWorkaround")) {
ManualNameChange.register(this, floodgateService);
logger.info("Floodgate prefix injection workaround has been enabled.");
logger.info("If you have problems joining the server, try disabling it in the configuration.");
} else {
logger.warn("We have detected that you are running FastLogin alongside Floodgate and ProtocolLib.");
logger.warn("Currently there is an issue with FastLogin that prevents Floodgate name prefixes from "
+ "showing up when it is together used with ProtocolLib.");
logger.warn("If you would like to use Floodgate name prefixes, you can enable an experimental "
+ "workaround by changing the value 'floodgatePrefixWorkaround' to true in config.yml.");
logger.warn("For more information visit https://github.com/games647/FastLogin/issues/493");
}
}
private boolean initializeFloodgate() {
@@ -187,9 +207,9 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
}
}
if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().unregisterAsyncHandlers(this);
}
// if (isPluginInstalled("ProtocolLib")) {
// ProtocolLibrary.getProtocolManager().getAsynchronousManager().unregisterAsyncHandlers(this);
// }
}
public FastLoginCore<Player, CommandSender, FastLoginBukkit> getCore() {
@@ -236,7 +256,7 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
* @return the online status or unknown if an error happened, the player isn't online or BungeeCord doesn't send
* us the status message yet (This means you cannot check the login status on the PlayerJoinEvent).
*/
public @NotNull PremiumStatus getStatus(@NotNull UUID onlinePlayer) {
public PremiumStatus getStatus(UUID onlinePlayer) {
return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN);
}
@@ -305,4 +325,18 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
}
return geyserService;
}
/**
* Send warning messages to log if incompatible plugins are used
*/
private void dependencyWarnings() {
if (isPluginInstalled("floodgate-bukkit")) {
logger.warn("We have detected that you are running Floodgate 1.0 which is not supported by the Bukkit "
+ "version of FastLogin.");
logger.warn("If you would like to use FastLogin with Floodgate, you can download development builds of "
+ "Floodgate 2.0 from https://ci.opencollab.dev/job/GeyserMC/job/Floodgate/job/dev%252F2.0/");
logger.warn("Don't forget to update Geyser to a supported version as well from "
+ "https://ci.opencollab.dev/job/GeyserMC/job/Geyser/job/floodgate-2.0/");
}
}
}

View File

@@ -62,7 +62,7 @@ public class AuthMeHook implements AuthPlugin<Player>, Listener {
this.plugin = plugin;
this.authmeAPI = AuthMeApi.getInstance();
if (plugin.getCore().getConfig().getBoolean("respectIpLimit", false)) {
if (plugin.getConfig().getBoolean("respectIpLimit", false)) {
try {
Field managementField = this.authmeAPI.getClass().getDeclaredField("management");
managementField.setAccessible(true);

View File

@@ -45,7 +45,7 @@ import org.jetbrains.annotations.NotNull;
/**
* Responsible for receiving messages from a BungeeCord instance.
* <p>
*
* This class also receives the plugin message from the bungeecord version of this plugin in order to get notified if
* the connection is in online mode.
*/

View File

@@ -34,7 +34,6 @@ import com.google.common.primitives.Longs;
import java.io.IOException;
import java.math.BigInteger;
import java.nio.ByteBuffer;
import java.nio.charset.StandardCharsets;
import java.security.InvalidKeyException;
import java.security.KeyFactory;
@@ -52,7 +51,6 @@ import java.util.Arrays;
import java.util.Base64;
import java.util.Base64.Encoder;
import java.util.Random;
import java.util.UUID;
import javax.crypto.BadPaddingException;
import javax.crypto.Cipher;
@@ -79,8 +77,6 @@ final class EncryptionUtil {
private static final Encoder KEY_ENCODER = Base64.getMimeEncoder(
LINE_LENGTH, "\n".getBytes(StandardCharsets.UTF_8)
);
private static final int MILLISECOND_SIZE = 8;
private static final int UUID_SIZE = 2 * MILLISECOND_SIZE;
static {
try {
@@ -150,7 +146,7 @@ final class EncryptionUtil {
return new SecretKeySpec(decrypt(privateKey, sharedKey), "AES");
}
public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimestamp, UUID premiumId)
public static boolean verifyClientKey(ClientPublicKey clientKey, Instant verifyTimestamp)
throws NoSuchAlgorithmException, InvalidKeyException, SignatureException {
if (clientKey.isExpired(verifyTimestamp)) {
return false;
@@ -159,27 +155,10 @@ final class EncryptionUtil {
Signature verifier = Signature.getInstance("SHA1withRSA");
// key of the signer
verifier.initVerify(MOJANG_SESSION_KEY);
verifier.update(toSignable(clientKey, premiumId));
verifier.update(toSignable(clientKey).getBytes(StandardCharsets.US_ASCII));
return verifier.verify(clientKey.signature());
}
private static byte[] toSignable(ClientPublicKey clientPublicKey, UUID ownerPremiumId) {
if (ownerPremiumId == null) {
long expiry = clientPublicKey.expiry().toEpochMilli();
String encoded = KEY_ENCODER.encodeToString(clientPublicKey.key().getEncoded());
return (expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n")
.getBytes(StandardCharsets.US_ASCII);
}
byte[] keyData = clientPublicKey.key().getEncoded();
return ByteBuffer.allocate(keyData.length + UUID_SIZE + MILLISECOND_SIZE)
.putLong(ownerPremiumId.getMostSignificantBits())
.putLong(ownerPremiumId.getLeastSignificantBits())
.putLong(clientPublicKey.expiry().toEpochMilli())
.put(keyData)
.array();
}
public static boolean verifyNonce(byte[] expected, PrivateKey decryptionKey, byte[] encryptedNonce)
throws NoSuchPaddingException, IllegalBlockSizeException, NoSuchAlgorithmException,
BadPaddingException, InvalidKeyException {
@@ -207,6 +186,12 @@ final class EncryptionUtil {
return KeyFactory.getInstance("RSA").generatePublic(keySpec);
}
private static String toSignable(ClientPublicKey clientPublicKey) {
long expiry = clientPublicKey.expiry().toEpochMilli();
String encoded = KEY_ENCODER.encodeToString(clientPublicKey.key().getEncoded());
return expiry + "-----BEGIN RSA PUBLIC KEY-----\n" + encoded + "\n-----END RSA PUBLIC KEY-----\n";
}
private static byte[] decrypt(PrivateKey key, byte[] data)
throws NoSuchPaddingException, NoSuchAlgorithmException, InvalidKeyException,
IllegalBlockSizeException, BadPaddingException {

View File

@@ -0,0 +1,101 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2022 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.bukkit.listener.protocollib;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.bedrock.FloodgateService;
import java.util.UUID;
import org.geysermc.floodgate.api.FloodgateApi;
import static com.comphenix.protocol.PacketType.Login.Client.START;
/**
* Manually inject Floodgate player name prefixes.
* <br>
* This is used as a workaround, because Floodgate fails to inject
* the prefixes when it's used together with ProtocolLib and FastLogin.
* <br>
* For more information visit: <a href="https://github.com/games647/FastLogin/issues/493">...</a>
*/
public class ManualNameChange extends PacketAdapter {
private final FloodgateService floodgate;
public ManualNameChange(FastLoginBukkit plugin, FloodgateService floodgate) {
super(params()
.plugin(plugin)
.types(START));
this.plugin = plugin;
this.floodgate = floodgate;
}
public static void register(FastLoginBukkit plugin, FloodgateService floodgate) {
// they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
// ProtocolLibrary.getProtocolManager()
// .getAsynchronousManager()
// .registerAsyncHandler(new ManualNameChange(plugin, floodgate))
// .start();
}
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
PacketContainer packet = packetEvent.getPacket();
String username = readUsername(packet);
if (floodgate.getBedrockPlayer(username) == null) {
//not a Floodgate player, no need to add a prefix
return;
}
packet.setMeta("original_name", username);
String prefixedName = FloodgateApi.getInstance().getPlayerPrefix() + username;
setUsername(packet, prefixedName);
}
private void setUsername(PacketContainer packet, String name) {
if (packet.getGameProfiles().size() > 0) {
WrappedGameProfile updatedProfile = new WrappedGameProfile(UUID.randomUUID(), name);
packet.getGameProfiles().write(0, updatedProfile);
} else {
packet.getStrings().write(0, name);
}
}
private String readUsername(PacketContainer packet) {
if (packet.getGameProfiles().size() > 0) {
return packet.getGameProfiles().read(0).getName();
} else {
return packet.getStrings().read(0);
}
}
}

View File

@@ -25,7 +25,6 @@
*/
package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketEvent;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
@@ -42,7 +41,7 @@ import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class NameCheckTask extends JoinManagement<Player, CommandSender, ProtocolLibLoginSource>
implements Runnable {
implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
@@ -70,11 +69,11 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
@Override
public void run() {
try {
super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey));
} finally {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
// try {
super.onLogin(username, new ProtocolLibLoginSource(player, random, serverKey, clientKey));
// } finally {
// ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
// }
}
@Override
@@ -106,9 +105,9 @@ public class NameCheckTask extends JoinManagement<Player, CommandSender, Protoco
BukkitLoginSession playerSession = new BukkitLoginSession(username, verify, clientKey, registered, profile);
plugin.putSession(player.getAddress(), playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
// synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
// }
}
@Override

View File

@@ -30,15 +30,11 @@ import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketAdapter;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.PacketFilterManager;
import com.comphenix.protocol.injector.player.PlayerInjectionHandler;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftVersion;
import com.comphenix.protocol.wrappers.BukkitConverters;
import com.comphenix.protocol.wrappers.Converters;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedProfilePublicKey.WrappedProfileKeyData;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.listener.protocollib.packet.ClientPublicKey;
@@ -46,10 +42,6 @@ import com.github.games647.fastlogin.core.antibot.AntiBotService;
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
import com.mojang.datafixers.util.Either;
import io.netty.channel.Channel;
import io.netty.channel.ChannelHandler;
import io.netty.util.AttributeKey;
import java.net.InetSocketAddress;
import java.security.InvalidKeyException;
import java.security.KeyPair;
@@ -58,25 +50,25 @@ import java.security.PublicKey;
import java.security.SecureRandom;
import java.security.SignatureException;
import java.time.Instant;
import java.util.Objects;
import java.util.Optional;
import java.util.UUID;
import java.util.function.Function;
import javax.crypto.BadPaddingException;
import javax.crypto.IllegalBlockSizeException;
import javax.crypto.NoSuchPaddingException;
import lombok.val;
import lombok.var;
import org.bukkit.entity.Player;
import org.geysermc.floodgate.api.player.FloodgatePlayer;
import org.jetbrains.annotations.Nullable;
import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
import static com.comphenix.protocol.PacketType.Login.Client.START;
public class ProtocolLibListener extends PacketAdapter {
public static final String SOURCE_META_KEY = "source";
private final FastLoginBukkit plugin;
private final PlayerInjectionHandler handler;
//just create a new once on plugin enable. This used for verify token generation
private final SecureRandom random = new SecureRandom();
@@ -85,51 +77,63 @@ public class ProtocolLibListener extends PacketAdapter {
private final boolean verifyClientKeys;
@Nullable
private PacketContainer lastStartPacket;
public ProtocolLibListener(FastLoginBukkit plugin, AntiBotService antiBotService, boolean verifyClientKeys) {
//run async in order to not block the server, because we are making api calls to Mojang
super(params()
.plugin(plugin)
.types(START, ENCRYPTION_BEGIN)
.optionAsync());
.types(START, ENCRYPTION_BEGIN));
this.plugin = plugin;
this.antiBotService = antiBotService;
this.verifyClientKeys = verifyClientKeys;
this.handler = getHandler();
}
public static void register(FastLoginBukkit plugin, AntiBotService antiBotService, boolean verifyClientKeys) {
// they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
// TODO: make synchronous processing, but do web or database requests async
ProtocolLibrary.getProtocolManager()
.getAsynchronousManager()
.registerAsyncHandler(new ProtocolLibListener(plugin, antiBotService, verifyClientKeys))
.start();
.addPacketListener(new ProtocolLibListener(plugin, antiBotService, verifyClientKeys));
}
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
PacketContainer packet = packetEvent.getPacket();
plugin.getLog().info("New packet {} from {}; Cancellation: {}, Meta: {}",
packetEvent.getPacketType(), packetEvent.getPlayer(), packetEvent.isCancelled(),
packet.getMeta(SOURCE_META_KEY)
);
if (packetEvent.getPacketType() == START) {
if (lastStartPacket != null) {
plugin.getLog().info("Start-packet equality (Last/New): {}/{}, {}",
lastStartPacket.getHandle().hashCode(), packet.getHandle().hashCode(),
Objects.equals(lastStartPacket.getHandle(), packet.getHandle())
);
plugin.getLog().info("Content: {}, {}", lastStartPacket.getHandle(), packet.getHandle());
}
lastStartPacket = packet;
}
if (packetEvent.isCancelled()
|| plugin.getCore().getAuthPluginHook() == null
|| !plugin.isServerFullyStarted()) {
return;
}
plugin.getLog().info("New packet {} from {}", packetEvent.getPacketType(), packetEvent.getPlayer());
if (isFastLoginPacket(packetEvent)) {
// this is our own packet
return;
}
Player sender = packetEvent.getPlayer();
PacketType packetType = packetEvent.getPacketType();
if (packetType == START) {
if (plugin.getFloodgateService() != null) {
boolean success = processFloodgateTasks(packetEvent);
// don't continue execution if the player was kicked by Floodgate
if (!success) {
return;
}
}
PacketContainer packet = packetEvent.getPacket();
// PacketContainer packet = packet;
InetSocketAddress address = sender.getAddress();
String username = getUsername(packet);
@@ -154,6 +158,12 @@ public class ProtocolLibListener extends PacketAdapter {
}
}
private boolean isFastLoginPacket(PacketEvent packetEvent) {
return packetEvent.getPacket().getMeta(SOURCE_META_KEY)
.map(val -> val.equals(plugin.getName()))
.orElse(false);
}
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
@@ -164,12 +174,13 @@ public class ProtocolLibListener extends PacketAdapter {
} else {
byte[] expectedVerifyToken = session.getVerifyToken();
if (verifyNonce(sender, packetEvent.getPacket(), session.getClientPublicKey(), expectedVerifyToken)) {
packetEvent.getAsyncMarker().incrementProcessingDelay();
// packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable verifyTask = new VerifyResponseTask(
plugin, packetEvent, sender, session, sharedSecret, keyPair
);
plugin.getScheduler().runAsync(verifyTask);
verifyTask.run();
// plugin.getScheduler().runAsync(verifyTask);
} else {
sender.kickPlayer(plugin.getCore().getMessage("invalid-verify-token"));
}
@@ -179,8 +190,7 @@ public class ProtocolLibListener extends PacketAdapter {
private boolean verifyNonce(Player sender, PacketContainer packet,
ClientPublicKey clientPublicKey, byte[] expectedToken) {
try {
if (new MinecraftVersion(1, 19, 0).atOrAbove()
&& !(new MinecraftVersion(1, 19, 3).atOrAbove())) {
if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) {
Either<byte[], ?> either = packet.getSpecificModifier(Either.class).read(0);
if (clientPublicKey == null) {
Optional<byte[]> left = either.left();
@@ -222,49 +232,48 @@ public class ProtocolLibListener extends PacketAdapter {
//remove old data every time on a new login in order to keep the session only for one person
plugin.removeSession(player.getAddress());
if (packetEvent.getPacket().getMeta("original_name").isPresent()) {
//username has been injected by ManualNameChange.java
username = (String) packetEvent.getPacket().getMeta("original_name").get();
}
PacketContainer packet = packetEvent.getPacket();
Optional<ClientPublicKey> clientKey;
if (new MinecraftVersion(1, 19, 3).atOrAbove()) {
// public key sent separate
clientKey = Optional.empty();
} else {
val profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter())
.optionRead(0);
var profileKey = packet.getOptionals(BukkitConverters.getWrappedPublicKeyDataConverter())
.optionRead(0);
clientKey = profileKey.flatMap(Function.identity()).flatMap(data -> {
Instant expires = data.getExpireTime();
PublicKey key = data.getKey();
byte[] signature = data.getSignature();
return Optional.of(ClientPublicKey.of(expires, key, signature));
});
// start reading from index 1, because 0 is already used by the public key
Optional<UUID> sessionUUID = packet.getOptionals(Converters.passthrough(UUID.class)).readSafely(1);
if (verifyClientKeys && sessionUUID.isPresent() && clientKey.isPresent()
&& verifyPublicKey(clientKey.get(), sessionUUID.get())) {
// missing or incorrect
// expired always not allowed
player.kickPlayer(plugin.getCore().getMessage("invalid-public-key"));
plugin.getLog().warn("Invalid public key from player {}", username);
return;
}
var clientKey = profileKey.flatMap(opt -> opt).flatMap(this::verifyPublicKey);
if (verifyClientKeys && !clientKey.isPresent()) {
// missing or incorrect
// expired always not allowed
player.kickPlayer(plugin.getCore().getMessage("invalid-public-key"));
plugin.getLog().warn("Invalid public key from player {}", username);
return;
}
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
packetEvent.getAsyncMarker().incrementProcessingDelay();
// packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable nameCheckTask = new NameCheckTask(
plugin, random, player, packetEvent, username, clientKey.orElse(null), keyPair.getPublic()
);
plugin.getScheduler().runAsync(nameCheckTask);
// plugin.getScheduler().runAsync(nameCheckTask);
nameCheckTask.run();
}
private boolean verifyPublicKey(ClientPublicKey clientKey, UUID sessionPremiumUUID) {
private Optional<ClientPublicKey> verifyPublicKey(WrappedProfileKeyData profileKey) {
Instant expires = profileKey.getExpireTime();
PublicKey key = profileKey.getKey();
byte[] signature = profileKey.getSignature();
ClientPublicKey clientKey = ClientPublicKey.of(expires, key, signature);
try {
return EncryptionUtil.verifyClientKey(clientKey, Instant.now(), sessionPremiumUUID);
if (EncryptionUtil.verifyClientKey(clientKey, Instant.now())) {
return Optional.of(clientKey);
}
} catch (SignatureException | InvalidKeyException | NoSuchAlgorithmException ex) {
return false;
return Optional.empty();
}
return Optional.empty();
}
private String getUsername(PacketContainer packet) {
@@ -276,56 +285,4 @@ public class ProtocolLibListener extends PacketAdapter {
//player.getName() won't work at this state
return profile.getName();
}
private static PlayerInjectionHandler getHandler() {
PacketFilterManager manager = (PacketFilterManager) ProtocolLibrary.getProtocolManager();
FieldAccessor accessor = Accessors.getFieldAccessor(manager.getClass(), PlayerInjectionHandler.class, true);
return (PlayerInjectionHandler) accessor.get(manager);
}
private FloodgatePlayer getFloodgatePlayer(Player player) {
Channel channel = handler.getChannel(player);
AttributeKey<FloodgatePlayer> floodgateAttribute = AttributeKey.valueOf("floodgate-player");
return channel.attr(floodgateAttribute).get();
}
/**
* Reimplementation of the tasks injected Floodgate in ProtocolLib that are not run due to a bug
* @see <a href="https://github.com/GeyserMC/Floodgate/issues/143">Issue Floodgate#143</a>
* @see <a href="https://github.com/GeyserMC/Floodgate/blob/5d5713ed9e9eeab0f4abdaa9cf5cd8619dc1909b/spigot/src/main/java/org/geysermc/floodgate/addon/data/SpigotDataHandler.java#L121-L175">Floodgate/SpigotDataHandler</a>
* @param packetEvent the PacketEvent that won't be processed by Floodgate
* @return false if the player was kicked
*/
private boolean processFloodgateTasks(PacketEvent packetEvent) {
PacketContainer packet = packetEvent.getPacket();
Player player = packetEvent.getPlayer();
FloodgatePlayer floodgatePlayer = getFloodgatePlayer(player);
if (floodgatePlayer == null) {
return true;
}
// kick the player, if necessary
Channel channel = handler.getChannel(packetEvent.getPlayer());
AttributeKey<String> kickMessageAttribute = AttributeKey.valueOf("floodgate-kick-message");
String kickMessage = channel.attr(kickMessageAttribute).get();
if (kickMessage != null) {
player.kickPlayer(kickMessage);
return false;
}
// add prefix
String username = floodgatePlayer.getCorrectUsername();
if (packet.getGameProfiles().size() > 0) {
packet.getGameProfiles().write(0,
new WrappedGameProfile(floodgatePlayer.getCorrectUniqueId(), username));
} else {
packet.getStrings().write(0, username);
}
// remove real Floodgate data handler
ChannelHandler floodgateHandler = channel.pipeline().get("floodgate_data_handler");
channel.pipeline().remove(floodgateHandler);
return true;
}
}

View File

@@ -28,12 +28,10 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.injector.packet.PacketRegistry;
import com.comphenix.protocol.injector.temporary.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.EquivalentConverter;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.ConstructorAccessor;
import com.comphenix.protocol.reflect.accessors.FieldAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.utility.MinecraftVersion;
@@ -76,7 +74,7 @@ public class VerifyResponseTask implements Runnable {
static {
ENCRYPTION_CLASS = MinecraftReflection.getMinecraftClass(
"util." + ENCRYPTION_CLASS_NAME, ENCRYPTION_CLASS_NAME
"util." + ENCRYPTION_CLASS_NAME, ENCRYPTION_CLASS_NAME
);
}
@@ -110,11 +108,11 @@ public class VerifyResponseTask implements Runnable {
verifyResponse(session);
} finally {
//this is a fake packet; it shouldn't be sent to the server
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
// synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
// }
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
// ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
}
@@ -151,9 +149,9 @@ public class VerifyResponseTask implements Runnable {
} else {
//user tried to fake an authentication
disconnect(
"invalid-session",
"GameProfile {} ({}) tried to log in with an invalid session. ServerId: {}",
session.getRequestUsername(), socketAddress, serverId
"invalid-session",
"GameProfile {} ({}) tried to log in with an invalid session. ServerId: {}",
session.getRequestUsername(), socketAddress, serverId
);
}
} catch (IOException ioEx) {
@@ -219,15 +217,15 @@ public class VerifyResponseTask implements Runnable {
try {
// Try to get the old (pre MC 1.16.4) encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", SecretKey.class);
.getMethodByParameters("a", SecretKey.class);
} catch (IllegalArgumentException exception) {
// Get the new encryption method
encryptMethod = FuzzyReflection.fromClass(networkManagerClass)
.getMethodByParameters("a", Cipher.class, Cipher.class);
.getMethodByParameters("a", Cipher.class, Cipher.class);
// Get the needed Cipher helper method (used to generate ciphers from login key)
cipherMethod = FuzzyReflection.fromClass(ENCRYPTION_CLASS)
.getMethodByParameters("a", int.class, Key.class);
.getMethodByParameters("a", int.class, Key.class);
}
}
@@ -271,28 +269,27 @@ public class VerifyResponseTask implements Runnable {
//fake a new login packet in order to let the server handle all the other stuff
private void receiveFakeStartPacket(String username, ClientPublicKey clientKey) {
PacketContainer startPacket;
if (new MinecraftVersion(1, 19, 0).atOrAbove()) {
startPacket = new PacketContainer(START);
//see StartPacketListener for packet information
PacketContainer startPacket = new PacketContainer(START);
if (MinecraftVersion.atOrAbove(new MinecraftVersion(1, 19, 0))) {
startPacket.getStrings().write(0, username);
EquivalentConverter<WrappedProfileKeyData> converter = BukkitConverters.getWrappedPublicKeyDataConverter();
val wrappedKey = Optional.ofNullable(clientKey).map(key ->
new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
new WrappedProfileKeyData(clientKey.expiry(), clientKey.key(), clientKey.signature())
);
startPacket.getOptionals(converter).write(0, wrappedKey);
} else {
//uuid is ignored by the packet definition
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
Class<?> profileHandleType = fakeProfile.getHandleType();
Class<?> packetHandleType = PacketRegistry.getPacketClassFromType(START);
ConstructorAccessor startCons = Accessors.getConstructorAccessorOrNull(packetHandleType, profileHandleType);
startPacket = new PacketContainer(START, startCons.invoke(fakeProfile.getHandle()));
startPacket.getGameProfiles().write(0, fakeProfile);
}
//we don't want to handle our own packets so ignore filters
ProtocolLibrary.getProtocolManager().receiveClientPacket(player, startPacket, false);
startPacket.setMeta(ProtocolLibListener.SOURCE_META_KEY, plugin.getName());
ProtocolLibrary.getProtocolManager().receiveClientPacket(player, startPacket, true);
plugin.getLog().info("Sending new fake login start packet to {}-{}", player, username);
}
}

View File

@@ -27,8 +27,6 @@ package com.github.games647.fastlogin.bukkit.listener.protocollib.packet;
import java.security.PublicKey;
import java.time.Instant;
import java.util.Base64;
import java.util.StringJoiner;
import lombok.Value;
import lombok.experimental.Accessors;
@@ -43,13 +41,4 @@ public class ClientPublicKey {
public boolean isExpired(Instant verifyTimestamp) {
return !verifyTimestamp.isBefore(expiry);
}
@Override
public String toString() {
return new StringJoiner(", ", ClientPublicKey.class.getSimpleName() + '[', "]")
.add("expiry=" + expiry)
.add("key=" + Base64.getEncoder().encodeToString(key.getEncoded()))
.add("signature=" + Base64.getEncoder().encodeToString(signature))
.toString();
}
}

View File

@@ -41,7 +41,6 @@ import java.security.SignatureException;
import java.security.interfaces.RSAPublicKey;
import java.time.Instant;
import java.time.temporal.ChronoUnit;
import java.util.UUID;
import java.util.concurrent.ThreadLocalRandom;
import javax.crypto.BadPaddingException;
@@ -94,7 +93,7 @@ class EncryptionUtilTest {
// Client expires at the exact second mentioned, so use it for verification
val expiredTimestamp = clientKey.expiry();
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp, null));
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expiredTimestamp));
}
@ParameterizedTest
@@ -110,7 +109,7 @@ class EncryptionUtilTest {
val clientKey = ResourceLoader.loadClientKey(clientKeySource);
Instant expireTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp, null));
assertFalse(EncryptionUtil.verifyClientKey(clientKey, expireTimestamp));
}
@Test
@@ -118,25 +117,7 @@ class EncryptionUtilTest {
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key.json");
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, null));
}
@Test
void testValid191ClientKey() throws Exception {
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
val ownerPremiumId = UUID.fromString("0aaa2c13-922a-411b-b655-9b8c08404695");
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, ownerPremiumId));
}
@Test
void testIncorrect191ClientOwner() throws Exception {
val clientKey = ResourceLoader.loadClientKey("client_keys/valid_public_key_19_1.json");
val verificationTimestamp = clientKey.expiry().minus(5, ChronoUnit.HOURS);
val ownerPremiumId = UUID.fromString("61699b2e-d327-4a01-9f1e-0ea8c3f06bc6");
assertFalse(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp, ownerPremiumId));
assertTrue(EncryptionUtil.verifyClientKey(clientKey, verificationTimestamp));
}
@Test
@@ -178,8 +159,7 @@ class EncryptionUtilTest {
assertEquals(EncryptionUtil.getServerIdHashString(serverId, sharedSecret, serverPK), sessionHash);
}
private static String getServerHash(@SuppressWarnings("SameParameterValue") CharSequence serverId,
SecretKey sharedSecret, PublicKey serverPK) {
private static String getServerHash(CharSequence serverId, SecretKey sharedSecret, PublicKey serverPK) {
// https://wiki.vg/Protocol_Encryption#Client
// sha1 := Sha1()
// sha1.update(ASCII encoding of the server id string from Encryption Request)

View File

@@ -40,7 +40,6 @@ import java.security.NoSuchAlgorithmException;
import java.security.interfaces.RSAPrivateKey;
import java.security.interfaces.RSAPublicKey;
import java.security.spec.InvalidKeySpecException;
import java.security.spec.KeySpec;
import java.security.spec.PKCS8EncodedKeySpec;
import java.security.spec.X509EncodedKeySpec;
import java.time.Instant;
@@ -59,7 +58,7 @@ public class ResourceLoader {
) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
KeySpec privateKeySpec = new PKCS8EncodedKeySpec(content);
PKCS8EncodedKeySpec privateKeySpec = new PKCS8EncodedKeySpec(content);
KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPrivateKey) factory.generatePrivate(privateKeySpec);
@@ -89,7 +88,7 @@ public class ResourceLoader {
) {
PemObject pemObject = pemReader.readPemObject();
byte[] content = pemObject.getContent();
KeySpec pubKeySpec = new X509EncodedKeySpec(content);
X509EncodedKeySpec pubKeySpec = new X509EncodedKeySpec(content);
KeyFactory factory = KeyFactory.getInstance("RSA");
return (RSAPublicKey) factory.generatePublic(pubKeySpec);

View File

@@ -1,5 +0,0 @@
{
"expires_at": "2022-07-29T12:57:39.011Z",
"key": "-----BEGIN RSA PUBLIC KEY-----\nMIIBIjANBgkqhkiG9w0BAQEFAAOCAQ8AMIIBCgKCAQEAtYOUXdid0c09/eYoseLf8qG9fKQ/G2DY9wlSyEZaFMflwZ8ZpLFYigxzfaimpT3A5cbFIdIH2W2sYl5PwsKSs128GBh/rxXUEZlLkIkS+EfxyuMp9ITclxAjCqvFgfJbZHugtB9Ofi6knCEEgjFwMDh2efdpOXkCxtHuPFfnVzDJBbHWdlCCtJesMAnA2jCT7CqCwsi7sW2QxuTarqHP/cHKiBeBIu/SngGUB6eWmvAwERW5x2D+O26w8Z5sQCND3xQ4D868RALiPNG94TyKoJV+jKi0tTUmjGGs/1ksbSGDQb5xqIH0NYKZhoZrczYPNmJX4k7g5BA5RHX8AGORaQIDAQAB\n-----END RSA PUBLIC KEY-----\n",
"signature": "Fto/GDqEMTWpNrktWSi3tnP3ZZlo8r4Jled/5PKYRvaL/zksfjB2RK2O8pZL+w5mI2VAViTVAQmSJEF2o/BCb2L0zXp3/hC9VhZj5NTVi4KbHfnfMorj7/WJP2vvMgVxIxgLb3EEQXGS2Mmo0w2ikUVauwXgLWECvVt10KAZnTAWNIvpM8NUoZ2oCCxVimYHBtlwWQ7WvowAatP4ypa7fo3xhQg8Im1hvQDsFTNp58pgnd7l3l99xLj1uYOUJM06HGZJ/Xd0kzzJz44Csh4m50Q0RP4Nq5L+fYPeUx990Z1r1lw0sSayk+vA2Qnxgbs/z6KgkxfhBg7oOlp4ykl9cLC2kA/LdV6igqsdr/KoP4GWxwTA7RgQbhMkDFdmIg1W+gh3XqwASFQK2BAN/eAfmDTf8u9BtOEF7Ehn9uPOaiFiGztyaHxXNIkVSPTG2GXMFSijnd3Ms28jHYVY/67INTnDRmN0//KzBAoTRMe1S5idai19kug4EUVIRKDziipowzCPdbD18trdQGpK0dYOrw9XQiQd4N4V3eItpyAULGiZd8KcjgKo4orqgsUfNyhLI1keig7TyJUE3FkBOfX4hlZBm7Q/Wq4hwarlc5yZIjhsuivKV/q4tcnYYPwjP7kNMRsIApWG+yHmSIo8QfZhBiPxvtWSSLZgoFgnlxfaEko="
}

View File

@@ -25,7 +25,7 @@
SOFTWARE.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -47,7 +47,7 @@
<plugins>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<version>3.3.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
@@ -74,8 +74,7 @@
<!-- Rename the service file too to let SLF4J api find our own relocated jdk logger -->
<!-- Located in META-INF/services -->
<transformers>
<transformer
implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
<transformer implementation="org.apache.maven.plugins.shade.resource.ServicesResourceTransformer"/>
</transformers>
<filters>
<filter>
@@ -105,6 +104,11 @@
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
<repository>
<id>spigotplugins-repo</id>
<url>https://maven.gamestrike.de/mvn/</url>
</repository>
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
@@ -169,7 +173,11 @@
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.geysermc.cumulus</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
@@ -177,7 +185,7 @@
<!-- Bedrock player bridge -->
<dependency>
<groupId>org.geysermc.geyser</groupId>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>${geyser.version}</version>
<scope>provided</scope>
@@ -191,16 +199,10 @@
<!-- We need the API, but it was excluded above -->
<dependency>
<groupId>org.geysermc.geyser</groupId>
<artifactId>api</artifactId>
<groupId>org.geysermc</groupId>
<artifactId>geyser-api</artifactId>
<version>${geyser.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Login plugin-->
@@ -211,5 +213,18 @@
<scope>system</scope>
<systemPath>${project.basedir}/lib/BungeeAuth-1.4.jar</systemPath>
</dependency>
<dependency>
<groupId>de.xxschrandxx.bca</groupId>
<artifactId>BungeeCordAuthenticator</artifactId>
<version>0.0.3</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
</dependencies>
</project>

View File

@@ -26,6 +26,7 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.bungee.hook.BungeeAuthHook;
import com.github.games647.fastlogin.bungee.hook.BungeeCordAuthenticatorBungeeHook;
import com.github.games647.fastlogin.bungee.listener.ConnectListener;
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
import com.github.games647.fastlogin.core.AsyncScheduler;
@@ -46,7 +47,7 @@ import com.google.common.io.ByteStreams;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.nio.file.Path;
import java.util.Collections;
import java.util.Arrays;
import java.util.List;
import java.util.concurrent.ConcurrentMap;
import java.util.concurrent.ThreadFactory;
@@ -128,9 +129,8 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
private void registerHook() {
try {
List<Class<? extends AuthPlugin<ProxiedPlayer>>> hooks = Collections.singletonList(
BungeeAuthHook.class
);
List<Class<? extends AuthPlugin<ProxiedPlayer>>> hooks = Arrays.asList(
BungeeAuthHook.class, BungeeCordAuthenticatorBungeeHook.class);
for (Class<? extends AuthPlugin<ProxiedPlayer>> clazz : hooks) {
String pluginName = clazz.getSimpleName();

View File

@@ -36,9 +36,9 @@ import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* GitHub:
* <a href="https://github.com/vik1395/BungeeAuth-Minecraft">...</a>
* <p>
*
* Project page:
* <p>
*
* Spigot:
* <a href="https://www.spigotmc.org/resources/bungeeauth.493/">...</a>
*/

View File

@@ -0,0 +1,92 @@
/*
* SPDX-License-Identifier: MIT
*
* The MIT License (MIT)
*
* Copyright (c) 2015-2022 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.bungee.hook;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import de.xxschrandxx.bca.bungee.BungeeCordAuthenticatorBungee;
import de.xxschrandxx.bca.bungee.api.BungeeCordAuthenticatorBungeeAPI;
import java.sql.SQLException;
import java.util.logging.Level;
import net.md_5.bungee.api.connection.ProxiedPlayer;
/**
* GitHub:
* <a href="https://github.com/xXSchrandXx/SpigotPlugins/tree/master/BungeeCordAuthenticator">...</a>
* <p>
* Project page:
* <p>
* Spigot: <a href="https://www.spigotmc.org/resources/bungeecordauthenticator.87669/">...</a>
*/
public class BungeeCordAuthenticatorBungeeHook implements AuthPlugin<ProxiedPlayer> {
public final BungeeCordAuthenticatorBungeeAPI api;
public BungeeCordAuthenticatorBungeeHook(FastLoginBungee plugin) {
api = ((BungeeCordAuthenticatorBungee) plugin.getProxy().getPluginManager()
.getPlugin("BungeeCordAuthenticatorBungee")).getAPI();
plugin.getLog().info("BungeeCordAuthenticatorHook | Hooked successful!");
}
@Override
public boolean forceLogin(ProxiedPlayer player) {
if (api.isAuthenticated(player)) {
return true;
}
try {
api.setAuthenticated(player);
} catch (SQLException sqlEx) {
api.getLogger().log(Level.WARNING, "Failed to force login", sqlEx);
return false;
}
return true;
}
@Override
public boolean isRegistered(String playerName) {
try {
return api.getSQL().checkPlayerEntry(playerName);
} catch (SQLException sqlEx) {
api.getLogger().log(Level.WARNING, "Failed to check registration", sqlEx);
return false;
}
}
@Override
public boolean forceRegister(ProxiedPlayer player, String password) {
try {
return api.createPlayerEntry(player, password);
} catch (SQLException sqlEx) {
api.getLogger().log(Level.WARNING, "Failed to force register", sqlEx);
return false;
}
}
}

View File

@@ -101,7 +101,6 @@
<module name="LineLength">
<property name="max" value="120"/>
<property name="fileExtensions" value="java"/>
<property name="ignorePattern" value="^ *\* *@see.+$"/>
</module>
<!-- Checks for whitespace -->

View File

@@ -25,7 +25,7 @@
SOFTWARE.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -44,7 +44,7 @@
<repositories>
<repository>
<id>luck-repo</id>
<url>https://ci.lucko.me/plugin/repository/everything/</url>
<url>https://ci.lucko.me/plugin/repository/everything</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
@@ -70,7 +70,7 @@
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>5.0.2-SNAPSHOT</version>
<version>4.0.3</version>
<exclusions>
<!-- HikariCP uses an old version of this API that has a typo in the service interface -->
<!-- We will use the api provided by the jdk14 dependency -->
@@ -85,14 +85,14 @@
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>2.0.7</version>
<version>2.0.0-alpha7</version>
</dependency>
<!-- snakeyaml is present in Bungee, Spigot, Cauldron, so we could use this independent implementation -->
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-config</artifactId>
<version>1.19-R0.1-SNAPSHOT</version>
<version>1.19-R0.1-20220702.004052-16</version>
<exclusions>
<exclusion>
<groupId>*</groupId>
@@ -109,7 +109,11 @@
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<groupId>io.netty</groupId>
<artifactId>*</artifactId>
</exclusion>
<exclusion>
<groupId>org.geysermc.cumulus</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
@@ -117,7 +121,7 @@
<!-- Bedrock player bridge -->
<dependency>
<groupId>org.geysermc.geyser</groupId>
<groupId>org.geysermc</groupId>
<artifactId>core</artifactId>
<version>${geyser.version}</version>
<scope>provided</scope>
@@ -131,23 +135,17 @@
<!-- We need the API, but it was excluded above -->
<dependency>
<groupId>org.geysermc.geyser</groupId>
<artifactId>api</artifactId>
<groupId>org.geysermc</groupId>
<artifactId>geyser-api</artifactId>
<version>${geyser.version}</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
</dependency>
<!--Common component for contacting the Mojang API-->
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>craftapi</artifactId>
<version>0.6.2</version>
<version>0.5.3</version>
</dependency>
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee, Velocity) -->
@@ -184,14 +182,7 @@
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.10</version>
</dependency>
<dependency>
<groupId>org.xerial</groupId>
<artifactId>sqlite-jdbc</artifactId>
<version>[3.36,)</version>
<scope>provided</scope>
<version>2.9.0</version>
</dependency>
</dependencies>
</project>

View File

@@ -57,6 +57,6 @@ public class AntiBotService {
Block,
Continue
Continue;
}
}

View File

@@ -23,7 +23,7 @@
* OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
* SOFTWARE.
*/
package com.github.games647.fastlogin.core;
package com.github.games647.fastlogin.core.mojang;
import com.github.games647.craftapi.model.auth.Verification;
import com.github.games647.craftapi.resolver.MojangResolver;

View File

@@ -37,7 +37,7 @@ public interface AuthPlugin<P> {
/**
* Login the premium (paid account) player after the player joined successfully the server.
* <p>
*
* <strong>This operation will be performed async while the player successfully
* joined the server.</strong>
*
@@ -48,17 +48,17 @@ public interface AuthPlugin<P> {
/**
* Forces a register in order to protect the paid account.
* <p>
*
* <strong>This operation will be performed async while the player successfully
* joined the server.</strong>
* <p>
*
* After a successful registration the player should be logged
* in too.
* <p>
*
* The method will be called only for premium accounts.
* So it's recommended to set additionally premium property
* if possible.
* <p>
*
* Background: If we don't register an account, cracked players
* could steal the unregistered account from the paid
* player account
@@ -71,11 +71,11 @@ public interface AuthPlugin<P> {
/**
* Checks whether an account exists for this player name.
* <p>
*
* This check should check if a cracked player account exists,
* so we can be sure the premium player doesn't steal the account
* of that player.
* <p>
*
* This operation will be performed async while the player is
* connecting.
*

View File

@@ -99,11 +99,23 @@ public class FloodgateService extends BedrockService<FloodgatePlayer> {
* The FloodgateApi does not support querying players by name, so this function
* iterates over every online FloodgatePlayer and checks if the requested
* username can be found
* <br>
* <i>Falls back to non-prefixed name checks, if ProtocolLib is installed</i>
*
* @param prefixedUsername the name of the player with the prefix appended
* @return FloodgatePlayer if found, null otherwise
*/
public FloodgatePlayer getBedrockPlayer(String prefixedUsername) {
//prefixes are broken with ProtocolLib, so fall back to name checks without prefixes
//this should be removed if #493 gets fixed
if (core.getPlugin().isPluginInstalled("ProtocolLib")) {
for (FloodgatePlayer floodgatePlayer : floodgate.getPlayers()) {
if (floodgatePlayer.getUsername().equals(prefixedUsername)) {
return floodgatePlayer;
}
}
return null;
}
for (FloodgatePlayer floodgatePlayer : floodgate.getPlayers()) {
if (floodgatePlayer.getCorrectUsername().equals(prefixedUsername)) {
return floodgatePlayer;

View File

@@ -31,8 +31,8 @@ import com.github.games647.fastlogin.core.shared.LoginSource;
import java.util.UUID;
import org.geysermc.geyser.GeyserImpl;
import org.geysermc.geyser.api.network.AuthType;
import org.geysermc.geyser.session.GeyserSession;
import org.geysermc.geyser.session.auth.AuthType;
public class GeyserService extends BedrockService<GeyserSession> {
@@ -44,7 +44,7 @@ public class GeyserService extends BedrockService<GeyserSession> {
super(core);
this.geyser = geyser;
this.core = core;
this.authType = GeyserImpl.getInstance().getConfig().getRemote().authType();
this.authType = GeyserImpl.getInstance().getConfig().getRemote().getAuthType();
}
@Override
@@ -65,12 +65,7 @@ public class GeyserService extends BedrockService<GeyserSession> {
@Override
public GeyserSession getBedrockPlayer(String username) {
for (GeyserSession gSess : geyser.getSessionManager().getAllSessions()) {
if (username.equals(gSess.getClientData().getUsername())) {
return gSess;
}
}
return null;
return geyser.connectionByName(username);
}
@Override

View File

@@ -48,12 +48,4 @@ public class NamespaceKey {
public static String getCombined(String namespace, String key) {
return new NamespaceKey(namespace, key).combined;
}
public String getNamespace() {
return namespace;
}
public String getKey() {
return key;
}
}

View File

@@ -28,7 +28,6 @@ package com.github.games647.fastlogin.core.shared;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.ProxyAgnosticMojangResolver;
import com.github.games647.fastlogin.core.antibot.AntiBotService;
import com.github.games647.fastlogin.core.antibot.AntiBotService.Action;
import com.github.games647.fastlogin.core.antibot.RateLimiter;
@@ -36,10 +35,12 @@ 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.mojang.ProxyAgnosticMojangResolver;
import com.github.games647.fastlogin.core.storage.MySQLStorage;
import com.github.games647.fastlogin.core.storage.SQLStorage;
import com.github.games647.fastlogin.core.storage.SQLiteStorage;
import com.google.common.base.Ticker;
import com.google.common.net.HostAndPort;
import com.zaxxer.hikari.HikariConfig;
import java.io.IOException;
@@ -65,6 +66,8 @@ import net.md_5.bungee.config.Configuration;
import net.md_5.bungee.config.ConfigurationProvider;
import net.md_5.bungee.config.YamlConfiguration;
import org.slf4j.Logger;
import static java.util.function.Function.identity;
import static java.util.stream.Collectors.toMap;
import static java.util.stream.Collectors.toSet;
@@ -125,8 +128,8 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
antiBot = createAntiBotService(config.getSection("anti-bot"));
Set<Proxy> proxies = config.getStringList("proxies")
.stream()
.map(proxy -> proxy.split(":"))
.map(proxy -> new InetSocketAddress(proxy[0], Integer.parseInt(proxy[1])))
.map(HostAndPort::fromString)
.map(proxy -> new InetSocketAddress(proxy.getHost(), proxy.getPort()))
.map(sa -> new Proxy(Type.HTTP, sa))
.collect(toSet());
@@ -221,15 +224,20 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
public boolean setupDatabase() {
String type = config.getString("driver");
String driver = config.getString("driver");
if (!checkDriver(driver)) {
return false;
}
HikariConfig databaseConfig = new HikariConfig();
databaseConfig.setDriverClassName(driver);
String database = config.getString("database");
databaseConfig.setConnectionTimeout(config.getInt("timeout", 30) * 1_000L);
databaseConfig.setMaxLifetime(config.getInt("lifetime", 30) * 1_000L);
if (type.contains("sqlite")) {
if (driver.contains("sqlite")) {
storage = new SQLiteStorage(this, database, databaseConfig);
} else {
String host = config.get("host", "");
@@ -248,7 +256,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
databaseConfig.setUsername(config.get("username", ""));
databaseConfig.setPassword(config.getString("password"));
storage = new MySQLStorage(this, type, host, port, database, databaseConfig, useSSL);
storage = new MySQLStorage(this, driver, host, port, database, databaseConfig, useSSL);
}
try {
@@ -260,6 +268,20 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
}
private boolean checkDriver(String className) {
try {
Class.forName(className);
return true;
} catch (ClassNotFoundException notFoundEx) {
Logger log = plugin.getLog();
log.warn("This driver {} is not supported on this platform", className);
log.warn("Please choose either MySQL (Spigot, BungeeCord), SQLite (Spigot, Sponge) or "
+ "MariaDB (Sponge, Velocity)", notFoundEx);
}
return false;
}
public Configuration getConfig() {
return config;
}
@@ -268,7 +290,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
return passwordGenerator;
}
@SuppressWarnings("unused")
public void setPasswordGenerator(PasswordGenerator<P> passwordGenerator) {
this.passwordGenerator = passwordGenerator;
}

View File

@@ -91,10 +91,10 @@ public abstract class LoginSession {
@Override
public String toString() {
return new StringJoiner(", ", LoginSession.class.getSimpleName() + '[', "]")
return new StringJoiner(", ", LoginSession.class.getSimpleName() + "[", "]")
.add("profile=" + profile)
.add("requestUsername='" + requestUsername + '\'')
.add("username='" + username + '\'')
.add("requestUsername='" + requestUsername + "'")
.add("username='" + username + "'")
.add("uuid=" + uuid)
.add("registered=" + registered)
.toString();

View File

@@ -30,43 +30,11 @@ import com.zaxxer.hikari.HikariConfig;
public class MySQLStorage extends SQLStorage {
private static final String JDBC_PROTOCOL = "jdbc:";
private static final String MYSQL_DRIVER = "com.mysql.cj.jdbc.Driver";
private static final String MARIADB_DRIVER = "fastlogin.mariadb.jdbc.Driver";
public MySQLStorage(FastLoginCore<?, ?, ?> core, String driver, String host, int port, String database,
HikariConfig config, boolean useSSL) {
super(core, setParams(config, driver, host, port, database, useSSL));
}
private static HikariConfig setParams(HikariConfig config,
String driver, String host, int port, String database,
boolean useSSL) {
if ("mysql".equalsIgnoreCase(driver.trim())) {
config.setDriverClassName(MYSQL_DRIVER);
} else if ("mariadb".equalsIgnoreCase(driver.trim())) {
config.setDriverClassName(MARIADB_DRIVER);
} else {
config.setDriverClassName(driver);
}
// 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);
//enable leak detection of holding connections
config.setLeakDetectionThreshold(10_000);
// 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));
// enable MySQL specific optimizations
// addPerformanceProperties(config);
return config;
super(core,
buildJDBCUrl(driver, host, port, database),
setParams(config, useSSL));
}
private static String buildJDBCUrl(String driver, String host, int port, String database) {
@@ -78,6 +46,21 @@ public class MySQLStorage extends SQLStorage {
return protocol + "://" + host + ':' + port + '/' + database;
}
private static HikariConfig setParams(HikariConfig config, 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);
// enable MySQL specific optimizations
addPerformanceProperties(config);
return config;
}
private static void addPerformanceProperties(HikariConfig config) {
// disabled by default - will return the same prepared statement instance
config.addDataSourceProperty("cachePrepStmts", true);
@@ -88,7 +71,7 @@ public class MySQLStorage extends SQLStorage {
// default false - available in newer versions caches the statements server-side
config.addDataSourceProperty("useServerPrepStmts", true);
// default false - prefer use of local values for autocommit and
// transaction isolation (alwaysSendSetIsolation) should only be enabled if we always use the set* methods
// transaction isolation (alwaysSendSetIsolation) should only be enabled if always use the set* methods
// instead of raw SQL
// https://forums.mysql.com/read.php?39,626495,626512
config.addDataSourceProperty("useLocalSessionState", true);

View File

@@ -45,6 +45,8 @@ import static java.sql.Statement.RETURN_GENERATED_KEYS;
public abstract class SQLStorage implements AuthStorage {
private static final String JDBC_PROTOCOL = "jdbc:";
protected static final String PREMIUM_TABLE = "premium";
protected static final String CREATE_TABLE_STMT = "CREATE TABLE IF NOT EXISTS `" + PREMIUM_TABLE + "` ("
+ "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, "
@@ -68,7 +70,7 @@ public abstract class SQLStorage implements AuthStorage {
protected final FastLoginCore<?, ?, ?> core;
protected final HikariDataSource dataSource;
public SQLStorage(FastLoginCore<?, ?, ?> core, HikariConfig config) {
public SQLStorage(FastLoginCore<?, ?, ?> core, String jdbcURL, HikariConfig config) {
this.core = core;
config.setPoolName(core.getPlugin().getName());
@@ -77,6 +79,7 @@ public abstract class SQLStorage implements AuthStorage {
config.setThreadFactory(platformThreadFactory);
}
config.setJdbcUrl(JDBC_PROTOCOL + jdbcURL);
this.dataSource = new HikariDataSource(config);
}
@@ -143,12 +146,11 @@ public abstract class SQLStorage implements AuthStorage {
@Override
public void save(StoredProfile playerProfile) {
String uuid = playerProfile.getOptId().map(UUIDAdapter::toMojangId).orElse(null);
core.getPlugin().getLog().info("Before Lock");
synchronized (playerProfile) {
core.getPlugin().getLog().info("Inside Lock - Before acquiring connection");
try (Connection con = dataSource.getConnection()) {
core.getPlugin().getLog().info("Acquired connection");
try (Connection con = dataSource.getConnection()) {
String uuid = playerProfile.getOptId().map(UUIDAdapter::toMojangId).orElse(null);
playerProfile.getSaveLock().lock();
try {
if (playerProfile.isSaved()) {
try (PreparedStatement saveStmt = con.prepareStatement(UPDATE_PROFILE)) {
saveStmt.setString(1, uuid);
@@ -175,14 +177,12 @@ public abstract class SQLStorage implements AuthStorage {
}
}
}
} catch (SQLException ex) {
core.getPlugin().getLog().error("Failed to save playerProfile {}", playerProfile, ex);
} finally {
playerProfile.getSaveLock().unlock();
}
core.getPlugin().getLog().info("Released connection");
} catch (SQLException ex) {
core.getPlugin().getLog().error("Failed to save playerProfile {}", playerProfile, ex);
}
core.getPlugin().getLog().info("Released lock");
}
@Override

View File

@@ -37,38 +37,26 @@ import java.util.UUID;
import java.util.concurrent.locks.Lock;
import java.util.concurrent.locks.ReentrantLock;
import org.sqlite.JDBC;
import org.sqlite.SQLiteConfig;
public class SQLiteStorage extends SQLStorage {
private static final String SQLITE_DRIVER = "org.sqlite.SQLiteDataSource";
private final Lock lock = new ReentrantLock();
public SQLiteStorage(FastLoginCore<?, ?, ?> core, String databasePath, HikariConfig config) {
super(core, setParams(config, replacePathVariables(core.getPlugin(), databasePath)));
super(core,
"sqlite://" + replacePathVariables(core.getPlugin(), databasePath),
setParams(config));
}
private static HikariConfig setParams(HikariConfig config, String path) {
config.setDataSourceClassName(SQLITE_DRIVER);
private static HikariConfig setParams(HikariConfig config) {
config.setConnectionTestQuery("SELECT 1");
config.setMaximumPoolSize(1);
config.addDataSourceProperty("url", JDBC.PREFIX + path);
// a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
// format strings retrieved by the timestamp column to match them from MySQL
// vs the default: yyyy-MM-dd HH:mm:ss.SSS
try {
SQLiteConfig.class.getDeclaredMethod("setDateStringFormat", String.class);
config.addDataSourceProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
SQLiteConfig sqLiteConfig = new SQLiteConfig();
sqLiteConfig.setDateStringFormat("yyyy-MM-dd HH:mm:ss");
config.addDataSourceProperty("config", sqLiteConfig);
} catch (NoSuchMethodException noSuchMethodException) {
// Versions below this driver version do set the default timestamp value, so this change is not necessary
}
// TODO: test first for compatibility
// config.addDataSourceProperty("date_precision", "seconds");
return config;
}

View File

@@ -202,8 +202,8 @@ mojang-request-limit: 600
# https://github.com/games647/FastLogin/issues/85
auto-register-unknown: false
# By setting this option to false, you can disable the auto login from fastlogin. So a premium (like a paid account)
# authentication is requested, but the player won't be auto logged into the account from the auth plugin.
# This disables the auto login from fastlogin. So a premium (like a paid account) authentication is requested, but
# the player won't be auto logged into the account.
#
# This can be used as 2Factor authentication for better security of your accounts. A hacker then needs both passwords.
# The password of your Minecraft and the password to login in with your auth plugin
@@ -236,6 +236,8 @@ autoLoginFloodgate: false
#
# To prevent conflicts from two different players having the same name, it is highly recommended using a
# 'username-prefix' in floodgate/config.yml
# Note: 'username-prefix' is currently broken when used with FastLogin and ProtocolLib.
# A solution to this is to enable 'floodgatePrefixWorkaround' below.
#
# Possible values:
# false: Kick Bedrock players, if they are using an existing Premium Java account's name
@@ -262,6 +264,14 @@ allowFloodgateNameConflict: false
# Enabling this might lead to people gaining unauthorized access to other's accounts!
autoRegisterFloodgate: false
# Make FastLogin inject the Floodgate name prefixes, instead of Floodgate.
# This can fix prefixes, if you are using Floodgate alongside ProtocolLib.
# If either of those plugins are not installed, this option will have no effect.
# For more information visit: https://github.com/games647/FastLogin/issues/493
# !!!!!!!! WARNING: FLOODGATE SUPPORT IS AN EXPERIMENTAL FEATURE !!!!!!!!
# Enabling this might lead to people gaining unauthorized access to other's accounts!
floodgatePrefixWorkaround: false
# This option resembles the vanilla configuration option 'enforce-secure-profile' in the 'server.properties' file.
# It verifies if the incoming cryptographic key in the login request from the player is signed by Mojang. This key
# is necessary for servers where you or other in-game players want to verify that a chat message sent and signed by
@@ -275,14 +285,14 @@ verifyClientKeys: false
# Recommended is the use of MariaDB (a better version of MySQL)
# Single file SQLite database
driver: 'sqlite'
driver: 'org.sqlite.JDBC'
# File location
database: '{pluginDir}/FastLogin.db'
# MySQL/MariaDB
# If you want to enable it, uncomment only the lines below; this not this line.
# If on velocity use 'mariadb' as the driver
#driver: 'mysql'
# If on velocity use 'fastlogin.mariadb.jdbc.Driver' as driver
#driver: 'com.mysql.jdbc.Driver'
#host: '127.0.0.1'
#port: 3306
#database: 'fastlogin'

14
pom.xml
View File

@@ -25,7 +25,7 @@
SOFTWARE.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -55,8 +55,8 @@
<maven.compiler.target>${java.version}</maven.compiler.target>
<maven.compiler.release>${java.version}</maven.compiler.release>
<floodgate.version>development-2.2.2-SNAPSHOT</floodgate.version>
<geyser.version>2.1.0-SNAPSHOT</geyser.version>
<floodgate.version>2.2.0-SNAPSHOT</floodgate.version>
<geyser.version>2.0.0-SNAPSHOT</geyser.version>
</properties>
<modules>
@@ -141,7 +141,7 @@
<dependency>
<groupId>com.puppycrawl.tools</groupId>
<artifactId>checkstyle</artifactId>
<version>10.9.3</version>
<version>10.3.1</version>
</dependency>
</dependencies>
<executions>
@@ -187,14 +187,14 @@
<dependency>
<groupId>org.projectlombok</groupId>
<artifactId>lombok</artifactId>
<version>1.18.26</version>
<version>1.18.24</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>org.junit.jupiter</groupId>
<artifactId>junit-jupiter</artifactId>
<version>5.9.1</version>
<version>5.8.2</version>
<scope>test</scope>
</dependency>
@@ -202,7 +202,7 @@
<dependency>
<groupId>org.mockito</groupId>
<artifactId>mockito-inline</artifactId>
<version>5.2.0</version>
<version>4.6.1</version>
<scope>test</scope>
</dependency>
</dependencies>

View File

@@ -25,7 +25,7 @@
SOFTWARE.
-->
<project xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xmlns="http://maven.apache.org/POM/4.0.0"
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 https://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
@@ -60,7 +60,7 @@
</plugin>
<plugin>
<artifactId>maven-shade-plugin</artifactId>
<version>3.4.1</version>
<version>3.3.0</version>
<configuration>
<minimizeJar>true</minimizeJar>
@@ -144,7 +144,7 @@
<dependency>
<groupId>org.mariadb.jdbc</groupId>
<artifactId>mariadb-java-client</artifactId>
<version>3.1.0</version>
<version>3.0.6</version>
</dependency>
</dependencies>
</project>

View File

@@ -46,9 +46,9 @@ import com.velocitypowered.api.plugin.Plugin;
import com.velocitypowered.api.plugin.annotation.DataDirectory;
import com.velocitypowered.api.proxy.Player;
import com.velocitypowered.api.proxy.ProxyServer;
import com.velocitypowered.api.proxy.messages.ChannelMessageSink;
import com.velocitypowered.api.proxy.messages.ChannelRegistrar;
import com.velocitypowered.api.proxy.messages.MinecraftChannelIdentifier;
import com.velocitypowered.api.proxy.server.RegisteredServer;
import java.io.IOException;
import java.net.InetSocketAddress;
@@ -159,7 +159,7 @@ public class FastLoginVelocity implements PlatformPlugin<CommandSource> {
return server;
}
public void sendPluginMessage(ChannelMessageSink server, ChannelMessage message) {
public void sendPluginMessage(RegisteredServer server, ChannelMessage message) {
if (server != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
message.writeTo(dataOutput);

View File

@@ -48,7 +48,7 @@ public class ForceLoginTask
private final RegisteredServer server;
//treat player as if they had a premium account, even when they don't use to do auto login for Floodgate
//treat player as if they had a premium account, even when they don't used to do auto login for Floodgate
private final boolean forcedOnlineMode;
public ForceLoginTask(FastLoginCore<Player, CommandSource, FastLoginVelocity> core,