summaryrefslogtreecommitdiff
path: root/server-rcon.c
diff options
context:
space:
mode:
Diffstat (limited to 'server-rcon.c')
-rw-r--r--server-rcon.c375
1 files changed, 375 insertions, 0 deletions
diff --git a/server-rcon.c b/server-rcon.c
new file mode 100644
index 0000000..5409b48
--- /dev/null
+++ b/server-rcon.c
@@ -0,0 +1,375 @@
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netinet/ip.h>
+#include <arpa/inet.h>
+#include <stdint.h>
+#include <inttypes.h>
+#include <errno.h>
+
+#include "main.h"
+#include "uring.h"
+#include "server.h"
+#include "server-rcon.h"
+
+struct rcon {
+ struct server *server;
+ struct connection conn;
+ struct uring_task task;
+ struct uring_task_buf tbuf;
+};
+
+static int32_t
+read_int(char **pos, size_t *len)
+{
+ uint32_t val;
+ char *p;
+
+ assert_return(pos && *pos, 0);
+
+ if (len && *len < 4)
+ return 0;
+
+ p = *pos;
+ val = ((uint8_t)p[0] << 0);
+ val += ((uint8_t)p[1] << 8);
+ val += ((uint8_t)p[2] << 16);
+ val += ((uint8_t)p[3] << 24);
+
+ *pos += 4;
+ if (len)
+ *len -= 4;
+
+ return (int32_t)val;
+}
+
+static void
+write_int(char **pos, size_t *len, int32_t orig)
+{
+ uint32_t val = (uint32_t)orig;
+ char *p;
+
+ assert_return(pos && *pos);
+
+ p = *pos;
+ p[0] = (val >> 0) & 0xff;
+ p[1] = (val >> 8) & 0xff;
+ p[2] = (val >> 16) & 0xff;
+ p[3] = (val >> 24) & 0xff;
+
+ *pos += 4;
+ if (len)
+ *len += 4;
+}
+
+static void
+write_str(char **pos, size_t *len, const char *str)
+{
+ size_t towrite;
+
+ assert_return(pos && *pos && !empty_str(str));
+
+ towrite = strlen(str);
+ memcpy(*pos, str, towrite);
+ *pos += towrite;
+ if (len)
+ *len += towrite;
+}
+
+static void
+write_end(char **pos, size_t *len)
+{
+ char *p;
+
+ assert_return(pos && *pos);
+
+ p = *pos;
+ p[0] = 0x00;
+ p[1] = 0x00;
+
+ *pos += 2;
+ if (len)
+ *len += 2;
+}
+
+enum rcon_packet_type {
+ RCON_PACKET_LOGIN = 3,
+ RCON_PACKET_LOGIN_OK = 2,
+ RCON_PACKET_LOGIN_FAIL = -1,
+ RCON_PACKET_COMMAND = 2,
+ RCON_PACKET_RESPONSE = 0,
+};
+
+static void
+create_packet(struct rcon *rcon, int32_t reqid,
+ enum rcon_packet_type type, const char *msg)
+{
+ char *pos;
+
+ assert_return(rcon && !empty_str(msg));
+
+ /* Body */
+ pos = &rcon->tbuf.buf[4];
+ rcon->tbuf.len = 4;
+ write_int(&pos, &rcon->tbuf.len, reqid);
+ write_int(&pos, &rcon->tbuf.len, type);
+ write_str(&pos, &rcon->tbuf.len, msg);
+ write_end(&pos, &rcon->tbuf.len);
+
+ /* Header (length of body) */
+ pos = &rcon->tbuf.buf[0];
+ write_int(&pos, NULL, rcon->tbuf.len - 4);
+
+ debug(DBG_RCON, "created packet (reqid: %" PRIi32 ", type %" PRIi32
+ ", len %zu, payload: %s)",
+ reqid, type, rcon->tbuf.len, msg);
+}
+
+static int
+packet_complete(struct uring_task *task, int res)
+{
+ char *pos;
+ size_t len;
+ int32_t plen;
+
+ assert_return(task, 0);
+ assert_task_alive_or(DBG_RCON, task, return -EINTR);
+
+ pos = task->tbuf->buf;
+ len = task->tbuf->len;
+ if (task->tbuf->len < 14)
+ return 0;
+
+ plen = read_int(&pos, &len);
+ debug(DBG_RCON, "reply size: %zu bytes, packet size %" PRIi32,
+ task->tbuf->len, plen + 4);
+
+ if (task->tbuf->len < plen + 4)
+ return 0;
+ else
+ return 1;
+}
+
+static bool
+rcon_read_packet(struct rcon *rcon, int32_t *id,
+ int32_t *type, char **rmsg)
+{
+ char *pos;
+ size_t len;
+ int32_t plen;
+
+ assert_return(rcon && id && type && rmsg, false);
+
+ pos = rcon->tbuf.buf;
+ len = rcon->tbuf.len;
+ plen = read_int(&pos, &len);
+ *id = read_int(&pos, &len);
+ *type = read_int(&pos, &len);
+ *rmsg = NULL;
+
+ if (plen < 10) {
+ error("invalid packet length: %" PRIi32, plen);
+ return false;
+ }
+
+ if (len > 2) {
+ *rmsg = pos;
+ pos += len - 2;
+ len = 2;
+ }
+
+ if (len < 2) {
+ error("short message");
+ return false;
+ }
+
+ if (pos[0] != '\0' || pos[1] != '\0') {
+ error("invalid trailer");
+ return false;
+ }
+
+ debug(DBG_RCON, "response - len: %" PRIi32 ", id: %" PRIi32
+ ", type: %" PRIi32 ", msg: %s",
+ plen, *id, *type, *rmsg);
+
+ return true;
+}
+
+static void
+rcon_stop_reply(struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+ int32_t id;
+ int32_t type;
+ char *msg;
+
+ assert_return(task);
+ assert_task_alive(DBG_RCON, task);
+
+ if (res < 0) {
+ debug(DBG_RCON, "res: %i", res);
+ goto out;
+ }
+
+ debug(DBG_RCON, "packet complete");
+ rcon_read_packet(rcon, &id, &type, &msg);
+
+ if (id != 2) {
+ error("rcon stop failed - reply id (%" PRIi32 ")", id);
+ goto out;
+ } else if (type != RCON_PACKET_RESPONSE) {
+ error("rcon stop failed - reply type (%" PRIi32 ")", type);
+ goto out;
+ }
+
+ verbose("rcon stop successful (%s)", msg);
+
+out:
+ uring_task_put(&rcon->task);
+}
+
+static void
+rcon_stop_sent(struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+
+ assert_return(task);
+ assert_task_alive(DBG_RCON, task);
+
+ if (res < 0) {
+ debug(DBG_RCON, "res: %i", res);
+ uring_task_put(&rcon->task);
+ return;
+ }
+
+ debug(DBG_RCON, "stop cmd sent");
+ uring_tbuf_read_until(&rcon->task, packet_complete, rcon_stop_reply);
+}
+
+static void
+rcon_login_reply(struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+ int32_t id;
+ int32_t type;
+ char *msg;
+
+ assert_return(task);
+ assert_task_alive(DBG_RCON, task);
+
+ if (res < 0) {
+ debug(DBG_RCON, "res: %i", res);
+ goto error;
+ }
+
+ debug(DBG_RCON, "packet complete");
+ rcon_read_packet(rcon, &id, &type, &msg);
+
+ if (id != 1) {
+ error("rcon login failed - unexpected reply id (%" PRIi32 ")", id);
+ goto error;
+ } else if (type == RCON_PACKET_LOGIN_FAIL) {
+ error("rcon login failed - incorrect password");
+ goto error;
+ } else if (type != RCON_PACKET_LOGIN_OK) {
+ error("rcon login failed - unexpected reply type (%" PRIi32 ")", type);
+ goto error;
+ }
+
+ debug(DBG_RCON, "rcon login successful");
+ create_packet(rcon, 2, RCON_PACKET_COMMAND, "stop");
+ uring_tbuf_write(&rcon->task, rcon_stop_sent);
+ return;
+
+error:
+ uring_task_put(&rcon->task);
+}
+
+static void
+rcon_login_sent(struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+
+ assert_return(task);
+ assert_task_alive(DBG_RCON, task);
+
+ if (res < 0) {
+ debug(DBG_RCON, "res: %i", res);
+ uring_task_put(&rcon->task);
+ return;
+ }
+
+ debug(DBG_RCON, "login sent");
+ uring_tbuf_read_until(&rcon->task, packet_complete, rcon_login_reply);
+}
+
+static void
+rcon_connected_cb(struct connection *conn, bool connected)
+{
+ struct rcon *rcon = container_of(conn, struct rcon, conn);
+
+ assert_return(conn);
+ assert_task_alive(DBG_RCON, &rcon->task);
+
+ if (!connected) {
+ error("rcon connection to remote server (%s) failed",
+ rcon->server->name);
+ uring_task_put(&rcon->task);
+ return;
+ }
+
+ create_packet(rcon, 1, RCON_PACKET_LOGIN, rcon->server->rcon_password);
+ uring_tbuf_write(&rcon->task, rcon_login_sent);
+}
+
+static void
+rcon_free(struct uring_task *task)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+
+ assert_return(task);
+
+ debug(DBG_RCON, "task %p, idle %p", task, rcon);
+ rcon->server->rcon = NULL;
+ xfree(rcon);
+}
+
+void
+rcon_refdump(struct rcon *rcon)
+{
+ assert_return_silent(rcon);
+
+ uring_task_refdump(&rcon->task);
+}
+
+void
+rcon_delete(struct server *server)
+{
+ assert_return_silent(server->rcon);
+
+ debug(DBG_RCON, "closing fd %i", server->rcon->task.fd);
+ uring_task_destroy(&server->rcon->task);
+ server->rcon = NULL;
+}
+
+void
+rcon_init(struct server *server)
+{
+ struct rcon *rcon;
+
+ assert_return(server && !server->rcon && !list_empty(&server->rcons) && !empty_str(server->rcon_password));
+
+ rcon = zmalloc(sizeof(*rcon));
+ if (!rcon)
+ die("malloc: %m");
+
+ uring_task_init(&rcon->task, "rcon", &server->task, rcon_free);
+ uring_task_set_buf(&rcon->task, &rcon->tbuf);
+ rcon->server = server;
+ server->rcon = rcon;
+
+ connect_any(&rcon->task, &server->rcons, &rcon->conn, rcon_connected_cb);
+}