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