diff --git a/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/antibot/AntiBotModule.java b/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/antibot/AntiBotModule.java deleted file mode 100644 index 3ff9864..0000000 --- a/_trash/2026-05-07T19-39-23-130Z/src/main/java/net/viper/status/modules/antibot/AntiBotModule.java +++ /dev/null @@ -1,840 +0,0 @@ -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.connection.ProxiedPlayer; -import net.md_5.bungee.api.event.PreLoginEvent; -import net.md_5.bungee.api.event.PostLoginEvent; -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.BufferedWriter; -import java.io.File; -import java.io.FileInputStream; -import java.io.FileOutputStream; -import java.io.FileWriter; -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.text.SimpleDateFormat; -import java.util.ArrayDeque; -import java.util.ArrayList; -import java.util.Date; -import java.util.Deque; -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.UUID; -import java.util.concurrent.ConcurrentHashMap; -import java.util.concurrent.TimeUnit; -import java.util.concurrent.atomic.AtomicInteger; -import java.util.concurrent.atomic.AtomicLong; - -/** - * Eigenständiger AntiBot/Attack-Guard. - * - * Fixes: - * - cleanupExpired() nutzt jetzt removeIf() statt Iteration + remove() (Bug #3) - * - applyProfileDefaults() setzt korrekten attackDefaultSource aus Config - */ -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 boolean securityLogEnabled = true; - private String securityLogFileName = "antibot-security.log"; - private File securityLogFile; - private final Object securityLogLock = new Object(); - - private boolean learningModeEnabled = true; - private int learningScoreThreshold = 100; - private int learningDecayPerSecond = 2; - private int learningStateWindowSeconds = 120; - private int learningRapidWindowMs = 1500; - private int learningRapidPoints = 12; - private int learningIpRateExceededPoints = 30; - private int learningVpnProxyPoints = 40; - private int learningVpnHostingPoints = 30; - private int learningAttackModePoints = 12; - private int learningHighCpsPoints = 10; - private int learningRecentEventLimit = 30; - - 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 blockedIpsCurrentAttack = ConcurrentHashMap.newKeySet(); - - private final Map perIpWindows = new ConcurrentHashMap<>(); - private final Map blockedIpsUntil = new ConcurrentHashMap<>(); - private final Map vpnCache = new ConcurrentHashMap<>(); - private final Map recentIdentityByIp = new ConcurrentHashMap<>(); - private final Map learningProfiles = new ConcurrentHashMap<>(); - private final Deque learningRecentEvents = new ArrayDeque<>(); - - @Override - public String getName() { return "AntiBotModule"; } - - @Override - public void onEnable(Plugin plugin) { - if (!(plugin instanceof StatusAPI)) return; - this.plugin = (StatusAPI) plugin; - ensureModuleConfigExists(); - loadConfig(); - ensureSecurityLogFile(); - - if (!enabled) { - this.plugin.getLogger().info("[AntiBotModule] deaktiviert via " + CONFIG_FILE_NAME); - 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(); - learningProfiles.clear(); - synchronized (learningRecentEvents) { learningRecentEvents.clear(); } - blockedIpsCurrentAttack.clear(); - attackMode = false; - } - - public boolean isEnabled() { return enabled; } - - private void reloadRuntimeState() { - perIpWindows.clear(); - blockedIpsUntil.clear(); - vpnCache.clear(); - learningProfiles.clear(); - synchronized (learningRecentEvents) { learningRecentEvents.clear(); } - blockedIpsCurrentAttack.clear(); - attackMode = false; - attackCalmSince = 0L; - blockedConnectionsCurrentAttack.set(0L); - currentSecondConnections.set(0); - lastCps = 0; - peakCps.set(0); - loadConfig(); - ensureSecurityLogFile(); - } - - public Map buildSnapshot() { - Map out = new LinkedHashMap<>(); - 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("learning_mode_enabled", learningModeEnabled); - out.put("learning_profiles", learningProfiles.size()); - out.put("thresholds", buildThresholds()); - return out; - } - - private Map buildThresholds() { - Map m = new LinkedHashMap<>(); - 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); - m.put("learning_score_threshold", learningScoreThreshold); - m.put("learning_decay_per_second", learningDecayPerSecond); - return m; - } - - @EventHandler - public void onPreLogin(PreLoginEvent event) { - if (!enabled) return; - - String ip = extractIp(event.getConnection()); - if (ip == null || ip.isEmpty()) return; - - cacheRecentIdentity(ip, event.getConnection(), System.currentTimeMillis()); - recordConnection(); - long now = System.currentTimeMillis(); - - // FIX #3: cleanupExpired verwendet removeIf statt Iteration+remove - cleanupExpired(now); - - Long blockedUntil = blockedIpsUntil.get(ip); - if (blockedUntil != null && blockedUntil > now) { - logSecurityEvent("ip_block_active", ip, event.getConnection(), "blocked_until_ms=" + blockedUntil); - blockEvent(event); - return; - } - - if (learningModeEnabled) { - evaluateLearningBaseline(ip, now); - Long learningBlock = blockedIpsUntil.get(ip); - if (learningBlock != null && learningBlock > now) { - blockEvent(event); - return; - } - } - - boolean ipRateExceeded = isIpRateExceeded(ip, now); - if (ipRateExceeded) { - if (learningModeEnabled) { - int score = addLearningScore(ip, now, learningIpRateExceededPoints, "ip-rate-exceeded", true); - logSecurityEvent("ip_rate_exceeded_scored", ip, event.getConnection(), "score=" + score + ", threshold=" + learningScoreThreshold); - if (score >= learningScoreThreshold) { - logSecurityEvent("learning_threshold_block", ip, event.getConnection(), "reason=ip-rate-exceeded, score=" + score); - blockEvent(event); - return; - } - } else { - blockIp(ip, now); - logSecurityEvent("ip_rate_limit_block", ip, event.getConnection(), "mode=direct"); - blockEvent(event); - return; - } - } - - if (vpnCheckEnabled) { - VpnCheckResult info = getVpnInfo(ip, now); - if (info != null) { - boolean shouldBlock = (vpnBlockProxy && info.proxy) || (vpnBlockHosting && info.hosting); - if (shouldBlock) { - logSecurityEvent("vpn_detected", ip, event.getConnection(), "proxy=" + info.proxy + ", hosting=" + info.hosting); - if (learningModeEnabled) { - if (vpnBlockProxy && info.proxy) addLearningScore(ip, now, learningVpnProxyPoints, "vpn-proxy", false); - if (vpnBlockHosting && info.hosting) addLearningScore(ip, now, learningVpnHostingPoints, "vpn-hosting", false); - int current = getLearningScore(ip, now); - if (current >= learningScoreThreshold) { - blockIp(ip, now); - logSecurityEvent("learning_threshold_block", ip, event.getConnection(), "reason=vpn, score=" + current); - recordLearningEvent("BLOCK " + ip + " reason=vpn score=" + current); - blockEvent(event); - } - } else { - blockIp(ip, now); - logSecurityEvent("vpn_block", ip, event.getConnection(), "mode=direct, proxy=" + info.proxy + ", hosting=" + info.hosting); - blockEvent(event); - } - } - } - } - } - - @EventHandler - public void onPostLogin(PostLoginEvent event) { - if (!enabled || event == null || event.getPlayer() == null) return; - ProxiedPlayer player = event.getPlayer(); - String ip = extractIpFromPlayer(player); - if (ip == null || ip.isEmpty()) return; - cacheRecentIdentityDirect(ip, player.getName(), player.getUniqueId(), System.currentTimeMillis()); - } - - 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(); - return sa.getAddress() != null ? sa.getAddress().getHostAddress() : sa.getHostString(); - } - return String.valueOf(conn.getAddress()); - } - - private String extractIpFromPlayer(ProxiedPlayer player) { - if (player == null || player.getAddress() == null) return null; - if (player.getAddress() instanceof InetSocketAddress) { - InetSocketAddress sa = (InetSocketAddress) player.getAddress(); - return sa.getAddress() != null ? sa.getAddress().getHostAddress() : sa.getHostString(); - } - return String.valueOf(player.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); - } - } - - /** - * FIX #3: Verwendet removeIf() statt for-each + remove() um ConcurrentModificationException zu vermeiden. - */ - private void cleanupExpired(long now) { - blockedIpsUntil.entrySet().removeIf(e -> e.getValue() <= now); - vpnCache.entrySet().removeIf(e -> e.getValue().expiresAt <= now); - recentIdentityByIp.entrySet().removeIf(e -> { - RecentPlayerIdentity id = e.getValue(); - return id == null || (now - id.updatedAtMs) > 600_000L; - }); - if (learningModeEnabled) { - long staleAfter = Math.max(60, learningStateWindowSeconds) * 1000L; - learningProfiles.entrySet().removeIf(e -> { - LearningProfile lp = e.getValue(); - return lp == null || ((now - lp.lastSeenAt) > staleAfter && lp.score <= 0); - }); - } - } - - 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); - securityLogEnabled = parseBoolean(props.getProperty("antibot.security_log.enabled"), securityLogEnabled); - securityLogFileName = props.getProperty("antibot.security_log.file", securityLogFileName).trim(); - if (securityLogFileName.isEmpty()) securityLogFileName = "antibot-security.log"; - - learningModeEnabled = parseBoolean(props.getProperty("antibot.learning.enabled"), learningModeEnabled); - learningScoreThreshold = parseInt(props.getProperty("antibot.learning.score_threshold"), learningScoreThreshold); - learningDecayPerSecond = parseInt(props.getProperty("antibot.learning.decay_per_second"), learningDecayPerSecond); - learningStateWindowSeconds = parseInt(props.getProperty("antibot.learning.state_window_seconds"), learningStateWindowSeconds); - learningRapidWindowMs = parseInt(props.getProperty("antibot.learning.rapid.window_ms"), learningRapidWindowMs); - learningRapidPoints = parseInt(props.getProperty("antibot.learning.rapid.points"), learningRapidPoints); - learningIpRateExceededPoints = parseInt(props.getProperty("antibot.learning.ip_rate_exceeded.points"), learningIpRateExceededPoints); - learningVpnProxyPoints = parseInt(props.getProperty("antibot.learning.vpn_proxy.points"), learningVpnProxyPoints); - learningVpnHostingPoints = parseInt(props.getProperty("antibot.learning.vpn_hosting.points"), learningVpnHostingPoints); - learningAttackModePoints = parseInt(props.getProperty("antibot.learning.attack_mode.points"), learningAttackModePoints); - learningHighCpsPoints = parseInt(props.getProperty("antibot.learning.high_cps.points"), learningHighCpsPoints); - learningRecentEventLimit = parseInt(props.getProperty("antibot.learning.recent_events.limit"), learningRecentEventLimit); - } 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 v = raw.trim().toLowerCase(Locale.ROOT); - return "strict".equals(v) ? "strict" : "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; - } else { - 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 v = raw.trim().toLowerCase(Locale.ROOT); - return "strict".equals(v) || "high-traffic".equals(v); - } - - private boolean applyProfileAndPersist(String requestedProfile) { - if (!isSupportedProfile(requestedProfile)) return false; - String normalized = normalizeProfile(requestedProfile); - profile = normalized; - applyProfileDefaults(normalized); - - Map values = new LinkedHashMap<>(); - 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 keyValues) throws Exception { - File target = new File(plugin.getDataFolder(), CONFIG_FILE_NAME); - List lines = target.exists() - ? Files.readAllLines(target.toPath(), StandardCharsets.UTF_8) - : new ArrayList<>(); - for (Map.Entry entry : keyValues.entrySet()) { - String key = entry.getKey(); - String newLine = key + "=" + entry.getValue(); - boolean replaced = false; - for (int i = 0; i < lines.size(); i++) { - if (lines.get(i).trim().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 nicht im JAR."); return; } - byte[] buffer = new byte[4096]; int read; - while ((read = in.read(buffer)) != -1) out.write(buffer, 0, read); - } catch (Exception e) { - plugin.getLogger().warning("[AntiBotModule] Konnte Config nicht erstellen: " + e.getMessage()); - } - } - - private void evaluateLearningBaseline(String ip, long now) { - LearningProfile lp = learningProfiles.computeIfAbsent(ip, k -> new LearningProfile(now)); - synchronized (lp) { - decayLearningProfile(lp, now); - long delta = now - lp.lastConnectionAt; - if (lp.lastConnectionAt > 0 && delta <= Math.max(250L, learningRapidWindowMs)) { - lp.rapidStreak++; - int points = learningRapidPoints + Math.min(lp.rapidStreak, 5); - lp.score += Math.max(1, points); - recordLearningEvent("IP=" + ip + " +" + points + " rapid-connect score=" + lp.score); - } else { - lp.rapidStreak = 0; - } - if (attackMode) { lp.score += Math.max(1, learningAttackModePoints); recordLearningEvent("IP=" + ip + " +" + learningAttackModePoints + " attack-mode score=" + lp.score); } - if (lastCps >= Math.max(1, maxCps)) { lp.score += Math.max(1, learningHighCpsPoints); recordLearningEvent("IP=" + ip + " +" + learningHighCpsPoints + " high-cps score=" + lp.score); } - lp.lastConnectionAt = now; - lp.lastSeenAt = now; - if (lp.score >= learningScoreThreshold) { - blockIp(ip, now); - recordLearningEvent("BLOCK " + ip + " reason=learning-threshold score=" + lp.score); - } - } - } - - private int addLearningScore(String ip, long now, int points, String reason, boolean checkThreshold) { - LearningProfile lp = learningProfiles.computeIfAbsent(ip, k -> new LearningProfile(now)); - synchronized (lp) { - decayLearningProfile(lp, now); - int add = Math.max(1, points); - lp.score += add; - lp.lastSeenAt = now; - recordLearningEvent("IP=" + ip + " +" + add + " " + reason + " score=" + lp.score); - if (checkThreshold && lp.score >= learningScoreThreshold) { - blockIp(ip, now); - recordLearningEvent("BLOCK " + ip + " reason=" + reason + " score=" + lp.score); - } - return lp.score; - } - } - - private int getLearningScore(String ip, long now) { - LearningProfile lp = learningProfiles.get(ip); - if (lp == null) return 0; - synchronized (lp) { decayLearningProfile(lp, now); return lp.score; } - } - - private void decayLearningProfile(LearningProfile lp, long now) { - long elapsedMs = Math.max(0L, now - lp.lastScoreUpdateAt); - if (elapsedMs > 0L) { - long decay = (elapsedMs / 1000L) * Math.max(0, learningDecayPerSecond); - if (decay > 0L) lp.score = (int) Math.max(0L, lp.score - decay); - lp.lastScoreUpdateAt = now; - } - long resetAfter = Math.max(30, learningStateWindowSeconds) * 1000L; - if (lp.lastSeenAt > 0L && now - lp.lastSeenAt > resetAfter) { - lp.score = 0; - lp.rapidStreak = 0; - } - } - - private void recordLearningEvent(String event) { - String line = new SimpleDateFormat("HH:mm:ss").format(new Date()) + " " + event; - synchronized (learningRecentEvents) { - learningRecentEvents.addLast(line); - while (learningRecentEvents.size() > Math.max(5, learningRecentEventLimit)) learningRecentEvents.pollFirst(); - } - } - - private void ensureSecurityLogFile() { - if (plugin == null) return; - if (!plugin.getDataFolder().exists()) plugin.getDataFolder().mkdirs(); - securityLogFile = new File(plugin.getDataFolder(), securityLogFileName); - try { if (!securityLogFile.exists()) securityLogFile.createNewFile(); } - catch (Exception e) { plugin.getLogger().warning("[AntiBotModule] Konnte Sicherheitslog nicht erstellen: " + e.getMessage()); } - } - - private void logSecurityEvent(String eventType, String ip, PendingConnection conn, String details) { - if (!securityLogEnabled || plugin == null) return; - if (securityLogFile == null) { ensureSecurityLogFile(); if (securityLogFile == null) return; } - - String name = extractPlayerName(conn); - String uuid = extractPlayerUuid(conn, name); - if ((name == null || name.isEmpty() || "unknown".equalsIgnoreCase(name)) && ip != null) { - RecentPlayerIdentity cached = recentIdentityByIp.get(ip); - if (cached != null) { - if (cached.playerName != null && !cached.playerName.trim().isEmpty()) name = cached.playerName; - if ((uuid == null || uuid.isEmpty()) && cached.playerUuid != null && !cached.playerUuid.trim().isEmpty()) uuid = cached.playerUuid; - } - } - if (name == null || name.trim().isEmpty()) name = "unknown"; - if (uuid == null || uuid.trim().isEmpty()) uuid = "unknown"; - - String line = new SimpleDateFormat("yyyy-MM-dd HH:mm:ss").format(new Date()) - + " | event=" + safeLog(eventType) - + " | ip=" + safeLog(ip) - + " | player=" + safeLog(name) - + " | uuid=" + safeLog(uuid) - + " | details=" + safeLog(details); - - synchronized (securityLogLock) { - try (BufferedWriter bw = new BufferedWriter(new FileWriter(securityLogFile, true))) { - bw.write(line); bw.newLine(); - } catch (Exception e) { - plugin.getLogger().warning("[AntiBotModule] Sicherheitslog-Schreibfehler: " + e.getMessage()); - } - } - } - - private void cacheRecentIdentity(String ip, PendingConnection conn, long now) { - if (ip == null || ip.isEmpty() || conn == null) return; - String name = extractPlayerName(conn); - String uuid = extractPlayerUuid(conn, name); - if ((name == null || name.isEmpty()) && (uuid == null || uuid.isEmpty())) return; - RecentPlayerIdentity identity = recentIdentityByIp.computeIfAbsent(ip, k -> new RecentPlayerIdentity()); - synchronized (identity) { - if (name != null && !name.trim().isEmpty()) identity.playerName = name.trim(); - if (uuid != null && !uuid.trim().isEmpty()) identity.playerUuid = uuid.trim(); - identity.updatedAtMs = now; - } - } - - private void cacheRecentIdentityDirect(String ip, String playerName, UUID playerUuid, long now) { - if (ip == null || ip.isEmpty()) return; - String name = playerName == null ? "" : playerName.trim(); - String uuid = playerUuid == null ? "" : playerUuid.toString(); - if (name.isEmpty() && uuid.isEmpty()) return; - RecentPlayerIdentity identity = recentIdentityByIp.computeIfAbsent(ip, k -> new RecentPlayerIdentity()); - synchronized (identity) { - if (!name.isEmpty()) identity.playerName = name; - if (!uuid.isEmpty()) identity.playerUuid = uuid; - identity.updatedAtMs = now; - } - } - - private String extractPlayerName(PendingConnection conn) { - if (conn == null) return ""; - try { String raw = conn.getName(); return raw == null ? "" : raw.trim(); } catch (Exception ignored) { return ""; } - } - - private String extractPlayerUuid(PendingConnection conn, String playerName) { - if (conn != null) { - try { UUID uuid = conn.getUniqueId(); if (uuid != null) return uuid.toString(); } catch (Exception ignored) {} - } - if (playerName != null && !playerName.trim().isEmpty()) { - return UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName.trim()).getBytes(StandardCharsets.UTF_8)).toString(); - } - return ""; - } - - private String safeLog(String input) { - if (input == null || input.isEmpty()) return "-"; - return input.replace("\n", " ").replace("\r", " ").trim(); - } - - private List getRecentLearningEvents(int max) { - List out = new ArrayList<>(); - synchronized (learningRecentEvents) { - int skip = Math.max(0, learningRecentEvents.size() - Math.max(1, max)); - int idx = 0; - for (String line : learningRecentEvents) { - if (idx++ < skip) continue; - out.add(line); - } - } - return out; - } - - 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"); - if (conn.getResponseCode() < 200 || conn.getResponseCode() >= 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(); } - } - - // --- Interne Klassen --- - - 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 static class LearningProfile { - long lastConnectionAt, lastScoreUpdateAt, lastSeenAt; - int rapidStreak, score; - LearningProfile(long now) { lastConnectionAt = lastScoreUpdateAt = lastSeenAt = now; } - } - - private static class RecentPlayerIdentity { String playerName; String playerUuid; long updatedAtMs; } - - // --- Command --- - - 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); - sender.sendMessage(ChatColor.YELLOW + "Profil: " + ChatColor.WHITE + profile); - if (attackMode) sender.sendMessage(ChatColor.YELLOW + "Attack Mode: " + ChatColor.RED + "AKTIV"); - else sender.sendMessage(ChatColor.YELLOW + "Attack Mode: " + ChatColor.GREEN + "Normal"); - 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); - sender.sendMessage(ChatColor.YELLOW + "Learning Mode: " + ChatColor.WHITE + learningModeEnabled - + ChatColor.GRAY + " (threshold=" + learningScoreThreshold + ")"); - List recent = getRecentLearningEvents(3); - if (!recent.isEmpty()) { - sender.sendMessage(ChatColor.YELLOW + "Learning Events:"); - for (String line : recent) sender.sendMessage(ChatColor.GRAY + "- " + line); - } - 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(); - if (blockedIpsUntil.remove(ip) != 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 "); - 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."); 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 "); - sender.sendMessage(ChatColor.YELLOW + "/antibot profile "); - sender.sendMessage(ChatColor.YELLOW + "/antibot reload"); - } - } -}