/* * Copyright (c) 2010 SimpleServer authors (see CONTRIBUTORS) * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ package simpleserver.rcon; import static simpleserver.util.Util.*; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.net.Socket; import java.nio.ByteBuffer; import java.nio.ByteOrder; import java.util.concurrent.Semaphore; import simpleserver.Server; public class RconHandler implements Runnable { private final int BUF_SIZE = 8192; private final int SERVERDATA_AUTH = 3; private final int SERVERDATA_EXECCOMMAND = 2; private final int SERVERDATA_AUTH_RESPONSE = 2; private final int SERVERDATA_RESPONSE_VALUE = 0; private final int INT = 4; private final int BB_DEFAULT = 128; private byte[] buf; private int r; private int a; private boolean done = false; private InputStream in; private OutputStream out; private Semaphore lock; static final int IDLE_TIME = 60 * 1000; private ByteBuffer bb = ByteBuffer.allocate(BB_DEFAULT); private ByteBuffer bbSend = ByteBuffer.allocate(4096); private Socket s; private RconTCP parent; private Server server; public RconHandler(Socket s, RconTCP r, Server server) throws IOException { this.s = s; in = s.getInputStream(); out = s.getOutputStream(); parent = r; this.server = server; buf = new byte[BUF_SIZE]; lock = new Semaphore(1); bb.order(ByteOrder.LITTLE_ENDIAN); } public void run() { // int packetid=0; try { s.setSoTimeout(IDLE_TIME); parent.lastRead = System.currentTimeMillis(); while (!parent.isClosed() && !Thread.interrupted()) { // int read = in.read(buf, 0, 4); int packetSize = readInt(); // int read = in.read(buf, 4, packetSize); ensureRead(packetSize); /* if (System.currentTimeMillis()-lastRead>IDLE_TIME) { if (!parent.isRobot) print("Disconnecting " + parent.getIPAddress() + " due to inactivity."); try {in.close();} catch (IOException e1) {} try {out.close();} catch (IOException e1) {} parent.close(); } */ int requestID = readInt(); int requestType = readInt(); String s1 = readString2(); readString2(); if (a - r > 0) { System.out.println(a - r); byte[] cpy = new byte[BUF_SIZE]; System.arraycopy(buf, r, cpy, 0, a - r); System.arraycopy(cpy, 0, buf, 0, a - r); } a -= r; r = 0; int responseType = 0; String response; if (requestType == SERVERDATA_EXECCOMMAND && parent.auth) { response = parsePacket(s1); done = true; } else if (requestType == SERVERDATA_AUTH) { response = auth(s1); responseType = SERVERDATA_AUTH_RESPONSE; if (response == null) { requestID = -1; response = ""; } junkResponse(); } else if (!parent.auth) { responseType = SERVERDATA_AUTH_RESPONSE; requestID = -1; response = ""; } else { responseType = SERVERDATA_RESPONSE_VALUE; requestID = -1; response = "Error"; } if (!response.equals("")) { int i = 0; for (i = 0; i + 4096 < bbSend.capacity(); i += 4096) { out.write(assemblePacket(response.substring(i, i + 4096), requestID, responseType)); } out.write(assemblePacket(response.substring(i), requestID, responseType)); } else { out.write(assemblePacket("", requestID, responseType)); } out.flush(); s.setSendBufferSize(10); // s.sendUrgentData(0); if (done) { out.close(); break; } if (parent.isClosed()) { throw new InterruptedException(); } bb = ByteBuffer.allocate(BB_DEFAULT); bb.order(ByteOrder.LITTLE_ENDIAN); Thread.sleep(20); } } catch (InterruptedException e1) { // We don't care about an Interrupted Exception. // Don't even print out to the console that we received the exception. // We are only interrupted if we are closing. // e1.printStackTrace(); } catch (Exception e) { e.printStackTrace(); } // try {s.shutdownInput();} catch (IOException e2) {} // try {s.shutdownOutput();} catch (IOException e2) {} try { out.close(); } catch (IOException e1) { } try { in.close(); } catch (IOException e) { } in = null; out = null; try { s.close(); } catch (IOException e) { } parent = null; lock = null; } private byte[] assemblePacket(String command, int requestID, int responseType) { byte[] strBytes = command.getBytes(); return assemblePacket(strBytes, requestID, responseType); } private byte[] assemblePacket(byte[] command, int requestID, int responseType) { ByteBuffer bbuf; int packetSize = INT * 2 + (command.length) + 1 + 1; bbuf = ByteBuffer.allocate(packetSize + 4); bbuf.order(ByteOrder.LITTLE_ENDIAN); bbuf.putInt(packetSize); bbuf.putInt(requestID); bbuf.putInt(responseType); bbuf.put(command); bbuf.put((byte) 0); bbuf.put((byte) 0); byte[] send = bbuf.array(); return send; } private void junkResponse() throws IOException { int packetSize = INT * 2 + 1 + 1; bb = ByteBuffer.allocate(INT + packetSize); bb.order(ByteOrder.LITTLE_ENDIAN); bb.putInt(packetSize); bb.putInt(-1); bb.putInt(SERVERDATA_RESPONSE_VALUE); bb.put((byte) 0); bb.put((byte) 0); out.write(bb.array()); } private String getConsole() { String console = ""; String[] consolearray = server.getOutputLog(); for (String i : consolearray) { console += i; } return console; } protected String parsePacket(String command) throws IOException, InterruptedException { String[] tokens = command.split(" "); if (tokens.length > 0) { if (tokens[0].equalsIgnoreCase("rcon")) { if (tokens.length > 1) { int idx = command.indexOf(tokens[1]) + tokens[1].length() + 1; server.runCommand(tokens[1], command.substring(idx)); return command; } else { return "Error: No Command"; } } if (tokens[0].equalsIgnoreCase("help")) { if (tokens.length > 1) { if (tokens[1].equalsIgnoreCase("get")) { return "Resources:\n" + "console Shows console output\n"; } } return "Commands:\n" + "help Shows this message\n" + "rcon Execute Command\n" + "get Get a resource"; } if (tokens[0].equalsIgnoreCase("get")) { if (tokens.length > 1) { if (tokens[1].equalsIgnoreCase("console")) { return getConsole(); } } return "Error: No Command"; } } return "Error: Unrecognized Command"; } protected String auth(String passwd) { if (!server.options.contains("rconPassword")) { println("RCON Auth Attempt from " + s.getInetAddress().getHostAddress() + "! (rconPassword is blank)"); return null; } if (passwd.equals(server.options.get("rconPassword"))) { parent.auth = true; return ""; } else { println("RCON Authentication Failed from " + s.getInetAddress().getHostAddress() + "!"); return null; } } private int readMore() throws InterruptedException, IOException { int tmp = 0; int avail; try { lock.acquire(); avail = in.available(); if (avail == 0) { lock.release(); Thread.sleep(20); return a; } if (a + avail > buf.length) { avail = buf.length - a; } if (avail > 0) { tmp = in.read(buf, a, avail); } if (tmp > 0) { a += tmp; parent.lastRead = System.currentTimeMillis(); } lock.release(); return a; } catch (IOException e) { lock.release(); throw e; } catch (InterruptedException e) { lock.release(); throw e; } } protected boolean ensureRead(int n) throws InterruptedException, IOException { if (r + n > BUF_SIZE) { return false; } if (a >= r + n) { return true; } while (a < r + n) { if (parent.isClosed() || Thread.interrupted()) { throw new InterruptedException(); } readMore(); } return true; } protected String readString2() throws IOException, InterruptedException { bb.clear(); byte b = 0; int i = 0; while (true) { ensureRead(1); b = readByte(); if (b != 0) { bb.put(b); } else { break; } i++; } if (bb.get(0) == 0) { return ""; } byte[] string = new byte[i]; bb.position(0); bb.get(string, 0, i); return new String(string); } protected int readInt() throws IOException, InterruptedException { ensureRead(4); byte[] cpy = new byte[4]; cpy[0] = buf[r]; cpy[1] = buf[r + 1]; cpy[2] = buf[r + 2]; cpy[3] = buf[r + 3]; r += 4; return bytesToInt(cpy); } protected double readDouble() throws IOException, InterruptedException { ensureRead(8); byte[] cpy = new byte[8]; cpy[0] = buf[r]; cpy[1] = buf[r + 1]; cpy[2] = buf[r + 2]; cpy[3] = buf[r + 3]; cpy[4] = buf[r + 4]; cpy[5] = buf[r + 5]; cpy[6] = buf[r + 6]; cpy[7] = buf[r + 7]; r += 8; return bytesToDouble(cpy); } protected short readShort() throws IOException, InterruptedException { ensureRead(2); byte[] cpy = new byte[2]; cpy[0] = buf[r]; cpy[1] = buf[r + 1]; r += 2; return bytesToShort(cpy); } protected byte[] readBytes(int n) throws IOException, InterruptedException { ensureRead(n); byte[] cpy = new byte[n]; System.arraycopy(buf, r, cpy, 0, n); r += n; return cpy; } protected byte readByte() throws IOException, InterruptedException { ensureRead(1); return buf[r++]; } protected void skipBytes(int n) { r += n; } protected void removeBytes(int n) throws IOException, InterruptedException { ensureRead(0); lock.acquire(); if (a - r != 0) { // byte[] cpy = new byte[a-r]; System.arraycopy(buf, r, buf, r - n, a - r); // System.arraycopy(cpy, 0, buf, r-n, a-r); } r -= n; a -= n; lock.release(); } private short bytesToShort(byte[] data) { bb.clear(); bb.put(data[0]); bb.put(data[1]); short s = bb.getShort(0); return s; } private int bytesToInt(byte[] data) { bb.clear(); bb.put(data[0]); bb.put(data[1]); bb.put(data[2]); bb.put(data[3]); int s = bb.getInt(0); return s; } private double bytesToDouble(byte[] data) { bb.clear(); bb.put(data[0]); bb.put(data[1]); bb.put(data[2]); bb.put(data[3]); bb.put(data[4]); bb.put(data[5]); bb.put(data[6]); bb.put(data[7]); double s = bb.getDouble(0); return s; } }