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 }