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    }