Compare commits

..

72 Commits

Author SHA1 Message Date
games647
5676e99dec Require player re-join to confirm auto-login request
Fixes #159
Fixes #242
Fixes #256
Fixes #286
2020-03-20 16:06:42 +01:00
games647
65469ed579 Make scheduler platform dependent 2020-03-20 13:44:58 +01:00
games647
8ecb5657d3 Drop nullable annotation usage to compile
Thanks CI
2020-03-14 19:06:05 +01:00
games647
b0cf6e39c7 Drop forced dependency 2020-03-14 19:01:09 +01:00
games647
88a526b5bf Drop support for Minecraft 1.7 2020-03-14 18:51:27 +01:00
games647
57b84509da Limit the total number of running threads
(Related #304)
2020-03-14 18:20:34 +01:00
games647
e0bc7d914c Load the after auth plugins
Fixes #306

There seems to be some capability problems
that it doesn't correctly load ProtocolLib before
this plugin. Furthermore if we use
events for our hooks they print out a warning.
2020-03-10 09:59:31 +01:00
games647
f1933f735a Merge pull request #305 from games647/dependabot/maven/pl.project13.maven-git-commit-id-plugin-4.0.0
Bump git-commit-id-plugin from 3.0.0 to 4.0.0
2020-03-09 19:18:30 +01:00
dependabot-preview[bot]
f69154418e Bump git-commit-id-plugin from 3.0.0 to 4.0.0
Bumps [git-commit-id-plugin](https://github.com/git-commit-id/maven-git-commit-id-plugin) from 3.0.0 to 4.0.0.
- [Release notes](https://github.com/git-commit-id/maven-git-commit-id-plugin/releases)
- [Commits](https://github.com/git-commit-id/maven-git-commit-id-plugin/compare/v3.0.0...v4.0.0)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>
2020-03-09 16:17:00 +00:00
ㄗㄠˋ ㄑㄧˊ
74b13231f8 bump craftapi to 0.3 (#303) 2020-03-08 12:15:37 +01:00
dependabot-preview[bot]
79627e3b60 Bump slf4j-jdk14 from 1.7.26 to 1.7.30 (#301)
Bumps [slf4j-jdk14](https://github.com/qos-ch/slf4j) from 1.7.26 to 1.7.30.
- [Release notes](https://github.com/qos-ch/slf4j/releases)
- [Commits](https://github.com/qos-ch/slf4j/compare/v_1.7.26...v_1.7.30)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-06 19:28:02 +01:00
dependabot-preview[bot]
1722ab3267 Bump maven-shade-plugin from 3.2.0 to 3.2.2 (#302)
Bumps [maven-shade-plugin](https://github.com/apache/maven-shade-plugin) from 3.2.0 to 3.2.2.
- [Release notes](https://github.com/apache/maven-shade-plugin/releases)
- [Commits](https://github.com/apache/maven-shade-plugin/compare/maven-shade-plugin-3.2.0...maven-shade-plugin-3.2.2)

Signed-off-by: dependabot-preview[bot] <support@dependabot.com>

Co-authored-by: dependabot-preview[bot] <27856297+dependabot-preview[bot]@users.noreply.github.com>
2020-03-06 19:27:19 +01:00
games647
fdc0485f05 [CI-SKIP] Use HTTPS and newer URLs in Maven 2020-03-03 10:50:48 +01:00
games647
1068ddbadd Enable Maven cache for runner 2020-03-03 10:47:55 +01:00
games647
b0d5bd606c Rework issue templates 2020-03-03 10:47:37 +01:00
games647
1e59101ada Enable MySQL storage caching 2020-03-03 10:46:43 +01:00
games647
46ac3c74e4 Fix Java setup action version 2020-02-17 15:28:58 +01:00
games647
d22af0f42e Fix unique name 2020-02-17 15:24:45 +01:00
games647
2cc078773b Migrate to GitHub actions 2020-02-17 15:18:58 +01:00
games647
c1b8b60bf7 Use HTTPS for code comments too 2020-02-12 10:59:11 +01:00
games647
c54d6eecfc Merge branch 'JLLeitschuh/fix/JLL/use_https_to_resolve_dependencies' 2020-02-12 10:57:30 +01:00
Jonathan Leitschuh
0cf6c4c188 Use HTTPS instead of HTTP to resolve dependencies
This fixes a security vulnerability in this project where the `pom.xml`
files were configuring Maven to resolve dependencies over HTTP instead of
HTTPS.

Signed-off-by: Jonathan Leitschuh <Jonathan.Leitschuh@gmail.com>
2020-02-12 10:55:08 +01:00
games647
282467d21b Set a default kick reason if nothing is specified 2020-01-08 10:44:10 +01:00
games647
e8a5dc7433 Use BungeeCord event methods for kicking the player 2020-01-07 20:35:16 +01:00
games647
4b45932d6a Migrate to asynchronous calls to LoginSecurity
(Fixes #290)
2020-01-06 10:04:44 +01:00
games647
1375cf3997 Fix ProtocolLib version 2019-12-29 13:35:33 +01:00
games647
5a5cf016d9 Remove on blacklist status on join too 2019-12-29 13:33:18 +01:00
games647
2c7e569653 Fix force login events being sync instead of async (Fixes #278) 2019-11-09 13:57:12 +01:00
games647
94c5fe302e Merge pull request #277 from FearGames/master
Fix build and add events
2019-11-02 16:41:43 +01:00
Gabriele C
c80012ddb1 Allow multi-line messages 2019-10-25 11:54:26 +02:00
Gabriele C
17361b1b54 Implement PreLogin and AutoLogin events 2019-10-23 14:45:51 +02:00
Gabriele C
426b458a58 Fix BungeeAuth dependency 2019-10-23 12:50:40 +02:00
games647
62a8b939cc Use 'TEXT' for strings in the configuration file 2019-08-16 14:59:14 +02:00
games647
327c8c4c9d Dump dependencies 2019-07-09 12:56:14 +02:00
games647
fbbe7a735a Initialize the decryption cipher only once
(Related #259)
2019-07-09 12:52:54 +02:00
games647
4110ce2fa2 Fix creating logging session in ProtocolSupport environments
Fixes #251
2019-05-11 21:16:38 +02:00
games647
28743d23bf Remove duplicate repository 2019-05-06 17:39:21 +02:00
games647
145bd95679 Drop usage of deprecated apache lang library 2019-05-06 17:37:13 +02:00
games647
3fe17cf8d9 Merge pull request #250 from lenis0012/patch-2
Update LoginSecurity maven repository
2019-05-06 17:36:15 +02:00
lenis0012
3446d4c443 Update LoginSecurity maven repository 2019-05-06 11:35:39 +02:00
games647
c528433079 Allow username only database logins
Related #247
2019-04-15 20:35:56 +02:00
games647
25858ea11f Enable travis caching 2019-04-14 10:16:53 +02:00
games647
204ffbb2ee Document network requests 2019-04-14 10:16:33 +02:00
games647
9c1ba81cbe Fix running force actions in LoginSecurity thread-safe 2019-04-14 10:16:19 +02:00
games647
15c5857c4f Correctly set the offline UUID in ProtocolSupport (Thanks to @Shevchik) 2018-08-30 12:32:42 +02:00
games647
4b0ad3b186 Update ProtocolSupport hook 2018-08-29 19:49:10 +02:00
games647
101f7207a9 Limit plugin channels to 16 characters for 1.7 support
Fixes #223
2018-08-07 13:41:30 +02:00
games647
64175fe9e8 Update to CraftApi 0.2 2018-07-29 18:47:07 +02:00
games647
f7a10d86eb Don't transform because it's already compatible with 1.13 2018-07-25 16:55:52 +02:00
games647
542aabad73 Lowercase the namespace correctly 2018-07-24 14:33:33 +02:00
games647
260b93a565 Channel names should be lowercase according to the spec
Related #217, #218
2018-07-24 10:17:21 +02:00
games647
6604cca8bd Use Plugin:Subchannel for channel messages
This is required to follow 1.13 spec.
(Related #216, #215)
2018-07-23 14:11:40 +02:00
games647
c172b1ec84 Prevent duplicate message fetching for kick in PLib (Fixes #212) 2018-06-27 18:59:40 +02:00
games647
9b0b8f5fcb Fix repository link of ProtocolLib 2018-06-06 20:17:24 +02:00
games647
20104b2b00 Update ProtocolLib to fix building 2018-06-06 09:33:05 +02:00
games647
fd76d2448e Add advanced options for the connection pool (Fixes #210) 2018-06-04 21:28:19 +02:00
games647
53a1821a9d Fix ProtocolLib repository 2018-06-04 21:27:23 +02:00
games647
1c6f4e82e0 Fix NPE for cracked players on non-bungee environments 2018-05-23 19:14:20 +02:00
games647
084afef899 Update premium UUID on verification (Related #208) 2018-05-04 19:45:43 +02:00
games647
8a9eed3a74 Add /newline variable 2018-05-04 18:54:26 +02:00
games647
1ea6d929b1 Clarify how to configure MariaDB/MySQL correctly 2018-04-26 10:09:40 +02:00
games647
ddc3aa9279 Replace deprecated PropertiesResolveEvent with LoginFinishEvent
affects only ProtocolSupport
2018-04-26 10:08:56 +02:00
games647
2a79a9511b Fix auto register type in BungeeCord not being sent 2018-04-07 16:14:46 +02:00
games647
791df26702 Fix defaults overriding config 2018-04-05 18:42:50 +02:00
games647
cdf1988f2f Fix comping after craftapi update 2018-04-05 17:38:16 +02:00
games647
f476c091bb Fix default message loading, because default values are ignored by .getKeys() 2018-04-05 17:33:32 +02:00
games647
352c72df64 Add note about developments builds 2018-04-02 14:43:43 +02:00
games647
2cd0b194aa We are SNAPSHOT build not a release candidate
Maven versions plugin is great for multi-modules.
Run mvn version:set -DnewVersion=... and that's it.
2018-03-31 10:38:57 +02:00
games647
f2e42019d6 Mention the new FastLogin module names in the setup guide 2018-03-31 10:34:26 +02:00
games647
82ec71e8d0 Update premium status for non-bungeecord setups (Related #200) 2018-03-27 20:43:53 +02:00
games647
6d207d62ba Fix BungeeCord blacklist condition checking 2018-03-25 15:44:51 +02:00
games647
889dab3152 Migrate to PlaceholderExpansion from PlaceholderAPI 2018-03-21 11:02:55 +01:00
74 changed files with 1983 additions and 431 deletions

View File

@@ -1,6 +1,14 @@
---
name: Bug report
about: Something isn't working
title: ''
labels: 'bug'
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 duplicate issues!)
[//]: # (Before reporting make sure you're running the **latest build** of the plugin and checked for existing issues!)
### What behaviour is observed:
[//]: # (What happened?)
@@ -11,18 +19,23 @@
### Steps/models to reproduce:
[//]: # (The actions that cause the issue. Please explain it in detail)
### Screenshots (if applicable)
[//]: # (You can drop the files here directly)
### Plugin list:
[//]: # (This can be found by running `/pl`)
### Environment description
[//]: # (Server software with exact version number, Minecraft version, SQLite/MySQL, ...)
[//]: # (Server software with exact version number, Minecraft version, SQLite/MySQL/MariaDB, ...)
### Plugin version or build number (don't write latest):
[//]: # (This can be found by running `/version plugin-name`.)
### Error Log:
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of the error or stacktrace (if any)
### Server Log:
[//]: # (No images please - only the textual representation)
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of the error, stacktrace or the complete log (if any)
### Configuration:
[//]: # (No images please - only the textual representation)
[//]: # (remember to delete any sensitive data)
[Hastebin](https://hastebin.com/) / [Gist](https://gist.github.com/) link of your config.yml file

View File

@@ -0,0 +1,22 @@
---
name: Enhancement request
about: New feature or change request
title: ''
labels: 'enhancement'
assignees: ''
---
[//]: # (Lines in this format are considered as comments and will not be displayed.)
### 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.)

10
.github/ISSUE_TEMPLATE/question.md vendored Normal file
View File

@@ -0,0 +1,10 @@
---
name: Question
about: You want to ask something
title: ''
labels: 'question'
assignees: ''
---

8
.github/pull_request_template.md vendored Normal file
View File

@@ -0,0 +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: motiviation, enhancement)
### Related issue
[//]: # (Reference it using '#NUMBER'. Ex: Fixes/Related #...)

42
.github/workflows/maven.yml vendored Normal file
View File

@@ -0,0 +1,42 @@
# Human readable name
name: Java CI
# Build on every push and pull request regardless of the branch
# Wiki: https://help.github.com/en/actions/reference/events-that-trigger-workflows
on:
- push
- pull_request
jobs:
# job id
build_and_test:
# Environment image - always newest OS
runs-on: ubuntu-latest
# Run steps
steps:
# Pull changes
- uses: actions/checkout@v2
# Cache artifacts - however this has the downside that we don't get notified of
# artifact resolution failures like invalid repository
# Nevertheless the repositories should be more stable and it makes no sense to pull
# a same version every time
# A dry run would make more sense
- uses: actions/cache@v1
with:
path: ~/.m2/repository
key: ${{ runner.os }}-maven-${{ hashFiles('**/pom.xml') }}
restore-keys: |
${{ runner.os }}-maven-
# Setup Java
- name: Set up JDK
uses: actions/setup-java@v1.3.0
with:
# Use Java 8, because it's minimum required version
java-version: 8
# 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 posssible errors in depdendencies
run: mvn --batch-mode package --no-snapshot-updates --strict-checksums --file pom.xml

1
.gitignore vendored
View File

@@ -15,6 +15,7 @@ nb-configuration.xml
# Maven
target/
pom.xml.versionsBackup
# Gradle
.gradle

View File

@@ -1,13 +0,0 @@
# Use https://travis-ci.org/ for automatic testing
# speed up testing https://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/
sudo: false
# This is a java project
language: java
script: mvn test -B
jdk:
- oraclejdk8
- oraclejdk9

View File

@@ -3,7 +3,7 @@
Checks if a Minecraft player has a paid account (premium). If so, they can skip offline authentication (auth plugins).
So they don't need to enter passwords. This is also called auto login (auto-login).
### Features:
## Features
* Detect paid accounts from others
* Automatically login paid accounts (premium)
@@ -20,31 +20,43 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
* Good performance by using async non blocking operations
* Locale messages
* Import the database from similar plugins
* Free
* Open source
## Development builds
Development builds of this project can be acquired at the provided CI (continuous integration) server. It contains the
latest changes from the Source-Code in preparation for the following release. This means they could contain new
features, bug fixes and other changes since the last release.
Nevertheless builds are only tested using a small set of automated and a few manual tests. Therefore they **could**
contain new bugs and are likely to be less stable than released versions.
https://ci.codemc.org/job/Games647/job/FastLogin/changes
***
### Commands:
## Commands
/premium [player] Label the invoker or the argument as paid account
/cracked [player] Label the invoker or the argument as cracked account
### Permissions:
## Permissions
fastlogin.bukkit.command.premium
fastlogin.bukkit.command.cracked
fastlogin.command.premium.other
fastlogin.command.cracked.other
fastlogin.command.import
### Requirements:
* Plugin: [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or
[ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
* [Spigot](https://www.spigotmc.org) 1.7+
## Requirements
* Plugin:
* [ProtocolLib](https://www.spigotmc.org/resources/protocollib.1997/) or
* [ProtocolSupport](https://www.spigotmc.org/resources/protocolsupport.7201/)
* [Spigot](https://www.spigotmc.org) 1.8.8+
* Java 8+
* Run Spigot and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
* An auth plugin. Supported plugins
#### Bukkit/Spigot/Paper
### Bukkit/Spigot/Paper
* [AuthMe (5.X)](https://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
* [xAuth](https://dev.bukkit.org/bukkit-plugins/xauth/)
@@ -54,28 +66,36 @@ So they don't need to enter passwords. This is also called auto login (auto-logi
* [LoginSecurity](https://dev.bukkit.org/bukkit-plugins/loginsecurity/)
* [UltraAuth](https://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
#### BungeeCord/Waterfall
### BungeeCord/Waterfall
* [BungeeAuth](https://www.spigotmc.org/resources/bungeeauth.493/)
## Network requests
This plugin performs network requests to:
* https://api.mojang.com - retrieving uuid data to decide if we should activate premium login
* https://sessionserver.mojang.com - verify if the player is the owner of that account
***
### How to install
## How to install
#### Bukkit/Spigot/Paper
### Bukkit/Spigot/Paper
1. Download and install ProtocolLib
2. Download and install FastLogin
1. Download and install ProtocolLib/ProtocolSupport
2. Download and install FastLogin (or FastLoginBukkit for newer versions)
3. Set your server in offline mode by setting the value onlinemode in your server.properties to false
#### BungeeCord/Waterfall
### BungeeCord/Waterfall
1. Activate BungeeCord in the Spigot configuration
2. Restart your server
3. Now there is proxy-whitelist file in the FastLogin folder
Put your stats id from the BungeeCord config into this file
4. Activate ipForward in your BungeeCord config
5. Download and Install FastLogin on BungeeCord AND Spigot (on the servers where your login plugin is)
5. Download and Install FastLogin (or FastLoginBungee in newer versions) on BungeeCord AND Spigot
(on the servers where your login plugin is or where player should be able to execute the commands of FastLogin)
6. Check your database settings in the config of FastLogin on BungeeCord
7. Set your proxy (BungeeCord) in offline mode by setting the value onlinemode in your config.yml to false
8. You should *always* firewall your Spigot server that it's only accessible through BungeeCord

View File

@@ -1,11 +1,11 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
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>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.11</version>
<version>1.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -20,7 +20,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<version>3.2.2</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
@@ -56,44 +56,52 @@
</build>
<repositories>
<!--Bukkit-Server-API -->
<!-- Bukkit-Server-API -->
<repository>
<id>spigot-repo</id>
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
<!-- Disable snapshot release policy to speed up, when finding a artifact -->
<releases>
<enabled>false</enabled>
</releases>
</repository>
<!--LoginSecurity-->
<repository>
<id>lenis0012-repo</id>
<url>http://ci.lenis0012.com/plugin/repository/everything/</url>
</repository>
<!--ProtocolLib-->
<!-- ProtocolLib -->
<repository>
<id>dmulloy2-repo</id>
<url>http://repo.dmulloy2.net/content/groups/public/</url>
<url>https://repo.dmulloy2.net/nexus/repository/public/</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--AuthMe Reloaded and xAuth -->
<!-- AuthMe Reloaded, xAuth and LoginSecurity -->
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
<!--GitHub automatic maven builds-->
<!-- GitHub automatic maven builds -->
<repository>
<id>jitpack.io</id>
<url>https://jitpack.io</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<!--PlaceholderAPI -->
<!-- PlaceholderAPI -->
<repository>
<id>placeholderapi</id>
<url>http://repo.extendedclip.com/content/repositories/placeholderapi/</url>
<url>https://repo.extendedclip.com/content/repositories/placeholderapi</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
</repositories>
<dependencies>
<!--Common plugin component-->
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin.core</artifactId>
@@ -112,22 +120,24 @@
<dependency>
<groupId>com.comphenix.protocol</groupId>
<artifactId>ProtocolLib</artifactId>
<version>4.3.0</version>
<version>4.5.0</version>
<scope>provided</scope>
</dependency>
<!--Changing onlinemode on login process-->
<dependency>
<groupId>com.github.ProtocolSupport</groupId>
<artifactId>ProtocolSupport</artifactId>
<!--4.25.dev-->
<version>a4f060dc46</version>
<!--4.29.dev after commit about API improvements-->
<version>3a80c661fe</version>
<scope>provided</scope>
</dependency>
<!--Provide premium placeholders-->
<dependency>
<groupId>me.clip</groupId>
<artifactId>placeholderapi</artifactId>
<version>2.8.4</version>
<version>2.10.4</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>
@@ -156,7 +166,7 @@
<dependency>
<groupId>com.lenis0012.bukkit</groupId>
<artifactId>loginsecurity</artifactId>
<version>2.1.7</version>
<version>3.0.1</version>
<scope>provided</scope>
<optional>true</optional>
<exclusions>

View File

@@ -13,21 +13,55 @@ import java.util.Optional;
*/
public class BukkitLoginSession extends LoginSession {
private SkinProperty skinProperty;
private static final byte[] EMPTY_ARRAY = {};
private final String serverId;
private final byte[] verifyToken;
private boolean verified;
public BukkitLoginSession(String username, boolean registered, StoredProfile profile) {
private SkinProperty skinProperty;
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
, StoredProfile profile) {
super(username, registered, profile);
this.serverId = serverId;
this.verifyToken = verifyToken.clone();
}
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, StoredProfile profile) {
// confirmation login
super(username, profile);
this.serverId = serverId;
this.verifyToken = verifyToken.clone();
}
//available for BungeeCord
public BukkitLoginSession(String username, boolean registered) {
this(username, registered, null);
this(username, "", EMPTY_ARRAY, registered, null);
}
//cracked player
public BukkitLoginSession(String username, StoredProfile profile) {
this(username, false, profile);
this(username, "", EMPTY_ARRAY, false, profile);
}
//ProtocolSupport
public BukkitLoginSession(String username, boolean registered, StoredProfile profile) {
this(username, "", EMPTY_ARRAY, registered, profile);
}
/**
* Gets the verify token the server sent to the client.
*
* Empty if it's a BungeeCord connection
*
* @return the verify token from the server
*/
public synchronized byte[] getVerifyToken() {
return verifyToken.clone();
}
/**

View File

@@ -0,0 +1,27 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.core.AsyncScheduler;
import java.util.concurrent.Executor;
import java.util.concurrent.ThreadFactory;
import org.bukkit.Bukkit;
import org.bukkit.plugin.Plugin;
import org.slf4j.Logger;
public class BukkitScheduler extends AsyncScheduler {
private final Plugin plugin;
private final Executor syncExecutor;
public BukkitScheduler(Plugin plugin, Logger logger, ThreadFactory threadFactory) {
super(logger, threadFactory);
this.plugin = plugin;
syncExecutor = r -> Bukkit.getScheduler().runTask(plugin, r);
}
public Executor getSyncExecutor() {
return syncExecutor;
}
}

View File

@@ -0,0 +1,116 @@
package com.github.games647.fastlogin.bukkit;
import java.math.BigInteger;
import java.nio.charset.StandardCharsets;
import java.security.GeneralSecurityException;
import java.security.Key;
import java.security.KeyPair;
import java.security.KeyPairGenerator;
import java.security.MessageDigest;
import java.security.NoSuchAlgorithmException;
import java.security.PublicKey;
import java.util.Random;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import javax.crypto.spec.SecretKeySpec;
/**
* Encryption and decryption minecraft util for connection between servers
* and paid Minecraft account clients.
*
* @see net.minecraft.server.MinecraftEncryption
*/
public class EncryptionUtil {
public static final int VERIFY_TOKEN_LENGTH = 4;
public static final String KEY_PAIR_ALGORITHM = "RSA";
private EncryptionUtil() {
//utility
}
/**
* Generate a RSA key pair
*
* @return The RSA key pair.
*/
public static KeyPair generateKeyPair() {
try {
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance(KEY_PAIR_ALGORITHM);
keyPairGenerator.initialize(1_024);
return keyPairGenerator.generateKeyPair();
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
//Should be existing in every vm
throw new ExceptionInInitializerError(nosuchalgorithmexception);
}
}
/**
* Generate a random token. This is used to verify that we are communicating with the same player
* in a login session.
*
* @param random random generator
* @return an error with 4 bytes long
*/
public static byte[] generateVerifyToken(Random random) {
byte[] token = new byte[VERIFY_TOKEN_LENGTH];
random.nextBytes(token);
return token;
}
/**
* Generate the server id based on client and server data.
*
* @param sessionId session for the current login attempt
* @param sharedSecret shared secret between the client and the server
* @param publicKey public key of the server
* @return the server id formatted as a hexadecimal string.
*/
public static String getServerIdHashString(String sessionId, Key sharedSecret, PublicKey publicKey) {
try {
byte[] serverHash = getServerIdHash(sessionId, sharedSecret, publicKey);
return (new BigInteger(serverHash)).toString(16);
} catch (NoSuchAlgorithmException e) {
e.printStackTrace();
}
return "";
}
/**
* Decrypts the content and extracts the key spec.
*
* @param cipher decryption cipher initialized with the private key
* @param sharedKey the encrypted shared key
* @return shared secret key
* @throws GeneralSecurityException if it fails to decrypt the data
*/
public static SecretKey decryptSharedKey(Cipher cipher, byte[] sharedKey) throws GeneralSecurityException {
return new SecretKeySpec(decrypt(cipher, sharedKey), "AES");
}
/**
* Decrypted the given data using the cipher.
*
* @param cipher decryption cypher initialized with the private key
* @param data the encrypted data
* @return clear text data
* @throws GeneralSecurityException if it fails to decrypt the data
*/
public static byte[] decrypt(Cipher cipher, byte[] data) throws GeneralSecurityException {
return cipher.doFinal(data);
}
private static byte[] getServerIdHash(String sessionId, Key sharedSecret, PublicKey publicKey)
throws NoSuchAlgorithmException {
MessageDigest digest = MessageDigest.getInstance("SHA-1");
digest.update(sessionId.getBytes(StandardCharsets.ISO_8859_1));
digest.update(sharedSecret.getEncoded());
digest.update(publicKey.getEncoded());
return digest.digest();
}
}

View File

@@ -1,14 +1,18 @@
package com.github.games647.fastlogin.bukkit;
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
import com.github.games647.fastlogin.bukkit.command.CrackedCommand;
import com.github.games647.fastlogin.bukkit.command.PremiumCommand;
import com.github.games647.fastlogin.bukkit.listener.BungeeListener;
import com.github.games647.fastlogin.bukkit.listener.ConnectionListener;
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;
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
import com.github.games647.fastlogin.bukkit.task.DelayedAuthHook;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.google.common.io.ByteArrayDataOutput;
@@ -27,24 +31,34 @@ import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.plugin.messaging.PluginMessageRecipient;
import org.slf4j.Logger;
import static com.github.games647.fastlogin.core.message.ChangePremiumMessage.CHANGE_CHANNEL;
import static com.github.games647.fastlogin.core.message.SuccessMessage.SUCCESS_CHANNEL;
/**
* This plugin checks if a player has a paid account and if so tries to skip offline mode authentication.
*/
public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<CommandSender> {
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
private final ConcurrentMap<String, BukkitLoginSession> loginSession = new ConcurrentHashMap<>();
private final Logger logger = CommonUtil.createLoggerFromJDK(getLogger());
private final ConcurrentMap<String, BukkitLoginSession> loginSession = CommonUtil.buildCache(1, -1);
private final Map<UUID, PremiumStatus> premiumPlayers = new ConcurrentHashMap<>();
private final Logger logger;
private boolean serverStarted;
private boolean bungeeCord;
private final BukkitScheduler scheduler;
private FastLoginCore<Player, CommandSender, FastLoginBukkit> core;
public FastLoginBukkit() {
this.logger = CommonUtil.createLoggerFromJDK(getLogger());
this.scheduler = new BukkitScheduler(this, logger, getThreadFactory());
}
@Override
public void onEnable() {
core = new FastLoginCore<>(this);
core.load();
try {
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
} catch (ClassNotFoundException notFoundEx) {
@@ -62,9 +76,17 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
PluginManager pluginManager = getServer().getPluginManager();
if (bungeeCord) {
//check for incoming messages from the bungeecord version of this plugin
getServer().getMessenger().registerIncomingPluginChannel(this, getName(), new BungeeListener(this));
getServer().getMessenger().registerOutgoingPluginChannel(this, getName());
setServerStarted();
// check for incoming messages from the bungeecord version of this plugin
String forceChannel = NamespaceKey.getCombined(getName(), LoginActionMessage.FORCE_CHANNEL);
getServer().getMessenger().registerIncomingPluginChannel(this, forceChannel, new BungeeListener(this));
// outgoing
String successChannel = new NamespaceKey(getName(), SUCCESS_CHANNEL).getCombinedName();
String changeChannel = new NamespaceKey(getName(), CHANGE_CHANNEL).getCombinedName();
getServer().getMessenger().registerOutgoingPluginChannel(this, successChannel);
getServer().getMessenger().registerOutgoingPluginChannel(this, changeChannel);
} else {
if (!core.setupDatabase()) {
setEnabled(false);
@@ -73,8 +95,11 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
if (pluginManager.isPluginEnabled("ProtocolSupport")) {
pluginManager.registerEvents(new ProtocolSupportListener(this), this);
} else if (pluginManager.isPluginEnabled("ProtocolLib")) {
ProtocolLibListener.register(this);
pluginManager.registerEvents(new SkinApplyListener(this), this);
} else {
logger.warn("ProtocolSupport have to be installed if you don't use BungeeCord");
logger.warn("Either ProtocolLib or ProtocolSupport have to be installed if you don't use BungeeCord");
}
}
@@ -141,13 +166,29 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return premiumPlayers.getOrDefault(onlinePlayer, PremiumStatus.UNKNOWN);
}
/**
* Wait before the server is fully started. This is workaround, because connections right on startup are not
* injected by ProtocolLib
*
* @return true if ProtocolLib can now intercept packets
*/
public boolean isServerFullyStarted() {
return serverStarted;
}
public void setServerStarted() {
if (!this.serverStarted) {
this.serverStarted = true;
}
}
public void sendPluginMessage(PluginMessageRecipient player, ChannelMessage message) {
if (player != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
dataOutput.writeUTF(message.getChannelName());
message.writeTo(dataOutput);
player.sendPluginMessage(this, this.getName(), dataOutput.toByteArray());
NamespaceKey channel = new NamespaceKey(getName(), message.getChannelName());
player.sendPluginMessage(this, channel.getCombinedName(), dataOutput.toByteArray());
}
}
@@ -161,6 +202,11 @@ public class FastLoginBukkit extends JavaPlugin implements PlatformPlugin<Comman
return logger;
}
@Override
public BukkitScheduler getScheduler() {
return scheduler;
}
@Override
public void sendMessage(CommandSender receiver, String message) {
receiver.sendMessage(message);

View File

@@ -1,11 +1,15 @@
package com.github.games647.fastlogin.bukkit;
import java.util.stream.Collectors;
import me.clip.placeholderapi.PlaceholderAPI;
import me.clip.placeholderapi.PlaceholderHook;
import me.clip.placeholderapi.expansion.PlaceholderExpansion;
import org.bukkit.entity.Player;
public class PremiumPlaceholder extends PlaceholderHook {
public class PremiumPlaceholder extends PlaceholderExpansion {
private static final String PLACEHOLDER_VARIABLE = "fastlogin_status";
private final FastLoginBukkit plugin;
@@ -13,16 +17,37 @@ public class PremiumPlaceholder extends PlaceholderHook {
this.plugin = plugin;
}
public static void register(FastLoginBukkit plugin) {
PremiumPlaceholder placeholderHook = new PremiumPlaceholder(plugin);
PlaceholderAPI.registerPlaceholderHook(PLACEHOLDER_VARIABLE, placeholderHook);
}
@Override
public String onPlaceholderRequest(Player player, String variable) {
if (player != null && "fastlogin_status".contains(variable)) {
if (player != null && PLACEHOLDER_VARIABLE.equals(variable)) {
return plugin.getStatus(player.getUniqueId()).name();
}
return "";
}
public static void register(FastLoginBukkit plugin) {
PlaceholderAPI.registerPlaceholderHook(plugin, new PremiumPlaceholder(plugin));
@Override
public String getIdentifier() {
return PLACEHOLDER_VARIABLE;
}
@Override
public String getPlugin() {
return plugin.getName();
}
@Override
public String getAuthor() {
return plugin.getDescription().getAuthors().stream().collect(Collectors.joining(", "));
}
@Override
public String getVersion() {
return plugin.getName();
}
}

View File

@@ -1,9 +1,8 @@
package com.github.games647.fastlogin.bukkit.commands;
package com.github.games647.fastlogin.bukkit.command;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.StoredProfile;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
@@ -44,7 +43,7 @@ public class CrackedCommand extends ToggleCommand {
profile.setPremium(false);
profile.setId(null);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
});
} else {
@@ -76,7 +75,7 @@ public class CrackedCommand extends ToggleCommand {
plugin.getCore().sendLocaleMessage("remove-premium", sender);
profile.setPremium(false);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
});
}

View File

@@ -1,11 +1,9 @@
package com.github.games647.fastlogin.bukkit.commands;
package com.github.games647.fastlogin.bukkit.command;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.ConfirmationState;
import com.github.games647.fastlogin.core.StoredProfile;
import java.util.UUID;
import org.bukkit.Bukkit;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -37,31 +35,44 @@ public class PremiumCommand extends ToggleCommand {
return;
}
if (forwardPremiumCommand(sender, sender.getName())) {
Player player = (Player) sender;
String playerName = sender.getName();
if (forwardPremiumCommand(sender, playerName)) {
return;
}
UUID id = ((Player) sender).getUniqueId();
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getCore().getPendingConfirms().contains(id)) {
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
plugin.getCore().getPendingConfirms().add(id);
return;
// non-bungee mode
if (plugin.getConfig().getBoolean("premium-confirm")) {
ConfirmationState state = plugin.getCore().getPendingConfirms().get(playerName);
if (state == null) {
// no pending confirmation
plugin.getCore().getPendingConfirms().put(playerName, ConfirmationState.REQUIRE_RELOGIN);
player.kickPlayer(plugin.getCore().getMessage("premium-confirm"));
} else if (state == ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN) {
// player logged in successful using premium authentication
activate(sender, playerName);
}
} else {
activate(sender, playerName);
}
}
plugin.getCore().getPendingConfirms().remove(id);
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
if (profile.isPremium()) {
plugin.getCore().sendLocaleMessage("already-exists", sender);
} else {
//todo: resolve uuid
profile.setPremium(true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getCore().getStorage().save(profile);
});
private void activate(CommandSender sender, String playerName) {
plugin.getCore().getPendingConfirms().remove(playerName);
plugin.getCore().sendLocaleMessage("add-premium", sender);
}
//todo: load async
StoredProfile profile = plugin.getCore().getStorage().loadProfile(playerName);
if (profile.isPremium()) {
plugin.getCore().sendLocaleMessage("already-exists", sender);
} else {
//todo: resolve uuid
profile.setPremium(true);
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
});
plugin.getCore().sendLocaleMessage("add-premium", sender);
}
}
private void onPremiumOther(CommandSender sender, Command command, String[] args) {
@@ -85,7 +96,7 @@ public class PremiumCommand extends ToggleCommand {
} else {
//todo: resolve uuid
profile.setPremium(true);
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
plugin.getScheduler().runAsync(() -> {
plugin.getCore().getStorage().save(profile);
});

View File

@@ -1,8 +1,8 @@
package com.github.games647.fastlogin.bukkit.commands;
package com.github.games647.fastlogin.bukkit.command;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.messages.ChangePremiumMessage;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import java.util.Optional;

View File

@@ -0,0 +1,52 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import org.bukkit.event.Cancellable;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
private static final HandlerList handlers = new HandlerList();
private final LoginSession session;
private final StoredProfile profile;
private boolean cancelled;
public BukkitFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
super(true);
this.session = session;
this.profile = profile;
}
@Override
public LoginSession getSession() {
return session;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -0,0 +1,47 @@
package com.github.games647.fastlogin.bukkit.event;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSource;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import org.bukkit.event.Event;
import org.bukkit.event.HandlerList;
public class BukkitFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
private static final HandlerList handlers = new HandlerList();
private final String username;
private final LoginSource source;
private final StoredProfile profile;
public BukkitFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
super(true);
this.username = username;
this.source = source;
this.profile = profile;
}
@Override
public String getUsername() {
return username;
}
@Override
public LoginSource getSource() {
return source;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public HandlerList getHandlers() {
return handlers;
}
public static HandlerList getHandlerList() {
return handlers;
}
}

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hooks;
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
@@ -42,11 +42,11 @@ public class AuthMeHook implements AuthPlugin<Player>, Listener {
@Override
public boolean forceLogin(Player player) {
//skips registration and login
if (AuthMeApi.getInstance().isAuthenticated(player)) {
return false;
}
//skips registration and login
AuthMeApi.getInstance().forceLogin(player);
return true;
}
@@ -58,7 +58,7 @@ public class AuthMeHook implements AuthPlugin<Player>, Listener {
@Override
public boolean forceRegister(Player player, String password) {
//this automatically registers the player too
//this automatically login the player too
AuthMeApi.getInstance().forceRegister(player, password);
return true;
}

View File

@@ -1,5 +1,6 @@
package com.github.games647.fastlogin.bukkit.hooks;
package com.github.games647.fastlogin.bukkit.hook;
import com.comphenix.protocol.reflect.FieldUtils;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
@@ -13,7 +14,6 @@ import java.util.Optional;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Future;
import org.apache.commons.lang.reflect.FieldUtils;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hooks;
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hooks;
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
@@ -11,7 +11,7 @@ import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction;
import org.bukkit.entity.Player;
/**
* GitHub: https://github.com/lenis0012/LoginSecurity-2
* GitHub: https://github.com/lenis0012/LoginSecurity-2
* <p>
* Project page:
* <p>
@@ -30,8 +30,8 @@ public class LoginSecurityHook implements AuthPlugin<Player> {
@Override
public boolean forceLogin(Player player) {
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
return session.isAuthorized() || session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
return session.isAuthorized()
|| session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
}
@Override

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hooks;
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bukkit.hooks;
package com.github.games647.fastlogin.bukkit.hook;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;

View File

@@ -2,11 +2,11 @@ package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
import com.github.games647.fastlogin.bukkit.task.ForceLoginTask;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.messages.LoginActionMessage;
import com.github.games647.fastlogin.core.messages.LoginActionMessage.Type;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
@@ -46,16 +46,7 @@ public class BungeeListener implements PluginMessageListener {
@Override
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
if (!channel.equals(plugin.getName())) {
return;
}
ByteArrayDataInput dataInput = ByteStreams.newDataInput(message);
String subChannel = dataInput.readUTF();
if (!"LoginAction".equals(subChannel)) {
plugin.getLog().info("Unknown sub channel {}", subChannel);
return;
}
LoginActionMessage loginMessage = new LoginActionMessage();
loginMessage.readFrom(dataInput);
@@ -70,6 +61,8 @@ public class BungeeListener implements PluginMessageListener {
//fail if target player is blacklisted because already authenticated or wrong bungeecord id
if (checkedPlayer.hasMetadata(plugin.getName())) {
plugin.getLog().warn("Received message {} from a blacklisted player {}", loginMessage, checkedPlayer);
} else {
//fail if BungeeCord support is disabled (id = null)
UUID sourceId = loginMessage.getProxyId();
if (proxyIds.contains(sourceId)) {
@@ -77,8 +70,6 @@ public class BungeeListener implements PluginMessageListener {
} else {
plugin.getLog().warn("Received proxy id: {} that doesn't exist in the proxy whitelist file", sourceId);
}
} else {
plugin.getLog().warn("Received message {} from a blacklisted player {}", loginMessage, checkedPlayer);
}
}
@@ -89,16 +80,12 @@ public class BungeeListener implements PluginMessageListener {
InetSocketAddress address = player.getAddress();
String id = '/' + address.getAddress().getHostAddress() + ':' + address.getPort();
if (type == Type.LOGIN) {
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.PREMIUM);
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
playerSession.setVerified(true);
plugin.getLoginSessions().put(id, playerSession);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin.getCore(), player), 20L);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin.getCore(), player), 10L);
} else if (type == Type.REGISTER) {
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.PREMIUM);
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, () -> {
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
try {
@@ -112,8 +99,9 @@ public class BungeeListener implements PluginMessageListener {
} catch (Exception ex) {
plugin.getLog().error("Failed to query isRegistered for player: {}", player, ex);
}
}, 20L);
}, 10L);
} else if (type == Type.CRACKED) {
//we don't start a force login task here so update it manually
plugin.getPremiumPlayers().put(player.getUniqueId(), PremiumStatus.CRACKED);
}
}

View File

@@ -1,13 +1,16 @@
package com.github.games647.fastlogin.bukkit.listener;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
import com.github.games647.fastlogin.bukkit.task.ForceLoginTask;
import org.bukkit.Bukkit;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerJoinEvent;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
import org.bukkit.event.player.PlayerQuitEvent;
/**
@@ -24,10 +27,18 @@ public class ConnectionListener implements Listener {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOWEST)
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) {
loginEvent.disallow(Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
}
}
@EventHandler(ignoreCancelled = true)
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
Player player = joinEvent.getPlayer();
removeBlacklistStatus(player);
if (!plugin.isBungeeEnabled()) {
//Wait before auth plugin and we received a message from BungeeCord initializes the player
Runnable forceLoginTask = new ForceLoginTask(plugin.getCore(), player);
@@ -38,9 +49,12 @@ public class ConnectionListener implements Listener {
@EventHandler
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
Player player = quitEvent.getPlayer();
player.removeMetadata(plugin.getName(), plugin);
removeBlacklistStatus(player);
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
plugin.getPremiumPlayers().remove(player.getUniqueId());
}
private void removeBlacklistStatus(Player player) {
player.removeMetadata(plugin.getName(), plugin);
}
}

View File

@@ -0,0 +1,109 @@
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;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.security.PublicKey;
import java.util.Random;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
public class NameCheckTask extends JoinManagement<Player, CommandSender, ProtocolLibLoginSource>
implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final PublicKey publicKey;
private final Random random;
private final Player player;
private final String username;
public NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random,
Player player, String username, PublicKey publicKey) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
this.packetEvent = packetEvent;
this.publicKey = publicKey;
this.random = random;
this.player = player;
this.username = username;
}
@Override
public void run() {
try {
super.onLogin(username, new ProtocolLibLoginSource(packetEvent, player, random, publicKey));
} finally {
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLibLoginSource source, StoredProfile profile) {
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
plugin.getServer().getPluginManager().callEvent(event);
return event;
}
//Minecraft server implementation
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
@Override
public void requestPremiumLogin(ProtocolLibLoginSource source, StoredProfile profile
, String username, boolean registered) {
try {
source.setOnlineMode();
} catch (Exception ex) {
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
return;
}
String ip = player.getAddress().getAddress().getHostAddress();
core.getPendingLogin().put(ip + username, new Object());
String serverId = source.getServerId();
byte[] verify = source.getVerifyToken();
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
plugin.getLoginSessions().put(player.getAddress().toString(), playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
}
@Override
public void requestConfirmationLogin(ProtocolLibLoginSource source, StoredProfile profile, String username) {
try {
source.setOnlineMode();
} catch (Exception ex) {
plugin.getLog().error("Cannot send encryption packet. Falling back to cracked login for: {}", profile, ex);
return;
}
String serverId = source.getServerId();
byte[] verify = source.getVerifyToken();
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, profile);
plugin.getLoginSessions().put(player.getAddress().toString(), playerSession);
//cancel only if the player has a paid account otherwise login as normal offline player
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
}
@Override
public void startCrackedSession(ProtocolLibLoginSource source, StoredProfile profile, String username) {
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
plugin.getLoginSessions().put(player.getAddress().toString(), loginSession);
}
}

View File

@@ -0,0 +1,89 @@
package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.comphenix.protocol.PacketType;
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.github.games647.fastlogin.bukkit.EncryptionUtil;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import java.security.KeyPair;
import java.security.SecureRandom;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Client.ENCRYPTION_BEGIN;
import static com.comphenix.protocol.PacketType.Login.Client.START;
public class ProtocolLibListener extends PacketAdapter {
private static final int WORKER_THREADS = 3;
private final FastLoginBukkit plugin;
//just create a new once on plugin enable. This used for verify token generation
private final SecureRandom random = new SecureRandom();
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
public ProtocolLibListener(FastLoginBukkit plugin) {
//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());
this.plugin = plugin;
}
public static void register(FastLoginBukkit plugin) {
//they will be created with a static builder, because otherwise it will throw a NoClassDefFoundError
ProtocolLibrary.getProtocolManager()
.getAsynchronousManager()
.registerAsyncHandler(new ProtocolLibListener(plugin))
.start(WORKER_THREADS);
}
@Override
public void onPacketReceiving(PacketEvent packetEvent) {
if (packetEvent.isCancelled()
|| plugin.getCore().getAuthPluginHook()== null
|| !plugin.isServerFullyStarted()) {
return;
}
Player sender = packetEvent.getPlayer();
PacketType packetType = packetEvent.getPacketType();
if (packetType == START) {
onLogin(packetEvent, sender);
} else {
onEncryptionBegin(packetEvent, sender);
}
}
private void onEncryptionBegin(PacketEvent packetEvent, Player sender) {
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret, keyPair);
plugin.getScheduler().runAsync(verifyTask);
}
private void onLogin(PacketEvent packetEvent, Player player) {
//this includes ip:port. Should be unique for an incoming login request with a timeout of 2 minutes
String sessionKey = player.getAddress().toString();
//remove old data every time on a new login in order to keep the session only for one person
plugin.getLoginSessions().remove(sessionKey);
//player.getName() won't work at this state
PacketContainer packet = packetEvent.getPacket();
String username = packet.getGameProfiles().read(0).getName();
plugin.getLog().trace("GameProfile {} with {} connecting", sessionKey, username);
packetEvent.getAsyncMarker().incrementProcessingDelay();
Runnable nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username, keyPair.getPublic());
plugin.getScheduler().runAsync(nameCheckTask);
}
}

View File

@@ -0,0 +1,100 @@
package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.comphenix.protocol.ProtocolLibrary;
import com.comphenix.protocol.ProtocolManager;
import com.comphenix.protocol.events.PacketContainer;
import com.comphenix.protocol.events.PacketEvent;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
import com.github.games647.fastlogin.core.shared.LoginSource;
import java.lang.reflect.InvocationTargetException;
import java.net.InetSocketAddress;
import java.security.PublicKey;
import java.util.Arrays;
import java.util.Random;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
import static com.comphenix.protocol.PacketType.Login.Server.ENCRYPTION_BEGIN;
public class ProtocolLibLoginSource implements LoginSource {
private final PacketEvent packetEvent;
private final Player player;
private final Random random;
private final PublicKey publicKey;
private final String serverId = "";
private byte[] verifyToken;
public ProtocolLibLoginSource(PacketEvent packetEvent, Player player, Random random, PublicKey publicKey) {
this.packetEvent = packetEvent;
this.player = player;
this.random = random;
this.publicKey = publicKey;
}
@Override
public void setOnlineMode() throws Exception {
verifyToken = EncryptionUtil.generateVerifyToken(random);
/*
* Packet Information: https://wiki.vg/Protocol#Encryption_Request
*
* ServerID="" (String) key=public server key verifyToken=random 4 byte array
*/
PacketContainer newPacket = new PacketContainer(ENCRYPTION_BEGIN);
newPacket.getStrings().write(0, serverId);
newPacket.getSpecificModifier(PublicKey.class).write(0, publicKey);
newPacket.getByteArrays().write(0, verifyToken);
//serverId is a empty string
ProtocolLibrary.getProtocolManager().sendServerPacket(player, newPacket);
}
@Override
public void kick(String message) throws InvocationTargetException {
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(message));
try {
//send kick packet at login state
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
protocolManager.sendServerPacket(player, kickPacket);
} finally {
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
}
}
@Override
public InetSocketAddress getAddress() {
return packetEvent.getPlayer().getAddress();
}
public String getServerId() {
return serverId;
}
public byte[] getVerifyToken() {
return verifyToken.clone();
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '{' +
"packetEvent=" + packetEvent +
", player=" + player +
", random=" + random +
", serverId='" + serverId + '\'' +
", verifyToken=" + Arrays.toString(verifyToken) +
'}';
}
}

View File

@@ -0,0 +1,70 @@
package com.github.games647.fastlogin.bukkit.listener.protocollib;
import com.comphenix.protocol.reflect.MethodUtils;
import com.comphenix.protocol.reflect.accessors.Accessors;
import com.comphenix.protocol.reflect.accessors.MethodAccessor;
import com.comphenix.protocol.utility.MinecraftReflection;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
import com.github.games647.craftapi.model.skin.Textures;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import java.lang.reflect.InvocationTargetException;
import org.bukkit.entity.Player;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.PlayerLoginEvent;
import org.bukkit.event.player.PlayerLoginEvent.Result;
public class SkinApplyListener implements Listener {
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAccessor(GAME_PROFILE, "getProperties");
private final FastLoginBukkit plugin;
public SkinApplyListener(FastLoginBukkit plugin) {
this.plugin = plugin;
}
@EventHandler(priority = EventPriority.LOW)
//run this on the loginEvent to let skins plugins see the skin like in normal Minecraft behaviour
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
if (loginEvent.getResult() != Result.ALLOWED) {
return;
}
Player player = loginEvent.getPlayer();
if (plugin.getConfig().getBoolean("forwardSkin")) {
//go through every session, because player.getAddress is null
//loginEvent.getAddress is just a InetAddress not InetSocketAddress, so not unique enough
for (BukkitLoginSession session : plugin.getLoginSessions().values()) {
if (session.getUsername().equals(player.getName())) {
session.getSkin().ifPresent(skin -> applySkin(player, skin.getValue(), skin.getSignature()));
break;
}
}
}
}
private void applySkin(Player player, String skinData, String signature) {
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
WrappedSignedProperty skin = WrappedSignedProperty.fromValues(Textures.KEY, skinData, signature);
try {
gameProfile.getProperties().put(Textures.KEY, skin);
} catch (ClassCastException castException) {
//Cauldron, MCPC, Thermos, ...
Object map = GET_PROPERTIES.invoke(gameProfile.getHandle());
try {
MethodUtils.invokeMethod(map, "put", new Object[]{Textures.KEY, skin.getHandle()});
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
plugin.getLog().error("Error setting premium skin of: {}", player, ex);
}
}
}
}

View File

@@ -0,0 +1,234 @@
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.server.TemporaryPlayerFactory;
import com.comphenix.protocol.reflect.FieldUtils;
import com.comphenix.protocol.reflect.FuzzyReflection;
import com.comphenix.protocol.wrappers.WrappedChatComponent;
import com.comphenix.protocol.wrappers.WrappedGameProfile;
import com.github.games647.craftapi.model.auth.Verification;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import java.io.IOException;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.security.GeneralSecurityException;
import java.security.KeyPair;
import java.security.PrivateKey;
import java.util.Arrays;
import java.util.Optional;
import java.util.UUID;
import javax.crypto.Cipher;
import javax.crypto.SecretKey;
import org.bukkit.entity.Player;
import static com.comphenix.protocol.PacketType.Login.Client.START;
import static com.comphenix.protocol.PacketType.Login.Server.DISCONNECT;
public class VerifyResponseTask implements Runnable {
private final FastLoginBukkit plugin;
private final PacketEvent packetEvent;
private final KeyPair serverKey;
private final Player player;
private final byte[] sharedSecret;
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player player,
byte[] sharedSecret, KeyPair keyPair) {
this.plugin = plugin;
this.packetEvent = packetEvent;
this.player = player;
this.sharedSecret = Arrays.copyOf(sharedSecret, sharedSecret.length);
this.serverKey = keyPair;
}
@Override
public void run() {
try {
BukkitLoginSession session = plugin.getLoginSessions().get(player.getAddress().toString());
if (session == null) {
disconnect("invalid-request", true
, "GameProfile {0} tried to send encryption response at invalid state", player.getAddress());
} else {
verifyResponse(session);
}
} finally {
//this is a fake packet; it shouldn't be send to the server
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
packetEvent.setCancelled(true);
}
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
}
}
private void verifyResponse(BukkitLoginSession session) {
PrivateKey privateKey = serverKey.getPrivate();
Cipher cipher;
SecretKey loginKey;
try {
cipher = Cipher.getInstance(privateKey.getAlgorithm());
cipher.init(Cipher.DECRYPT_MODE, privateKey);
loginKey = EncryptionUtil.decryptSharedKey(cipher, sharedSecret);
} catch (GeneralSecurityException securityEx) {
disconnect("error-kick", false, "Cannot decrypt received contents", securityEx);
return;
}
try {
if (!checkVerifyToken(session, cipher, privateKey) || !encryptConnection(loginKey)) {
return;
}
} catch (Exception ex) {
disconnect("error-kick", false, "Cannot decrypt received contents", ex);
return;
}
String serverId = EncryptionUtil.getServerIdHashString("", loginKey, serverKey.getPublic());
String username = session.getUsername();
InetSocketAddress socketAddress = player.getAddress();
try {
MojangResolver resolver = plugin.getCore().getResolver();
InetAddress address = socketAddress.getAddress();
Optional<Verification> response = resolver.hasJoined(username, serverId, address);
if (response.isPresent()) {
plugin.getLog().info("GameProfile {} has a verified premium account", username);
SkinProperty[] properties = response.get().getProperties();
if (properties.length > 0) {
session.setSkinProperty(properties[0]);
}
session.setUuid(response.get().getId());
session.setVerified(true);
setPremiumUUID(session.getUuid());
receiveFakeStartPacket(username);
} else {
//user tried to fake a authentication
disconnect("invalid-session", true
, "GameProfile {0} ({1}) tried to log in with an invalid session ServerId: {2}"
, session.getUsername(), socketAddress, serverId);
}
} catch (IOException ioEx) {
disconnect("error-kick", false, "Failed to connect to session server", ioEx);
}
}
private void setPremiumUUID(UUID premiumUUID) {
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
try {
Object networkManager = getNetworkManager();
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
FieldUtils.writeField(networkManager, "spoofedUUID", premiumUUID, true);
} catch (Exception exc) {
plugin.getLog().error("Error setting premium uuid of {}", player, exc);
}
}
}
private boolean checkVerifyToken(BukkitLoginSession session, Cipher cipher, PrivateKey privateKey)
throws GeneralSecurityException {
byte[] requestVerify = session.getVerifyToken();
//encrypted verify token
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L182
if (!Arrays.equals(requestVerify, EncryptionUtil.decrypt(cipher, responseVerify))) {
//check if the verify token are equal to the server sent one
disconnect("invalid-verify-token", true
, "GameProfile {0} ({1}) tried to login with an invalid verify token. Server: {2} Client: {3}"
, session.getUsername(), packetEvent.getPlayer().getAddress(), requestVerify, responseVerify);
return false;
}
return true;
}
//try to get the networkManager from ProtocolLib
private Object getNetworkManager() throws IllegalAccessException, ClassNotFoundException {
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(player);
//ChannelInjector
Class<?> injectorClass = Class.forName("com.comphenix.protocol.injector.netty.Injector");
Object rawInjector = FuzzyReflection.getFieldValue(injectorContainer, injectorClass, true);
return FieldUtils.readField(rawInjector, "networkManager", true);
}
private boolean encryptConnection(SecretKey loginKey) throws IllegalArgumentException {
try {
//get the NMS connection handle of this player
Object networkManager = getNetworkManager();
//try to detect the method by parameters
Method encryptMethod = FuzzyReflection
.fromObject(networkManager).getMethodByParameters("a", SecretKey.class);
//encrypt/decrypt following packets
//the client expects this behaviour
encryptMethod.invoke(networkManager, loginKey);
} catch (Exception ex) {
disconnect("error-kick", false, "Couldn't enable encryption", ex);
return false;
}
return true;
}
private void disconnect(String reasonKey, boolean debug, String logMessage, Object... arguments) {
if (debug) {
plugin.getLog().debug(logMessage, arguments);
} else {
plugin.getLog().error(logMessage, arguments);
}
kickPlayer(plugin.getCore().getMessage(reasonKey));
}
private void kickPlayer(String reason) {
PacketContainer kickPacket = new PacketContainer(DISCONNECT);
kickPacket.getChatComponents().write(0, WrappedChatComponent.fromText(reason));
try {
//send kick packet at login state
//the normal event.getPlayer.kickPlayer(String) method does only work at play state
ProtocolLibrary.getProtocolManager().sendServerPacket(player, kickPacket);
//tell the server that we want to close the connection
player.kickPlayer("Disconnect");
} catch (InvocationTargetException ex) {
plugin.getLog().error("Error sending kick packet for: {}", player, ex);
}
}
//fake a new login packet in order to let the server handle all the other stuff
private void receiveFakeStartPacket(String username) {
//see StartPacketListener for packet information
PacketContainer startPacket = new PacketContainer(START);
//uuid is ignored by the packet definition
WrappedGameProfile fakeProfile = new WrappedGameProfile(UUID.randomUUID(), username);
startPacket.getGameProfiles().write(0, fakeProfile);
try {
//we don't want to handle our own packets so ignore filters
ProtocolLibrary.getProtocolManager().recieveClientPacket(player, startPacket, false);
} catch (InvocationTargetException | IllegalAccessException ex) {
plugin.getLog().warn("Failed to fake a new start packet for: {}", username, ex);
//cancel the event in order to prevent the server receiving an invalid packet
kickPlayer(plugin.getCore().getMessage("error-kick"));
}
}
}

View File

@@ -29,10 +29,6 @@ public class ProtocolLoginSource implements LoginSource {
return loginStartEvent.getAddress();
}
public PlayerLoginStartEvent getLoginStartEvent() {
return loginStartEvent;
}
@Override
public String toString() {
return this.getClass().getSimpleName() + '{' +

View File

@@ -1,12 +1,15 @@
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
import com.github.games647.craftapi.model.skin.SkinProperty;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.net.InetSocketAddress;
import java.util.Optional;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -14,7 +17,7 @@ import org.bukkit.event.EventHandler;
import org.bukkit.event.Listener;
import protocolsupport.api.events.ConnectionCloseEvent;
import protocolsupport.api.events.PlayerLoginStartEvent;
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
import protocolsupport.api.events.PlayerProfileCompleteEvent;
public class ProtocolSupportListener extends JoinManagement<Player, CommandSender, ProtocolLoginSource>
implements Listener {
@@ -33,7 +36,7 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
return;
}
String username = loginStartEvent.getName();
String username = loginStartEvent.getConnection().getProfile().getName();
InetSocketAddress address = loginStartEvent.getAddress();
//remove old data every time on a new login in order to keep the session only for one person
@@ -49,16 +52,28 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
}
@EventHandler
public void onPropertiesResolve(PlayerPropertiesResolveEvent propertiesResolveEvent) {
InetSocketAddress address = propertiesResolveEvent.getAddress();
public void onPropertiesResolve(PlayerProfileCompleteEvent profileCompleteEvent) {
InetSocketAddress address = profileCompleteEvent.getAddress();
BukkitLoginSession session = plugin.getLoginSessions().get(address.toString());
//skin was resolved -> premium player
if (propertiesResolveEvent.hasProperty(SkinProperty.TEXTURE_KEY) && session != null) {
if (session != null && profileCompleteEvent.getConnection().getProfile().isOnlineMode()) {
session.setVerified(true);
if (!plugin.getConfig().getBoolean("premiumUuid")) {
String username = Optional.ofNullable(profileCompleteEvent.getForcedName())
.orElse(profileCompleteEvent.getConnection().getProfile().getName());
profileCompleteEvent.setForcedUUID(UUIDAdapter.generateOfflineId(username));
}
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, ProtocolLoginSource source, StoredProfile profile) {
BukkitFastLoginPreLoginEvent event = new BukkitFastLoginPreLoginEvent(username, source, profile);
plugin.getServer().getPluginManager().callEvent(event);
return event;
}
@Override
public void requestPremiumLogin(ProtocolLoginSource source, StoredProfile profile, String username
, boolean registered) {
@@ -69,9 +84,14 @@ public class ProtocolSupportListener extends JoinManagement<Player, CommandSende
BukkitLoginSession playerSession = new BukkitLoginSession(username, registered, profile);
plugin.getLoginSessions().put(source.getAddress().toString(), playerSession);
if (plugin.getConfig().getBoolean("premiumUuid")) {
source.getLoginStartEvent().setUseOnlineModeUUID(true);
}
}
@Override
public void requestConfirmationLogin(ProtocolLoginSource source, StoredProfile profile, String username) {
source.setOnlineMode();
BukkitLoginSession playerSession = new BukkitLoginSession(username, profile);
plugin.getLoginSessions().put(source.getAddress().toString(), playerSession);
}
@Override

View File

@@ -1,12 +1,12 @@
package com.github.games647.fastlogin.bukkit.tasks;
package com.github.games647.fastlogin.bukkit.task;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.bukkit.hooks.AuthMeHook;
import com.github.games647.fastlogin.bukkit.hooks.CrazyLoginHook;
import com.github.games647.fastlogin.bukkit.hooks.LogItHook;
import com.github.games647.fastlogin.bukkit.hooks.LoginSecurityHook;
import com.github.games647.fastlogin.bukkit.hooks.UltraAuthHook;
import com.github.games647.fastlogin.bukkit.hooks.xAuthHook;
import com.github.games647.fastlogin.bukkit.hook.AuthMeHook;
import com.github.games647.fastlogin.bukkit.hook.CrazyLoginHook;
import com.github.games647.fastlogin.bukkit.hook.LogItHook;
import com.github.games647.fastlogin.bukkit.hook.LoginSecurityHook;
import com.github.games647.fastlogin.bukkit.hook.UltraAuthHook;
import com.github.games647.fastlogin.bukkit.hook.xAuthHook;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import java.lang.reflect.Constructor;
@@ -36,6 +36,10 @@ public class DelayedAuthHook implements Runnable {
+ "and BungeeCord is deactivated. "
+ "Either one or both of the checks have to pass in order to use this plugin");
}
if (hookFound) {
plugin.setServerStarted();
}
}
private boolean isHookFound() {

View File

@@ -1,14 +1,18 @@
package com.github.games647.fastlogin.bukkit.tasks;
package com.github.games647.fastlogin.bukkit.task;
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
import com.github.games647.fastlogin.core.messages.SuccessMessage;
import com.github.games647.fastlogin.bukkit.event.BukkitFastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.PremiumStatus;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
import com.github.games647.fastlogin.core.shared.LoginSession;
import java.util.concurrent.ExecutionException;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import org.bukkit.Bukkit;
import org.bukkit.command.CommandSender;
import org.bukkit.entity.Player;
@@ -28,11 +32,25 @@ public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender,
@Override
public void run() {
//blacklist this target player for BungeeCord Id brute force attacks
//blacklist this target player for BungeeCord ID brute force attacks
FastLoginBukkit plugin = core.getPlugin();
player.setMetadata(core.getPlugin().getName(), new FixedMetadataValue(plugin, true));
super.run();
PremiumStatus status = PremiumStatus.CRACKED;
if (isOnlineMode()) {
status = PremiumStatus.PREMIUM;
}
plugin.getPremiumPlayers().put(player.getUniqueId(), status);
}
@Override
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
BukkitFastLoginAutoLoginEvent event = new BukkitFastLoginAutoLoginEvent(session, profile);
core.getPlugin().getServer().getPluginManager().callEvent(event);
return event;
}
@Override
@@ -60,6 +78,10 @@ public class ForceLoginTask extends ForceLoginManagement<Player, CommandSender,
@Override
public boolean isOnlineMode() {
if (session == null) {
return false;
}
return session.isVerified() && player.getName().equals(session.getUsername());
}
}

View File

@@ -11,14 +11,21 @@ description: |
website: ${project.url}
dev-url: ${project.url}
# Load the plugin as early as possible to inject it for all players
load: STARTUP
# This plugin don't have to be transformed for compatibility with Minecraft >= 1.13
api-version: '1.13'
softdepend:
# We depend either ProtocolLib or ProtocolSupport
- ProtocolSupport
- ProtocolLib
- PlaceholderAPI
# Auth plugins
- AuthMe
- LoginSecurity
- xAuth
- LogIt
- UltraAuth
- CrazyLogin
commands:
${project.parent.name}:

View File

@@ -0,0 +1,42 @@
package com.github.games647.fastlogin.bukkit;
import java.security.SecureRandom;
import org.junit.Test;
import static org.hamcrest.CoreMatchers.is;
import static org.hamcrest.CoreMatchers.notNullValue;
import static org.junit.Assert.assertThat;
public class EncryptionUtilTest {
@Test
public void testVerifyToken() throws Exception {
SecureRandom random = new SecureRandom();
byte[] token = EncryptionUtil.generateVerifyToken(random);
assertThat(token, notNullValue());
assertThat(token.length, is(4));
}
// @Test
// public void testDecryptSharedSecret() throws Exception {
//
// }
//
// @Test
// public void testDecryptData() throws Exception {
//
// }
// private static SecretKey createNewSharedKey() {
// try {
// KeyGenerator keygenerator = KeyGenerator.getInstance("AES");
// keygenerator.init(128);
// return keygenerator.generateKey();
// } catch (NoSuchAlgorithmException nosuchalgorithmexception) {
// throw new Error(nosuchalgorithmexception);
// }
// }
}

Binary file not shown.

View File

@@ -1,11 +1,11 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
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>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.11</version>
<version>1.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -21,7 +21,7 @@
<plugin>
<groupId>org.apache.maven.plugins</groupId>
<artifactId>maven-shade-plugin</artifactId>
<version>3.1.0</version>
<version>3.2.2</version>
<configuration>
<createDependencyReducedPom>false</createDependencyReducedPom>
<shadedArtifactAttached>false</shadedArtifactAttached>
@@ -56,18 +56,14 @@
</build>
<repositories>
<repository>
<id>vik1395-repo</id>
<url>https://vik1395.github.io/repo.vik1395.me/repositories</url>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
</repositories>
<dependencies>
<!--Common plugin component-->
<dependency>
<groupId>${project.groupId}</groupId>
<artifactId>fastlogin.core</artifactId>
@@ -78,21 +74,17 @@
<dependency>
<groupId>net.md-5</groupId>
<artifactId>bungeecord-proxy</artifactId>
<version>1.12-SNAPSHOT</version>
<version>1.14-SNAPSHOT</version>
<scope>provided</scope>
</dependency>
<!--Login plugin-->
<dependency>
<groupId>me.vik1395</groupId>
<artifactId>BungeeAuth</artifactId>
<version>1.4</version>
<scope>provided</scope>
<exclusions>
<exclusion>
<groupId>*</groupId>
<artifactId>*</artifactId>
</exclusion>
</exclusions>
<scope>system</scope>
<systemPath>${project.basedir}/lib/BungeeAuth-1.4.jar</systemPath>
</dependency>
</dependencies>
</project>

View File

@@ -12,28 +12,33 @@ public class BungeeLoginSession extends LoginSession {
super(username, registered, profile);
}
public void setRegistered(boolean registered) {
public BungeeLoginSession(String username, StoredProfile profile) {
super(username, profile);
}
public synchronized void setRegistered(boolean registered) {
this.registered = registered;
}
public boolean isAlreadySaved() {
public synchronized boolean isAlreadySaved() {
return alreadySaved;
}
public void setAlreadySaved(boolean alreadySaved) {
public synchronized void setAlreadySaved(boolean alreadySaved) {
this.alreadySaved = alreadySaved;
this.confirmationLogin = false;
}
public boolean isAlreadyLogged() {
public synchronized boolean isAlreadyLogged() {
return alreadyLogged;
}
public void setAlreadyLogged(boolean alreadyLogged) {
public synchronized void setAlreadyLogged(boolean alreadyLogged) {
this.alreadyLogged = alreadyLogged;
}
@Override
public String toString() {
public synchronized String toString() {
return this.getClass().getSimpleName() + '{' +
"alreadySaved=" + alreadySaved +
", alreadyLogged=" + alreadyLogged +

View File

@@ -4,15 +4,20 @@ import com.github.games647.fastlogin.core.shared.LoginSource;
import java.net.InetSocketAddress;
import net.md_5.bungee.api.ChatColor;
import net.md_5.bungee.api.chat.ComponentBuilder;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.event.PreLoginEvent;
public class BungeeLoginSource implements LoginSource {
private final PendingConnection connection;
private final PreLoginEvent preLoginEvent;
public BungeeLoginSource(PendingConnection connection) {
public BungeeLoginSource(PendingConnection connection, PreLoginEvent preLoginEvent) {
this.connection = connection;
this.preLoginEvent = preLoginEvent;
}
@Override
@@ -22,7 +27,12 @@ public class BungeeLoginSource implements LoginSource {
@Override
public void kick(String message) {
connection.disconnect(TextComponent.fromLegacyText(message));
preLoginEvent.setCancelled(true);
if (message != null)
preLoginEvent.setCancelReason(TextComponent.fromLegacyText(message));
else
preLoginEvent.setCancelReason(new ComponentBuilder("Kicked").color(ChatColor.WHITE).create());
}
@Override

View File

@@ -1,10 +1,14 @@
package com.github.games647.fastlogin.bungee;
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthHook;
import com.github.games647.fastlogin.bungee.hook.BungeeAuthHook;
import com.github.games647.fastlogin.bungee.listener.ConnectListener;
import com.github.games647.fastlogin.bungee.listener.MessageListener;
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
import com.github.games647.fastlogin.core.AsyncScheduler;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.PlatformPlugin;
import com.google.common.collect.MapMaker;
@@ -34,11 +38,13 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
private final ConcurrentMap<PendingConnection, BungeeLoginSession> session = new MapMaker().weakKeys().makeMap();
private FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core;
private AsyncScheduler scheduler;
private Logger logger;
@Override
public void onEnable() {
logger = CommonUtil.createLoggerFromJDK(getLogger());
scheduler = new AsyncScheduler(logger, getThreadFactory());
core = new FastLoginCore<>(this);
core.load();
@@ -48,10 +54,11 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
//events
getProxy().getPluginManager().registerListener(this, new ConnectListener(this));
getProxy().getPluginManager().registerListener(this, new MessageListener(this));
getProxy().getPluginManager().registerListener(this, new PluginMessageListener(this));
//this is required to listen to messages from the server
getProxy().registerChannel(getName());
//this is required to listen to incoming messages from the server
getProxy().registerChannel(NamespaceKey.getCombined(getName(), ChangePremiumMessage.CHANGE_CHANNEL));
getProxy().registerChannel(NamespaceKey.getCombined(getName(), SuccessMessage.SUCCESS_CHANNEL));
registerHook();
}
@@ -82,10 +89,10 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
public void sendPluginMessage(Server server, ChannelMessage message) {
if (server != null) {
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
dataOutput.writeUTF(message.getChannelName());
message.writeTo(dataOutput);
server.sendData(core.getPlugin().getName(), dataOutput.toByteArray());
NamespaceKey channel = new NamespaceKey(getName(), message.getChannelName());
server.sendData(channel.getCombinedName(), dataOutput.toByteArray());
}
}
@@ -113,10 +120,15 @@ public class FastLoginBungee extends Plugin implements PlatformPlugin<CommandSen
@SuppressWarnings("deprecation")
public ThreadFactory getThreadFactory() {
return new ThreadFactoryBuilder()
.setNameFormat(core.getPlugin().getName() + " Database Pool Thread #%1$d")
.setNameFormat(getName() + " Pool Thread #%1$d")
//Hikari create daemons by default
.setDaemon(true)
.setThreadFactory(new GroupedThreadFactory(this, getName()))
.build();
}
@Override
public AsyncScheduler getScheduler() {
return scheduler;
}
}

View File

@@ -0,0 +1,39 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSession;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import net.md_5.bungee.api.plugin.Cancellable;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginAutoLoginEvent extends Event implements FastLoginAutoLoginEvent, Cancellable {
private final LoginSession session;
private final StoredProfile profile;
private boolean cancelled;
public BungeeFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
this.session = session;
this.profile = profile;
}
@Override
public LoginSession getSession() {
return session;
}
@Override
public StoredProfile getProfile() {
return profile;
}
@Override
public boolean isCancelled() {
return cancelled;
}
@Override
public void setCancelled(boolean cancelled) {
this.cancelled = cancelled;
}
}

View File

@@ -0,0 +1,34 @@
package com.github.games647.fastlogin.bungee.event;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSource;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import net.md_5.bungee.api.plugin.Event;
public class BungeeFastLoginPreLoginEvent extends Event implements FastLoginPreLoginEvent {
private final String username;
private final LoginSource source;
private final StoredProfile profile;
public BungeeFastLoginPreLoginEvent(String username, LoginSource source, StoredProfile profile) {
this.username = username;
this.source = source;
this.profile = profile;
}
@Override
public String getUsername() {
return username;
}
@Override
public LoginSource getSource() {
return source;
}
@Override
public StoredProfile getProfile() {
return profile;
}
}

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bungee.hooks;
package com.github.games647.fastlogin.bungee.hook;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;

View File

@@ -2,15 +2,15 @@ package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.craftapi.UUIDAdapter;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.tasks.AsyncPremiumCheck;
import com.github.games647.fastlogin.bungee.tasks.ForceLoginTask;
import com.github.games647.fastlogin.bungee.task.AsyncPremiumCheck;
import com.github.games647.fastlogin.bungee.task.ForceLoginTask;
import com.github.games647.fastlogin.core.ConfirmationState;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSession;
import java.lang.reflect.Field;
import java.util.UUID;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
@@ -48,7 +48,7 @@ public class ConnectListener implements Listener {
PendingConnection connection = preLoginEvent.getConnection();
Runnable asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection);
ProxyServer.getInstance().getScheduler().runAsync(plugin, asyncPremiumCheck);
plugin.getScheduler().runAsync(asyncPremiumCheck);
}
@EventHandler(priority = EventPriority.LOWEST)
@@ -101,13 +101,13 @@ public class ConnectListener implements Listener {
Server server = serverConnectedEvent.getServer();
Runnable loginTask = new ForceLoginTask(plugin.getCore(), player, server);
ProxyServer.getInstance().getScheduler().runAsync(plugin, loginTask);
plugin.getScheduler().runAsync(loginTask);
}
@EventHandler
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
ProxiedPlayer player = disconnectEvent.getPlayer();
plugin.getSession().remove(player.getPendingConnection());
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
plugin.getCore().getPendingConfirms().remove(player.getName(), ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN);
}
}

View File

@@ -1,101 +0,0 @@
package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.tasks.AsyncToggleMessage;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.messages.ChangePremiumMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.chat.TextComponent;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class MessageListener implements Listener {
private final FastLoginBungee plugin;
public MessageListener(FastLoginBungee plugin) {
this.plugin = plugin;
}
@EventHandler
public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
String channel = pluginMessageEvent.getTag();
if (pluginMessageEvent.isCancelled() || !plugin.getName().equals(channel)) {
return;
}
//the client shouldn't be able to read the messages in order to know something about server internal states
//moreover the client shouldn't be able fake a running premium check by sending the result message
pluginMessageEvent.setCancelled(true);
if (!(pluginMessageEvent.getSender() instanceof Server)) {
//check if the message is sent from the server
return;
}
//so that we can safely process this in the background
byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
ProxyServer.getInstance().getScheduler().runAsync(plugin, () -> readMessage(forPlayer, data));
}
private void readMessage(ProxiedPlayer forPlayer, byte[] data) {
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
String subChannel = dataInput.readUTF();
if ("Success".equals(subChannel)) {
onSuccessMessage(forPlayer);
} else if ("ChangeStatus".equals(subChannel)) {
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
changeMessage.readFrom(dataInput);
String playerName = changeMessage.getPlayerName();
boolean isSourceInvoker = changeMessage.isSourceInvoker();
if (changeMessage.shouldEnable()) {
if (playerName.equals(forPlayer.getName()) && plugin.getCore().getConfig().get("premium-warning", true)
&& !core.getPendingConfirms().contains(forPlayer.getUniqueId())) {
String message = core.getMessage("premium-warning");
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
core.getPendingConfirms().add(forPlayer.getUniqueId());
return;
}
core.getPendingConfirms().remove(forPlayer.getUniqueId());
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, true, isSourceInvoker);
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
} else {
Runnable task = new AsyncToggleMessage(core, forPlayer, playerName, false, isSourceInvoker);
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
}
}
}
private void onSuccessMessage(ProxiedPlayer forPlayer) {
if (forPlayer.getPendingConnection().isOnlineMode()) {
//bukkit module successfully received and force logged in the user
//update only on success to prevent corrupt data
BungeeLoginSession loginSession = plugin.getSession().get(forPlayer.getPendingConnection());
StoredProfile playerProfile = loginSession.getProfile();
loginSession.setRegistered(true);
if (!loginSession.isAlreadySaved()) {
playerProfile.setPremium(true);
plugin.getCore().getStorage().save(playerProfile);
loginSession.setAlreadySaved(true);
}
}
}
}

View File

@@ -0,0 +1,127 @@
package com.github.games647.fastlogin.bungee.listener;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.task.AsyncToggleMessage;
import com.github.games647.fastlogin.core.ConfirmationState;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.message.ChangePremiumMessage;
import com.github.games647.fastlogin.core.message.NamespaceKey;
import com.github.games647.fastlogin.core.message.SuccessMessage;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteStreams;
import java.util.Arrays;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.connection.Server;
import net.md_5.bungee.api.event.PluginMessageEvent;
import net.md_5.bungee.api.plugin.Listener;
import net.md_5.bungee.event.EventHandler;
public class PluginMessageListener implements Listener {
private final FastLoginBungee plugin;
private final String successChannel;
private final String changeChannel;
public PluginMessageListener(FastLoginBungee plugin) {
this.plugin = plugin;
this.successChannel = new NamespaceKey(plugin.getName(), SuccessMessage.SUCCESS_CHANNEL).getCombinedName();
this.changeChannel = new NamespaceKey(plugin.getName(), ChangePremiumMessage.CHANGE_CHANNEL).getCombinedName();
}
@EventHandler
public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
String channel = pluginMessageEvent.getTag();
if (pluginMessageEvent.isCancelled() || !channel.startsWith(plugin.getName().toLowerCase())) {
return;
}
//the client shouldn't be able to read the messages in order to know something about server internal states
//moreover the client shouldn't be able fake a running premium check by sending the result message
pluginMessageEvent.setCancelled(true);
if (!(pluginMessageEvent.getSender() instanceof Server)) {
//check if the message is sent from the server
return;
}
//so that we can safely process this in the background
byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
plugin.getScheduler().runAsync(() -> readMessage(forPlayer, channel, data));
}
private void readMessage(ProxiedPlayer fromPlayer, String channel, byte[] data) {
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
if (successChannel.equals(channel)) {
onSuccessMessage(fromPlayer);
} else if (changeChannel.equals(channel)) {
ChangePremiumMessage changeMessage = new ChangePremiumMessage();
changeMessage.readFrom(dataInput);
String playerName = changeMessage.getPlayerName();
boolean isSourceInvoker = changeMessage.isSourceInvoker();
onChangeMessage(fromPlayer, changeMessage.shouldEnable(), playerName, isSourceInvoker);
}
}
private void onChangeMessage(ProxiedPlayer fromPlayer, boolean shouldEnable, String playerName, boolean isSourceInvoker) {
FastLoginCore<ProxiedPlayer, CommandSender, FastLoginBungee> core = plugin.getCore();
if (shouldEnable) {
if (!isSourceInvoker) {
// fromPlayer is not the target player
activePremiumLogin(fromPlayer, playerName, false);
return;
}
if (plugin.getCore().getConfig().getBoolean("premium-confirm", true)) {
ConfirmationState state = plugin.getCore().getPendingConfirms().get(playerName);
if (state == null) {
// no pending confirmation
core.sendLocaleMessage("premium-confirm", fromPlayer);
core.getPendingConfirms().put(playerName, ConfirmationState.REQUIRE_RELOGIN);
} else if (state == ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN) {
// player logged in successful using premium authentication
activePremiumLogin(fromPlayer, playerName, true);
}
} else {
activePremiumLogin(fromPlayer, playerName, true);
}
} else {
Runnable task = new AsyncToggleMessage(core, fromPlayer, playerName, false, isSourceInvoker);
plugin.getScheduler().runAsync(task);
}
}
private void activePremiumLogin(ProxiedPlayer fromPlayer, String playerName, boolean isSourceInvoker) {
plugin.getCore().getPendingConfirms().remove(playerName);
Runnable task = new AsyncToggleMessage(plugin.getCore(), fromPlayer, playerName, true, isSourceInvoker);
plugin.getScheduler().runAsync(task);
}
private void onSuccessMessage(ProxiedPlayer forPlayer) {
if (forPlayer.getPendingConnection().isOnlineMode()) {
//bukkit module successfully received and force logged in the user
//update only on success to prevent corrupt data
BungeeLoginSession loginSession = plugin.getSession().get(forPlayer.getPendingConnection());
StoredProfile playerProfile = loginSession.getProfile();
loginSession.setRegistered(true);
if (!loginSession.isAlreadySaved()) {
loginSession.setAlreadySaved(true);
playerProfile.setId(loginSession.getUuid());
playerProfile.setPremium(true);
plugin.getCore().getStorage().save(playerProfile);
}
}
}
}

View File

@@ -1,26 +1,28 @@
package com.github.games647.fastlogin.bungee.tasks;
package com.github.games647.fastlogin.bungee.task;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.BungeeLoginSource;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginPreLoginEvent;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.JoinManagement;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.connection.PendingConnection;
import net.md_5.bungee.api.connection.ProxiedPlayer;
import net.md_5.bungee.api.event.AsyncEvent;
import net.md_5.bungee.api.event.PreLoginEvent;
import net.md_5.bungee.connection.InitialHandler;
public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSender, BungeeLoginSource>
implements Runnable {
private final FastLoginBungee plugin;
private final AsyncEvent<?> preLoginEvent;
private final PreLoginEvent preLoginEvent;
private final PendingConnection connection;
public AsyncPremiumCheck(FastLoginBungee plugin, AsyncEvent<?> preLoginEvent, PendingConnection connection) {
public AsyncPremiumCheck(FastLoginBungee plugin, PreLoginEvent preLoginEvent, PendingConnection connection) {
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
this.plugin = plugin;
@@ -35,12 +37,19 @@ public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSend
InitialHandler initialHandler = (InitialHandler) connection;
String username = initialHandler.getLoginRequest().getData();
try {
super.onLogin(username, new BungeeLoginSource(connection));
super.onLogin(username, new BungeeLoginSource(connection, preLoginEvent));
} finally {
preLoginEvent.completeIntent(plugin);
}
}
@Override
public FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, BungeeLoginSource source,
StoredProfile profile) {
return plugin.getProxy().getPluginManager()
.callEvent(new BungeeFastLoginPreLoginEvent(username, source, profile));
}
@Override
public void requestPremiumLogin(BungeeLoginSource source, StoredProfile profile,
String username, boolean registered) {
@@ -51,6 +60,12 @@ public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, CommandSend
plugin.getCore().getPendingLogin().put(ip + username, new Object());
}
@Override
public void requestConfirmationLogin(BungeeLoginSource source, StoredProfile profile, String username) {
source.setOnlineMode();
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, profile));
}
@Override
public void startCrackedSession(BungeeLoginSource source, StoredProfile profile, String username) {
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, false, profile));

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.bungee.tasks;
package com.github.games647.fastlogin.bungee.task;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.core.StoredProfile;

View File

@@ -1,16 +1,19 @@
package com.github.games647.fastlogin.bungee.tasks;
package com.github.games647.fastlogin.bungee.task;
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
import com.github.games647.fastlogin.bungee.FastLoginBungee;
import com.github.games647.fastlogin.core.messages.ChannelMessage;
import com.github.games647.fastlogin.core.messages.LoginActionMessage;
import com.github.games647.fastlogin.core.messages.LoginActionMessage.Type;
import com.github.games647.fastlogin.bungee.event.BungeeFastLoginAutoLoginEvent;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.message.ChannelMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage;
import com.github.games647.fastlogin.core.message.LoginActionMessage.Type;
import com.github.games647.fastlogin.core.shared.FastLoginCore;
import com.github.games647.fastlogin.core.shared.ForceLoginManagement;
import com.github.games647.fastlogin.core.shared.LoginSession;
import java.util.UUID;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
import net.md_5.bungee.api.CommandSender;
import net.md_5.bungee.api.ProxyServer;
import net.md_5.bungee.api.connection.ProxiedPlayer;
@@ -51,10 +54,15 @@ public class ForceLoginTask
return super.forceLogin(player);
}
@Override
public FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile) {
return core.getPlugin().getProxy().getPluginManager()
.callEvent(new BungeeFastLoginAutoLoginEvent(session, profile));
}
@Override
public boolean forceRegister(ProxiedPlayer player) {
return session.isAlreadyLogged() || super.forceRegister(player);
}
@Override
@@ -62,7 +70,7 @@ public class ForceLoginTask
//sub channel name
Type type = Type.LOGIN;
if (session.needsRegistration()) {
type = Type.LOGIN;
type = Type.REGISTER;
}
UUID proxyId = UUID.fromString(ProxyServer.getInstance().getConfig().getUuid());

View File

@@ -1,11 +1,11 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
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>
<parent>
<groupId>com.github.games647</groupId>
<artifactId>fastlogin</artifactId>
<version>1.11</version>
<version>1.11-SNAPSHOT</version>
<relativePath>../pom.xml</relativePath>
</parent>
@@ -18,11 +18,13 @@
<repository>
<id>luck-repo</id>
<url>https://ci.lucko.me/plugin/repository/everything</url>
<snapshots>
<enabled>false</enabled>
</snapshots>
</repository>
<repository>
<id>codemc-repo</id>
<url>https://repo.codemc.org/repository/maven-public/</url>
<url>https://repo.codemc.io/repository/maven-public/</url>
</repository>
</repositories>
@@ -33,21 +35,14 @@
<dependency>
<groupId>com.zaxxer</groupId>
<artifactId>HikariCP</artifactId>
<version>2.7.8</version>
<version>3.4.2</version>
</dependency>
<!--Logging framework implements slf4j which is required by hikari-->
<dependency>
<groupId>org.slf4j</groupId>
<artifactId>slf4j-jdk14</artifactId>
<version>1.7.25</version>
</dependency>
<!--GSON is not at the right position for Minecraft 1.7-->
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
<version>1.7.30</version>
</dependency>
<!-- snakeyaml is present in Bungee, Spigot, Cauldron and so we could use this independent implementation -->
@@ -63,20 +58,25 @@
</exclusions>
</dependency>
<!--Common component for contacting the Mojang API-->
<dependency>
<groupId>com.github.games647</groupId>
<artifactId>craftapi</artifactId>
<version>0.1.3-SNAPSHOT</version>
<version>0.4</version>
</dependency>
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee, Cauldron) -->
<!-- APIs we can use because they are available in all platforms (Spigot, Bungee) -->
<dependency>
<groupId>com.google.guava</groupId>
<artifactId>guava</artifactId>
<!-- The Uranium project (fork of Cauldron) uses 17.0 like Spigot 1.8 as experimental feature -->
<!-- Project url: https://github.com/UraniumMC/Uranium -->
<version>10.0.1</version>
<version>17.0</version>
<scope>provided</scope>
</dependency>
<dependency>
<groupId>com.google.code.gson</groupId>
<artifactId>gson</artifactId>
<version>2.2.4</version>
</dependency>
</dependencies>
</project>

View File

@@ -0,0 +1,69 @@
package com.github.games647.fastlogin.core;
import com.google.common.util.concurrent.MoreExecutors;
import java.util.concurrent.CompletableFuture;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.LinkedBlockingQueue;
import java.util.concurrent.ThreadFactory;
import java.util.concurrent.ThreadPoolExecutor;
import java.util.concurrent.TimeUnit;
import org.slf4j.Logger;
/**
* This limits the number of threads that are used at maximum. Thread creation can be very heavy for the CPU and
* context switching between threads too. However we need many threads for blocking HTTP and database calls.
* Nevertheless this number can be further limited, because the number of actually working database threads
* is limited by the size of our database pool. The goal is to separate concerns into processing and blocking only
* threads.
*/
public class AsyncScheduler {
private static final int MAX_CAPACITY = 1024;
//todo: single thread for delaying and scheduling tasks
private final Logger logger;
// 30 threads are still too many - the optimal solution is to separate into processing and blocking threads
// where processing threads could only be max number of cores while blocking threads could be minimized using
// non-blocking I/O and a single event executor
private final ExecutorService processingPool;
/*
private final ExecutorService databaseExecutor = new ThreadPoolExecutor(1, 10,
0L, TimeUnit.MILLISECONDS,
new LinkedBlockingQueue<>(MAX_CAPACITY));
*/
public AsyncScheduler(Logger logger, ThreadFactory threadFactory) {
this.logger = logger;
processingPool = new ThreadPoolExecutor(6, 32,
60L, TimeUnit.SECONDS,
new LinkedBlockingQueue<>(MAX_CAPACITY), threadFactory);
}
/*
public <R> CompletableFuture<R> runDatabaseTask(Supplier<R> databaseTask) {
return CompletableFuture.supplyAsync(databaseTask, databaseExecutor)
.exceptionally(error -> {
logger.warn("Error occurred on thread pool", error);
return null;
})
// change context to the processing pool
.thenApplyAsync(r -> r, processingPool);
}
*/
public CompletableFuture<Void> runAsync(Runnable task) {
return CompletableFuture.runAsync(task, processingPool).exceptionally(error -> {
logger.warn("Error occurred on thread pool", error);
return null;
});
}
public void shutdown() {
MoreExecutors.shutdownAndAwaitTermination(processingPool, 1, TimeUnit.MINUTES);
//MoreExecutors.shutdownAndAwaitTermination(databaseExecutor, 1, TimeUnit.MINUTES);
}
}

View File

@@ -22,40 +22,34 @@ public class AuthStorage {
private static final String PREMIUM_TABLE = "premium";
private static final String LOAD_BY_NAME = "SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=? LIMIT 1";
private static final String LOAD_BY_UUID = "SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=? LIMIT 1";
private static final String INSERT_PROFILE = "INSERT INTO " + PREMIUM_TABLE + " (UUID, Name, Premium, LastIp) "
+ "VALUES (?, ?, ?, ?) ";
private static final String UPDATE_PROFILE = "UPDATE " + PREMIUM_TABLE
+ " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?";
private static final String LOAD_BY_NAME = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `Name`=? LIMIT 1";
private static final String LOAD_BY_UUID = "SELECT * FROM `" + PREMIUM_TABLE + "` WHERE `UUID`=? LIMIT 1";
private static final String INSERT_PROFILE = "INSERT INTO `" + PREMIUM_TABLE
+ "` (`UUID`, `Name`, `Premium`, `LastIp`) " + "VALUES (?, ?, ?, ?) ";
// limit not necessary here, because it's unique
private static final String UPDATE_PROFILE = "UPDATE `" + PREMIUM_TABLE
+ "` SET `UUID`=?, `Name`=?, `Premium`=?, `LastIp`=?, `LastLogin`=CURRENT_TIMESTAMP WHERE `UserID`=?";
private final FastLoginCore<?, ?, ?> core;
private final HikariDataSource dataSource;
public AuthStorage(FastLoginCore<?, ?, ?> core, String driver, String host, int port, String databasePath
, String user, String pass, boolean useSSL) {
public AuthStorage(FastLoginCore<?, ?, ?> core, String host, int port, String databasePath,
HikariConfig config, boolean useSSL) {
this.core = core;
HikariConfig config = new HikariConfig();
config.setPoolName(core.getPlugin().getName());
config.setUsername(user);
config.setPassword(pass);
config.setDriverClassName(driver);
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
Properties properties = new Properties();
properties.setProperty("date_string_format", "yyyy-MM-dd HH:mm:ss");
properties.setProperty("useSSL", String.valueOf(useSSL));
config.setDataSourceProperties(properties);
ThreadFactory platformThreadFactory = core.getPlugin().getThreadFactory();
if (platformThreadFactory != null) {
config.setThreadFactory(platformThreadFactory);
}
String jdbcUrl = "jdbc:";
if (driver.contains("sqlite")) {
if (config.getDriverClassName().contains("sqlite")) {
String pluginFolder = core.getPlugin().getPluginFolder().toAbsolutePath().toString();
databasePath = databasePath.replace("{pluginDir}", pluginFolder);
@@ -64,6 +58,13 @@ public class AuthStorage {
config.setMaximumPoolSize(1);
} else {
jdbcUrl += "mysql://" + host + ':' + port + '/' + databasePath;
// enable MySQL specific optimizations
// default prepStmtCacheSize 25 - amount of cached statements - enough for us
// default prepStmtCacheSqlLimit 256 - length of SQL - our queries are not longer
// disabled by default - will return the same prepared statement instance
config.addDataSourceProperty("cachePrepStmts", true);
// default false - available in newer versions caches the statements server-side
config.addDataSourceProperty("useServerPrepStmts", true);
}
config.setJdbcUrl(jdbcUrl);
@@ -71,23 +72,27 @@ public class AuthStorage {
}
public void createTables() throws SQLException {
// choose surrogate PK(ID), because UUID can be null for offline players
// if UUID is always Premium UUID we would have to update offline player entries on insert
// name cannot be PK, because it can be changed for premium players
String createDataStmt = "CREATE TABLE IF NOT EXISTS `" + PREMIUM_TABLE + "` ("
+ "`UserID` INTEGER PRIMARY KEY AUTO_INCREMENT, "
+ "`UUID` CHAR(36), "
+ "`Name` VARCHAR(16) NOT NULL, "
+ "`Premium` BOOLEAN NOT NULL, "
+ "`LastIp` VARCHAR(255) NOT NULL, "
+ "`LastLogin` TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
//the premium shouldn't steal the cracked account by changing the name
+ "UNIQUE (`Name`) "
+ ')';
if (dataSource.getJdbcUrl().contains("sqlite")) {
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
}
//todo: add unique uuid index usage
try (Connection con = dataSource.getConnection();
Statement createStmt = con.createStatement()) {
String createDataStmt = "CREATE TABLE IF NOT EXISTS " + PREMIUM_TABLE + " ("
+ "UserID INTEGER PRIMARY KEY AUTO_INCREMENT, "
+ "UUID CHAR(36), "
+ "Name VARCHAR(16) NOT NULL, "
+ "Premium BOOLEAN NOT NULL, "
+ "LastIp VARCHAR(255) NOT NULL, "
+ "LastLogin TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
//the premium shouldn't steal the cracked account by changing the name
+ "UNIQUE (Name) "
+ ')';
if (dataSource.getJdbcUrl().contains("sqlite")) {
createDataStmt = createDataStmt.replace("AUTO_INCREMENT", "AUTOINCREMENT");
}
createStmt.executeUpdate(createDataStmt);
}
}

View File

@@ -0,0 +1,15 @@
package com.github.games647.fastlogin.core;
public enum ConfirmationState {
/**
* Require server login where we request onlinemode authentication
*/
REQUIRE_RELOGIN,
/**
* The command have to be invoked again to confirm that the player who joined through onlinemode knows
* the password of the cracked account
*/
REQUIRE_AUTH_PLUGIN_LOGIN
}

View File

@@ -6,8 +6,6 @@ import java.time.Instant;
import java.util.Optional;
import java.util.UUID;
import javax.annotation.Nullable;
public class StoredProfile extends Profile {
private long rowId;
@@ -45,7 +43,7 @@ public class StoredProfile extends Profile {
this.rowId = generatedId;
}
@Nullable
// can be null
public synchronized UUID getId() {
return id;
}

View File

@@ -2,9 +2,11 @@ package com.github.games647.fastlogin.core.hooks;
import java.security.SecureRandom;
import java.util.Random;
import java.util.stream.IntStream;
public class DefaultPasswordGenerator<P> implements PasswordGenerator<P> {
private static final int PASSWORD_LENGTH = 8;
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
.toCharArray();
@@ -13,9 +15,10 @@ public class DefaultPasswordGenerator<P> implements PasswordGenerator<P> {
@Override
public String getRandomPassword(P player) {
StringBuilder generatedPassword = new StringBuilder(8);
for (int i = 1; i <= 8; i++) {
generatedPassword.append(PASSWORD_CHARACTERS[random.nextInt(PASSWORD_CHARACTERS.length - 1)]);
}
IntStream.rangeClosed(1, PASSWORD_LENGTH)
.map(i -> random.nextInt(PASSWORD_CHARACTERS.length - 1))
.mapToObj(pos -> PASSWORD_CHARACTERS[pos])
.forEach(generatedPassword::append);
return generatedPassword.toString();
}

View File

@@ -1,10 +1,12 @@
package com.github.games647.fastlogin.core.messages;
package com.github.games647.fastlogin.core.message;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
public class ChangePremiumMessage implements ChannelMessage {
public static final String CHANGE_CHANNEL = "ch-st";
private String playerName;
private boolean willEnable;
private boolean isSourceInvoker;
@@ -27,13 +29,16 @@ public class ChangePremiumMessage implements ChannelMessage {
return willEnable;
}
/**
* @return true if the player invoker = target
*/
public boolean isSourceInvoker() {
return isSourceInvoker;
}
@Override
public String getChannelName() {
return "ChangeStatus";
return CHANGE_CHANNEL;
}
@Override

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.core.messages;
package com.github.games647.fastlogin.core.message;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;

View File

@@ -1,4 +1,4 @@
package com.github.games647.fastlogin.core.messages;
package com.github.games647.fastlogin.core.message;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
@@ -7,6 +7,8 @@ import java.util.UUID;
public class LoginActionMessage implements ChannelMessage {
public static final String FORCE_CHANNEL = "force";
private Type type;
private String playerName;
@@ -60,7 +62,7 @@ public class LoginActionMessage implements ChannelMessage {
@Override
public String getChannelName() {
return "LoginAction";
return FORCE_CHANNEL;
}
@Override

View File

@@ -0,0 +1,26 @@
package com.github.games647.fastlogin.core.message;
public class NamespaceKey {
private static final char SEPARATOR_CHAR = ':';
private final String namespace;
private final String key;
private final String combined;
public NamespaceKey(String namespace, String key) {
this.namespace = namespace.toLowerCase();
this.key = key.toLowerCase();
this.combined = this.namespace + SEPARATOR_CHAR + this.key;
}
public String getCombinedName() {
return combined;
}
public static String getCombined(String namespace, String key) {
return new NamespaceKey(namespace, key).combined;
}
}

View File

@@ -1,13 +1,15 @@
package com.github.games647.fastlogin.core.messages;
package com.github.games647.fastlogin.core.message;
import com.google.common.io.ByteArrayDataInput;
import com.google.common.io.ByteArrayDataOutput;
public class SuccessMessage implements ChannelMessage {
public static final String SUCCESS_CHANNEL = "succ";
@Override
public String getChannelName() {
return "Success";
return SUCCESS_CHANNEL;
}
@Override

View File

@@ -4,13 +4,16 @@ import com.github.games647.craftapi.resolver.MojangResolver;
import com.github.games647.craftapi.resolver.http.RotatingProxySelector;
import com.github.games647.fastlogin.core.AuthStorage;
import com.github.games647.fastlogin.core.CommonUtil;
import com.github.games647.fastlogin.core.ConfirmationState;
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.google.common.net.HostAndPort;
import com.zaxxer.hikari.HikariConfig;
import java.io.IOException;
import java.io.InputStream;
import java.io.Reader;
import java.net.InetAddress;
import java.net.InetSocketAddress;
import java.net.Proxy;
@@ -22,7 +25,6 @@ import java.util.Collection;
import java.util.HashSet;
import java.util.Map;
import java.util.Set;
import java.util.UUID;
import java.util.concurrent.ConcurrentHashMap;
import java.util.concurrent.ConcurrentMap;
@@ -45,7 +47,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
private final Map<String, String> localeMessages = new ConcurrentHashMap<>();
private final ConcurrentMap<String, Object> pendingLogin = CommonUtil.buildCache(5, -1);
private final Collection<UUID> pendingConfirms = new HashSet<>();
private final ConcurrentMap<String, ConfirmationState> pendingConfirms = CommonUtil.buildCache(1, 1024);
private final T plugin;
private final MojangResolver resolver = new MojangResolver();
@@ -74,7 +76,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
.forEach((key, message) -> {
String colored = CommonUtil.translateColorCodes((String) message);
if (!colored.isEmpty()) {
localeMessages.put(key, colored);
localeMessages.put(key, colored.replace("/newline", "\n"));
}
});
} catch (IOException ioEx) {
@@ -103,15 +105,26 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
private Configuration loadFile(String fileName) throws IOException {
Configuration defaults;
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
Configuration defaults;
try (InputStream defaultStream = getClass().getClassLoader().getResourceAsStream(fileName)) {
defaults = configProvider.load(defaultStream);
}
Path file = plugin.getPluginFolder().resolve(fileName);
return configProvider.load(Files.newBufferedReader(file), defaults);
Configuration config;
try (Reader reader = Files.newBufferedReader(file)) {
config = configProvider.load(reader, defaults);
}
//explicitly add keys here, because Configuration.getKeys doesn't return the keys from the default configuration
for (String key : defaults.getKeys()) {
config.set(key, config.get(key));
}
return config;
}
public MojangResolver getResolver() {
@@ -129,7 +142,7 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
public void sendLocaleMessage(String key, C receiver) {
String message = localeMessages.get(key);
if (message != null) {
plugin.sendMessage(receiver, message);
plugin.sendMultiLineMessage(receiver, message);
}
}
@@ -138,8 +151,9 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
public boolean setupDatabase() {
String driver = config.getString("driver");
if (!checkDriver(driver)) {
HikariConfig databaseConfig = new HikariConfig();
databaseConfig.setDriverClassName(config.getString("driver"));
if (!checkDriver(databaseConfig.getDriverClassName())) {
return false;
}
@@ -147,12 +161,15 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
int port = config.get("port", 3306);
String database = config.getString("database");
String user = config.get("username", "");
String password = config.get("password", "");
boolean useSSL = config.get("useSSL", false);
storage = new AuthStorage(this, driver, host, port, database, user, password, useSSL);
databaseConfig.setUsername(config.get("username", ""));
databaseConfig.setPassword(config.getString("password"));
databaseConfig.setConnectionTimeout(config.getInt("timeout", 30) * 1_000L);
databaseConfig.setMaxLifetime(config.getInt("lifetime", 30) * 1_000L);
storage = new AuthStorage(this, host, port, database, databaseConfig, useSSL);
try {
storage.createTables();
return true;
@@ -175,7 +192,6 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
return false;
}
public Configuration getConfig() {
return config;
}
@@ -188,11 +204,18 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
this.passwordGenerator = passwordGenerator;
}
/**
* @return list of player names that are currently during the login process and might fail and so could be used
* for second attempt logins
*/
public ConcurrentMap<String, Object> getPendingLogin() {
return pendingLogin;
}
public Collection<UUID> getPendingConfirms() {
/**
* @return list of player names that request onlinemode authentication but are not yet approved
*/
public ConcurrentMap<String, ConfirmationState> getPendingConfirms() {
return pendingConfirms;
}
@@ -224,6 +247,9 @@ public class FastLoginCore<P extends C, C, T extends PlatformPlugin<C>> {
}
public void close() {
plugin.getLog().info("Safely shutting down scheduler. This could take up to one minute.");
plugin.getScheduler().shutdown();
if (storage != null) {
storage.close();
}

View File

@@ -3,6 +3,7 @@ package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.AuthStorage;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.shared.event.FastLoginAutoLoginEvent;
public abstract class ForceLoginManagement<P extends C, C, L extends LoginSession, T extends PlatformPlugin<C>>
implements Runnable {
@@ -24,14 +25,19 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
}
AuthStorage storage = core.getStorage();
StoredProfile playerProfile = session.getProfile();
try {
if (isOnlineMode()) {
if (session.isConfirmationPending()) {
// do not perform force actions, because this confirmation login we have to verify that it's the
// owner of the account
return;
}
//premium player
AuthPlugin<P> authPlugin = core.getAuthPluginHook();
if (authPlugin == null) {
//maybe only bungeecord plugin
//maybe only bungeecord plugin without any auth plugins on Bungee
onForceActionSuccess(session);
} else {
boolean success = true;
@@ -41,7 +47,7 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
|| (core.getConfig().get("auto-register-unknown", false)
&& !authPlugin.isRegistered(playerName))) {
success = forceRegister(player);
} else {
} else if (!callFastLoginAutoLoginEvent(session, playerProfile).isCancelled()) {
success = forceLogin(player);
}
}
@@ -85,8 +91,8 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
public boolean forceLogin(P player) {
core.getPlugin().getLog().info("Logging player {} in", getName(player));
boolean success = core.getAuthPluginHook().forceLogin(player);
boolean success = core.getAuthPluginHook().forceLogin(player);
if (success) {
core.sendLocaleMessage("auto-login", player);
}
@@ -94,6 +100,8 @@ public abstract class ForceLoginManagement<P extends C, C, L extends LoginSessio
return success;
}
public abstract FastLoginAutoLoginEvent callFastLoginAutoLoginEvent(LoginSession session, StoredProfile profile);
public abstract void onForceActionSuccess(LoginSession session);
public abstract String getName(P player);

View File

@@ -2,8 +2,10 @@ package com.github.games647.fastlogin.core.shared;
import com.github.games647.craftapi.model.Profile;
import com.github.games647.craftapi.resolver.RateLimitException;
import com.github.games647.fastlogin.core.ConfirmationState;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
import com.github.games647.fastlogin.core.shared.event.FastLoginPreLoginEvent;
import java.util.Optional;
@@ -25,6 +27,8 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
return;
}
callFastLoginPreLoginEvent(username, source, profile);
Configuration config = core.getConfig();
String ip = source.getAddress().getAddress().getHostAddress();
@@ -34,7 +38,14 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
if (profile.isPremium()) {
requestPremiumLogin(source, profile, username, true);
} else {
startCrackedSession(source, profile, username);
ConfirmationState confirmationState = core.getPendingConfirms().get(username);
if (confirmationState == ConfirmationState.REQUIRE_RELOGIN) {
core.getPendingConfirms().put(username, ConfirmationState.REQUIRE_AUTH_PLUGIN_LOGIN);
requestPremiumLogin(source, profile, username, true);
} else {
// cracked player, but wants to change to premium
startCrackedSession(source, profile, username);
}
}
} else {
if (core.getPendingLogin().remove(ip + username) != null && config.get("secondAttemptCracked", false)) {
@@ -101,7 +112,11 @@ public abstract class JoinManagement<P extends C, C, S extends LoginSource> {
return false;
}
public abstract FastLoginPreLoginEvent callFastLoginPreLoginEvent(String username, S source, StoredProfile profile);
public abstract void requestPremiumLogin(S source, StoredProfile profile, String username, boolean registered);
public abstract void requestConfirmationLogin(S source, StoredProfile profile, String username);
public abstract void startCrackedSession(S source, StoredProfile profile, String username);
}

View File

@@ -13,12 +13,19 @@ public abstract class LoginSession {
protected boolean registered;
protected boolean confirmationLogin;
public LoginSession(String username, boolean registered, StoredProfile profile) {
this.username = username;
this.registered = registered;
this.profile = profile;
}
public LoginSession(String username, StoredProfile profile) {
this(username, true, profile);
this.confirmationLogin = true;
}
public String getUsername() {
return username;
}
@@ -54,6 +61,10 @@ public abstract class LoginSession {
this.uuid = uuid;
}
public synchronized boolean isConfirmationPending() {
return confirmationLogin;
}
@Override
public synchronized String toString() {
return this.getClass().getSimpleName() + '{' +

View File

@@ -1,5 +1,8 @@
package com.github.games647.fastlogin.core.shared;
import com.github.games647.fastlogin.core.AsyncScheduler;
import com.google.common.util.concurrent.ThreadFactoryBuilder;
import java.nio.file.Path;
import java.util.concurrent.ThreadFactory;
@@ -15,7 +18,20 @@ public interface PlatformPlugin<C> {
void sendMessage(C receiver, String message);
AsyncScheduler getScheduler();
default void sendMultiLineMessage(C receiver, String message) {
for (String line : message.split("%nl%")) {
sendMessage(receiver, line);
}
}
default ThreadFactory getThreadFactory() {
return null;
return new ThreadFactoryBuilder()
.setNameFormat(getName() + " Pool Thread #%1$d")
// Hikari create daemons by default and we could daemon threads for our own scheduler too
// because we safely shutdown
.setDaemon(true)
.build();
}
}

View File

@@ -0,0 +1,9 @@
package com.github.games647.fastlogin.core.shared.event;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSession;
public interface FastLoginAutoLoginEvent extends FastLoginCancellableEvent {
LoginSession getSession();
StoredProfile getProfile();
}

View File

@@ -0,0 +1,7 @@
package com.github.games647.fastlogin.core.shared.event;
public interface FastLoginCancellableEvent {
boolean isCancelled();
void setCancelled(boolean cancelled);
}

View File

@@ -0,0 +1,11 @@
package com.github.games647.fastlogin.core.shared.event;
import com.github.games647.fastlogin.core.StoredProfile;
import com.github.games647.fastlogin.core.shared.LoginSource;
public interface FastLoginPreLoginEvent {
String getUsername();
LoginSource getSource();
StoredProfile getProfile();
}

View File

@@ -115,11 +115,11 @@ nameChangeCheck: false
# ChangeSkin, SkinRestorer, ...
forwardSkin: true
# Displays a warning message that this message SHOULD only be invoked by
# users who actually are the owner of this account. So not by cracked players
#
# If they still want to invoke the command, they have to invoke /premium again
premium-warning: true
# Players have to rejoin to verify that they can join through onlinemode authentication.
# After that they have to confirm again using the /premium command that they are also the owner of the auth plugin
# account.
# If the onlinemode authentication fails or the player waits to long, it will fallback to offlinemode authentication.
premium-confirm: true
# If you have autoRegister or nameChangeCheck enabled, you could be rate-limited by Mojang.
# The requests of the both options will be only made by FastLogin if the username is unknown to the server
@@ -161,17 +161,22 @@ autoLogin: true
# Recommended is the use of MariaDB (a better version of MySQL)
# Single file SQLite database
driver: org.sqlite.JDBC
driver: 'org.sqlite.JDBC'
# File location
database: '{pluginDir}/FastLogin.db'
# MySQL/MariaDB
#driver: com.mysql.jdbc.Driver
#host: 127.0.0.1
# If you want to enable it uncomment only the lines below this not this line.
#driver: 'com.mysql.jdbc.Driver'
#host: '127.0.0.1'
#port: 3306
#database: fastlogin
#username: myUser
#password: myPassword
#database: 'fastlogin'
#username: 'myUser'
#password: 'myPassword'
# Advanced Connection Pool settings in seconds
#timeout: 30
#lifetime: 30
# It's strongly recommended to enable SSL and setup a SSL certificate if the MySQL server isn't running on the same
# machine

View File

@@ -89,9 +89,9 @@ invalid-requst: '&4Invalid request'
# Message if the Bukkit isn't fully started to inject the packets
not-started: '&cServer is not fully started yet. Please retry'
# Warning message if a user invoked /premium command
premium-warning: '&c&lWARNING: &6This command should &lonly&6 be invoked if you are the owner of this paid Minecraft account
Type &a/premium&6 again to confirm'
# Premium confirmation message if the player tries to activate the command
premium-confirm: '&6Please relogin and type the command again to apply the changes.
If the request fails or you wait to long, it will fallback to offlinemode.'
# ========= Bungee/Waterfall only ================================

25
pom.xml
View File

@@ -1,5 +1,5 @@
<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 http://maven.apache.org/xsd/maven-4.0.0.xsd">
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>
<groupId>com.github.games647</groupId>
@@ -8,11 +8,11 @@
<packaging>pom</packaging>
<name>FastLogin</name>
<version>1.11</version>
<version>1.11-SNAPSHOT</version>
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
<description>
Automatically logins premium (paid accounts) player on a offline mode server
Automatically login premium (paid accounts) player on a offline mode server
</description>
<properties>
@@ -21,8 +21,9 @@
<!-- Set default for non-git clones -->
<git.commit.id>Unknown</git.commit.id>
<maven.compiler.source>1.8</maven.compiler.source>
<maven.compiler.target>1.8</maven.compiler.target>
<java.version>1.8</java.version>
<maven.compiler.source>${java.version}</maven.compiler.source>
<maven.compiler.target>${java.version}</maven.compiler.target>
</properties>
<modules>
@@ -31,6 +32,18 @@
<module>bungee</module>
</modules>
<!--Deployment configuration for the Maven repository-->
<distributionManagement>
<snapshotRepository>
<id>codemc-snapshots</id>
<url>https://repo.codemc.io/repository/maven-snapshots/</url>
</snapshotRepository>
<repository>
<id>codemc-releases</id>
<url>https://repo.codemc.io/repository/maven-releases/</url>
</repository>
</distributionManagement>
<build>
<!--Just use the project name to replace an old version of the plugin if the user does only copy-paste-->
<finalName>${project.name}</finalName>
@@ -39,7 +52,7 @@
<plugin>
<groupId>pl.project13.maven</groupId>
<artifactId>git-commit-id-plugin</artifactId>
<version>2.2.4</version>
<version>4.0.0</version>
<configuration>
<failOnNoGitDirectory>false</failOnNoGitDirectory>
</configuration>