Upload folder via GUI - src

This commit is contained in:
Git Manager GUI
2026-04-02 09:03:02 +02:00
parent 3fad92a6de
commit a27feca901
3 changed files with 552 additions and 0 deletions

View File

@@ -0,0 +1,509 @@
package net.viper.backendguard;
import org.bukkit.ChatColor;
import org.bukkit.command.Command;
import org.bukkit.command.CommandSender;
import org.bukkit.configuration.file.FileConfiguration;
import org.bukkit.event.EventHandler;
import org.bukkit.event.EventPriority;
import org.bukkit.event.Listener;
import org.bukkit.event.player.AsyncPlayerPreLoginEvent;
import org.bukkit.plugin.java.JavaPlugin;
import org.bukkit.scheduler.BukkitTask;
import java.io.BufferedReader;
import java.io.InputStreamReader;
import java.net.HttpURLConnection;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.net.URL;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.HashSet;
import java.util.List;
import java.util.Locale;
import java.util.Set;
public class BackendJoinGuardPlugin extends JavaPlugin implements Listener {
private boolean enforcementEnabled;
private boolean logBlockedAttempts;
private String kickMessage;
private final Set<String> allowedExactIps = new HashSet<String>();
private final List<SubnetRule> allowedCidrs = new ArrayList<SubnetRule>();
private boolean statusApiSyncEnabled;
private String statusApiBaseUrl;
private String statusApiEndpointPath;
private String statusApiApiKey;
private int statusApiIntervalSeconds;
private boolean statusApiLogSyncErrors;
private BukkitTask syncTask;
@Override
public void onEnable() {
saveDefaultConfig();
reloadGuardConfig();
getServer().getPluginManager().registerEvents(this, this);
getLogger().info("BackendJoinGuard aktiviert. Erlaubte Proxy-IPs=" + allowedExactIps.size() + ", CIDRs=" + allowedCidrs.size() + ", StatusAPI-Sync=" + statusApiSyncEnabled);
}
@Override
public void onDisable() {
cancelSyncTask();
}
@EventHandler(priority = EventPriority.HIGHEST)
public void onAsyncPlayerPreLogin(AsyncPlayerPreLoginEvent event) {
if (!enforcementEnabled) {
return;
}
InetAddress address = event.getAddress();
if (address == null) {
return;
}
if (isAllowed(address)) {
return;
}
event.disallow(AsyncPlayerPreLoginEvent.Result.KICK_OTHER, colorize(kickMessage));
if (logBlockedAttempts) {
getLogger().warning("Direktjoin blockiert: player=" + event.getName() + ", ip=" + address.getHostAddress());
}
}
@Override
public boolean onCommand(CommandSender sender, Command command, String label, String[] args) {
if (!"backendguard".equalsIgnoreCase(command.getName())) {
return false;
}
if (!sender.hasPermission("backendguard.admin")) {
sender.sendMessage(colorize("&cDafuer hast du keine Rechte."));
return true;
}
if (args.length == 1 && "reload".equalsIgnoreCase(args[0])) {
reloadConfig();
reloadGuardConfig();
if (statusApiSyncEnabled) {
syncFromStatusApi(true);
sender.sendMessage(colorize("&aBackendJoinGuard neu geladen. StatusAPI-Sync aktiv."));
} else {
sender.sendMessage(colorize("&aBackendJoinGuard neu geladen. Standalone-Modus aktiv."));
}
return true;
}
sender.sendMessage(colorize("&e/backendguard reload"));
return true;
}
private void reloadGuardConfig() {
FileConfiguration config = getConfig();
enforcementEnabled = config.getBoolean("enforcement-enabled", true);
logBlockedAttempts = config.getBoolean("log-blocked-attempts", true);
kickMessage = config.getString("kick-message", "&cBitte verbinde dich nur ueber den Proxy.");
allowedExactIps.clear();
allowedCidrs.clear();
for (String entry : config.getStringList("allowed-proxy-ips")) {
String normalized = normalizeIp(entry);
if (normalized != null) {
allowedExactIps.add(normalized);
}
}
for (String entry : config.getStringList("allowed-proxy-cidrs")) {
SubnetRule rule = parseSubnet(entry);
if (rule != null) {
allowedCidrs.add(rule);
}
}
statusApiSyncEnabled = config.getBoolean("statusapi-sync.enabled", false);
statusApiBaseUrl = config.getString("statusapi-sync.base-url", "http://127.0.0.1:9191");
statusApiEndpointPath = config.getString("statusapi-sync.endpoint-path", "/network/backendguard/config");
statusApiApiKey = config.getString("statusapi-sync.api-key", "");
statusApiIntervalSeconds = Math.max(10, config.getInt("statusapi-sync.interval-seconds", 60));
statusApiLogSyncErrors = config.getBoolean("statusapi-sync.log-sync-errors", true);
cancelSyncTask();
if (statusApiSyncEnabled) {
// Erst lokale Werte laden, dann zentral ueberschreiben falls Sync klappt.
syncFromStatusApi(false);
syncTask = getServer().getScheduler().runTaskTimerAsynchronously(
this,
() -> syncFromStatusApi(false),
statusApiIntervalSeconds * 20L,
statusApiIntervalSeconds * 20L
);
}
}
private void cancelSyncTask() {
if (syncTask != null) {
syncTask.cancel();
syncTask = null;
}
}
private void syncFromStatusApi(boolean manualTrigger) {
if (!statusApiSyncEnabled) {
return;
}
HttpURLConnection conn = null;
try {
URL url = new URL(buildSyncUrl());
conn = (HttpURLConnection) url.openConnection();
conn.setRequestMethod("GET");
conn.setConnectTimeout(4000);
conn.setReadTimeout(6000);
conn.setRequestProperty("Accept", "application/json");
if (statusApiApiKey != null && !statusApiApiKey.trim().isEmpty()) {
conn.setRequestProperty("x-api-key", statusApiApiKey.trim());
}
int code = conn.getResponseCode();
if (code < 200 || code >= 300) {
if (statusApiLogSyncErrors || manualTrigger) {
getLogger().warning("StatusAPI-Sync fehlgeschlagen (HTTP " + code + "). Nutze lokale Guard-Werte.");
}
return;
}
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();
Boolean remoteEnforcement = extractJsonBoolean(json, "enforcement_enabled");
Boolean remoteLogBlocked = extractJsonBoolean(json, "log_blocked_attempts");
String remoteKickMessage = extractJsonString(json, "kick_message");
List<String> remoteIps = extractJsonStringArray(json, "allowed_proxy_ips");
List<String> remoteCidrs = extractJsonStringArray(json, "allowed_proxy_cidrs");
if (remoteEnforcement != null) {
enforcementEnabled = remoteEnforcement;
}
if (remoteLogBlocked != null) {
logBlockedAttempts = remoteLogBlocked;
}
if (remoteKickMessage != null && !remoteKickMessage.trim().isEmpty()) {
kickMessage = remoteKickMessage;
}
if (remoteIps != null) {
allowedExactIps.clear();
for (String ip : remoteIps) {
String normalized = normalizeIp(ip);
if (normalized != null) {
allowedExactIps.add(normalized);
}
}
}
if (remoteCidrs != null) {
allowedCidrs.clear();
for (String cidr : remoteCidrs) {
SubnetRule rule = parseSubnet(cidr);
if (rule != null) {
allowedCidrs.add(rule);
}
}
}
if (manualTrigger) {
getLogger().info("StatusAPI-Sync erfolgreich. Erlaubte Proxy-IPs=" + allowedExactIps.size() + ", CIDRs=" + allowedCidrs.size());
}
} catch (Exception e) {
if (statusApiLogSyncErrors || manualTrigger) {
getLogger().warning("StatusAPI-Sync Fehler: " + e.getMessage() + ". Nutze lokale Guard-Werte.");
}
} finally {
if (conn != null) {
conn.disconnect();
}
}
}
private String buildSyncUrl() {
String base = statusApiBaseUrl == null ? "" : statusApiBaseUrl.trim();
String path = statusApiEndpointPath == null ? "" : statusApiEndpointPath.trim();
if (base.endsWith("/") && path.startsWith("/")) {
return base.substring(0, base.length() - 1) + path;
}
if (!base.endsWith("/") && !path.startsWith("/")) {
return base + "/" + path;
}
return base + path;
}
private Boolean extractJsonBoolean(String json, String key) {
String token = extractJsonToken(json, key);
if (token == null) {
return null;
}
if ("true".equalsIgnoreCase(token)) {
return Boolean.TRUE;
}
if ("false".equalsIgnoreCase(token)) {
return Boolean.FALSE;
}
return null;
}
private String extractJsonString(String json, String key) {
if (json == null || key == null) {
return null;
}
String search = "\"" + key + "\"";
int idx = json.indexOf(search);
if (idx < 0) {
return null;
}
int colon = json.indexOf(':', idx + search.length());
if (colon < 0) {
return null;
}
int i = colon + 1;
while (i < json.length() && Character.isWhitespace(json.charAt(i))) {
i++;
}
if (i >= json.length() || json.charAt(i) != '"') {
return null;
}
i++;
StringBuilder sb = new StringBuilder();
boolean escape = false;
while (i < json.length()) {
char ch = json.charAt(i++);
if (escape) {
if (ch == 'n') {
sb.append('\n');
} else if (ch == 'r') {
sb.append('\r');
} else {
sb.append(ch);
}
escape = false;
continue;
}
if (ch == '\\') {
escape = true;
continue;
}
if (ch == '"') {
break;
}
sb.append(ch);
}
return sb.toString();
}
private String extractJsonToken(String json, String key) {
if (json == null || key == null) {
return null;
}
String search = "\"" + key + "\"";
int idx = json.indexOf(search);
if (idx < 0) {
return null;
}
int colon = json.indexOf(':', idx + search.length());
if (colon < 0) {
return null;
}
int i = colon + 1;
while (i < json.length() && Character.isWhitespace(json.charAt(i))) {
i++;
}
if (i >= json.length()) {
return null;
}
StringBuilder sb = new StringBuilder();
while (i < json.length()) {
char ch = json.charAt(i);
if (ch == ',' || ch == '}' || ch == ']' || Character.isWhitespace(ch)) {
break;
}
sb.append(ch);
i++;
}
return sb.toString().trim();
}
private List<String> extractJsonStringArray(String json, String key) {
if (json == null || key == null) {
return null;
}
String search = "\"" + key + "\"";
int idx = json.indexOf(search);
if (idx < 0) {
return null;
}
int colon = json.indexOf(':', idx + search.length());
if (colon < 0) {
return null;
}
int start = json.indexOf('[', colon + 1);
if (start < 0) {
return null;
}
int depth = 0;
int end = -1;
for (int i = start; i < json.length(); i++) {
char ch = json.charAt(i);
if (ch == '[') {
depth++;
} else if (ch == ']') {
depth--;
if (depth == 0) {
end = i;
break;
}
}
}
if (end < 0 || end <= start) {
return null;
}
String raw = json.substring(start + 1, end);
List<String> out = new ArrayList<String>();
boolean inString = false;
boolean escape = false;
StringBuilder current = new StringBuilder();
for (int i = 0; i < raw.length(); i++) {
char ch = raw.charAt(i);
if (!inString) {
if (ch == '"') {
inString = true;
current.setLength(0);
}
continue;
}
if (escape) {
if (ch == 'n') {
current.append('\n');
} else if (ch == 'r') {
current.append('\r');
} else {
current.append(ch);
}
escape = false;
continue;
}
if (ch == '\\') {
escape = true;
continue;
}
if (ch == '"') {
inString = false;
out.add(current.toString());
continue;
}
current.append(ch);
}
return out;
}
private boolean isAllowed(InetAddress address) {
String normalized = normalizeIp(address.getHostAddress());
if (normalized != null && allowedExactIps.contains(normalized)) {
return true;
}
for (SubnetRule rule : allowedCidrs) {
if (rule.matches(address)) {
return true;
}
}
return false;
}
private String normalizeIp(String raw) {
if (raw == null || raw.trim().isEmpty()) {
return null;
}
try {
return InetAddress.getByName(raw.trim()).getHostAddress().toLowerCase(Locale.ROOT);
} catch (UnknownHostException e) {
getLogger().warning("Ungueltige IP in Config ignoriert: " + raw);
return null;
}
}
private SubnetRule parseSubnet(String raw) {
if (raw == null || raw.trim().isEmpty() || !raw.contains("/")) {
return null;
}
String[] parts = raw.trim().split("/", 2);
try {
InetAddress network = InetAddress.getByName(parts[0]);
int prefix = Integer.parseInt(parts[1]);
return new SubnetRule(network, prefix);
} catch (Exception e) {
getLogger().warning("Ungueltige CIDR in Config ignoriert: " + raw);
return null;
}
}
private String colorize(String text) {
return ChatColor.translateAlternateColorCodes('&', text == null ? "" : text);
}
private static final class SubnetRule {
private final byte[] networkBytes;
private final int prefixLength;
private SubnetRule(InetAddress network, int prefixLength) {
this.networkBytes = network.getAddress();
this.prefixLength = prefixLength;
}
private boolean matches(InetAddress address) {
byte[] candidate = address.getAddress();
if (candidate.length != networkBytes.length) {
return false;
}
int fullBytes = prefixLength / 8;
int remainderBits = prefixLength % 8;
for (int i = 0; i < fullBytes; i++) {
if (candidate[i] != networkBytes[i]) {
return false;
}
}
if (remainderBits == 0) {
return true;
}
int mask = (-1) << (8 - remainderBits);
return (candidate[fullBytes] & mask) == (networkBytes[fullBytes] & mask);
}
}
}

