001 package net.minecraft.network.rcon; 002 003 import java.io.IOException; 004 import java.net.DatagramPacket; 005 import java.net.DatagramSocket; 006 import java.net.InetAddress; 007 import java.net.PortUnreachableException; 008 import java.net.SocketAddress; 009 import java.net.SocketException; 010 import java.net.SocketTimeoutException; 011 import java.net.UnknownHostException; 012 import java.util.Date; 013 import java.util.HashMap; 014 import java.util.Iterator; 015 import java.util.Map; 016 import java.util.Map.Entry; 017 018 public class RConThreadQuery extends RConThreadBase 019 { 020 /** The time of the last client auth check */ 021 private long lastAuthCheckTime; 022 023 /** The RCon query port */ 024 private int queryPort; 025 026 /** Port the server is running on */ 027 private int serverPort; 028 029 /** The maximum number of players allowed on the server */ 030 private int maxPlayers; 031 032 /** The current server message of the day */ 033 private String serverMotd; 034 035 /** The name of the currently loaded world */ 036 private String worldName; 037 038 /** The remote socket querying the server */ 039 private DatagramSocket querySocket = null; 040 041 /** A buffer for incoming DatagramPackets */ 042 private byte[] buffer = new byte[1460]; 043 044 /** Storage for incoming DatagramPackets */ 045 private DatagramPacket incomingPacket = null; 046 private Map field_72644_p; 047 048 /** The hostname of this query server */ 049 private String queryHostname; 050 051 /** The hostname of the running server */ 052 private String serverHostname; 053 054 /** A map of SocketAddress objects to RConThreadQueryAuth objects */ 055 private Map queryClients; 056 057 /** 058 * The time that this RConThreadQuery was constructed, from (new Date()).getTime() 059 */ 060 private long time; 061 062 /** The RConQuery output stream */ 063 private RConOutputStream output; 064 065 /** The time of the last query response sent */ 066 private long lastQueryResponseTime; 067 068 public RConThreadQuery(IServer par1IServer) 069 { 070 super(par1IServer); 071 this.queryPort = par1IServer.getIntProperty("query.port", 0); 072 this.serverHostname = par1IServer.getHostname(); 073 this.serverPort = par1IServer.getPort(); 074 this.serverMotd = par1IServer.getServerMOTD(); 075 this.maxPlayers = par1IServer.getMaxPlayers(); 076 this.worldName = par1IServer.getFolderName(); 077 this.lastQueryResponseTime = 0L; 078 this.queryHostname = "0.0.0.0"; 079 080 if (0 != this.serverHostname.length() && !this.queryHostname.equals(this.serverHostname)) 081 { 082 this.queryHostname = this.serverHostname; 083 } 084 else 085 { 086 this.serverHostname = "0.0.0.0"; 087 088 try 089 { 090 InetAddress var2 = InetAddress.getLocalHost(); 091 this.queryHostname = var2.getHostAddress(); 092 } 093 catch (UnknownHostException var3) 094 { 095 this.logWarning("Unable to determine local host IP, please set server-ip in \'" + par1IServer.getSettingsFilename() + "\' : " + var3.getMessage()); 096 } 097 } 098 099 if (0 == this.queryPort) 100 { 101 this.queryPort = this.serverPort; 102 this.logInfo("Setting default query port to " + this.queryPort); 103 par1IServer.setProperty("query.port", Integer.valueOf(this.queryPort)); 104 par1IServer.setProperty("debug", Boolean.valueOf(false)); 105 par1IServer.saveProperties(); 106 } 107 108 this.field_72644_p = new HashMap(); 109 this.output = new RConOutputStream(1460); 110 this.queryClients = new HashMap(); 111 this.time = (new Date()).getTime(); 112 } 113 114 /** 115 * Sends a byte array as a DatagramPacket response to the client who sent the given DatagramPacket 116 */ 117 private void sendResponsePacket(byte[] par1ArrayOfByte, DatagramPacket par2DatagramPacket) throws IOException 118 { 119 this.querySocket.send(new DatagramPacket(par1ArrayOfByte, par1ArrayOfByte.length, par2DatagramPacket.getSocketAddress())); 120 } 121 122 /** 123 * Parses an incoming DatagramPacket, returning true if the packet was valid 124 */ 125 private boolean parseIncomingPacket(DatagramPacket par1DatagramPacket) throws IOException 126 { 127 byte[] var2 = par1DatagramPacket.getData(); 128 int var3 = par1DatagramPacket.getLength(); 129 SocketAddress var4 = par1DatagramPacket.getSocketAddress(); 130 this.logDebug("Packet len " + var3 + " [" + var4 + "]"); 131 132 if (3 <= var3 && -2 == var2[0] && -3 == var2[1]) 133 { 134 this.logDebug("Packet \'" + RConUtils.getByteAsHexString(var2[2]) + "\' [" + var4 + "]"); 135 136 switch (var2[2]) 137 { 138 case 0: 139 if (!this.verifyClientAuth(par1DatagramPacket).booleanValue()) 140 { 141 this.logDebug("Invalid challenge [" + var4 + "]"); 142 return false; 143 } 144 else if (15 == var3) 145 { 146 this.sendResponsePacket(this.createQueryResponse(par1DatagramPacket), par1DatagramPacket); 147 this.logDebug("Rules [" + var4 + "]"); 148 } 149 else 150 { 151 RConOutputStream var5 = new RConOutputStream(1460); 152 var5.writeInt(0); 153 var5.writeByteArray(this.getRequestID(par1DatagramPacket.getSocketAddress())); 154 var5.writeString(this.serverMotd); 155 var5.writeString("SMP"); 156 var5.writeString(this.worldName); 157 var5.writeString(Integer.toString(this.getNumberOfPlayers())); 158 var5.writeString(Integer.toString(this.maxPlayers)); 159 var5.writeShort((short)this.serverPort); 160 var5.writeString(this.queryHostname); 161 this.sendResponsePacket(var5.toByteArray(), par1DatagramPacket); 162 this.logDebug("Status [" + var4 + "]"); 163 } 164 case 9: 165 this.sendAuthChallenge(par1DatagramPacket); 166 this.logDebug("Challenge [" + var4 + "]"); 167 return true; 168 default: 169 return true; 170 } 171 } 172 else 173 { 174 this.logDebug("Invalid packet [" + var4 + "]"); 175 return false; 176 } 177 } 178 179 /** 180 * Creates a query response as a byte array for the specified query DatagramPacket 181 */ 182 private byte[] createQueryResponse(DatagramPacket par1DatagramPacket) throws IOException 183 { 184 long var2 = System.currentTimeMillis(); 185 186 if (var2 < this.lastQueryResponseTime + 5000L) 187 { 188 byte[] var7 = this.output.toByteArray(); 189 byte[] var8 = this.getRequestID(par1DatagramPacket.getSocketAddress()); 190 var7[1] = var8[0]; 191 var7[2] = var8[1]; 192 var7[3] = var8[2]; 193 var7[4] = var8[3]; 194 return var7; 195 } 196 else 197 { 198 this.lastQueryResponseTime = var2; 199 this.output.reset(); 200 this.output.writeInt(0); 201 this.output.writeByteArray(this.getRequestID(par1DatagramPacket.getSocketAddress())); 202 this.output.writeString("splitnum"); 203 this.output.writeInt(128); 204 this.output.writeInt(0); 205 this.output.writeString("hostname"); 206 this.output.writeString(this.serverMotd); 207 this.output.writeString("gametype"); 208 this.output.writeString("SMP"); 209 this.output.writeString("game_id"); 210 this.output.writeString("MINECRAFT"); 211 this.output.writeString("version"); 212 this.output.writeString(this.server.getMinecraftVersion()); 213 this.output.writeString("plugins"); 214 this.output.writeString(this.server.getPlugins()); 215 this.output.writeString("map"); 216 this.output.writeString(this.worldName); 217 this.output.writeString("numplayers"); 218 this.output.writeString("" + this.getNumberOfPlayers()); 219 this.output.writeString("maxplayers"); 220 this.output.writeString("" + this.maxPlayers); 221 this.output.writeString("hostport"); 222 this.output.writeString("" + this.serverPort); 223 this.output.writeString("hostip"); 224 this.output.writeString(this.queryHostname); 225 this.output.writeInt(0); 226 this.output.writeInt(1); 227 this.output.writeString("player_"); 228 this.output.writeInt(0); 229 String[] var4 = this.server.getAllUsernames(); 230 byte var5 = (byte)var4.length; 231 232 for (byte var6 = (byte)(var5 - 1); var6 >= 0; --var6) 233 { 234 this.output.writeString(var4[var6]); 235 } 236 237 this.output.writeInt(0); 238 return this.output.toByteArray(); 239 } 240 } 241 242 /** 243 * Returns the request ID provided by the authorized client 244 */ 245 private byte[] getRequestID(SocketAddress par1SocketAddress) 246 { 247 return ((RConThreadQueryAuth)this.queryClients.get(par1SocketAddress)).getRequestId(); 248 } 249 250 /** 251 * Returns true if the client has a valid auth, otherwise false 252 */ 253 private Boolean verifyClientAuth(DatagramPacket par1DatagramPacket) 254 { 255 SocketAddress var2 = par1DatagramPacket.getSocketAddress(); 256 257 if (!this.queryClients.containsKey(var2)) 258 { 259 return Boolean.valueOf(false); 260 } 261 else 262 { 263 byte[] var3 = par1DatagramPacket.getData(); 264 return ((RConThreadQueryAuth)this.queryClients.get(var2)).getRandomChallenge() != RConUtils.getBytesAsBEint(var3, 7, par1DatagramPacket.getLength()) ? Boolean.valueOf(false) : Boolean.valueOf(true); 265 } 266 } 267 268 /** 269 * Sends an auth challenge DatagramPacket to the client and adds the client to the queryClients map 270 */ 271 private void sendAuthChallenge(DatagramPacket par1DatagramPacket) throws IOException 272 { 273 RConThreadQueryAuth var2 = new RConThreadQueryAuth(this, par1DatagramPacket); 274 this.queryClients.put(par1DatagramPacket.getSocketAddress(), var2); 275 this.sendResponsePacket(var2.getChallengeValue(), par1DatagramPacket); 276 } 277 278 /** 279 * Removes all clients whose auth is no longer valid 280 */ 281 private void cleanQueryClientsMap() 282 { 283 if (this.running) 284 { 285 long var1 = System.currentTimeMillis(); 286 287 if (var1 >= this.lastAuthCheckTime + 30000L) 288 { 289 this.lastAuthCheckTime = var1; 290 Iterator var3 = this.queryClients.entrySet().iterator(); 291 292 while (var3.hasNext()) 293 { 294 Entry var4 = (Entry)var3.next(); 295 296 if (((RConThreadQueryAuth)var4.getValue()).hasExpired(var1).booleanValue()) 297 { 298 var3.remove(); 299 } 300 } 301 } 302 } 303 } 304 305 public void run() 306 { 307 this.logInfo("Query running on " + this.serverHostname + ":" + this.queryPort); 308 this.lastAuthCheckTime = System.currentTimeMillis(); 309 this.incomingPacket = new DatagramPacket(this.buffer, this.buffer.length); 310 311 try 312 { 313 while (this.running) 314 { 315 try 316 { 317 this.querySocket.receive(this.incomingPacket); 318 this.cleanQueryClientsMap(); 319 this.parseIncomingPacket(this.incomingPacket); 320 } 321 catch (SocketTimeoutException var7) 322 { 323 this.cleanQueryClientsMap(); 324 } 325 catch (PortUnreachableException var8) 326 { 327 ; 328 } 329 catch (IOException var9) 330 { 331 this.stopWithException(var9); 332 } 333 } 334 } 335 finally 336 { 337 this.closeAllSockets(); 338 } 339 } 340 341 /** 342 * Creates a new Thread object from this class and starts running 343 */ 344 public void startThread() 345 { 346 if (!this.running) 347 { 348 if (0 < this.queryPort && 65535 >= this.queryPort) 349 { 350 if (this.initQuerySystem()) 351 { 352 super.startThread(); 353 } 354 } 355 else 356 { 357 this.logWarning("Invalid query port " + this.queryPort + " found in \'" + this.server.getSettingsFilename() + "\' (queries disabled)"); 358 } 359 } 360 } 361 362 /** 363 * Stops the query server and reports the given Exception 364 */ 365 private void stopWithException(Exception par1Exception) 366 { 367 if (this.running) 368 { 369 this.logWarning("Unexpected exception, buggy JRE? (" + par1Exception.toString() + ")"); 370 371 if (!this.initQuerySystem()) 372 { 373 this.logSevere("Failed to recover from buggy JRE, shutting down!"); 374 this.running = false; 375 } 376 } 377 } 378 379 /** 380 * Initializes the query system by binding it to a port 381 */ 382 private boolean initQuerySystem() 383 { 384 try 385 { 386 this.querySocket = new DatagramSocket(this.queryPort, InetAddress.getByName(this.serverHostname)); 387 this.registerSocket(this.querySocket); 388 this.querySocket.setSoTimeout(500); 389 return true; 390 } 391 catch (SocketException var2) 392 { 393 this.logWarning("Unable to initialise query system on " + this.serverHostname + ":" + this.queryPort + " (Socket): " + var2.getMessage()); 394 } 395 catch (UnknownHostException var3) 396 { 397 this.logWarning("Unable to initialise query system on " + this.serverHostname + ":" + this.queryPort + " (Unknown Host): " + var3.getMessage()); 398 } 399 catch (Exception var4) 400 { 401 this.logWarning("Unable to initialise query system on " + this.serverHostname + ":" + this.queryPort + " (E): " + var4.getMessage()); 402 } 403 404 return false; 405 } 406 }