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.net.InetSocketAddress;
|
||||
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.Executors;
|
||||
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 {
|
||||
|
||||
@ -39,6 +47,18 @@ public class BungeeCord {
|
||||
* Current version.
|
||||
*/
|
||||
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 {
|
||||
System.out.println(Util.hex(15));
|
||||
@ -50,13 +70,34 @@ public class BungeeCord {
|
||||
while (instance.isRunning) {
|
||||
String line = br.readLine();
|
||||
if (line != null) {
|
||||
if (line.equals("end")) {
|
||||
instance.stop();
|
||||
boolean handled = instance.dispatchCommand(line, ConsoleCommandSender.instance);
|
||||
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 {
|
||||
config.load();
|
||||
isRunning = true;
|
||||
@ -83,8 +124,10 @@ public class BungeeCord {
|
||||
$().info("Closing pending connections");
|
||||
threadPool.shutdown();
|
||||
|
||||
$().info("Disconnecting " + "x" + " connections");
|
||||
// TODO: Kick everyone
|
||||
$().info("Disconnecting " + connections.size() + " connections");
|
||||
for (UserConnection user : connections.values()) {
|
||||
user.disconnect("Proxy restarting, brb.");
|
||||
}
|
||||
|
||||
$().info("Saving reconnect locations");
|
||||
saveThread.interrupt();
|
||||
@ -96,10 +139,6 @@ public class BungeeCord {
|
||||
$().info("Thank you and goodbye");
|
||||
}
|
||||
|
||||
public int getOnlinePlayers() {
|
||||
return 123;
|
||||
}
|
||||
|
||||
public void setSocketOptions(Socket socket) throws IOException {
|
||||
socket.setSoTimeout(config.timeout);
|
||||
socket.setTrafficClass(0x18);
|
||||
|
@ -92,6 +92,10 @@ public class Configuration {
|
||||
add("mbaxter");
|
||||
}
|
||||
};
|
||||
/**
|
||||
* Maximum number of lines to log before old ones are removed.
|
||||
*/
|
||||
public int logNumLines = 1 << 14;
|
||||
|
||||
public void load() {
|
||||
try {
|
||||
@ -124,7 +128,6 @@ public class Configuration {
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
$().info("-----------------------------------------------------");
|
||||
|
||||
if (servers.get(defaultServerName) == null) {
|
||||
@ -190,7 +193,7 @@ public class Configuration {
|
||||
if (hostline != null) {
|
||||
return Util.getAddr(hostline);
|
||||
} 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.net.URL;
|
||||
import java.net.URLEncoder;
|
||||
import java.security.InvalidKeyException;
|
||||
import java.security.Key;
|
||||
import java.security.KeyFactory;
|
||||
import java.security.KeyPair;
|
||||
import java.security.KeyPairGenerator;
|
||||
import java.security.MessageDigest;
|
||||
import java.security.NoSuchAlgorithmException;
|
||||
import java.security.PublicKey;
|
||||
import java.security.SecureRandom;
|
||||
import java.security.Security;
|
||||
import java.security.spec.InvalidKeySpecException;
|
||||
import java.security.spec.X509EncodedKeySpec;
|
||||
import java.util.Arrays;
|
||||
import java.util.Random;
|
||||
import javax.crypto.BadPaddingException;
|
||||
import javax.crypto.Cipher;
|
||||
import javax.crypto.IllegalBlockSizeException;
|
||||
import javax.crypto.NoSuchPaddingException;
|
||||
import javax.crypto.SecretKey;
|
||||
import javax.crypto.spec.SecretKeySpec;
|
||||
import net.md_5.bungee.packet.PacketFCEncryptionResponse;
|
||||
@ -28,6 +37,7 @@ import org.bouncycastle.jce.provider.BouncyCastleProvider;
|
||||
|
||||
public class EncryptionUtil {
|
||||
|
||||
private static final Random secure = new SecureRandom();
|
||||
private static final Random random = new Random();
|
||||
private static KeyPair keys;
|
||||
|
||||
@ -86,4 +96,26 @@ public class EncryptionUtil {
|
||||
cip.init(forEncryption, new ParametersWithIV(new KeyParameter(shared.getEncoded()), shared.getEncoded(), 0, 16));
|
||||
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;
|
||||
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:
|
||||
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]);
|
||||
}
|
||||
|
||||
public PacketFCEncryptionResponse(byte[] sharedSecret, byte[] verifyToken) {
|
||||
super(0xFC);
|
||||
writeArray(sharedSecret);
|
||||
writeArray(verifyToken);
|
||||
}
|
||||
|
||||
public PacketFCEncryptionResponse(byte[] buf) {
|
||||
super(0xFC, buf);
|
||||
this.sharedSecret = readArray();
|
||||
|
@ -20,4 +20,11 @@ public class PacketFDEncryptionRequest extends DefinedPacket {
|
||||
this.publicKey = publicKey;
|
||||
this.verifyToken = verifyToken;
|
||||
}
|
||||
|
||||
public PacketFDEncryptionRequest(byte[] buf) {
|
||||
super(0xFD, buf);
|
||||
serverId = readUTF();
|
||||
publicKey = readArray();
|
||||
verifyToken = readArray();
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user