diff --git a/proxy/build.gradle.kts b/proxy/build.gradle.kts index 290bff8..62f29fa 100644 --- a/proxy/build.gradle.kts +++ b/proxy/build.gradle.kts @@ -29,5 +29,5 @@ application { tasks.shadowJar { destinationDirectory = rootProject.file("dev") - archiveFileName = "vloginproxy.jar" + archiveFileName = "vproxy.jar" } diff --git a/proxy/src/main/java/proxy/Proxy.java b/proxy/src/main/java/proxy/Proxy.java index a5076dc..dcab6c3 100755 --- a/proxy/src/main/java/proxy/Proxy.java +++ b/proxy/src/main/java/proxy/Proxy.java @@ -28,6 +28,8 @@ import java.io.BufferedReader; import java.io.File; import java.io.IOException; import java.io.InputStreamReader; +import java.lang.reflect.Field; +import java.lang.reflect.Modifier; import java.net.IDN; import java.net.InetAddress; import java.util.Collection; @@ -36,6 +38,7 @@ import java.util.Iterator; import java.util.List; import java.util.Locale; import java.util.Map; +import java.util.Map.Entry; import java.util.Queue; import java.util.UUID; import java.util.concurrent.Callable; @@ -76,9 +79,11 @@ import proxy.packet.S38PacketPlayerListItem; import proxy.packet.S38PacketPlayerListItem.Action; import proxy.packet.S40PacketDisconnect; import proxy.packet.ServerInfo; +import proxy.util.Config; import proxy.util.Formatter; import proxy.util.User; import proxy.util.Log; +import proxy.util.Option; public class Proxy { public static final String NAME = "VProxy"; @@ -99,6 +104,7 @@ public class Proxy { private volatile boolean isAlive; private volatile boolean running = true; + private final Map options = Maps.newTreeMap(); private final List endpoints = Collections.synchronizedList(Lists.newArrayList()); private final List networkManagers = Collections.synchronizedList(Lists.newArrayList()); private final Queue> futureTaskQueue = Queues.>newArrayDeque(); @@ -106,24 +112,43 @@ public class Proxy { private final Map players = Maps.newTreeMap(); private final Map commands = Maps.newTreeMap(); private final Thread serverThread; + private final ServerInfo status = new ServerInfo(NAME + " 1.8.9", 47); + @Config private int compression = -1; + @Config private boolean epoll = true; + @Config private boolean register = true; + @Config private boolean checkCase = true; + @Config private boolean kickOnConnect = true; + @Config private String forwardHost = "127.0.0.1"; + @Config private int forwardPort = 25563; + @Config private String proxyHost = ""; + @Config private int proxyPort = 25564; + @Config private int minPassLength = 8; + @Config private int maxAttempts = 5; + @Config private int maxPlayers = 64; + @Config private int pingInterval = 5; + @Config private String defaultSkinTexture = "ewogICJ0aW1lc3RhbXAiIDogMTc0OTEyOTgyODQ2MCwKICAicHJvZmlsZUlkIiA6ICJlYjcxMWNlYmNjMWM0ZGQwYTc5MmE3ZGM2Y2Q3NzcyZiIsCiAgInByb2ZpbGVOYW1lIiA6ICJTZW5qaXVuIiwKICAic2lnbmF0dXJlUmVxdWlyZWQiIDogdHJ1ZSwKICAidGV4dHVyZXMiIDogewogICAgIlNLSU4iIDogewogICAgICAidXJsIiA6ICJodHRwOi8vdGV4dHVyZXMubWluZWNyYWZ0Lm5ldC90ZXh0dXJlLzk5ZGMxYmU3ODQ1N2ViODk3Zjk3MTYxYzZjYTEyOTFlMjkxN2IzYzI4ZWIzMTc4OGY3MGEzYWY4ZGIwMGEyN2IiCiAgICB9CiAgfQp9"; + @Config private String defaultSkinSignature = "uX4TdWeMU7nQZp4ZZY5d4GUOg3e+KzgndM3UhcQHZuxpfJqg5l9vZ6KmHpYpUf5hZqEzTLxWefhxLpurm3nd2kzL51GVHMQuquiISqXXGBY33bfCJtl0iVijKhAuvVWrwvBZo5tI/zCz3OQBEdW+DkNAfX7ZXntrl+0JWqhxg2+suzL4pnW7ye45/p4rVhiZ4pU/n0jTF8q6Jv6Rx4L8AoemsQ4MWmC/J5rT24gUD6DuuAc06KjUJy9g1gHNwVJ5uUjzaJ/li6as22Io09B0MdyVe5ryO9WbRKriUComNDWp/YdQrpp/hZThSNTAeJphAS5+YslRod/ZJPknrgx1yLauYaYDoHaVfQx9ZZGvJFOO+tIPQGBIefO1W3QgBqz+hdgFwsevtcqsewcdT/1khAAA2nESahOX5OuVZZhphmbfcUXK4FPn2H4jqcquP5VcsEpq3+IgE/+Nt8qFi4uANB6of/bPDWP/oDzISvtFVj/Odh+kZ/Y+OEYxn9ALYiVz5QF5gww2mtwiOh5/P9kJNc88ZrLgtnf/PggLDr6rET6L2t9DN2biwbm8NlMEB0oMa3wF/E166HRgXsxvTGvYxod1L4c70uaiyU/FlA0fL+K03bxIKYRuH7yht2E2MnzxeuPDIU/26DUG56Ek00eYdHVk9NuyUEX/96uMCArjpZ0="; + @Config + private String[] motds = new String[0]; + @Config + private String[] info = new String[0]; - private ServerInfo status; private long lastPingSync; private static String getIcon(File file) { @@ -160,6 +185,14 @@ public class Proxy { public Proxy() { this.serverThread = Thread.currentThread(); this.isAlive = true; + for(Field field : Proxy.class.getDeclaredFields()) { + if(field.isAnnotationPresent(Config.class)) { + field.setAccessible(true); + if(Modifier.isStatic(field.getModifiers()) || Modifier.isFinal(field.getModifiers())) + throw new IllegalArgumentException(field.getName() + ": is static or final"); + this.options.put(field.getName(), new Option(field, this)); + } + } this.registerCommands(); } @@ -189,28 +222,40 @@ public class Proxy { return elem.getAsJsonObject(); } - private void loadConfig() { + public void loadConfig() { JsonObject obj = loadFile(new File("vproxy.json")); - // ... - obj = loadFile(new File("users.json")); + for(Entry entry : obj.entrySet()) { + if(!this.options.containsKey(entry.getKey())) + continue; + if(!this.options.get(entry.getKey()).set(entry.getValue())) + Log.warn("Could not set option %s", entry.getKey()); + } + + this.status.setCapacity(this.maxPlayers); + this.status.getMotds().clear(); + this.status.getIcons().clear(); + this.status.getList().clear(); + Collections.addAll(this.status.getMotds(), this.motds); + Collections.addAll(this.status.getList(), this.info); + File[] icons = new File("icons").listFiles(file -> file.isFile() && file.getName().endsWith(".png")); + if(icons != null) { + for(File file : icons) { + String icon = getIcon(file); + if(file != null) + this.status.getIcons().add(icon); + } + } + } + + private void loadUsers() { + JsonObject obj = loadFile(new File("users.json")); if(obj.has("users") && obj.get("users").isJsonArray()) { for(User user : User.fromJson(obj.get("users").getAsJsonArray())) { this.users.put(user.getUsername().toLowerCase(Locale.US), user); } } // ... - - this.status = new ServerInfo(NAME + " 1.8.9", 47); - this.status.setCapacity(this.maxPlayers); - Collections.addAll(this.status.getMotds(), Formatter.DARK_RED + "Miau\n" + Formatter.YELLOW + "Test??", Formatter.AQUA + "Server\n" + Formatter.GREEN + "Test!!"); - String icon = getIcon(new File("icon1.png")); - if(icon != null) - this.status.getIcons().add(icon); - icon = getIcon(new File("icon2.png")); - if(icon != null) - this.status.getIcons().add(icon); - Collections.addAll(this.status.getList(), Formatter.DARK_GREEN + "TESTTTT", "Test 2!", Formatter.BLUE + "Test numbah 3!!!!!"); } private static void saveFile(JsonObject obj, File file) { @@ -229,7 +274,11 @@ public class Proxy { public void saveConfig() { JsonObject obj = new JsonObject(); - // ... + + for(Entry entry : this.options.entrySet()) { + obj.add(entry.getKey(), entry.getValue().get()); + } + saveFile(obj, new File("vproxy.json")); } @@ -242,6 +291,7 @@ public class Proxy { public void run() { this.loadConfig(); + this.loadUsers(); Log.info("Hosting proxy on %s:%d", this.proxyHost.isEmpty() ? "0.0.0.0" : this.proxyHost, this.proxyPort); try { this.addLanEndpoint(this.proxyHost.isEmpty() ? null : InetAddress.getByName(IDN.toASCII(this.proxyHost)), this.proxyPort); @@ -561,19 +611,27 @@ public class Proxy { private void register(Command cmd) { if(this.commands.containsKey(cmd.getName())) - throw new IllegalArgumentException("Command '" + cmd.getName() + "' ist already registered"); + throw new IllegalArgumentException("Command '" + cmd.getName() + "' is already registered"); this.commands.put(cmd.getName(), cmd); } private void registerCommands() { this.register(new CommandHelp()); this.register(new CommandExit()); + this.register(new CommandReload()); this.register(new CommandAdmin()); this.register(new CommandRevoke()); this.register(new CommandRegister()); this.register(new CommandDelete()); this.register(new CommandList()); this.register(new CommandPing()); + this.register(new CommandKick()); + this.register(new CommandSave()); + this.register(new CommandSeed()); + + for(String cmd : new String[] {"?", "about", "ban", "ban-ip", "banlist", "icanhasbukkit", "me", "pardon", "pardon-ip", "pl", "plugins", "restart", "rl", "say", "scoreboard", "stop", "tell", "tellraw", "timings", "title", "toggledownfall", "trigger", "ver", "version", "whitelist", "clone", "debug", "fill", "testfor", "testforblock", "testforblocks", "setidletimeout", "stats", "save-off", "save-on", "save-all"}) { + this.register(new CommandDummy(cmd)); + } } public Map getCommands() { @@ -617,8 +675,11 @@ public class Proxy { Log.error("Command '%s' not found", args[0]); return false; } - if(player != null && (cmd.getPermission() == null || (cmd.getPermission().isEmpty() && !player.isAdmin()) || !player.hasPermission(cmd.getPermission()))) { - player.sendMessage(Formatter.DARK_RED + "You are not allowed to execute this command"); + if(!cmd.canUse(player)) { + if(player == null) + Log.error("The console can not use this command"); + else + player.sendMessage(Formatter.DARK_RED + "You are not allowed to execute this command"); return true; } String[] argv = new String[args.length - 1]; diff --git a/proxy/src/main/java/proxy/command/Command.java b/proxy/src/main/java/proxy/command/Command.java index 9455357..1504bfd 100644 --- a/proxy/src/main/java/proxy/command/Command.java +++ b/proxy/src/main/java/proxy/command/Command.java @@ -21,6 +21,9 @@ public abstract class Command { public String getPermission() { return "vproxy.cmd." + this.getName(); } + public boolean canUse(ProxyHandler player) { + return player == null || (this.getPermission() != null && (this.getPermission().isEmpty() ? player.isAdmin() : player.hasPermission(this.getPermission()))); + } protected static void sendMessage(ProxyHandler player, String msg) { if(player != null) diff --git a/proxy/src/main/java/proxy/command/CommandDummy.java b/proxy/src/main/java/proxy/command/CommandDummy.java new file mode 100644 index 0000000..7bb63a4 --- /dev/null +++ b/proxy/src/main/java/proxy/command/CommandDummy.java @@ -0,0 +1,27 @@ +package proxy.command; + +import proxy.Proxy; +import proxy.handler.ProxyHandler; + +public class CommandDummy extends Command { + private final String name; + + public CommandDummy(String name) { + this.name = name; + } + + public String getName() { + return this.name; + } + + public String getHelp() { + return null; + } + + public boolean canUse(ProxyHandler player) { + return false; + } + + public void run(Proxy proxy, ProxyHandler player, String[] args) { + } +} diff --git a/proxy/src/main/java/proxy/command/CommandExit.java b/proxy/src/main/java/proxy/command/CommandExit.java index f011b75..e85ded3 100644 --- a/proxy/src/main/java/proxy/command/CommandExit.java +++ b/proxy/src/main/java/proxy/command/CommandExit.java @@ -20,7 +20,7 @@ public class CommandExit extends Command { } public void run(Proxy proxy, ProxyHandler player, String[] args) { - String msg = args.length == 0 ? "Proxy stopped" : Formatter.joinSpace(args); + String msg = args.length == 0 ? "Proxy stopped" : Formatter.joinSpace(0, args); for(ProxyHandler plr : Lists.newArrayList(proxy.getPlayers())) { plr.disconnect(msg); } diff --git a/proxy/src/main/java/proxy/command/CommandHelp.java b/proxy/src/main/java/proxy/command/CommandHelp.java index 48f2d21..0ac503c 100644 --- a/proxy/src/main/java/proxy/command/CommandHelp.java +++ b/proxy/src/main/java/proxy/command/CommandHelp.java @@ -2,6 +2,8 @@ package proxy.command; import java.util.Locale; +import com.google.common.collect.Collections2; + import proxy.Proxy; import proxy.handler.ProxyHandler; import proxy.util.Formatter; @@ -28,17 +30,20 @@ public class CommandHelp extends Command { public void run(Proxy proxy, ProxyHandler player, String[] args) { if(args.length == 0) { for(Command cmd : proxy.getCommands().values()) { - sendMessage(player, formatCommand(cmd, player != null)); + if(cmd.canUse(player)) + sendMessage(player, formatCommand(cmd, player != null)); } return; } Command cmd = proxy.getCommands().get(args[0].toLowerCase(Locale.US)); if(cmd == null) throw new RunException("Command '%s' not found", args[0]); + if(!cmd.canUse(player)) + throw new RunException("Command '%s' can not be used", args[0]); sendMessage(player, formatCommand(cmd, player != null)); } public Iterable complete(Proxy proxy, ProxyHandler player, String[] args) { - return args.length == 1 ? proxy.getCommands().keySet() : null; + return args.length == 1 ? Collections2.filter(proxy.getCommands().keySet(), name -> proxy.getCommands().get(name).canUse(player)) : null; } } diff --git a/proxy/src/main/java/proxy/command/CommandKick.java b/proxy/src/main/java/proxy/command/CommandKick.java new file mode 100644 index 0000000..588f515 --- /dev/null +++ b/proxy/src/main/java/proxy/command/CommandKick.java @@ -0,0 +1,38 @@ +package proxy.command; + +import com.google.common.collect.Collections2; + +import proxy.Proxy; +import proxy.handler.ProxyHandler; +import proxy.util.Formatter; + +public class CommandKick extends Command { + public String getName() { + return "kick"; + } + + public String getHelp() { + return "Kicks a player off the server"; + } + + public String getArgs() { + return " [reason ...]"; + } + + public void run(Proxy proxy, ProxyHandler player, String[] args) { + if(args.length < 1) + throw new RunException("Please provide a player name"); + ProxyHandler plr = proxy.getPlayer(args[0]); + if(plr == null) + throw new RunException("'%s' is not online", args[0]); + if(plr == player) + throw new RunException("You cannot kick yourself"); + if(player != null && plr.isAdmin()) + throw new RunException("Admins can only be kicked by the console"); + plr.disconnect(args.length <= 1 ? "Kicked from server" : Formatter.joinSpace(1, args)); + } + + public Iterable complete(Proxy proxy, ProxyHandler player, String[] args) { + return args.length == 1 ? (player == null ? proxy.getPlayerNames() : Collections2.filter(proxy.getPlayerNames(), name -> !proxy.getPlayer(name).isAdmin())) : null; + } +} diff --git a/proxy/src/main/java/proxy/command/CommandReload.java b/proxy/src/main/java/proxy/command/CommandReload.java new file mode 100644 index 0000000..c5e04ba --- /dev/null +++ b/proxy/src/main/java/proxy/command/CommandReload.java @@ -0,0 +1,19 @@ +package proxy.command; + +import proxy.Proxy; +import proxy.handler.ProxyHandler; + +public class CommandReload extends Command { + public String getName() { + return "reload"; + } + + public String getHelp() { + return "Reloads the proxy config"; + } + + public void run(Proxy proxy, ProxyHandler player, String[] args) { + proxy.loadConfig(); + sendInfo(player, "Configuration reloaded"); + } +} diff --git a/proxy/src/main/java/proxy/command/CommandSave.java b/proxy/src/main/java/proxy/command/CommandSave.java new file mode 100644 index 0000000..e2c72c7 --- /dev/null +++ b/proxy/src/main/java/proxy/command/CommandSave.java @@ -0,0 +1,23 @@ +package proxy.command; + +import proxy.Proxy; +import proxy.handler.ProxyHandler; +import proxy.packet.C01PacketChatMessage; + +public class CommandSave extends Command { + public String getName() { + return "save"; + } + + public String getHelp() { + return "Saves all worlds"; + } + + public boolean canUse(ProxyHandler player) { + return player != null && super.canUse(player); + } + + public void run(Proxy proxy, ProxyHandler player, String[] args) { + player.sendToServer(new C01PacketChatMessage("/save-all")); + } +} diff --git a/proxy/src/main/java/proxy/command/CommandSeed.java b/proxy/src/main/java/proxy/command/CommandSeed.java new file mode 100644 index 0000000..13bc5b7 --- /dev/null +++ b/proxy/src/main/java/proxy/command/CommandSeed.java @@ -0,0 +1,23 @@ +package proxy.command; + +import proxy.Proxy; +import proxy.handler.ProxyHandler; +import proxy.packet.C01PacketChatMessage; + +public class CommandSeed extends Command { + public String getName() { + return "seed"; + } + + public String getHelp() { + return "Displays the world seed"; + } + + public boolean canUse(ProxyHandler player) { + return player != null && super.canUse(player); + } + + public void run(Proxy proxy, ProxyHandler player, String[] args) { + player.sendToServer(new C01PacketChatMessage("/seed")); + } +} diff --git a/proxy/src/main/java/proxy/handler/ProxyHandler.java b/proxy/src/main/java/proxy/handler/ProxyHandler.java index 25ba9e6..ff58c23 100755 --- a/proxy/src/main/java/proxy/handler/ProxyHandler.java +++ b/proxy/src/main/java/proxy/handler/ProxyHandler.java @@ -389,7 +389,7 @@ public class ProxyHandler extends User implements Handler { if(cmd == null) return null; List list = Lists.newArrayList(); - if(cmd.getPermission() == null || (cmd.getPermission().isEmpty() && !this.isAdmin()) || !this.hasPermission(cmd.getPermission())) + if(!cmd.canUse(this)) return list; String[] argv = new String[args.length - 1]; System.arraycopy(args, 1, argv, 0, argv.length); @@ -456,7 +456,7 @@ public class ProxyHandler extends User implements Handler { } for(Command cmd : this.proxy.getCommands().values()) { String command = "/" + cmd.getName(); - if(cmd.getPermission() != null && (!cmd.getPermission().isEmpty() || this.isAdmin()) && command.regionMatches(true, 0, this.completion, 0, this.completion.length())) + if(cmd.canUse(this) && command.regionMatches(true, 0, this.completion, 0, this.completion.length())) list.add(command); } Collections.sort(list); diff --git a/proxy/src/main/java/proxy/util/Config.java b/proxy/src/main/java/proxy/util/Config.java new file mode 100644 index 0000000..5b5b0d7 --- /dev/null +++ b/proxy/src/main/java/proxy/util/Config.java @@ -0,0 +1,12 @@ +package proxy.util; + +import static java.lang.annotation.ElementType.FIELD; +import static java.lang.annotation.RetentionPolicy.RUNTIME; + +import java.lang.annotation.Retention; +import java.lang.annotation.Target; + +@Retention(RUNTIME) +@Target(FIELD) +public @interface Config { +} diff --git a/proxy/src/main/java/proxy/util/Formatter.java b/proxy/src/main/java/proxy/util/Formatter.java index cf4fac9..631bc6d 100755 --- a/proxy/src/main/java/proxy/util/Formatter.java +++ b/proxy/src/main/java/proxy/util/Formatter.java @@ -78,16 +78,16 @@ public enum Formatter { return sb.toString().trim(); } - public static String joinSpace(String ... objs) { - return joinSpace((Object[])objs); + public static String joinSpace(int index, String ... objs) { + return joinSpace(index, (Object[])objs); } - public static String joinSpace(Object ... objs) { + public static String joinSpace(int index, Object ... objs) { StringBuilder sb = new StringBuilder(); - for(Object obj : objs) { + for(int z = index; z < objs.length; z++) { if(sb.length() != 0) sb.append(' '); - sb.append(obj); + sb.append(objs[z]); } return sb.toString(); } diff --git a/proxy/src/main/java/proxy/util/Option.java b/proxy/src/main/java/proxy/util/Option.java new file mode 100644 index 0000000..4fe6709 --- /dev/null +++ b/proxy/src/main/java/proxy/util/Option.java @@ -0,0 +1,83 @@ +package proxy.util; + +import java.lang.reflect.Field; + +import com.google.gson.JsonArray; +import com.google.gson.JsonElement; +import com.google.gson.JsonPrimitive; + +public class Option { + private final Field field; + private final Object object; + + public Option(Field field, Object object) { + this.field = field; + if(this.field.getType() != int.class && this.field.getType() != boolean.class && this.field.getType() != String.class && this.field.getType() != String[].class) + throw new IllegalArgumentException(field.getName() + ": is not int, boolean, String or String[]"); + this.object = object; + } + + public JsonElement get() { + Object obj; + try { + obj = this.field.get(this.object); + } + catch(IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + if(this.field.getType() == int.class) + return new JsonPrimitive((Integer)obj); + if(this.field.getType() == boolean.class) + return new JsonPrimitive((Boolean)obj); + if(this.field.getType() == String.class) + return new JsonPrimitive((String)obj); + JsonArray arr = new JsonArray(); + for(String str : (String[])obj) { + arr.add(new JsonPrimitive(str)); + } + return arr; + } + + public boolean set(JsonElement data) { + Object value; + if(this.field.getType() == int.class) { + if(!data.isJsonPrimitive()) + return false; + value = data.getAsNumber().intValue(); + } + else if(this.field.getType() == boolean.class) { + if(!data.isJsonPrimitive()) + return false; + value = data.getAsBoolean(); + } + else if(this.field.getType() == String.class) { + if(!data.isJsonPrimitive()) + return false; + value = data.getAsString(); + } + else { + if(data.isJsonPrimitive()) { + value = data.getAsString(); + } + else { + if(!data.isJsonArray()) + return false; + JsonArray arr = data.getAsJsonArray(); + String[] strs = new String[arr.size()]; + for(int z = 0; z < strs.length; z++) { + if(!arr.get(z).isJsonPrimitive()) + return false; + strs[z] = arr.get(z).getAsString(); + } + value = strs; + } + } + try { + this.field.set(this.object, value); + } + catch(IllegalArgumentException | IllegalAccessException e) { + throw new RuntimeException(e); + } + return true; + } +}