View Javadoc

1   package net.sourceforge.rconed;
2   
3   import java.io.IOException;
4   import java.net.DatagramPacket;
5   import java.net.DatagramSocket;
6   import java.net.InetAddress;
7   import java.net.SocketTimeoutException;
8   
9   import net.sourceforge.rconed.exception.BadRcon;
10  import net.sourceforge.rconed.exception.ResponseEmpty;
11  
12  /**
13   * Rcon is a simple Java library for issuing RCON commands to game servers.
14   * <p/>
15   * This has currently only been used with HalfLife based servers.
16   * <p/>
17   * Example:
18   * <p/>
19   * response = Rcon.send(27778, "127.0.0.1", 27015, rconPassword, "log on");
20   * <p/>
21   * PiTaGoRas - 21/12/2004<br>
22   * Now also supports responses divided into multiple packets, bad rcon password
23   * detection and other minor fixes/improvements.
24   * <p/>
25   * @author DeadEd
26   * @version 1.1
27   */
28  public abstract class Rcon {
29  
30      private static final int RESPONSE_TIMEOUT = 2000;
31      private static final int MULTIPLE_PACKETS_TIMEOUT = 300;
32  
33      /**
34       * Send the RCON request.  Sends the command to the game server.  A port
35       * (localPort must be opened to send the command through.
36       *
37       * @param localPort The port on the local machine where the RCON request can be made from.
38       * @param ipStr     The IP (as a String) of the machine where the RCON command will go.
39       * @param port      The port of the machine where the RCON command will go.
40       * @param password  The RCON password.
41       * @param command   The RCON command (without the rcon prefix).
42       * @return The reponse text from the server after trying the RCON command.
43       * @throws SocketTimeoutException when there is any problem communicating with the server.
44       */
45      public static String send(int localPort, String ipStr, int port, String password, String command)
46              throws SocketTimeoutException, BadRcon, ResponseEmpty {
47  
48          RconPacket[] requested = sendRequest(localPort, ipStr, port, password, command);
49  
50          String response = assemblePacket(requested);
51  
52          if (response.matches("Bad rcon_password.\n")) {
53              throw new BadRcon();
54          }
55          if (response.length() == 0) {
56              throw new ResponseEmpty();
57          }
58  
59          return response;
60      }
61  
62      private static DatagramPacket getDatagramPacket(String request, InetAddress inet, int port) {
63          byte first = -1;
64          byte last = 0;
65          byte[] buffer = request.getBytes();
66          byte[] commandBytes = new byte[buffer.length + 5];
67          commandBytes[0] = first;
68          commandBytes[1] = first;
69          commandBytes[2] = first;
70          commandBytes[3] = first;
71          for (int i = 0; i < buffer.length; i++) {
72              commandBytes[i + 4] = buffer[i];
73          }
74          commandBytes[buffer.length + 4] = last;
75  
76          return new DatagramPacket(commandBytes, commandBytes.length, inet, port);
77      }
78  
79      private static RconPacket[] sendRequest(int localPort, String ipStr, int port, String password,
80                                              String command) throws SocketTimeoutException {
81  
82          DatagramSocket socket = null;
83          RconPacket[] resp = new RconPacket[128];
84  
85          try {
86              socket = new DatagramSocket(localPort);
87              int packetSize = 1400;
88  
89              InetAddress address = InetAddress.getByName(ipStr);
90              byte[] ip = address.getAddress();
91              InetAddress inet = InetAddress.getByAddress(ip);
92              String msg = "challenge rcon\n";
93  
94              DatagramPacket out = getDatagramPacket(msg, inet, port);
95              socket.send(out);
96  
97              // get the challenge
98              byte[] data = new byte[packetSize];
99              DatagramPacket inPacket = new DatagramPacket(data, packetSize);
100 
101             socket.setSoTimeout(RESPONSE_TIMEOUT);
102             socket.receive(inPacket);
103 
104             // compose the final command and send to the server
105             String challenge = parseResponse(inPacket.getData());
106             String challengeNumber = challenge.substring(challenge.indexOf("rcon") + 5).trim();
107             String commandStr = "rcon " + challengeNumber + " \"" + password + "\" " + command;
108             DatagramPacket out2 = getDatagramPacket(commandStr, inet, port);
109             socket.send(out2);
110 
111             // get the response
112             byte[] data2 = new byte[packetSize];
113             DatagramPacket inPacket2 = new DatagramPacket(data2, packetSize);
114             socket.setSoTimeout(RESPONSE_TIMEOUT);
115             socket.receive(inPacket2);
116 
117             resp[0] = new RconPacket(inPacket2);
118             try {
119             // Wait for a possible multiple packets response
120                 socket.setSoTimeout(MULTIPLE_PACKETS_TIMEOUT);
121                 int i = 1;
122                 while (true) {
123                     socket.receive(inPacket2);
124                     resp[i++] = new RconPacket(inPacket2);
125                 }
126             } catch (SocketTimeoutException sex) {
127                 // Server didn't send more packets
128             }
129 
130         } catch (SocketTimeoutException sex) {
131             throw sex;
132         } catch (IOException ex) {
133             ex.printStackTrace();
134         } finally {
135             if (socket != null) {
136                 socket.close();
137             }
138         }
139 
140         return resp;
141     }
142 
143     private static String parseResponse(byte[] buf) {
144         String retVal = "";
145 
146         if (buf[0] != -1 || buf[1] != -1 || buf[2] != -1 || buf[3] != -1) {
147             retVal = "ERROR";
148         } else {
149             int off = 5;
150             StringBuffer challenge = new StringBuffer(20);
151             while (buf[off] != 0) {
152                 challenge.append((char) (buf[off++] & 255));
153             }
154             retVal = challenge.toString();
155         }
156 
157         return retVal;
158     }
159 
160     private static String assemblePacket(RconPacket[] respPacket) {
161 
162         String resp = "";
163 
164         // TODO: inspect the headers to decide the correct order
165 
166         for (int i = 0; i < respPacket.length; i++) {
167             if (respPacket[i] != null) {
168                 resp = resp.concat(respPacket[i].data);
169             }
170         }
171         return resp;
172     }
173 
174 }
175 
176 class RconPacket {
177 
178     /**
179      * ASCII representation of the full packet received (header included)
180      */
181     public String ascii = "";
182 
183     /**
184      * The data included in the packet, header removed
185      */
186     public String data = "";
187 
188     /**
189      * The full packet received (header included) in bytes
190      */
191     public byte[] bytes = new byte[1400];
192 
193     /**
194      * Length of the packet
195      */
196     public int length = 0;
197 
198     /**
199      * Represents a rcon response packet from the game server. A response may be split
200      * into multiple packets, so an array of RconPackets should be used.
201      *
202      * @param packet One DatagramPacket returned by the server
203      */
204     public RconPacket(DatagramPacket packet) {
205 
206         this.ascii = new String(packet.getData(), 0, packet.getLength());
207         this.bytes = ascii.getBytes();
208         this.length = packet.getLength();
209 
210         // Now we remove the headers from the packet to have just the text
211         if (bytes[0] == -2) {
212             // this response comes divided into two packets
213             if (bytes[13] == 108) {
214                 this.data = new String(packet.getData(), 14, packet.getLength() - 16);
215             } else {
216                 this.data = new String(packet.getData(), 11, packet.getLength() - 13);
217             }
218         } else {
219             // Single packet
220             this.data = new String(packet.getData(), 5, packet.getLength() - 7);
221         }
222     }
223 }