Compare commits

...
4 Commits
Author SHA1 Message Date
Jones 65e059368c Release 2.0.23 (#239) 2024-03-13 16:15:38 +01:00
dependabot[bot] b7a031da12 Bump io.github.goooler.shadow from 8.1.5 to 8.1.7 (#238)
Bumps [io.github.goooler.shadow](https://github.com/Goooler/shadow) from 8.1.5 to 8.1.7.
- [Release notes](https://github.com/Goooler/shadow/releases)
- [Commits](https://github.com/Goooler/shadow/compare/v8.1.5...v8.1.7)

---
updated-dependencies:
- dependency-name: io.github.goooler.shadow
  dependency-type: direct:production
  update-type: version-update:semver-patch
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2024-03-11 19:11:17 +01:00
Jones e15206a382 Implement CAPTCHA-on-fail option (#237)
* feat: add option to display a CAPTCHA on fail

* fix: remove debug message
2024-03-10 13:11:09 +01:00
Jones 5e4af3053e Fix local database cache duplicating after reload (#236) 2024-03-07 20:19:42 +01:00
11 changed files with 67 additions and 33 deletions

No files matched your search

+1 -1
View File
@@ -6,7 +6,7 @@ buildscript {
plugins {
id("java")
id("io.github.goooler.shadow") version "8.1.5"
id("io.github.goooler.shadow") version "8.1.7"
id("io.toolebox.git-versioner") version "1.6.7"
}
+1 -1
View File
@@ -1,3 +1,3 @@
group=xyz.jonesdev.sonar
version=2.0.22
version=2.0.23
description=Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)
@@ -173,6 +173,7 @@ public final class SonarConfiguration {
verification.timing = Verification.Timing.valueOf(generalConfig.getString("verification.timing"));
verification.gravity.enabled = generalConfig.getBoolean("verification.checks.gravity.enabled");
verification.gravity.checkCollisions = generalConfig.getBoolean("verification.checks.gravity.check-collisions");
verification.gravity.captchaOnFail = generalConfig.getBoolean("verification.checks.gravity.captcha-on-fail");
verification.gravity.maxMovementTicks = clamp(generalConfig.getInt("verification.checks.gravity.max-movement-ticks"), 2, 100);
verification.gravity.maxIgnoredTicks = clamp(generalConfig.getInt("verification.checks.gravity.max-ignored-ticks"), 1, 128);
verification.gravity.gamemode = Verification.Gravity.Gamemode.valueOf(generalConfig.getString("verification.checks.gravity.gamemode"));
@@ -456,6 +457,7 @@ public final class SonarConfiguration {
public static final class Gravity {
private boolean enabled;
private boolean checkCollisions;
private boolean captchaOnFail;
private Gamemode gamemode;
private int maxMovementTicks;
private int maxIgnoredTicks;
@@ -27,6 +27,7 @@ import lombok.Getter;
import org.jetbrains.annotations.NotNull;
import org.jetbrains.annotations.Nullable;
import org.jetbrains.annotations.Range;
import org.jetbrains.annotations.Unmodifiable;
import xyz.jonesdev.sonar.api.Sonar;
import xyz.jonesdev.sonar.api.config.SonarConfiguration;
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
@@ -43,7 +44,6 @@ import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
public final class VerifiedPlayerController {
private static final Map<String, Collection<UUID>> MAP = new ConcurrentHashMap<>();
private static final ExecutorService DB_UPDATE_SERVICE = Executors.newSingleThreadExecutor();
private static final LoggerWrapper LOGGER = new LoggerWrapper() {
@@ -64,6 +64,7 @@ public final class VerifiedPlayerController {
}
};
private final Map<String, Collection<UUID>> cache = new ConcurrentHashMap<>(128);
private @Nullable ConnectionSource connectionSource;
private Dao<VerifiedPlayer, Integer> dao;
private QueryBuilder<VerifiedPlayer, Integer> queryBuilder;
@@ -198,7 +199,7 @@ public final class VerifiedPlayerController {
* @param inetAddress IP address of the player
*/
private void _remove(final @NotNull String inetAddress) {
MAP.remove(inetAddress);
cache.remove(inetAddress);
}
/**
@@ -236,24 +237,30 @@ public final class VerifiedPlayerController {
* @param player VerifiedPlayer model
*/
private void _add(final @NotNull VerifiedPlayer player) {
MAP.computeIfAbsent(player.getInetAddress(), v -> new Vector<>())
cache.computeIfAbsent(player.getInetAddress(), v -> new ArrayList<>())
.add(player.getPlayerUUID());
}
/**
* Returns the amount of verified IP addresses
*
* @return Estimated size of the local cache
*/
public synchronized int estimatedSize() {
return MAP.values().stream()
.mapToInt(Collection::size)
.sum();
/*
* TODO: Work on this
* return MAP.values().stream()
* .mapToInt(Collection::size)
* .sum();
*/
return cache.size();
}
/**
* @return List of UUIDs associated with one IP address
*/
public Collection<UUID> getUUIDs(final @NotNull String inetAddress) {
return MAP.getOrDefault(inetAddress, Collections.emptyList());
public @Unmodifiable Collection<UUID> getUUIDs(final @NotNull String inetAddress) {
return cache.getOrDefault(inetAddress, Collections.emptyList());
}
/**
@@ -262,7 +269,7 @@ public final class VerifiedPlayerController {
*/
public void clearAll() {
try {
MAP.clear();
cache.clear();
// Only update the column if the database type is not NONE
if (cachedDatabaseType != SonarConfiguration.Database.Type.NONE) {
@@ -279,7 +286,7 @@ public final class VerifiedPlayerController {
* @return Whether the local cache contains the IP and UUID
*/
public boolean has(final @NotNull String inetAddress, final @NotNull UUID uuid) {
final Collection<UUID> got = MAP.get(inetAddress);
final Collection<UUID> got = cache.get(inetAddress);
if (got != null) {
return got.contains(uuid);
}
@@ -301,7 +308,7 @@ public final class VerifiedPlayerController {
* @return Whether the local cache contains the IP
*/
public boolean has(final @NotNull String inetAddress) {
return MAP.containsKey(inetAddress);
return cache.containsKey(inetAddress);
}
/**
@@ -473,6 +473,8 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
tick = 1;
posY = -1;
expectedTeleportId = -1;
// Check for ground state in the first packet
checkFrame(!ground, "invalid ground state");
}
posX = x;
@@ -533,8 +535,12 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
if (ground) {
// Make sure the player is actually colliding with the blocks and not only spoofing ground
checkFrame(collisionOffsetY > -0.03, "illegal collision: " + collisionOffsetY);
// The player is colliding to blocks, finish verification
captchaOrFinish();
// Check if the player is not spoofing ground
// We cannot use checkFrame as it interferes with the CAPTCHA
if (collisionOffsetY > -1) {
// The player is colliding to blocks, finish verification
captchaOrFinish();
}
return;
} else {
// Make sure the player is colliding with blocks but is sending an invalid ground state
@@ -567,27 +573,27 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
private void captchaOrFinish() {
if (FALLBACK.shouldDoMapCaptcha()) {
if (MapInfoPreparer.getPreparedCAPTCHAs() == 0) {
// This should not happen, but we have to return if there is no captcha prepared
user.disconnect(Sonar.get().getConfig().getVerification().getCurrentlyPreparing());
return;
}
// Set the state to MAP_CAPTCHA, so we don't handle any unnecessary packets
state = State.MAP_CAPTCHA;
if (!Sonar.get().getConfig().getVerification().getGravity().isEnabled()
&& user.getProtocolVersion().compareTo(MINECRAFT_1_18_2) >= 0) {
// Make sure the player escapes the 1.18.2+ "Loading terrain" screen
user.delayedWrite(CAPTCHA_SPAWN_POSITION);
}
// Initialize the map captcha
handleMapCaptcha();
} else {
// Finish the verification
finish();
handleCAPTCHA();
return;
}
// Finish the verification
finish();
}
private void handleMapCaptcha() {
private void handleCAPTCHA() {
if (MapInfoPreparer.getPreparedCAPTCHAs() == 0) {
// This should not happen, but we have to return if there is no captcha prepared
user.disconnect(Sonar.get().getConfig().getVerification().getCurrentlyPreparing());
return;
}
// Set the state to MAP_CAPTCHA, so we don't handle any unnecessary packets
state = State.MAP_CAPTCHA;
if (!Sonar.get().getConfig().getVerification().getGravity().isEnabled()
&& user.getProtocolVersion().compareTo(MINECRAFT_1_18_2) >= 0) {
// Make sure the player escapes the 1.18.2+ "Loading terrain" screen
user.delayedWrite(CAPTCHA_SPAWN_POSITION);
}
// Reset max tries
captchaTriesLeft = Sonar.get().getConfig().getVerification().getMap().getMaxTries();
@@ -608,6 +614,9 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
}
private void finish() {
// Something must've gone horribly wrong...
if (state == State.SUCCESS) return;
state = State.SUCCESS;
// Increment amount of total successful verifications
@@ -646,6 +655,11 @@ public final class FallbackVerificationHandler implements FallbackPacketListener
*/
private void checkFrame(final boolean condition, final String message) {
if (!condition) {
if (state == State.POSITION // Gravity check
&& Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
handleCAPTCHA();
return;
}
user.fail(message);
throw new CorruptedFrameException(message);
}
@@ -128,7 +128,8 @@ public class FallbackPreparer {
invalidUsername = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getInvalidUsername(), true);
// Precompute captcha answers
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER) {
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|| Sonar.get().getConfig().getVerification().getGravity().isCaptchaOnFail()) {
enterCodeMessage = new UniversalChatPacket(Sonar.get().getConfig().getVerification().getMap().getEnterCode(), UniversalChatPacket.SYSTEM_TYPE);
incorrectCaptcha = new UniversalChatPacket(
Sonar.get().getConfig().getVerification().getMap().getFailedCaptcha(), UniversalChatPacket.SYSTEM_TYPE);
@@ -78,6 +78,8 @@ verification:
enabled: true
# Měl by Sonar zkoušet jestli se hráč dotýka bloku? (Doporučováno)
check-collisions: true
# Měl by Sonar přimět hráče, aby vyřešil CAPTCHA, pokud neuspějí při kontrole gravitace?
captcha-on-fail: false
# Maximální počet ticků, který má hráč padat aby byl puštěn na verifikační platformu
max-movement-ticks: 8
# Maximum ignorovaných změn Y-nové souřadnice hráče, aby neprošel verifikací
@@ -75,6 +75,8 @@ verification:
enabled: true
# Soll Sonar prüfen, ob der Spieler mit Blöcken kollidiert? (Empfohlen)
check-collisions: true
# Soll Sonar den Spieler dazu bringen, ein CAPTCHA zu lösen, wenn er die Schwerkraftprüfung nicht besteht?
captcha-on-fail: false
# Maximale Anzahl an Ticks, die der Spieler fallen muss, um die Plattform erreichen zu dürfen
max-movement-ticks: 8
# Maximale Anzahl an ignorierten Y-Bewegungsänderungen, bevor ein Spieler die Überprüfung nicht besteht
@@ -77,6 +77,8 @@ verification:
enabled: true
# Should Sonar check if the player collides with blocks? (Recommended)
check-collisions: true
# Should Sonar make the player solve a CAPTCHA if they fail the gravity check?
captcha-on-fail: false
# Maximum number of ticks the player has to fall in order to be allowed to hit the platform
max-movement-ticks: 8
# Maximum number of ignored Y movement changes before a player fails verification
@@ -77,6 +77,8 @@ verification:
enabled: true
# უნდა შეამოწმოს თუ არა Sonar-მა, ეტაკება თუ არა მოთამაშე ბლოკებს?
check-collisions: true
# უნდა აიძულოს თუ არა Sonar-მა მოთამაშეს გადაჭრას CAPTCHA, თუ ისინი ვერ შეასრულებენ სიმძიმის შემოწმებას?
captcha-on-fail: false
# ტიკების მაქსიმალური რაოდენობა რომელითაც მოთამაშე უნდა დაეცეს, რათა პლატფორმაზე დაცემა შეეძლოს
max-movement-ticks: 8
# იგნორირებული Y მოძრაობის მაქსიმალური რაოდენობა სანამ, მოთამაშე ვერიფიკაციას ვერ გაივლის
@@ -77,6 +77,8 @@ verification:
enabled: true
# Должен ли Sonar проверять, сталкивается ли игрок с блоками? (Рекомендуется)
check-collisions: true
# Должен ли Sonar проверять, игрока решать CAPTCHA, если он не прошел проверку гравитации?
captcha-on-fail: false
# Максимальное количество тиков, на которое должен упасть игрок, чтобы ему было разрешено упасть на платформу
max-movement-ticks: 8
# Максимальное количество проигнорированных изменений движения Y, прежде чем игрок не пройдет проверку