1 package net.sourceforge.rconed;
2
3 import java.io.BufferedReader;
4 import java.io.IOException;
5 import java.io.InputStream;
6 import java.io.InputStreamReader;
7 import java.io.OutputStream;
8 import java.net.InetAddress;
9 import java.net.InetSocketAddress;
10 import java.net.Socket;
11 import java.net.SocketTimeoutException;
12 import java.net.UnknownHostException;
13 import java.security.MessageDigest;
14 import java.security.NoSuchAlgorithmException;
15
16 import net.sourceforge.rconed.exception.BadRcon;
17 import net.sourceforge.rconed.exception.ResponseEmpty;
18
19 /**
20 * BF2Rcon is a simple Java library for issuing RCON commands to BF2 game servers.
21 * <p/>
22 * This has been used with default and BF2CC managed servers.
23 * <p/>
24 * Example:
25 * <p/>
26 * String response = BF2Rcon.send("127.0.0.1", 6711, "admin", "game.sayAll \"ploppers\"");
27 * <p/>
28 * @author DeadEd
29 */
30 public class BF2Rcon {
31
32 final static int RESPONSE_TIMEOUT = 2000;
33
34 static Socket rconSocket = null;
35 static InputStream in = null;
36 static OutputStream out = null;
37
38
39 /**
40 * Send the RCON command to the game server
41 *
42 * @param ipStr The IP (as a String) of the machine where the RCON command will go.
43 * @param port The port of the machine where the RCON command will go.
44 * @param password The RCON password.
45 * @param command The RCON command (without the rcon prefix).
46 * @return The reponse text from the server after trying the RCON command.
47 * @throws SocketTimeoutException when there is any problem communicating with the server.
48 * @throws BadRcon when authentication fails
49 * @throws ResponseEmpty when the response is empty
50 */
51 public static String send(String ipStr, int port, String password, String command) throws SocketTimeoutException, BadRcon, ResponseEmpty {
52 return send(ipStr, port, password, command, 0);
53 }
54
55 /**
56 * Send the RCON command to the game server (must have been previously authed with the correct rcon_password)
57 *
58 * @param ipStr The IP (as a String) of the machine where the RCON command will go.
59 * @param port The port of the machine where the RCON command will go.
60 * @param password The RCON password.
61 * @param command The RCON command (without the rcon prefix).
62 * @param localPort The port of the local machine to use for sending out the RCON request.
63 * @return The reponse text from the server after trying the RCON command.
64 * @throws SocketTimeoutException when there is any problem communicating with the server.
65 * @throws BadRcon when authentication fails
66 * @throws ResponseEmpty when the response is empty
67 */
68 public static String send(String ipStr, int port, String password, String command, int localPort) throws SocketTimeoutException, BadRcon, ResponseEmpty {
69 return send(ipStr, port, password, command, null, localPort);
70 }
71
72 /**
73 * Send the RCON command to the game server (must have been previously authed with the correct rcon_password)
74 *
75 * @param ipStr The IP (as a String) of the machine where the RCON command will go.
76 * @param port The port of the machine where the RCON command will go.
77 * @param password The RCON password.
78 * @param command The RCON command (without the rcon prefix).
79 * @param localhost The IP of the local machine to use for sending out the RCON request.
80 * @param localPort The port of the local machine to use for sending out the RCON request.
81 * @return The reponse text from the server after trying the RCON command.
82 * @throws SocketTimeoutException when there is any problem communicating with the server.
83 * @throws BadRcon when authentication fails
84 * @throws ResponseEmpty when the response is empty
85 */
86 public static String send(String ipStr, int port, String password, String command, InetAddress localhost, int localPort) throws SocketTimeoutException, BadRcon, ResponseEmpty {
87 StringBuffer response = new StringBuffer();
88
89 try {
90 rconSocket = new Socket();
91
92 //InetAddress addr = InetAddress.getLocalHost();
93 //byte[] ipAddr = addr.getAddress();
94 //InetAddress inetLocal = InetAddress.getByAddress(ipAddr);
95
96 //rconSocket.bind(new InetSocketAddress(inetLocal, localPort));
97 rconSocket.bind(new InetSocketAddress(localhost, localPort));
98 rconSocket.connect(new InetSocketAddress(ipStr, port), RESPONSE_TIMEOUT);
99
100 out = rconSocket.getOutputStream();
101 in = rconSocket.getInputStream();
102 BufferedReader buffRead = new BufferedReader(new InputStreamReader(in));
103
104 rconSocket.setSoTimeout(RESPONSE_TIMEOUT);
105
106 String digestSeed = "";
107 boolean loggedIn = false;
108 boolean keepGoing = true;
109 while(keepGoing) {
110 String receivedContent = buffRead.readLine();
111 if(receivedContent.startsWith("### Digest seed: ")) {
112 digestSeed = receivedContent.substring(17, receivedContent.length());
113 try {
114 MessageDigest md5 = MessageDigest.getInstance("MD5");
115 md5.update(digestSeed.getBytes());
116 md5.update(password.getBytes());
117 String digestStr = "login "+ digestedToHex(md5.digest()) +"\n";
118 out.write(digestStr.getBytes());
119 } catch (NoSuchAlgorithmException e1) {
120 response.append("MD5 algorithm not available - unable to complete RCON request.");
121 keepGoing = false;
122 }
123 } else if(receivedContent.startsWith("error: not authenticated: you can only invoke 'login'")) {
124 throw new BadRcon();
125 } else if(receivedContent.startsWith("Authentication failed.")) {
126 throw new BadRcon();
127 } else if(receivedContent.startsWith("Authentication successful, rcon ready.")) {
128 keepGoing = false;
129 loggedIn = true;
130 }
131 }
132 if(loggedIn) {
133 // logged in, now we can execute the command
134 String cmd = "\u0002exec "+ command +"\n";
135 out.write(cmd.getBytes());
136
137 readResponse(buffRead, response);
138 if(response.length() == 0) {
139 throw new ResponseEmpty();
140 }
141 }
142
143 } catch (SocketTimeoutException timeout) {
144 throw timeout;
145 } catch (UnknownHostException e) {
146 response.append("UnknownHostException: "+ e.getMessage());
147 } catch (IOException e) {
148 response.append("Couldn't get I/O for the connection: "+ e.getMessage());
149 e.printStackTrace();
150 } finally {
151 try {
152 if(out != null) {
153 out.close();
154 }
155 if(in != null) {
156 in.close();
157 }
158 if(rconSocket != null) {
159 rconSocket.close();
160 }
161 } catch (IOException e1) {
162 // eat ... messed up at this point anyway
163 }
164 }
165
166 return response.toString();
167 }
168
169 /**
170 * @param buffRead where to read from
171 * @param sb where to put read data
172 * @throws IOException
173 */
174 private static void readResponse(BufferedReader buffRead, StringBuffer sb) throws IOException {
175 int ch;
176 while(true) {
177 ch = buffRead.read();
178 if(ch == -1 || ch == 4) {
179 return;
180 }
181 sb.append((char)ch);
182 }
183 }
184
185 private static String digestedToHex(byte digest[]) {
186 StringBuffer store = new StringBuffer();
187 for(int x=0; x < digest.length; x++) {
188 byte bite = digest[x];
189 String val = Integer.toHexString(bite & 255);
190 if(val.length() == 1) {
191 store.append("0");
192 }
193 store.append(val);
194 }
195 return store.toString();
196 }
197 }