mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-23 23:28:08 +01:00
Compare commits
192 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
87ca00d75d | ||
|
|
62ffb1a904 | ||
|
|
5075a71843 | ||
|
|
da266c7e91 | ||
|
|
acab4766b1 | ||
|
|
bef90d11cd | ||
|
|
a02acd2d63 | ||
|
|
ca42a7c19e | ||
|
|
b533197f05 | ||
|
|
c94711f315 | ||
|
|
ee7af80bf0 | ||
|
|
17c2099bf1 | ||
|
|
31d6b67381 | ||
|
|
4b423c9ccb | ||
|
|
4292e9aaa0 | ||
|
|
07d0aededa | ||
|
|
218bc50c96 | ||
|
|
a3b2e33aad | ||
|
|
76f5ba7ed1 | ||
|
|
2cd50d23ad | ||
|
|
9f5f61f1c2 | ||
|
|
3e9c8e3a7e | ||
|
|
8e5da01be0 | ||
|
|
5022c9aa7b | ||
|
|
ad1ab22586 | ||
|
|
99ef5ce726 | ||
|
|
9b7634a9f3 | ||
|
|
115fc2e7ba | ||
|
|
b660951e1e | ||
|
|
e495f70ccd | ||
|
|
b35d67b5c0 | ||
|
|
58ac73a5a9 | ||
|
|
ebe768f7a2 | ||
|
|
d20db79f46 | ||
|
|
c28d889c1b | ||
|
|
ad60397851 | ||
|
|
88fdeff3f1 | ||
|
|
558ee1c92c | ||
|
|
3e84ebd787 | ||
|
|
36d7564c3a | ||
|
|
596caa0573 | ||
|
|
fe4331298f | ||
|
|
a67d84ef3f | ||
|
|
71362dfd7d | ||
|
|
fcd98fce43 | ||
|
|
6c1c4e7286 | ||
|
|
164fb735d6 | ||
|
|
fa1b0970a5 | ||
|
|
974bf498fc | ||
|
|
27c04ff08f | ||
|
|
fb357424e6 | ||
|
|
c73bb70256 | ||
|
|
dc395cdc3f | ||
|
|
f7626ab969 | ||
|
|
5f9802d589 | ||
|
|
642c1621ad | ||
|
|
eb965d5a48 | ||
|
|
457bc9cf47 | ||
|
|
2ab3c6b77c | ||
|
|
f27bad02d3 | ||
|
|
9334296beb | ||
|
|
fd9940e6f0 | ||
|
|
0745957e79 | ||
|
|
bb2e60f6e1 | ||
|
|
d15861b8e5 | ||
|
|
b84b340a77 | ||
|
|
c50249edea | ||
|
|
757ddb905a | ||
|
|
9914b7f358 | ||
|
|
bba4eb4eec | ||
|
|
2b16f3341f | ||
|
|
167ce66057 | ||
|
|
8d1021e44c | ||
|
|
a811a741f5 | ||
|
|
a6348766b3 | ||
|
|
22dcc50950 | ||
|
|
bd3494eed0 | ||
|
|
1aba9a0f3b | ||
|
|
6faf00e1bf | ||
|
|
0d89614f3c | ||
|
|
b009658eea | ||
|
|
2881689f09 | ||
|
|
6d1a97fd32 | ||
|
|
b74faa2fd5 | ||
|
|
4800a88886 | ||
|
|
92c9ab5b76 | ||
|
|
d90e3fdb44 | ||
|
|
8abbb8f07c | ||
|
|
f04a44b1d2 | ||
|
|
1a66121977 | ||
|
|
413a0325f8 | ||
|
|
9fc7e0bf43 | ||
|
|
ac8bcb1758 | ||
|
|
bebcb3e9de | ||
|
|
0b899f61a8 | ||
|
|
7733135ce4 | ||
|
|
be89eec23b | ||
|
|
679060d4e9 | ||
|
|
f6aa064835 | ||
|
|
de4b73c3bd | ||
|
|
ac15829dcc | ||
|
|
0b709997a4 | ||
|
|
8809875ca4 | ||
|
|
aa30c070b9 | ||
|
|
51d0aefbf3 | ||
|
|
cb876a52bd | ||
|
|
3e844be65d | ||
|
|
dce95cf0d0 | ||
|
|
81eeaeae83 | ||
|
|
6b1542de88 | ||
|
|
99b7367366 | ||
|
|
961b144efb | ||
|
|
dcd06ad613 | ||
|
|
c4c043e1c5 | ||
|
|
87aa9dd668 | ||
|
|
2838c06ab3 | ||
|
|
ae58e0539a | ||
|
|
624745728f | ||
|
|
d0287ec2b4 | ||
|
|
e6a4af92cc | ||
|
|
8f3920fa99 | ||
|
|
a723b2ddd3 | ||
|
|
5cf67127c7 | ||
|
|
e5309b9fa1 | ||
|
|
e439126294 | ||
|
|
59703bac4e | ||
|
|
bfaf390463 | ||
|
|
9e06fd7735 | ||
|
|
d56a0f9ff1 | ||
|
|
96fe190cac | ||
|
|
d4f5b547d4 | ||
|
|
67a4f41056 | ||
|
|
5174a84a17 | ||
|
|
a7b164b513 | ||
|
|
ffa5059c67 | ||
|
|
dfe37dfc1b | ||
|
|
6edd40742d | ||
|
|
b697dc6655 | ||
|
|
36974450ce | ||
|
|
57a59045ce | ||
|
|
11cc4eabc0 | ||
|
|
53e02d5457 | ||
|
|
0f85674ec1 | ||
|
|
378ab09bc8 | ||
|
|
740b11b434 | ||
|
|
77f0184899 | ||
|
|
2885daf8b9 | ||
|
|
1e128d12f5 | ||
|
|
f2a8446c8d | ||
|
|
cc8c49e25b | ||
|
|
25e182148f | ||
|
|
f00608c321 | ||
|
|
b86bdf5f23 | ||
|
|
9a30a0b299 | ||
|
|
fd3b1ed8b6 | ||
|
|
f3e675e547 | ||
|
|
0967f31b9a | ||
|
|
8cb4621055 | ||
|
|
f610001c9b | ||
|
|
dd386408d1 | ||
|
|
10bfd279d6 | ||
|
|
5608821fe3 | ||
|
|
9c0ad7d70c | ||
|
|
099b8e5d0a | ||
|
|
b4ade882be | ||
|
|
4a3cb42152 | ||
|
|
8fc5050e8e | ||
|
|
015739fe4c | ||
|
|
b2ae46a90a | ||
|
|
353cd17823 | ||
|
|
157b8499a9 | ||
|
|
bd46dae086 | ||
|
|
eacbb1ed76 | ||
|
|
e389433138 | ||
|
|
d1b2fe8865 | ||
|
|
3b4c4a1c79 | ||
|
|
b22df62f90 | ||
|
|
d118de8649 | ||
|
|
f8c10d6890 | ||
|
|
c3f8e59a9a | ||
|
|
834818bb7a | ||
|
|
fa46dc690b | ||
|
|
fdc2772f38 | ||
|
|
53af09ae34 | ||
|
|
f6f6aaf1de | ||
|
|
c0ef95e808 | ||
|
|
cb129547f5 | ||
|
|
eb394b5f60 | ||
|
|
aebbc84621 | ||
|
|
0eee6ba2be | ||
|
|
7e2057a7a2 | ||
|
|
800f077be0 |
7
.gitignore
vendored
7
.gitignore
vendored
@@ -6,6 +6,7 @@
|
||||
# netbeans
|
||||
/nbproject
|
||||
nb-configuration.xml
|
||||
/bukkit/nbproject/
|
||||
|
||||
# maven
|
||||
/target
|
||||
@@ -40,3 +41,9 @@ gradle-app.setting
|
||||
|
||||
# Avoid ignoring Gradle wrapper jar file (.jar files are usually ignored)
|
||||
!gradle-wrapper.jar
|
||||
|
||||
# Project module targets
|
||||
bukkit/target
|
||||
universal/target
|
||||
bungee/target
|
||||
core/target
|
||||
|
||||
12
.travis.yml
Normal file
12
.travis.yml
Normal file
@@ -0,0 +1,12 @@
|
||||
# Use https://travis-ci.org/ for automatic tests
|
||||
|
||||
# speed up testing http://blog.travis-ci.com/2014-12-17-faster-builds-with-container-based-infrastructure/
|
||||
sudo: false
|
||||
|
||||
# This is a java project
|
||||
language: java
|
||||
|
||||
script: mvn compile test
|
||||
|
||||
# We run on 8
|
||||
jdk: [oraclejdk8]
|
||||
218
CHANGELOG.md
Normal file
218
CHANGELOG.md
Normal file
@@ -0,0 +1,218 @@
|
||||
######1.9
|
||||
|
||||
* Added second attempt login -> cracked login
|
||||
* Added cracked whitelist (switch-mode -> switching to online-mode from offlinemode)
|
||||
* Added configuration to disable auto logins for 2Factor authentication
|
||||
* Added missing add-premium-other message
|
||||
* Upgrade to Java 8 -> Minimize file size
|
||||
* Refactored/Cleaned up a lot of code
|
||||
* [API] Deprecated platform specific authplugin. Please use AuthPlugin< platform specific player type >
|
||||
* [API] Deprecated bukkit's password generator. Please use PasswordGenerator< platform specific player type >
|
||||
* Fix ProtocolSupport autoRegister
|
||||
* Fix update username in FastLogin database after nameChange
|
||||
* Fix logging exceptions on encryption enabling
|
||||
* Fix compatibility with older ProtocolLib versions (for 1.7) because of the missing getMethodAcccessorOrNull method
|
||||
* Fix correct cracked permission for bukkit
|
||||
* A try to fix SQLite timestamp parsing
|
||||
* Drop support for LoginSecurity 1.X since 2.X seems to be stable
|
||||
* Remove the nasty UltraAuth fakeplayer workaround by using a new api method. You should UltraAuth if you have it
|
||||
|
||||
######1.8
|
||||
|
||||
* Added autoIn importer
|
||||
* Added BFA importer
|
||||
* Added ElDziAuth importer
|
||||
* Fix third-party not premium player detection
|
||||
* Fix ProtocolSupport BungeeCord
|
||||
* Fix duplicate logins for BungeeAuth users
|
||||
|
||||
######1.7.1
|
||||
|
||||
* Fix BungeeCord autoRegister (Fixes #46)
|
||||
* Fix protocollsupport autoregister
|
||||
|
||||
######1.7
|
||||
|
||||
* Added support for making requests to Mojang from different IPv4 addresses
|
||||
* Added us.mcapi.com as third-party APIs to workaround rate-limits
|
||||
* Fixed NPE in BungeeCord on cracked session
|
||||
* Fixed skin applies if premium uuid is deactivated
|
||||
* Fix player entry is not saved if namechangecheck is enabled
|
||||
* Fix skin applies for third-party plugins
|
||||
* Switch to mcapi.ca for uuid lookups
|
||||
* Fix BungeeCord not setting an premium uuid
|
||||
* Fix setting skin on Cauldron
|
||||
* Fix saving on name change
|
||||
|
||||
######1.6.2
|
||||
|
||||
* Fixed support for new LoginSecurity version
|
||||
|
||||
######1.6.1
|
||||
|
||||
* Fix message typo in BungeeCord which created a NPE if premium-warning is activated
|
||||
|
||||
######1.6
|
||||
|
||||
* Add a warning message if the user tries to invoke the premium command
|
||||
* Added missing translation if the server isn't fully started
|
||||
* Removed ProtocolLib as required dependency. You can use ProtocolSupport or BungeeCord as alternative
|
||||
* Reduce the number of worker threads from 5 to 3 in ProtocolLib
|
||||
* Process packets in ProtocolLib async/non-blocking -> better performance
|
||||
* Fixed missing translation in commands
|
||||
* Fixed cracked command not working on BungeeCord
|
||||
* Fix error if forward skins is disabled
|
||||
|
||||
######1.5.2
|
||||
|
||||
* Fixed BungeeCord force logins if there is a lobby server
|
||||
* Removed cache expire in BungeeCord
|
||||
* Applies skin earlier to make it visible for other plugins listening on login events
|
||||
|
||||
######1.5.1
|
||||
|
||||
* Fixed BungeeCord support by correctly saving the proxy ids
|
||||
|
||||
######1.5
|
||||
|
||||
* Added localization
|
||||
* Fixed NPE on premium name check if it's pure cracked player
|
||||
* Fixed NPE in BungeeCord on cracked login for existing players
|
||||
* Fixed saving of existing cracked players
|
||||
|
||||
######1.4
|
||||
|
||||
* Added Bungee setAuthPlugin method
|
||||
* Added nameChangeCheck
|
||||
* Multiple BungeeCord support
|
||||
|
||||
######1.3.1
|
||||
|
||||
* Prevent thread create violation in BungeeCord
|
||||
|
||||
######1.3
|
||||
|
||||
* Added support for AuthMe 3.X
|
||||
* Fixed premium logins if the server is not fully started
|
||||
* Added other command argument to /premium and /cracked
|
||||
* Added support for LogIt
|
||||
* Fixed 1.7 Minecraft support by removing guava 11+ only features -> Cauldron support
|
||||
* Fixed BungeeCord support in Cauldron
|
||||
|
||||
######1.2.1
|
||||
|
||||
* Fix premium status change notification message on BungeeCord
|
||||
|
||||
######1.2
|
||||
|
||||
* Fix race condition in BungeeCord
|
||||
* Fix dead lock in xAuth
|
||||
* Added API methods for plugins to set their own password generator
|
||||
* Added API methods for plugins to set their own auth plugin hook
|
||||
=> Added support for AdvancedLogin
|
||||
|
||||
######1.1
|
||||
|
||||
* Make the configuration options also work under BungeeCord (premiumUUID, forwardSkin)
|
||||
* Catch configuration loading exception if it's not spigot build
|
||||
* Fix config loading for older Spigot builds
|
||||
|
||||
######1.0
|
||||
|
||||
* Massive refactor to handle errors on force actions safely
|
||||
* force Methods now runs async too
|
||||
* force methods now returns a boolean to reflect if the method was successful
|
||||
* isRegistered method should now throw an exception if the plugin was unable to query the requested data
|
||||
|
||||
######0.8
|
||||
|
||||
* Fixed BungeeCord support for the Bukkit module
|
||||
* Added database storage to save the premium state
|
||||
* Fix logical error on /premium (Thanks to @NorbiPeti)
|
||||
* Fixed issues with host lookup from hosts file (Thanks to @NorbiPeti)
|
||||
* Remove handshake listener because it creates errors on some systems
|
||||
|
||||
######0.7
|
||||
|
||||
* Added BungeeAuth support
|
||||
* Added /premium [player] command with optional player parameter
|
||||
* Added a check if the player is already on the premium list
|
||||
* Added a forwardSkin config option
|
||||
* Added premium UUID support
|
||||
* Updated to the newest changes of Spigot
|
||||
* Removes the need of an Bukkit auth plugin if you use a bungeecord one
|
||||
* Optimize performance and thread-safety
|
||||
* Fixed BungeeCord support
|
||||
* Changed config option autologin to autoregister to clarify the usage
|
||||
|
||||
######0.6
|
||||
|
||||
* Fixed 1.9 bugs
|
||||
* Added UltraAuth support
|
||||
|
||||
######0.5
|
||||
|
||||
* Added unpremium command
|
||||
* Added autologin - See config
|
||||
* Added config
|
||||
* Added isRegistered API method
|
||||
* Added forceRegister API method
|
||||
|
||||
* Fixed CrazyLogin player data restore -> Fixes memory leaks with this plugin
|
||||
* Fixed premium name check to protocolsupport
|
||||
* Improved permissions management
|
||||
|
||||
######0.4
|
||||
|
||||
* Added forward premium skin
|
||||
* Added plugin support for protocolsupport
|
||||
|
||||
######0.3.2
|
||||
|
||||
* Run packet readers in a different thread (separated from the Netty I/O Thread)
|
||||
-> Improves performance
|
||||
* Fixed Plugin disable if the server is in online mode but have to be in offline mode
|
||||
|
||||
######0.3.1
|
||||
|
||||
* Improved BungeeCord security
|
||||
|
||||
#####0.3
|
||||
|
||||
* Added BungeeCord support
|
||||
* Decrease timeout checks in order to fail faster on connection problems
|
||||
* Code style improvements
|
||||
|
||||
######0.2.4
|
||||
|
||||
* Fixed NPE on invalid sessions
|
||||
* Improved security by generating a randomized serverId
|
||||
* Removed /premium [player] because it's safer for premium players who join without registration
|
||||
|
||||
######0.2.3
|
||||
|
||||
* Remove useless AuthMe forcelogin code
|
||||
* Send a kick message to the client instead of just "Disconnect"
|
||||
* Reformat source code
|
||||
* Fix thread safety for fake start packets (Bukkit.getOfflinePlayer doesn't look like to be thread-safe)
|
||||
* Added more documentation
|
||||
|
||||
######0.2.2
|
||||
|
||||
* Compile project with Java 7 :(
|
||||
|
||||
######0.2.1
|
||||
* A couple of security fixes (premium players cannot longer steal the account of a cracked account)
|
||||
* Added a /premium command to mark you as premium player
|
||||
|
||||
#####0.2
|
||||
|
||||
* Added support for CrazyLogin and LoginSecurity
|
||||
* Now minecraft version independent
|
||||
* Added debug logging
|
||||
* Code clean up
|
||||
* More state validation
|
||||
* Added better error handling
|
||||
|
||||
#####0.1
|
||||
* First release
|
||||
187
README.md
187
README.md
@@ -1,7 +1,184 @@
|
||||
# mcMMOExtras
|
||||
# FastLogin
|
||||
|
||||
A visual boss bar Bukkit plugin for mcMMO that keeps people entertained and encourages them to want to level up.
|
||||
[](https://travis-ci.org/games647/FastLogin)
|
||||
[](https://www.paypal.com/cgi-bin/webscr?cmd=_s-xclick&hosted_button_id=8ZBULMAPN7MZC)
|
||||
|
||||
See
|
||||
* http://dev.bukkit.org/bukkit-plugins/mcmmoextras/
|
||||
* http://www.curse.com/bukkit-plugins/minecraft/mcmmoextras
|
||||
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:
|
||||
|
||||
* Detect paid accounts from others
|
||||
* Automatically login paid accounts (premium)
|
||||
* Support various of auth plugins
|
||||
* Cauldron support
|
||||
* Forge/Sponge message support
|
||||
* Premium UUID support
|
||||
* Forwards Skins
|
||||
* Detect user name changed and will update the existing database record
|
||||
* BungeeCord support
|
||||
* Auto register new premium players
|
||||
* Plugin: ProtocolSupport is supported and can be used as an alternative to ProtocolLib
|
||||
* No client modifications needed
|
||||
* Good performance by using async non blocking operations
|
||||
* Locale messages
|
||||
* Import the database from similar plugins
|
||||
* Free
|
||||
* Open source
|
||||
|
||||
***
|
||||
|
||||
###Commands:
|
||||
* /premium [player] Label the invoker or the argument as paid account
|
||||
* /cracked [player] Label the invoker or the argument as cracked account
|
||||
* /importdb <autoIn/bpa/eldzi> <mysql/sqlite> [host:port] [database] [username] [password] - Imports the database from another plugin
|
||||
|
||||
###Permissions:
|
||||
* fastlogin.bukkit.command.premium
|
||||
* fastlogin.bukkit.command.cracked
|
||||
* fastlogin.command.premium.other
|
||||
* fastlogin.command.cracked.other
|
||||
* fastlogin.command.import
|
||||
|
||||
###Requirements:
|
||||
* Plugin: [ProtocolLib](http://www.spigotmc.org/resources/protocollib.1997/) or [ProtocolSupport](http://www.spigotmc.org/resources/protocolsupport.7201/)
|
||||
* Tested Bukkit/[Spigot](https://www.spigotmc.org) 1.9 (could also work with other versions)
|
||||
* Java 7+
|
||||
* Run Spigot and/or BungeeCord/Waterfall in offline mode (see server.properties or config.yml)
|
||||
* An auth plugin. Supported plugins
|
||||
|
||||
####Bukkit/Spigot/PaperSpigot
|
||||
|
||||
* [AuthMe (both 5.X and 3.X)](http://dev.bukkit.org/bukkit-plugins/authme-reloaded/)
|
||||
* [xAuth](http://dev.bukkit.org/bukkit-plugins/xauth/)
|
||||
* [LogIt](https://github.com/XziomekX/LogIt)
|
||||
* [AdvancedLogin (Paid)](https://www.spigotmc.org/resources/advancedlogin.10510/)
|
||||
* [CrazyLogin](http://dev.bukkit.org/bukkit-plugins/crazylogin/)
|
||||
* [LoginSecurity](http://dev.bukkit.org/bukkit-plugins/loginsecurity/)
|
||||
* [RoyalAuth](http://dev.bukkit.org/bukkit-plugins/royalauth/)
|
||||
* [UltraAuth](http://dev.bukkit.org/bukkit-plugins/ultraauth-aa/)
|
||||
|
||||
####BungeeCord/Waterfall
|
||||
|
||||
* [BungeeAuth](https://www.spigotmc.org/resources/bungeeauth.493/)
|
||||
|
||||
###Downloads
|
||||
|
||||
https://www.spigotmc.org/resources/fastlogin.14153/history
|
||||
|
||||
***
|
||||
|
||||
###How to install
|
||||
|
||||
####Bukkit/Spigot/PaperSpigot
|
||||
|
||||
1. Download and install ProtocolLib
|
||||
2. Download and install FastLogin
|
||||
3. Set your server in offline mode by setting the value onlinemode in your server.properties to false
|
||||
|
||||
####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
|
||||
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. (BungeeCord doesn't support SQLite per default, so you should change the configuration to MySQL or MariaDB)
|
||||
|
||||
***
|
||||
|
||||
###FAQ
|
||||
|
||||
####Index
|
||||
1. [How does Minecraft logins work?](#how-does-minecraft-logins-work)
|
||||
2. [How does this plugin work?](#how-does-this-plugin-work)
|
||||
3. [Why does the plugin require offline mode?](#why-does-the-plugin-require-offline-mode)
|
||||
4. [Can cracked player join with premium usernames?](#can-cracked-player-join-with-premium-usernames)
|
||||
5. [Why do players have to invoke a command?](#why-do-players-have-to-invoke-a-command)
|
||||
6. [What happens if a paid account joins with a used username?](#what-happens-if-a-paid-account-joins-with-a-used-username)
|
||||
7. [Does the plugin have BungeeCord support?](#does-the-plugin-have-bungeecord-support)
|
||||
8. [Could premium players have a premium UUID and Skin?](#could-premium-players-have-a-premium-uuid-and-skin)
|
||||
9. [Is this plugin compatible with Cauldron?](#is-this-plugin-compatible-with-cauldron)
|
||||
|
||||
####How does minecraft logins work?
|
||||
######Online Mode
|
||||
1. Client -> Server: I want to login, here is my username
|
||||
2. Server -> Client: Okay. I'm in online mode so here is my public key for encryption and my serverid
|
||||
3. Client -> Mojang: I'm player "xyz". I want to join a server with that serverid
|
||||
4. Mojang -> Client: Session data checked. You can continue
|
||||
5. Client -> Server: I received a successful response from Mojang. Heres our shared secret key
|
||||
6. Server -> Mojang: Does the player "xyz" with this shared secret key has a valid account to join me?
|
||||
7. Mojang -> Server: Yes, the player has the following additionally properties (UUID, Skin)
|
||||
8. Client and Server: encrypt all following communication packet
|
||||
9. Server -> Client: Everything checked you can play now
|
||||
|
||||
|
||||
######Offline Mode
|
||||
In offline mode step 2-7 is skipped. So a login request is directly followed by 8.
|
||||
|
||||
######More details
|
||||
http://wiki.vg/Protocol#Login
|
||||
|
||||
####How does this plugin work?
|
||||
By using ProtocolLib, this plugin works as a proxy between the client and server. This plugin will fake that the server
|
||||
runs in online mode. It does everything an online mode server would do. This will be for example, generating keys or
|
||||
checking for valid sessions. Because everything is the same compared to an offline mode login after an encrypted
|
||||
connection, we will intercept only **login** packets of **premium** players.
|
||||
|
||||
1. Player is connecting to the server.
|
||||
2. Plugin checks if the username we received activated the fast login method (i.e. using command)
|
||||
3. Run a check if the username is currently used by a paid account.
|
||||
(We don't know yet if the client connecting is premium)
|
||||
4. Request an Mojang Session Server authentication
|
||||
5. On response check if all data is correct
|
||||
6. Encrypt the connection
|
||||
7. On success intercept all related login packets and fake a new login packet as a normal offline login
|
||||
|
||||
####Why does the plugin require offline mode?
|
||||
1. As you can see in the question "how does minecraft login works", offline mode is equivalent to online mode except of
|
||||
the encryption and session checks on login. So we can intercept and cancel the first packets for premium players and
|
||||
enable an encrypted connection. Then we send a new fake packet in order to pretend that this a new login request from
|
||||
a offline mode player. The server will handle the rest.
|
||||
2. Some plugins check if the server is in online mode. If so, they could process the real offline (cracked) accounts
|
||||
incorrectly. For example, a plugin tries to fetch the UUID from Mojang, but the name of the player is not associated to
|
||||
a paid account.
|
||||
3. Servers, who allow cracked players and just speed up logins for premium players, are **already** in offline mode.
|
||||
|
||||
####Can cracked player join with premium usernames?
|
||||
Yes, indeed. Therefore the command for toggling the fast login method exists.
|
||||
|
||||
####Why do players have to invoke a command?
|
||||
1. It's a secure way to make sure a person with a paid account cannot steal the account
|
||||
of a cracked player that has the same username. The player have to proof first that it's his own account.
|
||||
2. We only receive the username from the player on login. We could check if that username is associated
|
||||
to a paid account but if we request a online mode login from a cracked player (who uses a username from
|
||||
a paid account), the player will disconnect with the reason "bad login" or "Invalid session". There is no way to change
|
||||
that message on the server side (without client modifications), because it's a connection between the Client and the
|
||||
Sessionserver.
|
||||
3. If a premium player would skip registration too, a player of a cracked account could later still register the
|
||||
account and would claim and steal the account from the premium player. Because commands cannot be invoked unless the
|
||||
player has a account or is logged in, protects this method also premium players
|
||||
|
||||
###What happens if a paid account joins with a used username?
|
||||
The player on the server have to activate the feature of this plugin by command. If a person buys the username
|
||||
of his own account, it's still secured. A normal offline mode login makes sure he's the owner of the server account
|
||||
and Mojang account. Then the command can be executed. So someone different cannot steal the account of cracked player
|
||||
by buying the username.
|
||||
|
||||
####Does the plugin have BungeeCord support?
|
||||
Yes it has. See the how to install above.
|
||||
|
||||
####Could premium players have a premium UUID and Skin?
|
||||
Since 0.7 both features are implemented. You can check the config.yml in order to activate it.
|
||||
|
||||
####Is this plugin compatible with Cauldron?
|
||||
It's not tested yet, but all needed methods also exists in Cauldron so it could work together.
|
||||
|
||||
***
|
||||
|
||||
###Useful Links:
|
||||
* [Login Protocol](http://wiki.vg/Protocol#Login)
|
||||
* [Protocol Encryption](http://wiki.vg/Protocol_Encryption)
|
||||
BIN
bukkit/lib/CrazyCore v10.7.7.jar
Normal file
BIN
bukkit/lib/CrazyCore v10.7.7.jar
Normal file
Binary file not shown.
BIN
bukkit/lib/CrazyLogin v7.23.2.jar
Normal file
BIN
bukkit/lib/CrazyLogin v7.23.2.jar
Normal file
Binary file not shown.
BIN
bukkit/lib/UltraAuth v2.1.2.jar
Normal file
BIN
bukkit/lib/UltraAuth v2.1.2.jar
Normal file
Binary file not shown.
182
bukkit/pom.xml
Normal file
182
bukkit/pom.xml
Normal file
@@ -0,0 +1,182 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||
<artifactId>fastlogin.bukkit</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>FastLoginBukkit</name>
|
||||
|
||||
<repositories>
|
||||
<!--Bukkit-Server-API -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
|
||||
<!--LoginSecurity-->
|
||||
<repository>
|
||||
<id>lenis0012-repo</id>
|
||||
<url>http://ci.lenis0012.com/plugin/repository/everything/</url>
|
||||
</repository>
|
||||
|
||||
<!--ProtocolLib-->
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>http://repo.dmulloy2.net/content/groups/public/</url>
|
||||
</repository>
|
||||
|
||||
<!--Authme Reloaded-->
|
||||
<repository>
|
||||
<id>xephi-repo</id>
|
||||
<url>http://ci.xephi.fr/plugin/repository/everything/</url>
|
||||
</repository>
|
||||
|
||||
<!--xAuth-->
|
||||
<repository>
|
||||
<id>luricos.de-repo</id>
|
||||
<url>http://repo.luricos.de/bukkit-plugins/</url>
|
||||
</repository>
|
||||
|
||||
<!--Github automatic maven builds-->
|
||||
<repository>
|
||||
<id>jitpack.io</id>
|
||||
<url>https://jitpack.io</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Server API-->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.10-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Library for listening and sending Minecraft packets-->
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>3.6.5</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.ProtocolSupport</groupId>
|
||||
<artifactId>ProtocolSupport</artifactId>
|
||||
<!--4.25.dev-->
|
||||
<version>5554413b51</version>
|
||||
</dependency>
|
||||
|
||||
<!--Login Plugins-->
|
||||
<dependency>
|
||||
<groupId>fr.xephi</groupId>
|
||||
<artifactId>authme</artifactId>
|
||||
<version>5.2-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.lenis0012.bukkit</groupId>
|
||||
<artifactId>loginsecurity</artifactId>
|
||||
<version>2.1.3-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>LogIt</artifactId>
|
||||
<version>9e3581db27</version>
|
||||
<optional>true</optional>
|
||||
<scope>provided</scope>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.github.RoyalDev</groupId>
|
||||
<artifactId>RoyalAuth</artifactId>
|
||||
<version>-e21354a9b7-1</version>
|
||||
<optional>true</optional>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.luricos.bukkit</groupId>
|
||||
<artifactId>xAuth</artifactId>
|
||||
<version>2.6</version>
|
||||
<optional>true</optional>
|
||||
<!--These artifacts produce conflicts on downloading-->
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<!--No maven repository :(-->
|
||||
<dependency>
|
||||
<groupId>de.st_ddt.crazy</groupId>
|
||||
<artifactId>CrazyCore</artifactId>
|
||||
<version>10.7.7</version>
|
||||
<optional>true</optional>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/CrazyCore v10.7.7.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.st_ddt.crazy</groupId>
|
||||
<artifactId>CrazyLogin</artifactId>
|
||||
<version>7.23</version>
|
||||
<optional>true</optional>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/CrazyLogin v7.23.2.jar</systemPath>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>ultraauth</groupId>
|
||||
<artifactId>ultraauth</artifactId>
|
||||
<version>2.0.2</version>
|
||||
<optional>true</optional>
|
||||
<scope>system</scope>
|
||||
<systemPath>${project.basedir}/lib/UltraAuth v2.1.2.jar</systemPath>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,72 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.configuration.file.YamlConfiguration;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class BukkitCore extends FastLoginCore<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public BukkitCore(FastLoginBukkit plugin) {
|
||||
super(plugin.getConfig().getValues(false));
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return plugin.getLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
public ThreadFactory getThreadFactory() {
|
||||
String pluginName = plugin.getName();
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(pluginName + " Database Pool Thread #%1$d")
|
||||
//Hikari create daemons by default
|
||||
.setDaemon(true)
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessages() {
|
||||
plugin.saveResource("messages.yml", false);
|
||||
|
||||
File messageFile = new File(plugin.getDataFolder(), "messages.yml");
|
||||
YamlConfiguration messageConfig = YamlConfiguration.loadConfiguration(messageFile);
|
||||
|
||||
InputStreamReader defaultReader = new InputStreamReader(plugin.getResource("messages.yml"), Charsets.UTF_8);
|
||||
YamlConfiguration defaults = YamlConfiguration.loadConfiguration(defaultReader);
|
||||
|
||||
messageConfig.setDefaults(defaults);
|
||||
|
||||
messageConfig.getKeys(false).forEach((key) -> {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
localeMessages.put(key, message);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@Override
|
||||
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests) {
|
||||
return new MojangApiBukkit(logger, addresses, requests);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,101 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
import org.apache.commons.lang.ArrayUtils;
|
||||
|
||||
/**
|
||||
* Represents a client connecting to the server.
|
||||
*
|
||||
* This session is invalid if the player disconnects or the login was successful
|
||||
*/
|
||||
public class BukkitLoginSession extends LoginSession {
|
||||
|
||||
private final String serverId;
|
||||
private final byte[] verifyToken;
|
||||
|
||||
private boolean verified;
|
||||
|
||||
private String encodedSkinData;
|
||||
private String skinSignature;
|
||||
|
||||
public BukkitLoginSession(String username, String serverId, byte[] verifyToken, boolean registered
|
||||
, PlayerProfile profile) {
|
||||
super(username, registered, profile);
|
||||
|
||||
this.serverId = serverId;
|
||||
this.verifyToken = ArrayUtils.clone(verifyToken);
|
||||
}
|
||||
|
||||
//available for bungeecord
|
||||
public BukkitLoginSession(String username, boolean registered) {
|
||||
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, registered, null);
|
||||
}
|
||||
|
||||
//cracked player
|
||||
public BukkitLoginSession(String username, PlayerProfile profile) {
|
||||
this(username, "", ArrayUtils.EMPTY_BYTE_ARRAY, false, profile);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the random generated server id. This makes sure the request sent from the client is just for this server.
|
||||
*
|
||||
* See this for details http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||
*
|
||||
* Empty if it's a BungeeCord connection
|
||||
*
|
||||
* @return random generated server id
|
||||
*/
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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 byte[] getVerifyToken() {
|
||||
return ArrayUtils.clone(verifyToken);
|
||||
}
|
||||
|
||||
public synchronized String getEncodedSkinData() {
|
||||
return encodedSkinData;
|
||||
}
|
||||
|
||||
public synchronized String getSkinSignature() {
|
||||
return skinSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the premium skin property which was retrieved by the session server
|
||||
*
|
||||
* @param encodedData
|
||||
* @param skinSignature
|
||||
*/
|
||||
public synchronized void setSkin(String encodedData, String skinSignature) {
|
||||
this.encodedSkinData = encodedData;
|
||||
this.skinSignature = skinSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets whether the player has a premium (paid account) account and valid session
|
||||
*
|
||||
* @param verified whether the player has valid session
|
||||
*/
|
||||
public synchronized void setVerified(boolean verified) {
|
||||
this.verified = verified;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get whether the player has a premium (paid account) account and valid session
|
||||
*
|
||||
* @return whether the player has a valid session
|
||||
*/
|
||||
public synchronized boolean isVerified() {
|
||||
return verified;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,122 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PrivateKey;
|
||||
import java.security.PublicKey;
|
||||
import java.util.stream.Stream;
|
||||
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
|
||||
/**
|
||||
* Encryption and decryption minecraft util for connection between servers
|
||||
* and paid minecraft account clients.
|
||||
*
|
||||
* Source: https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/MinecraftEncryption.java
|
||||
*
|
||||
* Remapped by: https://github.com/Techcable/MinecraftMappings/tree/master/1.8
|
||||
*/
|
||||
public class EncryptionUtil {
|
||||
|
||||
public static KeyPair generateKeyPair() {
|
||||
try {
|
||||
KeyPairGenerator keyPairGenerator = KeyPairGenerator.getInstance("RSA");
|
||||
|
||||
keyPairGenerator.initialize(1_024);
|
||||
return keyPairGenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
//Should be existing in every vm
|
||||
throw new ExceptionInInitializerError(nosuchalgorithmexception);
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] getServerIdHash(String serverId, PublicKey publicKey, SecretKey secretKey) {
|
||||
return digestOperation("SHA-1"
|
||||
, new byte[][]{serverId.getBytes(Charsets.ISO_8859_1), secretKey.getEncoded(), publicKey.getEncoded()});
|
||||
}
|
||||
|
||||
private static byte[] digestOperation(String algo, byte[]... content) {
|
||||
try {
|
||||
MessageDigest messagedigest = MessageDigest.getInstance(algo);
|
||||
Stream.of(content).forEach(messagedigest::update);
|
||||
|
||||
return messagedigest.digest();
|
||||
} catch (NoSuchAlgorithmException nosuchalgorithmexception) {
|
||||
nosuchalgorithmexception.printStackTrace();
|
||||
return null;
|
||||
}
|
||||
}
|
||||
|
||||
// public static PublicKey decodePublicKey(byte[] encodedKey) {
|
||||
// try {
|
||||
// KeyFactory keyfactory = KeyFactory.getInstance("RSA");
|
||||
//
|
||||
// X509EncodedKeySpec x509encodedkeyspec = new X509EncodedKeySpec(encodedKey);
|
||||
// return keyfactory.generatePublic(x509encodedkeyspec);
|
||||
// } catch (NoSuchAlgorithmException | InvalidKeySpecException nosuchalgorithmexception) {
|
||||
// //ignore
|
||||
// }
|
||||
//
|
||||
// System.err.println("Public key reconstitute failed!");
|
||||
// return null;
|
||||
// }
|
||||
|
||||
public static SecretKey decryptSharedKey(PrivateKey privateKey, byte[] encryptedSharedKey) {
|
||||
return new SecretKeySpec(decryptData(privateKey, encryptedSharedKey), "AES");
|
||||
}
|
||||
|
||||
public static byte[] decryptData(Key key, byte[] data) {
|
||||
return cipherOperation(Cipher.DECRYPT_MODE, key, data);
|
||||
}
|
||||
|
||||
private static byte[] cipherOperation(int operationMode, Key key, byte[] data) {
|
||||
try {
|
||||
return createCipherInstance(operationMode, key.getAlgorithm(), key).doFinal(data);
|
||||
} catch (IllegalBlockSizeException | BadPaddingException illegalblocksizeexception) {
|
||||
illegalblocksizeexception.printStackTrace();
|
||||
}
|
||||
|
||||
System.err.println("Cipher data failed!");
|
||||
return null;
|
||||
}
|
||||
|
||||
private static Cipher createCipherInstance(int operationMode, String cipherName, Key key) {
|
||||
try {
|
||||
Cipher cipher = Cipher.getInstance(cipherName);
|
||||
|
||||
cipher.init(operationMode, key);
|
||||
return cipher;
|
||||
} catch (InvalidKeyException | NoSuchAlgorithmException | NoSuchPaddingException invalidkeyexception) {
|
||||
invalidkeyexception.printStackTrace();
|
||||
}
|
||||
|
||||
System.err.println("Cipher creation failed!");
|
||||
return null;
|
||||
}
|
||||
//
|
||||
// public static Cipher createBufferedBlockCipher(int operationMode, Key key) {
|
||||
// try {
|
||||
// Cipher cipher = Cipher.getInstance("AES/CFB8/NoPadding");
|
||||
//
|
||||
// cipher.init(operationMode, key, new IvParameterSpec(key.getEncoded()));
|
||||
// return cipher;
|
||||
// } catch (GeneralSecurityException generalsecurityexception) {
|
||||
// throw new RuntimeException(generalsecurityexception);
|
||||
// }
|
||||
// }
|
||||
|
||||
private EncryptionUtil() {
|
||||
//utility
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,212 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
|
||||
import com.github.games647.fastlogin.bukkit.commands.ImportCommand;
|
||||
import com.github.games647.fastlogin.bukkit.commands.PremiumCommand;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.bukkit.listener.BukkitJoinListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.BungeeCordListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.EncryptionPacketListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.LoginSkinApplyListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocollib.StartPacketListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.protocolsupport.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.google.common.collect.Iterables;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
/**
|
||||
* This plugin checks if a player has a paid account and if so tries to skip offline mode authentication.
|
||||
*/
|
||||
public class FastLoginBukkit extends JavaPlugin {
|
||||
|
||||
private static final int WORKER_THREADS = 3;
|
||||
|
||||
//provide a immutable key pair to be thread safe | used for encrypting and decrypting traffic
|
||||
private final KeyPair keyPair = EncryptionUtil.generateKeyPair();
|
||||
|
||||
private boolean bungeeCord;
|
||||
private BukkitCore core;
|
||||
private boolean serverStarted;
|
||||
|
||||
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
private final ConcurrentMap<String, BukkitLoginSession> session = FastLoginCore.buildCache(1, -1);
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
saveDefaultConfig();
|
||||
core = new BukkitCore(this);
|
||||
|
||||
core.loadMessages();
|
||||
core.setApiConnector();
|
||||
try {
|
||||
if (ClassUtil.isPresent("org.spigotmc.SpigotConfig")) {
|
||||
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.WARNING, "Cannot check bungeecord support. You use a non-spigot build", ex);
|
||||
}
|
||||
|
||||
if (getServer().getOnlineMode()) {
|
||||
//we need to require offline to prevent a session request for a offline player
|
||||
getLogger().severe("Server have to be in offline mode");
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (bungeeCord) {
|
||||
setServerStarted();
|
||||
|
||||
//check for incoming messages from the bungeecord version of this plugin
|
||||
getServer().getMessenger().registerIncomingPluginChannel(this, getName(), new BungeeCordListener(this));
|
||||
getServer().getMessenger().registerOutgoingPluginChannel(this, getName());
|
||||
//register listeners on success
|
||||
} else {
|
||||
if (!core.setupDatabase()) {
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
if (getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) {
|
||||
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
|
||||
} else if (getServer().getPluginManager().isPluginEnabled("ProtocolLib")) {
|
||||
//we are performing HTTP request on these so run it async (seperate from the Netty IO threads)
|
||||
AsynchronousManager asynchronousManager = ProtocolLibrary.getProtocolManager().getAsynchronousManager();
|
||||
|
||||
StartPacketListener startPacketListener = new StartPacketListener(this);
|
||||
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this);
|
||||
|
||||
asynchronousManager.registerAsyncHandler(startPacketListener).start(WORKER_THREADS);
|
||||
asynchronousManager.registerAsyncHandler(encryptionPacketListener).start(WORKER_THREADS);
|
||||
getServer().getPluginManager().registerEvents(new LoginSkinApplyListener(this), this);
|
||||
} else {
|
||||
getLogger().warning("Either ProtocolLib or ProtocolSupport have to be installed "
|
||||
+ "if you don't use BungeeCord");
|
||||
}
|
||||
}
|
||||
|
||||
//delay dependency setup because we load the plugin very early where plugins are initialized yet
|
||||
getServer().getScheduler().runTask(this, new DelayedAuthHook(this));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new BukkitJoinListener(this), this);
|
||||
|
||||
//register commands using a unique name
|
||||
getCommand("premium").setExecutor(new PremiumCommand(this));
|
||||
getCommand("cracked").setExecutor(new CrackedCommand(this));
|
||||
getCommand("import-auth").setExecutor(new ImportCommand(core));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
session.clear();
|
||||
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
|
||||
//remove old blacklists
|
||||
getServer().getOnlinePlayers().forEach(player -> player.removeMetadata(getName(), this));
|
||||
}
|
||||
|
||||
public BukkitCore getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
public void sendBungeeActivateMessage(CommandSender sender, String target, boolean activate) {
|
||||
if (sender instanceof Player) {
|
||||
notifiyBungeeCord((Player) sender, target, activate);
|
||||
} else {
|
||||
Player firstPlayer = Iterables.getFirst(getServer().getOnlinePlayers(), null);
|
||||
if (firstPlayer == null) {
|
||||
getLogger().info("No player online to send a plugin message to the proxy");
|
||||
return;
|
||||
}
|
||||
|
||||
notifiyBungeeCord(firstPlayer, target, activate);
|
||||
}
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setPasswordGenerator(PasswordGenerator passwordGenerator) {
|
||||
core.setPasswordGenerator(passwordGenerator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets a thread-safe map about players which are connecting to the server are being checked to be premium (paid
|
||||
* account)
|
||||
*
|
||||
* @return a thread-safe session map
|
||||
*/
|
||||
public ConcurrentMap<String, BukkitLoginSession> getSessions() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the server KeyPair. This is used to encrypt or decrypt traffic between the client and server
|
||||
*
|
||||
* @return the server KeyPair
|
||||
*/
|
||||
public KeyPair getServerKey() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the auth plugin hook in order to interact with the plugins. This can be null if no supporting auth plugin
|
||||
* was found.
|
||||
*
|
||||
* @return interface to any supported auth plugin
|
||||
*/
|
||||
@Deprecated
|
||||
public BukkitAuthPlugin getAuthPlugin() {
|
||||
return (BukkitAuthPlugin) core.getAuthPluginHook();
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setAuthPluginHook(BukkitAuthPlugin authPlugin) {
|
||||
core.setAuthPluginHook(authPlugin);
|
||||
}
|
||||
|
||||
public boolean isBungeeCord() {
|
||||
return bungeeCord;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wait before the server is fully started. This is workaround, because connections right on startup are not
|
||||
* injected by ProtocolLib
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean isServerFullyStarted() {
|
||||
return serverStarted;
|
||||
}
|
||||
|
||||
public void setServerStarted() {
|
||||
if (!this.serverStarted) {
|
||||
this.serverStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(Player sender, String target, boolean activate) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
if (activate) {
|
||||
dataOutput.writeUTF("ON");
|
||||
} else {
|
||||
dataOutput.writeUTF("OFF");
|
||||
}
|
||||
|
||||
dataOutput.writeUTF(target);
|
||||
sender.sendPluginMessage(this, getName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
public class MojangApiBukkit extends MojangApiConnector {
|
||||
|
||||
//mojang api check to prove a player is logged in minecraft and made a join server request
|
||||
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
|
||||
|
||||
public MojangApiBukkit(Logger logger, List<String> localAddresses, int rateLimit) {
|
||||
super(logger, localAddresses, rateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinedServer(LoginSession session, String serverId) {
|
||||
BukkitLoginSession playerSession = (BukkitLoginSession) session;
|
||||
try {
|
||||
String url = HAS_JOINED_URL + "username=" + playerSession.getUsername() + "&serverId=" + serverId;
|
||||
HttpURLConnection conn = getConnection(url);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (line != null && !line.equals("null")) {
|
||||
//validate parsing
|
||||
//http://wiki.vg/Protocol_Encryption#Server
|
||||
JSONObject userData = (JSONObject) JSONValue.parseWithException(line);
|
||||
String uuid = (String) userData.get("id");
|
||||
playerSession.setUuid(FastLoginCore.parseId(uuid));
|
||||
|
||||
JSONArray properties = (JSONArray) userData.get("properties");
|
||||
JSONObject skinProperty = (JSONObject) properties.get(0);
|
||||
|
||||
String propertyName = (String) skinProperty.get("name");
|
||||
if (propertyName.equals("textures")) {
|
||||
String skinValue = (String) skinProperty.get("value");
|
||||
String signature = (String) skinProperty.get("signature");
|
||||
playerSession.setSkin(skinValue, signature);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
//catch not only ioexceptions also parse and NPE on unexpected json format
|
||||
logger.log(Level.WARNING, "Failed to verify session", ex);
|
||||
}
|
||||
|
||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getUUIDFromJson(String json) {
|
||||
boolean isArray = json.startsWith("[");
|
||||
|
||||
JSONObject mojangPlayer;
|
||||
if (isArray) {
|
||||
JSONArray array = (JSONArray) JSONValue.parse(json);
|
||||
mojangPlayer = (JSONObject) array.get(0);
|
||||
} else {
|
||||
mojangPlayer = (JSONObject) JSONValue.parse(json);
|
||||
}
|
||||
|
||||
String uuid = (String) mojangPlayer.get("id");
|
||||
if ("null".equals(uuid)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return uuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,14 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
*
|
||||
* @deprecated please use com.github.games647.fastlogin.core.hooks.PasswordGenerator<org.bukkit.entity.Player>
|
||||
*/
|
||||
@Deprecated
|
||||
public interface PasswordGenerator extends com.github.games647.fastlogin.core.hooks.PasswordGenerator<Player> {
|
||||
|
||||
@Override
|
||||
String getRandomPassword(Player player);
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class CrackedCommand implements CommandExecutor {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public CrackedCommand(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player)) {
|
||||
//console or command block
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-console"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
plugin.sendBungeeActivateMessage(sender, sender.getName(), false);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async if
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("remove-premium"));
|
||||
|
||||
profile.setPremium(false);
|
||||
profile.setUuid(null);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage(plugin.getCore().getMessage("not-premium"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
onCrackedOther(sender, command, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onCrackedOther(CommandSender sender, Command command, String[] args) {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
plugin.sendBungeeActivateMessage(sender, args[0], false);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage("Error occured");
|
||||
return;
|
||||
}
|
||||
|
||||
//existing player is already cracked
|
||||
if (profile.getUserId() != -1 && !profile.isPremium()) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("not-premium-other"));
|
||||
} else {
|
||||
sender.sendMessage(plugin.getCore().getMessage("remove-premium"));
|
||||
profile.setPremium(false);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitCore;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
import org.bukkit.ChatColor;
|
||||
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
|
||||
public class ImportCommand implements CommandExecutor {
|
||||
|
||||
private final BukkitCore core;
|
||||
|
||||
public ImportCommand(BukkitCore core) {
|
||||
this.core = core;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length < 2) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You need to specify the import plugin and database type");
|
||||
return true;
|
||||
}
|
||||
|
||||
ImportPlugin importPlugin;
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "autoin":
|
||||
importPlugin = ImportPlugin.AUTO_IN;
|
||||
break;
|
||||
case "bpa":
|
||||
importPlugin = ImportPlugin.BPA;
|
||||
break;
|
||||
case "eldzi":
|
||||
importPlugin = ImportPlugin.ELDZI;
|
||||
break;
|
||||
default:
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Unknown auto login plugin");
|
||||
return true;
|
||||
}
|
||||
|
||||
boolean sqlite;
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "sqlite":
|
||||
sqlite = true;
|
||||
break;
|
||||
case "mysql":
|
||||
sqlite = false;
|
||||
break;
|
||||
default:
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Unknown storage type to import from. Either SQLite or MySQL");
|
||||
return true;
|
||||
}
|
||||
|
||||
String host = "";
|
||||
String database = "";
|
||||
String username = "";
|
||||
String password = "";
|
||||
if (!sqlite) {
|
||||
if (args.length <= 5) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "If importing from MySQL, you need to specify host database "
|
||||
+ "and username passowrd too");
|
||||
return true;
|
||||
}
|
||||
|
||||
host = args[2];
|
||||
database = args[3];
|
||||
username = args[4];
|
||||
password = args[5];
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
boolean success = core.importDatabase(importPlugin, true, storage, host, database, username, password);
|
||||
if (success) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Successful imported the data");
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Failed to import the data. Check out the logs");
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,108 @@
|
||||
package com.github.games647.fastlogin.bukkit.commands;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Let users activate fast login by command. This only be accessible if
|
||||
* the user has access to it's account. So we can make sure that not another
|
||||
* person with a paid account and the same username can steal his account.
|
||||
*/
|
||||
public class PremiumCommand implements CommandExecutor {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public PremiumCommand(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
|
||||
if (args.length == 0) {
|
||||
if (!(sender instanceof Player)) {
|
||||
//console or command block
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-console"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
plugin.sendBungeeActivateMessage(sender, sender.getName(), true);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
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 true;
|
||||
}
|
||||
|
||||
plugin.getCore().getPendingConfirms().remove(id);
|
||||
//todo: load async
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("already-exists"));
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
|
||||
sender.sendMessage(plugin.getCore().getMessage("add-premium"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
onPremiumOther(sender, command, args);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void onPremiumOther(CommandSender sender, Command command, String[] args) {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return ;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
plugin.sendBungeeActivateMessage(sender, args[0], true);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("already-exists-other"));
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
});
|
||||
|
||||
sender.sendMessage(plugin.getCore().getMessage("add-premium-other"));
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,61 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import fr.xephi.authme.api.API;
|
||||
import fr.xephi.authme.api.NewAPI;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/Xephi/AuthMeReloaded/
|
||||
* Project page:
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/authme-reloaded/
|
||||
* Spigot: https://www.spigotmc.org/resources/authme-reloaded.6269/
|
||||
*/
|
||||
public class AuthMeHook implements AuthPlugin<Player> {
|
||||
|
||||
private final boolean isNewAPIAvailable;
|
||||
|
||||
public AuthMeHook() {
|
||||
this.isNewAPIAvailable = ClassUtil.isPresent("fr.xephi.authme.api.NewAPI");
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean forceLogin(Player player) {
|
||||
//skips registration and login
|
||||
if (isNewAPIAvailable) {
|
||||
NewAPI.getInstance().forceLogin(player);
|
||||
} else {
|
||||
API.forceLogin(player);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
if (isNewAPIAvailable) {
|
||||
return NewAPI.getInstance().isRegistered(playerName);
|
||||
} else {
|
||||
return API.isRegistered(playerName);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
if (isNewAPIAvailable) {
|
||||
NewAPI.getInstance().forceRegister(player, password);
|
||||
} else {
|
||||
API.registerPlayer(player.getName(), password);
|
||||
forceLogin(player);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* @deprecated please use com.github.games647.fastlogin.core.hooks.AuthPlugin<org.bukkit.entity.Player>
|
||||
*/
|
||||
@Deprecated
|
||||
public interface BukkitAuthPlugin extends AuthPlugin<Player> {
|
||||
|
||||
@Override
|
||||
boolean forceLogin(Player player);
|
||||
|
||||
@Override
|
||||
boolean isRegistered(String playerName) throws Exception;
|
||||
|
||||
@Override
|
||||
boolean forceRegister(Player player, String password);
|
||||
}
|
||||
@@ -0,0 +1,115 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import de.st_ddt.crazylogin.CrazyLogin;
|
||||
import de.st_ddt.crazylogin.data.LoginPlayerData;
|
||||
import de.st_ddt.crazylogin.databases.CrazyLoginDataDatabase;
|
||||
import de.st_ddt.crazylogin.listener.PlayerListener;
|
||||
import de.st_ddt.crazylogin.metadata.Authenticated;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.apache.commons.lang.reflect.FieldUtils;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/ST-DDT/CrazyLogin
|
||||
*
|
||||
* Project page:
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/server-mods/crazylogin/
|
||||
*/
|
||||
public class CrazyLoginHook implements AuthPlugin<Player> {
|
||||
|
||||
private final CrazyLogin crazyLoginPlugin = CrazyLogin.getPlugin();
|
||||
private final PlayerListener playerListener = getListener();
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe operation
|
||||
Future<LoginPlayerData> future = Bukkit.getScheduler().callSyncMethod(crazyLoginPlugin, () -> {
|
||||
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player);
|
||||
if (playerData != null) {
|
||||
//mark the account as logged in
|
||||
playerData.setLoggedIn(true);
|
||||
|
||||
String ip = player.getAddress().getAddress().getHostAddress();
|
||||
//this should be done after login to restore the inventory, unhide players, prevent potential memory leaks...
|
||||
//from: https://github.com/ST-DDT/CrazyLogin/blob/master/src/main/java/de/st_ddt/crazylogin/CrazyLogin.java#L1948
|
||||
playerData.resetLoginFails();
|
||||
player.setFireTicks(0);
|
||||
|
||||
if (playerListener != null) {
|
||||
playerListener.removeMovementBlocker(player);
|
||||
playerListener.disableHidenInventory(player);
|
||||
playerListener.disableSaveLogin(player);
|
||||
playerListener.unhidePlayer(player);
|
||||
}
|
||||
|
||||
//loginFailuresPerIP.remove(IP);
|
||||
//illegalCommandUsesPerIP.remove(IP);
|
||||
//tempBans.remove(IP);
|
||||
playerData.addIP(ip);
|
||||
player.setMetadata("Authenticated", new Authenticated(crazyLoginPlugin, player));
|
||||
crazyLoginPlugin.unregisterDynamicHooks();
|
||||
return playerData;
|
||||
}
|
||||
|
||||
return null;
|
||||
});
|
||||
|
||||
try {
|
||||
LoginPlayerData result = future.get();
|
||||
if (result != null && result.isLoggedIn()) {
|
||||
//SQL-Queries should run async
|
||||
crazyLoginPlugin.getCrazyDatabase().saveWithoutPassword(result);
|
||||
return true;
|
||||
}
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
crazyLoginPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
return crazyLoginPlugin.getPlayerData(playerName) != null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
CrazyLoginDataDatabase crazyDatabase = crazyLoginPlugin.getCrazyDatabase();
|
||||
|
||||
//this executes a sql query and accesses only thread safe collections so we can run it async
|
||||
LoginPlayerData playerData = crazyLoginPlugin.getPlayerData(player.getName());
|
||||
if (playerData == null) {
|
||||
//create a fake account - this will be saved to the database with the password=FAILEDLOADING
|
||||
//user cannot login with that password unless the admin uses plain text
|
||||
//this automatically marks the player as logged in
|
||||
playerData = new LoginPlayerData(player);
|
||||
crazyDatabase.save(playerData);
|
||||
|
||||
return forceLogin(player);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private PlayerListener getListener() {
|
||||
PlayerListener listener;
|
||||
try {
|
||||
listener = (PlayerListener) FieldUtils.readField(crazyLoginPlugin, "playerListener", true);
|
||||
} catch (IllegalAccessException ex) {
|
||||
crazyLoginPlugin.getLogger().log(Level.SEVERE, "Failed to get the listener instance for auto login", ex);
|
||||
listener = null;
|
||||
}
|
||||
|
||||
return listener;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,38 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import io.github.lucaseasedup.logit.CancelledState;
|
||||
import io.github.lucaseasedup.logit.LogItCore;
|
||||
import io.github.lucaseasedup.logit.account.Account;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/XziomekX/LogIt
|
||||
* Project page:
|
||||
*
|
||||
* Bukkit: Unknown
|
||||
* Spigot: Unknown
|
||||
*/
|
||||
public class LogItHook implements AuthPlugin<Player> {
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
return LogItCore.getInstance().getSessionManager().startSession(player) == CancelledState.NOT_CANCELLED;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
return LogItCore.getInstance().getAccountManager().isRegistered(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
Account account = new Account(player.getName());
|
||||
account.changePassword(password);
|
||||
account.setLastActiveDate(System.currentTimeMillis() / 1000);
|
||||
account.setRegistrationDate(System.currentTimeMillis() / 1000);
|
||||
return LogItCore.getInstance().getAccountManager().insertAccount(account) == CancelledState.NOT_CANCELLED;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.lenis0012.bukkit.loginsecurity.LoginSecurity;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.AuthService;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.PlayerSession;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.action.LoginAction;
|
||||
import com.lenis0012.bukkit.loginsecurity.session.action.RegisterAction;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/lenis0012/LoginSecurity-2 Project page:
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/loginsecurity/
|
||||
* Spigot: https://www.spigotmc.org/resources/loginsecurity.19362/
|
||||
*/
|
||||
public class LoginSecurityHook implements AuthPlugin<Player> {
|
||||
|
||||
private final FastLoginBukkit plugin = (FastLoginBukkit) Bukkit.getPluginManager().getPlugin("FastLogin");
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
return session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getOfflineSession(playerName);
|
||||
return session.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
return session.performAction(new RegisterAction(AuthService.PLUGIN, plugin, password)).isSuccess();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,62 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.royaldev.royalauth.AuthPlayer;
|
||||
import org.royaldev.royalauth.Config;
|
||||
import org.royaldev.royalauth.RoyalAuth;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/RoyalDev/RoyalAuth
|
||||
*
|
||||
* Project page:
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/royalauth/
|
||||
*/
|
||||
public class RoyalAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final RoyalAuth royalAuthPlugin = (RoyalAuth) Bukkit.getPluginManager().getPlugin("RoyalAuth");
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(player);
|
||||
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(royalAuthPlugin, () -> {
|
||||
//https://github.com/RoyalDev/RoyalAuth/blob/master/src/main/java/org/royaldev/royalauth/commands/CmdLogin.java#L62
|
||||
//not thread-safe
|
||||
authPlayer.login();
|
||||
|
||||
return authPlayer.isLoggedIn();
|
||||
});
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
royalAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(playerName);
|
||||
return authPlayer.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
//https://github.com/RoyalDev/RoyalAuth/blob/master/src/main/java/org/royaldev/royalauth/commands/CmdRegister.java#L50
|
||||
AuthPlayer authPlayer = AuthPlayer.getAuthPlayer(player);
|
||||
|
||||
boolean registerSuccess = authPlayer.setPassword(password, Config.passwordHashType);
|
||||
|
||||
//login in the player after registration
|
||||
return registerSuccess && forceLogin(player);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.plugin.Plugin;
|
||||
|
||||
import ultraauth.api.UltraAuthAPI;
|
||||
import ultraauth.main.Main;
|
||||
import ultraauth.managers.PlayerManager;
|
||||
|
||||
/**
|
||||
* Project page:
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/ultraauth-aa/
|
||||
* Spigot: https://www.spigotmc.org/resources/ultraauth.17044/
|
||||
*/
|
||||
public class UltraAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final Plugin ultraAuthPlugin = Main.main;
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(ultraAuthPlugin, () -> {
|
||||
UltraAuthAPI.authenticatedPlayer(player);
|
||||
return UltraAuthAPI.isAuthenticated(player);
|
||||
});
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
ultraAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
return UltraAuthAPI.isRegisterd(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
UltraAuthAPI.setPlayerPasswordOnline(player, password);
|
||||
if (PlayerManager.getInstance().checkPlayerPassword(player, password)) {
|
||||
//the register method silents any excpetion so check if our entry was saved
|
||||
return forceLogin(player);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,83 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import de.luricos.bukkit.xAuth.xAuth;
|
||||
import de.luricos.bukkit.xAuth.xAuthPlayer;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/LycanDevelopment/xAuth/
|
||||
*
|
||||
* Project page:
|
||||
*
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/xauth/
|
||||
*/
|
||||
public class xAuthHook implements AuthPlugin<Player> {
|
||||
|
||||
private final xAuth xAuthPlugin = xAuth.getPlugin();
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(Player player) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, () -> {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
if (xAuthPlayer != null) {
|
||||
//we checked that the player is premium (paid account)
|
||||
xAuthPlayer.setPremium(true);
|
||||
|
||||
//unprotect the inventory, op status...
|
||||
return xAuthPlugin.getPlayerManager().doLogin(xAuthPlayer);
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
try {
|
||||
return future.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
xAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
//this will load the player if it's not in the cache
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(playerName);
|
||||
return xAuthPlayer != null && xAuthPlayer.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, final String password) {
|
||||
//not thread-safe
|
||||
Future<Boolean> future = Bukkit.getScheduler().callSyncMethod(xAuthPlugin, () -> {
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
if (xAuthPlayer != null) {
|
||||
//this should run async because the plugin executes a sql query, but the method
|
||||
//accesses non thread-safe collections :(
|
||||
boolean registerSuccess = xAuthPlugin.getAuthClass(xAuthPlayer)
|
||||
.adminRegister(player.getName(), password, null);
|
||||
|
||||
return registerSuccess;
|
||||
}
|
||||
|
||||
return false;
|
||||
});
|
||||
|
||||
try {
|
||||
//login in the player after registration
|
||||
return future.get() && forceLogin(player);
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
xAuthPlugin.getLogger().log(Level.SEVERE, "Failed to forceLogin", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.tasks.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;
|
||||
|
||||
/**
|
||||
* This listener tells authentication plugins if the player has a premium account and we checked it successfully. So the
|
||||
* plugin can skip authentication.
|
||||
*/
|
||||
public class BukkitJoinListener implements Listener {
|
||||
|
||||
private static final long DELAY_LOGIN = 20L / 2;
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public BukkitJoinListener(FastLoginBukkit plugin) {
|
||||
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();
|
||||
|
||||
if (!plugin.isBungeeCord()) {
|
||||
//Wait before auth plugin and we received a message from BungeeCord initializes the player
|
||||
Bukkit.getScheduler().runTaskLaterAsynchronously(plugin, new ForceLoginTask(plugin, player), DELAY_LOGIN);
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPlayerQuit(PlayerQuitEvent quitEvent) {
|
||||
Player player = quitEvent.getPlayer();
|
||||
player.removeMetadata(plugin.getName(), plugin);
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,120 @@
|
||||
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.core.hooks.AuthPlugin;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.nio.file.Files;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.metadata.FixedMetadataValue;
|
||||
import org.bukkit.plugin.messaging.PluginMessageListener;
|
||||
|
||||
/**
|
||||
* Responsible for receiving messages from a BungeeCord instance.
|
||||
*
|
||||
* This class also receives the plugin message from the bungeecord version of this plugin in order to get notified if
|
||||
* the connection is in online mode.
|
||||
*/
|
||||
public class BungeeCordListener implements PluginMessageListener {
|
||||
|
||||
private static final String FILE_NAME = "proxy-whitelist.txt";
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
//null if whitelist is empty so bungeecord support is disabled
|
||||
private final Set<UUID> proxyIds;
|
||||
|
||||
public BungeeCordListener(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
this.proxyIds = loadBungeeCordIds();
|
||||
}
|
||||
|
||||
@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();
|
||||
plugin.getLogger().log(Level.FINEST, "Received plugin message for subchannel {0} from {1}"
|
||||
, new Object[]{subchannel, player});
|
||||
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
//check if the player is still online or disconnected
|
||||
Player checkedPlayer = plugin.getServer().getPlayerExact(playerName);
|
||||
//fail if target player is blacklisted because already authed or wrong bungeecord id
|
||||
if (checkedPlayer != null && !checkedPlayer.hasMetadata(plugin.getName())) {
|
||||
//blacklist this target player for BungeeCord Id brute force attacks
|
||||
player.setMetadata(plugin.getName(), new FixedMetadataValue(plugin, true));
|
||||
|
||||
//bungeecord UUID
|
||||
long mostSignificantBits = dataInput.readLong();
|
||||
long leastSignificantBits = dataInput.readLong();
|
||||
UUID sourceId = new UUID(mostSignificantBits, leastSignificantBits);
|
||||
plugin.getLogger().log(Level.FINEST, "Received proxy id {0} from {1}", new Object[]{sourceId, player});
|
||||
|
||||
//fail if BungeeCord support is disabled (id = null)
|
||||
if (proxyIds.contains(sourceId)) {
|
||||
readMessage(checkedPlayer, subchannel, playerName, player);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void readMessage(Player checkedPlayer, String subchannel, String playerName, Player player) {
|
||||
InetSocketAddress address = checkedPlayer.getAddress();
|
||||
String id = '/' + address.getAddress().getHostAddress() + ':' + address.getPort();
|
||||
if ("AUTO_LOGIN".equalsIgnoreCase(subchannel)) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, true);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getSessions().put(id, playerSession);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new ForceLoginTask(plugin, player));
|
||||
} else if ("AUTO_REGISTER".equalsIgnoreCase(subchannel)) {
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, () -> {
|
||||
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
|
||||
try {
|
||||
//we need to check if the player is registered on Bukkit too
|
||||
if (authPlugin == null || !authPlugin.isRegistered(playerName)) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(playerName, false);
|
||||
playerSession.setVerified(true);
|
||||
plugin.getSessions().put(id, playerSession);
|
||||
new ForceLoginTask(plugin, player).run();
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
public Set<UUID> loadBungeeCordIds() {
|
||||
File whitelistFile = new File(plugin.getDataFolder(), FILE_NAME);
|
||||
try {
|
||||
if (!whitelistFile.exists()) {
|
||||
whitelistFile.createNewFile();
|
||||
}
|
||||
|
||||
List<String> lines = Files.readAllLines(whitelistFile.toPath());
|
||||
return lines.stream().map(String::trim).map(UUID::fromString).collect(Collectors.toSet());
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to create file for Proxy whitelist", ex);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to retrieve proxy Id. Disabling BungeeCord support", ex);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,65 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
|
||||
/**
|
||||
* Handles incoming encryption responses from connecting clients.
|
||||
* It prevents them from reaching the server because that cannot handle
|
||||
* it in offline mode.
|
||||
*
|
||||
* Moreover this manages a started premium check from
|
||||
* this plugin. So check if all data is correct and we can prove him as a
|
||||
* owner of a paid minecraft account.
|
||||
*
|
||||
* Receiving packet information:
|
||||
* http://wiki.vg/Protocol#Encryption_Response
|
||||
*
|
||||
* sharedSecret=encrypted byte array
|
||||
* verify token=encrypted byte array
|
||||
*/
|
||||
public class EncryptionPacketListener extends PacketAdapter {
|
||||
|
||||
//hides the inherit Plugin plugin field, but we need this type
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public EncryptionPacketListener(FastLoginBukkit plugin) {
|
||||
//run async in order to not block the server, because we make api calls to Mojang
|
||||
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*
|
||||
* On offline logins is Login Start followed by Login Success
|
||||
*
|
||||
* Minecraft Server implementation
|
||||
* https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L180
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
if (packetEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player sender = packetEvent.getPlayer();
|
||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
VerifyResponseTask verifyTask = new VerifyResponseTask(plugin, packetEvent, sender, sharedSecret);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, verifyTask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.util.logging.Level;
|
||||
|
||||
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 LoginSkinApplyListener 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 LoginSkinApplyListener(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 InetSocketAddres, so not unique enough
|
||||
for (BukkitLoginSession session : plugin.getSessions().values()) {
|
||||
if (session.getUsername().equals(player.getName())) {
|
||||
String signature = session.getSkinSignature();
|
||||
String skinData = session.getEncodedSkinData();
|
||||
|
||||
applySkin(player, skinData, signature);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySkin(Player player, String skinData, String signature) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
if (skinData != null && signature != null) {
|
||||
WrappedSignedProperty skin = WrappedSignedProperty.fromValues("textures", skinData, signature);
|
||||
try {
|
||||
gameProfile.getProperties().put("textures", skin);
|
||||
} catch (ClassCastException castException) {
|
||||
Object map = GET_PROPERTIES.invoke(gameProfile.getHandle());
|
||||
try {
|
||||
MethodUtils.invokeMethod(map, "put", new Object[]{"textures", skin.getHandle()});
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error setting premium skin", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,74 @@
|
||||
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.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class NameCheckTask extends JoinManagement<Player, ProtocolLibLoginSource> implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private final Player player;
|
||||
private final String username;
|
||||
|
||||
public NameCheckTask(FastLoginBukkit plugin, PacketEvent packetEvent, Random random, Player player, String username) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.random = random;
|
||||
this.player = player;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
super.onLogin(username, new ProtocolLibLoginSource(plugin, packetEvent, player, random));
|
||||
} finally {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
//minecraft server implementation
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLibLoginSource source, PlayerProfile profile, String username, boolean registered) {
|
||||
try {
|
||||
source.setOnlineMode();
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to cracked login", ex);
|
||||
return;
|
||||
}
|
||||
|
||||
String ip = player.getAddress().getAddress().getHostAddress();
|
||||
core.getPendingLogins().put(ip + username, new Object());
|
||||
|
||||
String serverId = source.getServerId();
|
||||
byte[] verify = source.getVerifyToken();
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId, verify, registered, profile);
|
||||
plugin.getSessions().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, PlayerProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(player.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,99 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
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.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.InetSocketAddress;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ProtocolLibLoginSource implements LoginSource {
|
||||
|
||||
private static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
private final PacketEvent packetEvent;
|
||||
private final Player player;
|
||||
|
||||
private final Random random;
|
||||
|
||||
private String serverId;
|
||||
private final byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
||||
|
||||
public ProtocolLibLoginSource(FastLoginBukkit plugin, PacketEvent packetEvent, Player player, Random random) {
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.player = player;
|
||||
this.random = random;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() throws Exception {
|
||||
//randomized server id to make sure the request is for our server
|
||||
//this could be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||
serverId = Long.toString(random.nextLong(), 16);
|
||||
|
||||
//generate a random token which should be the same when we receive it from the client
|
||||
random.nextBytes(verifyToken);
|
||||
|
||||
sentEncryptionRequest();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) throws Exception {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.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();
|
||||
}
|
||||
|
||||
private void sentEncryptionRequest() throws InvocationTargetException {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
/**
|
||||
* Packet Information: http://wiki.vg/Protocol#Encryption_Request
|
||||
*
|
||||
* ServerID="" (String) key=public server key verifyToken=random 4 byte array
|
||||
*/
|
||||
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN);
|
||||
|
||||
newPacket.getStrings().write(0, serverId);
|
||||
newPacket.getSpecificModifier(PublicKey.class).write(0, plugin.getServerKey().getPublic());
|
||||
|
||||
newPacket.getByteArrays().write(0, verifyToken);
|
||||
|
||||
//serverId is a empty string
|
||||
protocolManager.sendServerPacket(player, newPacket);
|
||||
}
|
||||
|
||||
public String getServerId() {
|
||||
return serverId;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,77 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
/**
|
||||
* Handles incoming start packets from connecting clients. It
|
||||
* checks if we can start checking if the player is premium and
|
||||
* start a request to the client that it should start online mode
|
||||
* login.
|
||||
*
|
||||
* Receiving packet information:
|
||||
* http://wiki.vg/Protocol#Login_Start
|
||||
*
|
||||
* String=Username
|
||||
*/
|
||||
public class StartPacketListener extends PacketAdapter {
|
||||
|
||||
//hides the inherit Plugin plugin field, but we need a more detailed type than just Plugin
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
//just create a new once on plugin enable. This used for verify token generation
|
||||
private final Random random = new Random();
|
||||
|
||||
public StartPacketListener(FastLoginBukkit plugin) {
|
||||
//run async in order to not block the server, because we are making api calls to Mojang
|
||||
super(params(plugin, PacketType.Login.Client.START).optionAsync());
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
/**
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*
|
||||
* On offline logins is Login Start followed by Login Success
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
if (packetEvent.isCancelled()
|
||||
|| plugin.getCore().getAuthPluginHook()== null || !plugin.isServerFullyStarted()) {
|
||||
return;
|
||||
}
|
||||
|
||||
Player player = packetEvent.getPlayer();
|
||||
|
||||
//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.getSessions().remove(sessionKey);
|
||||
|
||||
//player.getName() won't work at this state
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} with {1} connecting", new Object[]{sessionKey, username});
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
NameCheckTask nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,206 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
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.injector.netty.Injector;
|
||||
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.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.EncryptionUtil;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.lang.reflect.Method;
|
||||
import java.math.BigInteger;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class VerifyResponseTask implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
|
||||
private final Player fromPlayer;
|
||||
|
||||
private final byte[] sharedSecret;
|
||||
|
||||
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player fromPlayer, byte[] sharedSecret) {
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.fromPlayer = fromPlayer;
|
||||
this.sharedSecret = sharedSecret;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
BukkitLoginSession session = plugin.getSessions().get(fromPlayer.getAddress().toString());
|
||||
if (session == null) {
|
||||
disconnect(plugin.getCore().getMessage("invalid-requst"), true
|
||||
, "Player {0} tried to send encryption response at invalid state", fromPlayer.getAddress());
|
||||
} else {
|
||||
String ip = fromPlayer.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().remove(ip + session.getUsername());
|
||||
|
||||
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 = plugin.getServerKey().getPrivate();
|
||||
|
||||
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
|
||||
if (!checkVerifyToken(session, privateKey) || !encryptConnection(loginKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
//this makes sure the request from the client is for us
|
||||
//this might be relevant http://www.sk89q.com/2011/09/minecraft-name-spoofing-exploit/
|
||||
String generatedId = session.getServerId();
|
||||
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L193
|
||||
//generate the server id based on client and server data
|
||||
byte[] serverIdHash = EncryptionUtil.getServerIdHash(generatedId, plugin.getServerKey().getPublic(), loginKey);
|
||||
String serverId = (new BigInteger(serverIdHash)).toString(16);
|
||||
|
||||
String username = session.getUsername();
|
||||
if (plugin.getCore().getApiConnector().hasJoinedServer(session, serverId)) {
|
||||
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
||||
|
||||
session.setVerified(true);
|
||||
setPremiumUUID(session.getUuid());
|
||||
receiveFakeStartPacket(username);
|
||||
} else {
|
||||
//user tried to fake a authentication
|
||||
disconnect(plugin.getCore().getMessage("invalid-session"), true
|
||||
, "Player {0} ({1}) tried to log in with an invalid session ServerId: {2}"
|
||||
, session.getUsername(), fromPlayer.getAddress(), serverId);
|
||||
}
|
||||
}
|
||||
|
||||
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.getLogger().log(Level.SEVERE, "Error setting premium uuid", exc);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey) {
|
||||
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.decryptData(privateKey, responseVerify))) {
|
||||
//check if the verify token are equal to the server sent one
|
||||
disconnect(plugin.getCore().getMessage("invalid-verify-token"), true
|
||||
, "Player {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, NoSuchFieldException {
|
||||
Object injectorContainer = TemporaryPlayerFactory.getInjectorFromPlayer(fromPlayer);
|
||||
|
||||
//ChannelInjector
|
||||
Injector rawInjector = FuzzyReflection.getFieldValue(injectorContainer, Injector.class, 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) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Couldn't enable encryption", ex);
|
||||
disconnect(plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption");
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void disconnect(String kickReason, boolean debug, String logMessage, Object... arguments) {
|
||||
if (debug) {
|
||||
plugin.getLogger().log(Level.FINE, logMessage, arguments);
|
||||
} else {
|
||||
plugin.getLogger().log(Level.SEVERE, logMessage, arguments);
|
||||
}
|
||||
|
||||
kickPlayer(packetEvent.getPlayer(), kickReason);
|
||||
}
|
||||
|
||||
private void kickPlayer(Player player, String reason) {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
PacketContainer kickPacket = protocolManager.createPacket(PacketType.Login.Server.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
|
||||
protocolManager.sendServerPacket(player, kickPacket);
|
||||
//tell the server that we want to close the connection
|
||||
player.kickPlayer("Disconnect");
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error sending kickpacket", ex);
|
||||
}
|
||||
}
|
||||
|
||||
//fake a new login packet in order to let the server handle all the other stuff
|
||||
private void receiveFakeStartPacket(String username) {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
//see StartPacketListener for packet information
|
||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.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
|
||||
protocolManager.recieveClientPacket(fromPlayer, startPacket, false);
|
||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to fake a new start packet", ex);
|
||||
//cancel the event in order to prevent the server receiving an invalid packet
|
||||
kickPlayer(fromPlayer, plugin.getCore().getMessage("error-kick"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,35 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
|
||||
public class ProtocolLoginSource implements LoginSource {
|
||||
|
||||
private final PlayerLoginStartEvent loginStartEvent;
|
||||
|
||||
public ProtocolLoginSource(PlayerLoginStartEvent loginStartEvent) {
|
||||
this.loginStartEvent = loginStartEvent;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() {
|
||||
loginStartEvent.setOnlineMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
loginStartEvent.denyLogin(message);
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return loginStartEvent.getAddress();
|
||||
}
|
||||
|
||||
public PlayerLoginStartEvent getLoginStartEvent() {
|
||||
return loginStartEvent;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
|
||||
import protocolsupport.api.events.PlayerLoginStartEvent;
|
||||
import protocolsupport.api.events.PlayerPropertiesResolveEvent;
|
||||
|
||||
public class ProtocolSupportListener extends JoinManagement<Player, ProtocolLoginSource> implements Listener {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public ProtocolSupportListener(FastLoginBukkit plugin) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLoginStart(PlayerLoginStartEvent loginStartEvent) {
|
||||
plugin.setServerStarted();
|
||||
if (loginStartEvent.isLoginDenied() || plugin.getCore().getAuthPluginHook() == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
String username = loginStartEvent.getName();
|
||||
InetSocketAddress address = loginStartEvent.getAddress();
|
||||
|
||||
//remove old data every time on a new login in order to keep the session only for one person
|
||||
plugin.getSessions().remove(address.toString());
|
||||
|
||||
super.onLogin(username, new ProtocolLoginSource(loginStartEvent));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPropertiesResolve(PlayerPropertiesResolveEvent propertiesResolveEvent) {
|
||||
InetSocketAddress address = propertiesResolveEvent.getAddress();
|
||||
BukkitLoginSession session = plugin.getSessions().get(address.toString());
|
||||
|
||||
//skin was resolved -> premium player
|
||||
if (propertiesResolveEvent.hasProperty("textures") && session != null) {
|
||||
String ip = address.getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().remove(ip + session.getUsername());
|
||||
|
||||
session.setVerified(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPremiumLogin(ProtocolLoginSource source, PlayerProfile profile, String username, boolean registered) {
|
||||
source.setOnlineMode();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().put(ip + username, new Object());
|
||||
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, null, null, registered, profile);
|
||||
plugin.getSessions().put(source.getAddress().toString(), playerSession);
|
||||
if (plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
source.getLoginStartEvent().setUseOnlineModeUUID(true);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(ProtocolLoginSource source, PlayerProfile profile, String username) {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(source.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,75 @@
|
||||
package com.github.games647.fastlogin.bukkit.tasks;
|
||||
|
||||
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.core.hooks.AuthPlugin;
|
||||
import com.google.common.collect.Lists;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class DelayedAuthHook implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
|
||||
public DelayedAuthHook(FastLoginBukkit plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
boolean hookFound = registerHooks();
|
||||
if (plugin.isBungeeCord()) {
|
||||
plugin.getLogger().info("BungeeCord setting detected. No auth plugin is required");
|
||||
} else if (!hookFound) {
|
||||
plugin.getLogger().warning("No auth plugin were found by this plugin "
|
||||
+ "(other plugins could hook into this after the intialization of this plugin)"
|
||||
+ "and bungeecord is deactivated. "
|
||||
+ "Either one or both of the checks have to pass in order to use this plugin");
|
||||
}
|
||||
}
|
||||
|
||||
private boolean registerHooks() {
|
||||
AuthPlugin<Player> authPluginHook = null;
|
||||
try {
|
||||
@SuppressWarnings("unchecked")
|
||||
List<Class<? extends AuthPlugin<Player>>> supportedHooks = Lists.newArrayList(AuthMeHook.class
|
||||
, CrazyLoginHook.class, LogItHook.class, LoginSecurityHook.class, UltraAuthHook.class
|
||||
, xAuthHook.class);
|
||||
|
||||
for (Class<? extends AuthPlugin<Player>> clazz : supportedHooks) {
|
||||
String pluginName = clazz.getSimpleName().replace("Hook", "");
|
||||
//uses only member classes which uses AuthPlugin interface (skip interfaces)
|
||||
if (Bukkit.getServer().getPluginManager().getPlugin(pluginName) != null) {
|
||||
//check only for enabled plugins. A single plugin could be disabled by plugin managers
|
||||
authPluginHook = clazz.newInstance();
|
||||
plugin.getLogger().log(Level.INFO, "Hooking into auth plugin: {0}", pluginName);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} catch (InstantiationException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Couldn't load the integration class", ex);
|
||||
}
|
||||
|
||||
if (authPluginHook == null) {
|
||||
//run this check for exceptions (errors) and not found plugins
|
||||
plugin.getLogger().warning("No support offline Auth plugin found. ");
|
||||
return false;
|
||||
}
|
||||
|
||||
if (plugin.getCore().getAuthPluginHook() == null) {
|
||||
plugin.getCore().setAuthPluginHook(authPluginHook);
|
||||
plugin.setServerStarted();
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,133 @@
|
||||
package com.github.games647.fastlogin.bukkit.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.concurrent.ExecutionException;
|
||||
import java.util.concurrent.Future;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class ForceLoginTask implements Runnable {
|
||||
|
||||
private final FastLoginBukkit plugin;
|
||||
private final Player player;
|
||||
|
||||
public ForceLoginTask(FastLoginBukkit plugin, Player player) {
|
||||
this.plugin = plugin;
|
||||
this.player = player;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (!isOnlineThreadSafe()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//remove the bungeecord identifier if there is ones
|
||||
String id = '/' + player.getAddress().getAddress().getHostAddress() + ':' + player.getAddress().getPort();
|
||||
BukkitLoginSession session = plugin.getSessions().remove(id);
|
||||
if (session == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
AuthStorage storage = plugin.getCore().getStorage();
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
|
||||
//check if it's the same player as we checked before
|
||||
if (session.isVerified() && player.getName().equals(session.getUsername())) {
|
||||
//premium player
|
||||
AuthPlugin<Player> authPlugin = plugin.getCore().getAuthPluginHook();
|
||||
if (authPlugin == null) {
|
||||
//maybe only bungeecord plugin
|
||||
sendSuccessNotification();
|
||||
} else {
|
||||
boolean success = false;
|
||||
if (isOnlineThreadSafe()) {
|
||||
if (plugin.getConfig().getBoolean("autoLogin")) {
|
||||
if (session.needsRegistration()) {
|
||||
success = forceRegister(authPlugin, player);
|
||||
} else {
|
||||
success = forceLogin(authPlugin, player);
|
||||
}
|
||||
} else {
|
||||
success = true;
|
||||
}
|
||||
}
|
||||
|
||||
if (success) {
|
||||
//update only on success to prevent corrupt data
|
||||
if (playerProfile != null) {
|
||||
playerProfile.setUuid(session.getUuid());
|
||||
//save cracked players too
|
||||
playerProfile.setPremium(true);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
|
||||
sendSuccessNotification();
|
||||
}
|
||||
}
|
||||
} else {
|
||||
//cracked player
|
||||
if (playerProfile != null) {
|
||||
playerProfile.setUuid(null);
|
||||
playerProfile.setPremium(false);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean forceRegister(AuthPlugin<Player> authPlugin, Player player) {
|
||||
plugin.getLogger().log(Level.FINE, "Register player {0}", player.getName());
|
||||
|
||||
String generatedPassword = plugin.getCore().getPasswordGenerator().getRandomPassword(player);
|
||||
boolean success = authPlugin.forceRegister(player, generatedPassword);
|
||||
String message = plugin.getCore().getMessage("auto-register");
|
||||
if (success && message != null) {
|
||||
message = message.replace("%password", generatedPassword);
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private boolean forceLogin(AuthPlugin<Player> authPlugin, Player player) {
|
||||
plugin.getLogger().log(Level.FINE, "Logging player {0} in", player.getName());
|
||||
boolean success = authPlugin.forceLogin(player);
|
||||
|
||||
String message = plugin.getCore().getMessage("auto-login");
|
||||
if (success && message != null) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
return success;
|
||||
}
|
||||
|
||||
private void sendSuccessNotification() {
|
||||
if (plugin.isBungeeCord()) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
dataOutput.writeUTF("SUCCESS");
|
||||
|
||||
player.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isOnlineThreadSafe() {
|
||||
//the playerlist isn't thread-safe
|
||||
Future<Boolean> onlineFuture = Bukkit.getScheduler().callSyncMethod(plugin, player::isOnline);
|
||||
|
||||
try {
|
||||
return onlineFuture.get();
|
||||
} catch (InterruptedException | ExecutionException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to perform thread-safe online check", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
}
|
||||
57
bukkit/src/main/resources/plugin.yml
Normal file
57
bukkit/src/main/resources/plugin.yml
Normal file
@@ -0,0 +1,57 @@
|
||||
# project informations for Bukkit in order to register our plugin with all it components
|
||||
# ${-} are variables from Maven (pom.xml) which will be replaced after the build
|
||||
name: ${project.parent.name}
|
||||
version: ${project.version}
|
||||
main: ${project.groupId}.${project.artifactId}.${project.name}
|
||||
|
||||
# meta informations for plugin managers
|
||||
authors: [games647, 'https://github.com/games647/FastLogin/graphs/contributors']
|
||||
description: |
|
||||
${project.description}
|
||||
website: ${project.url}
|
||||
dev-url: ${project.url}
|
||||
|
||||
# Load the plugin as early as possible to inject it for all players
|
||||
load: STARTUP
|
||||
|
||||
softdepend:
|
||||
# We depend either ProtocolLib or ProtocolSupport
|
||||
- ProtocolSupport
|
||||
- ProtocolLib
|
||||
|
||||
commands:
|
||||
${project.parent.name}:
|
||||
description: 'Label the invoker as premium'
|
||||
aliases: [prem, premium, loginfast]
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.premium
|
||||
|
||||
cracked:
|
||||
description: 'Label the invoker or the player specified as cracked if he was marked premium before'
|
||||
aliases: [unpremium]
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.cracked
|
||||
|
||||
import-auth:
|
||||
description: 'Imports the auth data from another auto login'
|
||||
usage: /<command> [player]
|
||||
permission: ${project.artifactId}.command.import
|
||||
|
||||
permissions:
|
||||
${project.artifactId}.command.premium:
|
||||
description: 'Label themselves as premium'
|
||||
default: true
|
||||
|
||||
${project.artifactId}.command.premium.other:
|
||||
description: 'Label others as premium'
|
||||
children:
|
||||
${project.artifactId}.command.premium: true
|
||||
|
||||
${project.artifactId}.command.cracked:
|
||||
description: 'Label themselves as cracked'
|
||||
default: true
|
||||
|
||||
${project.artifactId}.command.cracked.other:
|
||||
description: 'Label others as cracked'
|
||||
children:
|
||||
${project.artifactId}.command.cracked: true
|
||||
53
bungee/pom.xml
Normal file
53
bungee/pom.xml
Normal file
@@ -0,0 +1,53 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||
<artifactId>fastlogin.bungee</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<!--Represents the main plugin-->
|
||||
<name>FastLoginBungee</name>
|
||||
|
||||
<repositories>
|
||||
<!--BungeeCord with also the part outside the API-->
|
||||
<repository>
|
||||
<id>myplayplanet-REPO</id>
|
||||
<url>http://maven.myplayplanet.net/</url>
|
||||
</repository>
|
||||
|
||||
<repository>
|
||||
<id>vik1395-repo</id>
|
||||
<url>http://repo.vik1395.me/repositories</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>me.vik1395</groupId>
|
||||
<artifactId>BungeeAuth</artifactId>
|
||||
<version>1.3.1</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,87 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.stream.Collectors;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.scheduler.GroupedThreadFactory;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
public class BungeeCore extends FastLoginCore<ProxiedPlayer> {
|
||||
|
||||
private static Map<String, Object> generateConfigMap(Configuration config) {
|
||||
return config.getKeys().stream()
|
||||
.filter(key -> config.get(key) != null)
|
||||
.collect(Collectors.toMap(key -> key, config::get));
|
||||
}
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public BungeeCore(FastLoginBungee plugin, Configuration config) {
|
||||
super(generateConfigMap(config));
|
||||
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@Override
|
||||
public File getDataFolder() {
|
||||
return plugin.getDataFolder();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Logger getLogger() {
|
||||
return plugin.getLogger();
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public ThreadFactory getThreadFactory() {
|
||||
String pluginName = plugin.getDescription().getName();
|
||||
return new ThreadFactoryBuilder()
|
||||
.setNameFormat(pluginName + " Database Pool Thread #%1$d")
|
||||
//Hikari create daemons by default
|
||||
.setDaemon(true)
|
||||
.setThreadFactory(new GroupedThreadFactory(plugin, pluginName))
|
||||
.build();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void loadMessages() {
|
||||
try {
|
||||
plugin.saveDefaultFile("messages.yml");
|
||||
ConfigurationProvider configProvider = ConfigurationProvider.getProvider(YamlConfiguration.class);
|
||||
|
||||
Configuration defaults = configProvider.load(getClass().getResourceAsStream("/messages.yml"));
|
||||
|
||||
File messageFile = new File(getDataFolder(), "messages.yml");
|
||||
Configuration messageConfig = configProvider.load(messageFile, defaults);
|
||||
|
||||
messageConfig.getKeys().forEach(key -> {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
localeMessages.put(key, message);
|
||||
}
|
||||
});
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, "Failed to load messages", ex);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests) {
|
||||
return new MojangApiBungee(logger, addresses, requests);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,34 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
|
||||
public class BungeeLoginSession extends LoginSession {
|
||||
|
||||
private boolean alreadySaved;
|
||||
private boolean alreadyLogged;
|
||||
|
||||
public BungeeLoginSession(String username, boolean registered, PlayerProfile profile) {
|
||||
super(username, registered, profile);
|
||||
}
|
||||
|
||||
public void setRegistered(boolean registered) {
|
||||
this.registered = registered;
|
||||
}
|
||||
|
||||
public boolean isAlreadySaved() {
|
||||
return alreadySaved;
|
||||
}
|
||||
|
||||
public void setAlreadySaved(boolean alreadySaved) {
|
||||
this.alreadySaved = alreadySaved;
|
||||
}
|
||||
|
||||
public boolean isAlreadyLogged() {
|
||||
return alreadyLogged;
|
||||
}
|
||||
|
||||
public void setAlreadyLogged(boolean alreadyLogged) {
|
||||
this.alreadyLogged = alreadyLogged;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,36 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSource;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
|
||||
public class BungeeLoginSource implements LoginSource {
|
||||
|
||||
private final PendingConnection connection;
|
||||
|
||||
public BungeeLoginSource(PendingConnection connection) {
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setOnlineMode() {
|
||||
connection.setOnlineMode(true);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void kick(String message) {
|
||||
connection.disconnect(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
|
||||
@Override
|
||||
public InetSocketAddress getAddress() {
|
||||
return connection.getAddress();
|
||||
}
|
||||
|
||||
public PendingConnection getConnection() {
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,130 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthHook;
|
||||
import com.github.games647.fastlogin.bungee.hooks.BungeeAuthPlugin;
|
||||
import com.github.games647.fastlogin.bungee.listener.PlayerConnectionListener;
|
||||
import com.github.games647.fastlogin.bungee.listener.PluginMessageListener;
|
||||
import com.google.common.collect.Maps;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.nio.file.Files;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.plugin.Plugin;
|
||||
import net.md_5.bungee.config.Configuration;
|
||||
import net.md_5.bungee.config.ConfigurationProvider;
|
||||
import net.md_5.bungee.config.YamlConfiguration;
|
||||
|
||||
/**
|
||||
* BungeeCord version of FastLogin. This plugin keeps track on online mode connections.
|
||||
*/
|
||||
public class FastLoginBungee extends Plugin {
|
||||
|
||||
private final ConcurrentMap<PendingConnection, BungeeLoginSession> session = Maps.newConcurrentMap();
|
||||
|
||||
private BungeeCore core;
|
||||
private Configuration config;
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
saveDefaultFile("config.yml");
|
||||
|
||||
try {
|
||||
File configFile = new File(getDataFolder(), "config.yml");
|
||||
ConfigurationProvider provider = ConfigurationProvider.getProvider(YamlConfiguration.class);
|
||||
Configuration defaults = provider.load(getResourceAsStream("config.yml"));
|
||||
config = provider.load(configFile, defaults);
|
||||
|
||||
core = new BungeeCore(this, config);
|
||||
if (!core.setupDatabase()) {
|
||||
return;
|
||||
}
|
||||
} catch (IOException ioExc) {
|
||||
getLogger().log(Level.SEVERE, "Error loading config. Disabling plugin...", ioExc);
|
||||
return;
|
||||
}
|
||||
|
||||
core.setApiConnector();
|
||||
core.loadMessages();
|
||||
|
||||
//events
|
||||
getProxy().getPluginManager().registerListener(this, new PlayerConnectionListener(this));
|
||||
getProxy().getPluginManager().registerListener(this, new PluginMessageListener(this));
|
||||
|
||||
//bungee only commands
|
||||
getProxy().getPluginManager().registerCommand(this, new ImportCommand(this));
|
||||
|
||||
//this is required to listen to messages from the server
|
||||
getProxy().registerChannel(getDescription().getName());
|
||||
|
||||
registerHook();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onDisable() {
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
}
|
||||
|
||||
public void saveDefaultFile(String fileName) {
|
||||
if (!getDataFolder().exists()) {
|
||||
getDataFolder().mkdir();
|
||||
}
|
||||
|
||||
File configFile = new File(getDataFolder(), fileName);
|
||||
if (!configFile.exists()) {
|
||||
InputStream in = getResourceAsStream(fileName);
|
||||
try {
|
||||
Files.copy(in, configFile.toPath());
|
||||
} catch (IOException ioExc) {
|
||||
getLogger().log(Level.SEVERE, "Error saving default " + fileName, ioExc);
|
||||
} finally {
|
||||
try {
|
||||
in.close();
|
||||
} catch (IOException ex) {
|
||||
getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public BungeeCore getCore() {
|
||||
return core;
|
||||
}
|
||||
|
||||
@Deprecated
|
||||
public void setAuthPluginHook(BungeeAuthPlugin authPlugin) {
|
||||
core.setAuthPluginHook(authPlugin);
|
||||
}
|
||||
|
||||
public Configuration getConfig() {
|
||||
return config;
|
||||
}
|
||||
|
||||
public ConcurrentMap<PendingConnection, BungeeLoginSession> getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auth plugin hook for BungeeCord
|
||||
*
|
||||
* @return the auth hook for BungeeCord. null if none found
|
||||
*/
|
||||
@Deprecated
|
||||
public BungeeAuthPlugin getBungeeAuthPlugin() {
|
||||
return (BungeeAuthPlugin) core.getAuthPluginHook();
|
||||
}
|
||||
|
||||
private void registerHook() {
|
||||
Plugin plugin = getProxy().getPluginManager().getPlugin("BungeeAuth");
|
||||
if (plugin != null) {
|
||||
core.setAuthPluginHook(new BungeeAuthHook());
|
||||
getLogger().info("Hooked into BungeeAuth");
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
|
||||
import net.md_5.bungee.api.ChatColor;
|
||||
import net.md_5.bungee.api.CommandSender;
|
||||
import net.md_5.bungee.api.chat.BaseComponent;
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.plugin.Command;
|
||||
|
||||
public class ImportCommand extends Command {
|
||||
|
||||
private final BungeeCore core;
|
||||
|
||||
public ImportCommand(FastLoginBungee plugin) {
|
||||
super("import-db", plugin.getDescription().getName().toLowerCase() + ".import");
|
||||
|
||||
this.core = plugin.getCore();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void execute(CommandSender sender, String[] args) {
|
||||
if (args.length < 2) {
|
||||
String message = ChatColor.DARK_RED + "You need to specify the import plugin and database type";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
ImportPlugin importPlugin;
|
||||
switch (args[0].toLowerCase()) {
|
||||
case "autoin":
|
||||
importPlugin = ImportPlugin.AUTO_IN;
|
||||
break;
|
||||
case "bpa":
|
||||
importPlugin = ImportPlugin.BPA;
|
||||
break;
|
||||
case "eldzi":
|
||||
importPlugin = ImportPlugin.ELDZI;
|
||||
break;
|
||||
default:
|
||||
String message = ChatColor.DARK_RED + "Unknown auto login plugin";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
boolean sqlite;
|
||||
switch (args[1].toLowerCase()) {
|
||||
case "sqlite":
|
||||
sqlite = true;
|
||||
break;
|
||||
case "mysql":
|
||||
sqlite = false;
|
||||
break;
|
||||
default:
|
||||
String message = ChatColor.DARK_RED + "Unknown storage type to import from. Either SQLite or MySQL";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
String host = "";
|
||||
String database = "";
|
||||
String username = "";
|
||||
String password = "";
|
||||
if (!sqlite) {
|
||||
if (args.length <= 5) {
|
||||
String message = ChatColor.DARK_RED + "If importing from MySQL, you need to specify host database "
|
||||
+ "and username passowrd too";
|
||||
sender.sendMessage(convertFromLegacy(message));
|
||||
return;
|
||||
}
|
||||
|
||||
host = args[2];
|
||||
database = args[3];
|
||||
username = args[4];
|
||||
password = args[5];
|
||||
}
|
||||
|
||||
AuthStorage storage = core.getStorage();
|
||||
boolean success = core.importDatabase(importPlugin, true, storage, host, database, username, password);
|
||||
if (success) {
|
||||
sender.sendMessage(convertFromLegacy(ChatColor.DARK_GREEN + "Successful imported the data"));
|
||||
} else {
|
||||
sender.sendMessage(convertFromLegacy(ChatColor.DARK_RED + "Failed to import the data. Check out the logs"));
|
||||
}
|
||||
}
|
||||
|
||||
private BaseComponent[] convertFromLegacy(String message) {
|
||||
return TextComponent.fromLegacyText(message);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,41 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.github.games647.fastlogin.core.shared.MojangApiConnector;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
|
||||
public class MojangApiBungee extends MojangApiConnector {
|
||||
|
||||
public MojangApiBungee(Logger logger, List<String> localAddresses, int rateLimit) {
|
||||
super(logger, localAddresses, rateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected String getUUIDFromJson(String json) {
|
||||
boolean isArray = json.startsWith("[");
|
||||
|
||||
MojangPlayer mojangPlayer;
|
||||
if (isArray) {
|
||||
mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer[].class)[0];
|
||||
} else {
|
||||
mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer.class);
|
||||
}
|
||||
|
||||
String id = mojangPlayer.getId();
|
||||
if ("null".equals(id)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return id;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasJoinedServer(LoginSession session, String serverId) {
|
||||
//this is not needed in Bungee
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
package com.github.games647.fastlogin.bungee;
|
||||
|
||||
public class MojangPlayer {
|
||||
|
||||
private String id;
|
||||
private String name;
|
||||
|
||||
public String getId() {
|
||||
return id;
|
||||
}
|
||||
|
||||
public String getName() {
|
||||
return name;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,111 @@
|
||||
package com.github.games647.fastlogin.bungee.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.text.SimpleDateFormat;
|
||||
import java.util.Date;
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import me.vik1395.BungeeAuth.ListenerClass;
|
||||
import me.vik1395.BungeeAuth.Main;
|
||||
import me.vik1395.BungeeAuth.Password.PasswordHandler;
|
||||
import me.vik1395.BungeeAuth.Tables;
|
||||
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
/**
|
||||
* Github: https://github.com/MatteCarra/BungeeAuth
|
||||
*
|
||||
* Project page:
|
||||
*
|
||||
* Spigot: https://www.spigotmc.org/resources/bungeeauth.493/
|
||||
*/
|
||||
public class BungeeAuthHook implements AuthPlugin<ProxiedPlayer> {
|
||||
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Login.java#L32
|
||||
private final Tables databaseConnection = new Tables();
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(ProxiedPlayer player) {
|
||||
String playerName = player.getName();
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Login.java#L92-95
|
||||
if (Main.plonline.contains(playerName)) {
|
||||
return true;
|
||||
}
|
||||
|
||||
Main.plonline.add(playerName);
|
||||
|
||||
//renamed from ct to databaseConnection
|
||||
// databaseConnection.setStatus(player.getName(), "online");
|
||||
Class<?>[] parameterTypes = new Class<?>[]{String.class, String.class};
|
||||
Object[] arguments = new Object[]{playerName, "online"};
|
||||
|
||||
try {
|
||||
callProtected("setStatus", parameterTypes, arguments);
|
||||
ListenerClass.movePlayer(player, false);
|
||||
|
||||
//proparly not thread-safe
|
||||
ListenerClass.prelogin.get(playerName).cancel();
|
||||
} catch (Exception ex) {
|
||||
Main.plugin.getLogger().log(Level.SEVERE, "Error force loging in player", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Register.java#L46
|
||||
//renamed t to databaseConnection
|
||||
return databaseConnection.checkPlayerEntry(playerName);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(ProxiedPlayer player, String password) {
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Register.java#L102
|
||||
PasswordHandler ph = new PasswordHandler();
|
||||
Random rand = new Random();
|
||||
int maxp = 7; //Total Password Hashing methods.
|
||||
Date dNow = new Date();
|
||||
SimpleDateFormat ft = new SimpleDateFormat("yyyy.MM.dd HH:mm:ss");
|
||||
|
||||
String Pw = password;
|
||||
String pType = "" + rand.nextInt(maxp + 1);
|
||||
String regdate = ft.format(dNow);
|
||||
//https://github.com/MatteCarra/BungeeAuth/blob/master/src/me/vik1395/BungeeAuth/Register.java#L60
|
||||
String lastip = player.getAddress().getAddress().getHostAddress();
|
||||
String lastseen = regdate;
|
||||
String hash = ph.newHash(Pw, pType);
|
||||
|
||||
//creates a new SQL entry with the player's details.
|
||||
Class<?>[] parameterTypes = new Class<?>[] {String.class, String.class, String.class, String.class
|
||||
, String.class, String.class, String.class, String.class};
|
||||
Object[] arguments = new Object[] {player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen};
|
||||
|
||||
try {
|
||||
|
||||
callProtected("newPlayerEntry", parameterTypes, arguments);
|
||||
//proparly not thread-safe
|
||||
forceLogin(player);
|
||||
} catch (Exception ex) {
|
||||
Main.plugin.getLogger().log(Level.SEVERE, "[BungeeAuth] Error when creating a new player in the Database", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
//pail ;(
|
||||
private void callProtected(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {
|
||||
Class<Tables> tableClass = Tables.class;
|
||||
|
||||
Method method = tableClass.getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
//renamed t to databaseConnection
|
||||
//databaseConnection.newPlayerEntry(player.getName(), hash, pType, "", lastip, regdate, lastip, lastseen);
|
||||
method.invoke(databaseConnection, arguments);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.games647.fastlogin.bungee.hooks;
|
||||
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
/**
|
||||
* @deprecated please use com.github.games647.fastlogin.core.hooks.AuthPlugin<net.md_5.bungee.api.connection.ProxiedPlayer>
|
||||
*/
|
||||
@Deprecated
|
||||
public interface BungeeAuthPlugin extends AuthPlugin<ProxiedPlayer> {
|
||||
|
||||
@Override
|
||||
boolean forceLogin(ProxiedPlayer player);
|
||||
|
||||
@Override
|
||||
boolean isRegistered(String playerName) throws Exception;
|
||||
|
||||
@Override
|
||||
boolean forceRegister(ProxiedPlayer player, String password);
|
||||
}
|
||||
@@ -0,0 +1,113 @@
|
||||
package com.github.games647.fastlogin.bungee.listener;
|
||||
|
||||
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.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.LoginSession;
|
||||
import com.google.common.base.Charsets;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
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.event.LoginEvent;
|
||||
import net.md_5.bungee.api.event.PlayerDisconnectEvent;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.connection.LoginResult;
|
||||
import net.md_5.bungee.connection.LoginResult.Property;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
|
||||
/**
|
||||
* Enables online mode logins for specified users and sends
|
||||
* plugin message to the Bukkit version of this plugin in
|
||||
* order to clear that the connection is online mode.
|
||||
*/
|
||||
public class PlayerConnectionListener implements Listener {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public PlayerConnectionListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPreLogin(PreLoginEvent preLoginEvent) {
|
||||
if (preLoginEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
preLoginEvent.registerIntent(plugin);
|
||||
|
||||
PendingConnection connection = preLoginEvent.getConnection();
|
||||
AsyncPremiumCheck asyncPremiumCheck = new AsyncPremiumCheck(plugin, preLoginEvent, connection);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, asyncPremiumCheck);
|
||||
}
|
||||
|
||||
@EventHandler(priority = EventPriority.LOW)
|
||||
public void onLogin(LoginEvent loginEvent) {
|
||||
if (loginEvent.isCancelled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
//use the loginevent instead of the postlogin event in order to send the loginsuccess packet to the client
|
||||
//with the offline uuid this makes it possible to set the skin then
|
||||
PendingConnection connection = loginEvent.getConnection();
|
||||
String username = connection.getName();
|
||||
if (connection.isOnlineMode()) {
|
||||
String ip = connection.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().remove(ip + username);
|
||||
|
||||
LoginSession session = plugin.getSession().get(connection);
|
||||
session.setUuid(connection.getUniqueId());
|
||||
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
playerProfile.setUuid(connection.getUniqueId());
|
||||
|
||||
//bungeecord will do this automatically so override it on disabled option
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
if (!plugin.getConfig().getBoolean("premiumUuid")) {
|
||||
try {
|
||||
UUID offlineUUID = UUID.nameUUIDFromBytes(("OfflinePlayer:" + username).getBytes(Charsets.UTF_8));
|
||||
|
||||
//bungeecord doesn't support overriding the premium uuid
|
||||
//so we have to do it with reflection
|
||||
Field idField = InitialHandler.class.getDeclaredField("uniqueId");
|
||||
idField.setAccessible(true);
|
||||
idField.set(connection, offlineUUID);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to set offline uuid", ex);
|
||||
}
|
||||
}
|
||||
|
||||
if (!plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
//this is null on offline mode
|
||||
LoginResult loginProfile = initialHandler.getLoginProfile();
|
||||
if (loginProfile != null) {
|
||||
loginProfile.setProperties(new Property[]{});
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(ServerConnectedEvent serverConnectedEvent) {
|
||||
ProxiedPlayer player = serverConnectedEvent.getPlayer();
|
||||
ForceLoginTask loginTask = new ForceLoginTask(plugin, player, serverConnectedEvent.getServer());
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, loginTask);
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
plugin.getSession().remove(player.getPendingConnection());
|
||||
plugin.getCore().getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,91 @@
|
||||
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.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataInput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.Arrays;
|
||||
|
||||
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 PluginMessageListener implements Listener {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
|
||||
public PluginMessageListener(FastLoginBungee plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onPluginMessage(PluginMessageEvent pluginMessageEvent) {
|
||||
String channel = pluginMessageEvent.getTag();
|
||||
if (pluginMessageEvent.isCancelled() || !plugin.getDescription().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);
|
||||
|
||||
//check if the message is sent from the server
|
||||
if (Server.class.isAssignableFrom(pluginMessageEvent.getSender().getClass())) {
|
||||
//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) {
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
String subchannel = dataInput.readUTF();
|
||||
if ("ON".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
if (playerName.equals(forPlayer.getName()) && plugin.getConfig().getBoolean("premium-warning")
|
||||
&& !plugin.getCore().getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
String message = plugin.getCore().getMessage("premium-warning");
|
||||
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
|
||||
plugin.getCore().getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.getCore().getPendingConfirms().remove(forPlayer.getUniqueId());
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin.getCore(), forPlayer, playerName, true);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("OFF".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin.getCore(), forPlayer, playerName, false);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("SUCCESS".equals(subchannel)) {
|
||||
onSuccessMessage(forPlayer);
|
||||
}
|
||||
}
|
||||
|
||||
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());
|
||||
PlayerProfile playerProfile = loginSession.getProfile();
|
||||
loginSession.setRegistered(true);
|
||||
|
||||
if (!loginSession.isAlreadySaved()) {
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
loginSession.setAlreadySaved(true);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,53 @@
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
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.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.shared.JoinManagement;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.event.AsyncEvent;
|
||||
|
||||
public class AsyncPremiumCheck extends JoinManagement<ProxiedPlayer, BungeeLoginSource> implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final AsyncEvent<?> preLoginEvent;
|
||||
|
||||
private final PendingConnection connection;
|
||||
|
||||
public AsyncPremiumCheck(FastLoginBungee plugin, AsyncEvent<?> preLoginEvent, PendingConnection connection) {
|
||||
super(plugin.getCore(), plugin.getCore().getAuthPluginHook());
|
||||
|
||||
this.plugin = plugin;
|
||||
this.preLoginEvent = preLoginEvent;
|
||||
this.connection = connection;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getSession().remove(connection);
|
||||
|
||||
String username = connection.getName();
|
||||
try {
|
||||
super.onLogin(username, new BungeeLoginSource(connection));
|
||||
} finally {
|
||||
preLoginEvent.completeIntent(plugin);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void requestPremiumLogin(BungeeLoginSource source, PlayerProfile profile, String username, boolean registered) {
|
||||
source.setOnlineMode();
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, registered, profile));
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
plugin.getCore().getPendingLogins().put(ip + username, new Object());
|
||||
}
|
||||
|
||||
@Override
|
||||
public void startCrackedSession(BungeeLoginSource source, PlayerProfile profile, String username) {
|
||||
plugin.getSession().put(source.getConnection(), new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,57 @@
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.BungeeCore;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
|
||||
public class AsyncToggleMessage implements Runnable {
|
||||
|
||||
private final BungeeCore core;
|
||||
private final ProxiedPlayer fromPlayer;
|
||||
private final String targetPlayer;
|
||||
private final boolean toPremium;
|
||||
|
||||
public AsyncToggleMessage(BungeeCore core, ProxiedPlayer fromPlayer, String targetPlayer, boolean toPremium) {
|
||||
this.core = core;
|
||||
this.fromPlayer = fromPlayer;
|
||||
this.targetPlayer = targetPlayer;
|
||||
this.toPremium = toPremium;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
if (toPremium) {
|
||||
activatePremium();
|
||||
} else {
|
||||
turnOffPremium();
|
||||
}
|
||||
}
|
||||
|
||||
private void turnOffPremium() {
|
||||
PlayerProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
|
||||
//existing player is already cracked
|
||||
if (playerProfile.getUserId() != -1 && !playerProfile.isPremium()) {
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("not-premium")));
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(false);
|
||||
playerProfile.setUuid(null);
|
||||
core.getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("remove-premium")));
|
||||
}
|
||||
|
||||
private void activatePremium() {
|
||||
PlayerProfile playerProfile = core.getStorage().loadProfile(targetPlayer);
|
||||
if (playerProfile.isPremium()) {
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("already-exists")));
|
||||
return;
|
||||
}
|
||||
|
||||
playerProfile.setPremium(true);
|
||||
core.getStorage().save(playerProfile);
|
||||
fromPlayer.sendMessage(TextComponent.fromLegacyText(core.getMessage("add-premium")));
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,135 @@
|
||||
package com.github.games647.fastlogin.bungee.tasks;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.BungeeLoginSession;
|
||||
import com.github.games647.fastlogin.bungee.FastLoginBungee;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.md_5.bungee.api.chat.TextComponent;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import net.md_5.bungee.api.connection.Server;
|
||||
|
||||
public class ForceLoginTask implements Runnable {
|
||||
|
||||
private final FastLoginBungee plugin;
|
||||
private final ProxiedPlayer player;
|
||||
private final Server server;
|
||||
|
||||
public ForceLoginTask(FastLoginBungee plugin, ProxiedPlayer player, Server server) {
|
||||
this.plugin = plugin;
|
||||
this.player = player;
|
||||
this.server = server;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
PendingConnection pendingConnection = player.getPendingConnection();
|
||||
BungeeLoginSession session = plugin.getSession().get(pendingConnection);
|
||||
|
||||
if (session == null || !player.isConnected()) {
|
||||
plugin.getLogger().log(Level.FINE, "Invalid session player {0} propaly left the server", player);
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
|
||||
//force login only on success
|
||||
if (pendingConnection.isOnlineMode()) {
|
||||
boolean autoRegister = session.needsRegistration();
|
||||
|
||||
//2fa authentication - no need to send bukkit force login notification and so we also don't need
|
||||
// to wait for a response -> save immediatly
|
||||
if (!plugin.getConfig().getBoolean("autoLogin")) {
|
||||
playerProfile.setPremium(true);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
session.setAlreadySaved(true);
|
||||
}
|
||||
|
||||
AuthPlugin<ProxiedPlayer> authPlugin = plugin.getCore().getAuthPluginHook();
|
||||
if (authPlugin == null) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
} else if (session.needsRegistration()) {
|
||||
forceRegister(session, authPlugin);
|
||||
} else if (authPlugin.forceLogin(player)) {
|
||||
forceLogin(session, authPlugin);
|
||||
}
|
||||
} else {
|
||||
//cracked player
|
||||
if (!session.isAlreadySaved()) {
|
||||
playerProfile.setPremium(false);
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
session.setAlreadySaved(true);
|
||||
}
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.INFO, "ERROR ON FORCE LOGIN", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private void forceRegister(BungeeLoginSession session, AuthPlugin<ProxiedPlayer> authPlugin) {
|
||||
if (session.isAlreadyLogged()) {
|
||||
sendBukkitLoginNotification(true);
|
||||
return;
|
||||
}
|
||||
|
||||
session.setAlreadyLogged(true);
|
||||
|
||||
String password = plugin.getCore().getPasswordGenerator().getRandomPassword(player);
|
||||
if (authPlugin.forceRegister(player, password)) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(true);
|
||||
String message = plugin.getCore().getMessage("auto-register");
|
||||
if (message != null) {
|
||||
message = message.replace("%password", password);
|
||||
player.sendMessage(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void forceLogin(BungeeLoginSession session, AuthPlugin<ProxiedPlayer> authPlugin) {
|
||||
if (session.isAlreadyLogged()) {
|
||||
sendBukkitLoginNotification(false);
|
||||
return;
|
||||
}
|
||||
|
||||
session.setAlreadyLogged(true);
|
||||
if (authPlugin.forceLogin(player)) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(false);
|
||||
String message = plugin.getCore().getMessage("auto-login");
|
||||
if (message != null) {
|
||||
player.sendMessage(TextComponent.fromLegacyText(message));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void sendBukkitLoginNotification(boolean autoRegister) {
|
||||
ByteArrayDataOutput dataOutput = ByteStreams.newDataOutput();
|
||||
//subchannel name
|
||||
if (autoRegister) {
|
||||
dataOutput.writeUTF("AUTO_REGISTER");
|
||||
} else {
|
||||
dataOutput.writeUTF("AUTO_LOGIN");
|
||||
}
|
||||
|
||||
//Data is sent through a random player. We have to tell the Bukkit version of this plugin the target
|
||||
dataOutput.writeUTF(player.getName());
|
||||
|
||||
//proxy identifier to check if it's a acceptable proxy
|
||||
UUID proxyId = UUID.fromString(plugin.getProxy().getConfig().getUuid());
|
||||
dataOutput.writeLong(proxyId.getMostSignificantBits());
|
||||
dataOutput.writeLong(proxyId.getLeastSignificantBits());
|
||||
|
||||
if (server != null) {
|
||||
server.sendData(plugin.getDescription().getName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
16
bungee/src/main/resources/bungee.yml
Normal file
16
bungee/src/main/resources/bungee.yml
Normal file
@@ -0,0 +1,16 @@
|
||||
# project informations for BungeeCord
|
||||
# This file will be prioritised over plugin.yml which can be also used for Bungee
|
||||
# This make it easy to combine BungeeCord and Bukkit support in one plugin
|
||||
name: ${project.parent.name}
|
||||
# ${-} will be automatically replaced by Maven
|
||||
main: ${project.groupId}.${project.artifactId}.${project.name}
|
||||
|
||||
version: ${project.version}
|
||||
author: games647, http://github.com/games647/FastLogin/graphs/contributors
|
||||
|
||||
softdepend:
|
||||
# BungeeCord auth plugins
|
||||
- BungeeAuth
|
||||
|
||||
description: |
|
||||
${project.description}
|
||||
@@ -1,198 +0,0 @@
|
||||
Options:
|
||||
ConfigurationVersion_DO_NOT_CHANGE_THIS: 6
|
||||
|
||||
# 0 -> unlimited/as needed.
|
||||
NettyThreads: 0
|
||||
|
||||
# DON'T USE THAT IF YOU CAN USE SOCKETS, Enable when you can't use sockets but you want use that on bungee server, it isn't good as sockets, but it should not explode. NOTE: You NEED MySQL for that, and this is slower and may by VERY buggy on first join.
|
||||
UseChannels: false
|
||||
|
||||
# Time in ms between trying connect LISTENER to HANLDER socket, on linux using smaller times can cause some weird problems.
|
||||
ReTryConnectSocketTime: 250
|
||||
|
||||
# where AutoIn should store exceptions, enabled login, whitelists, cache and other player settings.
|
||||
SaveData:
|
||||
Type: MySQL
|
||||
MySQL:
|
||||
Host: localhost
|
||||
Port: 3306
|
||||
User: minecraft
|
||||
Pass:
|
||||
Database: minecraft
|
||||
Prefix: AutoIn_
|
||||
SQLite:
|
||||
File: AutoIn_PlayerOptions.db
|
||||
|
||||
# Both
|
||||
|
||||
Both:
|
||||
|
||||
Dependencies:
|
||||
|
||||
# If you have one of that plugin, you can force AutoIn to don't use them even if they can be used. (change to true)
|
||||
ForceDisable:
|
||||
ProtocolLib: false
|
||||
Skript: false
|
||||
AuthMe: false
|
||||
LogIt: false
|
||||
XAuth: false
|
||||
LoginSecurity: false
|
||||
|
||||
Listeners:
|
||||
|
||||
# you can try disable that and manualy configure priority below if auto-login will stop work after update of auth pluhin. PS: tell me about that problem!
|
||||
GetFromModule: true
|
||||
PlayerLoginEvent: LOWEST
|
||||
PlayerJoinEvent: LOWEST
|
||||
PlayerQuitEvent: MONITOR
|
||||
|
||||
Players:
|
||||
|
||||
# no more /login commands for premium users! Enable only on servers where you have auth plugin!
|
||||
AutoLogin: true
|
||||
|
||||
# if true autoin will try fix skins, you can disable that if you want use other plugin. (some plugins may still not work)
|
||||
FixSkins: true
|
||||
|
||||
# if true, then all players are exception by default, they can use /ai IAmNowPremium to remove exception flag (NOTE: they will lose all data after login as premium if you have fixedUUIDs set to false)
|
||||
NegateExceptions: false
|
||||
|
||||
# if true, then even premium players needs to register. PS: You can make registration optional, see wiki: https://github.com/GotoFinal/AutoIn/wiki/Registration
|
||||
Registration: true
|
||||
|
||||
# A.K.A. SwitchMode, If you disable that, only old cracked players will be able to join. New cracked players will be kicked from server. Good if you want switch from offline-mode to online-mode without losing players!
|
||||
AllowNewCrackedPlayers: true
|
||||
|
||||
# if true, then everyone have UUID generated from nickname
|
||||
FixedUUID: false
|
||||
|
||||
# WhiteList that works only on cracked players, black-list works even if this is disabled.
|
||||
CrackedWhiteListEnabled: false
|
||||
|
||||
ForceLogin:
|
||||
|
||||
# Allow use RegEx in nicknames list, like 'Test\\d{1,3}'
|
||||
UseRegEx: false
|
||||
|
||||
# Nicknames from this list don't need use login or register command, use to supprot mods like buildcraft and others
|
||||
Nicknames:
|
||||
- SomeNicknameThatDoNotNeedUseLoginOrRegisterCommandEvenIfHeDoNotHavePremium
|
||||
|
||||
Sessions:
|
||||
|
||||
# If enabled, plugin will remember premium players with their IP number, when servers will be down, premium players can be still auto-logged if IP will be valid.
|
||||
Enabled: true
|
||||
|
||||
# If true, then players with valid session can join even if mojang server are down and without using password. PS: You can enable this and ServerProtect, then registered players OR players with valid session can join.
|
||||
AsServerProtect: true
|
||||
|
||||
# time in easy format, 1w = 1 week, 1d = 1 day, 1d5h12s -> 1 day, 5 hours, 12 seconds
|
||||
Expires: 1d
|
||||
|
||||
# If true sessions aren't saved on reload.
|
||||
ExpiresOnRestart: true
|
||||
|
||||
Protections:
|
||||
|
||||
# If enabled, when new cracked player join to game he will get special protection "flag", that work like exception-player. so even if someone buy premium account with this same nickname, he will be still tract as cracked player.
|
||||
# If player will buy premium, he must disable protection using "IamNowPremium" command, or admin must disable it manually, using API or /setProtection command.
|
||||
CrackedPlayersNicknames:
|
||||
Enabled: true
|
||||
|
||||
# If disabled, player must use "ProtectMe" command, or protection must be enabled manually, using API or /setProtection command
|
||||
AutoProtect: true
|
||||
|
||||
|
||||
|
||||
ServerProtect:
|
||||
|
||||
# If enabled and mojang server don't work, only registered players can join (So you must set "Registration" to true). If disabled, all players will be kicked.
|
||||
Enabled: false
|
||||
|
||||
|
||||
# If enabled plugin will remember if username was premium. (It's saved to database/file) NOTE: Enable that if you have bigger server, like 100/200+ players online.
|
||||
Cache: true
|
||||
|
||||
# Server
|
||||
|
||||
|
||||
# ignored by proxy plugin
|
||||
Server:
|
||||
|
||||
|
||||
# It will try check if cache is updated before player join.
|
||||
UpdateCacheOnPreJoin: false
|
||||
|
||||
# It will try check if cache is updated after player join.
|
||||
UpdateCacheOnJoin: true
|
||||
|
||||
|
||||
# Proxy
|
||||
|
||||
|
||||
# ignored by server
|
||||
Proxy:
|
||||
|
||||
# Players needs to be logged in to use commands
|
||||
NeedLoginToUseCommands: true
|
||||
|
||||
# Player can use this commands
|
||||
NotBlocked:
|
||||
- /login
|
||||
- /register
|
||||
- /l
|
||||
|
||||
# players from selected group can be send to other servers. This override Proxy/BungeeCord setting!
|
||||
ForcedServers:
|
||||
PREMIUM:
|
||||
Enabled: false
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: PremiumServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: false
|
||||
|
||||
# works like "forced_hosts" from bungee -> this override "Force" option
|
||||
ForcedHosts:
|
||||
eg: nul.goto. ...\\.. .final..IIlIlIIlIl@71c66aab
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
CRACKED:
|
||||
Enabled: true
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: CrackedServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: true
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
EXCEPTION:
|
||||
Enabled: false
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: PremiumServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: true
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
|
||||
# This status will use this same settings as parent status, but you can override that settings by adding them here.
|
||||
Parent: CRACKED
|
||||
ERROR:
|
||||
Enabled: false
|
||||
|
||||
# works like "default_server" from bungee
|
||||
Server: ErrorServer
|
||||
|
||||
# works like "force_default_server" from bungee
|
||||
Force: false
|
||||
|
||||
# like "fallback_server" from bungee
|
||||
Fallback: FallbackServer
|
||||
|
||||
39
core/pom.xml
Normal file
39
core/pom.xml
Normal file
@@ -0,0 +1,39 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>FastLoginCore</name>
|
||||
|
||||
<dependencies>
|
||||
<!--Database pooling-->
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>2.5.0</version>
|
||||
</dependency>
|
||||
|
||||
<!--Logging framework implements slf4j which is required by hikari-->
|
||||
<dependency>
|
||||
<groupId>org.slf4j</groupId>
|
||||
<artifactId>slf4j-jdk14</artifactId>
|
||||
<version>1.7.21</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.google.guava</groupId>
|
||||
<artifactId>guava</artifactId>
|
||||
<version>10.0.1</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
@@ -0,0 +1,253 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.github.games647.fastlogin.core.shared.FastLoginCore;
|
||||
import com.zaxxer.hikari.HikariConfig;
|
||||
import com.zaxxer.hikari.HikariDataSource;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.PreparedStatement;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.Calendar;
|
||||
import java.util.Locale;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class AuthStorage {
|
||||
|
||||
private static final String PREMIUM_TABLE = "premium";
|
||||
|
||||
private final FastLoginCore<?> core;
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
//a try to fix https://www.spigotmc.org/threads/fastlogin.101192/page-26#post-1874647
|
||||
private final Calendar calendar = Calendar.getInstance(Locale.US);
|
||||
|
||||
public AuthStorage(FastLoginCore<?> core, String driver, String host, int port, String databasePath
|
||||
, String user, String pass) {
|
||||
this.core = core;
|
||||
|
||||
HikariConfig databaseConfig = new HikariConfig();
|
||||
databaseConfig.setUsername(user);
|
||||
databaseConfig.setPassword(pass);
|
||||
databaseConfig.setDriverClassName(driver);
|
||||
databaseConfig.setThreadFactory(core.getThreadFactory());
|
||||
|
||||
databasePath = databasePath.replace("{pluginDir}", core.getDataFolder().getAbsolutePath());
|
||||
|
||||
String jdbcUrl = "jdbc:";
|
||||
if (driver.contains("sqlite")) {
|
||||
jdbcUrl += "sqlite" + "://" + databasePath;
|
||||
databaseConfig.setConnectionTestQuery("SELECT 1");
|
||||
} else {
|
||||
jdbcUrl += "mysql" + "://" + host + ':' + port + '/' + databasePath;
|
||||
}
|
||||
|
||||
databaseConfig.setJdbcUrl(jdbcUrl);
|
||||
this.dataSource = new HikariDataSource(databaseConfig);
|
||||
}
|
||||
|
||||
public HikariDataSource getDataSource() {
|
||||
return dataSource;
|
||||
}
|
||||
|
||||
public void createTables() throws SQLException {
|
||||
Connection con = null;
|
||||
Statement createStmt = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
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);
|
||||
|
||||
//drop the old unique uuid index
|
||||
try {
|
||||
if (dataSource.getJdbcUrl().contains("sqlite")) {
|
||||
String tempTableCreate = createDataStmt.replace(PREMIUM_TABLE, PREMIUM_TABLE + "_TEMP")
|
||||
//if we already imported the table fail here
|
||||
.replace("IF NOT EXISTS", "");
|
||||
//create a temp table insert it there and then back
|
||||
createStmt.executeUpdate(tempTableCreate);
|
||||
createStmt.executeUpdate("INSERT INTO " + PREMIUM_TABLE + "_TEMP SELECT * FROM " + PREMIUM_TABLE);
|
||||
|
||||
createStmt.executeUpdate("DROP TABLE " + PREMIUM_TABLE);
|
||||
createStmt.executeUpdate(createDataStmt);
|
||||
|
||||
//insert it back into the new table
|
||||
createStmt.executeUpdate("INSERT INTO " + PREMIUM_TABLE + " SELECT * FROM " + PREMIUM_TABLE + "_TEMP");
|
||||
} else {
|
||||
createStmt.executeUpdate("ALTER TABLE premium DROP INDEX UUID");
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
//silent - we already migrated
|
||||
}
|
||||
|
||||
try {
|
||||
createStmt.executeUpdate("CREATE INDEX uuid_idx on premium (UUID)");
|
||||
} catch (SQLException sqlEx) {
|
||||
//silent - we already migrated
|
||||
}
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(createStmt);
|
||||
}
|
||||
}
|
||||
|
||||
public PlayerProfile loadProfile(String name) {
|
||||
Connection con = null;
|
||||
PreparedStatement loadStmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=? LIMIT 1");
|
||||
loadStmt.setString(1, name);
|
||||
|
||||
resultSet = loadStmt.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt(1);
|
||||
|
||||
UUID uuid = FastLoginCore.parseId(resultSet.getString(2));
|
||||
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6, calendar).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
return playerProfile;
|
||||
} else {
|
||||
PlayerProfile crackedProfile = new PlayerProfile(null, name, false, "");
|
||||
return crackedProfile;
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(loadStmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public PlayerProfile loadProfile(UUID uuid) {
|
||||
Connection con = null;
|
||||
PreparedStatement loadStmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=? LIMIT 1");
|
||||
loadStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
|
||||
resultSet = loadStmt.executeQuery();
|
||||
if (resultSet.next()) {
|
||||
long userId = resultSet.getInt(1);
|
||||
|
||||
String name = resultSet.getString(3);
|
||||
boolean premium = resultSet.getBoolean(4);
|
||||
String lastIp = resultSet.getString(5);
|
||||
long lastLogin = resultSet.getTimestamp(6, calendar).getTime();
|
||||
PlayerProfile playerProfile = new PlayerProfile(userId, uuid, name, premium, lastIp, lastLogin);
|
||||
return playerProfile;
|
||||
}
|
||||
} catch (SQLException sqlEx) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to query profile", sqlEx);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(loadStmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public boolean save(PlayerProfile playerProfile) {
|
||||
Connection con = null;
|
||||
PreparedStatement updateStmt = null;
|
||||
PreparedStatement saveStmt = null;
|
||||
|
||||
ResultSet generatedKeys = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
|
||||
UUID uuid = playerProfile.getUuid();
|
||||
|
||||
if (playerProfile.getUserId() == -1) {
|
||||
saveStmt = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE
|
||||
+ " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?) ", Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
if (uuid == null) {
|
||||
saveStmt.setString(1, null);
|
||||
} else {
|
||||
saveStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStmt.setString(2, playerProfile.getPlayerName());
|
||||
saveStmt.setBoolean(3, playerProfile.isPremium());
|
||||
saveStmt.setString(4, playerProfile.getLastIp());
|
||||
|
||||
saveStmt.execute();
|
||||
|
||||
generatedKeys = saveStmt.getGeneratedKeys();
|
||||
if (generatedKeys != null && generatedKeys.next()) {
|
||||
playerProfile.setUserId(generatedKeys.getInt(1));
|
||||
}
|
||||
} else {
|
||||
saveStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE
|
||||
+ " SET UUID=?, Name=?, Premium=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP WHERE UserID=?");
|
||||
|
||||
if (uuid == null) {
|
||||
saveStmt.setString(1, null);
|
||||
} else {
|
||||
saveStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
}
|
||||
|
||||
saveStmt.setString(2, playerProfile.getPlayerName());
|
||||
saveStmt.setBoolean(3, playerProfile.isPremium());
|
||||
saveStmt.setString(4, playerProfile.getLastIp());
|
||||
|
||||
saveStmt.setLong(5, playerProfile.getUserId());
|
||||
saveStmt.execute();
|
||||
}
|
||||
|
||||
return true;
|
||||
} catch (SQLException ex) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to save playerProfile", ex);
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(updateStmt);
|
||||
closeQuietly(saveStmt);
|
||||
closeQuietly(generatedKeys);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
dataSource.close();
|
||||
}
|
||||
|
||||
private void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception closeEx) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to close connection", closeEx);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,70 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import com.google.common.collect.ImmutableList;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.concurrent.atomic.AtomicInteger;
|
||||
|
||||
import javax.net.ssl.SSLSocketFactory;
|
||||
|
||||
public class BalancedSSLFactory extends SSLSocketFactory {
|
||||
|
||||
private final SSLSocketFactory oldFactory;
|
||||
|
||||
//in order to be thread-safe
|
||||
private final List<InetAddress> localAddresses;
|
||||
|
||||
private AtomicInteger id;
|
||||
|
||||
public BalancedSSLFactory(SSLSocketFactory oldFactory, Iterable<InetAddress> localAddresses) {
|
||||
this.oldFactory = oldFactory;
|
||||
this.localAddresses = ImmutableList.copyOf(localAddresses);
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getDefaultCipherSuites() {
|
||||
return oldFactory.getDefaultCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public String[] getSupportedCipherSuites() {
|
||||
return oldFactory.getSupportedCipherSuites();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(Socket socket, String host, int port, boolean autoclose) throws IOException {
|
||||
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port) throws IOException, UnknownHostException {
|
||||
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(String host, int port, InetAddress localAddress, int localPort)
|
||||
throws IOException, UnknownHostException {
|
||||
//default
|
||||
return oldFactory.createSocket(host, port, localAddress, localPort);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port) throws IOException {
|
||||
return oldFactory.createSocket(host, port, getNextLocalAddress(), 0);
|
||||
}
|
||||
|
||||
@Override
|
||||
public Socket createSocket(InetAddress host, int port, InetAddress local, int localPort) throws IOException {
|
||||
//Default
|
||||
return oldFactory.createSocket(host, port, local, localPort);
|
||||
}
|
||||
|
||||
private InetAddress getNextLocalAddress() {
|
||||
int index = id.incrementAndGet() % localAddresses.size();
|
||||
return localAddresses.get(index);
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,321 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.lang.reflect.Method;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import com.google.common.base.Ticker;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.cache.RemovalListener;
|
||||
|
||||
/**
|
||||
* Represents a Guava CacheBuilder that is compatible with both Guava 10 (Minecraft 1.7.X) and 13
|
||||
*/
|
||||
public class CompatibleCacheBuilder<K, V> {
|
||||
|
||||
private static Method BUILD_METHOD;
|
||||
private static Method AS_MAP_METHOD;
|
||||
|
||||
/**
|
||||
* Construct a new safe cache builder.
|
||||
*
|
||||
* @param <K> Key type
|
||||
* @param <V> Value type
|
||||
*
|
||||
* @return A new cache builder.
|
||||
*/
|
||||
public static <K, V> CompatibleCacheBuilder<K, V> newBuilder() {
|
||||
return new CompatibleCacheBuilder<>();
|
||||
}
|
||||
|
||||
private final CacheBuilder<K, V> builder;
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
private CompatibleCacheBuilder() {
|
||||
builder = (CacheBuilder<K, V>) CacheBuilder.newBuilder();
|
||||
}
|
||||
|
||||
/**
|
||||
* Guides the allowed concurrency among update operations. Used as a hint for internal sizing. The table is
|
||||
* internally partitioned to try to permit the indicated number of concurrent updates without contention. Because
|
||||
* assignment of entries to these partitions is not necessarily uniform, the actual concurrency observed may vary.
|
||||
* Ideally, you should choose a value to accommodate as many threads as will ever concurrently modify the table.
|
||||
* Using a significantly higher value than you need can waste space and time, and a significantly lower value can
|
||||
* lead to thread contention. But overestimates and underestimates within an order of magnitude do not usually have
|
||||
* much noticeable impact. A value of one permits only one thread to modify the cache at a time, but since read
|
||||
* operations can proceed concurrently, this still yields higher concurrency than full synchronization. Defaults to
|
||||
* 4.
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b>The default may change in the future. If you care about this value, you should always choose it
|
||||
* explicitly.
|
||||
*
|
||||
* @param concurrencyLevel New concurrency level
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code concurrencyLevel} is nonpositive
|
||||
* @throws IllegalStateException if a concurrency level was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> concurrencyLevel(int concurrencyLevel) {
|
||||
builder.concurrencyLevel(concurrencyLevel);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after
|
||||
* the entry's creation, or last access. Access time is reset by
|
||||
* {@link com.google.common.cache.Cache#get Cache.get()}, but not by operations on the view returned by
|
||||
* {@link com.google.common.cache.Cache#asMap() Cache.asMap()}.
|
||||
*
|
||||
* <p>
|
||||
* When {@code duration} is zero, elements will be evicted immediately after being loaded into the cache. This has
|
||||
* the same effect as invoking {@link #maximumSize maximumSize}{@code (0)}. It can be useful in testing, or to
|
||||
* disable caching temporarily without a code change.
|
||||
*
|
||||
* <p>
|
||||
* Expired entries may be counted by {@link com.google.common.cache.Cache#size Cache.size()}, but will never be
|
||||
* visible to read or write operations. Expired entries are currently cleaned up during write operations, or during
|
||||
* occasional read operations in the absense of writes; though this behavior may change in the future.
|
||||
*
|
||||
* @param duration the length of time after an entry is last accessed that it should be automatically removed
|
||||
* @param unit the unit that {@code duration} is expressed in
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code duration} is negative
|
||||
* @throws IllegalStateException if the time to idle or time to live was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> expireAfterAccess(long duration, TimeUnit unit) {
|
||||
builder.expireAfterAccess(duration, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each entry should be automatically removed from the cache once a fixed duration has elapsed after
|
||||
* the entry's creation, or the most recent replacement of its value.
|
||||
*
|
||||
* <p>
|
||||
* When {@code duration} is zero, elements will be evicted immediately after being loaded into the cache. This has
|
||||
* the same effect as invoking {@link #maximumSize maximumSize}{@code (0)}. It can be useful in testing, or to
|
||||
* disable caching temporarily without a code change.
|
||||
*
|
||||
* <p>
|
||||
* Expired entries may be counted by {@link com.google.common.cache.Cache#size Cache.size()}, but will never be
|
||||
* visible to read or write operations. Expired entries are currently cleaned up during write operations, or during
|
||||
* occasional read operations in the absense of writes; though this behavior may change in the future.
|
||||
*
|
||||
* @param duration the length of time after an entry is created that it should be automatically removed
|
||||
* @param unit the unit that {@code duration} is expressed in
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code duration} is negative
|
||||
* @throws IllegalStateException if the time to live or time to idle was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> expireAfterWrite(long duration, TimeUnit unit) {
|
||||
builder.expireAfterWrite(duration, unit);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the minimum total size for the internal hash tables. For example, if the initial capacity is {@code 60}, and
|
||||
* the concurrency level is {@code 8}, then eight segments are created, each having a hash table of size eight.
|
||||
* Providing a large enough estimate at construction time avoids the need for expensive resizing operations later,
|
||||
* but setting this value unnecessarily high wastes memory.
|
||||
*
|
||||
* @param initialCapacity - initial capacity
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code initialCapacity} is negative
|
||||
* @throws IllegalStateException if an initial capacity was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> initialCapacity(int initialCapacity) {
|
||||
builder.initialCapacity(initialCapacity);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies the maximum number of entries the cache may contain. Note that the cache <b>may evict an entry before
|
||||
* this limit is exceeded</b>. As the cache size grows close to the maximum, the cache evicts entries that are less
|
||||
* likely to be used again. For example, the cache may evict an entry because it hasn't been used recently or very
|
||||
* often.
|
||||
*
|
||||
* <p>
|
||||
* When {@code size} is zero, elements will be evicted immediately after being loaded into the cache. This has the
|
||||
* same effect as invoking {@link #expireAfterWrite expireAfterWrite}{@code (0, unit)} or {@link #expireAfterAccess expireAfterAccess}{@code (0,
|
||||
* unit)}. It can be useful in testing, or to disable caching temporarily without a code change.
|
||||
*
|
||||
* @param size the maximum size of the cache
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalArgumentException if {@code size} is negative
|
||||
* @throws IllegalStateException if a maximum size was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> maximumSize(int size) {
|
||||
builder.maximumSize(size);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a listener instance, which all caches built using this {@code CacheBuilder} will notify each time an
|
||||
* entry is removed from the cache by any means.
|
||||
*
|
||||
* <p>
|
||||
* Each cache built by this {@code CacheBuilder} after this method is called invokes the supplied listener after
|
||||
* removing an element for any reason (see removal causes in
|
||||
* {@link com.google.common.cache.RemovalCause RemovalCause}). It will invoke the listener during invocations of any
|
||||
* of that cache's public methods (even read-only methods).
|
||||
*
|
||||
* <p>
|
||||
* <b>Important note:</b> Instead of returning <em>this</em> as a {@code CacheBuilder} instance, this method returns
|
||||
* {@code CacheBuilder<K1, V1>}. From this point on, either the original reference or the returned reference may be
|
||||
* used to complete configuration and build the cache, but only the "generic" one is type-safe. That is, it will
|
||||
* properly prevent you from building caches whose key or value types are incompatible with the types accepted by
|
||||
* the listener already provided; the {@code CacheBuilder} type cannot do this. For best results, simply use the
|
||||
* standard method-chaining idiom, as illustrated in the documentation at top, configuring a {@code CacheBuilder}
|
||||
* and building your {@link com.google.common.cache.Cache Cache} all in a single statement.
|
||||
*
|
||||
* <p>
|
||||
* <b>Warning:</b> if you ignore the above advice, and use this {@code CacheBuilder} to build a cache whose key or
|
||||
* value type is incompatible with the listener, you will likely experience a {@link ClassCastException} at some
|
||||
* <i>undefined</i> point in the future.
|
||||
*
|
||||
* @param <K1> Key type
|
||||
* @param <V1> Value type
|
||||
* @param listener - removal listener
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if a removal listener was already set
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K1 extends K, V1 extends V> CompatibleCacheBuilder<K1, V1> removalListener(RemovalListener<? super K1, ? super V1> listener) {
|
||||
builder.removalListener(listener);
|
||||
return (CompatibleCacheBuilder<K1, V1>) this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies a nanosecond-precision time source for use in determining when entries should be expired. By default,
|
||||
* {@link System#nanoTime} is used.
|
||||
*
|
||||
* <p>
|
||||
* The primary intent of this method is to facilitate testing of caches which have been configured with
|
||||
* {@link #expireAfterWrite} or {@link #expireAfterAccess}.
|
||||
*
|
||||
* @param ticker - ticker
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if a ticker was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> ticker(Ticker ticker) {
|
||||
builder.ticker(ticker);
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each value (not key) stored in the cache should be wrapped in a
|
||||
* {@link java.lang.ref.SoftReference SoftReference} (by default, strong references are used). Softly-referenced
|
||||
* objects will be garbage-collected in a <i>globally</i>
|
||||
* least-recently-used manner, in response to memory demand.
|
||||
*
|
||||
* <p>
|
||||
* <b>Warning:</b> in most circumstances it is better to set a per-cache {@linkplain #maximumSize maximum size}
|
||||
* instead of using soft references. You should only use this method if you are well familiar with the practical
|
||||
* consequences of soft references.
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b> when this method is used, the resulting cache will use identity ({@code ==}) comparison to determine
|
||||
* equality of values.
|
||||
*
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if the value strength was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> softValues() {
|
||||
builder.softValues();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each key (not value) stored in the cache should be wrapped in a
|
||||
* {@link java.lang.ref.WeakReference WeakReference} (by default, strong references are used).
|
||||
*
|
||||
* <p>
|
||||
* <b>Warning:</b> when this method is used, the resulting cache will use identity ({@code ==}) comparison to
|
||||
* determine equality of keys.
|
||||
*
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if the key strength was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> weakKeys() {
|
||||
builder.weakKeys();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Specifies that each value (not key) stored in the cache should be wrapped in a
|
||||
* {@link java.lang.ref.WeakReference WeakReference} (by default, strong references are used).
|
||||
*
|
||||
* <p>
|
||||
* Weak values will be garbage collected once they are weakly reachable. This makes them a poor candidate for
|
||||
* caching; consider {@link #softValues} instead.
|
||||
*
|
||||
* <p>
|
||||
* <b>Note:</b> when this method is used, the resulting cache will use identity ({@code ==}) comparison to determine
|
||||
* equality of values.
|
||||
*
|
||||
* @return This for chaining
|
||||
*
|
||||
* @throws IllegalStateException if the value strength was already set
|
||||
*/
|
||||
public CompatibleCacheBuilder<K, V> weakValues() {
|
||||
builder.weakValues();
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the cache wrapped as a ConcurrentMap.
|
||||
* <p>
|
||||
* We can't return the direct Cache instance as it changed in Guava 13.
|
||||
*
|
||||
* @param <K1> Key type
|
||||
* @param <V1> Value type
|
||||
* @param loader - cache loader
|
||||
* @return The cache as a a map.
|
||||
*/
|
||||
@SuppressWarnings("unchecked")
|
||||
public <K1 extends K, V1 extends V> ConcurrentMap<K1, V1> build(CacheLoader<? super K1, V1> loader) {
|
||||
Object cache = null;
|
||||
|
||||
if (BUILD_METHOD == null) {
|
||||
try {
|
||||
BUILD_METHOD = builder.getClass().getDeclaredMethod("build", CacheLoader.class);
|
||||
BUILD_METHOD.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to find CacheBuilder.build(CacheLoader)", e);
|
||||
}
|
||||
}
|
||||
|
||||
// Attempt to build the Cache
|
||||
try {
|
||||
cache = BUILD_METHOD.invoke(builder, loader);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to invoke " + BUILD_METHOD + " on " + builder, e);
|
||||
}
|
||||
|
||||
if (AS_MAP_METHOD == null) {
|
||||
try {
|
||||
AS_MAP_METHOD = cache.getClass().getMethod("asMap");
|
||||
AS_MAP_METHOD.setAccessible(true);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to find Cache.asMap() in " + cache, e);
|
||||
}
|
||||
}
|
||||
|
||||
// Retrieve it as a map
|
||||
try {
|
||||
return (ConcurrentMap<K1, V1>) AS_MAP_METHOD.invoke(cache);
|
||||
} catch (Exception e) {
|
||||
throw new IllegalStateException("Unable to invoke " + AS_MAP_METHOD + " on " + cache, e);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,76 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public class PlayerProfile {
|
||||
|
||||
private String playerName;
|
||||
|
||||
private long userId;
|
||||
|
||||
private UUID uuid;
|
||||
private boolean premium;
|
||||
private String lastIp;
|
||||
private long lastLogin;
|
||||
|
||||
public PlayerProfile(long userId, UUID uuid, String playerName, boolean premium, String lastIp, long lastLogin) {
|
||||
this.userId = userId;
|
||||
this.uuid = uuid;
|
||||
this.playerName = playerName;
|
||||
this.premium = premium;
|
||||
this.lastIp = lastIp;
|
||||
this.lastLogin = lastLogin;
|
||||
}
|
||||
|
||||
public PlayerProfile(UUID uuid, String playerName, boolean premium, String lastIp) {
|
||||
this(-1, uuid, playerName, premium, lastIp, System.currentTimeMillis());
|
||||
}
|
||||
|
||||
public synchronized String getPlayerName() {
|
||||
return playerName;
|
||||
}
|
||||
|
||||
public synchronized void setPlayerName(String playerName) {
|
||||
this.playerName = playerName;
|
||||
}
|
||||
|
||||
public synchronized long getUserId() {
|
||||
return userId;
|
||||
}
|
||||
|
||||
public synchronized void setUserId(long generatedId) {
|
||||
this.userId = generatedId;
|
||||
}
|
||||
|
||||
public synchronized UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
public synchronized void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
|
||||
public synchronized boolean isPremium() {
|
||||
return premium;
|
||||
}
|
||||
|
||||
public synchronized void setPremium(boolean premium) {
|
||||
this.premium = premium;
|
||||
}
|
||||
|
||||
public synchronized String getLastIp() {
|
||||
return lastIp;
|
||||
}
|
||||
|
||||
public synchronized void setLastIp(String lastIp) {
|
||||
this.lastIp = lastIp;
|
||||
}
|
||||
|
||||
public synchronized long getLastLogin() {
|
||||
return lastLogin;
|
||||
}
|
||||
|
||||
public synchronized void setLastLogin(long lastLogin) {
|
||||
this.lastLogin = lastLogin;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,26 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.util.Map;
|
||||
|
||||
public class SharedConfig {
|
||||
|
||||
private final Map<String, Object> configValues;
|
||||
|
||||
public SharedConfig(Map<String, Object> configValues) {
|
||||
this.configValues = configValues;
|
||||
}
|
||||
|
||||
@SuppressWarnings("unchecked")
|
||||
public <T> T get(String path, T def) {
|
||||
Object val = configValues.get(path);
|
||||
return ( val != null ) ? (T) val : def;
|
||||
}
|
||||
|
||||
public <T> T get(String path) {
|
||||
return get(path, null);
|
||||
}
|
||||
|
||||
public Map<String, Object> getConfigValues() {
|
||||
return configValues;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,59 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
/**
|
||||
* Represents a supporting authentication plugin in BungeeCord and Bukkit/Spigot/... servers
|
||||
*
|
||||
* @param <P> either org.bukkit.entity.Player for Bukkit or net.md_5.bungee.api.connection.ProxiedPlayer for BungeeCord
|
||||
*/
|
||||
public interface AuthPlugin<P> {
|
||||
|
||||
/**
|
||||
* Login the premium (paid account) player after the player joined successfully the server.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* @param player the player that needs to be logged in
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
boolean forceLogin(P player);
|
||||
|
||||
/**
|
||||
* Forces a register in order to protect the paid account.
|
||||
*
|
||||
* <strong>This operation will be performed async while the player successfully
|
||||
* joined the server.</strong>
|
||||
*
|
||||
* After a successful registration the player should be logged
|
||||
* in too.
|
||||
*
|
||||
* The method will be called only for premium accounts.
|
||||
* So it's recommended to set additionally premium property
|
||||
* if possible.
|
||||
*
|
||||
* Background: If we don't register an account, cracked players
|
||||
* could steal the unregistered account from the paid
|
||||
* player account
|
||||
*
|
||||
* @param player the premium account
|
||||
* @param password a strong random generated password
|
||||
* @return if the operation was successful
|
||||
*/
|
||||
boolean forceRegister(P player, String password);
|
||||
|
||||
/**
|
||||
* Checks whether an account exists for this player name.
|
||||
*
|
||||
* This check should check if a cracked player account exists
|
||||
* so we can be sure the premium player doesn't steal the account
|
||||
* of that player.
|
||||
*
|
||||
* This operation will be performed async while the player is
|
||||
* connecting.
|
||||
*
|
||||
* @param playerName player name
|
||||
* @return if the player has an account
|
||||
* @throws Exception if an error occurred
|
||||
*/
|
||||
boolean isRegistered(String playerName) throws Exception;
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
import java.util.Random;
|
||||
|
||||
public class DefaultPasswordGenerator<P> implements PasswordGenerator<P> {
|
||||
|
||||
private static final char[] PASSWORD_CHARACTERS = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ0123456789"
|
||||
.toCharArray();
|
||||
private final Random random = new Random();
|
||||
|
||||
@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)]);
|
||||
}
|
||||
|
||||
return generatedPassword.toString();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,6 @@
|
||||
package com.github.games647.fastlogin.core.hooks;
|
||||
|
||||
public interface PasswordGenerator<P> {
|
||||
|
||||
String getRandomPassword(P player);
|
||||
}
|
||||
@@ -0,0 +1,73 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class AutoInImporter extends Importer {
|
||||
|
||||
private static final String PLUGIN_NAME = "AutoIn";
|
||||
|
||||
private static final String SQLITE_FILE = "plugins/" + PLUGIN_NAME + "/AutoIn_PlayerOptions.db";
|
||||
|
||||
private static final String USER_TABLE = "nicknames";
|
||||
private static final String UUID_TABLE = "uuids";
|
||||
private static final String SESSION_TABLE = "sessions";
|
||||
|
||||
public static String getSQLitePath() {
|
||||
return SQLITE_FILE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT name, protection, premium, puuid FROM " + USER_TABLE
|
||||
+ " LEFT JOIN " + " ("
|
||||
/* Prevent duplicates */
|
||||
+ "SELECT * FROM " + UUID_TABLE + " GROUP BY nickname_id"
|
||||
+ ") uuids"
|
||||
+ " ON " + USER_TABLE + ".id = uuids.nickname_id");
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean protection = resultSet.getBoolean(2);
|
||||
/* Enable premium authentication only for those who want to be auto logged in,
|
||||
so they have their cracked protection disabled */
|
||||
boolean premium = !protection && resultSet.getBoolean(3);
|
||||
String puuid = resultSet.getString(4);
|
||||
|
||||
/* FastLogin will also make lookups on the uuid column for name changes
|
||||
the old 1.6.2 version won't check if those user have premium enabled
|
||||
|
||||
so it could happen that a premium could steal the account if we don't do this
|
||||
|
||||
It seems the uuid is saved on autoin too if the player is cracked */
|
||||
PlayerProfile profile;
|
||||
if (premium) {
|
||||
profile = new PlayerProfile(UUID.fromString(puuid), name, premium, "");
|
||||
} else {
|
||||
profile = new PlayerProfile(null, name, premium, "");
|
||||
}
|
||||
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,50 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class BPAImporter extends Importer {
|
||||
|
||||
private static final String DEFAULT_TABLE_NAME = "users";
|
||||
|
||||
@Override
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT "
|
||||
+ "nick, "
|
||||
+ "checked, "
|
||||
+ "lastIP, "
|
||||
+ "FROM_UNIXTIME(lastJoined * 0.001) AS LastLogin "
|
||||
+ "FROM " + DEFAULT_TABLE_NAME);
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean premium = resultSet.getBoolean(2);
|
||||
String lastIP = resultSet.getString(3);
|
||||
Timestamp lastLogin = resultSet.getTimestamp(4);
|
||||
|
||||
//uuid doesn't exist here
|
||||
PlayerProfile profile = new PlayerProfile(null, name, premium, lastIP);
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,58 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.sql.Connection;
|
||||
import java.sql.ResultSet;
|
||||
import java.sql.SQLException;
|
||||
import java.sql.Statement;
|
||||
import java.sql.Timestamp;
|
||||
import java.util.UUID;
|
||||
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public class ElDziAuthImporter extends Importer {
|
||||
|
||||
private static final String TABLE_NAME = "accounts";
|
||||
|
||||
@Override
|
||||
public int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException {
|
||||
Statement stmt = null;
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
stmt = source.createStatement();
|
||||
resultSet = stmt.executeQuery("SELECT "
|
||||
+ "nick, "
|
||||
+ "premium, "
|
||||
+ "lastIP, "
|
||||
+ "FROM_UNIXTIME(lastPlayed * 0.001) AS LastLogin "
|
||||
+ "FROM " + TABLE_NAME);
|
||||
|
||||
int rows = 0;
|
||||
while (resultSet.next()) {
|
||||
String name = resultSet.getString(1);
|
||||
boolean premium = resultSet.getBoolean(2);
|
||||
String lastIP = resultSet.getString(3);
|
||||
Timestamp lastLogin = resultSet.getTimestamp(4);
|
||||
|
||||
String uuid = resultSet.getString(5);
|
||||
|
||||
PlayerProfile profile;
|
||||
if (premium) {
|
||||
profile = new PlayerProfile(UUID.fromString(uuid), name, premium, lastIP);
|
||||
} else {
|
||||
profile = new PlayerProfile(null, name, premium, "");
|
||||
}
|
||||
|
||||
storage.save(profile);
|
||||
rows++;
|
||||
}
|
||||
|
||||
return rows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
closeQuietly(resultSet);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,20 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
public enum ImportPlugin {
|
||||
|
||||
AUTO_IN(AutoInImporter.class),
|
||||
|
||||
BPA(BPAImporter.class),
|
||||
|
||||
ELDZI(ElDziAuthImporter.class);
|
||||
|
||||
private final Class<? extends Importer> importerClass;
|
||||
|
||||
ImportPlugin(Class<? extends Importer> importer) {
|
||||
this.importerClass = importer;
|
||||
}
|
||||
|
||||
public Class<? extends Importer> getImporter() {
|
||||
return importerClass;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,21 @@
|
||||
package com.github.games647.fastlogin.core.importer;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import java.sql.Connection;
|
||||
import java.sql.SQLException;
|
||||
import javax.sql.DataSource;
|
||||
|
||||
public abstract class Importer {
|
||||
|
||||
public abstract int importData(Connection source, DataSource target, AuthStorage storage) throws SQLException;
|
||||
|
||||
protected void closeQuietly(AutoCloseable closeable) {
|
||||
if (closeable != null) {
|
||||
try {
|
||||
closeable.close();
|
||||
} catch (Exception ignore) {
|
||||
//ignore
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,198 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.CompatibleCacheBuilder;
|
||||
import com.github.games647.fastlogin.core.SharedConfig;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
import com.github.games647.fastlogin.core.hooks.DefaultPasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.hooks.PasswordGenerator;
|
||||
import com.github.games647.fastlogin.core.importer.AutoInImporter;
|
||||
import com.github.games647.fastlogin.core.importer.ImportPlugin;
|
||||
import com.github.games647.fastlogin.core.importer.Importer;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.io.File;
|
||||
import java.sql.Connection;
|
||||
import java.sql.DriverManager;
|
||||
import java.sql.SQLException;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentHashMap;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
/**
|
||||
* @param <P> Player class
|
||||
*/
|
||||
public abstract class FastLoginCore<P> {
|
||||
|
||||
public static <K, V> ConcurrentMap<K, V> buildCache(int expireAfterWrite, int maxSize) {
|
||||
CompatibleCacheBuilder<Object, Object> builder = CompatibleCacheBuilder.newBuilder();
|
||||
|
||||
if (expireAfterWrite > 0) {
|
||||
builder.expireAfterWrite(expireAfterWrite, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (maxSize > 0) {
|
||||
builder.maximumSize(maxSize);
|
||||
}
|
||||
|
||||
return builder.build(CacheLoader.from(() -> {
|
||||
throw new UnsupportedOperationException();
|
||||
}));
|
||||
}
|
||||
|
||||
public static UUID parseId(String withoutDashes) {
|
||||
if (withoutDashes == null) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return UUID.fromString(withoutDashes.substring(0, 8)
|
||||
+ "-" + withoutDashes.substring(8, 12)
|
||||
+ "-" + withoutDashes.substring(12, 16)
|
||||
+ "-" + withoutDashes.substring(16, 20)
|
||||
+ "-" + withoutDashes.substring(20, 32));
|
||||
}
|
||||
|
||||
protected final Map<String, String> localeMessages = new ConcurrentHashMap<>();
|
||||
|
||||
private final ConcurrentMap<String, Object> pendingLogins = FastLoginCore.buildCache(5, 0);
|
||||
private final Set<UUID> pendingConfirms = Sets.newHashSet();
|
||||
private final SharedConfig sharedConfig;
|
||||
|
||||
private MojangApiConnector apiConnector;
|
||||
private AuthStorage storage;
|
||||
private PasswordGenerator<P> passwordGenerator = new DefaultPasswordGenerator<>();
|
||||
private AuthPlugin<P> authPlugin;
|
||||
|
||||
public FastLoginCore(Map<String, Object> config) {
|
||||
this.sharedConfig = new SharedConfig(config);
|
||||
}
|
||||
|
||||
public void setApiConnector() {
|
||||
List<String> ipAddresses = sharedConfig.get("ip-addresses");
|
||||
int requestLimit = sharedConfig.get("mojang-request-limit");
|
||||
this.apiConnector = makeApiConnector(getLogger(), ipAddresses, requestLimit);
|
||||
}
|
||||
|
||||
public MojangApiConnector getApiConnector() {
|
||||
return apiConnector;
|
||||
}
|
||||
|
||||
public AuthStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
public abstract File getDataFolder();
|
||||
|
||||
public abstract Logger getLogger();
|
||||
|
||||
public abstract ThreadFactory getThreadFactory();
|
||||
|
||||
public String getMessage(String key) {
|
||||
return localeMessages.get(key);
|
||||
}
|
||||
|
||||
public abstract void loadMessages();
|
||||
|
||||
public abstract MojangApiConnector makeApiConnector(Logger logger, List<String> addresses, int requests);
|
||||
|
||||
public boolean setupDatabase() {
|
||||
String driver = sharedConfig.get("driver");
|
||||
String host = sharedConfig.get("host", "");
|
||||
int port = sharedConfig.get("port", 3306);
|
||||
String database = sharedConfig.get("database");
|
||||
|
||||
String user = sharedConfig.get("username", "");
|
||||
String password = sharedConfig.get("password", "");
|
||||
|
||||
storage = new AuthStorage(this, driver, host, port, database, user, password);
|
||||
try {
|
||||
storage.createTables();
|
||||
return true;
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Failed to setup database. Disabling plugin...", ex);
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
public boolean importDatabase(ImportPlugin plugin, boolean sqlite, AuthStorage storage, String host, String database
|
||||
, String username, String pass) {
|
||||
if (sqlite && (plugin == ImportPlugin.BPA || plugin == ImportPlugin.ELDZI)) {
|
||||
throw new IllegalArgumentException("These plugins doesn't support flat file databases");
|
||||
}
|
||||
|
||||
Importer importer;
|
||||
try {
|
||||
importer = plugin.getImporter().newInstance();
|
||||
} catch (Exception ex) {
|
||||
getLogger().log(Level.SEVERE, "Couldn't not setup importer class", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
try {
|
||||
if (sqlite && plugin == ImportPlugin.AUTO_IN) {
|
||||
//load sqlite driver
|
||||
Class.forName("org.sqlite.JDBC");
|
||||
|
||||
String jdbcUrl = "jdbc:sqlite:" + AutoInImporter.getSQLitePath();
|
||||
Connection con = DriverManager.getConnection(jdbcUrl);
|
||||
importer.importData(con, storage.getDataSource(), storage);
|
||||
return true;
|
||||
} else {
|
||||
Class.forName("com.mysql.jdbc.Driver");
|
||||
|
||||
String jdbcUrl = "jdbc:mysql://" + host + "/" + database;
|
||||
Connection con = DriverManager.getConnection(jdbcUrl, username, pass);
|
||||
importer.importData(con, storage.getDataSource(), storage);
|
||||
return true;
|
||||
}
|
||||
} catch (ClassNotFoundException ex) {
|
||||
getLogger().log(Level.SEVERE, "Cannot find SQL driver. Do you removed it?", ex);
|
||||
} catch (SQLException ex) {
|
||||
getLogger().log(Level.SEVERE, "Couldn't import data. Aborting...", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public SharedConfig getConfig() {
|
||||
return sharedConfig;
|
||||
}
|
||||
|
||||
public PasswordGenerator<P> getPasswordGenerator() {
|
||||
return passwordGenerator;
|
||||
}
|
||||
|
||||
public void setPasswordGenerator(PasswordGenerator<P> passwordGenerator) {
|
||||
this.passwordGenerator = passwordGenerator;
|
||||
}
|
||||
|
||||
public ConcurrentMap<String, Object> getPendingLogins() {
|
||||
return pendingLogins;
|
||||
}
|
||||
|
||||
public Set<UUID> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
public AuthPlugin<P> getAuthPluginHook() {
|
||||
return authPlugin;
|
||||
}
|
||||
|
||||
public void setAuthPluginHook(AuthPlugin<P> authPlugin) {
|
||||
this.authPlugin = authPlugin;
|
||||
}
|
||||
|
||||
public void close() {
|
||||
if (storage != null) {
|
||||
storage.close();
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,98 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.SharedConfig;
|
||||
import com.github.games647.fastlogin.core.hooks.AuthPlugin;
|
||||
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public abstract class JoinManagement<T, S extends LoginSource> {
|
||||
|
||||
protected final FastLoginCore<T> core;
|
||||
protected final AuthPlugin<T> authHook;
|
||||
|
||||
public JoinManagement(FastLoginCore<T> core, AuthPlugin<T> authHook) {
|
||||
this.core = core;
|
||||
this.authHook = authHook;
|
||||
}
|
||||
|
||||
public void onLogin(String username, S source) {
|
||||
PlayerProfile profile = core.getStorage().loadProfile(username);
|
||||
if (profile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
SharedConfig config = core.getConfig();
|
||||
|
||||
String ip = source.getAddress().getAddress().getHostAddress();
|
||||
try {
|
||||
if (profile.getUserId() == -1) {
|
||||
if (core.getPendingLogins().containsKey(ip + username) && config.get("secondAttemptCracked", false)) {
|
||||
core.getLogger().log(Level.INFO, "Second attempt login -> cracked {0}", username);
|
||||
|
||||
//first login request failed so make a cracked session
|
||||
startCrackedSession(source, profile, username);
|
||||
return;
|
||||
}
|
||||
|
||||
UUID premiumUUID = null;
|
||||
if (config.get("nameChangeCheck", false) || config.get("autoRegister", false)) {
|
||||
core.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
premiumUUID = core.getApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
if (premiumUUID == null
|
||||
|| (!checkNameChange(source, username, premiumUUID)
|
||||
&& !checkPremiumName(source, username, profile))) {
|
||||
//nothing detected the player as premium -> start a cracked session
|
||||
if (core.getConfig().get("switchMode", false)) {
|
||||
source.kick(core.getMessage("switch-kick-message"));
|
||||
return;
|
||||
}
|
||||
|
||||
startCrackedSession(source, profile, username);
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
requestPremiumLogin(source, profile, username, true);
|
||||
} else {
|
||||
|
||||
startCrackedSession(source, profile, username);
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
core.getLogger().log(Level.SEVERE, "Failed to check premium state", ex);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkPremiumName(S source, String username, PlayerProfile profile) throws Exception {
|
||||
if (core.getConfig().get("autoRegister", false) && (authHook == null || !authHook.isRegistered(username))) {
|
||||
requestPremiumLogin(source, profile, username, false);
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
private boolean checkNameChange(S source, String username, UUID premiumUUID) {
|
||||
//user not exists in the db
|
||||
if (core.getConfig().get("nameChangeCheck", false)) {
|
||||
PlayerProfile profile = core.getStorage().loadProfile(premiumUUID);
|
||||
if (profile != null) {
|
||||
//uuid exists in the database
|
||||
core.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||
|
||||
//update the username to the new one in the database
|
||||
profile.setPlayerName(username);
|
||||
|
||||
requestPremiumLogin(source, profile, username, false);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
public abstract void requestPremiumLogin(S source, PlayerProfile profile, String username, boolean registered);
|
||||
|
||||
public abstract void startCrackedSession(S source, PlayerProfile profile, String username);
|
||||
}
|
||||
@@ -0,0 +1,56 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
public abstract class LoginSession {
|
||||
|
||||
private final String username;
|
||||
private final PlayerProfile profile;
|
||||
|
||||
private UUID uuid;
|
||||
|
||||
protected boolean registered;
|
||||
|
||||
public LoginSession(String username, boolean registered, PlayerProfile profile) {
|
||||
this.username = username;
|
||||
this.registered = registered;
|
||||
this.profile = profile;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
|
||||
/**
|
||||
* This value is always false if we authenticate the player with a cracked authentication
|
||||
*
|
||||
* @return
|
||||
*/
|
||||
public boolean needsRegistration() {
|
||||
return !registered;
|
||||
}
|
||||
|
||||
public PlayerProfile getProfile() {
|
||||
return profile;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the premium UUID of this player
|
||||
*
|
||||
* @return the premium UUID or null if not fetched
|
||||
*/
|
||||
public synchronized UUID getUuid() {
|
||||
return uuid;
|
||||
}
|
||||
|
||||
/**
|
||||
* Set the online UUID if it's fetched
|
||||
*
|
||||
* @param uuid premium UUID
|
||||
*/
|
||||
public synchronized void setUuid(UUID uuid) {
|
||||
this.uuid = uuid;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,12 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import java.net.InetSocketAddress;
|
||||
|
||||
public interface LoginSource {
|
||||
|
||||
void setOnlineMode() throws Exception;
|
||||
|
||||
void kick(String message) throws Exception;
|
||||
|
||||
InetSocketAddress getAddress();
|
||||
}
|
||||
@@ -0,0 +1,158 @@
|
||||
package com.github.games647.fastlogin.core.shared;
|
||||
|
||||
import com.github.games647.fastlogin.core.BalancedSSLFactory;
|
||||
import com.google.common.collect.Sets;
|
||||
import com.google.common.io.CharStreams;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.io.Reader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
import java.util.regex.Pattern;
|
||||
|
||||
import javax.net.ssl.HttpsURLConnection;
|
||||
|
||||
public abstract class MojangApiConnector {
|
||||
|
||||
//http connection, read timeout and user agent for a connection to mojang api servers
|
||||
private static final int TIMEOUT = 3 * 1_000;
|
||||
private static final String USER_AGENT = "Premium-Checker";
|
||||
|
||||
private static final String MCAPI_UUID_URL = "https://mcapi.ca/uuid/player/";
|
||||
|
||||
//only premium (paid account) users have a uuid from here
|
||||
private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
|
||||
//this includes a-zA-Z1-9_
|
||||
private static final String VALID_PLAYERNAME = "^\\w{2,16}$";
|
||||
|
||||
private static final int RATE_LIMIT_CODE = 429;
|
||||
|
||||
//compile the pattern only on plugin enable -> and this have to be threadsafe
|
||||
private final Pattern playernameMatcher = Pattern.compile(VALID_PLAYERNAME);
|
||||
|
||||
private final ConcurrentMap<Object, Object> requests = FastLoginCore.buildCache(10, -1);
|
||||
private final BalancedSSLFactory sslFactory;
|
||||
private final int rateLimit;
|
||||
private long lastRateLimit;
|
||||
|
||||
protected final Logger logger;
|
||||
|
||||
public MojangApiConnector(Logger logger, List<String> localAddresses, int rateLimit) {
|
||||
this.logger = logger;
|
||||
|
||||
if (rateLimit > 600) {
|
||||
this.rateLimit = 600;
|
||||
} else {
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
if (localAddresses.isEmpty()) {
|
||||
this.sslFactory = null;
|
||||
} else {
|
||||
Set<InetAddress> addresses = Sets.newHashSet();
|
||||
for (String localAddress : localAddresses) {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(localAddress);
|
||||
if (!address.isAnyLocalAddress()) {
|
||||
logger.log(Level.WARNING, "Submitted IP-Address is not local {0}", address);
|
||||
continue;
|
||||
}
|
||||
|
||||
addresses.add(address);
|
||||
} catch (UnknownHostException ex) {
|
||||
logger.log(Level.SEVERE, "IP-Address is unknown to us", ex);
|
||||
}
|
||||
}
|
||||
|
||||
this.sslFactory = new BalancedSSLFactory(HttpsURLConnection.getDefaultSSLSocketFactory(), addresses);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
*
|
||||
* @param playerName
|
||||
* @return null on non-premium
|
||||
*/
|
||||
public UUID getPremiumUUID(String playerName) {
|
||||
//check if it's a valid playername
|
||||
if (playernameMatcher.matcher(playerName).matches()) {
|
||||
// only make a API call if the name is valid existing mojang account
|
||||
|
||||
if (requests.size() >= rateLimit || System.currentTimeMillis() - lastRateLimit < 1_000 * 60 * 10) {
|
||||
// plugin.getLogger().fine("STILL WAITING FOR RATE_LIMIT - TRYING Third-party API");
|
||||
return getUUIDFromAPI(playerName);
|
||||
}
|
||||
|
||||
requests.put(new Object(), new Object());
|
||||
|
||||
try {
|
||||
HttpsURLConnection connection = getConnection(UUID_LINK + playerName);
|
||||
if (connection.getResponseCode() == HttpURLConnection.HTTP_OK) {
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(connection.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (!line.equals("null")) {
|
||||
return FastLoginCore.parseId(getUUIDFromJson(line));
|
||||
}
|
||||
} else if (connection.getResponseCode() == RATE_LIMIT_CODE) {
|
||||
logger.info("RATE_LIMIT REACHED - TRYING THIRD-PARTY API");
|
||||
lastRateLimit = System.currentTimeMillis();
|
||||
return getUUIDFromAPI(playerName);
|
||||
}
|
||||
//204 - no content for not found
|
||||
} catch (Exception ex) {
|
||||
logger.log(Level.SEVERE, "Failed to check if player has a paid account", ex);
|
||||
}
|
||||
//this connection doesn't need to be closed. So can make use of keep alive in java
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public UUID getUUIDFromAPI(String playerName) {
|
||||
try {
|
||||
HttpURLConnection httpConnection = getConnection(MCAPI_UUID_URL + playerName);
|
||||
|
||||
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
//cracked
|
||||
return null;
|
||||
}
|
||||
|
||||
Reader reader = new InputStreamReader(httpConnection.getInputStream());
|
||||
String json = CharStreams.toString(reader);
|
||||
return FastLoginCore.parseId(getUUIDFromJson(json));
|
||||
} catch (IOException iOException) {
|
||||
logger.log(Level.SEVERE, "Tried converting name->uuid from third-party api", iOException);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract boolean hasJoinedServer(LoginSession session, String serverId);
|
||||
|
||||
protected abstract String getUUIDFromJson(String json);
|
||||
|
||||
protected HttpsURLConnection getConnection(String url) throws IOException {
|
||||
HttpsURLConnection connection = (HttpsURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(TIMEOUT);
|
||||
connection.setReadTimeout(2 * TIMEOUT);
|
||||
//the new Mojang API just uses json as response
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
|
||||
if (sslFactory != null) {
|
||||
connection.setSSLSocketFactory(sslFactory);
|
||||
}
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
168
core/src/main/resources/config.yml
Normal file
168
core/src/main/resources/config.yml
Normal file
@@ -0,0 +1,168 @@
|
||||
# FastLogin config
|
||||
# Project site: https://www.spigotmc.org/resources/fastlogin.14153
|
||||
# Source code: https://github.com/games647/FastLogin
|
||||
#
|
||||
# You can access the newest config here:
|
||||
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/config.yml
|
||||
|
||||
# Request a premium login without forcing the player to type a command
|
||||
#
|
||||
# If you activate autoRegister, this plugin will check/do these points on login:
|
||||
# 1. An existing cracked account shouldn't exist
|
||||
# -> paid accounts cannot steal the existing account of cracked players
|
||||
# - (Already registered players could still use the /premium command to activate premium checks)
|
||||
# 2. Automatically registers an account with a strong random generated password
|
||||
# -> cracked player cannot register an account for the premium player and so cannot the steal the account
|
||||
#
|
||||
# Furthermore the premium player check have to be made based on the player name
|
||||
# This means if a cracked player connects to the server and we request a paid account login from this player
|
||||
# the player just disconnect and sees the message: 'bad login' or 'invalid session'
|
||||
# There is no way to change this message
|
||||
# For more information: https://github.com/games647/FastLogin#why-do-players-have-to-invoke-a-command
|
||||
autoRegister: false
|
||||
|
||||
# This is extra configuration option to the feature above. If we request a premium authentication from a player who
|
||||
# isn't actual premium but used a premium username, the player will disconnect with the reason "invalid session" or
|
||||
# "bad login".
|
||||
#
|
||||
# If you activate this, we are remembering this player and do not force another premium authentication if the player
|
||||
# tries to join again, so the player could join as cracked player.
|
||||
secondAttemptCracked: false
|
||||
|
||||
# New cracked players will be kicked from server. Good if you want switch from offline-mode to online-mode without
|
||||
# losing players!
|
||||
#
|
||||
# Existing cracked and premium players could still join your server. Moreover you could add playernames to a whitelist.
|
||||
# So that these cracked players could join too although they are new players.
|
||||
switchMode: false
|
||||
|
||||
# If this plugin detected that a player has a premium, it can also set the associated
|
||||
# uuid from that account. So if the players changes their usernames, they will still have
|
||||
# the same playerdata (inventory, permissions, ...)
|
||||
#
|
||||
# Warning: This also means that the UUID will be different if the player is connecting
|
||||
# through a offline mode connection. This **could** cause plugin compatibility issues.
|
||||
#
|
||||
# This is a example and doesn't apply for every plugin.
|
||||
# Example: If you want to ban players who aren't online at the moment, the ban plugin will look
|
||||
# after a offline uuid associated to the player, because the server is in offline mode. Then the premium
|
||||
# players could still join the server, because they have different UUID.
|
||||
#
|
||||
# Moreover you may want to convert the offline UUID to a premium UUID. This will ensure that the player
|
||||
# will have the same inventory, permissions, ... if they switched to premium authentification from offline/cracked
|
||||
# authentification.
|
||||
#
|
||||
# This feature requires Cauldron, Spigot or a fork of Spigot (PaperSpigot, TacoSpigot)
|
||||
premiumUuid: false
|
||||
|
||||
# This will make an additional check (only for player names which are not in the database) against the mojang servers
|
||||
# in order to get the premium UUID. If that premium UUID is in the database, we can assume on sucessful login that the
|
||||
# player changed it's username and we just update the name in the database.
|
||||
# Examples:
|
||||
# #### Case 1
|
||||
# nameChangeCheck = false ----- autoRegister = false
|
||||
#
|
||||
# Player logins as cracked until the player invoked the command /premium. Then we could override the existing database
|
||||
# record.
|
||||
#
|
||||
# #### Case 2
|
||||
#
|
||||
# nameChangeCheck = true ----- autoRegister = false
|
||||
#
|
||||
# Connect the Mojang API and check what UUID the player has (UUID exists => Paid Minecraft account). If that UUID is in
|
||||
# the database it's an **existing player** and FastLogin can **assume** the player is premium and changed the username.
|
||||
# If it's not in the database, it's a new player and **could be a cracked player**. So we just use a offline mode
|
||||
# authentication for this player.
|
||||
#
|
||||
# **Limitation**: Cracked players who uses the new username of a paid account cannot join the server if the database
|
||||
# contains the old name. (Example: The owner of the paid account no longer plays on the server, but changed the username
|
||||
# in the meanwhile).
|
||||
#
|
||||
# #### Case 3
|
||||
#
|
||||
# nameChangeCheck = false ----- autoRegister = true
|
||||
#
|
||||
# We will always request a premium authentication if the username is unknown to us, but is in use by a paid minecraft
|
||||
# account. This means it's kind of a more aggressive check like nameChangeCheck = true and autoRegister = false, because
|
||||
# it request a premium authentication which are completely new to us, that even the premium UUID is not in our database.
|
||||
#
|
||||
# **Limitation**: see below
|
||||
#
|
||||
# #### Case 4
|
||||
#
|
||||
# nameChangeCheck = true ----- autoRegister = true
|
||||
#
|
||||
# Based on autoRegister it checks if the player name is premium and login using a premium authentication. After that
|
||||
# fastlogin receives the premium UUID and can update the database record.
|
||||
#
|
||||
# **Limitation from autoRegister**: New offline players who uses the username of an existing minecraft cannot join the
|
||||
# server.
|
||||
nameChangeCheck: false
|
||||
|
||||
# If your players have a premium account and a skin associated to their account, this plugin
|
||||
# can download the data and set it to the online player.
|
||||
#
|
||||
# Keep in mind that this will only works if the player:
|
||||
# * is the owner of the premium account
|
||||
# * the serverconnection is established through a premium connection (paid account authentification)
|
||||
# * has a skin
|
||||
#
|
||||
# This means this plugin doesn't need to create a new connection to the Mojang servers, because
|
||||
# the skin data is included in the Auth-Verification-Response sent by Mojang. If you want to use for other
|
||||
# players like cracked player, you have to use other plugins.
|
||||
#
|
||||
# If you want to use skins for your cracked player, you need an additional plugin like
|
||||
# ChangeSkin, SkinRestoer, ...
|
||||
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
|
||||
|
||||
# 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
|
||||
# You are allowed to make 600 requests per 10-minutes (60 per minute)
|
||||
# If you own a big server this value could be too low
|
||||
# Once the limit is reached, new players are always logged in as cracked until the rate-limit is expired.
|
||||
# (to the next ten minutes)
|
||||
#
|
||||
# The limit is IP-wide. If you have multiple IPv4-Addreses you specify them here. FastLogin will then use it in rotating
|
||||
# order --> 5 different IP-addreses 5 * 600 per 10 minutes
|
||||
# If this list is empty only the default one will be used
|
||||
#
|
||||
# Lists are created like this:
|
||||
#ip-addresses:
|
||||
# - 192-168-0-2
|
||||
ip-addresses: []
|
||||
|
||||
# How many requests should be established until the plugin uses the third-party API https://mcapi.ca/
|
||||
# Once this number is reached in a range of ten minutes it will start connecting to https://mcapi.ca/ for the next ten minutes
|
||||
# This option exists in order to workaround the rate-limiting. Name -> UUID are fetched in the same way like heads
|
||||
#
|
||||
# If you want to join the discussion visit this: https://github.com/games647/FastLogin/issues/27#issuecomment-226954350
|
||||
mojang-request-limit: 600
|
||||
|
||||
# This disables the auto login from fastlogin. So a premium (like a paid account) authentication is requested, but
|
||||
# the player won't be auto logged into the account.
|
||||
#
|
||||
# This can be used as 2Factor authentication for better security of your accounts. A hacker then needs both passwords.
|
||||
# The password of your minecraft and the password to login in with your auth plugin
|
||||
autoLogin: true
|
||||
|
||||
# Database configuration
|
||||
# Recommened is the use of MariaDB (a better version of MySQL)
|
||||
|
||||
# Single file SQLite database
|
||||
driver: org.sqlite.JDBC
|
||||
# File location
|
||||
database: '{pluginDir}/FastLogin.db'
|
||||
|
||||
# MySQL
|
||||
#driver: com.mysql.jdbc.Driver
|
||||
#host: localhost
|
||||
#port: 3306
|
||||
#database: fastlogin
|
||||
#username: myUser
|
||||
#password: myPassword
|
||||
98
core/src/main/resources/messages.yml
Normal file
98
core/src/main/resources/messages.yml
Normal file
@@ -0,0 +1,98 @@
|
||||
# FastLogin localization
|
||||
# Project site: https://www.spigotmc.org/resources/fastlogin.14153
|
||||
# Source code: https://github.com/games647/FastLogin
|
||||
#
|
||||
# You can access the newest locale here:
|
||||
# https://github.com/games647/FastLogin/blob/master/core/src/main/resources/messages.yml
|
||||
#
|
||||
# You want to have language template? Visit the Github Wiki here:
|
||||
# https://github.com/games647/FastLogin/wiki/English
|
||||
|
||||
# In order to split a message into seperate lines you could just make a new line, but keep the '
|
||||
# Example:
|
||||
# bla: '&aFirst line
|
||||
# Second line
|
||||
# Third line'
|
||||
|
||||
# If you want to disable a message, you can just set it to a empty value.
|
||||
# In this case no message will be sent
|
||||
# Example:
|
||||
# bla: ''
|
||||
|
||||
# ========= Shared (BungeeCord and Bukkit) ============
|
||||
|
||||
# Switch mode is activated and a new (non-whitelist) cracked player tries to join
|
||||
switch-kick-message: '&4Only paid minecraft whitelisted accounts are allowed to join this server'
|
||||
|
||||
# Player activated premium logins in order to skip offline authentication
|
||||
add-premium: '&2Added to the list of premium players'
|
||||
|
||||
# Player activated premium logins in order to skip offline authentication
|
||||
add-premium-other: '&2Player has been added to the premium list'
|
||||
|
||||
# Player is already set be a paid account
|
||||
already-exists: '&4You are already on the premium list'
|
||||
|
||||
# Player is already set be a paid account
|
||||
already-exists-other: '&4Player is already on the premium list'
|
||||
|
||||
# Player was changed to be cracked
|
||||
remove-premium: '&2Removed from the list of premium players'
|
||||
|
||||
# Player is already set to be cracked
|
||||
not-premium: '&4You are not in the premium list'
|
||||
|
||||
# Player is already set to be cracked
|
||||
not-premium-other: '&4Player is not in the premium list'
|
||||
|
||||
# Admin wanted to change the premium of a user that isn't known to the plugin
|
||||
player-unknown: '&4Player not in the database'
|
||||
|
||||
# ========= Bukkit/Spigot/PaperSpigot/TacoSpigot only ================================
|
||||
|
||||
# The user skipped the authentication, because it was a premium player
|
||||
auto-login: '&2Auto logged in'
|
||||
|
||||
# The user was auto registered on the first join. The user account will be registered to protect it from cracked players
|
||||
# The password can be used if the mojang servers are down and you still want your premium users to login (PLANNED)
|
||||
auto-register: '&2Auto registered with password: %password
|
||||
You may want change it?'
|
||||
|
||||
# Player is not able to toggle the premium state of other players
|
||||
no-permission: '&4Not enough permissions'
|
||||
|
||||
# Although the console can toggle the premium state, it's not possible for the console itself.
|
||||
# Because the console is not a user. (obviously, isn't it?)
|
||||
no-console: '&4You are not a player. You cannot toggle the premium state for YOURSELF as a console'
|
||||
|
||||
# The user wants to toggle premium state, but BungeeCord support is enabled. This means the server have to communicate
|
||||
# with the BungeeCord first which will establish a connection with the database server.
|
||||
wait-on-proxy: '&6Sending request...'
|
||||
|
||||
# When ProtocolLib is enabled and the plugin is unable to continue handling a login request after a requested premium
|
||||
# authentication. In this state the client expects a success packet with a encrypted connection or disconnect packet.
|
||||
# So we kick the player, if we cannot encrypt the connection. In other situation (example: premium name check),
|
||||
# the player will be just authenticated as cracked
|
||||
error-kick: '&4Error occured'
|
||||
|
||||
# The server sents a verify token within the premium authentication reqest. If this doesn't match on response,
|
||||
# it could be another client sending malicious packets
|
||||
invalid-verify-token: '&4Invalid token'
|
||||
|
||||
# The client sent no request join server request to the mojang servers which would proof that it's owner of that
|
||||
# acciunt. Only modified clients would do this.
|
||||
invalid-session: '&4Invalid session'
|
||||
|
||||
# The client sent a malicous packet without a login request packet
|
||||
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'
|
||||
|
||||
# ========= Bungee/Waterfall only ================================
|
||||
|
||||
|
||||
99
pom.xml
99
pom.xml
@@ -5,14 +5,14 @@
|
||||
<groupId>com.github.games647</groupId>
|
||||
<!--This have to be in lowercase because it's used by plugin.yml-->
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>FastLogin</name>
|
||||
<version>0.1</version>
|
||||
<version>1.9</version>
|
||||
<inceptionYear>2015</inceptionYear>
|
||||
<url>http://dev.bukkit.org/bukkit-plugins/fastlogin</url>
|
||||
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
|
||||
<description>
|
||||
Automatically logins premium player on a offline mode server
|
||||
Automatically logins premium (paid accounts) player on a offline mode server
|
||||
</description>
|
||||
|
||||
<properties>
|
||||
@@ -21,6 +21,13 @@
|
||||
<outputDir>${basedir}/target</outputDir>
|
||||
</properties>
|
||||
|
||||
<modules>
|
||||
<module>core</module>
|
||||
<module>bukkit</module>
|
||||
<module>bungee</module>
|
||||
<module>universal</module>
|
||||
</modules>
|
||||
|
||||
<issueManagement>
|
||||
<system>GitHub</system>
|
||||
<url>https://github.com/games647/FastLogin/issues</url>
|
||||
@@ -41,24 +48,12 @@
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-compiler-plugin</artifactId>
|
||||
<version>3.2</version>
|
||||
<version>3.5.1</version>
|
||||
<configuration>
|
||||
<!--So many people still use Java 6 ;( http://mcstats.org/global/#Java+Version-->
|
||||
<source>1.8</source>
|
||||
<target>1.8</target>
|
||||
<showWarnings>true</showWarnings>
|
||||
<showDeprecation>true</showDeprecation>
|
||||
<!--false means actual true http://jira.codehaus.org/browse/MCOMPILER-209-->
|
||||
<useIncrementalCompilation>false</useIncrementalCompilation>
|
||||
</configuration>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>2.6</version>
|
||||
<configuration>
|
||||
<outputDirectory>${outputDir}</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
@@ -72,78 +67,12 @@
|
||||
|
||||
<!--Add the license to jar in order to see it in the final jar-->
|
||||
<resource>
|
||||
<directory>${basedir}</directory>
|
||||
<!--Parent folder-->
|
||||
<directory>${basedir}/..</directory>
|
||||
<includes>
|
||||
<include>LICENSE</include>
|
||||
</includes>
|
||||
</resource>
|
||||
</resources>
|
||||
</build>
|
||||
|
||||
<repositories>
|
||||
<!--Bukkit-Server-API -->
|
||||
<repository>
|
||||
<id>spigot-repo</id>
|
||||
<url>https://hub.spigotmc.org/nexus/content/repositories/snapshots/</url>
|
||||
</repository>
|
||||
|
||||
<!--ProtocolLib-->
|
||||
<repository>
|
||||
<id>dmulloy2-repo</id>
|
||||
<url>http://repo.dmulloy2.net/content/groups/public/</url>
|
||||
</repository>
|
||||
|
||||
<!--Authme Reloaded-->
|
||||
<repository>
|
||||
<id>xephi-repo</id>
|
||||
<url>http://ci.xephi.fr/plugin/repository/everything/</url>
|
||||
</repository>
|
||||
|
||||
<!--xAuth-->
|
||||
<repository>
|
||||
<id>luricos.de-repo</id>
|
||||
<url>http://repo.luricos.de/bukkit-plugins/</url>
|
||||
</repository>
|
||||
</repositories>
|
||||
|
||||
<dependencies>
|
||||
<!--Server API-->
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot</artifactId>
|
||||
<version>1.8.8-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
<!--Library for listening and sending Minecraft packets-->
|
||||
<dependency>
|
||||
<groupId>com.comphenix.protocol</groupId>
|
||||
<artifactId>ProtocolLib</artifactId>
|
||||
<version>3.6.3-SNAPSHOT</version>
|
||||
<optional>true</optional>
|
||||
</dependency>
|
||||
|
||||
<!--Login Plugins-->
|
||||
<dependency>
|
||||
<groupId>fr.xephi</groupId>
|
||||
<artifactId>authme</artifactId>
|
||||
<version>5.0-SNAPSHOT</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>de.luricos.bukkit</groupId>
|
||||
<artifactId>xAuth</artifactId>
|
||||
<version>2.6</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>net.gravitydevelopment.updater</groupId>
|
||||
<artifactId>updater</artifactId>
|
||||
</exclusion>
|
||||
<exclusion>
|
||||
<groupId>net.ess3</groupId>
|
||||
<artifactId>EssentialsGroupManager</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
|
||||
@@ -1,93 +0,0 @@
|
||||
package com.github.games647.fastlogin;
|
||||
|
||||
import com.github.games647.fastlogin.listener.PlayerListener;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.github.games647.fastlogin.listener.EncryptionPacketListener;
|
||||
import com.github.games647.fastlogin.listener.StartPacketListener;
|
||||
import com.google.common.cache.Cache;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.URL;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
import org.bukkit.plugin.java.JavaPlugin;
|
||||
|
||||
public class FastLogin extends JavaPlugin {
|
||||
|
||||
private final KeyPair keyPair = generateKey();
|
||||
private final Cache<String, PlayerData> session = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(2, TimeUnit.MINUTES)
|
||||
.build();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
if (!isEnabled()) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (!getServer().getPluginManager().isPluginEnabled("AuthMe")
|
||||
&& !getServer().getPluginManager().isPluginEnabled("xAuth")) {
|
||||
getLogger().warning("No support offline Auth plugin found. ");
|
||||
getLogger().warning("Disabling this plugin...");
|
||||
|
||||
setEnabled(false);
|
||||
return;
|
||||
}
|
||||
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
protocolManager.addPacketListener(new EncryptionPacketListener(this, protocolManager));
|
||||
protocolManager.addPacketListener(new StartPacketListener(this, protocolManager));
|
||||
|
||||
getServer().getPluginManager().registerEvents(new PlayerListener(this), this);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onLoad() {
|
||||
//online mode is only changeable aftter a restart
|
||||
if (getServer().getOnlineMode()) {
|
||||
getLogger().severe("Server have to be in offline mode");
|
||||
|
||||
setEnabled(false);
|
||||
}
|
||||
|
||||
generateKey();
|
||||
}
|
||||
|
||||
private KeyPair generateKey() {
|
||||
try {
|
||||
KeyPairGenerator keypairgenerator = KeyPairGenerator.getInstance("RSA");
|
||||
|
||||
keypairgenerator.initialize(1024);
|
||||
return keypairgenerator.generateKeyPair();
|
||||
} catch (NoSuchAlgorithmException noSuchAlgorithmException) {
|
||||
//Should be default existing in every vm
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public Cache<String, PlayerData> getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public KeyPair getKeyPair() {
|
||||
return keyPair;
|
||||
}
|
||||
|
||||
public HttpURLConnection getConnection(String url) throws IOException {
|
||||
final HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
connection.setConnectTimeout(15000);
|
||||
connection.setReadTimeout(15000);
|
||||
connection.setRequestProperty("Content-Type", "application/json");
|
||||
connection.setRequestProperty("User-Agent", "Premium-Checker");
|
||||
|
||||
return connection;
|
||||
}
|
||||
}
|
||||
@@ -1,20 +0,0 @@
|
||||
package com.github.games647.fastlogin;
|
||||
|
||||
public class PlayerData {
|
||||
|
||||
private final byte[] verifyToken;
|
||||
private final String username;
|
||||
|
||||
public PlayerData(byte[] verifyToken, String username) {
|
||||
this.username = username;
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
|
||||
public byte[] getVerifyToken() {
|
||||
return verifyToken;
|
||||
}
|
||||
|
||||
public String getUsername() {
|
||||
return username;
|
||||
}
|
||||
}
|
||||
@@ -1,145 +0,0 @@
|
||||
package com.github.games647.fastlogin.listener;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.server.SocketInjector;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
import com.comphenix.protocol.wrappers.WrappedGameProfile;
|
||||
import com.github.games647.fastlogin.FastLogin;
|
||||
import com.github.games647.fastlogin.PlayerData;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.lang.reflect.Field;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.math.BigInteger;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.PrivateKey;
|
||||
import java.util.Arrays;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import javax.crypto.SecretKey;
|
||||
|
||||
import net.minecraft.server.v1_8_R3.MinecraftEncryption;
|
||||
import net.minecraft.server.v1_8_R3.NetworkManager;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.json.simple.JSONObject;
|
||||
import org.json.simple.JSONValue;
|
||||
|
||||
public class EncryptionPacketListener extends PacketAdapter {
|
||||
|
||||
private static final String HAS_JOINED_URL = "https://sessionserver.mojang.com/session/minecraft/hasJoined?";
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
private final FastLogin fastLogin;
|
||||
|
||||
public EncryptionPacketListener(FastLogin plugin, ProtocolManager protocolManger) {
|
||||
super(params(plugin, PacketType.Login.Client.ENCRYPTION_BEGIN).optionAsync());
|
||||
|
||||
this.fastLogin = plugin;
|
||||
this.protocolManager = protocolManger;
|
||||
}
|
||||
|
||||
/*
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent event) {
|
||||
PacketContainer packet = event.getPacket();
|
||||
Player player = event.getPlayer();
|
||||
|
||||
final byte[] sharedSecret = packet.getByteArrays().read(0);
|
||||
byte[] clientVerify = packet.getByteArrays().read(1);
|
||||
|
||||
PrivateKey privateKey = fastLogin.getKeyPair().getPrivate();
|
||||
|
||||
String addressString = player.getAddress().toString();
|
||||
PlayerData cachedEntry = fastLogin.getSession().asMap().get(addressString);
|
||||
byte[] serverVerify = cachedEntry.getVerifyToken();
|
||||
if (!Arrays.equals(serverVerify, MinecraftEncryption.b(privateKey, clientVerify))) {
|
||||
player.kickPlayer("Invalid token");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//encrypt all following packets
|
||||
NetworkManager networkManager = getNetworkManager(event);
|
||||
SecretKey loginKey = MinecraftEncryption.a(privateKey, sharedSecret);
|
||||
networkManager.a(loginKey);
|
||||
String serverId = (new BigInteger(MinecraftEncryption.a("", fastLogin.getKeyPair().getPublic(), loginKey)))
|
||||
.toString(16);
|
||||
|
||||
String username = cachedEntry.getUsername();
|
||||
if (!hasJoinedServer(username, serverId)) {
|
||||
//user tried to fake a authentification
|
||||
player.kickPlayer("Invalid session");
|
||||
event.setCancelled(true);
|
||||
return;
|
||||
}
|
||||
|
||||
//fake a new login packet
|
||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START, true);
|
||||
WrappedGameProfile fakeProfile = WrappedGameProfile.fromOfflinePlayer(Bukkit.getOfflinePlayer(username));
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
try {
|
||||
protocolManager.recieveClientPacket(event.getPlayer(), startPacket, false);
|
||||
} catch (InvocationTargetException | IllegalAccessException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, null, ex);
|
||||
}
|
||||
|
||||
event.setCancelled(true);
|
||||
}
|
||||
|
||||
private NetworkManager getNetworkManager(PacketEvent event) throws IllegalArgumentException {
|
||||
SocketInjector injector = TemporaryPlayerFactory.getInjectorFromPlayer(event.getPlayer());
|
||||
NetworkManager networkManager = null;
|
||||
try {
|
||||
Field declaredField = injector.getClass().getDeclaredField("injector");
|
||||
declaredField.setAccessible(true);
|
||||
|
||||
Object rawInjector = declaredField.get(injector);
|
||||
|
||||
declaredField = rawInjector.getClass().getDeclaredField("networkManager");
|
||||
declaredField.setAccessible(true);
|
||||
networkManager = (NetworkManager) declaredField.get(rawInjector);
|
||||
} catch (IllegalAccessException | NoSuchFieldException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, null, ex);
|
||||
}
|
||||
|
||||
return networkManager;
|
||||
}
|
||||
|
||||
private boolean hasJoinedServer(String username, String serverId) {
|
||||
try {
|
||||
String url = HAS_JOINED_URL + "username=" + username + "&serverId=" + serverId;
|
||||
|
||||
HttpURLConnection conn = fastLogin.getConnection(url);
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(conn.getInputStream()));
|
||||
String line = reader.readLine();
|
||||
if (!line.equals("null")) {
|
||||
JSONObject object = (JSONObject) JSONValue.parse(line);
|
||||
String uuid = (String) object.get("id");
|
||||
String name = (String) object.get("name");
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.WARNING, null, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,59 +0,0 @@
|
||||
package com.github.games647.fastlogin.listener;
|
||||
|
||||
import com.github.games647.fastlogin.FastLogin;
|
||||
|
||||
import de.luricos.bukkit.xAuth.xAuth;
|
||||
import de.luricos.bukkit.xAuth.xAuthPlayer;
|
||||
import de.luricos.bukkit.xAuth.xAuthPlayer.Status;
|
||||
|
||||
import fr.xephi.authme.api.NewAPI;
|
||||
import fr.xephi.authme.cache.limbo.LimboCache;
|
||||
|
||||
import java.sql.Timestamp;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.bukkit.event.EventHandler;
|
||||
import org.bukkit.event.Listener;
|
||||
import org.bukkit.event.player.PlayerJoinEvent;
|
||||
|
||||
public class PlayerListener implements Listener {
|
||||
|
||||
private final FastLogin plugin;
|
||||
|
||||
public PlayerListener(FastLogin plugin) {
|
||||
this.plugin = plugin;
|
||||
}
|
||||
|
||||
@EventHandler(ignoreCancelled = true)
|
||||
public void onJoin(PlayerJoinEvent joinEvent) {
|
||||
final Player player = joinEvent.getPlayer();
|
||||
String address = player.getAddress().toString();
|
||||
if (plugin.getSession().asMap().containsKey(address)) {
|
||||
Bukkit.getScheduler().runTaskLater(plugin, () -> {
|
||||
doLogin(player);
|
||||
}, 1 * 20L);
|
||||
}
|
||||
}
|
||||
|
||||
private void doLogin(Player player) {
|
||||
if (Bukkit.getPluginManager().isPluginEnabled("AuthMe")) {
|
||||
//add cache entry - otherwise loggin wouldn't work
|
||||
LimboCache.getInstance().addLimboPlayer(player);
|
||||
|
||||
//skips registration and login
|
||||
NewAPI.getInstance().forceLogin(player);
|
||||
} else if (Bukkit.getPluginManager().isPluginEnabled("xAuth")) {
|
||||
xAuth xAuthPlugin = xAuth.getPlugin();
|
||||
|
||||
xAuthPlayer xAuthPlayer = xAuthPlugin.getPlayerManager().getPlayer(player);
|
||||
xAuthPlayer.setPremium(true);
|
||||
xAuthPlugin.getAuthClass(xAuthPlayer).online(xAuthPlayer.getName());
|
||||
xAuthPlayer.setLoginTime(new Timestamp(System.currentTimeMillis()));
|
||||
|
||||
xAuthPlayer.setStatus(Status.AUTHENTICATED);
|
||||
|
||||
xAuthPlugin.getPlayerManager().unprotect(xAuthPlayer);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,90 +0,0 @@
|
||||
package com.github.games647.fastlogin.listener;
|
||||
|
||||
import com.comphenix.protocol.PacketType;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.events.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.github.games647.fastlogin.FastLogin;
|
||||
import com.github.games647.fastlogin.PlayerData;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.security.PublicKey;
|
||||
|
||||
import java.util.Random;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class StartPacketListener extends PacketAdapter {
|
||||
|
||||
//only premium members have a uuid from there
|
||||
private static final String UUID_LINK = "https://api.mojang.com/users/profiles/minecraft/";
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
private final FastLogin fastLogin;
|
||||
|
||||
private final Random random = new Random();
|
||||
|
||||
public StartPacketListener(FastLogin plugin, ProtocolManager protocolManger) {
|
||||
super(params(plugin, PacketType.Login.Client.START).optionAsync());
|
||||
|
||||
this.fastLogin = plugin;
|
||||
this.protocolManager = protocolManger;
|
||||
}
|
||||
|
||||
/*
|
||||
* C->S : Handshake State=2
|
||||
* C->S : Login Start
|
||||
* S->C : Encryption Key Request
|
||||
* (Client Auth)
|
||||
* C->S : Encryption Key Response
|
||||
* (Server Auth, Both enable encryption)
|
||||
* S->C : Login Success (*)
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
PacketContainer packet = packetEvent.getPacket();
|
||||
Player player = packetEvent.getPlayer();
|
||||
|
||||
String username = packet.getGameProfiles().read(0).getName();
|
||||
if (isPremium(username)) {
|
||||
//do premium login process
|
||||
try {
|
||||
PacketContainer newPacket = protocolManager.createPacket(PacketType.Login.Server.ENCRYPTION_BEGIN, true);
|
||||
|
||||
//constr ServerID=""
|
||||
//public key=plugin.getPublic
|
||||
newPacket.getSpecificModifier(PublicKey.class).write(0, fastLogin.getKeyPair().getPublic());
|
||||
byte[] verifyToken = new byte[4];
|
||||
random.nextBytes(verifyToken);
|
||||
newPacket.getByteArrays().write(0, verifyToken);
|
||||
|
||||
String addressString = player.getAddress().toString();
|
||||
fastLogin.getSession().asMap().put(addressString, new PlayerData(verifyToken, username));
|
||||
|
||||
protocolManager.sendServerPacket(player, newPacket, false);
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
//cancel only if the player is premium
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean isPremium(String playerName) {
|
||||
try {
|
||||
final HttpURLConnection connection = fastLogin.getConnection(UUID_LINK + playerName);
|
||||
final int responseCode = connection.getResponseCode();
|
||||
|
||||
return responseCode == HttpURLConnection.HTTP_OK;
|
||||
} catch (IOException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, null, ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -1,14 +0,0 @@
|
||||
# project informations for Bukkit in order to register our plugin with all it components
|
||||
# ${project.name} are variables from Maven (pom.xml) which will be replaced after the build
|
||||
name: ${project.name}
|
||||
version: ${project.version}
|
||||
main: ${project.groupId}.${project.artifactId}.${project.name}
|
||||
|
||||
# meta informations for plugin managers
|
||||
authors: [Xeroun, games647, 'https://github.com/games647/FastLogin/graphs/contributors']
|
||||
description: |
|
||||
${project.description}
|
||||
website: ${project.url}
|
||||
dev-url: ${project.url}
|
||||
|
||||
depend: [ProtocolLib]
|
||||
77
universal/pom.xml
Normal file
77
universal/pom.xml
Normal file
@@ -0,0 +1,77 @@
|
||||
<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">
|
||||
<modelVersion>4.0.0</modelVersion>
|
||||
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.9</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
<artifactId>fastlogin-universal</artifactId>
|
||||
<packaging>jar</packaging>
|
||||
|
||||
<name>FastLoginUniversal</name>
|
||||
|
||||
<build>
|
||||
<defaultGoal>package</defaultGoal>
|
||||
<finalName>${project.parent.name}</finalName>
|
||||
|
||||
<plugins>
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-shade-plugin</artifactId>
|
||||
<version>2.4.3</version>
|
||||
<configuration>
|
||||
<createDependencyReducedPom>false</createDependencyReducedPom>
|
||||
<shadedArtifactAttached>false</shadedArtifactAttached>
|
||||
<artifactSet>
|
||||
<includes>
|
||||
<include>${project.groupId}:*</include>
|
||||
<include>com.zaxxer:HikariCP</include>
|
||||
<include>org.slf4j:*</include>
|
||||
</includes>
|
||||
</artifactSet>
|
||||
</configuration>
|
||||
<executions>
|
||||
<execution>
|
||||
<phase>package</phase>
|
||||
<goals>
|
||||
<goal>shade</goal>
|
||||
</goals>
|
||||
</execution>
|
||||
</executions>
|
||||
</plugin>
|
||||
|
||||
<plugin>
|
||||
<groupId>org.apache.maven.plugins</groupId>
|
||||
<artifactId>maven-jar-plugin</artifactId>
|
||||
<version>3.0.2</version>
|
||||
<configuration>
|
||||
<outputDirectory>${outputDir}</outputDirectory>
|
||||
</configuration>
|
||||
</plugin>
|
||||
</plugins>
|
||||
</build>
|
||||
|
||||
<dependencies>
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.core</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.bukkit</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>${project.groupId}</groupId>
|
||||
<artifactId>fastlogin.bungee</artifactId>
|
||||
<version>${project.version}</version>
|
||||
</dependency>
|
||||
</dependencies>
|
||||
</project>
|
||||
Reference in New Issue
Block a user