mirror of
https://github.com/TuxCoding/FastLogin.git
synced 2025-12-23 23:28:08 +01:00
Compare commits
49 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
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 |
43
CHANGELOG.md
43
CHANGELOG.md
@@ -1,7 +1,50 @@
|
||||
######1.8
|
||||
|
||||
* Fix third-party not premium player detection
|
||||
* Fix ProtocolSupport BungeeCord
|
||||
|
||||
######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
|
||||
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -22,6 +22,12 @@
|
||||
<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>
|
||||
@@ -64,7 +70,7 @@
|
||||
<dependency>
|
||||
<groupId>org.spigotmc</groupId>
|
||||
<artifactId>spigot-api</artifactId>
|
||||
<version>1.9-R0.1-SNAPSHOT</version>
|
||||
<version>1.10-R0.1-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
|
||||
@@ -99,9 +105,10 @@
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<!--Oringal owner is lenis, but maven always uses the newest version-->
|
||||
<groupId>com.github.lenis0012</groupId>
|
||||
<artifactId>LoginSecurity-2</artifactId>
|
||||
<!--Old version 2.0-->
|
||||
<!--Old version 2.0 -->
|
||||
<version>-9c09e73b7f-1</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
@@ -111,6 +118,18 @@
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>com.lenis0012.bukkit</groupId>
|
||||
<artifactId>loginsecurity</artifactId>
|
||||
<version>2.1.3-SNAPSHOT</version>
|
||||
<exclusions>
|
||||
<exclusion>
|
||||
<groupId>*</groupId>
|
||||
<artifactId>*</artifactId>
|
||||
</exclusion>
|
||||
</exclusions>
|
||||
</dependency>
|
||||
|
||||
<dependency>
|
||||
<groupId>io.github.lucaseasedup.logit</groupId>
|
||||
<artifactId>LogIt</artifactId>
|
||||
|
||||
@@ -1,9 +1,11 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.google.common.util.concurrent.ThreadFactoryBuilder;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.concurrent.ThreadFactory;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
@@ -44,9 +46,21 @@ public class BukkitCore extends FastLoginCore {
|
||||
|
||||
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);
|
||||
for (String key : defaults.getKeys(false)) {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', defaults.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
localeMessages.put(key, message);
|
||||
}
|
||||
}
|
||||
|
||||
for (String key : messageConfig.getKeys(false)) {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
if (message.isEmpty()) {
|
||||
localeMessages.remove(key);
|
||||
} else {
|
||||
localeMessages.put(key, message);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,6 +1,5 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
import com.github.games647.fastlogin.core.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
@@ -19,9 +18,11 @@ public class BukkitLoginSession extends LoginSession {
|
||||
private final byte[] verifyToken;
|
||||
|
||||
private UUID uuid;
|
||||
private WrappedSignedProperty skinProperty;
|
||||
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);
|
||||
@@ -64,22 +65,23 @@ public class BukkitLoginSession extends LoginSession {
|
||||
return ArrayUtils.clone(verifyToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the premium skin of this player
|
||||
*
|
||||
* @return skin property or null if the player has no skin or is a cracked account
|
||||
*/
|
||||
public synchronized WrappedSignedProperty getSkin() {
|
||||
return this.skinProperty;
|
||||
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 skinProperty premium skin property
|
||||
* @param encodedData
|
||||
* @param skinSignature
|
||||
*/
|
||||
public synchronized void setSkin(WrappedSignedProperty skinProperty) {
|
||||
this.skinProperty = skinProperty;
|
||||
public synchronized void setSkin(String encodedData, String skinSignature) {
|
||||
this.encodedSkinData = encodedData;
|
||||
this.skinSignature = skinSignature;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,322 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
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 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<K, V>();
|
||||
}
|
||||
|
||||
private 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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,23 +1,26 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.tasks.DelayedAuthHook;
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.comphenix.protocol.AsynchronousManager;
|
||||
import com.comphenix.protocol.ProtocolLibrary;
|
||||
import com.comphenix.protocol.ProtocolManager;
|
||||
import com.comphenix.protocol.utility.SafeCacheBuilder;
|
||||
import com.github.games647.fastlogin.bukkit.commands.CrackedCommand;
|
||||
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.ProtocolSupportListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.packet.EncryptionPacketListener;
|
||||
import com.github.games647.fastlogin.bukkit.listener.packet.StartPacketListener;
|
||||
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.FastLoginCore;
|
||||
import com.google.common.cache.CacheLoader;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.security.KeyPair;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
@@ -30,7 +33,7 @@ import org.bukkit.plugin.java.JavaPlugin;
|
||||
*/
|
||||
public class FastLoginBukkit extends JavaPlugin {
|
||||
|
||||
private static final int WORKER_THREADS = 5;
|
||||
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();
|
||||
@@ -39,31 +42,26 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
private final FastLoginCore core = new BukkitCore(this);
|
||||
private boolean serverStarted;
|
||||
|
||||
private final Set<UUID> pendingConfirms = Sets.newHashSet();
|
||||
|
||||
//this map is thread-safe for async access (Packet Listener)
|
||||
//SafeCacheBuilder is used in order to be version independent
|
||||
private final ConcurrentMap<String, BukkitLoginSession> session = SafeCacheBuilder.<String, BukkitLoginSession>newBuilder()
|
||||
//2 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
.expireAfterWrite(1, TimeUnit.MINUTES)
|
||||
//mapped by ip:port -> PlayerSession
|
||||
.build(new CacheLoader<String, BukkitLoginSession>() {
|
||||
|
||||
@Override
|
||||
public BukkitLoginSession load(String key) throws Exception {
|
||||
//A key should be inserted manually on start packet
|
||||
throw new UnsupportedOperationException("Not supported");
|
||||
}
|
||||
});
|
||||
private final ConcurrentMap<String, BukkitLoginSession> session = buildCache(1, -1);
|
||||
//1 minutes should be enough as a timeout for bad internet connection (Server, Client and Mojang)
|
||||
|
||||
private BukkitAuthPlugin authPlugin;
|
||||
private PasswordGenerator passwordGenerator = new DefaultPasswordGenerator();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
core.setMojangApiConnector(new MojangApiBukkit(core));
|
||||
|
||||
core.loadConfig();
|
||||
core.loadMessages();
|
||||
|
||||
List<String> ipAddresses = getConfig().getStringList("ip-addresses");
|
||||
int requestLimit = getConfig().getInt("mojang-request-limit");
|
||||
MojangApiBukkit mojangApi = new MojangApiBukkit(buildCache(10, -1), getLogger(), ipAddresses, requestLimit);
|
||||
core.setMojangApiConnector(mojangApi);
|
||||
|
||||
try {
|
||||
if (ClassUtil.isPresent("org.spigotmc.SpigotConfig")) {
|
||||
bungeeCord = Class.forName("org.spigotmc.SpigotConfig").getDeclaredField("bungee").getBoolean(null);
|
||||
@@ -103,17 +101,19 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
|
||||
if (getServer().getPluginManager().isPluginEnabled("ProtocolSupport")) {
|
||||
getServer().getPluginManager().registerEvents(new ProtocolSupportListener(this), this);
|
||||
} else {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
} 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 = protocolManager.getAsynchronousManager();
|
||||
AsynchronousManager asynchronousManager = ProtocolLibrary.getProtocolManager().getAsynchronousManager();
|
||||
|
||||
StartPacketListener startPacketListener = new StartPacketListener(this, protocolManager);
|
||||
EncryptionPacketListener encryptionPacketListener = new EncryptionPacketListener(this, protocolManager);
|
||||
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");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -132,14 +132,14 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
//clean up
|
||||
session.clear();
|
||||
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
|
||||
//remove old blacklists
|
||||
for (Player player : getServer().getOnlinePlayers()) {
|
||||
player.removeMetadata(getName(), this);
|
||||
}
|
||||
|
||||
if (core != null) {
|
||||
core.close();
|
||||
}
|
||||
}
|
||||
|
||||
public FastLoginCore getCore() {
|
||||
@@ -209,9 +209,32 @@ public class FastLoginBukkit extends JavaPlugin {
|
||||
return serverStarted;
|
||||
}
|
||||
|
||||
public Set<UUID> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
public void setServerStarted() {
|
||||
if (!this.serverStarted) {
|
||||
this.serverStarted = true;
|
||||
}
|
||||
}
|
||||
|
||||
private <K, V> ConcurrentMap<K, V> buildCache(int minutes, int maxSize) {
|
||||
CompatibleCacheBuilder<Object, Object> builder = CompatibleCacheBuilder.newBuilder();
|
||||
|
||||
if (minutes > 0) {
|
||||
builder.expireAfterWrite(minutes, TimeUnit.MINUTES);
|
||||
}
|
||||
|
||||
if (maxSize > 0) {
|
||||
builder.maximumSize(maxSize);
|
||||
}
|
||||
|
||||
return builder.build(new CacheLoader<K, V>() {
|
||||
@Override
|
||||
public V load(K key) throws Exception {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,14 +1,16 @@
|
||||
package com.github.games647.fastlogin.bukkit;
|
||||
|
||||
import com.comphenix.protocol.wrappers.WrappedSignedProperty;
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.MojangApiConnector;
|
||||
|
||||
import java.io.BufferedReader;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Level;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import org.json.simple.JSONArray;
|
||||
import org.json.simple.JSONObject;
|
||||
@@ -19,8 +21,9 @@ 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(FastLoginCore plugin) {
|
||||
super(plugin);
|
||||
public MojangApiBukkit(ConcurrentMap<Object, Object> requests, Logger logger, List<String> localAddresses
|
||||
, int rateLimit) {
|
||||
super(requests, logger, localAddresses, rateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
@@ -50,14 +53,14 @@ public class MojangApiBukkit extends MojangApiConnector {
|
||||
if (propertyName.equals("textures")) {
|
||||
String skinValue = (String) skinProperty.get("value");
|
||||
String signature = (String) skinProperty.get("signature");
|
||||
playerSession.setSkin(WrappedSignedProperty.fromValues(propertyName, skinValue, signature));
|
||||
playerSession.setSkin(skinValue, signature);
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
//catch not only ioexceptions also parse and NPE on unexpected json format
|
||||
plugin.getLogger().log(Level.WARNING, "Failed to verify session", ex);
|
||||
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
|
||||
@@ -66,8 +69,21 @@ public class MojangApiBukkit extends MojangApiConnector {
|
||||
|
||||
@Override
|
||||
protected UUID getUUIDFromJson(String json) {
|
||||
JSONObject userData = (JSONObject) JSONValue.parse(json);
|
||||
String uuid = (String) userData.get("id");
|
||||
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 (uuid == null || uuid.equals("null")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return FastLoginCore.parseId(uuid);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,7 +6,6 @@ import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -36,10 +35,10 @@ public class CrackedCommand implements CommandExecutor {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async if it's not in the cache anymore
|
||||
//todo: load async if
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
|
||||
sender.sendMessage(plugin.getCore().getMessage("remove-premium"));
|
||||
profile.setPremium(false);
|
||||
profile.setUuid(null);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@@ -49,54 +48,58 @@ public class CrackedCommand implements CommandExecutor {
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You are not in the premium list");
|
||||
sender.sendMessage(plugin.getCore().getMessage("not-premium"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord(sender, args[0]);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async if it's not in the cache anymore
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Removed from the list of premium players");
|
||||
profile.setPremium(false);
|
||||
profile.setUuid(null);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Player is not in the premium list");
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
notifiyBungeeCord(sender, args[0]);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async
|
||||
final 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("remove-premium"));
|
||||
profile.setPremium(false);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
} else {
|
||||
sender.sendMessage(plugin.getCore().getMessage("not-premium-other"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(CommandSender sender, String target) {
|
||||
if (sender instanceof Player) {
|
||||
notifiyBungeeCord((Player) sender, target);
|
||||
} else {
|
||||
plugin.getLogger().info("No player online to send a plugin message to the proxy");
|
||||
//todo: add console support
|
||||
// Player firstPlayer = Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
|
||||
// notifiyBungeeCord(firstPlayer, target);
|
||||
@@ -109,7 +112,7 @@ public class CrackedCommand implements CommandExecutor {
|
||||
dataOutput.writeUTF("OFF");
|
||||
dataOutput.writeUTF(target);
|
||||
|
||||
plugin.getLogger().info("No player online to send a plugin message to the proxy");
|
||||
sender.sendPluginMessage(plugin, plugin.getName(), dataOutput.toByteArray());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,8 +5,9 @@ import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
import java.util.UUID;
|
||||
|
||||
import org.bukkit.Bukkit;
|
||||
import org.bukkit.ChatColor;
|
||||
import org.bukkit.command.Command;
|
||||
import org.bukkit.command.CommandExecutor;
|
||||
import org.bukkit.command.CommandSender;
|
||||
@@ -41,10 +42,18 @@ public class PremiumCommand implements CommandExecutor {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
// //todo: load async if it's not in the cache anymore
|
||||
UUID id = ((Player) sender).getUniqueId();
|
||||
if (plugin.getConfig().getBoolean("premium-warning") && !plugin.getPendingConfirms().contains(id)) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("premium-warning"));
|
||||
plugin.getPendingConfirms().add(id);
|
||||
return true;
|
||||
}
|
||||
|
||||
plugin.getPendingConfirms().remove(id);
|
||||
//todo: load async
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(sender.getName());
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "You are already on the premium list");
|
||||
sender.sendMessage(plugin.getCore().getMessage("already-exists"));
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
@@ -55,55 +64,60 @@ public class PremiumCommand implements CommandExecutor {
|
||||
}
|
||||
});
|
||||
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
|
||||
sender.sendMessage(plugin.getCore().getMessage("add-premium"));
|
||||
}
|
||||
}
|
||||
|
||||
return true;
|
||||
} else {
|
||||
if (!sender.hasPermission(command.getPermission() + ".other")) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("no-permission"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (plugin.isBungeeCord()) {
|
||||
notifiyBungeeCord(sender, args[0]);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async if it's not in the cache anymore
|
||||
final PlayerProfile profile = plugin.getCore().getStorage().loadProfile(args[0]);
|
||||
if (profile == null) {
|
||||
sender.sendMessage(plugin.getCore().getMessage("player-unknown"));
|
||||
return true;
|
||||
}
|
||||
|
||||
if (profile.isPremium()) {
|
||||
sender.sendMessage(ChatColor.DARK_RED + "Player is already on the premium list");
|
||||
} else {
|
||||
//todo: resolve uuid
|
||||
profile.setPremium(true);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
|
||||
sender.sendMessage(ChatColor.DARK_GREEN + "Added to the list of premium players");
|
||||
}
|
||||
}
|
||||
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()) {
|
||||
notifiyBungeeCord(sender, args[0]);
|
||||
String message = plugin.getCore().getMessage("wait-on-proxy");
|
||||
if (message != null) {
|
||||
sender.sendMessage(message);
|
||||
}
|
||||
} else {
|
||||
//todo: load async
|
||||
final 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, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
plugin.getCore().getStorage().save(profile);
|
||||
}
|
||||
});
|
||||
|
||||
sender.sendMessage(plugin.getCore().getMessage("add-premium-other"));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void notifiyBungeeCord(CommandSender sender, String target) {
|
||||
if (sender instanceof Player) {
|
||||
notifiyBungeeCord((Player) sender, target);
|
||||
} else {
|
||||
plugin.getLogger().info("No player online to send a plugin message to the proxy");
|
||||
//todo: add console support
|
||||
// Player firstPlayer = Iterables.getFirst(Bukkit.getOnlinePlayers(), null);
|
||||
// notifiyBungeeCord(firstPlayer, target);
|
||||
|
||||
@@ -1,7 +1,5 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.comphenix.protocol.reflect.FuzzyReflection;
|
||||
|
||||
import de.st_ddt.crazylogin.CrazyLogin;
|
||||
import de.st_ddt.crazylogin.data.LoginPlayerData;
|
||||
import de.st_ddt.crazylogin.databases.CrazyLoginDataDatabase;
|
||||
@@ -13,6 +11,7 @@ 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;
|
||||
|
||||
@@ -108,7 +107,7 @@ public class CrazyLoginHook implements BukkitAuthPlugin {
|
||||
private PlayerListener getListener() {
|
||||
PlayerListener listener;
|
||||
try {
|
||||
listener = FuzzyReflection.getFieldValue(crazyLoginPlugin, PlayerListener.class, true);
|
||||
listener = (PlayerListener) FieldUtils.readField(crazyLoginPlugin, "playerListener", true);
|
||||
} catch (Exception ex) {
|
||||
crazyLoginPlugin.getLogger().log(Level.SEVERE, "Failed to get the listener instance for auto login", ex);
|
||||
listener = null;
|
||||
|
||||
@@ -1424,4 +1424,34 @@ public class FakePlayer implements Player {
|
||||
public boolean isCollidable() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSound(Sound sound) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void stopSound(String sound) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isSilent() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setSilent(boolean flag) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasGravity() {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setGravity(boolean gravity) {
|
||||
throw new UnsupportedOperationException("Not supported yet.");
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,7 +1,13 @@
|
||||
package com.github.games647.fastlogin.bukkit.hooks;
|
||||
|
||||
import com.avaje.ebeaninternal.api.ClassUtil;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.google.common.base.Charsets;
|
||||
import com.lenis0012.bukkit.ls.LoginSecurity;
|
||||
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 com.lenis0012.bukkit.ls.data.DataManager;
|
||||
|
||||
import java.net.InetAddress;
|
||||
@@ -17,24 +23,56 @@ 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/
|
||||
*
|
||||
* on join:
|
||||
* https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L282
|
||||
* Bukkit: http://dev.bukkit.org/bukkit-plugins/loginsecurity/
|
||||
* Spigot: https://www.spigotmc.org/resources/loginsecurity.19362/
|
||||
*/
|
||||
public class LoginSecurityHook implements BukkitAuthPlugin {
|
||||
|
||||
protected final LoginSecurity securityPlugin = LoginSecurity.instance;
|
||||
// protected final boolean newVersion;
|
||||
protected final com.lenis0012.bukkit.ls.LoginSecurity securityPlugin;
|
||||
protected final FastLoginBukkit plugin = (FastLoginBukkit) Bukkit.getPluginManager().getPlugin("FastLogin");
|
||||
protected final boolean newVersion;
|
||||
|
||||
public LoginSecurityHook() {
|
||||
// this.newVersion = ClassUtil.isPresent("com.lenis0012.bukkit.loginsecurity.session.action.LoginAction");
|
||||
this.newVersion = ClassUtil.isPresent("com.lenis0012.bukkit.loginsecurity.LoginSecurity", getClass());
|
||||
if (newVersion) {
|
||||
this.securityPlugin = null;
|
||||
} else {
|
||||
this.securityPlugin = com.lenis0012.bukkit.ls.LoginSecurity.instance;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceLogin(final Player player) {
|
||||
//Login command of this plugin: (How the plugin logs the player in)
|
||||
public boolean forceLogin(Player player) {
|
||||
if (!newVersion) {
|
||||
return oldForceLogin(player);
|
||||
}
|
||||
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
return session.performAction(new LoginAction(AuthService.PLUGIN, plugin)).isSuccess();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
if (!newVersion) {
|
||||
return oldIsRegistred(playerName);
|
||||
}
|
||||
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getOfflineSession(playerName);
|
||||
return session.isRegistered();
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
if (!newVersion) {
|
||||
return oldForceRegister(player, password);
|
||||
}
|
||||
|
||||
PlayerSession session = LoginSecurity.getSessionManager().getPlayerSession(player);
|
||||
return session.performAction(new RegisterAction(AuthService.PLUGIN, plugin, password)).isSuccess();
|
||||
}
|
||||
|
||||
public boolean oldForceLogin(final Player player) {
|
||||
//Login command of this plugin: (How the plugin logs the player in)
|
||||
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/commands/LoginCommand.java#L39
|
||||
|
||||
//not thread-safe operation
|
||||
@@ -62,20 +100,16 @@ public class LoginSecurityHook implements BukkitAuthPlugin {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean isRegistered(String playerName) throws Exception {
|
||||
public boolean oldIsRegistred(String playerName) throws Exception {
|
||||
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L296
|
||||
DataManager dataManager = securityPlugin.data;
|
||||
|
||||
//https://github.com/lenis0012/LoginSecurity-2/blob/master/src/main/java/com/lenis0012/bukkit/ls/LoginSecurity.java#L283
|
||||
UUID offlineUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName).getBytes(Charsets.UTF_8));
|
||||
return dataManager.isRegistered(offlineUuid.toString().replace("-", ""));
|
||||
//check for loginsecurity sessions in order to prevent a sql query?
|
||||
//sesUse && thread.getSession().containsKey(uuid) && checkLastIp(player)) {
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean forceRegister(Player player, String password) {
|
||||
public boolean oldForceRegister(Player player, String password) {
|
||||
DataManager dataManager = securityPlugin.data;
|
||||
|
||||
UUID playerUUID = player.getUniqueId();
|
||||
|
||||
@@ -1,8 +1,5 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
|
||||
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 com.github.games647.fastlogin.bukkit.tasks.ForceLoginTask;
|
||||
|
||||
@@ -14,6 +11,7 @@ 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
|
||||
@@ -32,7 +30,7 @@ public class BukkitJoinListener implements Listener {
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void onPlayerLogin(PlayerLoginEvent loginEvent) {
|
||||
if (loginEvent.getResult() == Result.ALLOWED && !plugin.isServerFullyStarted()) {
|
||||
loginEvent.disallow(Result.KICK_OTHER, "§cServer is not fully started yet. Please retry");
|
||||
loginEvent.disallow(Result.KICK_OTHER, plugin.getCore().getMessage("not-started"));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,18 +38,16 @@ public class BukkitJoinListener implements Listener {
|
||||
public void onPlayerJoin(PlayerJoinEvent joinEvent) {
|
||||
Player player = joinEvent.getPlayer();
|
||||
|
||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||
if (session != null && plugin.getConfig().getBoolean("forwardSkin")) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
WrappedSignedProperty skin = session.getSkin();
|
||||
if (skin != null) {
|
||||
gameProfile.getProperties().put("textures", skin);
|
||||
}
|
||||
}
|
||||
|
||||
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.getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -42,7 +42,7 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onPluginMessageReceived(String channel, Player player, byte[] message) {
|
||||
public void onPluginMessageReceived(String channel, final Player player, byte[] message) {
|
||||
if (!channel.equals(plugin.getName())) {
|
||||
return;
|
||||
}
|
||||
@@ -75,6 +75,7 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
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, new Runnable() {
|
||||
@Override
|
||||
@@ -82,10 +83,11 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
try {
|
||||
//we need to check if the player is registered on Bukkit too
|
||||
if (authPlugin != null && !authPlugin.isRegistered(playerName)) {
|
||||
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);
|
||||
@@ -93,8 +95,6 @@ public class BungeeCordListener implements PluginMessageListener {
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, new ForceLoginTask(plugin, player));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -1,174 +0,0 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
||||
|
||||
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.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
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 {
|
||||
|
||||
private static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
//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, ProtocolManager protocolManger) {
|
||||
//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;
|
||||
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 (*)
|
||||
*
|
||||
* On offline logins is Login Start followed by Login Success
|
||||
*/
|
||||
@Override
|
||||
public void onPacketReceiving(PacketEvent packetEvent) {
|
||||
plugin.setServerStarted();
|
||||
|
||||
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 to the server"
|
||||
, new Object[]{sessionKey, username});
|
||||
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
if (authPlugin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
|
||||
if (profile != null) {
|
||||
if (profile.getUserId() == -1) {
|
||||
UUID premiumUUID = null;
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
|
||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
//user not exists in the db
|
||||
try {
|
||||
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
|
||||
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
||||
if (profile != null) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (premiumUUID != null
|
||||
&& plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, false);
|
||||
return;
|
||||
}
|
||||
|
||||
//no premium check passed so we save it as a cracked player
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(sessionKey, loginSession);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
enablePremiumLogin(username, profile, sessionKey, player, packetEvent, true);
|
||||
} else {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(sessionKey, loginSession);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//minecraft server implementation
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
private void enablePremiumLogin(String username, PlayerProfile profile, String sessionKey, Player player
|
||||
, PacketEvent packetEvent, boolean registered) {
|
||||
//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/
|
||||
String serverId = Long.toString(random.nextLong(), 16);
|
||||
|
||||
//generate a random token which should be the same when we receive it from the client
|
||||
byte[] verifyToken = new byte[VERIFY_TOKEN_LENGTH];
|
||||
random.nextBytes(verifyToken);
|
||||
|
||||
boolean success = sentEncryptionRequest(player, serverId, verifyToken);
|
||||
if (success) {
|
||||
BukkitLoginSession playerSession = new BukkitLoginSession(username, serverId
|
||||
, verifyToken, registered, profile);
|
||||
plugin.getSessions().put(sessionKey, playerSession);
|
||||
//cancel only if the player has a paid account otherwise login as normal offline player
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
|
||||
try {
|
||||
/**
|
||||
* 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);
|
||||
return true;
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -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,72 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocollib;
|
||||
|
||||
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.lang.reflect.Method;
|
||||
import java.util.Collection;
|
||||
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;
|
||||
|
||||
public class LoginSkinApplyListener implements Listener {
|
||||
|
||||
private static final Class<?> GAME_PROFILE = MinecraftReflection.getGameProfileClass();
|
||||
|
||||
private static final MethodAccessor GET_PROPERTIES = Accessors.getMethodAcccessorOrNull(
|
||||
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) {
|
||||
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
|
||||
Collection<BukkitLoginSession> sessions = plugin.getSessions().values();
|
||||
for (BukkitLoginSession session : sessions) {
|
||||
if (session.getUsername().equals(player.getName())) {
|
||||
applySkin(player, session);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void applySkin(Player player, BukkitLoginSession session) {
|
||||
WrappedGameProfile gameProfile = WrappedGameProfile.fromPlayer(player);
|
||||
String skinData = session.getEncodedSkinData();
|
||||
String signature = session.getSkinSignature();
|
||||
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 {
|
||||
Method putMethod = map.getClass().getMethod("put", Object.class, Object.class);
|
||||
putMethod.invoke(map, "textures", skin.getHandle());
|
||||
} catch (NoSuchMethodException | IllegalAccessException | InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error setting premium skin", ex);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,141 @@
|
||||
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.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
|
||||
import java.lang.reflect.InvocationTargetException;
|
||||
import java.security.PublicKey;
|
||||
import java.util.Random;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import org.bukkit.entity.Player;
|
||||
|
||||
public class NameCheckTask implements Runnable {
|
||||
|
||||
private static final int VERIFY_TOKEN_LENGTH = 4;
|
||||
|
||||
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) {
|
||||
this.plugin = plugin;
|
||||
this.packetEvent = packetEvent;
|
||||
this.random = random;
|
||||
this.player = player;
|
||||
this.username = username;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
try {
|
||||
nameCheck();
|
||||
} finally {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void nameCheck() {
|
||||
PlayerProfile profile = plugin.getCore().getStorage().loadProfile(username);
|
||||
if (profile == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (profile.getUserId() == -1) {
|
||||
UUID premiumUUID = null;
|
||||
|
||||
//user not exists in the db
|
||||
try {
|
||||
boolean isRegistered = plugin.getAuthPlugin().isRegistered(username);
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck")
|
||||
|| (plugin.getConfig().getBoolean("autoRegister") && !isRegistered)) {
|
||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
|
||||
PlayerProfile uuidProfile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
||||
if (uuidProfile != null) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} changed it's username", premiumUUID);
|
||||
enablePremiumLogin(uuidProfile, false);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
if (premiumUUID != null && plugin.getConfig().getBoolean("autoRegister") && !isRegistered) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
enablePremiumLogin(profile, false);
|
||||
return;
|
||||
}
|
||||
|
||||
//no premium check passed so we save it as a cracked player
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(player.getAddress().toString(), loginSession);
|
||||
} catch (Exception ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to query isRegistered", ex);
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
enablePremiumLogin(profile, true);
|
||||
} else {
|
||||
BukkitLoginSession loginSession = new BukkitLoginSession(username, profile);
|
||||
plugin.getSessions().put(player.getAddress().toString(), loginSession);
|
||||
}
|
||||
}
|
||||
|
||||
//minecraft server implementation
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/LoginListener.java#L161
|
||||
private void enablePremiumLogin(PlayerProfile profile, boolean registered) {
|
||||
//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/
|
||||
String serverId = Long.toString(random.nextLong(), 16);
|
||||
|
||||
//generate a random token which should be the same when we receive it from the client
|
||||
byte[] verify = new byte[VERIFY_TOKEN_LENGTH];
|
||||
random.nextBytes(verify);
|
||||
|
||||
boolean success = sentEncryptionRequest(player, serverId, verify);
|
||||
if (success) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean sentEncryptionRequest(Player player, String serverId, byte[] verifyToken) {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
try {
|
||||
/**
|
||||
* 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);
|
||||
return true;
|
||||
} catch (InvocationTargetException ex) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Cannot send encryption packet. Falling back to normal login", ex);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,84 @@
|
||||
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 com.github.games647.fastlogin.bukkit.hooks.BukkitAuthPlugin;
|
||||
|
||||
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()) {
|
||||
return;
|
||||
}
|
||||
|
||||
plugin.setServerStarted();
|
||||
|
||||
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});
|
||||
|
||||
BukkitAuthPlugin authPlugin = plugin.getAuthPlugin();
|
||||
if (authPlugin == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
packetEvent.getAsyncMarker().incrementProcessingDelay();
|
||||
NameCheckTask nameCheckTask = new NameCheckTask(plugin, packetEvent, random, player, username);
|
||||
Bukkit.getScheduler().runTaskAsynchronously(plugin, nameCheckTask);
|
||||
}
|
||||
}
|
||||
@@ -1,8 +1,8 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener.packet;
|
||||
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.PacketAdapter;
|
||||
import com.comphenix.protocol.events.PacketContainer;
|
||||
import com.comphenix.protocol.events.PacketEvent;
|
||||
import com.comphenix.protocol.injector.server.TemporaryPlayerFactory;
|
||||
@@ -26,65 +26,42 @@ import javax.crypto.SecretKey;
|
||||
|
||||
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 {
|
||||
public class VerifyResponseTask implements Runnable {
|
||||
|
||||
private final ProtocolManager protocolManager;
|
||||
//hides the inherit Plugin plugin field, but we need this type
|
||||
private final FastLoginBukkit plugin;
|
||||
private final PacketEvent packetEvent;
|
||||
|
||||
public EncryptionPacketListener(FastLoginBukkit plugin, ProtocolManager protocolManger) {
|
||||
//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());
|
||||
private final Player fromPlayer;
|
||||
|
||||
private final byte[] sharedSecret;
|
||||
|
||||
public VerifyResponseTask(FastLoginBukkit plugin, PacketEvent packetEvent, Player fromPlayer, byte[] sharedSecret) {
|
||||
this.plugin = plugin;
|
||||
this.protocolManager = protocolManger;
|
||||
this.packetEvent = packetEvent;
|
||||
this.fromPlayer = fromPlayer;
|
||||
this.sharedSecret = sharedSecret;
|
||||
}
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
Player player = packetEvent.getPlayer();
|
||||
|
||||
BukkitLoginSession session = plugin.getSessions().get(player.getAddress().toString());
|
||||
if (session == null) {
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-requst"), true
|
||||
, "Player {0} tried to send encryption response at invalid state", player.getAddress());
|
||||
return;
|
||||
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 {
|
||||
verifyResponse(session);
|
||||
}
|
||||
} finally {
|
||||
ProtocolLibrary.getProtocolManager().getAsynchronousManager().signalPacketTransmission(packetEvent);
|
||||
}
|
||||
}
|
||||
|
||||
private void verifyResponse(BukkitLoginSession session) {
|
||||
PrivateKey privateKey = plugin.getServerKey().getPrivate();
|
||||
|
||||
byte[] sharedSecret = packetEvent.getPacket().getByteArrays().read(0);
|
||||
SecretKey loginKey = EncryptionUtil.decryptSharedKey(privateKey, sharedSecret);
|
||||
if (!checkVerifyToken(session, privateKey, packetEvent) || !encryptConnection(player, loginKey, packetEvent)) {
|
||||
if (!checkVerifyToken(session, privateKey) || !encryptConnection(loginKey)) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -102,34 +79,35 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
plugin.getLogger().log(Level.FINE, "Player {0} has a verified premium account", username);
|
||||
|
||||
session.setVerified(true);
|
||||
setPremiumUUID(session, player);
|
||||
receiveFakeStartPacket(username, player);
|
||||
setPremiumUUID(session.getUuid());
|
||||
receiveFakeStartPacket(username);
|
||||
} else {
|
||||
//user tried to fake a authentication
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("invalid-session"), true
|
||||
disconnect(plugin.getCore().getMessage("invalid-session"), true
|
||||
, "Player {0} ({1}) tried to log in with an invalid session ServerId: {2}"
|
||||
, session.getUsername(), player.getAddress(), serverId);
|
||||
, session.getUsername(), fromPlayer.getAddress(), serverId);
|
||||
}
|
||||
|
||||
//this is a fake packet; it shouldn't be send to the server
|
||||
packetEvent.setCancelled(true);
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
private void setPremiumUUID(BukkitLoginSession session, Player player) {
|
||||
UUID uuid = session.getUuid();
|
||||
if (plugin.getConfig().getBoolean("premiumUuid") && uuid != null) {
|
||||
private void setPremiumUUID(UUID premiumUUID) {
|
||||
if (plugin.getConfig().getBoolean("premiumUuid") && premiumUUID != null) {
|
||||
try {
|
||||
Object networkManager = getNetworkManager(player);
|
||||
Object networkManager = getNetworkManager();
|
||||
//https://github.com/bergerkiller/CraftSource/blob/master/net.minecraft.server/NetworkManager.java#L69
|
||||
Field spoofField = FuzzyReflection.fromObject(networkManager).getFieldByType("spoofedUUID", UUID.class);
|
||||
spoofField.set(networkManager, uuid);
|
||||
spoofField.set(networkManager, premiumUUID);
|
||||
} catch (ReflectiveOperationException reflectiveOperationException) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Error setting premium uuid", reflectiveOperationException);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey, PacketEvent packetEvent) {
|
||||
private boolean checkVerifyToken(BukkitLoginSession session, PrivateKey privateKey) {
|
||||
byte[] requestVerify = session.getVerifyToken();
|
||||
//encrypted verify token
|
||||
byte[] responseVerify = packetEvent.getPacket().getByteArrays().read(1);
|
||||
@@ -137,7 +115,7 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
//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(packetEvent, plugin.getCore().getMessage("invalid-verify-token"), true
|
||||
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;
|
||||
@@ -147,9 +125,8 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
}
|
||||
|
||||
//try to get the networkManager from ProtocolLib
|
||||
private Object getNetworkManager(Player player)
|
||||
throws IllegalAccessException, NoSuchFieldException {
|
||||
Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(player);
|
||||
private Object getNetworkManager() throws IllegalAccessException, NoSuchFieldException {
|
||||
Object socketInjector = TemporaryPlayerFactory.getInjectorFromPlayer(fromPlayer);
|
||||
Field injectorField = socketInjector.getClass().getDeclaredField("injector");
|
||||
injectorField.setAccessible(true);
|
||||
|
||||
@@ -160,11 +137,10 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
return injectorField.get(rawInjector);
|
||||
}
|
||||
|
||||
private boolean encryptConnection(Player player, SecretKey loginKey, PacketEvent packetEvent)
|
||||
throws IllegalArgumentException {
|
||||
private boolean encryptConnection(SecretKey loginKey) throws IllegalArgumentException {
|
||||
try {
|
||||
//get the NMS connection handle of this player
|
||||
Object networkManager = getNetworkManager(player);
|
||||
Object networkManager = getNetworkManager();
|
||||
|
||||
//try to detect the method by parameters
|
||||
Method encryptConnectionMethod = FuzzyReflection
|
||||
@@ -174,16 +150,15 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
//the client expects this behaviour
|
||||
encryptConnectionMethod.invoke(networkManager, loginKey);
|
||||
} catch (ReflectiveOperationException ex) {
|
||||
disconnect(packetEvent, plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption", ex);
|
||||
disconnect(plugin.getCore().getMessage("error-kick"), false, "Couldn't enable encryption", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
private void disconnect(PacketEvent packetEvent, String kickReason, boolean debugLevel, String logMessage
|
||||
, Object... arguments) {
|
||||
if (debugLevel) {
|
||||
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);
|
||||
@@ -191,10 +166,14 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
|
||||
kickPlayer(packetEvent.getPlayer(), kickReason);
|
||||
//cancel the event in order to prevent the server receiving an invalid packet
|
||||
packetEvent.setCancelled(true);
|
||||
synchronized (packetEvent.getAsyncMarker().getProcessingLock()) {
|
||||
packetEvent.setCancelled(true);
|
||||
}
|
||||
}
|
||||
|
||||
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));
|
||||
|
||||
@@ -210,7 +189,9 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
}
|
||||
|
||||
//fake a new login packet in order to let the server handle all the other stuff
|
||||
private void receiveFakeStartPacket(String username, Player from) {
|
||||
private void receiveFakeStartPacket(String username) {
|
||||
ProtocolManager protocolManager = ProtocolLibrary.getProtocolManager();
|
||||
|
||||
//see StartPacketListener for packet information
|
||||
PacketContainer startPacket = protocolManager.createPacket(PacketType.Login.Client.START);
|
||||
|
||||
@@ -219,11 +200,11 @@ public class EncryptionPacketListener extends PacketAdapter {
|
||||
startPacket.getGameProfiles().write(0, fakeProfile);
|
||||
try {
|
||||
//we don't want to handle our own packets so ignore filters
|
||||
protocolManager.recieveClientPacket(from, startPacket, false);
|
||||
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(from, plugin.getCore().getMessage("error-kick"));
|
||||
kickPlayer(fromPlayer, plugin.getCore().getMessage("error-kick"));
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -1,4 +1,4 @@
|
||||
package com.github.games647.fastlogin.bukkit.listener;
|
||||
package com.github.games647.fastlogin.bukkit.listener.protocolsupport;
|
||||
|
||||
import com.github.games647.fastlogin.bukkit.BukkitLoginSession;
|
||||
import com.github.games647.fastlogin.bukkit.FastLoginBukkit;
|
||||
@@ -44,12 +44,15 @@ public class ProtocolSupportListener implements Listener {
|
||||
if (profile != null) {
|
||||
if (profile.getUserId() == -1) {
|
||||
UUID premiumUUID = null;
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck") || plugin.getConfig().getBoolean("autoRegister")) {
|
||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
//user not exists in the db
|
||||
try {
|
||||
boolean isRegistered = plugin.getAuthPlugin().isRegistered(username);
|
||||
if (plugin.getConfig().getBoolean("nameChangeCheck")
|
||||
|| (plugin.getConfig().getBoolean("autoRegister") && !isRegistered)) {
|
||||
premiumUUID = plugin.getCore().getMojangApiConnector().getPremiumUUID(username);
|
||||
}
|
||||
|
||||
if (premiumUUID != null && plugin.getConfig().getBoolean("nameChangeCheck")) {
|
||||
profile = plugin.getCore().getStorage().loadProfile(premiumUUID);
|
||||
if (profile != null) {
|
||||
@@ -59,8 +62,7 @@ public class ProtocolSupportListener implements Listener {
|
||||
}
|
||||
}
|
||||
|
||||
if (premiumUUID != null
|
||||
&& plugin.getConfig().getBoolean("autoRegister") && !authPlugin.isRegistered(username)) {
|
||||
if (premiumUUID != null && plugin.getConfig().getBoolean("autoRegister") && !isRegistered) {
|
||||
plugin.getLogger().log(Level.FINER, "Player {0} uses a premium username", username);
|
||||
startPremiumSession(username, loginStartEvent, false, profile);
|
||||
return;
|
||||
@@ -3,8 +3,8 @@ 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.bukkit.hooks.BukkitAuthPlugin;
|
||||
import com.github.games647.fastlogin.core.AuthStorage;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
import com.github.games647.fastlogin.core.Storage;
|
||||
import com.google.common.io.ByteArrayDataOutput;
|
||||
import com.google.common.io.ByteStreams;
|
||||
|
||||
@@ -39,7 +39,7 @@ public class ForceLoginTask implements Runnable {
|
||||
return;
|
||||
}
|
||||
|
||||
Storage storage = plugin.getCore().getStorage();
|
||||
AuthStorage storage = plugin.getCore().getStorage();
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
|
||||
//check if it's the same player as we checked before
|
||||
@@ -64,7 +64,7 @@ public class ForceLoginTask implements Runnable {
|
||||
if (playerProfile != null) {
|
||||
playerProfile.setUuid(session.getUuid());
|
||||
//save cracked players too
|
||||
playerProfile.setPremium(session.isVerified());
|
||||
playerProfile.setPremium(true);
|
||||
storage.save(playerProfile);
|
||||
}
|
||||
|
||||
@@ -87,7 +87,7 @@ public class ForceLoginTask implements Runnable {
|
||||
String generatedPassword = plugin.generateStringPassword(player);
|
||||
boolean success = authPlugin.forceRegister(player, generatedPassword);
|
||||
String message = plugin.getCore().getMessage("auto-register");
|
||||
if (message != null) {
|
||||
if (success && message != null) {
|
||||
message = message.replace("%password", generatedPassword);
|
||||
player.sendMessage(message);
|
||||
}
|
||||
@@ -100,7 +100,7 @@ public class ForceLoginTask implements Runnable {
|
||||
boolean success = authPlugin.forceLogin(player);
|
||||
|
||||
String message = plugin.getCore().getMessage("auto-login");
|
||||
if (message != null) {
|
||||
if (success && message != null) {
|
||||
player.sendMessage(message);
|
||||
}
|
||||
|
||||
|
||||
@@ -14,12 +14,11 @@ dev-url: ${project.url}
|
||||
# Load the plugin as early as possible to inject it for all players
|
||||
load: STARTUP
|
||||
|
||||
# Without Protocollib the plugin does not work at all
|
||||
depend: [ProtocolLib]
|
||||
|
||||
softdepend:
|
||||
# We require either ProtocolLib or ProtocolSupport
|
||||
- ProtocolSupport
|
||||
# Auth plugins
|
||||
- ProtocolLib
|
||||
# Auth plugins - we delay the hook
|
||||
# - xAuth
|
||||
# - AuthMe
|
||||
# - LogIt
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -19,8 +19,8 @@
|
||||
<repositories>
|
||||
<!--BungeeCord with also the part outside the API-->
|
||||
<repository>
|
||||
<id>RYRED-REPO</id>
|
||||
<url>http://mvn.ryred.co/repository/snapshots/</url>
|
||||
<id>myplayplanet-REPO</id>
|
||||
<url>http://maven.myplayplanet.net/</url>
|
||||
</repository>
|
||||
|
||||
<!--Github automatic maven builds-->
|
||||
@@ -41,10 +41,10 @@
|
||||
<dependency>
|
||||
<groupId>net.md-5</groupId>
|
||||
<artifactId>bungeecord-proxy</artifactId>
|
||||
<version>1.9-SNAPSHOT</version>
|
||||
<version>1.8-SNAPSHOT</version>
|
||||
<scope>provided</scope>
|
||||
</dependency>
|
||||
<!-- <dependency>
|
||||
<!-- <dependency>
|
||||
<groupId>io.github.waterfallmc</groupId>
|
||||
<artifactId>waterfall-api</artifactId>
|
||||
<version>1.9-SNAPSHOT</version>
|
||||
|
||||
@@ -49,9 +49,13 @@ public class BungeeCore extends FastLoginCore {
|
||||
public void loadMessages() {
|
||||
try {
|
||||
saveDefaultFile("messages.yml");
|
||||
|
||||
|
||||
Configuration defaults = ConfigurationProvider.getProvider(YamlConfiguration.class)
|
||||
.load(getClass().getResourceAsStream("/messages.yml"));
|
||||
|
||||
File messageFile = new File(getDataFolder(), "messages.yml");
|
||||
Configuration messageConfig = ConfigurationProvider.getProvider(YamlConfiguration.class).load(messageFile);
|
||||
Configuration messageConfig = ConfigurationProvider.getProvider(YamlConfiguration.class)
|
||||
.load(messageFile, defaults);
|
||||
for (String key : messageConfig.getKeys()) {
|
||||
String message = ChatColor.translateAlternateColorCodes('&', messageConfig.getString(key));
|
||||
if (!message.isEmpty()) {
|
||||
|
||||
@@ -5,12 +5,18 @@ 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.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.google.common.cache.CacheBuilder;
|
||||
import com.google.common.collect.Maps;
|
||||
import com.google.common.collect.Sets;
|
||||
|
||||
import java.io.File;
|
||||
import java.io.IOException;
|
||||
import java.util.List;
|
||||
import java.util.Random;
|
||||
import java.util.Set;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
import java.util.logging.Level;
|
||||
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
@@ -29,30 +35,36 @@ public class FastLoginBungee extends Plugin {
|
||||
|
||||
private final FastLoginCore loginCore = new BungeeCore(this);
|
||||
private BungeeAuthPlugin bungeeAuthPlugin;
|
||||
private Configuration configuration;
|
||||
private Configuration config;
|
||||
|
||||
private final Random random = new Random();
|
||||
private final Set<UUID> pendingConfirms = Sets.newHashSet();
|
||||
|
||||
private final ConcurrentMap<PendingConnection, BungeeLoginSession> session = Maps.newConcurrentMap();
|
||||
|
||||
@Override
|
||||
public void onEnable() {
|
||||
loginCore.setMojangApiConnector(new MojangApiBungee(loginCore));
|
||||
|
||||
loginCore.loadConfig();
|
||||
loginCore.loadMessages();
|
||||
|
||||
try {
|
||||
File configFile = new File(getDataFolder(), "config.yml");
|
||||
configuration = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
|
||||
config = ConfigurationProvider.getProvider(YamlConfiguration.class).load(configFile);
|
||||
|
||||
String driver = configuration.getString("driver");
|
||||
String host = configuration.getString("host", "");
|
||||
int port = configuration.getInt("port", 3306);
|
||||
String database = configuration.getString("database");
|
||||
List<String> ipAddresses = getConfig().getStringList("ip-addresses");
|
||||
int requestLimit = getConfig().getInt("mojang-request-limit");
|
||||
ConcurrentMap<Object, Object> requestCache = CacheBuilder.newBuilder()
|
||||
.expireAfterWrite(10, TimeUnit.MINUTES).build().asMap();
|
||||
MojangApiBungee mojangApi = new MojangApiBungee(requestCache, getLogger(), ipAddresses, requestLimit);
|
||||
loginCore.setMojangApiConnector(mojangApi);
|
||||
|
||||
String username = configuration.getString("username", "");
|
||||
String password = configuration.getString("password", "");
|
||||
String driver = config.getString("driver");
|
||||
String host = config.getString("host", "");
|
||||
int port = config.getInt("port", 3306);
|
||||
String database = config.getString("database");
|
||||
|
||||
String username = config.getString("username", "");
|
||||
String password = config.getString("password", "");
|
||||
if (!loginCore.setupDatabase(driver, host, port, database, username, password)) {
|
||||
return;
|
||||
}
|
||||
@@ -94,13 +106,17 @@ public class FastLoginBungee extends Plugin {
|
||||
}
|
||||
|
||||
public Configuration getConfig() {
|
||||
return configuration;
|
||||
return config;
|
||||
}
|
||||
|
||||
public ConcurrentMap<PendingConnection, BungeeLoginSession> getSession() {
|
||||
return session;
|
||||
}
|
||||
|
||||
public Set<UUID> getPendingConfirms() {
|
||||
return pendingConfirms;
|
||||
}
|
||||
|
||||
/**
|
||||
* Get the auth plugin hook for BungeeCord
|
||||
*
|
||||
|
||||
@@ -3,19 +3,35 @@ package com.github.games647.fastlogin.bungee;
|
||||
import com.github.games647.fastlogin.core.FastLoginCore;
|
||||
import com.github.games647.fastlogin.core.MojangApiConnector;
|
||||
|
||||
import java.util.List;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ConcurrentMap;
|
||||
import java.util.logging.Logger;
|
||||
|
||||
import net.md_5.bungee.BungeeCord;
|
||||
|
||||
public class MojangApiBungee extends MojangApiConnector {
|
||||
|
||||
public MojangApiBungee(FastLoginCore plugin) {
|
||||
super(plugin);
|
||||
public MojangApiBungee(ConcurrentMap<Object, Object> requests, Logger logger, List<String> localAddresses
|
||||
, int rateLimit) {
|
||||
super(requests, logger, localAddresses, rateLimit);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected UUID getUUIDFromJson(String json) {
|
||||
MojangPlayer mojangPlayer = BungeeCord.getInstance().gson.fromJson(json, MojangPlayer.class);
|
||||
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);
|
||||
}
|
||||
|
||||
if (mojangPlayer.getId() == null || mojangPlayer.getId().equals("null")) {
|
||||
return null;
|
||||
}
|
||||
|
||||
return FastLoginCore.parseId(mojangPlayer.getId());
|
||||
}
|
||||
|
||||
|
||||
@@ -4,6 +4,7 @@ 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;
|
||||
@@ -41,7 +42,7 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
//proparly not thread-safe
|
||||
ListenerClass.prelogin.get(player.getName()).cancel();
|
||||
} catch (Exception ex) {
|
||||
Main.plugin.getLogger().severe("[BungeeAuth] Error force loging in player");
|
||||
Main.plugin.getLogger().log(Level.SEVERE, "Error force loging in player", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -86,7 +87,7 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
//proparly not thread-safe
|
||||
forceLogin(player);
|
||||
} catch (Exception ex) {
|
||||
Main.plugin.getLogger().severe("[BungeeAuth] Error when creating a new player in the Database");
|
||||
Main.plugin.getLogger().log(Level.SEVERE, "[BungeeAuth] Error when creating a new player in the Database", ex);
|
||||
return false;
|
||||
}
|
||||
|
||||
@@ -95,7 +96,7 @@ public class BungeeAuthHook implements BungeeAuthPlugin {
|
||||
|
||||
//pail ;(
|
||||
private void callProtected(String methodName, Class<?>[] parameterTypes, Object[] arguments) throws Exception {
|
||||
Class<? extends Tables> tableClass = databaseConnection.getClass();
|
||||
Class<Tables> tableClass = Tables.class;
|
||||
|
||||
Method method = tableClass.getDeclaredMethod(methodName, parameterTypes);
|
||||
method.setAccessible(true);
|
||||
|
||||
@@ -1,7 +1,7 @@
|
||||
package com.github.games647.fastlogin.bungee.listener;
|
||||
|
||||
import com.github.games647.fastlogin.bungee.tasks.AsyncPremiumCheck;
|
||||
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.LoginSession;
|
||||
import com.github.games647.fastlogin.core.PlayerProfile;
|
||||
@@ -14,8 +14,8 @@ 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.PostLoginEvent;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.event.ServerConnectedEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
@@ -23,6 +23,7 @@ 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
|
||||
@@ -47,15 +48,20 @@ public class PlayerConnectionListener implements Listener {
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, new AsyncPremiumCheck(plugin, preLoginEvent));
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onLogin(PostLoginEvent loginEvent) {
|
||||
ProxiedPlayer player = loginEvent.getPlayer();
|
||||
PendingConnection connection = player.getPendingConnection();
|
||||
@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()) {
|
||||
LoginSession session = plugin.getSession().get(connection);
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
playerProfile.setUuid(player.getUniqueId());
|
||||
playerProfile.setUuid(connection.getUniqueId());
|
||||
|
||||
//bungeecord will do this automatically so override it on disabled option
|
||||
InitialHandler initialHandler = (InitialHandler) connection;
|
||||
@@ -65,7 +71,7 @@ public class PlayerConnectionListener implements Listener {
|
||||
|
||||
//bungeecord doesn't support overriding the premium uuid
|
||||
//so we have to do it with reflection
|
||||
Field idField = initialHandler.getClass().getDeclaredField("uniqueId");
|
||||
Field idField = InitialHandler.class.getDeclaredField("uniqueId");
|
||||
idField.setAccessible(true);
|
||||
idField.set(connection, offlineUUID);
|
||||
} catch (NoSuchFieldException | IllegalAccessException ex) {
|
||||
@@ -91,8 +97,9 @@ public class PlayerConnectionListener implements Listener {
|
||||
}
|
||||
|
||||
@EventHandler
|
||||
public void onServerConnected(PlayerDisconnectEvent disconnectEvent) {
|
||||
public void onDisconnect(PlayerDisconnectEvent disconnectEvent) {
|
||||
ProxiedPlayer player = disconnectEvent.getPlayer();
|
||||
plugin.getSession().remove(player.getPendingConnection());
|
||||
plugin.getPendingConfirms().remove(player.getUniqueId());
|
||||
}
|
||||
}
|
||||
|
||||
@@ -6,8 +6,10 @@ 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;
|
||||
@@ -40,36 +42,50 @@ public class PluginMessageListener implements Listener {
|
||||
}
|
||||
|
||||
private void readMessage(PluginMessageEvent pluginMessageEvent) {
|
||||
byte[] data = pluginMessageEvent.getData();
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
String subchannel = dataInput.readUTF();
|
||||
//so that we can safely process this in the background
|
||||
final byte[] data = Arrays.copyOf(pluginMessageEvent.getData(), pluginMessageEvent.getData().length);
|
||||
final ProxiedPlayer forPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
|
||||
|
||||
ProxiedPlayer fromPlayer = (ProxiedPlayer) pluginMessageEvent.getReceiver();
|
||||
if ("ON".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, new Runnable() {
|
||||
@Override
|
||||
public void run() {
|
||||
ByteArrayDataInput dataInput = ByteStreams.newDataInput(data);
|
||||
String subchannel = dataInput.readUTF();
|
||||
if ("ON".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin, fromPlayer, playerName, true);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("OFF".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
if (playerName.equals(forPlayer.getName()) && plugin.getConfig().getBoolean("premium-warning")
|
||||
&& !plugin.getPendingConfirms().contains(forPlayer.getUniqueId())) {
|
||||
String message = plugin.getCore().getMessage("premium-warning");
|
||||
forPlayer.sendMessage(TextComponent.fromLegacyText(message));
|
||||
plugin.getPendingConfirms().add(forPlayer.getUniqueId());
|
||||
return;
|
||||
}
|
||||
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin, fromPlayer, playerName, false);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("SUCCESS".equals(subchannel)) {
|
||||
if (fromPlayer.getPendingConnection().isOnlineMode()) {
|
||||
//bukkit module successfully received and force logged in the user
|
||||
//update only on success to prevent corrupt data
|
||||
PlayerProfile playerProfile = plugin.getCore().getStorage().loadProfile(fromPlayer.getName());
|
||||
BungeeLoginSession loginSession = plugin.getSession().get(fromPlayer.getPendingConnection());
|
||||
loginSession.setRegistered(true);
|
||||
plugin.getPendingConfirms().remove(forPlayer.getUniqueId());
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin, forPlayer, playerName, true);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("OFF".equals(subchannel)) {
|
||||
String playerName = dataInput.readUTF();
|
||||
|
||||
if (!loginSession.isAlreadySaved()) {
|
||||
playerProfile.setPremium(true);
|
||||
//we override this in the loginevent
|
||||
plugin.getCore().getStorage().save(playerProfile);
|
||||
loginSession.setAlreadySaved(true);
|
||||
AsyncToggleMessage task = new AsyncToggleMessage(plugin, forPlayer, playerName, false);
|
||||
ProxyServer.getInstance().getScheduler().runAsync(plugin, task);
|
||||
} else if ("SUCCESS".equals(subchannel)) {
|
||||
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);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
@@ -40,14 +40,15 @@ public class AsyncPremiumCheck implements Runnable {
|
||||
}
|
||||
|
||||
if (premiumUUID == null
|
||||
|| checkNameChange(premiumUUID, connection, username)
|
||||
|| checkPremiumName(username, connection, profile)) {
|
||||
|| (!checkNameChange(premiumUUID, connection, username)
|
||||
&& !checkPremiumName(username, connection, profile))) {
|
||||
//nothing detected the player as premium -> start a cracked session
|
||||
plugin.getSession().put(connection, new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
} else if (profile.isPremium()) {
|
||||
requestPremiumLogin(connection, profile, username, true);
|
||||
} else {
|
||||
//Cracked session
|
||||
plugin.getSession().put(connection, new BungeeLoginSession(username, false, profile));
|
||||
}
|
||||
} catch (Exception ex) {
|
||||
|
||||
@@ -8,6 +8,7 @@ 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.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
@@ -27,38 +28,63 @@ public class ForceLoginTask implements Runnable {
|
||||
|
||||
@Override
|
||||
public void run() {
|
||||
PendingConnection pendingConnection = player.getPendingConnection();
|
||||
BungeeLoginSession session = plugin.getSession().get(pendingConnection);
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
try {
|
||||
PendingConnection pendingConnection = player.getPendingConnection();
|
||||
BungeeLoginSession session = plugin.getSession().get(pendingConnection);
|
||||
|
||||
if (player.isConnected()) {
|
||||
return;
|
||||
}
|
||||
if (session == null || !player.isConnected()) {
|
||||
plugin.getLogger().log(Level.FINE, "Invalid session player {0} proparly left the server", player);
|
||||
return;
|
||||
}
|
||||
|
||||
//force login only on success
|
||||
if (pendingConnection.isOnlineMode()) {
|
||||
boolean autoRegister = session.needsRegistration();
|
||||
PlayerProfile playerProfile = session.getProfile();
|
||||
|
||||
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
|
||||
if (authPlugin == null) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
} else if (session.needsRegistration()) {
|
||||
String password = plugin.generateStringPassword();
|
||||
if (authPlugin.forceRegister(player, password)) {
|
||||
//force login only on success
|
||||
if (pendingConnection.isOnlineMode()) {
|
||||
boolean autoRegister = session.needsRegistration();
|
||||
|
||||
BungeeAuthPlugin authPlugin = plugin.getBungeeAuthPlugin();
|
||||
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);
|
||||
}
|
||||
} else if (authPlugin.forceLogin(player)) {
|
||||
//save will happen on success message from bukkit
|
||||
sendBukkitLoginNotification(autoRegister);
|
||||
}
|
||||
} 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, BungeeAuthPlugin authPlugin) {
|
||||
String password = plugin.generateStringPassword();
|
||||
if (session.isAlreadySaved() || 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(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private void forceLogin(BungeeLoginSession session, BungeeAuthPlugin authPlugin) {
|
||||
if (session.isAlreadySaved() || 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(message);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
@@ -19,7 +19,7 @@
|
||||
<dependency>
|
||||
<groupId>com.zaxxer</groupId>
|
||||
<artifactId>HikariCP</artifactId>
|
||||
<version>2.4.6</version>
|
||||
<version>2.4.7</version>
|
||||
</dependency>
|
||||
|
||||
<!--Logging framework implements slf4j which is required by hikari-->
|
||||
|
||||
@@ -11,14 +11,14 @@ import java.sql.Statement;
|
||||
import java.util.UUID;
|
||||
import java.util.logging.Level;
|
||||
|
||||
public class Storage {
|
||||
public class AuthStorage {
|
||||
|
||||
private static final String PREMIUM_TABLE = "premium";
|
||||
|
||||
private final FastLoginCore core;
|
||||
private final HikariDataSource dataSource;
|
||||
|
||||
public Storage(FastLoginCore core, String driver, String host, int port, String databasePath
|
||||
public AuthStorage(FastLoginCore core, String driver, String host, int port, String databasePath
|
||||
, String user, String pass) {
|
||||
this.core = core;
|
||||
|
||||
@@ -30,8 +30,6 @@ public class Storage {
|
||||
|
||||
databasePath = databasePath.replace("{pluginDir}", core.getDataFolder().getAbsolutePath());
|
||||
|
||||
databaseConfig.setThreadFactory(core.getThreadFactory());
|
||||
|
||||
String jdbcUrl = "jdbc:";
|
||||
if (driver.contains("sqlite")) {
|
||||
jdbcUrl += "sqlite" + "://" + databasePath;
|
||||
@@ -50,6 +48,7 @@ public class Storage {
|
||||
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), "
|
||||
@@ -57,7 +56,6 @@ public class Storage {
|
||||
+ "Premium BOOLEAN NOT NULL, "
|
||||
+ "LastIp VARCHAR(255) NOT NULL, "
|
||||
+ "LastLogin TIMESTAMP NOT NULL DEFAULT CURRENT_TIMESTAMP, "
|
||||
+ "UNIQUE (UUID), "
|
||||
//the premium shouldn't steal the cracked account by changing the name
|
||||
+ "UNIQUE (Name) "
|
||||
+ ")";
|
||||
@@ -67,6 +65,34 @@ public class Storage {
|
||||
}
|
||||
|
||||
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) {
|
||||
core.getLogger().log(Level.FINE, "Error dropping unique uuid index", sqlEx);
|
||||
}
|
||||
|
||||
try {
|
||||
createStmt.executeUpdate("CREATE INDEX uuid_idx on premium (UUID)");
|
||||
} catch (SQLException sqlEx) {
|
||||
core.getLogger().log(Level.FINE, "Error creating uuid index", sqlEx);
|
||||
}
|
||||
} finally {
|
||||
closeQuietly(con);
|
||||
closeQuietly(createStmt);
|
||||
@@ -79,7 +105,7 @@ public class Storage {
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=?");
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE Name=? LIMIT 1");
|
||||
loadStmt.setString(1, name);
|
||||
|
||||
resultSet = loadStmt.executeQuery();
|
||||
@@ -120,7 +146,7 @@ public class Storage {
|
||||
ResultSet resultSet = null;
|
||||
try {
|
||||
con = dataSource.getConnection();
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=?");
|
||||
loadStmt = con.prepareStatement("SELECT * FROM " + PREMIUM_TABLE + " WHERE UUID=? LIMIT 1");
|
||||
loadStmt.setString(1, uuid.toString().replace("-", ""));
|
||||
|
||||
resultSet = loadStmt.executeQuery();
|
||||
@@ -155,12 +181,13 @@ public class Storage {
|
||||
con = dataSource.getConnection();
|
||||
|
||||
UUID uuid = playerProfile.getUuid();
|
||||
|
||||
if (playerProfile.getUserId() == -1) {
|
||||
//User was authenticated with a premium authentication, so it's possible that the player is premium
|
||||
if (uuid != null) {
|
||||
//User was authenticated with a premium authentication, so it's possible that the player is premium
|
||||
updateStmt = con.prepareStatement("UPDATE " + PREMIUM_TABLE
|
||||
+ " SET NAME=?, LastIp=?, LastLogin=CURRENT_TIMESTAMP"
|
||||
+ " WHERE UUID=? AND PREMIUM=1");
|
||||
+ " WHERE UUID=?");
|
||||
|
||||
updateStmt.setString(1, playerProfile.getPlayerName());
|
||||
updateStmt.setString(2, playerProfile.getLastIp());
|
||||
@@ -175,8 +202,7 @@ public class Storage {
|
||||
}
|
||||
|
||||
saveStmt = con.prepareStatement("INSERT INTO " + PREMIUM_TABLE
|
||||
+ " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?) "
|
||||
, Statement.RETURN_GENERATED_KEYS);
|
||||
+ " (UUID, Name, Premium, LastIp) VALUES (?, ?, ?, ?) ", Statement.RETURN_GENERATED_KEYS);
|
||||
|
||||
if (uuid == null) {
|
||||
saveStmt.setString(1, null);
|
||||
@@ -0,0 +1,80 @@
|
||||
package com.github.games647.fastlogin.core;
|
||||
|
||||
import java.io.IOException;
|
||||
import java.net.InetAddress;
|
||||
import java.net.Socket;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.ArrayList;
|
||||
import java.util.List;
|
||||
import java.util.Set;
|
||||
|
||||
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 final Object lock = new Object();
|
||||
|
||||
private int id;
|
||||
|
||||
public BalancedSSLFactory(SSLSocketFactory oldFactory, Set<InetAddress> localAddresses) {
|
||||
this.oldFactory = oldFactory;
|
||||
this.localAddresses = new ArrayList<>(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 next;
|
||||
synchronized (lock) {
|
||||
next = id;
|
||||
id++;
|
||||
if (next == Integer.MAX_VALUE) {
|
||||
id = 0;
|
||||
}
|
||||
}
|
||||
|
||||
int index = next % localAddresses.size();
|
||||
return localAddresses.get(index);
|
||||
}
|
||||
}
|
||||
@@ -20,7 +20,7 @@ public abstract class FastLoginCore {
|
||||
|
||||
protected final Map<String, String> localeMessages = new ConcurrentHashMap<>();
|
||||
private MojangApiConnector mojangApiConnector;
|
||||
private Storage storage;
|
||||
private AuthStorage storage;
|
||||
|
||||
public void setMojangApiConnector(MojangApiConnector mojangApiConnector) {
|
||||
this.mojangApiConnector = mojangApiConnector;
|
||||
@@ -30,7 +30,7 @@ public abstract class FastLoginCore {
|
||||
return mojangApiConnector;
|
||||
}
|
||||
|
||||
public Storage getStorage() {
|
||||
public AuthStorage getStorage() {
|
||||
return storage;
|
||||
}
|
||||
|
||||
@@ -49,7 +49,7 @@ public abstract class FastLoginCore {
|
||||
public abstract void loadConfig();
|
||||
|
||||
public boolean setupDatabase(String driver, String host, int port, String database, String user, String password) {
|
||||
storage = new Storage(this, driver, host, port, database, user, password);
|
||||
storage = new AuthStorage(this, driver, host, port, database, user, password);
|
||||
try {
|
||||
storage.createTables();
|
||||
return true;
|
||||
|
||||
@@ -4,29 +4,76 @@ import java.io.BufferedReader;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStreamReader;
|
||||
import java.net.HttpURLConnection;
|
||||
import java.net.InetAddress;
|
||||
import java.net.URL;
|
||||
import java.net.UnknownHostException;
|
||||
import java.util.HashSet;
|
||||
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 = 1 * 1_000;
|
||||
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);
|
||||
|
||||
protected final FastLoginCore plugin;
|
||||
private final ConcurrentMap<Object, Object> requests;
|
||||
private final BalancedSSLFactory sslFactory;
|
||||
private final int rateLimit;
|
||||
private long lastRateLimit;
|
||||
|
||||
public MojangApiConnector(FastLoginCore plugin) {
|
||||
this.plugin = plugin;
|
||||
protected final Logger logger;
|
||||
|
||||
public MojangApiConnector(ConcurrentMap<Object, Object> requests, Logger logger, List<String> localAddresses
|
||||
, int rateLimit) {
|
||||
this.logger = logger;
|
||||
this.requests = requests;
|
||||
|
||||
if (rateLimit > 600) {
|
||||
this.rateLimit = 600;
|
||||
} else {
|
||||
this.rateLimit = rateLimit;
|
||||
}
|
||||
|
||||
if (localAddresses.isEmpty()) {
|
||||
this.sslFactory = null;
|
||||
} else {
|
||||
Set<InetAddress> addresses = new HashSet<>();
|
||||
for (String localAddress : localAddresses) {
|
||||
try {
|
||||
InetAddress address = InetAddress.getByName(localAddress);
|
||||
if (!address.isAnyLocalAddress()) {
|
||||
logger.log(Level.WARNING, "Submitted IP-Address is not local", 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);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -37,19 +84,31 @@ public abstract class MojangApiConnector {
|
||||
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
|
||||
// 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 {
|
||||
HttpURLConnection connection = getConnection(UUID_LINK + playerName);
|
||||
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 != null && !line.equals("null")) {
|
||||
return 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) {
|
||||
plugin.getLogger().log(Level.SEVERE, "Failed to check if player has a paid account", 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
|
||||
}
|
||||
@@ -57,18 +116,49 @@ public abstract class MojangApiConnector {
|
||||
return null;
|
||||
}
|
||||
|
||||
public UUID getUUIDFromAPI(String playerName) {
|
||||
try {
|
||||
HttpURLConnection httpConnection = (HttpURLConnection) new URL(MCAPI_UUID_URL + playerName).openConnection();
|
||||
httpConnection.addRequestProperty("Content-Type", "application/json");
|
||||
httpConnection.setRequestProperty("User-Agent", USER_AGENT);
|
||||
|
||||
if (httpConnection.getResponseCode() == HttpURLConnection.HTTP_NOT_FOUND) {
|
||||
//cracked
|
||||
return null;
|
||||
}
|
||||
|
||||
BufferedReader reader = new BufferedReader(new InputStreamReader(httpConnection.getInputStream()));
|
||||
StringBuilder inputBuilder = new StringBuilder();
|
||||
String line;
|
||||
while ((line = reader.readLine()) != null) {
|
||||
inputBuilder.append(line);
|
||||
}
|
||||
|
||||
String input = inputBuilder.toString();
|
||||
return getUUIDFromJson(input);
|
||||
} catch (IOException iOException) {
|
||||
logger.log(Level.SEVERE, "Tried converting name->uuid from third-party api", iOException);
|
||||
}
|
||||
|
||||
return null;
|
||||
}
|
||||
|
||||
public abstract boolean hasJoinedServer(Object session, String serverId);
|
||||
|
||||
protected abstract UUID getUUIDFromJson(String json);
|
||||
|
||||
protected HttpURLConnection getConnection(String url) throws IOException {
|
||||
HttpURLConnection connection = (HttpURLConnection) new URL(url).openConnection();
|
||||
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;
|
||||
}
|
||||
}
|
||||
|
||||
@@ -19,14 +19,29 @@ public class AutoInImporter extends Importer {
|
||||
try {
|
||||
con = source.getConnection();
|
||||
stmt = con.createStatement();
|
||||
int importedRows = stmt.executeUpdate("INSERT INTO " + targetTable + " SELECT"
|
||||
int importedRows = stmt.executeUpdate("INSERT INTO " + targetTable + " (Name, Premium, LastIp, UUID) SELECT"
|
||||
+ " name AS Name,"
|
||||
+ " enabledLogin AS Premium,"
|
||||
/* Enable premium authentication only for those who want to be auto logged in, so
|
||||
they have their cracked protection disabled */
|
||||
+ " !protection AND premium AS Premium,"
|
||||
+ " '' AS LastIp,"
|
||||
/* Remove the dashes - returns null if puuid is null too */
|
||||
+ " REPLACE(puuid, '-', '') AS UUID"
|
||||
+ " FROM " + USER_TABLE
|
||||
+ " JOIN " + UUID_TABLE
|
||||
+ " ON " + UUID_TABLE + ".id = " + UUID_TABLE + ".nickname_id");
|
||||
/* Get the premium uuid */
|
||||
+ " LEFT JOIN " + " ("
|
||||
/* Prevent duplicates */
|
||||
+ "SELECT * FROM " + UUID_TABLE + " GROUP BY nickname_id"
|
||||
+ ") uuids"
|
||||
+ " ON " + USER_TABLE + ".id = uuids.nickname_id");
|
||||
|
||||
/* 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 */
|
||||
stmt.executeUpdate("UPDATE `premium` SET `UUID`=NULL WHERE PREMIUM=0");
|
||||
return importedRows;
|
||||
} finally {
|
||||
closeQuietly(stmt);
|
||||
|
||||
@@ -100,6 +100,35 @@ nameChangeCheck: false
|
||||
# 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
|
||||
|
||||
# Database configuration
|
||||
# Recommened is the use of MariaDB (a better version of MySQL)
|
||||
|
||||
|
||||
@@ -27,12 +27,18 @@ add-premium: '&2Added to the list of premium players'
|
||||
# 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'
|
||||
|
||||
@@ -74,6 +80,13 @@ 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 ================================
|
||||
|
||||
|
||||
|
||||
2
pom.xml
2
pom.xml
@@ -8,7 +8,7 @@
|
||||
<packaging>pom</packaging>
|
||||
|
||||
<name>FastLogin</name>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.1</version>
|
||||
<inceptionYear>2015</inceptionYear>
|
||||
<url>https://www.spigotmc.org/resources/fastlogin.14153/</url>
|
||||
<description>
|
||||
|
||||
@@ -5,7 +5,7 @@
|
||||
<parent>
|
||||
<groupId>com.github.games647</groupId>
|
||||
<artifactId>fastlogin</artifactId>
|
||||
<version>1.5.2</version>
|
||||
<version>1.7.1</version>
|
||||
<relativePath>../pom.xml</relativePath>
|
||||
</parent>
|
||||
|
||||
|
||||
Reference in New Issue
Block a user