mirror of
https://github.com/jonesdevelopment/sonar.git
synced 2026-11-05 17:22:34 -05:00
Compare commits
| Author | SHA1 | Date | |
|---|---|---|---|
|
|
4474f44b0a | ||
|
|
b159947d6d | ||
|
|
a29dfa15e4 | ||
|
|
cb9fe75db8 |
No files matched your search
+1
-1
@@ -1,3 +1,3 @@
|
||||
group=xyz.jonesdev.sonar
|
||||
version=2.0.25
|
||||
version=2.0.26
|
||||
description=Effective Anti-bot plugin for Velocity, BungeeCord, and Bukkit (1.7-latest)
|
||||
@@ -34,6 +34,7 @@ import xyz.jonesdev.sonar.api.verbose.Notification;
|
||||
import xyz.jonesdev.sonar.api.verbose.Verbose;
|
||||
|
||||
import java.io.File;
|
||||
import java.net.InetAddress;
|
||||
import java.text.DecimalFormat;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -52,6 +53,11 @@ public interface Sonar {
|
||||
*/
|
||||
@NotNull SonarPlatform getPlatform();
|
||||
|
||||
/**
|
||||
* @return If the amount of players online with the same IP address is greater than a maximum
|
||||
*/
|
||||
boolean hasTooManyAccounts(final @NotNull InetAddress inetAddress, final int limit);
|
||||
|
||||
/**
|
||||
* @return A small wrapper for the plugin logger so we can use the logger everywhere
|
||||
*/
|
||||
|
||||
@@ -53,13 +53,8 @@ public abstract class Subcommand {
|
||||
final InetAddress inetAddress;
|
||||
try {
|
||||
inetAddress = InetAddress.getByName(rawIP);
|
||||
|
||||
if (inetAddress.isAnyLocalAddress() || inetAddress.isLoopbackAddress()) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getIllegalIpAddress());
|
||||
return null;
|
||||
}
|
||||
} catch (UnknownHostException exception) {
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getIncorrectIpAddress());
|
||||
source.sendMessage(SONAR.getConfig().getCommands().getInvalidIpAddress());
|
||||
return null;
|
||||
}
|
||||
return inetAddress;
|
||||
|
||||
@@ -256,8 +256,7 @@ public final class SonarConfiguration {
|
||||
|
||||
noPermission = formatString(messagesConfig.getString("commands.no-permission"));
|
||||
commands.incorrectCommandUsage = formatString(messagesConfig.getString("commands.incorrect-usage"));
|
||||
commands.incorrectIpAddress = formatString(messagesConfig.getString("commands.invalid-ip-address"));
|
||||
commands.illegalIpAddress = formatString(messagesConfig.getString("commands.illegal-ip-address"));
|
||||
commands.invalidIpAddress = formatString(messagesConfig.getString("commands.invalid-ip-address"));
|
||||
commands.playersOnly = formatString(messagesConfig.getString("commands.player-only"));
|
||||
commands.consoleOnly = formatString(messagesConfig.getString("commands.console-only"));
|
||||
commands.commandCoolDown = formatString(messagesConfig.getString("commands.cool-down"));
|
||||
@@ -290,6 +289,8 @@ public final class SonarConfiguration {
|
||||
commands.verifiedCleared = formatString(messagesConfig.getString("commands.verified.cleared"));
|
||||
commands.verifiedSize = formatString(messagesConfig.getString("commands.verified.size"));
|
||||
commands.verifiedRemove = formatString(messagesConfig.getString("commands.verified.removed"));
|
||||
commands.verifiedAdd = formatString(messagesConfig.getString("commands.verified.added"));
|
||||
commands.verifiedAlready = formatString(messagesConfig.getString("commands.verified.already"));
|
||||
commands.verifiedNotFound = formatString(messagesConfig.getString("commands.verified.ip-not-found"));
|
||||
commands.verifiedBlocked = formatString(messagesConfig.getString("commands.verified.blocked"));
|
||||
|
||||
@@ -525,8 +526,7 @@ public final class SonarConfiguration {
|
||||
@NoArgsConstructor(access = AccessLevel.PRIVATE)
|
||||
public static final class Commands {
|
||||
private String incorrectCommandUsage;
|
||||
private String incorrectIpAddress;
|
||||
private String illegalIpAddress;
|
||||
private String invalidIpAddress;
|
||||
private String subCommandNoPerm;
|
||||
private String playersOnly;
|
||||
private String consoleOnly;
|
||||
@@ -543,6 +543,8 @@ public final class SonarConfiguration {
|
||||
private String blacklistSize;
|
||||
|
||||
private String verifiedRemove;
|
||||
private String verifiedAdd;
|
||||
private String verifiedAlready;
|
||||
private String verifiedNotFound;
|
||||
private String verifiedCleared;
|
||||
private String verifiedSize;
|
||||
|
||||
-9
@@ -310,13 +310,4 @@ public final class VerifiedPlayerController {
|
||||
public boolean has(final @NotNull String inetAddress) {
|
||||
return cache.containsKey(inetAddress);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param inetAddress IP address
|
||||
* @return Whether the local cache contains the IP
|
||||
* @see #has(String)
|
||||
*/
|
||||
public boolean has(final @NotNull InetAddress inetAddress) {
|
||||
return has(inetAddress.toString());
|
||||
}
|
||||
}
|
||||
@@ -23,6 +23,7 @@ import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.platform.bukkit.BukkitAudiences;
|
||||
import org.bstats.bukkit.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.bukkit.entity.Player;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import org.jetbrains.annotations.Nullable;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
@@ -32,6 +33,7 @@ import xyz.jonesdev.sonar.bukkit.command.BukkitSonarCommand;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
|
||||
@@ -59,6 +61,20 @@ public final class SonarBukkit extends SonarBootstrap<SonarBukkitPlugin> {
|
||||
return bukkitAudiences.player(uniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTooManyAccounts(final @NotNull InetAddress inetAddress, final int limit) {
|
||||
int count = 1;
|
||||
for (final Player player : getPlugin().getServer().getOnlinePlayers()) {
|
||||
// I don't know why, but Bukkit is *special*
|
||||
if (Objects.isNull(player.getAddress())) continue;
|
||||
// Check if the IP address of the player is equal to the IP trying to connect
|
||||
if (!Objects.equals(player.getAddress().getAddress(), inetAddress)) continue;
|
||||
// Increment count of duplicated accounts
|
||||
if (++count >= limit) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for the plugin logger, so we can use it outside
|
||||
* the velocity module.
|
||||
|
||||
@@ -21,6 +21,7 @@ import com.alessiodp.libby.BungeeLibraryManager;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import net.kyori.adventure.platform.bungeecord.BungeeAudiences;
|
||||
import net.md_5.bungee.api.connection.ProxiedPlayer;
|
||||
import org.bstats.bungeecord.Metrics;
|
||||
import org.bstats.charts.SimplePie;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
@@ -30,10 +31,11 @@ import xyz.jonesdev.sonar.api.SonarPlatform;
|
||||
import xyz.jonesdev.sonar.api.logger.LoggerWrapper;
|
||||
import xyz.jonesdev.sonar.bungee.command.BungeeSonarCommand;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackInjectionHelper;
|
||||
import xyz.jonesdev.sonar.bungee.fallback.FallbackLoginListener;
|
||||
import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -59,6 +61,19 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
return bungeeAudiences.player(uniqueId);
|
||||
}
|
||||
|
||||
@Override
|
||||
@SuppressWarnings("deprecation")
|
||||
public boolean hasTooManyAccounts(final @NotNull InetAddress inetAddress, final int limit) {
|
||||
int count = 1;
|
||||
for (final ProxiedPlayer player : getPlugin().getServer().getPlayers()) {
|
||||
// Check if the IP address of the player is equal to the IP trying to connect
|
||||
if (!Objects.equals(player.getAddress().getAddress(), inetAddress)) continue;
|
||||
// Increment count of duplicated accounts
|
||||
if (++count >= limit) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for the plugin logger, so we can use it outside
|
||||
* the velocity module.
|
||||
@@ -99,9 +114,6 @@ public final class SonarBungee extends SonarBootstrap<SonarBungeePlugin> {
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getPluginManager().registerCommand(getPlugin(), new BungeeSonarCommand());
|
||||
|
||||
// Register Fallback listener
|
||||
getPlugin().getServer().getPluginManager().registerListener(getPlugin(), new FallbackLoginListener());
|
||||
|
||||
// Register traffic service
|
||||
getPlugin().getServer().getScheduler().schedule(getPlugin(), CachedBandwidthStatistics::reset,
|
||||
1L, 1L, TimeUnit.SECONDS);
|
||||
|
||||
+4
-9
@@ -44,7 +44,6 @@ import java.util.UUID;
|
||||
import static net.md_5.bungee.netty.PipelineUtils.*;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.customDisconnect;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.deject;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter {
|
||||
@@ -135,15 +134,13 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
ctx.fireChannelRead(wrappedMessage);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
// Completely skip Geyser connections
|
||||
if (GeyserUtil.isGeyserConnection(channel, socketAddress)) {
|
||||
ctx.fireChannelRead(wrappedMessage);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -172,8 +169,7 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
final String offlineUUIDString = "OfflinePlayer:" + loginRequest.getData();
|
||||
final UUID offlineUUID = UUID.nameUUIDFromBytes(offlineUUIDString.getBytes(StandardCharsets.UTF_8));
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, offlineUUID)) {
|
||||
ctx.fireChannelRead(wrappedMessage);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -186,8 +182,7 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
// Check if the protocol ID of the player is allowed to bypass verification
|
||||
if (Sonar.get().getConfig().getVerification().getWhitelistedProtocols()
|
||||
.contains(protocolVersion.getProtocol())) {
|
||||
ctx.fireChannelRead(wrappedMessage);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, wrappedMessage, PACKET_ENCODER, BOSS_HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
-84
@@ -1,84 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.bungee.fallback;
|
||||
|
||||
import lombok.RequiredArgsConstructor;
|
||||
import net.kyori.adventure.text.Component;
|
||||
import net.kyori.adventure.text.serializer.gson.GsonComponentSerializer;
|
||||
import net.md_5.bungee.api.connection.PendingConnection;
|
||||
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||
import net.md_5.bungee.api.plugin.Listener;
|
||||
import net.md_5.bungee.chat.ComponentSerializer;
|
||||
import net.md_5.bungee.connection.InitialHandler;
|
||||
import net.md_5.bungee.event.EventHandler;
|
||||
import net.md_5.bungee.event.EventPriority;
|
||||
import net.md_5.bungee.netty.ChannelWrapper;
|
||||
import net.md_5.bungee.protocol.packet.Kick;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.ReflectiveOperationException;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.bungee.SonarBungee;
|
||||
|
||||
import java.lang.reflect.Field;
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.closeWith;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public final class FallbackLoginListener implements Listener {
|
||||
|
||||
private static final Field CHANNEL_FIELD;
|
||||
|
||||
static {
|
||||
try {
|
||||
CHANNEL_FIELD = InitialHandler.class.getDeclaredField("ch");
|
||||
CHANNEL_FIELD.setAccessible(true);
|
||||
} catch (NoSuchFieldException exception) {
|
||||
throw new ReflectiveOperationException(exception);
|
||||
}
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@EventHandler(priority = EventPriority.LOWEST)
|
||||
public void handle(final @NotNull PreLoginEvent event) throws IllegalAccessException {
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
// Don't do anything if the setting is disabled
|
||||
if (maxOnlinePerIp <= 0) return;
|
||||
|
||||
final InetAddress inetAddress = event.getConnection().getAddress().getAddress();
|
||||
|
||||
final long onlinePerIp = SonarBungee.INSTANCE.getPlugin().getServer().getPlayers().stream()
|
||||
.filter(player -> Objects.equals(player.getAddress().getAddress(), inetAddress))
|
||||
.count()
|
||||
+ 1 /* add 1 because the player hasn't been added to the list of online players yet */;
|
||||
|
||||
// We use '>=' because the player connecting to the server hasn't joined yet
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
final Component component = Sonar.get().getConfig().getTooManyOnlinePerIp();
|
||||
final String serialized = GsonComponentSerializer.gson().serialize(component);
|
||||
final PendingConnection connection = event.getConnection();
|
||||
closeWith(((ChannelWrapper) CHANNEL_FIELD.get(connection)).getHandle(),
|
||||
ProtocolVersion.fromId(connection.getVersion()),
|
||||
new Kick(ComponentSerializer.deserialize(serialized)));
|
||||
}
|
||||
}
|
||||
}
|
||||
+30
@@ -27,10 +27,14 @@ import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.api.fallback.Fallback;
|
||||
import xyz.jonesdev.sonar.api.fallback.FallbackUser;
|
||||
import xyz.jonesdev.sonar.api.fallback.protocol.ProtocolVersion;
|
||||
import xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer;
|
||||
import xyz.jonesdev.sonar.common.statistics.GlobalSonarStatistics;
|
||||
|
||||
import java.net.InetAddress;
|
||||
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.customDisconnect;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.deject;
|
||||
|
||||
@RequiredArgsConstructor
|
||||
public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter {
|
||||
protected final Channel channel;
|
||||
@@ -80,4 +84,30 @@ public class FallbackChannelHandlerAdapter extends ChannelInboundHandlerAdapter
|
||||
// Simply close the channel if we encounter any errors.
|
||||
ctx.close();
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the maximum accounts per IP limit check before letting the player join
|
||||
*
|
||||
* @param ctx Forwarded channel handler context
|
||||
* @param packet Login packet sent by the client
|
||||
*/
|
||||
protected void initialLogin(final @NotNull ChannelHandlerContext ctx,
|
||||
final @NotNull Object packet,
|
||||
final @NotNull String encoder,
|
||||
final @NotNull String boss) {
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
// Skip the maximum online per IP check if it's disabled in the configuration
|
||||
if (maxOnlinePerIp > 0) {
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
if (Sonar.get().hasTooManyAccounts(inetAddress, maxOnlinePerIp)) {
|
||||
customDisconnect(channel, protocolVersion, FallbackPreparer.tooManyOnlinePerIP, encoder, boss);
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
ctx.fireChannelRead(packet);
|
||||
// Deject the channel since we don't need it anymore
|
||||
deject(channel.pipeline());
|
||||
}
|
||||
}
|
||||
+2
@@ -64,6 +64,7 @@ public class FallbackPreparer {
|
||||
public FallbackPacket reconnectedTooFast;
|
||||
public FallbackPacket protocolBlacklisted;
|
||||
public FallbackPacket invalidUsername;
|
||||
public FallbackPacket tooManyOnlinePerIP;
|
||||
|
||||
// Vehicle
|
||||
public FallbackPacket removeEntities;
|
||||
@@ -137,6 +138,7 @@ public class FallbackPreparer {
|
||||
protocolBlacklisted = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getProtocolBlacklisted(), true);
|
||||
reconnectedTooFast = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getTooFastReconnect(), true);
|
||||
invalidUsername = DisconnectPacket.create(Sonar.get().getConfig().getVerification().getInvalidUsername(), true);
|
||||
tooManyOnlinePerIP = DisconnectPacket.create(Sonar.get().getConfig().getTooManyOnlinePerIp(), true);
|
||||
|
||||
// Precompute captcha answers
|
||||
if (Sonar.get().getConfig().getVerification().getMap().getTiming() != SonarConfiguration.Verification.Timing.NEVER
|
||||
|
||||
+1
-1
@@ -60,7 +60,7 @@ public final class BlacklistCommand extends Subcommand {
|
||||
}
|
||||
|
||||
// Display a warning if the IP is verified but being added to the blacklist
|
||||
if (SONAR.getVerifiedPlayerController().has(inetAddress)) {
|
||||
if (SONAR.getVerifiedPlayerController().has(inetAddress.toString())) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getBlacklistAddWarning()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
}
|
||||
|
||||
+48
@@ -23,8 +23,10 @@ import xyz.jonesdev.sonar.api.command.CommandInvocation;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.Subcommand;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.SubcommandInfo;
|
||||
import xyz.jonesdev.sonar.api.command.subcommand.argument.Argument;
|
||||
import xyz.jonesdev.sonar.api.model.VerifiedPlayer;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.Queue;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.LinkedBlockingQueue;
|
||||
@@ -35,6 +37,7 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
arguments = {
|
||||
@Argument("history"),
|
||||
@Argument("remove"),
|
||||
@Argument("add"),
|
||||
@Argument("clear"),
|
||||
@Argument("size")
|
||||
}
|
||||
@@ -107,6 +110,51 @@ public final class VerifiedCommand extends Subcommand {
|
||||
break;
|
||||
}
|
||||
|
||||
case "add": {
|
||||
if (invocation.getRawArguments().length <= 3) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getIncorrectCommandUsage()
|
||||
.replace("%usage%", "verified add <IP address> <UUID/username>"));
|
||||
return;
|
||||
}
|
||||
|
||||
final String rawInetAddress = invocation.getRawArguments()[2];
|
||||
|
||||
// Make sure we aren't currently locking the IP address to avoid a double I/O operation
|
||||
if (LOCK.contains(rawInetAddress)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedBlocked());
|
||||
return;
|
||||
}
|
||||
|
||||
final InetAddress inetAddress = getInetAddressIfValid(invocation.getSender(), rawInetAddress);
|
||||
// Make sure the given IP address is valid
|
||||
if (inetAddress == null) return;
|
||||
|
||||
// Try to parse the UUID (from the username, if needed)
|
||||
final String rawUUID = invocation.getRawArguments()[3];
|
||||
final UUID uuid = rawUUID.length() == 36 ? UUID.fromString(rawUUID)
|
||||
: UUID.nameUUIDFromBytes(("OfflinePlayer:" + rawUUID).getBytes(StandardCharsets.UTF_8));
|
||||
|
||||
// Make sure the player is verified already
|
||||
if (SONAR.getVerifiedPlayerController().has(inetAddress.toString(), uuid)) {
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedAlready());
|
||||
return;
|
||||
}
|
||||
|
||||
// Lock the IP address
|
||||
// Make sure we don't accidentally run 2 operations at the same time
|
||||
LOCK.add(rawInetAddress);
|
||||
// Add verified player to the database
|
||||
final long timestamp = System.currentTimeMillis();
|
||||
final VerifiedPlayer verifiedPlayer = new VerifiedPlayer(inetAddress.toString(), uuid, timestamp);
|
||||
Sonar.get().getVerifiedPlayerController().add(verifiedPlayer);
|
||||
|
||||
invocation.getSender().sendMessage(SONAR.getConfig().getCommands().getVerifiedAdd()
|
||||
.replace("%ip%", rawInetAddress));
|
||||
// Unlock the IP address
|
||||
LOCK.remove(rawInetAddress);
|
||||
break;
|
||||
}
|
||||
|
||||
case "clear": {
|
||||
final int verified = SONAR.getVerifiedPlayerController().estimatedSize();
|
||||
|
||||
|
||||
@@ -39,8 +39,6 @@ commands:
|
||||
incorrect-usage: '%prefix%<red>Benutzung: /sonar %usage%'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine ungültige IP-Adresse angibt (Ungültiges Format)
|
||||
invalid-ip-address: '%prefix%Die angegebene IP-Adresse scheint ungültig zu sein.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine illegale IP-Adresse angibt (Lokale IP)
|
||||
illegal-ip-address: '%prefix%Die angegebene IP-Adresse scheint entweder eine lokale oder eine Schleifenrückführungs-IP zu sein.'
|
||||
# Nachricht, die angezeigt wird, wenn die Konsole einen Befehl ausführt, der nur für Spieler ist
|
||||
player-only: '%prefix%<red>Diesen Befehl kannst du nur als Spieler ausführen.'
|
||||
# Nachricht, die angezeigt wird, wenn ein Spieler einen Befehl ausführt, der nur für die Konsole ist
|
||||
@@ -123,6 +121,10 @@ commands:
|
||||
size: '%prefix%Es sind derzeit %amount% einzigartige Spieler verifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse unverifiziert macht
|
||||
removed: '%prefix%Erfolgreich %ip% unverifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse verifiziert macht
|
||||
added: '%prefix%Erfolgreich %ip% verifiziert.'
|
||||
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand eine IP-Adresse unverifiziert macht, die nicht verifiziert ist
|
||||
ip-not-found: '%prefix%Die angegebene IP-Adresse ist nicht verifiziert.'
|
||||
# Nachricht, die angezeigt wird, wenn jemand versucht, die gleiche IP-Adresse zweimal zu unverifizieren (doppelte Operation)
|
||||
|
||||
@@ -38,9 +38,6 @@ commands:
|
||||
incorrect-usage: '%prefix%<red>Usage: /sonar %usage%'
|
||||
# Message that is shown when someone provides an invalid IP address (Invalid format)
|
||||
invalid-ip-address: '%prefix%The IP address you provided seems to be invalid.'
|
||||
# Message that is shown when someone provides an illegal IP address (Local IP)
|
||||
illegal-ip-address: '%prefix%The IP address you provided seems to be either a local
|
||||
or loopback IP.'
|
||||
# Message that is shown when the console runs a command that is player-only
|
||||
player-only: '%prefix%<red>You can only execute this command as a player.'
|
||||
# Message that is shown when a player runs a command that is console-only
|
||||
@@ -127,6 +124,10 @@ commands:
|
||||
size: '%prefix%There are currently %amount% unique player(s) verified.'
|
||||
# Message that is shown when someone un-verifies an IP address
|
||||
removed: '%prefix%Successfully unverified %ip%.'
|
||||
# Message that is shown when someone verifies an IP address
|
||||
added: '%prefix%Successfully verified %ip%.'
|
||||
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
# Message that is shown when someone un-verifies an IP address, but it is not verified
|
||||
ip-not-found: '%prefix%The IP address you provided is not verified.'
|
||||
# Message that is shown when someone tries un-verifying the same IP address twice (double operation)
|
||||
|
||||
@@ -38,8 +38,6 @@ commands:
|
||||
incorrect-usage: '%prefix%<red>Применение: /sonar %usage%'
|
||||
# Сообщение, которое отображается, когда кто-то предоставляет неверный формат IP-адреса
|
||||
invalid-ip-address: '%prefix%Указанный вами IP-адрес недействителен'
|
||||
# Сообщение, которое отображается, когда кто-то предоставляет локальный IP-адрес
|
||||
illegal-ip-address: '%prefix%IP-адрес который вы указали является локальным.'
|
||||
# Сообщение, которое отображается, когда консоль выполняет команду, предназначенную только для игроков
|
||||
player-only: '%prefix%<red>Эта команда может быть выполнена только игроком.'
|
||||
# Сообщение, которое отображается, когда игрок выполняет команду, доступную только для консоли
|
||||
@@ -124,6 +122,10 @@ commands:
|
||||
size: '%prefix%В настоящее время проверено %amount% уникальных игроков.'
|
||||
# Сообщение, которое отображается при удалении IP-адреса из списка проверенных игроков
|
||||
removed: '%prefix%Успешно удален %ip%.'
|
||||
# Message that is shown when someone verifies an IP address
|
||||
added: '%prefix%Successfully verified %ip%.'
|
||||
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
# Сообщение, которое показывается когда кто-то удаляет IP из списка но его небыло в списке
|
||||
ip-not-found: '%prefix%Указанный вами IP-адрес не в списке.'
|
||||
# Сообщение, которое показывается когда выполняю сразу две операции (двойная операция)
|
||||
|
||||
@@ -38,8 +38,6 @@ commands:
|
||||
incorrect-usage: '%prefix%<red>Kullanım: /sonar %usage%'
|
||||
# Birisi geçersiz bir IP adresi sağladığında gösterilen mesaj (Geçersiz format)
|
||||
invalid-ip-address: '%prefix%Sağladığınız IP adresi geçersiz görünüyor.'
|
||||
# Birisi yasal olmayan bir IP adresi sağladığında gösterilen mesaj (Yerel IP)
|
||||
illegal-ip-address: '%prefix%Sağladığınız IP adresi yerel veya döngüsel bir IP gibi görünüyor.'
|
||||
# Konsol, sadece oyuncunun çalıştırabileceği bir komut çalıştırdığında gösterilen mesaj
|
||||
player-only: '%prefix%<red>Bu komutu sadece bir oyuncu olarak çalıştırabilirsiniz.'
|
||||
# Bir oyuncu, sadece konsolun çalıştırabileceği bir komutu çalıştırdığında gösterilen mesaj
|
||||
@@ -115,6 +113,10 @@ commands:
|
||||
size: '%prefix%Şu anda %amount% benzersiz oyuncu doğrulanmış.'
|
||||
# Birisi bir IP adresini doğrulanmamış olarak işaretlediğinde gösterilen mesaj
|
||||
removed: '%prefix%Başarıyla %ip% doğrulanmamış olarak işaretlendi.'
|
||||
# Message that is shown when someone verifies an IP address
|
||||
added: '%prefix%Successfully verified %ip%.'
|
||||
# Message that is shown when someone tries to verify an IP address/UUID that are already verified
|
||||
already: '%prefix%The IP address and UUID you provided are already verified.'
|
||||
# Birisi bir IP adresini doğrulanmamış olarak işaretlemeye çalıştığında ama doğrulanmamış olduğunda gösterilen mesaj
|
||||
ip-not-found: '%prefix%Sağladığınız IP adresi doğrulanmış olarak işaretlenmemiş.'
|
||||
# Birisi aynı IP adresini iki kez (çift işlem) doğrulanmamış olarak işaretlemeye çalıştığında gösterilen mesaj
|
||||
|
||||
@@ -18,6 +18,7 @@
|
||||
package xyz.jonesdev.sonar.velocity;
|
||||
|
||||
import com.alessiodp.libby.VelocityLibraryManager;
|
||||
import com.velocitypowered.api.proxy.Player;
|
||||
import lombok.Getter;
|
||||
import net.kyori.adventure.audience.Audience;
|
||||
import org.bstats.charts.SimplePie;
|
||||
@@ -31,8 +32,9 @@ import xyz.jonesdev.sonar.common.boot.SonarBootstrap;
|
||||
import xyz.jonesdev.sonar.common.statistics.CachedBandwidthStatistics;
|
||||
import xyz.jonesdev.sonar.velocity.command.VelocitySonarCommand;
|
||||
import xyz.jonesdev.sonar.velocity.fallback.FallbackInjectionHelper;
|
||||
import xyz.jonesdev.sonar.velocity.fallback.FallbackLoginListener;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.TimeUnit;
|
||||
|
||||
@@ -58,6 +60,18 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
return getPlugin().getServer().getPlayer(uniqueId).orElse(null);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean hasTooManyAccounts(final @NotNull InetAddress inetAddress, final int limit) {
|
||||
int count = 1;
|
||||
for (final Player player : getPlugin().getServer().getAllPlayers()) {
|
||||
// Check if the IP address of the player is equal to the IP trying to connect
|
||||
if (!Objects.equals(player.getRemoteAddress().getAddress(), inetAddress)) continue;
|
||||
// Increment count of duplicated accounts
|
||||
if (++count >= limit) return true;
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Create a wrapper for the plugin logger, so we can use it outside
|
||||
* the velocity module.
|
||||
@@ -98,9 +112,6 @@ public final class SonarVelocity extends SonarBootstrap<SonarVelocityPlugin> {
|
||||
// Register Sonar command
|
||||
getPlugin().getServer().getCommandManager().register("sonar", new VelocitySonarCommand());
|
||||
|
||||
// Register Fallback listener
|
||||
getPlugin().getServer().getEventManager().register(getPlugin(), new FallbackLoginListener());
|
||||
|
||||
// Register traffic service
|
||||
getPlugin().getServer().getScheduler().buildTask(getPlugin(), CachedBandwidthStatistics::reset)
|
||||
.repeat(1L, TimeUnit.SECONDS)
|
||||
|
||||
+10
-15
@@ -40,7 +40,6 @@ import java.util.UUID;
|
||||
import static com.velocitypowered.proxy.network.Connections.*;
|
||||
import static xyz.jonesdev.sonar.api.fallback.FallbackPipelines.FALLBACK_BANDWIDTH;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.customDisconnect;
|
||||
import static xyz.jonesdev.sonar.common.fallback.FallbackUserWrapper.deject;
|
||||
import static xyz.jonesdev.sonar.common.fallback.protocol.FallbackPreparer.*;
|
||||
|
||||
public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter {
|
||||
@@ -108,27 +107,25 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
|
||||
// Check the blacklist here since we cannot let the player "ghost join"
|
||||
if (FALLBACK.getBlacklist().asMap().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, blacklisted, MINECRAFT_DECODER, HANDLER);
|
||||
customDisconnect(channel, protocolVersion, blacklisted, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
// Don't continue the verification process if the verification is disabled
|
||||
if (!Sonar.get().getFallback().shouldVerifyNewPlayers()) {
|
||||
ctx.fireChannelRead(serverLogin);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
// Completely skip Geyser connections
|
||||
if (GeyserUtil.isGeyserConnection(channel, socketAddress)) {
|
||||
ctx.fireChannelRead(serverLogin);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the player is already queued since we don't want bots to flood the queue
|
||||
if (FALLBACK.getQueue().getQueuedPlayers().containsKey(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyQueued, MINECRAFT_DECODER, HANDLER);
|
||||
customDisconnect(channel, protocolVersion, alreadyQueued, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -136,14 +133,14 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
// → is another player with the same IP address connected to Fallback?
|
||||
if (FALLBACK.getConnected().containsKey(serverLogin.getUsername())
|
||||
|| FALLBACK.getConnected().containsValue(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying, MINECRAFT_DECODER, HANDLER);
|
||||
customDisconnect(channel, protocolVersion, alreadyVerifying, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is not allowed to enter the server
|
||||
if (Sonar.get().getConfig().getVerification().getBlacklistedProtocols()
|
||||
.contains(protocolVersion.getProtocol())) {
|
||||
customDisconnect(channel, protocolVersion, protocolBlacklisted, MINECRAFT_DECODER, HANDLER);
|
||||
customDisconnect(channel, protocolVersion, protocolBlacklisted, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -151,22 +148,20 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
final String offlineUUIDString = "OfflinePlayer:" + serverLogin.getUsername();
|
||||
final UUID offlineUUID = UUID.nameUUIDFromBytes(offlineUUIDString.getBytes(StandardCharsets.UTF_8));
|
||||
if (Sonar.get().getVerifiedPlayerController().has(inetAddress, offlineUUID)) {
|
||||
ctx.fireChannelRead(serverLogin);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the IP address is currently being rate-limited
|
||||
if (!FALLBACK.getRatelimiter().attempt(inetAddress)) {
|
||||
customDisconnect(channel, protocolVersion, reconnectedTooFast, MINECRAFT_DECODER, HANDLER);
|
||||
customDisconnect(channel, protocolVersion, reconnectedTooFast, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
// Check if the protocol ID of the player is allowed to bypass verification
|
||||
if (Sonar.get().getConfig().getVerification().getWhitelistedProtocols()
|
||||
.contains(protocolVersion.getProtocol())) {
|
||||
ctx.fireChannelRead(serverLogin);
|
||||
deject(channel.pipeline());
|
||||
initialLogin(ctx, serverLogin, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -176,7 +171,7 @@ public final class FallbackChannelHandler extends FallbackChannelHandlerAdapter
|
||||
// UTF-16 names or other types of exploits
|
||||
if (!Sonar.get().getConfig().getVerification().getValidNameRegex()
|
||||
.matcher(serverLogin.getUsername()).matches()) {
|
||||
customDisconnect(channel, protocolVersion, invalidUsername, MINECRAFT_DECODER, HANDLER);
|
||||
customDisconnect(channel, protocolVersion, invalidUsername, MINECRAFT_ENCODER, HANDLER);
|
||||
return;
|
||||
}
|
||||
|
||||
|
||||
-51
@@ -1,51 +0,0 @@
|
||||
/*
|
||||
* Copyright (C) 2023-2024 Sonar Contributors
|
||||
*
|
||||
* This program is free software: you can redistribute it and/or modify
|
||||
* it under the terms of the GNU General Public License as published by
|
||||
* the Free Software Foundation, either version 3 of the License, or
|
||||
* (at your option) any later version.
|
||||
*
|
||||
* This program is distributed in the hope that it will be useful,
|
||||
* but WITHOUT ANY WARRANTY; without even the implied warranty of
|
||||
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
|
||||
* GNU General Public License for more details.
|
||||
*
|
||||
* You should have received a copy of the GNU General Public License
|
||||
* along with this program. If not, see <https://www.gnu.org/licenses/>.
|
||||
*/
|
||||
|
||||
package xyz.jonesdev.sonar.velocity.fallback;
|
||||
|
||||
import com.velocitypowered.api.event.PostOrder;
|
||||
import com.velocitypowered.api.event.Subscribe;
|
||||
import com.velocitypowered.api.event.connection.PreLoginEvent;
|
||||
import org.jetbrains.annotations.NotNull;
|
||||
import xyz.jonesdev.sonar.api.Sonar;
|
||||
import xyz.jonesdev.sonar.velocity.SonarVelocity;
|
||||
|
||||
import java.net.InetAddress;
|
||||
import java.util.Objects;
|
||||
|
||||
public final class FallbackLoginListener {
|
||||
|
||||
@Subscribe(order = PostOrder.LAST)
|
||||
public void handle(final @NotNull PreLoginEvent event) {
|
||||
final int maxOnlinePerIp = Sonar.get().getConfig().getMaxOnlinePerIp();
|
||||
// Don't do anything if the setting is disabled
|
||||
if (maxOnlinePerIp <= 0) return;
|
||||
|
||||
final InetAddress inetAddress = event.getConnection().getRemoteAddress().getAddress();
|
||||
|
||||
// Check if the number of online players using the same IP address as
|
||||
// the connecting player is greater than the configured amount
|
||||
final long onlinePerIp = SonarVelocity.INSTANCE.getPlugin().getServer().getAllPlayers().stream()
|
||||
.filter(player -> Objects.equals(player.getRemoteAddress().getAddress(), inetAddress))
|
||||
.count();
|
||||
|
||||
// We use '>=' because the player connecting to the server hasn't joined yet
|
||||
if (onlinePerIp >= maxOnlinePerIp) {
|
||||
event.setResult(PreLoginEvent.PreLoginComponentResult.denied(Sonar.get().getConfig().getTooManyOnlinePerIp()));
|
||||
}
|
||||
}
|
||||
}
|
||||
Reference in new issue
Block a user