Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-02 20:56:43 +02:00
parent 802d1dfb33
commit 44951c4001
10 changed files with 1106 additions and 43 deletions

View File

@@ -4,7 +4,9 @@ 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;
@@ -14,9 +16,11 @@ 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;
@@ -24,13 +28,18 @@ 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;
@@ -60,6 +69,23 @@ public class AntiBotModule implements Module, Listener {
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;
@@ -75,6 +101,9 @@ public class AntiBotModule implements Module, Listener {
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>();
private final Map<String, RecentPlayerIdentity> recentIdentityByIp = new ConcurrentHashMap<String, RecentPlayerIdentity>();
private final Map<String, LearningProfile> learningProfiles = new ConcurrentHashMap<String, LearningProfile>();
private final Deque<String> learningRecentEvents = new ArrayDeque<String>();
@Override
public String getName() {
@@ -89,6 +118,7 @@ public class AntiBotModule implements Module, Listener {
this.plugin = (StatusAPI) plugin;
ensureModuleConfigExists();
loadConfig();
ensureSecurityLogFile();
if (!enabled) {
this.plugin.getLogger().info("[AntiBotModule] deaktiviert via " + CONFIG_FILE_NAME + " (antibot.enabled=false)");
@@ -107,6 +137,10 @@ public class AntiBotModule implements Module, Listener {
perIpWindows.clear();
blockedIpsUntil.clear();
vpnCache.clear();
learningProfiles.clear();
synchronized (learningRecentEvents) {
learningRecentEvents.clear();
}
blockedIpsCurrentAttack.clear();
attackMode = false;
}
@@ -119,6 +153,10 @@ public class AntiBotModule implements Module, Listener {
perIpWindows.clear();
blockedIpsUntil.clear();
vpnCache.clear();
learningProfiles.clear();
synchronized (learningRecentEvents) {
learningRecentEvents.clear();
}
blockedIpsCurrentAttack.clear();
attackMode = false;
attackCalmSince = 0L;
@@ -127,6 +165,7 @@ public class AntiBotModule implements Module, Listener {
lastCps = 0;
peakCps.set(0);
loadConfig();
ensureSecurityLogFile();
}
public Map<String, Object> buildSnapshot() {
@@ -145,6 +184,8 @@ public class AntiBotModule implements Module, Listener {
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;
}
@@ -157,6 +198,8 @@ public class AntiBotModule implements Module, Listener {
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;
}
@@ -171,6 +214,8 @@ public class AntiBotModule implements Module, Listener {
return;
}
cacheRecentIdentity(ip, event.getConnection(), System.currentTimeMillis());
recordConnection();
long now = System.currentTimeMillis();
@@ -178,14 +223,36 @@ public class AntiBotModule implements Module, Listener {
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 (isIpRateExceeded(ip, now)) {
blockIp(ip, now);
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) {
@@ -193,13 +260,47 @@ public class AntiBotModule implements Module, Listener {
if (info != null) {
boolean shouldBlock = (vpnBlockProxy && info.proxy) || (vpnBlockHosting && info.hosting);
if (shouldBlock) {
blockIp(ip, now);
blockEvent(event);
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);
}
@@ -218,6 +319,20 @@ public class AntiBotModule implements Module, Listener {
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();
if (sa.getAddress() != null) {
return sa.getAddress().getHostAddress();
}
return sa.getHostString();
}
return String.valueOf(player.getAddress());
}
private void recordConnection() {
long sec = System.currentTimeMillis() / 1000L;
if (sec != currentSecond) {
@@ -266,6 +381,27 @@ public class AntiBotModule implements Module, Listener {
vpnCache.remove(entry.getKey());
}
}
for (Map.Entry<String, RecentPlayerIdentity> entry : recentIdentityByIp.entrySet()) {
RecentPlayerIdentity id = entry.getValue();
if (id == null || (now - id.updatedAtMs) > 600_000L) {
recentIdentityByIp.remove(entry.getKey());
}
}
if (learningModeEnabled) {
long staleAfter = Math.max(60, learningStateWindowSeconds) * 1000L;
for (Map.Entry<String, LearningProfile> entry : learningProfiles.entrySet()) {
LearningProfile lp = entry.getValue();
if (lp == null) {
learningProfiles.remove(entry.getKey());
continue;
}
if ((now - lp.lastSeenAt) > staleAfter && lp.score <= 0) {
learningProfiles.remove(entry.getKey());
}
}
}
}
private void tick() {
@@ -356,6 +492,24 @@ public class AntiBotModule implements Module, Listener {
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());
}
@@ -496,6 +650,266 @@ public class AntiBotModule implements Module, Listener {
}
}
private void evaluateLearningBaseline(String ip, long now) {
LearningProfile profile = learningProfiles.computeIfAbsent(ip, k -> new LearningProfile(now));
synchronized (profile) {
decayLearningProfile(profile, now);
long delta = now - profile.lastConnectionAt;
if (profile.lastConnectionAt > 0 && delta <= Math.max(250L, learningRapidWindowMs)) {
profile.rapidStreak++;
int points = learningRapidPoints + Math.min(profile.rapidStreak, 5);
profile.score += Math.max(1, points);
recordLearningEvent("IP=" + ip + " +" + points + " rapid-connect score=" + profile.score);
} else {
profile.rapidStreak = 0;
}
if (attackMode) {
profile.score += Math.max(1, learningAttackModePoints);
recordLearningEvent("IP=" + ip + " +" + learningAttackModePoints + " attack-mode score=" + profile.score);
}
if (lastCps >= Math.max(1, maxCps)) {
profile.score += Math.max(1, learningHighCpsPoints);
recordLearningEvent("IP=" + ip + " +" + learningHighCpsPoints + " high-cps score=" + profile.score);
}
profile.lastConnectionAt = now;
profile.lastSeenAt = now;
if (profile.score >= learningScoreThreshold) {
blockIp(ip, now);
recordLearningEvent("BLOCK " + ip + " reason=learning-threshold score=" + profile.score);
}
}
}
private int addLearningScore(String ip, long now, int points, String reason, boolean checkThreshold) {
LearningProfile profile = learningProfiles.computeIfAbsent(ip, k -> new LearningProfile(now));
synchronized (profile) {
decayLearningProfile(profile, now);
int add = Math.max(1, points);
profile.score += add;
profile.lastSeenAt = now;
recordLearningEvent("IP=" + ip + " +" + add + " " + reason + " score=" + profile.score);
if (checkThreshold && profile.score >= learningScoreThreshold) {
blockIp(ip, now);
recordLearningEvent("BLOCK " + ip + " reason=" + reason + " score=" + profile.score);
}
return profile.score;
}
}
private int getLearningScore(String ip, long now) {
LearningProfile profile = learningProfiles.get(ip);
if (profile == null) {
return 0;
}
synchronized (profile) {
decayLearningProfile(profile, now);
return profile.score;
}
}
private void decayLearningProfile(LearningProfile profile, long now) {
long elapsedMs = Math.max(0L, now - profile.lastScoreUpdateAt);
if (elapsedMs > 0L) {
long decay = (elapsedMs / 1000L) * Math.max(0, learningDecayPerSecond);
if (decay > 0L) {
profile.score = (int) Math.max(0L, profile.score - decay);
}
profile.lastScoreUpdateAt = now;
}
long resetAfter = Math.max(30, learningStateWindowSeconds) * 1000L;
if (profile.lastSeenAt > 0L && now - profile.lastSeenAt > resetAfter) {
profile.score = 0;
profile.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() || "unknown".equalsIgnoreCase(uuid))
&& 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()) {
UUID offlineUuid = UUID.nameUUIDFromBytes(("OfflinePlayer:" + playerName.trim()).getBytes(StandardCharsets.UTF_8));
return offlineUuid.toString();
}
return "";
}
private String safeLog(String input) {
if (input == null || input.isEmpty()) {
return "-";
}
return input.replace("\n", " ").replace("\r", " ").trim();
}
private List<String> getRecentLearningEvents(int max) {
List<String> out = new ArrayList<String>();
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());
@@ -586,6 +1000,28 @@ public class AntiBotModule implements Module, Listener {
boolean hosting;
}
private static class LearningProfile {
long lastConnectionAt;
long lastScoreUpdateAt;
long lastSeenAt;
int rapidStreak;
int score;
LearningProfile(long now) {
this.lastConnectionAt = now;
this.lastScoreUpdateAt = now;
this.lastSeenAt = now;
this.rapidStreak = 0;
this.score = 0;
}
}
private static class RecentPlayerIdentity {
String playerName;
String playerUuid;
long updatedAtMs;
}
private class AntiBotCommand extends Command {
AntiBotCommand() {
super("antibot", "statusapi.antibot");
@@ -611,6 +1047,16 @@ public class AntiBotModule implements Module, Listener {
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<String> recent = getRecentLearningEvents(3);
if (!recent.isEmpty()) {
sender.sendMessage(ChatColor.YELLOW + "Learning Events:");
for (String line : recent) {
sender.sendMessage(ChatColor.GRAY + "- " + line);
}
}
return;
}