001/* 002 * Trident - A Multithreaded Server Alternative 003 * Copyright 2014 The TridentSDK Team 004 * 005 * Licensed under the Apache License, Version 2.0 (the "License"); 006 * you may not use this file except in compliance with the License. 007 * You may obtain a copy of the License at 008 * 009 * http://www.apache.org/licenses/LICENSE-2.0 010 * 011 * Unless required by applicable law or agreed to in writing, software 012 * distributed under the License is distributed on an "AS IS" BASIS, 013 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 014 * See the License for the specific language governing permissions and 015 * limitations under the License. 016 */ 017 018package net.tridentsdk.server.netty; 019 020import io.netty.buffer.ByteBuf; 021import io.netty.buffer.Unpooled; 022import io.netty.channel.Channel; 023import io.netty.channel.ChannelHandlerAdapter; 024import io.netty.channel.ChannelHandlerContext; 025import net.tridentsdk.docs.InternalUseOnly; 026import net.tridentsdk.entity.living.Player; 027import net.tridentsdk.server.netty.packet.Packet; 028import net.tridentsdk.server.netty.protocol.Protocol; 029import net.tridentsdk.server.packets.login.LoginHandler; 030import net.tridentsdk.server.packets.login.PacketLoginOutDisconnect; 031import net.tridentsdk.server.packets.login.PacketLoginOutSetCompression; 032import net.tridentsdk.server.packets.play.out.PacketPlayOutDisconnect; 033import net.tridentsdk.server.player.PlayerConnection; 034import net.tridentsdk.server.player.TridentPlayer; 035import net.tridentsdk.util.TridentLogger; 036 037import javax.crypto.Cipher; 038import javax.crypto.SecretKey; 039import javax.crypto.spec.IvParameterSpec; 040import javax.crypto.spec.SecretKeySpec; 041import java.net.InetSocketAddress; 042import java.nio.ByteBuffer; 043import java.security.InvalidAlgorithmParameterException; 044import java.security.InvalidKeyException; 045import java.security.KeyPair; 046import java.security.SecureRandom; 047import java.util.UUID; 048import java.util.concurrent.ConcurrentHashMap; 049import java.util.concurrent.ConcurrentMap; 050 051/** 052 * Handles the connection of a client upon joining 053 * 054 * @author The TridentSDK Team 055 */ 056public class ClientConnection { 057 /** 058 * Map of client connections registered 059 */ 060 protected static final ConcurrentMap<InetSocketAddress, ClientConnection> clientData = 061 new ConcurrentHashMap<>(); 062 063 /** 064 * Random for generating the verification token 065 */ 066 protected static final SecureRandom SR = new SecureRandom(); 067 /** 068 * The RSA cipher used to encrypt client data 069 */ 070 protected final ThreadLocal<Cipher> encryptCipher = new ThreadLocal<Cipher>() { 071 @Override 072 protected Cipher initialValue() { 073 return getCipher(); 074 } 075 }; 076 protected final ThreadLocal<Cipher> decryptCipher = new ThreadLocal<Cipher>() { 077 @Override 078 protected Cipher initialValue() { 079 return getCipher(); 080 } 081 }; 082 083 /* Network fields */ 084 private final Object BARRIER; 085 086 /* Encryption and client data fields */ 087 /** 088 * The client's connection address 089 */ 090 protected InetSocketAddress address; 091 /** 092 * The data channel 093 */ 094 protected Channel channel; 095 /** 096 * The login key pair 097 */ 098 protected volatile KeyPair loginKeyPair; 099 /** 100 * The client stage during login 101 */ 102 protected volatile Protocol.ClientStage stage; 103 /** 104 * Whether or not encryption is enabled for the client 105 */ 106 protected volatile boolean encryptionEnabled; 107 /** 108 * The secret key shared between the client and server 109 */ 110 protected volatile SecretKey sharedSecret; 111 /** 112 * The verification token 113 */ 114 protected volatile byte[] verificationToken; // DO NOT WRITE INDIVIDUAL ELEMENTS TO IT. Consult AgentTroll 115 /** 116 * Whether or not encryption is enabled 117 */ 118 protected volatile boolean compressionEnabled = false; 119 private volatile UUID uuid; 120 private volatile IvParameterSpec ivSpec; 121 122 /** 123 * Creates a new connection handler for the joining channel stream 124 */ 125 protected ClientConnection(Channel channel) { 126 this.address = (InetSocketAddress) channel.remoteAddress(); 127 this.channel = channel; 128 BARRIER = new Object(); 129 130 this.encryptionEnabled = false; 131 this.stage = Protocol.ClientStage.HANDSHAKE; 132 channel.closeFuture().addListener(future -> logout()); 133 channel.pipeline().addLast(new ChannelHandlerAdapter() { 134 @Override 135 public void exceptionCaught(ChannelHandlerContext ctx, Throwable cause) throws Exception { 136 if (cause.getMessage().toLowerCase().contains("connection reset")) { 137 logout(); 138 return; 139 } 140 141 super.exceptionCaught(ctx, cause); 142 } 143 }); 144 } 145 146 protected ClientConnection() { 147 BARRIER = new Object(); 148 } 149 150 private static Cipher getCipher() { 151 try { 152 return Cipher.getInstance("AES/CFB8/NoPadding"); 153 } catch (Exception ex) { 154 TridentLogger.get().error(ex); 155 } 156 157 return null; 158 } 159 160 /** 161 * Checks if an IP address is logged into the server 162 * 163 * @param address the address to check if online 164 * @return {@code true} if the IP is on the server, {@code false} if not 165 */ 166 public static boolean isLoggedIn(InetSocketAddress address) { 167 return clientData.keySet().contains(address); 168 } 169 170 /** 171 * Gets the connection by the IP address 172 * 173 * @param address the IP to lookup the connection handler 174 * @return the instance of the client handler associated with the IP, or {@code null} if not registered 175 */ 176 public static ClientConnection getConnection(InetSocketAddress address) { 177 return clientData.get(address); 178 } 179 180 /** 181 * Gets the connection of a channel handler context 182 * 183 * @param chx the context of which to find the client from 184 * @return the client connection given the handler context, or {@code null} if not registered 185 */ 186 public static ClientConnection connection(ChannelHandlerContext chx) { 187 return getConnection((InetSocketAddress) chx.channel().remoteAddress()); 188 } 189 190 /** 191 * Registers the client channel with a protocol connection wrapper 192 * 193 * @param channel the channel of which the player is connected by 194 * @return the client connection that was registered 195 */ 196 public static ClientConnection registerConnection(final Channel channel) { 197 return clientData.computeIfAbsent((InetSocketAddress) channel.remoteAddress(), 198 (k) -> new ClientConnection(channel)); 199 } 200 201 /** 202 * Sends protocol data through the client stream 203 * 204 * @param packet the packet to send, encoded and written to the stream 205 */ 206 public void sendPacket(Packet packet) { 207 // Create new ByteBuf 208 ByteBuf buffer = this.channel.alloc().buffer(); 209 210 Codec.writeVarInt32(buffer, packet.id()); 211 packet.encode(buffer); 212 TridentLogger.get().debug(packet.getClass().getSimpleName() + " sent"); 213 214 // Write the packet and flush it 215 this.channel.write(buffer); 216 this.channel.flush(); 217 218 if (packet instanceof PacketPlayOutDisconnect 219 || packet instanceof PacketLoginOutDisconnect) { 220 logout(); 221 } 222 } 223 224 /** 225 * Encrypts the given {@code byte} data 226 * 227 * @param data the data to encrypt 228 * @return the encrypted data 229 * @throws Exception if something wrong occurs 230 */ 231 public ByteBuf encrypt(ByteBuf data) throws Exception { 232 ByteBuffer out = ByteBuffer.allocate(data.readableBytes()); 233 234 encryptCipher.get().update(data.nioBuffer(), out); 235 out.flip(); 236 237 return Unpooled.wrappedBuffer(out); 238 } 239 240 /** 241 * Decrypts the given {@code byte} encryption data 242 * 243 * @param data the data to decrypt 244 * @return the decrypted data 245 * @throws Exception if something wrong occurs 246 */ 247 public ByteBuf decrypt(ByteBuf data) throws Exception { 248 ByteBuffer out = ByteBuffer.allocate(data.readableBytes()); 249 250 decryptCipher.get().update(data.nioBuffer(), out); 251 out.flip(); 252 253 return Unpooled.wrappedBuffer(out); 254 } 255 256 /** 257 * Generates the client token and stores it in the {@link #verificationToken} 258 */ 259 public void generateToken() { 260 byte[] localToken = new byte[4]; 261 SR.nextBytes(localToken); 262 this.verificationToken = localToken; 263 } 264 265 /** 266 * Enables client data encryption 267 * 268 * @param secret the client secret to encrypt data with 269 */ 270 public void enableEncryption(byte... secret) { 271 //Makes sure the secret is only set once 272 if (!this.encryptionEnabled) { 273 this.sharedSecret = new SecretKeySpec(secret, "AES"); 274 this.ivSpec = new IvParameterSpec(this.sharedSecret.getEncoded()); 275 this.encryptionEnabled = true; 276 277 try { 278 encryptCipher.get().init(Cipher.ENCRYPT_MODE, sharedSecret, ivSpec); 279 decryptCipher.get().init(Cipher.DECRYPT_MODE, sharedSecret, ivSpec); 280 } catch (InvalidKeyException | InvalidAlgorithmParameterException e) { 281 TridentLogger.get().error(e); 282 } 283 } 284 } 285 286 /** 287 * Allows compression on the server and client 288 */ 289 public void enableCompression() { 290 if (compressionEnabled) { 291 TridentLogger.get().error(new UnsupportedOperationException("Compression is already enabled!")); 292 } 293 294 if (stage != Protocol.ClientStage.LOGIN) { 295 TridentLogger.get().error(new UnsupportedOperationException()); 296 return; 297 } 298 299 sendPacket(new PacketLoginOutSetCompression()); 300 compressionEnabled = true; 301 } 302 303 /** 304 * Sets the UUID of the connection 305 * 306 * @param uuid the uuid of the connection 307 */ 308 @InternalUseOnly 309 public void setUuid(UUID uuid) { 310 this.uuid = uuid; 311 } 312 313 /** 314 * Gets the channel context for the connection stream 315 * 316 * @return the netty channel wrapped by the handler 317 */ 318 public Channel channel() { 319 return this.channel; 320 } 321 322 /** 323 * The IP address of the client handled by this connection wrapper 324 * 325 * @return the handled IP address 326 */ 327 public InetSocketAddress address() { 328 return this.address; 329 } 330 331 /** 332 * Gets the current state of the connection 333 * 334 * @return the current state of the protocol for the client 335 */ 336 public Protocol.ClientStage stage() { 337 return this.stage; 338 } 339 340 /** 341 * Sets the client state, should only be used by the ClientConnectionHandler 342 * 343 * @param stage the state to set the client to 344 */ 345 public void setStage(Protocol.ClientStage stage) { 346 this.stage = stage; 347 } 348 349 /** 350 * Gets the client verification token 351 * 352 * @return the token of which to verify the client 353 */ 354 public byte[] verificationToken() { 355 return this.verificationToken; 356 } 357 358 /** 359 * Whether or not encryption is enabled 360 * 361 * @return {@code true} if encryption is enabled, {@code false} if it is not 362 */ 363 public boolean isEncryptionEnabled() { 364 return this.encryptionEnabled; 365 } 366 367 public boolean isCompressionEnabled() { 368 return this.compressionEnabled; 369 } 370 371 /** 372 * Gets the key pair for client login 373 * 374 * @return the {@link java.security.KeyPair} for the client 375 */ 376 public KeyPair loginKeyPair() { 377 return this.loginKeyPair; 378 } 379 380 /** 381 * Sets the client login key pair 382 * 383 * @param keyPair the key pair used by the client 384 */ 385 public void setLoginKeyPair(KeyPair keyPair) { 386 this.loginKeyPair = keyPair; 387 } 388 389 /** 390 * The protocol login encryption secret 391 * 392 * @return the {@link javax.crypto.SecretKey} shared between the server and client 393 */ 394 public SecretKey sharedSecret() { 395 return this.sharedSecret; 396 } 397 398 /** 399 * Removes the client's server side client handler 400 */ 401 public void logout() { 402 // Don't change the order of this, it is important for thread-safety 403 ClientConnection connection = clientData.remove(this.address); 404 LoginHandler.getInstance().finish(address()); // In case they errored while logging in 405 406 if (connection == null) return; 407 408 Player p = null; 409 if (this instanceof PlayerConnection) { 410 p = ((PlayerConnection) this).player(); 411 } else if (uuid != null) { 412 p = TridentPlayer.getPlayer(uuid); 413 } 414 415 if (p == null) { 416 return; 417 } 418 419 this.channel.close(); 420 421 p.remove(); 422 } 423}