View File

@@ -0,0 +1,29 @@
# BackendJoinGuard
# Dieses Plugin kommt auf JEDEN Unterserver (Paper/Spigot), nicht auf den Proxy.
# Nur Verbindungen von diesen Proxy-IPs oder Netzbereichen duerfen joinen.
enforcement-enabled: true
log-blocked-attempts: true
kick-message: "&cBitte verbinde dich nur ueber den Proxy-Server. Direkte Unterserver-Joins sind blockiert."
# Exakte Proxy-IPs
allowed-proxy-ips:
- "127.0.0.1"
- "::1"
# Optional ganze Netze im CIDR-Format
allowed-proxy-cidrs: []
# Beispiel:
# allowed-proxy-cidrs:
# - "10.0.0.0/24"
# - "192.168.178.10/32"
# Optional: zentrale Steuerung ueber StatusAPI
# Standard bleibt Standalone (enabled=false), damit BackendJoinGuard auch ohne StatusAPI nutzbar ist.
statusapi-sync:
enabled: true
base-url: "http://127.0.0.1:9191"
endpoint-path: "/network/backendguard/config"
api-key: "bgSync_7Rk9pQ2nLm5xV8cH4tW1yZ6"
interval-seconds: 60
log-sync-errors: true

View File

@@ -0,0 +1,14 @@
name: BackendJoinGuard
main: net.viper.backendguard.BackendJoinGuardPlugin
version: 1.0.0
author: M_Viper
description: Blockiert direkte Joins auf Backendserver und erlaubt nur Proxy-IPs.
api-version: '1.20'
commands:
backendguard:
description: BackendJoinGuard neu laden
usage: /backendguard reload
permissions:
backendguard.admin:
description: Darf BackendJoinGuard verwalten
default: op