Add commands and server connect framework.

This commit is contained in:
md_5 2012-10-05 12:58:54 +10:00
parent e4cf00280d
commit 1c67b9b9c0
14 changed files with 285 additions and 11 deletions

View File

@ -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);

View File

@ -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);
} }
} }

View File

@ -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());
}
} }

View 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) {
}
}
}
}

View File

@ -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));
} }

View 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);
}
}

View 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;
}
}

View 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;
}
}

View 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();
}
}

View 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);
}

View File

@ -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));
}
}

View File

@ -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);
}
}

View File

@ -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();

View File

@ -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();
}
} }