Add commands and server connect framework.
This commit is contained in:
parent
e4cf00280d
commit
1c67b9b9c0
@ -5,9 +5,17 @@ import java.io.IOException;
|
|||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.net.InetSocketAddress;
|
import java.net.InetSocketAddress;
|
||||||
import java.net.Socket;
|
import java.net.Socket;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
import static net.md_5.bungee.Logger.$;
|
import static net.md_5.bungee.Logger.$;
|
||||||
|
import net.md_5.bungee.command.Command;
|
||||||
|
import net.md_5.bungee.command.CommandEnd;
|
||||||
|
import net.md_5.bungee.command.CommandSender;
|
||||||
|
import net.md_5.bungee.command.ConsoleCommandSender;
|
||||||
|
|
||||||
public class BungeeCord {
|
public class BungeeCord {
|
||||||
|
|
||||||
@ -39,6 +47,18 @@ public class BungeeCord {
|
|||||||
* Current version.
|
* Current version.
|
||||||
*/
|
*/
|
||||||
private String version = (getClass().getPackage().getImplementationVersion() == null) ? "unknown" : getClass().getPackage().getImplementationVersion();
|
private String version = (getClass().getPackage().getImplementationVersion() == null) ? "unknown" : getClass().getPackage().getImplementationVersion();
|
||||||
|
/**
|
||||||
|
* Fully qualified connections.
|
||||||
|
*/
|
||||||
|
public Map<String, UserConnection> connections = new ConcurrentHashMap<>();
|
||||||
|
/**
|
||||||
|
* Registered commands.
|
||||||
|
*/
|
||||||
|
private Map<String, Command> commandMap = new HashMap<>();
|
||||||
|
|
||||||
|
{
|
||||||
|
commandMap.put("end", new CommandEnd());
|
||||||
|
}
|
||||||
|
|
||||||
public static void main(String[] args) throws IOException {
|
public static void main(String[] args) throws IOException {
|
||||||
System.out.println(Util.hex(15));
|
System.out.println(Util.hex(15));
|
||||||
@ -50,13 +70,34 @@ public class BungeeCord {
|
|||||||
while (instance.isRunning) {
|
while (instance.isRunning) {
|
||||||
String line = br.readLine();
|
String line = br.readLine();
|
||||||
if (line != null) {
|
if (line != null) {
|
||||||
if (line.equals("end")) {
|
boolean handled = instance.dispatchCommand(line, ConsoleCommandSender.instance);
|
||||||
instance.stop();
|
if (!handled) {
|
||||||
|
System.err.println("Command not found");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public boolean dispatchCommand(String commandLine, CommandSender sender) {
|
||||||
|
String[] split = commandLine.trim().split(" ");
|
||||||
|
String commandName = split[0].toLowerCase();
|
||||||
|
if (commandMap.containsKey(commandName)) {
|
||||||
|
String[] args = Arrays.copyOfRange(split, 1, split.length);
|
||||||
|
Command c = commandMap.get(commandName);
|
||||||
|
try {
|
||||||
|
c.execute(sender, args);
|
||||||
|
} catch (Exception ex) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "An error occurred while executing this command!");
|
||||||
|
System.err.println("----------------------- [Start of command error] -----------------------");
|
||||||
|
ex.printStackTrace();
|
||||||
|
System.err.println("----------------------- [End of command error] -----------------------");
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
} else {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
public void start() throws IOException {
|
public void start() throws IOException {
|
||||||
config.load();
|
config.load();
|
||||||
isRunning = true;
|
isRunning = true;
|
||||||
@ -83,8 +124,10 @@ public class BungeeCord {
|
|||||||
$().info("Closing pending connections");
|
$().info("Closing pending connections");
|
||||||
threadPool.shutdown();
|
threadPool.shutdown();
|
||||||
|
|
||||||
$().info("Disconnecting " + "x" + " connections");
|
$().info("Disconnecting " + connections.size() + " connections");
|
||||||
// TODO: Kick everyone
|
for (UserConnection user : connections.values()) {
|
||||||
|
user.disconnect("Proxy restarting, brb.");
|
||||||
|
}
|
||||||
|
|
||||||
$().info("Saving reconnect locations");
|
$().info("Saving reconnect locations");
|
||||||
saveThread.interrupt();
|
saveThread.interrupt();
|
||||||
@ -96,10 +139,6 @@ public class BungeeCord {
|
|||||||
$().info("Thank you and goodbye");
|
$().info("Thank you and goodbye");
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getOnlinePlayers() {
|
|
||||||
return 123;
|
|
||||||
}
|
|
||||||
|
|
||||||
public void setSocketOptions(Socket socket) throws IOException {
|
public void setSocketOptions(Socket socket) throws IOException {
|
||||||
socket.setSoTimeout(config.timeout);
|
socket.setSoTimeout(config.timeout);
|
||||||
socket.setTrafficClass(0x18);
|
socket.setTrafficClass(0x18);
|
||||||
|
@ -92,6 +92,10 @@ public class Configuration {
|
|||||||
add("mbaxter");
|
add("mbaxter");
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
/**
|
||||||
|
* Maximum number of lines to log before old ones are removed.
|
||||||
|
*/
|
||||||
|
public int logNumLines = 1 << 14;
|
||||||
|
|
||||||
public void load() {
|
public void load() {
|
||||||
try {
|
try {
|
||||||
@ -124,7 +128,6 @@ public class Configuration {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
$().info("-----------------------------------------------------");
|
$().info("-----------------------------------------------------");
|
||||||
|
|
||||||
if (servers.get(defaultServerName) == null) {
|
if (servers.get(defaultServerName) == null) {
|
||||||
@ -190,7 +193,7 @@ public class Configuration {
|
|||||||
if (hostline != null) {
|
if (hostline != null) {
|
||||||
return Util.getAddr(hostline);
|
return Util.getAddr(hostline);
|
||||||
} else {
|
} else {
|
||||||
return null;
|
throw new IllegalArgumentException("No server by name " + name);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -6,15 +6,24 @@ import java.io.InputStreamReader;
|
|||||||
import java.math.BigInteger;
|
import java.math.BigInteger;
|
||||||
import java.net.URL;
|
import java.net.URL;
|
||||||
import java.net.URLEncoder;
|
import java.net.URLEncoder;
|
||||||
|
import java.security.InvalidKeyException;
|
||||||
import java.security.Key;
|
import java.security.Key;
|
||||||
|
import java.security.KeyFactory;
|
||||||
import java.security.KeyPair;
|
import java.security.KeyPair;
|
||||||
import java.security.KeyPairGenerator;
|
import java.security.KeyPairGenerator;
|
||||||
import java.security.MessageDigest;
|
import java.security.MessageDigest;
|
||||||
import java.security.NoSuchAlgorithmException;
|
import java.security.NoSuchAlgorithmException;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import java.security.SecureRandom;
|
||||||
import java.security.Security;
|
import java.security.Security;
|
||||||
|
import java.security.spec.InvalidKeySpecException;
|
||||||
|
import java.security.spec.X509EncodedKeySpec;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Random;
|
import java.util.Random;
|
||||||
|
import javax.crypto.BadPaddingException;
|
||||||
import javax.crypto.Cipher;
|
import javax.crypto.Cipher;
|
||||||
|
import javax.crypto.IllegalBlockSizeException;
|
||||||
|
import javax.crypto.NoSuchPaddingException;
|
||||||
import javax.crypto.SecretKey;
|
import javax.crypto.SecretKey;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
|
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
|
||||||
@ -28,6 +37,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
|||||||
|
|
||||||
public class EncryptionUtil {
|
public class EncryptionUtil {
|
||||||
|
|
||||||
|
private static final Random secure = new SecureRandom();
|
||||||
private static final Random random = new Random();
|
private static final Random random = new Random();
|
||||||
private static KeyPair keys;
|
private static KeyPair keys;
|
||||||
|
|
||||||
@ -86,4 +96,26 @@ public class EncryptionUtil {
|
|||||||
cip.init(forEncryption, new ParametersWithIV(new KeyParameter(shared.getEncoded()), shared.getEncoded(), 0, 16));
|
cip.init(forEncryption, new ParametersWithIV(new KeyParameter(shared.getEncoded()), shared.getEncoded(), 0, 16));
|
||||||
return cip;
|
return cip;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static SecretKey getSecret() {
|
||||||
|
byte[] rand = new byte[32];
|
||||||
|
secure.nextBytes(rand);
|
||||||
|
return new SecretKeySpec(rand, "AES");
|
||||||
|
}
|
||||||
|
|
||||||
|
public static PublicKey getPubkey(PacketFDEncryptionRequest request) throws InvalidKeySpecException, NoSuchAlgorithmException {
|
||||||
|
return KeyFactory.getInstance("RSA").generatePublic(new X509EncodedKeySpec(request.publicKey));
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] encrypt(Key key, byte[] b) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
|
||||||
|
Cipher hasher = Cipher.getInstance("RSA");
|
||||||
|
hasher.init(Cipher.ENCRYPT_MODE, key);
|
||||||
|
return hasher.doFinal(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static byte[] getShared(SecretKey key, PublicKey pubkey) throws BadPaddingException, IllegalBlockSizeException, InvalidKeyException, NoSuchAlgorithmException, NoSuchPaddingException {
|
||||||
|
Cipher cipher = Cipher.getInstance("RSA");
|
||||||
|
cipher.init(Cipher.ENCRYPT_MODE, pubkey);
|
||||||
|
return cipher.doFinal(key.getEncoded());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
31
src/main/java/net/md_5/bungee/GenericConnection.java
Normal file
31
src/main/java/net/md_5/bungee/GenericConnection.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package net.md_5.bungee;
|
||||||
|
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.RequiredArgsConstructor;
|
||||||
|
import net.md_5.bungee.packet.PacketFFKick;
|
||||||
|
import net.md_5.bungee.packet.PacketInputStream;
|
||||||
|
|
||||||
|
@EqualsAndHashCode
|
||||||
|
@RequiredArgsConstructor
|
||||||
|
public class GenericConnection {
|
||||||
|
|
||||||
|
private final Socket socket;
|
||||||
|
private final PacketInputStream in;
|
||||||
|
private final OutputStream out;
|
||||||
|
|
||||||
|
public void disconnect(String reason) {
|
||||||
|
try {
|
||||||
|
out.write(new PacketFFKick(reason).getPacket());
|
||||||
|
} catch (IOException ex) {
|
||||||
|
} finally {
|
||||||
|
try {
|
||||||
|
out.flush();
|
||||||
|
socket.close();
|
||||||
|
} catch (IOException ioe) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -53,7 +53,7 @@ public class InitialHandler implements Runnable {
|
|||||||
|
|
||||||
break;
|
break;
|
||||||
case 0xFE:
|
case 0xFE:
|
||||||
throw new KickException(BungeeCord.instance.config.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.getOnlinePlayers() + ChatColor.COLOR_CHAR + BungeeCord.instance.config.maxPlayers);
|
throw new KickException(BungeeCord.instance.config.motd + ChatColor.COLOR_CHAR + BungeeCord.instance.connections.size() + ChatColor.COLOR_CHAR + BungeeCord.instance.config.maxPlayers);
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException("Wasn't ready for packet id " + Util.hex(id));
|
throw new IllegalArgumentException("Wasn't ready for packet id " + Util.hex(id));
|
||||||
}
|
}
|
||||||
|
55
src/main/java/net/md_5/bungee/ServerConnection.java
Normal file
55
src/main/java/net/md_5/bungee/ServerConnection.java
Normal file
@ -0,0 +1,55 @@
|
|||||||
|
package net.md_5.bungee;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.InetSocketAddress;
|
||||||
|
import java.net.Socket;
|
||||||
|
import java.security.PublicKey;
|
||||||
|
import javax.crypto.SecretKey;
|
||||||
|
import net.md_5.bungee.packet.Packet1Login;
|
||||||
|
import net.md_5.bungee.packet.Packet2Handshake;
|
||||||
|
import net.md_5.bungee.packet.PacketCDClientStatus;
|
||||||
|
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
|
||||||
|
import net.md_5.bungee.packet.PacketFDEncryptionRequest;
|
||||||
|
import net.md_5.bungee.packet.PacketInputStream;
|
||||||
|
import org.bouncycastle.crypto.io.CipherInputStream;
|
||||||
|
import org.bouncycastle.crypto.io.CipherOutputStream;
|
||||||
|
|
||||||
|
public class ServerConnection extends GenericConnection {
|
||||||
|
|
||||||
|
private final Packet1Login loginPacket;
|
||||||
|
|
||||||
|
public ServerConnection(Socket socket, PacketInputStream in, OutputStream out, Packet1Login loginPacket) {
|
||||||
|
super(socket, in, out);
|
||||||
|
this.loginPacket = loginPacket;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ServerConnection connect(InetSocketAddress address, Packet2Handshake handshake) throws Exception {
|
||||||
|
Socket socket = new Socket();
|
||||||
|
socket.connect(address, BungeeCord.instance.config.timeout);
|
||||||
|
BungeeCord.instance.setSocketOptions(socket);
|
||||||
|
|
||||||
|
PacketInputStream in = new PacketInputStream(socket.getInputStream());
|
||||||
|
OutputStream out = socket.getOutputStream();
|
||||||
|
|
||||||
|
out.write(handshake.getPacket());
|
||||||
|
PacketFDEncryptionRequest encryptRequest = new PacketFDEncryptionRequest(in.readPacket());
|
||||||
|
|
||||||
|
SecretKey myKey = EncryptionUtil.getSecret();
|
||||||
|
PublicKey pub = EncryptionUtil.getPubkey(encryptRequest);
|
||||||
|
|
||||||
|
PacketFCEncryptionResponse response = new PacketFCEncryptionResponse(EncryptionUtil.getShared(myKey, pub), EncryptionUtil.encrypt(pub, encryptRequest.verifyToken));
|
||||||
|
|
||||||
|
int ciphId = Util.getId(in.readPacket());
|
||||||
|
if (ciphId != 0xFC) {
|
||||||
|
throw new RuntimeException("Server did not send encryption enable");
|
||||||
|
}
|
||||||
|
|
||||||
|
in = new PacketInputStream(new CipherInputStream(socket.getInputStream(), EncryptionUtil.getCipher(false, myKey)));
|
||||||
|
out = new CipherOutputStream(out, EncryptionUtil.getCipher(true, myKey));
|
||||||
|
|
||||||
|
out.write(new PacketCDClientStatus((byte) 0).getPacket());
|
||||||
|
Packet1Login login = new Packet1Login(in.readPacket());
|
||||||
|
|
||||||
|
return new ServerConnection(socket, in, out, login);
|
||||||
|
}
|
||||||
|
}
|
16
src/main/java/net/md_5/bungee/UserConnection.java
Normal file
16
src/main/java/net/md_5/bungee/UserConnection.java
Normal file
@ -0,0 +1,16 @@
|
|||||||
|
package net.md_5.bungee;
|
||||||
|
|
||||||
|
import java.io.OutputStream;
|
||||||
|
import java.net.Socket;
|
||||||
|
import net.md_5.bungee.packet.Packet2Handshake;
|
||||||
|
import net.md_5.bungee.packet.PacketInputStream;
|
||||||
|
|
||||||
|
public class UserConnection extends GenericConnection {
|
||||||
|
|
||||||
|
private final Packet2Handshake handshake;
|
||||||
|
|
||||||
|
public UserConnection(Socket socket, PacketInputStream in, OutputStream out, Packet2Handshake handshake) {
|
||||||
|
super(socket, in, out);
|
||||||
|
this.handshake = handshake;
|
||||||
|
}
|
||||||
|
}
|
31
src/main/java/net/md_5/bungee/command/Command.java
Normal file
31
src/main/java/net/md_5/bungee/command/Command.java
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
package net.md_5.bungee.command;
|
||||||
|
|
||||||
|
import net.md_5.bungee.ChatColor;
|
||||||
|
|
||||||
|
public abstract class Command {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Execute this command.
|
||||||
|
*
|
||||||
|
* @param sender the sender executing this command
|
||||||
|
* @param args the parameters to this command, does not include the '/' or
|
||||||
|
* the original command.
|
||||||
|
*/
|
||||||
|
public abstract void execute(CommandSender sender, String[] args);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Check if the arg count is at least the specified amount
|
||||||
|
*
|
||||||
|
* @param sender sender to send message to if unsuccessful
|
||||||
|
* @param args to check
|
||||||
|
* @param count to compare
|
||||||
|
* @return if the arguments are valid
|
||||||
|
*/
|
||||||
|
public final boolean testArgs(CommandSender sender, String[] args, int count) {
|
||||||
|
boolean valid = args.length >= count;
|
||||||
|
if (!valid) {
|
||||||
|
sender.sendMessage(ChatColor.RED + "Please review your argument count");
|
||||||
|
}
|
||||||
|
return valid;
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/net/md_5/bungee/command/CommandEnd.java
Normal file
11
src/main/java/net/md_5/bungee/command/CommandEnd.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package net.md_5.bungee.command;
|
||||||
|
|
||||||
|
import net.md_5.bungee.BungeeCord;
|
||||||
|
|
||||||
|
public class CommandEnd extends Command {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void execute(CommandSender sender, String[] args) {
|
||||||
|
BungeeCord.instance.stop();
|
||||||
|
}
|
||||||
|
}
|
11
src/main/java/net/md_5/bungee/command/CommandSender.java
Normal file
11
src/main/java/net/md_5/bungee/command/CommandSender.java
Normal file
@ -0,0 +1,11 @@
|
|||||||
|
package net.md_5.bungee.command;
|
||||||
|
|
||||||
|
public interface CommandSender {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sends a message to the client at the earliest available opportunity.
|
||||||
|
*
|
||||||
|
* @param message the message to send
|
||||||
|
*/
|
||||||
|
public abstract void sendMessage(String message);
|
||||||
|
}
|
@ -0,0 +1,13 @@
|
|||||||
|
package net.md_5.bungee.command;
|
||||||
|
|
||||||
|
import net.md_5.bungee.ChatColor;
|
||||||
|
|
||||||
|
public class ConsoleCommandSender implements CommandSender {
|
||||||
|
|
||||||
|
public static final ConsoleCommandSender instance = new ConsoleCommandSender();
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sendMessage(String message) {
|
||||||
|
System.out.println(ChatColor.stripColor(message));
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,19 @@
|
|||||||
|
package net.md_5.bungee.packet;
|
||||||
|
|
||||||
|
import lombok.EqualsAndHashCode;
|
||||||
|
import lombok.ToString;
|
||||||
|
|
||||||
|
@ToString
|
||||||
|
@EqualsAndHashCode(callSuper = false)
|
||||||
|
public class PacketCDClientStatus extends DefinedPacket {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sent from the client to the server upon respawn,
|
||||||
|
*
|
||||||
|
* @param payload 0 if initial spawn, 1 if respawn after death.
|
||||||
|
*/
|
||||||
|
public PacketCDClientStatus(byte payload) {
|
||||||
|
super(0xCD);
|
||||||
|
super.writeByte(payload);
|
||||||
|
}
|
||||||
|
}
|
@ -16,6 +16,12 @@ public class PacketFCEncryptionResponse extends DefinedPacket {
|
|||||||
writeArray(new byte[0]);
|
writeArray(new byte[0]);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PacketFCEncryptionResponse(byte[] sharedSecret, byte[] verifyToken) {
|
||||||
|
super(0xFC);
|
||||||
|
writeArray(sharedSecret);
|
||||||
|
writeArray(verifyToken);
|
||||||
|
}
|
||||||
|
|
||||||
public PacketFCEncryptionResponse(byte[] buf) {
|
public PacketFCEncryptionResponse(byte[] buf) {
|
||||||
super(0xFC, buf);
|
super(0xFC, buf);
|
||||||
this.sharedSecret = readArray();
|
this.sharedSecret = readArray();
|
||||||
|
@ -20,4 +20,11 @@ public class PacketFDEncryptionRequest extends DefinedPacket {
|
|||||||
this.publicKey = publicKey;
|
this.publicKey = publicKey;
|
||||||
this.verifyToken = verifyToken;
|
this.verifyToken = verifyToken;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public PacketFDEncryptionRequest(byte[] buf) {
|
||||||
|
super(0xFD, buf);
|
||||||
|
serverId = readUTF();
|
||||||
|
publicKey = readArray();
|
||||||
|
verifyToken = readArray();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
Reference in New Issue
Block a user