diff --git a/README.md b/README.md index 55e67ffa0e..38856c3eef 100644 --- a/README.md +++ b/README.md @@ -10,9 +10,21 @@ This version reimplements Minecraft 1.7.10 and basic 1.7.10 Forge support. Since release 134+, SpongePls is no longer needed when using Sponge-servers within your network! +<<<<<<< HEAD IMPORTANT: We WON'T fix any 1.7 bugs. This fork is designed for keeping your old servers in your network, until your modspacks are available for 1.10.2/1.11.2 or higher. Most of them are, so get rid of 1.7 fast and move to 1.10.2/1.11.2 modpacks asap. +======= +### Security warning + +As your Minecraft servers have to run without authentication (online-mode=false) for BungeeCord to work, this poses a new security risk. Users may connect to your servers directly, under any username they wish to use. The kick "If you wish to use IP forwarding, please enable it in your BungeeCord config as well!" does not protect your Spigot servers. + +To combat this, you need to restrict access to these servers for example with a firewall (please see [firewall guide](https://www.spigotmc.org/wiki/firewall-guide/)). + +Source +------ +Source code is currently available on [GitHub](https://www.spigotmc.org/go/bungeecord-git). +>>>>>>> 7dd09289ee7aed473caaf228e461f8927c51f1a5 This version is maintained by https://hexagonmc.eu diff --git a/api/src/main/java/net/md_5/bungee/api/ServerPing.java b/api/src/main/java/net/md_5/bungee/api/ServerPing.java index 2f707ba698..88d36ad206 100644 --- a/api/src/main/java/net/md_5/bungee/api/ServerPing.java +++ b/api/src/main/java/net/md_5/bungee/api/ServerPing.java @@ -5,7 +5,6 @@ import java.util.UUID; import lombok.AllArgsConstructor; import lombok.Data; -import lombok.Getter; import lombok.NoArgsConstructor; import lombok.ToString; import net.md_5.bungee.Util; diff --git a/api/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java b/api/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java index 05474d886a..4baa06a9c8 100644 --- a/api/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java +++ b/api/src/main/java/net/md_5/bungee/api/event/ProxyPingEvent.java @@ -6,7 +6,6 @@ import net.md_5.bungee.api.Callback; import net.md_5.bungee.api.ServerPing; import net.md_5.bungee.api.connection.PendingConnection; -import net.md_5.bungee.api.plugin.Event; /** * Called when the proxy is pinged with packet 0xFE from the server list. diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/Command.java b/api/src/main/java/net/md_5/bungee/api/plugin/Command.java index 54fa39c99e..eba3040aef 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/Command.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/Command.java @@ -51,4 +51,15 @@ public Command(String name, String permission, String... aliases) * @param args arguments used to invoke this command */ public abstract void execute(CommandSender sender, String[] args); + + /** + * Check if this command can be executed by the given sender. + * + * @param sender the sender to check + * @return whether the sender can execute this + */ + public boolean hasPermission(CommandSender sender) + { + return permission == null || permission.isEmpty() || sender.hasPermission( permission ); + } } diff --git a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java index afa98ed9af..3a871787f8 100644 --- a/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java +++ b/api/src/main/java/net/md_5/bungee/api/plugin/PluginManager.java @@ -11,6 +11,7 @@ import java.net.URLClassLoader; import java.util.Arrays; import java.util.Collection; +import java.util.Collections; import java.util.HashMap; import java.util.HashSet; import java.util.Iterator; @@ -143,8 +144,7 @@ public boolean dispatchCommand(CommandSender sender, String commandLine, List> getCommands() + { + return Collections.unmodifiableCollection( commandMap.entrySet() ); + } } diff --git a/bootstrap/pom.xml b/bootstrap/pom.xml index 9ed450b0e1..e4bc6f2aab 100644 --- a/bootstrap/pom.xml +++ b/bootstrap/pom.xml @@ -32,12 +32,6 @@ ${project.version} compile - - net.sf.jopt-simple - jopt-simple - 4.9 - compile - diff --git a/bootstrap/src/main/java/net/md_5/bungee/Bootstrap.java b/bootstrap/src/main/java/net/md_5/bungee/Bootstrap.java index b7cb81e2e4..6be22739dd 100644 --- a/bootstrap/src/main/java/net/md_5/bungee/Bootstrap.java +++ b/bootstrap/src/main/java/net/md_5/bungee/Bootstrap.java @@ -5,9 +5,9 @@ public class Bootstrap public static void main(String[] args) throws Exception { - if ( Float.parseFloat( System.getProperty( "java.class.version" ) ) < 51.0 ) + if ( Float.parseFloat( System.getProperty( "java.class.version" ) ) < 52.0 ) { - System.err.println( "*** ERROR *** BungeeCord requires Java 7 or above to function! Please download and install it!" ); + System.err.println( "*** ERROR *** BungeeCord requires Java 8 or above to function! Please download and install it!" ); System.out.println( "You can check your Java version with the command: java -version" ); return; } diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java b/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java index 1ced284f0e..1fb3111282 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/ClickEvent.java @@ -3,7 +3,6 @@ import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.ToString; -import net.md_5.bungee.api.chat.ClickEvent.Action; @Getter @ToString diff --git a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java index 2411793bf9..e3ef9b7e3a 100644 --- a/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java +++ b/chat/src/main/java/net/md_5/bungee/api/chat/TextComponent.java @@ -2,7 +2,6 @@ import lombok.AllArgsConstructor; import lombok.Getter; -import lombok.NoArgsConstructor; import lombok.Setter; import net.md_5.bungee.api.ChatColor; diff --git a/config/src/main/java/net/md_5/bungee/config/YamlConfiguration.java b/config/src/main/java/net/md_5/bungee/config/YamlConfiguration.java index db8aa4bed5..bb974a4a73 100644 --- a/config/src/main/java/net/md_5/bungee/config/YamlConfiguration.java +++ b/config/src/main/java/net/md_5/bungee/config/YamlConfiguration.java @@ -1,10 +1,12 @@ package net.md_5.bungee.config; +import com.google.common.base.Charsets; import java.io.File; -import java.io.FileReader; -import java.io.FileWriter; +import java.io.FileInputStream; +import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; +import java.io.OutputStreamWriter; import java.io.Reader; import java.io.Writer; import java.util.LinkedHashMap; @@ -51,7 +53,7 @@ public Node representData(Object data) @Override public void save(Configuration config, File file) throws IOException { - try ( FileWriter writer = new FileWriter( file ) ) + try ( Writer writer = new OutputStreamWriter( new FileOutputStream( file ), Charsets.UTF_8 ) ) { save( config, writer ); } @@ -72,9 +74,9 @@ public Configuration load(File file) throws IOException @Override public Configuration load(File file, Configuration defaults) throws IOException { - try ( FileReader reader = new FileReader( file ) ) + try ( FileInputStream is = new FileInputStream( file ) ) { - return load( reader, defaults ); + return load( is, defaults ); } } diff --git a/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java b/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java index 2767241002..ebf9a12dcb 100644 --- a/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java +++ b/module/cmd-server/src/main/java/net/md_5/bungee/module/cmd/server/CommandServer.java @@ -41,8 +41,7 @@ public void execute(CommandSender sender, String[] args) sender.sendMessage( ProxyServer.getInstance().getTranslation( "current_server", ( (ProxiedPlayer) sender ).getServer().getInfo().getName() ) ); } - TextComponent serverList = new TextComponent( ProxyServer.getInstance().getTranslation( "server_list" ) ); - serverList.setColor( ChatColor.GOLD ); + ComponentBuilder serverList = new ComponentBuilder( "" ).append( TextComponent.fromLegacyText( ProxyServer.getInstance().getTranslation( "server_list" ) ) ); boolean first = true; for ( ServerInfo server : servers.values() ) { @@ -55,11 +54,11 @@ public void execute(CommandSender sender, String[] args) .append( "Click to connect to the server" ).italic( true ) .create() ) ); serverTextComponent.setClickEvent( new ClickEvent( ClickEvent.Action.RUN_COMMAND, "/server " + server.getName() ) ); - serverList.addExtra( serverTextComponent ); + serverList.append( serverTextComponent ); first = false; } } - sender.sendMessage( serverList ); + sender.sendMessage( serverList.create() ); } else { if ( !( sender instanceof ProxiedPlayer ) ) diff --git a/native/src/main/c/NativeCipherImpl.cpp b/native/src/main/c/NativeCipherImpl.cpp index cfe5f08994..aa7a12b4a4 100644 --- a/native/src/main/c/NativeCipherImpl.cpp +++ b/native/src/main/c/NativeCipherImpl.cpp @@ -1,3 +1,6 @@ +// Support for CentOS 6 +__asm__(".symver memcpy,memcpy@GLIBC_2.2.5"); + #include #include diff --git a/native/src/main/resources/native-cipher.so b/native/src/main/resources/native-cipher.so index de786fe1fa..8f1717b93e 100644 Binary files a/native/src/main/resources/native-cipher.so and b/native/src/main/resources/native-cipher.so differ diff --git a/pom.xml b/pom.xml index df8b56d561..ef6575a2ef 100644 --- a/pom.xml +++ b/pom.xml @@ -73,7 +73,7 @@ unknown - 4.1.30.Final + 4.1.32.Final 1.7 1.7 UTF-8 @@ -101,7 +101,7 @@ org.projectlombok lombok - 1.16.16 + 1.18.4 provided @@ -139,7 +139,7 @@ org.codehaus.mojo.signature - java17 + java18 1.0 diff --git a/protocol/pom.xml b/protocol/pom.xml index ab9868bdf9..ec0240a42b 100644 --- a/protocol/pom.xml +++ b/protocol/pom.xml @@ -18,7 +18,28 @@ BungeeCord-Protocol Minimal implementation of the Minecraft protocol for use in BungeeCord + + + + sonatype-nexus-snapshots + Sonatype Nexus Snapshots + https://oss.sonatype.org/content/repositories/snapshots + + false + + + true + + + + + + net.md-5 + brigadier + 1.0.16-SNAPSHOT + compile + net.md-5 bungeecord-chat @@ -33,8 +54,8 @@ net.sf.trove4j - trove4j - 3.0.3 + core + 3.1.0 compile diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java b/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java index 93f324e29f..219488dd9d 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/AbstractPacketHandler.java @@ -6,6 +6,7 @@ import net.md_5.bungee.protocol.packet.ClientStatus; import net.md_5.bungee.protocol.packet.Login; import net.md_5.bungee.protocol.packet.Chat; +import net.md_5.bungee.protocol.packet.Commands; import net.md_5.bungee.protocol.packet.EncryptionRequest; import net.md_5.bungee.protocol.packet.PlayerListHeaderFooter; import net.md_5.bungee.protocol.packet.PlayerListItem; @@ -20,6 +21,7 @@ import net.md_5.bungee.protocol.packet.Respawn; import net.md_5.bungee.protocol.packet.Handshake; import net.md_5.bungee.protocol.packet.EncryptionResponse; +import net.md_5.bungee.protocol.packet.EntityStatus; import net.md_5.bungee.protocol.packet.LegacyHandshake; import net.md_5.bungee.protocol.packet.LegacyPing; import net.md_5.bungee.protocol.packet.LoginPayloadRequest; @@ -158,4 +160,12 @@ public void handle(LoginPayloadRequest request) throws Exception public void handle(LoginPayloadResponse response) throws Exception { } + + public void handle(EntityStatus status) throws Exception + { + } + + public void handle(Commands commands) throws Exception + { + } } diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java index 543487229c..c3c5103695 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/DefinedPacket.java @@ -104,6 +104,19 @@ public static byte[] readArrayLegacy(ByteBuf buf) return ret; } + public static int[] readVarIntArray(ByteBuf buf) + { + int len = readVarInt( buf ); + int[] ret = new int[ len ]; + + for ( int i = 0; i < len; i++ ) + { + ret[i] = readVarInt( buf ); + } + + return ret; + } + public static void writeStringArray(List s, ByteBuf buf) { writeVarInt( s.size(), buf ); diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java index d4f384c84d..27bf6dacb3 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/Protocol.java @@ -14,8 +14,10 @@ import net.md_5.bungee.protocol.packet.BossBar; import net.md_5.bungee.protocol.packet.Chat; import net.md_5.bungee.protocol.packet.ClientSettings; +import net.md_5.bungee.protocol.packet.Commands; import net.md_5.bungee.protocol.packet.EncryptionRequest; import net.md_5.bungee.protocol.packet.EncryptionResponse; +import net.md_5.bungee.protocol.packet.EntityStatus; import net.md_5.bungee.protocol.packet.Handshake; import net.md_5.bungee.protocol.packet.KeepAlive; import net.md_5.bungee.protocol.packet.Kick; @@ -177,6 +179,20 @@ public enum Protocol map( ProtocolConstants.MINECRAFT_1_7_6, 0x46 , false ), map( ProtocolConstants.MINECRAFT_1_8, 0x46 , false ) ); + TO_CLIENT.registerPacket( + EntityStatus.class, + map( ProtocolConstants.MINECRAFT_1_8, 0x1A ), + map( ProtocolConstants.MINECRAFT_1_9, 0x1B ), + map( ProtocolConstants.MINECRAFT_1_12, 0x1B ), + map( ProtocolConstants.MINECRAFT_1_13, 0x1C ) + ); + if ( Boolean.getBoolean( "net.md-5.bungee.protocol.register_commands" ) ) + { + TO_CLIENT.registerPacket( + Commands.class, + map( ProtocolConstants.MINECRAFT_1_13, 0x11 ) + ); + } TO_SERVER.registerPacket( KeepAlive.class, diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/Commands.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/Commands.java new file mode 100644 index 0000000000..80485a4d23 --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/Commands.java @@ -0,0 +1,638 @@ +package net.md_5.bungee.protocol.packet; + +import com.google.common.base.Preconditions; +import com.mojang.brigadier.Command; +import com.mojang.brigadier.StringReader; +import com.mojang.brigadier.arguments.ArgumentType; +import com.mojang.brigadier.arguments.DoubleArgumentType; +import com.mojang.brigadier.arguments.FloatArgumentType; +import com.mojang.brigadier.arguments.IntegerArgumentType; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.ArgumentBuilder; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.context.CommandContext; +import com.mojang.brigadier.exceptions.CommandSyntaxException; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.suggestion.Suggestions; +import com.mojang.brigadier.suggestion.SuggestionsBuilder; +import com.mojang.brigadier.tree.ArgumentCommandNode; +import com.mojang.brigadier.tree.CommandNode; +import com.mojang.brigadier.tree.LiteralCommandNode; +import com.mojang.brigadier.tree.RootCommandNode; +import io.netty.buffer.ByteBuf; +import java.util.ArrayDeque; +import java.util.Collection; +import java.util.Deque; +import java.util.HashMap; +import java.util.Iterator; +import java.util.LinkedHashMap; +import java.util.Map; +import java.util.concurrent.CompletableFuture; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import net.md_5.bungee.protocol.AbstractPacketHandler; +import net.md_5.bungee.protocol.DefinedPacket; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class Commands extends DefinedPacket +{ + + private static final int FLAG_TYPE = 0x3; + private static final int FLAG_EXECUTABLE = 0x4; + private static final int FLAG_REDIRECT = 0x8; + private static final int FLAG_SUGGESTIONS = 0x10; + // + private static final int NODE_ROOT = 0; + private static final int NODE_LITERAL = 1; + private static final int NODE_ARGUMENT = 2; + // + private RootCommandNode root; + + @Override + public void read(ByteBuf buf) + { + int nodeCount = readVarInt( buf ); + NetworkNode[] nodes = new NetworkNode[ nodeCount ]; + Deque nodeQueue = new ArrayDeque<>( nodes.length ); + + for ( int i = 0; i < nodeCount; i++ ) + { + byte flags = buf.readByte(); + int[] children = readVarIntArray( buf ); + int redirectNode = ( ( flags & FLAG_REDIRECT ) != 0 ) ? readVarInt( buf ) : 0; + ArgumentBuilder argumentBuilder; + + switch ( flags & FLAG_TYPE ) + { + case NODE_ROOT: + argumentBuilder = null; + break; + case NODE_LITERAL: + argumentBuilder = LiteralArgumentBuilder.literal( readString( buf ) ); + break; + case NODE_ARGUMENT: + String name = readString( buf ); + String parser = readString( buf ); + + argumentBuilder = RequiredArgumentBuilder.argument( name, ArgumentRegistry.read( parser, buf ) ); + + if ( ( flags & FLAG_SUGGESTIONS ) != 0 ) + { + String suggster = readString( buf ); + ( (RequiredArgumentBuilder) argumentBuilder ).suggests( SuggestionRegistry.getProvider( suggster ) ); + } + break; + default: + throw new IllegalArgumentException( "Unhandled node type " + flags ); + } + + NetworkNode node = new NetworkNode( argumentBuilder, flags, redirectNode, children ); + + nodes[i] = node; + nodeQueue.add( node ); + } + + boolean mustCycle; + do + { + if ( nodeQueue.isEmpty() ) + { + int rootIndex = readVarInt( buf ); + root = (RootCommandNode) nodes[rootIndex].command; + return; + } + + mustCycle = false; + + for ( Iterator iter = nodeQueue.iterator(); iter.hasNext(); ) + { + NetworkNode node = iter.next(); + if ( node.buildSelf( nodes ) ) + { + iter.remove(); + mustCycle = true; + } + } + } while ( mustCycle ); + + throw new IllegalStateException( "Did not finish building root node" ); + } + + @Override + public void write(ByteBuf buf) + { + Map indexMap = new LinkedHashMap<>(); + Deque nodeQueue = new ArrayDeque<>(); + nodeQueue.add( root ); + + while ( !nodeQueue.isEmpty() ) + { + CommandNode command = nodeQueue.pollFirst(); + + if ( !indexMap.containsKey( command ) ) + { + // Index the new node + int currentIndex = indexMap.size(); + indexMap.put( command, currentIndex ); + + // Queue children and redirect for processing + nodeQueue.addAll( command.getChildren() ); + if ( command.getRedirect() != null ) + { + nodeQueue.add( command.getRedirect() ); + } + } + } + + // Write out size + writeVarInt( indexMap.size(), buf ); + + int currentIndex = 0; + for ( Map.Entry entry : indexMap.entrySet() ) + { + // Using a LinkedHashMap, but sanity check this assumption + Preconditions.checkState( entry.getValue() == currentIndex++, "Iteration out of order!" ); + + CommandNode node = entry.getKey(); + byte flags = 0; + + if ( node.getRedirect() != null ) + { + flags |= FLAG_REDIRECT; + } + if ( node.getCommand() != null ) + { + flags |= FLAG_EXECUTABLE; + } + + if ( node instanceof RootCommandNode ) + { + flags |= NODE_ROOT; + } else if ( node instanceof LiteralCommandNode ) + { + flags |= NODE_LITERAL; + } else if ( node instanceof ArgumentCommandNode ) + { + flags |= NODE_ARGUMENT; + if ( ( (ArgumentCommandNode) node ).getCustomSuggestions() != null ) + { + flags |= FLAG_SUGGESTIONS; + } + } else + { + throw new IllegalArgumentException( "Unhandled node type " + node ); + } + + buf.writeByte( flags ); + + writeVarInt( node.getChildren().size(), buf ); + for ( CommandNode child : (Collection) node.getChildren() ) + { + writeVarInt( indexMap.get( child ), buf ); + } + if ( node.getRedirect() != null ) + { + writeVarInt( indexMap.get( node.getRedirect() ), buf ); + } + + if ( node instanceof LiteralCommandNode ) + { + writeString( ( (LiteralCommandNode) node ).getLiteral(), buf ); + } else if ( node instanceof ArgumentCommandNode ) + { + ArgumentCommandNode argumentNode = (ArgumentCommandNode) node; + + writeString( argumentNode.getName(), buf ); + ArgumentRegistry.write( argumentNode.getType(), buf ); + + if ( argumentNode.getCustomSuggestions() != null ) + { + writeString( SuggestionRegistry.getKey( argumentNode.getCustomSuggestions() ), buf ); + } + } + } + + // Get, check, and write the root index (should be first) + int rootIndex = indexMap.get( root ); + Preconditions.checkState( rootIndex == 0, "How did root not land up at index 0?!?" ); + writeVarInt( rootIndex, buf ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } + + @Data + private static class NetworkNode + { + + private final ArgumentBuilder argumentBuilder; + private final byte flags; + private final int redirectNode; + private final int[] children; + private CommandNode command; + + private boolean buildSelf(NetworkNode[] otherNodes) + { + // First cycle + if ( command == null ) + { + // Root node is merely the root + if ( argumentBuilder == null ) + { + command = new RootCommandNode(); + } else + { + // Add the redirect + if ( ( flags & FLAG_REDIRECT ) != 0 ) + { + if ( otherNodes[redirectNode].command == null ) + { + return false; + } + + argumentBuilder.redirect( otherNodes[redirectNode].command ); + } + + // Add dummy executable + if ( ( flags & FLAG_EXECUTABLE ) != 0 ) + { + argumentBuilder.executes( new Command() + { + @Override + public int run(CommandContext context) throws CommandSyntaxException + { + return 0; + } + } ); + } + + // Build our self command + command = argumentBuilder.build(); + } + } + + // Check that we have processed all children thus far + for ( int childIndex : children ) + { + if ( otherNodes[childIndex].command == null ) + { + // If not, we have to do another cycle + return false; + } + } + + for ( int childIndex : children ) + { + CommandNode child = otherNodes[childIndex].command; + Preconditions.checkArgument( !( child instanceof RootCommandNode ), "Cannot have RootCommandNode as child" ); + + command.addChild( child ); + } + + return true; + } + } + + @Data + private static class ArgumentRegistry + { + + private static final Map PROVIDERS = new HashMap<>(); + private static final Map, ProperArgumentSerializer> PROPER_PROVIDERS = new HashMap<>(); + // + private static final ArgumentSerializer VOID = new ArgumentSerializer() + { + @Override + protected Void read(ByteBuf buf) + { + return null; + } + + @Override + protected void write(ByteBuf buf, Void t) + { + } + }; + private static final ArgumentSerializer BOOLEAN = new ArgumentSerializer() + { + @Override + protected Boolean read(ByteBuf buf) + { + return buf.readBoolean(); + } + + @Override + protected void write(ByteBuf buf, Boolean t) + { + buf.writeBoolean( t ); + } + }; + private static final ArgumentSerializer BYTE = new ArgumentSerializer() + { + @Override + protected Byte read(ByteBuf buf) + { + return buf.readByte(); + } + + @Override + protected void write(ByteBuf buf, Byte t) + { + buf.writeByte( t ); + } + }; + private static final ArgumentSerializer FLOAT = new ArgumentSerializer() + { + @Override + protected FloatArgumentType read(ByteBuf buf) + { + byte flags = buf.readByte(); + float min = ( flags & 0x1 ) != 0 ? buf.readFloat() : -Float.MAX_VALUE; + float max = ( flags & 0x2 ) != 0 ? buf.readFloat() : -Float.MAX_VALUE; + + return FloatArgumentType.floatArg( min, max ); + } + + @Override + protected void write(ByteBuf buf, FloatArgumentType t) + { + boolean hasMin = t.getMinimum() != -Float.MAX_VALUE; + boolean hasMax = t.getMaximum() != Float.MAX_VALUE; + + buf.writeByte( binaryFlag( hasMin, hasMax ) ); + if ( hasMin ) + { + buf.writeFloat( t.getMinimum() ); + } + if ( hasMax ) + { + buf.writeFloat( t.getMaximum() ); + } + } + }; + private static final ArgumentSerializer DOUBLE = new ArgumentSerializer() + { + @Override + protected DoubleArgumentType read(ByteBuf buf) + { + byte flags = buf.readByte(); + double min = ( flags & 0x1 ) != 0 ? buf.readDouble() : -Double.MAX_VALUE; + double max = ( flags & 0x2 ) != 0 ? buf.readDouble() : -Double.MAX_VALUE; + + return DoubleArgumentType.doubleArg( min, max ); + } + + @Override + protected void write(ByteBuf buf, DoubleArgumentType t) + { + boolean hasMin = t.getMinimum() != -Double.MAX_VALUE; + boolean hasMax = t.getMaximum() != Double.MAX_VALUE; + + buf.writeByte( binaryFlag( hasMin, hasMax ) ); + if ( hasMin ) + { + buf.writeDouble( t.getMinimum() ); + } + if ( hasMax ) + { + buf.writeDouble( t.getMaximum() ); + } + } + }; + private static final ArgumentSerializer INTEGER = new ArgumentSerializer() + { + @Override + protected IntegerArgumentType read(ByteBuf buf) + { + byte flags = buf.readByte(); + int min = ( flags & 0x1 ) != 0 ? buf.readInt() : Integer.MIN_VALUE; + int max = ( flags & 0x2 ) != 0 ? buf.readInt() : Integer.MAX_VALUE; + + return IntegerArgumentType.integer( min, max ); + } + + @Override + protected void write(ByteBuf buf, IntegerArgumentType t) + { + boolean hasMin = t.getMinimum() != Integer.MIN_VALUE; + boolean hasMax = t.getMaximum() != Integer.MAX_VALUE; + + buf.writeByte( binaryFlag( hasMin, hasMax ) ); + if ( hasMin ) + { + buf.writeInt( t.getMinimum() ); + } + if ( hasMax ) + { + buf.writeInt( t.getMaximum() ); + } + } + }; + private static final ProperArgumentSerializer STRING = new ProperArgumentSerializer() + { + @Override + protected StringArgumentType read(ByteBuf buf) + { + int val = readVarInt( buf ); + switch ( val ) + { + case 0: + return StringArgumentType.word(); + case 1: + return StringArgumentType.string(); + case 2: + return StringArgumentType.greedyString(); + default: + throw new IllegalArgumentException( "Unknown string type " + val ); + } + } + + @Override + protected void write(ByteBuf buf, StringArgumentType t) + { + writeVarInt( t.getType().ordinal(), buf ); + } + + @Override + protected String getKey() + { + return "brigadier:string"; + } + }; + + static + { + PROVIDERS.put( "brigadier:bool", VOID ); + PROVIDERS.put( "brigadier:float", FLOAT ); + PROVIDERS.put( "brigadier:double", DOUBLE ); + PROVIDERS.put( "brigadier:integer", INTEGER ); + + PROVIDERS.put( "brigadier:string", STRING ); + PROPER_PROVIDERS.put( StringArgumentType.class, STRING ); + + PROVIDERS.put( "minecraft:entity", BYTE ); + PROVIDERS.put( "minecraft:game_profile", VOID ); + PROVIDERS.put( "minecraft:block_pos", VOID ); + PROVIDERS.put( "minecraft:column_pos", VOID ); + PROVIDERS.put( "minecraft:vec3", VOID ); + PROVIDERS.put( "minecraft:vec2", VOID ); + PROVIDERS.put( "minecraft:block_state", VOID ); + PROVIDERS.put( "minecraft:block_predicate", VOID ); + PROVIDERS.put( "minecraft:item_stack", VOID ); + PROVIDERS.put( "minecraft:item_predicate", VOID ); + PROVIDERS.put( "minecraft:color", VOID ); + PROVIDERS.put( "minecraft:component", VOID ); + PROVIDERS.put( "minecraft:message", VOID ); + PROVIDERS.put( "minecraft:nbt", VOID ); + PROVIDERS.put( "minecraft:nbt_path", VOID ); + PROVIDERS.put( "minecraft:objective", VOID ); + PROVIDERS.put( "minecraft:objective_criteria", VOID ); + PROVIDERS.put( "minecraft:operation", VOID ); + PROVIDERS.put( "minecraft:particle", VOID ); + PROVIDERS.put( "minecraft:rotation", VOID ); + PROVIDERS.put( "minecraft:scoreboard_slot", VOID ); + PROVIDERS.put( "minecraft:score_holder", BYTE ); + PROVIDERS.put( "minecraft:swizzle", VOID ); + PROVIDERS.put( "minecraft:team", VOID ); + PROVIDERS.put( "minecraft:item_slot", VOID ); + PROVIDERS.put( "minecraft:resource_location", VOID ); + PROVIDERS.put( "minecraft:mob_effect", VOID ); + PROVIDERS.put( "minecraft:function", VOID ); + PROVIDERS.put( "minecraft:entity_anchor", VOID ); + PROVIDERS.put( "minecraft:int_range", VOID ); + PROVIDERS.put( "minecraft:float_range", VOID ); + PROVIDERS.put( "minecraft:item_enchantment", VOID ); + PROVIDERS.put( "minecraft:entity_summon", VOID ); + PROVIDERS.put( "minecraft:dimension", VOID ); + } + + private static ArgumentType read(String key, ByteBuf buf) + { + ArgumentSerializer reader = PROVIDERS.get( key ); + Preconditions.checkArgument( reader != null, "No provider for argument " + key ); + + Object val = reader.read( buf ); + return val != null && PROPER_PROVIDERS.containsKey( val.getClass() ) ? (ArgumentType) val : new DummyType( key, reader, val ); + } + + private static void write(ArgumentType arg, ByteBuf buf) + { + ProperArgumentSerializer proper = PROPER_PROVIDERS.get( arg.getClass() ); + if ( proper != null ) + { + writeString( proper.getKey(), buf ); + proper.write( buf, arg ); + } else + { + Preconditions.checkArgument( arg instanceof DummyType, "Non dummy arg " + arg.getClass() ); + + DummyType dummy = (DummyType) arg; + writeString( dummy.key, buf ); + dummy.serializer.write( buf, dummy.value ); + } + } + + @Data + private static class DummyType implements ArgumentType + { + + private final String key; + private final ArgumentSerializer serializer; + private final T value; + + @Override + public T parse(StringReader reader) throws CommandSyntaxException + { + throw new UnsupportedOperationException( "Not supported." ); + } + } + + private static abstract class ArgumentSerializer + { + + protected abstract T read(ByteBuf buf); + + protected abstract void write(ByteBuf buf, T t); + } + + private static abstract class ProperArgumentSerializer extends ArgumentSerializer + { + + protected abstract String getKey(); + } + } + + @Data + public static class SuggestionRegistry + { + + public static final SuggestionProvider ASK_SERVER = new DummyProvider( "minecraft:ask_server" ); + private static final Map> PROVIDERS = new HashMap<>(); + + static + { + PROVIDERS.put( "minecraft:ask_server", ASK_SERVER ); + registerDummy( "minecraft:all_recipes" ); + registerDummy( "minecraft:available_sounds" ); + registerDummy( "minecraft:summonable_entities" ); + } + + private static void registerDummy(String name) + { + PROVIDERS.put( name, new DummyProvider( name ) ); + } + + private static SuggestionProvider getProvider(String key) + { + SuggestionProvider provider = PROVIDERS.get( key ); + Preconditions.checkArgument( provider != null, "Unknown completion provider " + key ); + + return provider; + } + + private static String getKey(SuggestionProvider provider) + { + Preconditions.checkArgument( provider instanceof DummyProvider, "Non dummy provider " + provider ); + + return ( (DummyProvider) provider ).key; + } + + @Data + private static final class DummyProvider implements SuggestionProvider + { + + private final String key; + + @Override + public CompletableFuture getSuggestions(CommandContext context, SuggestionsBuilder builder) throws CommandSyntaxException + { + return builder.buildFuture(); + } + } + } + + private static byte binaryFlag(boolean first, boolean second) + { + byte ret = 0; + + if ( first ) + { + ret = (byte) ( ret | 0x1 ); + } + if ( second ) + { + ret = (byte) ( ret | 0x2 ); + } + + return ret; + } +} diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/EntityStatus.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/EntityStatus.java new file mode 100644 index 0000000000..a151c0faa0 --- /dev/null +++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/EntityStatus.java @@ -0,0 +1,43 @@ +package net.md_5.bungee.protocol.packet; + +import io.netty.buffer.ByteBuf; +import lombok.AllArgsConstructor; +import lombok.Data; +import lombok.EqualsAndHashCode; +import lombok.NoArgsConstructor; +import net.md_5.bungee.protocol.AbstractPacketHandler; +import net.md_5.bungee.protocol.DefinedPacket; + +@Data +@NoArgsConstructor +@AllArgsConstructor +@EqualsAndHashCode(callSuper = false) +public class EntityStatus extends DefinedPacket +{ + + public static final byte DEBUG_INFO_REDUCED = 22; + public static final byte DEBUG_INFO_NORMAL = 23; + // + private int entityId; + private byte status; + + @Override + public void read(ByteBuf buf) + { + entityId = buf.readInt(); + status = buf.readByte(); + } + + @Override + public void write(ByteBuf buf) + { + buf.writeInt( entityId ); + buf.writeByte( status ); + } + + @Override + public void handle(AbstractPacketHandler handler) throws Exception + { + handler.handle( this ); + } +} diff --git a/protocol/src/main/java/net/md_5/bungee/protocol/packet/TabCompleteResponse.java b/protocol/src/main/java/net/md_5/bungee/protocol/packet/TabCompleteResponse.java index e99ceb54b1..64851ddeae 100644 --- a/protocol/src/main/java/net/md_5/bungee/protocol/packet/TabCompleteResponse.java +++ b/protocol/src/main/java/net/md_5/bungee/protocol/packet/TabCompleteResponse.java @@ -1,10 +1,13 @@ package net.md_5.bungee.protocol.packet; +import com.mojang.brigadier.LiteralMessage; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; import net.md_5.bungee.protocol.DefinedPacket; import io.netty.buffer.ByteBuf; import java.util.LinkedList; import java.util.List; -import lombok.AllArgsConstructor; import lombok.Data; import lombok.EqualsAndHashCode; import lombok.NoArgsConstructor; @@ -18,17 +21,14 @@ public class TabCompleteResponse extends DefinedPacket { private int transactionId; - private int start; - private int length; - private List matches; + private Suggestions suggestions; + // private List commands; - public TabCompleteResponse(int transactionId, int start, int length, List matches) + public TabCompleteResponse(int transactionId, Suggestions suggestions) { this.transactionId = transactionId; - this.start = start; - this.length = length; - this.matches = matches; + this.suggestions = suggestions; } public TabCompleteResponse(List commands) @@ -42,18 +42,21 @@ public void read(ByteBuf buf, ProtocolConstants.Direction direction, int protoco if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ) { transactionId = readVarInt( buf ); - start = readVarInt( buf ); - length = readVarInt( buf ); + int start = readVarInt( buf ); + int length = readVarInt( buf ); + StringRange range = StringRange.between( start, start + length ); int cnt = readVarInt( buf ); - matches = new LinkedList<>(); + List matches = new LinkedList<>(); for ( int i = 0; i < cnt; i++ ) { String match = readString( buf ); String tooltip = buf.readBoolean() ? readString( buf ) : null; - matches.add( new CommandMatch( match, tooltip ) ); + matches.add( new Suggestion( range, match, new LiteralMessage( tooltip ) ) ); } + + suggestions = new Suggestions( range, matches ); } if ( protocolVersion < ProtocolConstants.MINECRAFT_1_13 ) @@ -68,15 +71,18 @@ public void write(ByteBuf buf, ProtocolConstants.Direction direction, int protoc if ( protocolVersion >= ProtocolConstants.MINECRAFT_1_13 ) { writeVarInt( transactionId, buf ); - writeVarInt( start, buf ); - writeVarInt( length, buf ); + writeVarInt( suggestions.getRange().getStart(), buf ); + writeVarInt( suggestions.getRange().getLength(), buf ); - writeVarInt( matches.size(), buf ); - for ( CommandMatch match : matches ) + writeVarInt( suggestions.getList().size(), buf ); + for ( Suggestion suggestion : suggestions.getList() ) { - writeString( match.match, buf ); - buf.writeBoolean( match.tooltip != null ); - writeString( match.tooltip, buf ); + writeString( suggestion.getText(), buf ); + buf.writeBoolean( suggestion.getTooltip() != null ); + if ( suggestion.getTooltip() != null ) + { + writeString( suggestion.getTooltip().getString(), buf ); + } } } @@ -91,14 +97,4 @@ public void handle(AbstractPacketHandler handler) throws Exception { handler.handle( this ); } - - @Data - @NoArgsConstructor - @AllArgsConstructor - public static class CommandMatch - { - - private String match; - private String tooltip; - } } diff --git a/proxy/pom.xml b/proxy/pom.xml index 2db3785071..af3536bce1 100644 --- a/proxy/pom.xml +++ b/proxy/pom.xml @@ -84,6 +84,12 @@ ${project.version} compile + + net.sf.jopt-simple + jopt-simple + 4.9 + compile + mysql mysql-connector-java diff --git a/proxy/src/main/java/Test.java b/proxy/src/main/java/Test.java deleted file mode 100644 index 9d51608d86..0000000000 --- a/proxy/src/main/java/Test.java +++ /dev/null @@ -1,37 +0,0 @@ - -import net.md_5.bungee.BungeeCord; -import net.md_5.bungee.api.ChatColor; -import net.md_5.bungee.api.ProxyServer; -import net.md_5.bungee.command.ConsoleCommandSender; - -/* - * To change this template, choose Tools | Templates - * and open the template in the editor. - */ -/** - * - * @author michael - */ -public class Test -{ - - public static void main(String[] args) throws Exception - { - BungeeCord bungee = new BungeeCord(); - ProxyServer.setInstance( bungee ); - bungee.getLogger().info( "Enabled BungeeCord version " + bungee.getVersion() ); - bungee.start(); - - while ( bungee.isRunning ) - { - String line = bungee.getConsoleReader().readLine( ">" ); - if ( line != null ) - { - if ( !bungee.getPluginManager().dispatchCommand( ConsoleCommandSender.getInstance(), line ) ) - { - bungee.getConsole().sendMessage( ChatColor.RED + "Command not found" ); - } - } - } - } -} diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java index 9402f3d35a..83340b96c9 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeCord.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCord.java @@ -1,7 +1,6 @@ package net.md_5.bungee; import com.google.common.base.Charsets; -import com.google.common.base.Joiner; import com.google.common.base.Preconditions; import com.google.common.base.Predicate; import com.google.common.collect.Iterables; @@ -291,7 +290,7 @@ public void start() throws Exception if ( config.getThrottle() > 0 ) { - connectionThrottle = new ConnectionThrottle( config.getThrottle() ); + connectionThrottle = new ConnectionThrottle( config.getThrottle(), config.getThrottleLimit() ); } startListeners(); diff --git a/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java b/proxy/src/main/java/net/md_5/bungee/BungeeCordLauncher.java similarity index 98% rename from bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java rename to proxy/src/main/java/net/md_5/bungee/BungeeCordLauncher.java index 9e9cd79ad8..fdfb6222b8 100644 --- a/bootstrap/src/main/java/net/md_5/bungee/BungeeCordLauncher.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeCordLauncher.java @@ -38,7 +38,7 @@ public static void main(String[] args) throws Exception if ( options.has("version") ) { - System.out.println(Bootstrap.class.getPackage().getImplementationVersion()); + System.out.println( BungeeCord.class.getPackage().getImplementationVersion() ); return; } diff --git a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java index 89c8f48fdc..c0703afaa4 100644 --- a/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java +++ b/proxy/src/main/java/net/md_5/bungee/BungeeServerInfo.java @@ -1,6 +1,5 @@ package net.md_5.bungee; -import com.google.common.base.Objects; import com.google.common.base.Preconditions; import io.netty.bootstrap.Bootstrap; import io.netty.channel.ChannelFuture; @@ -12,6 +11,7 @@ import java.util.Collections; import java.util.HashSet; import java.util.LinkedList; +import java.util.Objects; import java.util.Queue; import lombok.Getter; import lombok.RequiredArgsConstructor; @@ -79,7 +79,7 @@ public boolean canAccess(CommandSender player) @Override public boolean equals(Object obj) { - return ( obj instanceof ServerInfo ) && Objects.equal( getAddress(), ( (ServerInfo) obj ).getAddress() ); + return ( obj instanceof ServerInfo ) && Objects.equals( getAddress(), ( (ServerInfo) obj ).getAddress() ); } @Override diff --git a/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java b/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java index 4c9190e21f..f1716a1fb3 100644 --- a/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java +++ b/proxy/src/main/java/net/md_5/bungee/ConnectionThrottle.java @@ -1,29 +1,45 @@ package net.md_5.bungee; -import com.google.common.cache.Cache; import com.google.common.cache.CacheBuilder; +import com.google.common.cache.CacheLoader; +import com.google.common.cache.LoadingCache; import java.net.InetAddress; import java.util.concurrent.TimeUnit; public class ConnectionThrottle { - private final Cache throttle; + private final LoadingCache throttle; + private final int throttleLimit; - public ConnectionThrottle(int throttleTime) + public ConnectionThrottle(int throttleTime, int throttleLimit) { this.throttle = CacheBuilder.newBuilder() .concurrencyLevel( Runtime.getRuntime().availableProcessors() ) .initialCapacity( 100 ) .expireAfterWrite( throttleTime, TimeUnit.MILLISECONDS ) - .build(); + .build( new CacheLoader() + { + @Override + public Integer load(InetAddress key) throws Exception + { + return 0; + } + } ); + this.throttleLimit = throttleLimit; + } + + public void unthrottle(InetAddress address) + { + int throttleCount = throttle.getUnchecked( address ) - 1; + throttle.put( address, throttleCount ); } public boolean throttle(InetAddress address) { - boolean isThrottled = throttle.getIfPresent( address ) != null; - throttle.put( address, true ); + int throttleCount = throttle.getUnchecked( address ) + 1; + throttle.put( address, throttleCount ); - return isThrottled; + return throttleCount > throttleLimit; } } diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnection.java b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java index 285e5b8f82..d11d601ec6 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnection.java @@ -2,7 +2,6 @@ import com.google.common.base.Preconditions; import java.net.InetSocketAddress; -import java.util.concurrent.TimeUnit; import lombok.Getter; import lombok.RequiredArgsConstructor; import lombok.Setter; diff --git a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java index 89f66eddb9..129b118060 100644 --- a/proxy/src/main/java/net/md_5/bungee/ServerConnector.java +++ b/proxy/src/main/java/net/md_5/bungee/ServerConnector.java @@ -38,6 +38,7 @@ import net.md_5.bungee.protocol.Protocol; import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.packet.EncryptionRequest; +import net.md_5.bungee.protocol.packet.EntityStatus; import net.md_5.bungee.protocol.packet.Handshake; import net.md_5.bungee.protocol.packet.Kick; import net.md_5.bungee.protocol.packet.Login; @@ -49,6 +50,7 @@ import net.md_5.bungee.protocol.packet.ScoreboardScore; import net.md_5.bungee.protocol.packet.SetCompression; import net.md_5.bungee.util.BufUtil; +import net.md_5.bungee.util.QuietException; @RequiredArgsConstructor @@ -149,7 +151,7 @@ public void handle(PacketWrapper packet) throws Exception { if ( packet.packet == null ) { - throw new IllegalArgumentException( "Unexpected packet received during server login process!\n" + BufUtil.dump( packet.buf, 64 ) ); + throw new QuietException( "Unexpected packet received during server login process!\n" + BufUtil.dump( packet.buf, 16 ) ); } } @@ -221,8 +223,6 @@ public void handle(Login login) throws Exception if (user.getServer() == null) { // Once again, first connection - user.setClientEntityId(login.getEntityId()); - user.setServerEntityId(login.getEntityId()); // Set tab list size, this sucks balls, TODO: what shall we do about packet mutability // Forge allows dimension ID's > 127 @@ -281,6 +281,9 @@ public void handle(Login login) throws Exception user.unsafe().sendPacket(new net.md_5.bungee.protocol.packet.BossBar(bossbar, 1)); user.getSentBossBars().clear(); + // Update debug info from login packet + user.unsafe().sendPacket( new EntityStatus( user.getClientEntityId(), login.isReducedDebugInfo() ? EntityStatus.DEBUG_INFO_REDUCED : EntityStatus.DEBUG_INFO_NORMAL ) ); + user.setDimensionChange( true ); if ( login.getDimension() == user.getDimension() ) { @@ -324,7 +327,7 @@ public void handle(Login login) throws Exception @Override public void handle(EncryptionRequest encryptionRequest) throws Exception { - throw new RuntimeException("Server is online mode!"); + throw new QuietException( "Server is online mode!" ); } @Override diff --git a/proxy/src/main/java/net/md_5/bungee/UserConnection.java b/proxy/src/main/java/net/md_5/bungee/UserConnection.java index b4a9db676d..f93f314955 100644 --- a/proxy/src/main/java/net/md_5/bungee/UserConnection.java +++ b/proxy/src/main/java/net/md_5/bungee/UserConnection.java @@ -1,6 +1,5 @@ package net.md_5.bungee; -import com.google.common.base.Objects; import com.google.common.base.Preconditions; import com.google.common.collect.ImmutableMap; import io.netty.bootstrap.Bootstrap; @@ -17,6 +16,7 @@ import java.util.LinkedList; import java.util.Locale; import java.util.Map; +import java.util.Objects; import java.util.Queue; import java.util.UUID; import java.util.logging.Level; @@ -242,7 +242,7 @@ public ServerInfo updateAndGetNextServer(ServerInfo currentTarget) while ( !serverJoinQueue.isEmpty() ) { ServerInfo candidate = ProxyServer.getInstance().getServerInfo( serverJoinQueue.remove() ); - if ( !Objects.equal( currentTarget, candidate ) ) + if ( !Objects.equals( currentTarget, candidate ) ) { next = candidate; break; @@ -301,7 +301,7 @@ public void connect(final ServerConnectRequest request) final BungeeServerInfo target = (BungeeServerInfo) event.getTarget(); // Update in case the event changed target - if ( getServer() != null && Objects.equal( getServer().getInfo(), target ) ) + if ( getServer() != null && Objects.equals( getServer().getInfo(), target ) ) { if ( callback != null ) { diff --git a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java index b5da6f9bcd..0b5eceffeb 100644 --- a/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java +++ b/proxy/src/main/java/net/md_5/bungee/conf/Configuration.java @@ -55,6 +55,7 @@ public class Configuration implements ProxyConfig private int playerLimit = -1; private Collection disabledCommands; private int throttle = 4000; + private int throttleLimit = 3; private boolean ipForward; private Favicon favicon; private int compressionThreshold = 256; @@ -62,6 +63,7 @@ public class Configuration implements ProxyConfig private boolean alwaysHandlePackets = false; private boolean preventProxyConnections; private boolean forgeSupport; + private boolean injectCommands; public void load() { @@ -88,12 +90,18 @@ public void load() logPings = adapter.getBoolean( "log_pings", logPings ); playerLimit = adapter.getInt( "player_limit", playerLimit ); throttle = adapter.getInt( "connection_throttle", throttle ); + throttleLimit = adapter.getInt( "connection_throttle_limit", throttleLimit ); ipForward = adapter.getBoolean( "ip_forward", ipForward ); compressionThreshold = adapter.getInt( "network_compression_threshold", compressionThreshold ); customServerName = adapter.getString( "custom_server_name", "HexaCord" ); alwaysHandlePackets = adapter.getBoolean( "always_handle_packets", false ); preventProxyConnections = adapter.getBoolean( "prevent_proxy_connections", preventProxyConnections ); forgeSupport = adapter.getBoolean( "forge_support", forgeSupport ); + injectCommands = adapter.getBoolean( "inject_commands", injectCommands ); + if ( injectCommands ) + { + System.setProperty( "net.md-5.bungee.protocol.register_commands", "true" ); + } disabledCommands = new CaseInsensitiveSet( (Collection) adapter.getList( "disabled_commands", Arrays.asList( "disabledcommandhere" ) ) ); diff --git a/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java b/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java index e15c24c140..4cfb13c024 100644 --- a/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java +++ b/proxy/src/main/java/net/md_5/bungee/conf/YamlConfig.java @@ -83,23 +83,23 @@ public void load() throw new RuntimeException( "Could not load configuration!", ex ); } - Map permissions = get( "permissions", new HashMap() ); - if ( permissions.isEmpty() ) + Map permissions = get( "permissions", null ); + if ( permissions == null ) { - permissions.put( "default", Arrays.asList( new String[] + set( "permissions.default", Arrays.asList( new String[] { "bungeecord.command.server", "bungeecord.command.list" } ) ); - permissions.put( "admin", Arrays.asList( new String[] + set( "permissions.admin", Arrays.asList( new String[] { "bungeecord.command.alert", "bungeecord.command.end", "bungeecord.command.ip", "bungeecord.command.reload" } ) ); } - Map groups = get( "groups", new HashMap() ); - if ( groups.isEmpty() ) + Map groups = get( "groups", null ); + if ( groups == null ) { - groups.put( "md_5", Collections.singletonList( "admin" ) ); + set( "groups.md_5", Collections.singletonList( "admin" ) ); } } @@ -136,6 +136,11 @@ private T get(String path, T def, Map submap) } } + private void set(String path, Object val) + { + set( path, val, config ); + } + @SuppressWarnings("unchecked") private void set(String path, Object val, Map submap) { diff --git a/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java index 223ae8777a..5bab8ae379 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/DownstreamBridge.java @@ -4,11 +4,18 @@ import com.google.common.base.Preconditions; import com.google.common.io.ByteArrayDataOutput; import com.google.common.io.ByteStreams; +import com.mojang.brigadier.arguments.StringArgumentType; +import com.mojang.brigadier.builder.LiteralArgumentBuilder; +import com.mojang.brigadier.builder.RequiredArgumentBuilder; +import com.mojang.brigadier.suggestion.SuggestionProvider; +import com.mojang.brigadier.tree.LiteralCommandNode; import java.io.DataInput; import io.netty.buffer.ByteBuf; import io.netty.buffer.ByteBufAllocator; import io.netty.buffer.Unpooled; +import java.util.Map; import lombok.RequiredArgsConstructor; +import net.md_5.bungee.BungeeCord; import net.md_5.bungee.ServerConnection; import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.event.ServerDisconnectEvent; @@ -21,6 +28,7 @@ import net.md_5.bungee.api.event.PluginMessageEvent; import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.event.ServerKickEvent; +import net.md_5.bungee.api.plugin.Command; import net.md_5.bungee.api.score.Objective; import net.md_5.bungee.api.score.Position; import net.md_5.bungee.api.score.Score; @@ -33,6 +41,7 @@ import net.md_5.bungee.protocol.PacketWrapper; import net.md_5.bungee.protocol.ProtocolConstants; import net.md_5.bungee.protocol.packet.BossBar; +import net.md_5.bungee.protocol.packet.Commands; import net.md_5.bungee.protocol.packet.KeepAlive; import net.md_5.bungee.protocol.packet.PlayerListItem; import net.md_5.bungee.protocol.packet.Respawn; @@ -438,8 +447,8 @@ public void handle(PluginMessage pluginMessage) throws Exception } if (subChannel.equals("ServerIP")) { - ServerInfo info = bungee.getServerInfo(in.readUTF()); - if (info != null) + ServerInfo info = bungee.getServerInfo( in.readUTF() ); + if ( info != null && !info.getAddress().isUnresolved() ) { out.writeUTF("ServerIP"); out.writeUTF(info.getName()); @@ -534,6 +543,35 @@ public void handle(Respawn respawn) con.setDimension( respawn.getDimension() ); } + @Override + public void handle(Commands commands) throws Exception + { + boolean modified = false; + + if ( BungeeCord.getInstance().config.isInjectCommands() ) + { + for ( Map.Entry command : bungee.getPluginManager().getCommands() ) + { + if ( commands.getRoot().getChild( command.getKey() ) == null && command.getValue().hasPermission( con ) ) + { + LiteralCommandNode dummy = LiteralArgumentBuilder.literal( command.getKey() ) + .then( RequiredArgumentBuilder.argument( "args", StringArgumentType.greedyString() ) + .suggests( Commands.SuggestionRegistry.ASK_SERVER ) ) + .build(); + commands.getRoot().addChild( dummy ); + + modified = true; + } + } + } + + if ( modified ) + { + con.unsafe().sendPacket( commands ); + throw CancelSendSignal.INSTANCE; + } + } + @Override public String toString() { diff --git a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java index e1242efd3c..2d4d67ec3b 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/InitialHandler.java @@ -4,7 +4,6 @@ import com.google.common.base.Preconditions; import com.google.gson.Gson; import java.math.BigInteger; -import java.net.InetAddress; import java.net.InetSocketAddress; import java.net.URLEncoder; import java.security.MessageDigest; @@ -29,7 +28,6 @@ import net.md_5.bungee.api.chat.TextComponent; import net.md_5.bungee.api.config.ListenerInfo; import net.md_5.bungee.api.config.ServerInfo; -import net.md_5.bungee.api.connection.Connection.Unsafe; import net.md_5.bungee.api.connection.PendingConnection; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.LoginEvent; @@ -66,6 +64,7 @@ import net.md_5.bungee.protocol.packet.StatusResponse; import net.md_5.bungee.util.BoundedArrayList; import net.md_5.bungee.util.BufUtil; +import net.md_5.bungee.util.QuietException; @RequiredArgsConstructor public class InitialHandler extends PacketHandler implements PendingConnection @@ -136,7 +135,7 @@ public void handle(PacketWrapper packet) throws Exception { if ( packet.packet == null ) { - throw new IllegalArgumentException( "Unexpected packet received during login process!\n" + BufUtil.dump( packet.buf, 64 ) ); + throw new QuietException( "Unexpected packet received during login process! " + BufUtil.dump( packet.buf, 16 ) ); } } @@ -236,6 +235,10 @@ public void done(ProxyPingEvent pingResult, Throwable error) { Gson gson = handshake.getProtocolVersion() == ProtocolConstants.MINECRAFT_1_7_2 ? BungeeCord.getInstance().gsonLegacy : BungeeCord.getInstance().gson; unsafe.sendPacket( new StatusResponse( gson.toJson( pingResult.getResponse() ) ) ); + if ( bungee.getConnectionThrottle() != null ) + { + bungee.getConnectionThrottle().unthrottle( getAddress().getAddress() ); + } } }; @@ -274,7 +277,7 @@ public void handle(Handshake handshake) throws Exception this.handshake = handshake; ch.setVersion( handshake.getProtocolVersion() ); - // Starting with FML 1.8, a "\0FML\0" token is appended to the handshake. This interferes + // Starting with FML 1.8, a "\0FML\0" token is appended to the handshake. This interferes // with Bungee's IP forwarding, so we detect it, and remove it from the host string, for now. // We know FML appends \00FML\00. However, we need to also consider that other systems might // add their own data to the end of the string. So, we just take everything from the \0 character @@ -327,11 +330,6 @@ public void handle(Handshake handshake) throws Exception } return; } - - if ( bungee.getConnectionThrottle() != null && bungee.getConnectionThrottle().throttle( getAddress().getAddress() ) ) - { - disconnect( bungee.getTranslation( "join_throttle_kick", TimeUnit.MILLISECONDS.toSeconds( bungee.getConfig().getThrottle() ) ) ); - } break; default: throw new IllegalArgumentException( "Cannot request protocol " + handshake.getRequestedProtocol() ); @@ -487,7 +485,7 @@ private void finish() } - offlineId = java.util.UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( Charsets.UTF_8 ) ); + offlineId = UUID.nameUUIDFromBytes( ( "OfflinePlayer:" + getName() ).getBytes( Charsets.UTF_8 ) ); if ( uniqueId == null ) { uniqueId = offlineId; diff --git a/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java b/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java index 6f60ed5f97..debcf47db2 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/PingHandler.java @@ -20,6 +20,7 @@ import net.md_5.bungee.protocol.packet.StatusRequest; import net.md_5.bungee.protocol.packet.StatusResponse; import net.md_5.bungee.util.BufUtil; +import net.md_5.bungee.util.QuietException; @RequiredArgsConstructor public class PingHandler extends PacketHandler @@ -56,7 +57,7 @@ public void handle(PacketWrapper packet) throws Exception { if ( packet.packet == null ) { - throw new IllegalArgumentException( "Unexpected packet received during ping process!\n" + BufUtil.dump( packet.buf, 64 ) ); + throw new QuietException( "Unexpected packet received during ping process! " + BufUtil.dump( packet.buf, 16 ) ); } } diff --git a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java index bd942ac4bc..ffdb1f6b32 100644 --- a/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java +++ b/proxy/src/main/java/net/md_5/bungee/connection/UpstreamBridge.java @@ -1,8 +1,12 @@ package net.md_5.bungee.connection; import com.google.common.base.Preconditions; +import com.mojang.brigadier.context.StringRange; +import com.mojang.brigadier.suggestion.Suggestion; +import com.mojang.brigadier.suggestion.Suggestions; import io.netty.channel.Channel; import java.util.ArrayList; +import java.util.LinkedList; import java.util.List; import net.md_5.bungee.BungeeCord; import net.md_5.bungee.UserConnection; @@ -175,6 +179,19 @@ public void handle(TabCompleteRequest tabComplete) throws Exception if ( con.getPendingConnection().getVersion() < ProtocolConstants.MINECRAFT_1_13 ) { con.unsafe().sendPacket( new TabCompleteResponse( results ) ); + } else if ( BungeeCord.getInstance().config.isInjectCommands() ) + { + int start = tabComplete.getCursor().lastIndexOf( ' ' ) + 1; + int end = tabComplete.getCursor().length(); + StringRange range = StringRange.between( start, end ); + + List brigadier = new LinkedList<>(); + for ( String s : results ) + { + brigadier.add( new Suggestion( range, s ) ); + } + + con.unsafe().sendPacket( new TabCompleteResponse( tabComplete.getTransactionId(), new Suggestions( range, brigadier ) ) ); } throw CancelSendSignal.INSTANCE; } diff --git a/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java index 5437699f76..cefa0206ee 100644 --- a/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/forge/ForgeUtils.java @@ -2,9 +2,9 @@ import com.google.common.base.Charsets; import com.google.common.collect.ImmutableSet; -import com.google.common.collect.Maps; import io.netty.buffer.ByteBuf; import io.netty.buffer.Unpooled; +import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.regex.Matcher; @@ -36,7 +36,7 @@ public static Set readRegisteredChannels(PluginMessage pluginMessage) */ public static Map readModList(PluginMessage pluginMessage) { - Map modTags = Maps.newHashMap(); + Map modTags = new HashMap<>(); ByteBuf payload = Unpooled.wrappedBuffer( pluginMessage.getData() ); byte discriminator = payload.readByte(); if ( discriminator == 2 ) // ModList diff --git a/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java index 7e7cf7bed4..a66099fc68 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/HandlerBoss.java @@ -16,6 +16,7 @@ import net.md_5.bungee.protocol.BadPacketException; import net.md_5.bungee.protocol.OverflowPacketException; import net.md_5.bungee.protocol.PacketWrapper; +import net.md_5.bungee.util.QuietException; /** * This class is a primitive wrapper for {@link PacketHandler} instances tied to @@ -147,6 +148,12 @@ public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws E { handler, cause.getClass().getSimpleName(), cause.getMessage() } ); + } else if ( cause instanceof QuietException ) + { + ProxyServer.getInstance().getLogger().log( Level.SEVERE, "{0} - encountered exception: {1}", new Object[] + { + handler, cause + } ); } else { ProxyServer.getInstance().getLogger().log( Level.SEVERE, handler + " - encountered exception", cause ); diff --git a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java index ffee7062e4..16e07a4c00 100644 --- a/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java +++ b/proxy/src/main/java/net/md_5/bungee/netty/PipelineUtils.java @@ -21,6 +21,7 @@ import io.netty.handler.timeout.ReadTimeoutHandler; import io.netty.util.AttributeKey; import io.netty.util.internal.PlatformDependent; +import java.net.InetSocketAddress; import java.util.concurrent.ThreadFactory; import java.util.concurrent.TimeUnit; import java.util.logging.Level; @@ -50,6 +51,12 @@ public class PipelineUtils @Override protected void initChannel(Channel ch) throws Exception { + if ( BungeeCord.getInstance().getConnectionThrottle() != null && BungeeCord.getInstance().getConnectionThrottle().throttle( ( (InetSocketAddress) ch.remoteAddress() ).getAddress() ) ) + { + ch.close(); + return; + } + ListenerInfo listener = ch.attr( LISTENER ).get(); BASE.initChannel( ch ); diff --git a/proxy/src/main/java/net/md_5/bungee/util/ChatComponentTransformer.java b/proxy/src/main/java/net/md_5/bungee/util/ChatComponentTransformer.java index 3457aaa7da..ad26893e2b 100644 --- a/proxy/src/main/java/net/md_5/bungee/util/ChatComponentTransformer.java +++ b/proxy/src/main/java/net/md_5/bungee/util/ChatComponentTransformer.java @@ -79,9 +79,8 @@ public BaseComponent[] transform(ProxiedPlayer player, BaseComponent... componen * Transform a ScoreComponent by replacing the name and value with the * appropriate values. * - * @param component the component to transform - * @param scoreboard the scoreboard to retrieve scores from * @param player the player to use for the component's name + * @param component the component to transform */ private void transformScoreComponent(ProxiedPlayer player, ScoreComponent component) { diff --git a/proxy/src/main/java/net/md_5/bungee/util/QuietException.java b/proxy/src/main/java/net/md_5/bungee/util/QuietException.java new file mode 100644 index 0000000000..3c958fde05 --- /dev/null +++ b/proxy/src/main/java/net/md_5/bungee/util/QuietException.java @@ -0,0 +1,25 @@ +package net.md_5.bungee.util; + +/** + * Exception without a stack trace component. + */ +public class QuietException extends RuntimeException +{ + + public QuietException(String message) + { + super( message ); + } + + @Override + public Throwable initCause(Throwable cause) + { + return this; + } + + @Override + public Throwable fillInStackTrace() + { + return this; + } +} diff --git a/proxy/src/main/resources/messages.properties b/proxy/src/main/resources/messages.properties index 84405e9985..dfb58c121b 100644 --- a/proxy/src/main/resources/messages.properties +++ b/proxy/src/main/resources/messages.properties @@ -23,5 +23,4 @@ total_players=Total players online: {0} name_too_long=Cannot have username longer than 16 characters name_invalid=Username contains invalid characters. ping_cannot_connect=\u00a7c[Bungee] Can't connect to server. -join_throttle_kick=You have connected too fast. You must wait at least {0} seconds between connections. offline_mode_player=Not authenticated with Minecraft.net diff --git a/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java b/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java index 5f501f7d88..881aa06a6b 100644 --- a/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java +++ b/proxy/src/test/java/net/md_5/bungee/ThrottleTest.java @@ -11,7 +11,7 @@ public class ThrottleTest @Test public void testThrottle() throws InterruptedException, UnknownHostException { - ConnectionThrottle throttle = new ConnectionThrottle( 10 ); + ConnectionThrottle throttle = new ConnectionThrottle( 10, 3 ); InetAddress address; try @@ -22,9 +22,17 @@ public void testThrottle() throws InterruptedException, UnknownHostException address = InetAddress.getByName( null ); } - Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); - Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) ); + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 1 + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 2 + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 3 + Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) ); // The 3rd one must be throttled, but also increased the count to 4 + + throttle.unthrottle( address ); // We are back at 3, next attempt will make it 4 and throttle + throttle.unthrottle( address ); // Now we are at 2, will not be throttled + Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); // 3 + Assert.assertTrue( "Address should be throttled", throttle.throttle( address ) ); // 4 + // Now test expiration Thread.sleep( 50 ); Assert.assertFalse( "Address should not be throttled", throttle.throttle( address ) ); }