package net.minecraft.network.rcon;
import java.io.IOException;
import java.net.DatagramPacket;
import java.net.DatagramSocket;
import java.net.InetAddress;
import java.net.PortUnreachableException;
import java.net.SocketAddress;
import java.net.SocketException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.Date;
import java.util.HashMap;
import java.util.Iterator;
import java.util.Map;
import java.util.Map.Entry;
public class RConThreadQuery extends RConThreadBase
{
/** The time of the last client auth check */
private long lastAuthCheckTime;
/** The RCon query port */
private int queryPort;
/** Port the server is running on */
private int serverPort;
/** The maximum number of players allowed on the server */
private int maxPlayers;
/** The current server message of the day */
private String serverMotd;
/** The name of the currently loaded world */
private String worldName;
/** The remote socket querying the server */
private DatagramSocket querySocket = null;
/** A buffer for incoming DatagramPackets */
private byte[] buffer = new byte[1460];
/** Storage for incoming DatagramPackets */
private DatagramPacket incomingPacket = null;
private Map field_72644_p;
/** The hostname of this query server */
private String queryHostname;
/** The hostname of the running server */
private String serverHostname;
/** A map of SocketAddress objects to RConThreadQueryAuth objects */
private Map queryClients;
/**
* The time that this RConThreadQuery was constructed, from (new Date()).getTime()
*/
private long time;
/** The RConQuery output stream */
private RConOutputStream output;
/** The time of the last query response sent */
private long lastQueryResponseTime;
public RConThreadQuery(IServer par1IServer)
{
super(par1IServer);
this.queryPort = par1IServer.getIntProperty("query.port", 0);
this.serverHostname = par1IServer.getHostname();
this.serverPort = par1IServer.getPort();
this.serverMotd = par1IServer.getServerMOTD();
this.maxPlayers = par1IServer.getMaxPlayers();
this.worldName = par1IServer.getFolderName();
this.lastQueryResponseTime = 0L;
this.queryHostname = "0.0.0.0";
if (0 != this.serverHostname.length() && !this.queryHostname.equals(this.serverHostname))
{
this.queryHostname = this.serverHostname;
}
else
{
this.serverHostname = "0.0.0.0";
try
{
InetAddress inetaddress = InetAddress.getLocalHost();
this.queryHostname = inetaddress.getHostAddress();
}
catch (UnknownHostException unknownhostexception)
{
this.logWarning("Unable to determine local host IP, please set server-ip in \'" + par1IServer.getSettingsFilename() + "\' : " + unknownhostexception.getMessage());
}
}
if (0 == this.queryPort)
{
this.queryPort = this.serverPort;
this.logInfo("Setting default query port to " + this.queryPort);
par1IServer.setProperty("query.port", Integer.valueOf(this.queryPort));
par1IServer.setProperty("debug", Boolean.valueOf(false));
par1IServer.saveProperties();
}
this.field_72644_p = new HashMap();
this.output = new RConOutputStream(1460);
this.queryClients = new HashMap();
this.time = (new Date()).getTime();
}
/**
* Sends a byte array as a DatagramPacket response to the client who sent the given DatagramPacket
*/
private void sendResponsePacket(byte[] par1ArrayOfByte, DatagramPacket par2DatagramPacket) throws IOException
{
this.querySocket.send(new DatagramPacket(par1ArrayOfByte, par1ArrayOfByte.length, par2DatagramPacket.getSocketAddress()));
}
/**
* Parses an incoming DatagramPacket, returning true if the packet was valid
*/
private boolean parseIncomingPacket(DatagramPacket par1DatagramPacket) throws IOException
{
byte[] abyte = par1DatagramPacket.getData();
int i = par1DatagramPacket.getLength();
SocketAddress socketaddress = par1DatagramPacket.getSocketAddress();
this.logDebug("Packet len " + i + " [" + socketaddress + "]");
if (3 <= i && -2 == abyte[0] && -3 == abyte[1])
{
this.logDebug("Packet \'" + RConUtils.getByteAsHexString(abyte[2]) + "\' [" + socketaddress + "]");
switch (abyte[2])
{
case 0:
if (!this.verifyClientAuth(par1DatagramPacket).booleanValue())
{
this.logDebug("Invalid challenge [" + socketaddress + "]");
return false;
}
else if (15 == i)
{
this.sendResponsePacket(this.createQueryResponse(par1DatagramPacket), par1DatagramPacket);
this.logDebug("Rules [" + socketaddress + "]");
}
else
{
RConOutputStream rconoutputstream = new RConOutputStream(1460);
rconoutputstream.writeInt(0);
rconoutputstream.writeByteArray(this.getRequestID(par1DatagramPacket.getSocketAddress()));
rconoutputstream.writeString(this.serverMotd);
rconoutputstream.writeString("SMP");
rconoutputstream.writeString(this.worldName);
rconoutputstream.writeString(Integer.toString(this.getNumberOfPlayers()));
rconoutputstream.writeString(Integer.toString(this.maxPlayers));
rconoutputstream.writeShort((short)this.serverPort);
rconoutputstream.writeString(this.queryHostname);
this.sendResponsePacket(rconoutputstream.toByteArray(), par1DatagramPacket);
this.logDebug("Status [" + socketaddress + "]");
}
case 9:
this.sendAuthChallenge(par1DatagramPacket);
this.logDebug("Challenge [" + socketaddress + "]");
return true;
default:
return true;
}
}
else
{
this.logDebug("Invalid packet [" + socketaddress + "]");
return false;
}
}
/**
* Creates a query response as a byte array for the specified query DatagramPacket
*/
private byte[] createQueryResponse(DatagramPacket par1DatagramPacket) throws IOException
{
long i = System.currentTimeMillis();
if (i < this.lastQueryResponseTime + 5000L)
{
byte[] abyte = this.output.toByteArray();
byte[] abyte1 = this.getRequestID(par1DatagramPacket.getSocketAddress());
abyte[1] = abyte1[0];
abyte[2] = abyte1[1];
abyte[3] = abyte1[2];
abyte[4] = abyte1[3];
return abyte;
}
else
{
this.lastQueryResponseTime = i;
this.output.reset();
this.output.writeInt(0);
this.output.writeByteArray(this.getRequestID(par1DatagramPacket.getSocketAddress()));
this.output.writeString("splitnum");
this.output.writeInt(128);
this.output.writeInt(0);
this.output.writeString("hostname");
this.output.writeString(this.serverMotd);
this.output.writeString("gametype");
this.output.writeString("SMP");
this.output.writeString("game_id");
this.output.writeString("MINECRAFT");
this.output.writeString("version");
this.output.writeString(this.server.getMinecraftVersion());
this.output.writeString("plugins");
this.output.writeString(this.server.getPlugins());
this.output.writeString("map");
this.output.writeString(this.worldName);
this.output.writeString("numplayers");
this.output.writeString("" + this.getNumberOfPlayers());
this.output.writeString("maxplayers");
this.output.writeString("" + this.maxPlayers);
this.output.writeString("hostport");
this.output.writeString("" + this.serverPort);
this.output.writeString("hostip");
this.output.writeString(this.queryHostname);
this.output.writeInt(0);
this.output.writeInt(1);
this.output.writeString("player_");
this.output.writeInt(0);
String[] astring = this.server.getAllUsernames();
byte b0 = (byte)astring.length;
for (byte b1 = (byte)(b0 - 1); b1 >= 0; --b1)
{
this.output.writeString(astring[b1]);
}
this.output.writeInt(0);
return this.output.toByteArray();
}
}
/**
* Returns the request ID provided by the authorized client
*/
private byte[] getRequestID(SocketAddress par1SocketAddress)
{
return ((RConThreadQueryAuth)this.queryClients.get(par1SocketAddress)).getRequestId();
}
/**
* Returns true if the client has a valid auth, otherwise false
*/
private Boolean verifyClientAuth(DatagramPacket par1DatagramPacket)
{
SocketAddress socketaddress = par1DatagramPacket.getSocketAddress();
if (!this.queryClients.containsKey(socketaddress))
{
return Boolean.valueOf(false);
}
else
{
byte[] abyte = par1DatagramPacket.getData();
return ((RConThreadQueryAuth)this.queryClients.get(socketaddress)).getRandomChallenge() != RConUtils.getBytesAsBEint(abyte, 7, par1DatagramPacket.getLength()) ? Boolean.valueOf(false) : Boolean.valueOf(true);
}
}
/**
* Sends an auth challenge DatagramPacket to the client and adds the client to the queryClients map
*/
private void sendAuthChallenge(DatagramPacket par1DatagramPacket) throws IOException
{
RConThreadQueryAuth rconthreadqueryauth = new RConThreadQueryAuth(this, par1DatagramPacket);
this.queryClients.put(par1DatagramPacket.getSocketAddress(), rconthreadqueryauth);
this.sendResponsePacket(rconthreadqueryauth.getChallengeValue(), par1DatagramPacket);
}
/**
* Removes all clients whose auth is no longer valid
*/
private void cleanQueryClientsMap()
{
if (this.running)
{
long i = System.currentTimeMillis();
if (i >= this.lastAuthCheckTime + 30000L)
{
this.lastAuthCheckTime = i;
Iterator iterator = this.queryClients.entrySet().iterator();
while (iterator.hasNext())
{
Entry entry = (Entry)iterator.next();
if (((RConThreadQueryAuth)entry.getValue()).hasExpired(i).booleanValue())
{
iterator.remove();
}
}
}
}
}
public void run()
{
this.logInfo("Query running on " + this.serverHostname + ":" + this.queryPort);
this.lastAuthCheckTime = System.currentTimeMillis();
this.incomingPacket = new DatagramPacket(this.buffer, this.buffer.length);
try
{
while (this.running)
{
try
{
this.querySocket.receive(this.incomingPacket);
this.cleanQueryClientsMap();
this.parseIncomingPacket(this.incomingPacket);
}
catch (SocketTimeoutException sockettimeoutexception)
{
this.cleanQueryClientsMap();
}
catch (PortUnreachableException portunreachableexception)
{
;
}
catch (IOException ioexception)
{
this.stopWithException(ioexception);
}
}
}
finally
{
this.closeAllSockets();
}
}
/**
* Creates a new Thread object from this class and starts running
*/
public void startThread()
{
if (!this.running)
{
if (0 < this.queryPort && 65535 >= this.queryPort)
{
if (this.initQuerySystem())
{
super.startThread();
}
}
else
{
this.logWarning("Invalid query port " + this.queryPort + " found in \'" + this.server.getSettingsFilename() + "\' (queries disabled)");
}
}
}
/**
* Stops the query server and reports the given Exception
*/
private void stopWithException(Exception par1Exception)
{
if (this.running)
{
this.logWarning("Unexpected exception, buggy JRE? (" + par1Exception.toString() + ")");
if (!this.initQuerySystem())
{
this.logSevere("Failed to recover from buggy JRE, shutting down!");
this.running = false;
}
}
}
/**
* Initializes the query system by binding it to a port
*/
private boolean initQuerySystem()
{
try
{
this.querySocket = new DatagramSocket(this.queryPort, InetAddress.getByName(this.serverHostname));
this.registerSocket(this.querySocket);
this.querySocket.setSoTimeout(500);
return true;
}
catch (SocketException socketexception)
{
this.logWarning("Unable to initialise query system on " + this.serverHostname + ":" + this.queryPort + " (Socket): " + socketexception.getMessage());
}
catch (UnknownHostException unknownhostexception)
{
this.logWarning("Unable to initialise query system on " + this.serverHostname + ":" + this.queryPort + " (Unknown Host): " + unknownhostexception.getMessage());
}
catch (Exception exception)
{
this.logWarning("Unable to initialise query system on " + this.serverHostname + ":" + this.queryPort + " (E): " + exception.getMessage());
}
return false;
}
}