Dateien nach "src/main/java/net/viper/status/modules/antibot" hochladen
This commit is contained in:
@@ -0,0 +1,672 @@
|
|||||||
|
package net.viper.status.modules.antibot;
|
||||||
|
|
||||||
|
import net.md_5.bungee.api.ChatColor;
|
||||||
|
import net.md_5.bungee.api.CommandSender;
|
||||||
|
import net.md_5.bungee.api.ProxyServer;
|
||||||
|
import net.md_5.bungee.api.connection.PendingConnection;
|
||||||
|
import net.md_5.bungee.api.event.PreLoginEvent;
|
||||||
|
import net.md_5.bungee.api.plugin.Command;
|
||||||
|
import net.md_5.bungee.api.plugin.Listener;
|
||||||
|
import net.md_5.bungee.api.plugin.Plugin;
|
||||||
|
import net.md_5.bungee.event.EventHandler;
|
||||||
|
import net.viper.status.StatusAPI;
|
||||||
|
import net.viper.status.module.Module;
|
||||||
|
import net.viper.status.modules.network.NetworkInfoModule;
|
||||||
|
|
||||||
|
import java.io.BufferedReader;
|
||||||
|
import java.io.File;
|
||||||
|
import java.io.FileInputStream;
|
||||||
|
import java.io.FileOutputStream;
|
||||||
|
import java.io.InputStream;
|
||||||
|
import java.io.InputStreamReader;
|
||||||
|
import java.net.HttpURLConnection;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.URL;
|
||||||
|
import java.nio.file.Files;
|
||||||
|
import java.nio.charset.StandardCharsets;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.LinkedHashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Locale;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.Properties;
|
||||||
|
import java.util.Set;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
|
import java.util.concurrent.TimeUnit;
|
||||||
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Eigenstaendige AntiBot/Attack-Guard Funktionen, angelehnt an BetterBungee-Ideen.
|
||||||
|
*/
|
||||||
|
public class AntiBotModule implements Module, Listener {
|
||||||
|
|
||||||
|
private static final String CONFIG_FILE_NAME = "network-guard.properties";
|
||||||
|
|
||||||
|
private StatusAPI plugin;
|
||||||
|
|
||||||
|
private boolean enabled = true;
|
||||||
|
private String profile = "high-traffic";
|
||||||
|
private int maxCps = 120;
|
||||||
|
private int attackStartCps = 220;
|
||||||
|
private int attackStopCps = 120;
|
||||||
|
private int attackCalmSeconds = 20;
|
||||||
|
private int ipConnectionsPerMinute = 18;
|
||||||
|
private int ipBlockSeconds = 600;
|
||||||
|
private String kickMessage = "Zu viele Verbindungen von deiner IP. Bitte warte kurz.";
|
||||||
|
|
||||||
|
private boolean vpnCheckEnabled = false;
|
||||||
|
private boolean vpnBlockProxy = true;
|
||||||
|
private boolean vpnBlockHosting = true;
|
||||||
|
private int vpnCacheMinutes = 30;
|
||||||
|
private int vpnTimeoutMs = 2500;
|
||||||
|
|
||||||
|
private final AtomicInteger currentSecondConnections = new AtomicInteger(0);
|
||||||
|
private volatile long currentSecond = System.currentTimeMillis() / 1000L;
|
||||||
|
private volatile int lastCps = 0;
|
||||||
|
private final AtomicInteger peakCps = new AtomicInteger(0);
|
||||||
|
|
||||||
|
private volatile boolean attackMode = false;
|
||||||
|
private volatile long attackCalmSince = 0L;
|
||||||
|
private final AtomicLong blockedConnectionsTotal = new AtomicLong(0L);
|
||||||
|
private final AtomicLong blockedConnectionsCurrentAttack = new AtomicLong(0L);
|
||||||
|
private final Set<String> blockedIpsCurrentAttack = ConcurrentHashMap.newKeySet();
|
||||||
|
|
||||||
|
private final Map<String, IpWindow> perIpWindows = new ConcurrentHashMap<String, IpWindow>();
|
||||||
|
private final Map<String, Long> blockedIpsUntil = new ConcurrentHashMap<String, Long>();
|
||||||
|
private final Map<String, VpnCacheEntry> vpnCache = new ConcurrentHashMap<String, VpnCacheEntry>();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String getName() {
|
||||||
|
return "AntiBotModule";
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onEnable(Plugin plugin) {
|
||||||
|
if (!(plugin instanceof StatusAPI)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
this.plugin = (StatusAPI) plugin;
|
||||||
|
ensureModuleConfigExists();
|
||||||
|
loadConfig();
|
||||||
|
|
||||||
|
if (!enabled) {
|
||||||
|
this.plugin.getLogger().info("[AntiBotModule] deaktiviert via " + CONFIG_FILE_NAME + " (antibot.enabled=false)");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
ProxyServer.getInstance().getPluginManager().registerListener(this.plugin, this);
|
||||||
|
ProxyServer.getInstance().getPluginManager().registerCommand(this.plugin, new AntiBotCommand());
|
||||||
|
ProxyServer.getInstance().getScheduler().schedule(this.plugin, this::tick, 1, 1, TimeUnit.SECONDS);
|
||||||
|
|
||||||
|
this.plugin.getLogger().info("[AntiBotModule] aktiviert. maxCps=" + maxCps + ", attackStartCps=" + attackStartCps + ", ip/min=" + ipConnectionsPerMinute);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onDisable(Plugin plugin) {
|
||||||
|
perIpWindows.clear();
|
||||||
|
blockedIpsUntil.clear();
|
||||||
|
vpnCache.clear();
|
||||||
|
blockedIpsCurrentAttack.clear();
|
||||||
|
attackMode = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
public boolean isEnabled() {
|
||||||
|
return enabled;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void reloadRuntimeState() {
|
||||||
|
perIpWindows.clear();
|
||||||
|
blockedIpsUntil.clear();
|
||||||
|
vpnCache.clear();
|
||||||
|
blockedIpsCurrentAttack.clear();
|
||||||
|
attackMode = false;
|
||||||
|
attackCalmSince = 0L;
|
||||||
|
blockedConnectionsCurrentAttack.set(0L);
|
||||||
|
currentSecondConnections.set(0);
|
||||||
|
lastCps = 0;
|
||||||
|
peakCps.set(0);
|
||||||
|
loadConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public Map<String, Object> buildSnapshot() {
|
||||||
|
Map<String, Object> out = new LinkedHashMap<String, Object>();
|
||||||
|
out.put("enabled", enabled);
|
||||||
|
out.put("profile", profile);
|
||||||
|
out.put("attack_mode", attackMode);
|
||||||
|
out.put("protection_enabled", enabled);
|
||||||
|
out.put("attack_mode_status", attackMode ? "active" : "normal");
|
||||||
|
out.put("attack_mode_display", attackMode ? "Angriff erkannt" : "Normalbetrieb");
|
||||||
|
out.put("status_message", enabled
|
||||||
|
? (attackMode ? "AntiBot aktiv: Angriff erkannt" : "AntiBot aktiv: kein Angriff erkannt")
|
||||||
|
: "AntiBot deaktiviert");
|
||||||
|
out.put("last_cps", lastCps);
|
||||||
|
out.put("peak_cps", peakCps.get());
|
||||||
|
out.put("blocked_ips_active", blockedIpsUntil.size());
|
||||||
|
out.put("blocked_connections_total", blockedConnectionsTotal.get());
|
||||||
|
out.put("vpn_check_enabled", vpnCheckEnabled);
|
||||||
|
out.put("thresholds", buildThresholds());
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
private Map<String, Object> buildThresholds() {
|
||||||
|
Map<String, Object> m = new LinkedHashMap<String, Object>();
|
||||||
|
m.put("max_cps", maxCps);
|
||||||
|
m.put("attack_start_cps", attackStartCps);
|
||||||
|
m.put("attack_stop_cps", attackStopCps);
|
||||||
|
m.put("attack_calm_seconds", attackCalmSeconds);
|
||||||
|
m.put("ip_connections_per_minute", ipConnectionsPerMinute);
|
||||||
|
m.put("ip_block_seconds", ipBlockSeconds);
|
||||||
|
return m;
|
||||||
|
}
|
||||||
|
|
||||||
|
@EventHandler
|
||||||
|
public void onPreLogin(PreLoginEvent event) {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String ip = extractIp(event.getConnection());
|
||||||
|
if (ip == null || ip.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
recordConnection();
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
cleanupExpired(now);
|
||||||
|
|
||||||
|
Long blockedUntil = blockedIpsUntil.get(ip);
|
||||||
|
if (blockedUntil != null && blockedUntil > now) {
|
||||||
|
blockEvent(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isIpRateExceeded(ip, now)) {
|
||||||
|
blockIp(ip, now);
|
||||||
|
blockEvent(event);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (vpnCheckEnabled) {
|
||||||
|
VpnCheckResult info = getVpnInfo(ip, now);
|
||||||
|
if (info != null) {
|
||||||
|
boolean shouldBlock = (vpnBlockProxy && info.proxy) || (vpnBlockHosting && info.hosting);
|
||||||
|
if (shouldBlock) {
|
||||||
|
blockIp(ip, now);
|
||||||
|
blockEvent(event);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void blockEvent(PreLoginEvent event) {
|
||||||
|
event.setCancelled(true);
|
||||||
|
}
|
||||||
|
|
||||||
|
private String extractIp(PendingConnection conn) {
|
||||||
|
if (conn == null || conn.getAddress() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (conn.getAddress() instanceof InetSocketAddress) {
|
||||||
|
InetSocketAddress sa = (InetSocketAddress) conn.getAddress();
|
||||||
|
if (sa.getAddress() != null) {
|
||||||
|
return sa.getAddress().getHostAddress();
|
||||||
|
}
|
||||||
|
return sa.getHostString();
|
||||||
|
}
|
||||||
|
return String.valueOf(conn.getAddress());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void recordConnection() {
|
||||||
|
long sec = System.currentTimeMillis() / 1000L;
|
||||||
|
if (sec != currentSecond) {
|
||||||
|
synchronized (this) {
|
||||||
|
if (sec != currentSecond) {
|
||||||
|
currentSecond = sec;
|
||||||
|
currentSecondConnections.set(0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
currentSecondConnections.incrementAndGet();
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isIpRateExceeded(String ip, long now) {
|
||||||
|
IpWindow window = perIpWindows.computeIfAbsent(ip, k -> new IpWindow(now));
|
||||||
|
synchronized (window) {
|
||||||
|
long diff = now - window.windowStart;
|
||||||
|
if (diff > 60_000L) {
|
||||||
|
window.windowStart = now;
|
||||||
|
window.count = 0;
|
||||||
|
}
|
||||||
|
window.count++;
|
||||||
|
return window.count > Math.max(1, ipConnectionsPerMinute);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void blockIp(String ip, long now) {
|
||||||
|
blockedIpsUntil.put(ip, now + Math.max(1, ipBlockSeconds) * 1000L);
|
||||||
|
blockedConnectionsTotal.incrementAndGet();
|
||||||
|
|
||||||
|
if (attackMode) {
|
||||||
|
blockedConnectionsCurrentAttack.incrementAndGet();
|
||||||
|
blockedIpsCurrentAttack.add(ip);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void cleanupExpired(long now) {
|
||||||
|
for (Map.Entry<String, Long> entry : blockedIpsUntil.entrySet()) {
|
||||||
|
if (entry.getValue() <= now) {
|
||||||
|
blockedIpsUntil.remove(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
for (Map.Entry<String, VpnCacheEntry> entry : vpnCache.entrySet()) {
|
||||||
|
if (entry.getValue().expiresAt <= now) {
|
||||||
|
vpnCache.remove(entry.getKey());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void tick() {
|
||||||
|
if (!enabled) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
int cps = currentSecondConnections.getAndSet(0);
|
||||||
|
lastCps = cps;
|
||||||
|
|
||||||
|
if (cps > peakCps.get()) {
|
||||||
|
peakCps.set(cps);
|
||||||
|
}
|
||||||
|
|
||||||
|
long now = System.currentTimeMillis();
|
||||||
|
|
||||||
|
if (!attackMode && cps >= Math.max(1, attackStartCps)) {
|
||||||
|
attackMode = true;
|
||||||
|
attackCalmSince = 0L;
|
||||||
|
blockedConnectionsCurrentAttack.set(0L);
|
||||||
|
blockedIpsCurrentAttack.clear();
|
||||||
|
sendAttackToWebhook("detected", cps, null, null, "StatusAPI AntiBot");
|
||||||
|
plugin.getLogger().warning("[AntiBotModule] Attack erkannt. CPS=" + cps);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (attackMode) {
|
||||||
|
if (cps <= Math.max(1, attackStopCps)) {
|
||||||
|
if (attackCalmSince == 0L) {
|
||||||
|
attackCalmSince = now;
|
||||||
|
}
|
||||||
|
|
||||||
|
long calmFor = now - attackCalmSince;
|
||||||
|
if (calmFor >= Math.max(1, attackCalmSeconds) * 1000L) {
|
||||||
|
attackMode = false;
|
||||||
|
attackCalmSince = 0L;
|
||||||
|
|
||||||
|
int blockedIps = blockedIpsCurrentAttack.size();
|
||||||
|
long blockedConns = blockedConnectionsCurrentAttack.get();
|
||||||
|
sendAttackToWebhook("stopped", cps, blockedIps, blockedConns, "StatusAPI AntiBot");
|
||||||
|
plugin.getLogger().warning("[AntiBotModule] Attack beendet. blockedIps=" + blockedIps + ", blockedConnections=" + blockedConns);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
attackCalmSince = 0L;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void sendAttackToWebhook(String type, Integer cps, Integer blockedIps, Long blockedConnections, String source) {
|
||||||
|
NetworkInfoModule networkInfoModule = getNetworkInfoModule();
|
||||||
|
if (networkInfoModule == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
networkInfoModule.sendAttackNotification(type, cps, blockedIps, blockedConnections, source);
|
||||||
|
}
|
||||||
|
|
||||||
|
private NetworkInfoModule getNetworkInfoModule() {
|
||||||
|
if (plugin == null || plugin.getModuleManager() == null) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
return (NetworkInfoModule) plugin.getModuleManager().getModule("NetworkInfoModule");
|
||||||
|
}
|
||||||
|
|
||||||
|
private void loadConfig() {
|
||||||
|
File file = new File(plugin.getDataFolder(), CONFIG_FILE_NAME);
|
||||||
|
if (!file.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Properties props = new Properties();
|
||||||
|
try (FileInputStream in = new FileInputStream(file)) {
|
||||||
|
props.load(new InputStreamReader(in, StandardCharsets.UTF_8));
|
||||||
|
|
||||||
|
enabled = parseBoolean(props.getProperty("antibot.enabled"), true);
|
||||||
|
profile = normalizeProfile(props.getProperty("antibot.profile", "high-traffic"));
|
||||||
|
applyProfileDefaults(profile);
|
||||||
|
|
||||||
|
maxCps = parseInt(props.getProperty("antibot.max_cps"), maxCps);
|
||||||
|
attackStartCps = parseInt(props.getProperty("antibot.attack.start_cps"), attackStartCps);
|
||||||
|
attackStopCps = parseInt(props.getProperty("antibot.attack.stop_cps"), attackStopCps);
|
||||||
|
attackCalmSeconds = parseInt(props.getProperty("antibot.attack.stop_grace_seconds"), attackCalmSeconds);
|
||||||
|
ipConnectionsPerMinute = parseInt(props.getProperty("antibot.ip.max_connections_per_minute"), ipConnectionsPerMinute);
|
||||||
|
ipBlockSeconds = parseInt(props.getProperty("antibot.ip.block_seconds"), ipBlockSeconds);
|
||||||
|
kickMessage = props.getProperty("antibot.kick_message", kickMessage);
|
||||||
|
|
||||||
|
vpnCheckEnabled = parseBoolean(props.getProperty("antibot.vpn_check.enabled"), vpnCheckEnabled);
|
||||||
|
vpnBlockProxy = parseBoolean(props.getProperty("antibot.vpn_check.block_proxy"), vpnBlockProxy);
|
||||||
|
vpnBlockHosting = parseBoolean(props.getProperty("antibot.vpn_check.block_hosting"), vpnBlockHosting);
|
||||||
|
vpnCacheMinutes = parseInt(props.getProperty("antibot.vpn_check.cache_minutes"), vpnCacheMinutes);
|
||||||
|
vpnTimeoutMs = parseInt(props.getProperty("antibot.vpn_check.timeout_ms"), vpnTimeoutMs);
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[AntiBotModule] Fehler beim Laden von " + CONFIG_FILE_NAME + ": " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private String normalizeProfile(String raw) {
|
||||||
|
if (raw == null) {
|
||||||
|
return "high-traffic";
|
||||||
|
}
|
||||||
|
String value = raw.trim().toLowerCase(Locale.ROOT);
|
||||||
|
if ("strict".equals(value)) {
|
||||||
|
return "strict";
|
||||||
|
}
|
||||||
|
return "high-traffic";
|
||||||
|
}
|
||||||
|
|
||||||
|
private void applyProfileDefaults(String profileName) {
|
||||||
|
if ("strict".equals(profileName)) {
|
||||||
|
maxCps = 120;
|
||||||
|
attackStartCps = 220;
|
||||||
|
attackStopCps = 120;
|
||||||
|
attackCalmSeconds = 20;
|
||||||
|
ipConnectionsPerMinute = 18;
|
||||||
|
ipBlockSeconds = 900;
|
||||||
|
vpnCheckEnabled = true;
|
||||||
|
vpnBlockProxy = true;
|
||||||
|
vpnBlockHosting = true;
|
||||||
|
vpnCacheMinutes = 30;
|
||||||
|
vpnTimeoutMs = 2500;
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
maxCps = 180;
|
||||||
|
attackStartCps = 300;
|
||||||
|
attackStopCps = 170;
|
||||||
|
attackCalmSeconds = 25;
|
||||||
|
ipConnectionsPerMinute = 24;
|
||||||
|
ipBlockSeconds = 600;
|
||||||
|
vpnCheckEnabled = false;
|
||||||
|
vpnBlockProxy = true;
|
||||||
|
vpnBlockHosting = true;
|
||||||
|
vpnCacheMinutes = 30;
|
||||||
|
vpnTimeoutMs = 2500;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean isSupportedProfile(String raw) {
|
||||||
|
if (raw == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
String value = raw.trim().toLowerCase(Locale.ROOT);
|
||||||
|
return "strict".equals(value) || "high-traffic".equals(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean applyProfileAndPersist(String requestedProfile) {
|
||||||
|
if (!isSupportedProfile(requestedProfile)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
String normalized = normalizeProfile(requestedProfile);
|
||||||
|
profile = normalized;
|
||||||
|
applyProfileDefaults(normalized);
|
||||||
|
|
||||||
|
Map<String, String> values = new LinkedHashMap<String, String>();
|
||||||
|
values.put("antibot.profile", normalized);
|
||||||
|
values.put("antibot.max_cps", String.valueOf(maxCps));
|
||||||
|
values.put("antibot.attack.start_cps", String.valueOf(attackStartCps));
|
||||||
|
values.put("antibot.attack.stop_cps", String.valueOf(attackStopCps));
|
||||||
|
values.put("antibot.attack.stop_grace_seconds", String.valueOf(attackCalmSeconds));
|
||||||
|
values.put("antibot.ip.max_connections_per_minute", String.valueOf(ipConnectionsPerMinute));
|
||||||
|
values.put("antibot.ip.block_seconds", String.valueOf(ipBlockSeconds));
|
||||||
|
values.put("antibot.vpn_check.enabled", String.valueOf(vpnCheckEnabled));
|
||||||
|
values.put("antibot.vpn_check.block_proxy", String.valueOf(vpnBlockProxy));
|
||||||
|
values.put("antibot.vpn_check.block_hosting", String.valueOf(vpnBlockHosting));
|
||||||
|
values.put("antibot.vpn_check.cache_minutes", String.valueOf(vpnCacheMinutes));
|
||||||
|
values.put("antibot.vpn_check.timeout_ms", String.valueOf(vpnTimeoutMs));
|
||||||
|
|
||||||
|
try {
|
||||||
|
updateConfigValues(values);
|
||||||
|
return true;
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[AntiBotModule] Konnte Profil nicht speichern: " + e.getMessage());
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private synchronized void updateConfigValues(Map<String, String> keyValues) throws Exception {
|
||||||
|
File target = new File(plugin.getDataFolder(), CONFIG_FILE_NAME);
|
||||||
|
List<String> lines = target.exists()
|
||||||
|
? Files.readAllLines(target.toPath(), StandardCharsets.UTF_8)
|
||||||
|
: new ArrayList<String>();
|
||||||
|
|
||||||
|
for (Map.Entry<String, String> entry : keyValues.entrySet()) {
|
||||||
|
String key = entry.getKey();
|
||||||
|
String newLine = key + "=" + entry.getValue();
|
||||||
|
boolean replaced = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < lines.size(); i++) {
|
||||||
|
String current = lines.get(i).trim();
|
||||||
|
if (current.startsWith(key + "=")) {
|
||||||
|
lines.set(i, newLine);
|
||||||
|
replaced = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!replaced) {
|
||||||
|
lines.add(newLine);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Files.write(target.toPath(), lines, StandardCharsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void ensureModuleConfigExists() {
|
||||||
|
File target = new File(plugin.getDataFolder(), CONFIG_FILE_NAME);
|
||||||
|
if (target.exists()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!plugin.getDataFolder().exists()) {
|
||||||
|
plugin.getDataFolder().mkdirs();
|
||||||
|
}
|
||||||
|
|
||||||
|
try (InputStream in = plugin.getResourceAsStream(CONFIG_FILE_NAME);
|
||||||
|
FileOutputStream out = new FileOutputStream(target)) {
|
||||||
|
if (in == null) {
|
||||||
|
plugin.getLogger().warning("[AntiBotModule] Standarddatei " + CONFIG_FILE_NAME + " nicht im JAR gefunden.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
byte[] buffer = new byte[4096];
|
||||||
|
int read;
|
||||||
|
while ((read = in.read(buffer)) != -1) {
|
||||||
|
out.write(buffer, 0, read);
|
||||||
|
}
|
||||||
|
plugin.getLogger().info("[AntiBotModule] " + CONFIG_FILE_NAME + " wurde erstellt.");
|
||||||
|
} catch (Exception e) {
|
||||||
|
plugin.getLogger().warning("[AntiBotModule] Konnte " + CONFIG_FILE_NAME + " nicht erstellen: " + e.getMessage());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean parseBoolean(String s, boolean fallback) {
|
||||||
|
if (s == null) return fallback;
|
||||||
|
return Boolean.parseBoolean(s.trim());
|
||||||
|
}
|
||||||
|
|
||||||
|
private int parseInt(String s, int fallback) {
|
||||||
|
try {
|
||||||
|
return Integer.parseInt(s == null ? "" : s.trim());
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return fallback;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private VpnCheckResult getVpnInfo(String ip, long now) {
|
||||||
|
VpnCacheEntry cached = vpnCache.get(ip);
|
||||||
|
if (cached != null && cached.expiresAt > now) {
|
||||||
|
return cached.result;
|
||||||
|
}
|
||||||
|
|
||||||
|
VpnCheckResult fresh = requestIpApi(ip);
|
||||||
|
if (fresh != null) {
|
||||||
|
VpnCacheEntry entry = new VpnCacheEntry();
|
||||||
|
entry.result = fresh;
|
||||||
|
entry.expiresAt = now + Math.max(1, vpnCacheMinutes) * 60_000L;
|
||||||
|
vpnCache.put(ip, entry);
|
||||||
|
}
|
||||||
|
return fresh;
|
||||||
|
}
|
||||||
|
|
||||||
|
private VpnCheckResult requestIpApi(String ip) {
|
||||||
|
HttpURLConnection conn = null;
|
||||||
|
try {
|
||||||
|
String url = "http://ip-api.com/json/" + ip + "?fields=status,proxy,hosting";
|
||||||
|
conn = (HttpURLConnection) new URL(url).openConnection();
|
||||||
|
conn.setRequestMethod("GET");
|
||||||
|
conn.setConnectTimeout(vpnTimeoutMs);
|
||||||
|
conn.setReadTimeout(vpnTimeoutMs);
|
||||||
|
conn.setRequestProperty("User-Agent", "StatusAPI-AntiBot/1.0");
|
||||||
|
|
||||||
|
int code = conn.getResponseCode();
|
||||||
|
if (code < 200 || code >= 300) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
StringBuilder sb = new StringBuilder();
|
||||||
|
try (BufferedReader br = new BufferedReader(new InputStreamReader(conn.getInputStream(), StandardCharsets.UTF_8))) {
|
||||||
|
String line;
|
||||||
|
while ((line = br.readLine()) != null) {
|
||||||
|
sb.append(line);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String json = sb.toString();
|
||||||
|
if (json.isEmpty() || !json.contains("\"status\":\"success\"")) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
VpnCheckResult result = new VpnCheckResult();
|
||||||
|
result.proxy = json.contains("\"proxy\":true");
|
||||||
|
result.hosting = json.contains("\"hosting\":true");
|
||||||
|
return result;
|
||||||
|
} catch (Exception ignored) {
|
||||||
|
return null;
|
||||||
|
} finally {
|
||||||
|
if (conn != null) {
|
||||||
|
conn.disconnect();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class IpWindow {
|
||||||
|
long windowStart;
|
||||||
|
int count;
|
||||||
|
|
||||||
|
IpWindow(long now) {
|
||||||
|
this.windowStart = now;
|
||||||
|
this.count = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class VpnCacheEntry {
|
||||||
|
VpnCheckResult result;
|
||||||
|
long expiresAt;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class VpnCheckResult {
|
||||||
|
boolean proxy;
|
||||||
|
boolean hosting;
|
||||||
|
}
|
||||||
|
|
||||||
|
private class AntiBotCommand extends Command {
|
||||||
|
AntiBotCommand() {
|
||||||
|
super("antibot", "statusapi.antibot");
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(CommandSender sender, String[] args) {
|
||||||
|
if (!enabled) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "AntiBotModule ist deaktiviert.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (args.length == 0 || "status".equalsIgnoreCase(args[0])) {
|
||||||
|
sender.sendMessage(ChatColor.GOLD + "----- AntiBot Status -----");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Schutz aktiv: " + ChatColor.WHITE + enabled + ChatColor.GRAY + " (Modul eingeschaltet)");
|
||||||
|
if (attackMode) {
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Attack Mode: " + ChatColor.RED + "AKTIV" + ChatColor.GRAY + " (Angriff erkannt)");
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Attack Mode: " + ChatColor.GREEN + "Normal" + ChatColor.GRAY + " (kein Angriff erkannt)");
|
||||||
|
}
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "CPS: " + ChatColor.WHITE + lastCps + ChatColor.GRAY + " (Peak " + peakCps.get() + ")");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Schwellen: " + ChatColor.WHITE + "start " + attackStartCps + ChatColor.GRAY + " / " + ChatColor.WHITE + "stop " + attackStopCps + ChatColor.GRAY + " CPS");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Active IP Blocks: " + ChatColor.WHITE + blockedIpsUntil.size());
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Total blocked connections: " + ChatColor.WHITE + blockedConnectionsTotal.get());
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "VPN Check: " + ChatColor.WHITE + vpnCheckEnabled);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("clearblocks".equalsIgnoreCase(args[0])) {
|
||||||
|
blockedIpsUntil.clear();
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "Alle IP-Blocks wurden entfernt.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("unblock".equalsIgnoreCase(args[0]) && args.length >= 2) {
|
||||||
|
String ip = args[1].trim();
|
||||||
|
Long removed = blockedIpsUntil.remove(ip);
|
||||||
|
if (removed != null) {
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "IP entblockt: " + ip);
|
||||||
|
} else {
|
||||||
|
sender.sendMessage(ChatColor.RED + "IP war nicht geblockt: " + ip);
|
||||||
|
}
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("profile".equalsIgnoreCase(args[0])) {
|
||||||
|
if (args.length < 2) {
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Aktuelles Profil: " + ChatColor.WHITE + profile);
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "Benutzung: /antibot profile <strict|high-traffic>");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
String requested = args[1].trim().toLowerCase(Locale.ROOT);
|
||||||
|
if (!isSupportedProfile(requested)) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Unbekanntes Profil. Erlaubt: strict, high-traffic");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
boolean ok = applyProfileAndPersist(requested);
|
||||||
|
if (!ok) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Profil konnte nicht gespeichert werden. Siehe Konsole.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "AntiBot-Profil umgestellt auf: " + requested);
|
||||||
|
sender.sendMessage(ChatColor.GRAY + "Werte wurden in " + CONFIG_FILE_NAME + " gespeichert.");
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if ("reload".equalsIgnoreCase(args[0])) {
|
||||||
|
reloadRuntimeState();
|
||||||
|
sender.sendMessage(ChatColor.GREEN + "AntiBot-Konfiguration neu geladen.");
|
||||||
|
sender.sendMessage(ChatColor.GRAY + "Aktives Profil: " + profile);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "/antibot status");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "/antibot clearblocks");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "/antibot unblock <ip>");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "/antibot profile <strict|high-traffic>");
|
||||||
|
sender.sendMessage(ChatColor.YELLOW + "/antibot reload");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user