package net.md_5.bungee; import com.google.common.base.Preconditions; import io.netty.bootstrap.Bootstrap; import io.netty.channel.*; import io.netty.channel.socket.nio.NioSocketChannel; import io.netty.util.concurrent.GenericFutureListener; import io.netty.util.internal.PlatformDependent; import java.beans.ConstructorProperties; import java.net.InetSocketAddress; import java.net.SocketAddress; import java.nio.charset.StandardCharsets; import java.util.*; import java.util.logging.Level; import net.md_5.bungee.api.ChatColor; import net.md_5.bungee.api.ProxyServer; import net.md_5.bungee.api.config.ServerInfo; import net.md_5.bungee.api.connection.Connection; import net.md_5.bungee.api.connection.ProxiedPlayer; import net.md_5.bungee.api.event.PermissionCheckEvent; import net.md_5.bungee.api.event.ServerConnectEvent; import net.md_5.bungee.api.score.Scoreboard; import net.md_5.bungee.api.tab.TabListHandler; import net.md_5.bungee.connection.InitialHandler; import net.md_5.bungee.eaglercraft.RedirectServerInfo; import net.md_5.bungee.netty.ChannelWrapper; import net.md_5.bungee.netty.HandlerBoss; import net.md_5.bungee.netty.PacketWrapper; import net.md_5.bungee.netty.PipelineUtils; import net.md_5.bungee.protocol.packet.DefinedPacket; import net.md_5.bungee.protocol.packet.Packet3Chat; import net.md_5.bungee.protocol.packet.PacketCCSettings; import net.md_5.bungee.protocol.packet.PacketFAPluginMessage; import net.md_5.bungee.protocol.packet.PacketFFKick; import net.md_5.bungee.util.CaseInsensitiveSet; public final class UserConnection implements ProxiedPlayer { /*========================================================================*/ private final ProxyServer bungee; private final ChannelWrapper ch; private final String name; private final InitialHandler pendingConnection; /*========================================================================*/ private ServerConnection server; private Object switchMutex = new Object(); private Collection pendingConnects = new HashSet<>(); /*========================================================================*/ private TabListHandler tabList; private int sentPingId; private long sentPingTime; private int ping = 100; private ServerInfo reconnectServer; /*========================================================================*/ private Collection groups = new CaseInsensitiveSet(); private Collection permissions = new CaseInsensitiveSet(); /*========================================================================*/ private int serverEntityId; private PacketCCSettings settings; private Scoreboard serverSentScoreboard = new Scoreboard(); /*========================================================================*/ private String displayName; /*========================================================================*/ private int clientEntityId; private final Map attachment = new WeakHashMap(); public void setServer(ServerConnection server) { this.server = server; } public void setSettings(PacketCCSettings settings){ this.settings = settings; } public void setServerEntityId(int serverEntityId) { this.serverEntityId = serverEntityId; } public void setSentPingId(int sentPingId) { this.sentPingId = sentPingId; } public void setSentPingTime(long sentPingTime) { this.sentPingTime = sentPingTime; } public void setPing(int ping) { this.ping = ping; } @Override public void setReconnectServer(ServerInfo reconnectServer) { this.reconnectServer = reconnectServer; } public void setClientEntityId(int clientEntityId) { this.clientEntityId = clientEntityId; } @Override public String getName() { return name; } @Override public InitialHandler getPendingConnection() { return pendingConnection; } @Override public TabListHandler getTabList() { return tabList; } public Collection getPendingConnects() { return pendingConnects; } public Object getSwitchMutex() { return switchMutex; } @Override public ServerConnection getServer() { return server; } public int getSentPingId() { return sentPingId; } public long getSentPingTime() { return sentPingTime; } @Override public int getPing() { return ping; } @Override public ServerInfo getReconnectServer() { return reconnectServer; } public Collection getPermissions() { return permissions; } public int getClientEntityId() { return clientEntityId; } public int getServerEntityId() { return serverEntityId; } public PacketCCSettings getSettings() { return settings; } public Scoreboard getServerSentScoreboard() { return serverSentScoreboard; } @Override public String getDisplayName() { return displayName; } private final Connection.Unsafe unsafe; @ConstructorProperties({ "bungee", "ch", "name", "pendingConnection" }) public UserConnection(final ProxyServer bungee, final ChannelWrapper ch, final String name, final InitialHandler pendingConnection) { this.switchMutex = new Object(); this.pendingConnects = new HashSet(); this.ping = 100; this.groups = (Collection) new CaseInsensitiveSet(); this.permissions = (Collection) new CaseInsensitiveSet(); this.serverSentScoreboard = new Scoreboard(); this.unsafe = new Connection.Unsafe() { @Override public void sendPacket(final DefinedPacket packet) { UserConnection.this.ch.write(packet); } }; if (bungee == null) { throw new NullPointerException("bungee"); } if (ch == null) { throw new NullPointerException("ch"); } if (name == null) { throw new NullPointerException("name"); } this.bungee = bungee; this.ch = ch; this.name = name; this.pendingConnection = pendingConnection; } public void init() { this.displayName = name; try { this.tabList = getPendingConnection().getListener().getTabList().getDeclaredConstructor().newInstance(); } catch ( ReflectiveOperationException ex ) { throw new RuntimeException( ex ); } this.tabList.init( this ); Collection g = bungee.getConfigurationAdapter().getGroups( name ); for ( String s : g ) { addGroups( s ); } } @Override public void setTabList(TabListHandler tabList) { tabList.init( this ); this.tabList = tabList; } public void sendPacket(final byte[] b) { this.ch.write(b); } @Deprecated public boolean isActive() { return !ch.isClosed(); } @Override public void setDisplayName(String name) { Preconditions.checkNotNull( name, "displayName" ); Preconditions.checkArgument( name.length() <= 16, "Display name cannot be longer than 16 characters" ); getTabList().onDisconnect(); displayName = name; getTabList().onConnect(); } @Override public void connect(ServerInfo target) { connect( target, false ); } void sendDimensionSwitch() { unsafe().sendPacket( PacketConstants.DIM1_SWITCH ); unsafe().sendPacket( PacketConstants.DIM2_SWITCH ); } public void connectNow(ServerInfo target) { sendDimensionSwitch(); connect( target ); } public void connect(final ServerInfo info, final boolean retry) { if(info instanceof RedirectServerInfo) { sendData("EAG|Reconnect", ((RedirectServerInfo)info).getRedirect().getBytes(StandardCharsets.UTF_8)); return; } final ServerConnectEvent event = new ServerConnectEvent(this, info); if (this.bungee.getPluginManager().callEvent(event).isCancelled()) { return; } final BungeeServerInfo target = (BungeeServerInfo) event.getTarget(); if (this.getServer() != null && this.getServer().getInfo() != null && Objects.equals(this.getServer().getInfo(), target)) { //this.sendMessage(ChatColor.RED + "Cannot connect to server you are already on!"); return; } if (this.pendingConnects.contains(target)) { this.sendMessage(ChatColor.RED + "Already connecting to this server!"); return; } this.pendingConnects.add(target); final ChannelInitializer initializer = new ChannelInitializer() { protected void initChannel(final Channel ch) throws Exception { PipelineUtils.BASE.initChannel(ch); ((HandlerBoss) ch.pipeline().get((Class) HandlerBoss.class)).setHandler(new ServerConnector(UserConnection.this.bungee, UserConnection.this, target)); } }; final ChannelFutureListener listener = (ChannelFutureListener) new ChannelFutureListener() { public void operationComplete(final ChannelFuture future) throws Exception { if (!future.isSuccess()) { future.channel().close(); UserConnection.this.pendingConnects.remove(target); final ServerInfo def = ProxyServer.getInstance().getServers().get(UserConnection.this.getPendingConnection().getListener().getFallbackServer()); if ((retry & target != def) && ((UserConnection.this.getServer() == null || UserConnection.this.getServer().getInfo() == null) || def != UserConnection.this.getServer().getInfo())) { UserConnection.this.sendMessage(UserConnection.this.bungee.getTranslation("fallback_lobby")); UserConnection.this.connect(def, false); } else if (UserConnection.this.getServer() == null || UserConnection.this.getServer().getInfo() == null) { UserConnection.this.disconnect(UserConnection.this.bungee.getTranslation("fallback_kick") + future.cause().getClass().getName()); } else { UserConnection.this.sendMessage(UserConnection.this.bungee.getTranslation("fallback_kick") + future.cause().getClass().getName()); } } } }; final Bootstrap b = ((Bootstrap) ((Bootstrap) ((Bootstrap) ((Bootstrap) new Bootstrap().channel((Class) NioSocketChannel.class)).group((EventLoopGroup) BungeeCord.getInstance().eventLoops)).handler((ChannelHandler) initializer)) .option(ChannelOption.CONNECT_TIMEOUT_MILLIS, Integer.valueOf(5000))).remoteAddress((SocketAddress) target.getAddress()); if (!PlatformDependent.isWindows()) { b.localAddress(this.getPendingConnection().getListener().getHost().getHostString(), 0); } b.connect().addListener((GenericFutureListener) listener); } @Override public synchronized void disconnect(String reason) { if ( ch.getHandle().isActive() ) { bungee.getLogger().log( Level.INFO, "[" + getName() + "] disconnected with: " + reason ); unsafe().sendPacket( new PacketFFKick( reason ) ); ch.close(); if ( server != null ) { server.disconnect( "Quitting" ); } } } @Override public void chat(String message) { Preconditions.checkState( server != null, "Not connected to server" ); server.getCh().write( new Packet3Chat( message ) ); } @Override public void sendMessage(String message) { this.unsafe().sendPacket(new Packet3Chat(message)); } @Override public void sendMessages(String... messages) { for ( String message : messages ) { sendMessage( message ); } } @Override public void sendData(String channel, byte[] data) { unsafe().sendPacket( new PacketFAPluginMessage( channel, data ) ); } @Override public InetSocketAddress getAddress() { return (InetSocketAddress) ch.getHandle().remoteAddress(); } @Override public Collection getGroups() { return Collections.unmodifiableCollection( groups ); } @Override public void addGroups(String... groups) { for ( String group : groups ) { this.groups.add( group ); for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) ) { setPermission( permission, true ); } } } @Override public void removeGroups(String... groups) { for ( String group : groups ) { this.groups.remove( group ); for ( String permission : bungee.getConfigurationAdapter().getPermissions( group ) ) { setPermission( permission, false ); } } } @Override public boolean hasPermission(String permission) { return bungee.getPluginManager().callEvent( new PermissionCheckEvent( this, permission, permissions.contains( permission ) ) ).hasPermission(); } @Override public void setPermission(String permission, boolean value) { if ( value ) { permissions.add( permission ); } else { permissions.remove( permission ); } } @Override public Map getAttachment() { // fix this (maybe) return attachment; } @Override public String toString() { return name; } @Override public Unsafe unsafe() { return unsafe; } }