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.UnexpectedDataException;
11  
12  /**
13   * Rcon is a simple Java library for issuing RCON commands to game servers.
14   * <p/>
15   * CODRcon is an extension to the library to allow for Call of Duty based
16   * servers to be connected against. This has currently only been tested with
17   * Call of Duty 2 servers, but the communication protocol should be valid for
18   * all current Call of Duty versions.
19   * <p/>
20   * Example:
21   * <p/>
22   * response = CODRcon.send("127.0.0.1", 28960, rconPassword, "status");
23   * <p/>
24  
25   * Copied bulk of original code from Rcon, making modifications where
26   * necessary for Call of Duty servers
27   * <p/>
28   * @author DeadEd, David Hayes
29   * @version 1.1
30   */
31  public class CODRcon {
32  
33      private static final int RESPONSE_TIMEOUT = 2000;
34      private static final int MULTIPLE_PACKETS_TIMEOUT = 300;
35  
36      /**
37       * Send the RCON request.  Sends the command to the game server. This version allows for
38       * a local portless request to be made.
39       * <p/>
40       * As the communication travels via an unreliable method (at the choice of the Call of
41       * Duty developers), the order in which the message will be returned is not guaranteed,
42       * nor is the fact that you will receive a complete message. It is more advisable to call
43       * commands that result in shorter returns from the console, than commands that return
44       * longer results.
45       *
46       * @param ipStr     The IP (as a String) of the machine where the RCON command will go.
47       * @param port      The port of the machine where the RCON command will go.
48       * @param password  The RCON password.
49       * @param command   The RCON command (without the rcon prefix).
50       * @return The reponse text from the server after trying the RCON command.
51       * @throws SocketTimeoutException when there is any problem communicating with the server.
52       */
53      public static String send(String ipStr, int port, String password, String command)
54  		    throws SocketTimeoutException, BadRcon, UnexpectedDataException {
55  		
56      	return send (-1, ipStr, port, password, command);
57      	
58  	}
59      
60      /**
61       * Send the RCON request.  Sends the command to the game server.  A port
62       * (localPort) must be opened to send the command through.
63       * <p/>
64       * As the communication travels via an unreliable method (at the choice of the Call of
65       * Duty developers), the order in which the message will be returned is not guaranteed,
66       * nor is the fact that you will receive a complete message. It is more advisable to call
67       * commands that result in shorter returns from the console, than commands that return
68       * longer results.
69       *
70       * @param localPort The port on the local machine where the RCON request can be made from.
71       * @param ipStr     The IP (as a String) of the machine where the RCON command will go.
72       * @param port      The port of the machine where the RCON command will go.
73       * @param password  The RCON password.
74       * @param command   The RCON command (without the rcon prefix).
75       * @return The reponse text from the server after trying the RCON command.
76       * @throws SocketTimeoutException when there is any problem communicating with the server.
77       */
78      public static String send(int localPort, String ipStr, int port, String password, String command)
79              throws SocketTimeoutException, BadRcon, UnexpectedDataException {
80  
81          CODRconPacket[] requested = sendRequest(localPort, ipStr, port, password, command);
82  
83          String response = assemblePacket(requested);
84  
85          if (response.matches("Invalid password.\n")) {
86          	throw new BadRcon();
87          }
88          return response;
89      }
90  
91      
92  
93      private static DatagramPacket getDatagramPacket(String request, InetAddress inet, int port) {
94          byte first = -1;
95          byte last = 0;
96          byte[] buffer = request.getBytes();
97          byte[] commandBytes = new byte[buffer.length + 5];
98          commandBytes[0] = first;
99          commandBytes[1] = first;
100         commandBytes[2] = first;
101         commandBytes[3] = first;
102         for (int i = 0; i < buffer.length; i++) {
103             commandBytes[i + 4] = buffer[i];
104         }
105         commandBytes[buffer.length + 4] = last;
106 
107         return new DatagramPacket(commandBytes, commandBytes.length, inet, port);
108     }
109 
110     private static CODRconPacket[] sendRequest(int localPort, String ipStr, int port, String password,
111                                             String command) throws SocketTimeoutException {
112 
113         DatagramSocket socket = null;
114         CODRconPacket[] resp = new CODRconPacket[128];
115 
116         try {
117             if (localPort == -1) {
118                 socket = new DatagramSocket();
119             } else {
120                 socket = new DatagramSocket(localPort);
121             }
122             int packetSize = 1400;
123 
124             InetAddress address = InetAddress.getByName(ipStr);
125             byte[] ip = address.getAddress();
126             InetAddress inet = InetAddress.getByAddress(ip);
127 
128             String commandStr = "rcon \"" + password + "\" " + command;
129             DatagramPacket out = getDatagramPacket(commandStr, inet, port);
130             socket.send(out);
131 
132             // get the response
133             byte[] data = new byte[packetSize];
134             DatagramPacket inPacket = new DatagramPacket(data, packetSize);
135             socket.setSoTimeout(RESPONSE_TIMEOUT);
136             socket.receive(inPacket);
137 
138             resp[0] = new CODRconPacket(inPacket);
139             try {
140             // Wait for a possible multiple packets response
141                 socket.setSoTimeout(MULTIPLE_PACKETS_TIMEOUT);
142                 int i = 1;
143                 while (true) {
144                     socket.receive(inPacket);
145                     resp[i++] = new CODRconPacket(inPacket);
146                 }
147             } catch (SocketTimeoutException sex) {
148                 // Server didn't send more packets
149             }
150 
151         } catch (SocketTimeoutException sex) {
152             throw sex;
153         } catch (IOException ex) {
154             ex.printStackTrace();
155         } finally {
156             if (socket != null) {
157                 socket.close();
158             }
159         }
160 
161         return resp;
162     }
163 
164     private static String assemblePacket(CODRconPacket[] respPacket) throws UnexpectedDataException {
165 
166         String resp = "";
167 
168         // TODO: inspect the headers to decide the correct order
169 
170         for (int i = 0; i < respPacket.length; i++) {
171             if (respPacket[i] != null) {
172             	// Verify that the packet was valid
173             	if (respPacket[i].valid) {
174             		resp = resp.concat(respPacket[i].data);
175             	} else {
176             		throw new UnexpectedDataException();
177             	}
178             }
179         }
180         return resp;
181     }
182 
183 }
184 
185 class CODRconPacket {
186 
187     /**
188      * ASCII representation of the full packet received (header included)
189      */
190     public String ascii = "";
191 
192     /**
193      * The data included in the packet, header removed
194      */
195     public String data = "";
196 
197     /**
198      * The full packet received (header included) in bytes
199      */
200     public byte[] bytes = new byte[1400];
201 
202     /**
203      * Length of the packet
204      */
205     public int length = 0;
206     
207     /**
208      * An indicator of the validity of the packet.
209      */
210     public boolean valid = true;
211 
212     /**
213      * Represents a rcon response packet from the game server. A response may be split
214      * into multiple packets, so an array of RconPackets should be used.
215      *
216      * @param packet One DatagramPacket returned by the server
217      */
218     public CODRconPacket(DatagramPacket packet) {
219 
220         this.ascii = new String(packet.getData(), 0, packet.getLength());
221         this.bytes = ascii.getBytes();
222         this.length = packet.getLength();
223 
224         try {
225         	this.data = parseResponse(packet.getData());
226         } catch (UnexpectedDataException e) {
227         	this.data = "ERROR";
228         	this.valid = false;
229         }
230     }
231 
232     private static String parseResponse(byte[] buf) throws UnexpectedDataException{
233         String retVal = "";
234 
235         /*
236          * The first four bytes of the return are always -1 (0xFF).
237          * Then follows the characters "print" plus the ascii new line (0x0A)
238          * thus the command below
239          */
240         if (buf[0] != -1 || buf[1] != -1 || buf[2] != -1 || buf[3] != -1 || buf[4] != 112 || buf[5] != 114 || buf[6] != 105 || buf[7] != 110 || buf[8] != 116 || !(buf[9] == 10 || buf[9] == 13)) {
241             throw new UnexpectedDataException();
242         }
243 		int off = 10;
244 		StringBuffer challenge = new StringBuffer(9995);
245 		while (buf[off] != 0) {
246 		    challenge.append((char) (buf[off++] & 255));
247 		}
248 		retVal = challenge.toString();
249 
250         return retVal;
251     }
252 }