summaryrefslogtreecommitdiff
path: root/rcon.c
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-09 20:51:44 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-09 20:51:44 +0200
commit09f1abfe18258807c412bce88ad459984add0cd3 (patch)
treeccbd07eabfb4c1a08de912d2dd0d09678cca090f /rcon.c
parent5fd0055aee5124b9ad6573e5ed363d5bf63bcf80 (diff)
Add an rcon shutdown method
Diffstat (limited to 'rcon.c')
-rw-r--r--rcon.c425
1 files changed, 425 insertions, 0 deletions
diff --git a/rcon.c b/rcon.c
new file mode 100644
index 0000000..756d480
--- /dev/null
+++ b/rcon.c
@@ -0,0 +1,425 @@
+#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 "main.h"
+#include "uring.h"
+#include "config.h"
+#include "server.h"
+#include "rcon.h"
+
+struct rcon {
+ struct server *server;
+ struct uring_task task;
+ unsigned next_rcon;
+ struct sockaddr_in46 rcon;
+ char rconstr[ADDRSTRLEN];
+ char rconbuf[4096];
+ size_t rconbuflen;
+ size_t rconbufdone;
+};
+
+static void
+rcon_free(struct uring_task *task)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+
+ fprintf(stderr, "%s: called with task 0x%p and idle 0x%p\n", __func__, task, rcon);
+ rcon->server->rcon = NULL;
+ free(rcon);
+}
+
+void
+rcon_refdump(struct rcon *rcon)
+{
+ if (!rcon)
+ return;
+
+ uring_task_refdump(&rcon->task);
+}
+
+void
+rcon_delete(struct cfg *cfg, struct server *server)
+{
+ struct rcon *rcon = server->rcon;
+
+ if (!rcon)
+ return;
+
+ fprintf(stderr, "%s called, closing fd %i\n", __func__, rcon->task.fd);
+ uring_cancel(cfg, &rcon->task);
+ uring_task_put(cfg, &rcon->task);
+ server->rcon = NULL;
+}
+
+static int32_t
+read_int(char **pos, size_t *len)
+{
+ uint32_t val;
+ char *p = *pos;
+
+ if (len && *len < 4)
+ return 0;
+
+ 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 = *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 = strlen(str);
+
+ memcpy(*pos, str, towrite);
+ *pos += towrite;
+ if (len)
+ *len += towrite;
+}
+
+static void
+write_end(char **pos, size_t *len)
+{
+ char *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 cfg *cfg, struct rcon *rcon, int32_t reqid, enum rcon_packet_type type, const char *msg)
+{
+ char *pos = &rcon->rconbuf[4];
+
+ rcon->rconbufdone = 0;
+ rcon->rconbuflen = 4;
+ write_int(&pos, &rcon->rconbuflen, reqid);
+ write_int(&pos, &rcon->rconbuflen, type);
+ write_str(&pos, &rcon->rconbuflen, msg);
+ write_end(&pos, &rcon->rconbuflen);
+ pos = &rcon->rconbuf[0];
+ write_int(&pos, NULL, rcon->rconbuflen - 4);
+
+ fprintf(stderr, "Created packet (reqid: %" PRIi32 ", type %" PRIi32 ", len %zu, payload: %s)\n",
+ reqid, type, rcon->rconbuflen, msg);
+}
+
+static bool
+packet_complete(struct cfg *cfg, struct rcon *rcon)
+{
+ char *pos = rcon->rconbuf;
+ size_t len = rcon->rconbufdone;
+ int32_t plen;
+
+ if (rcon->rconbufdone < 14)
+ return false;
+
+ plen = read_int(&pos, &len);
+ fprintf(stderr, "Reply is %zu bytes, packet size %" PRIi32 "\n", rcon->rconbufdone, plen + 4);
+ if (rcon->rconbufdone < plen + 4)
+ return false;
+ else
+ return true;
+}
+
+static bool
+read_packet(struct cfg *cfg, struct rcon *rcon, int32_t *id, int32_t *type, char **rmsg)
+{
+ char *pos = rcon->rconbuf;
+ size_t len = rcon->rconbufdone;
+ int32_t plen;
+
+ plen = read_int(&pos, &len);
+ *id = read_int(&pos, &len);
+ *type = read_int(&pos, &len);
+ *rmsg = NULL;
+
+ if (plen < 10) {
+ fprintf(stderr, "Invalid packet length: %" PRIi32 "\n", plen);
+ return false;
+ }
+
+ fprintf(stderr, "Remaining = %zu\n", len);
+ if (len > 2) {
+ char *msg;
+
+ msg = malloc(len - 1);
+ if (!msg) {
+ perror("malloc");
+ return false;
+ }
+
+ memcpy(msg, pos, len - 2);
+ msg[len - 2] = '\0';
+
+ *rmsg = msg;
+ pos += len - 2;
+ len = 2;
+ }
+
+ if (len < 2) {
+ fprintf(stderr, "Short message\n");
+ return false;
+ }
+
+ if (pos[0] != '\0' || pos[1] != '\0') {
+ fprintf(stderr, "Invalid trailer\n");
+ return false;
+ }
+
+ fprintf(stderr, "Response - len: %" PRIi32 ", id: %" PRIi32
+ ", type: %" PRIi32 ", msg: %s\n",
+ plen, *id, *type, *rmsg);
+
+ return true;
+}
+
+static void
+rcon_stop_reply(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+ int32_t id;
+ int32_t type;
+ char *msg;
+
+ fprintf(stderr, "%s: result %i\n", __func__, res);
+ if (res < 0)
+ goto out;
+
+ rcon->rconbufdone += res;
+
+ /* FIXME: could be multiple packets */
+ if (packet_complete(cfg, rcon)) {
+ fprintf(stderr, "Packet complete\n");
+ read_packet(cfg, rcon, &id, &type, &msg);
+ if (id != 2) {
+ fprintf(stderr, "RCon stop cmd failed - unexpected reply id (%" PRIi32 ")\n", id);
+ goto out;
+ } else if (type != RCON_PACKET_RESPONSE) {
+ fprintf(stderr, "RCon stop cmd failed - unexpected reply type (%" PRIi32 ")\n", type);
+ goto out;
+ }
+ fprintf(stderr, "RCon stop successful (%s)\n", msg);
+ free(msg);
+
+ } else {
+ fprintf(stderr, "Packet not complete\n");
+ uring_read(cfg, &rcon->task, &rcon->rconbuf + rcon->rconbufdone,
+ sizeof(rcon->rconbuf) - rcon->rconbufdone, 0, rcon_stop_reply);
+ }
+
+out:
+ uring_task_put(cfg, &rcon->task);
+}
+
+static void
+rcon_stop_sent(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+
+ fprintf(stderr, "%s: result %i\n", __func__, res);
+ if (res < 0) {
+ uring_task_put(cfg, &rcon->task);
+ return;
+ }
+
+ rcon->rconbufdone += res;
+ if (rcon->rconbufdone < rcon->rconbuflen) {
+ uring_write(cfg, &rcon->task, rcon->rconbuf + rcon->rconbufdone,
+ rcon->rconbuflen - rcon->rconbufdone, rcon_stop_sent);
+ return;
+ }
+
+ fprintf(stderr, "%s: stop cmd sent\n", __func__);
+ rcon->rconbufdone = 0;
+ rcon->rconbuflen = 0;
+ uring_read(cfg, &rcon->task, &rcon->rconbuf, sizeof(rcon->rconbuf), 0, rcon_stop_reply);
+}
+
+static void
+rcon_login_reply(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+ int32_t id;
+ int32_t type;
+ char *msg;
+
+ fprintf(stderr, "%s: result %i\n", __func__, res);
+ if (res < 0)
+ goto out;
+
+ rcon->rconbufdone += res;
+
+ if (packet_complete(cfg, rcon)) {
+ fprintf(stderr, "Packet complete\n");
+ read_packet(cfg, rcon, &id, &type, &msg);
+ if (id != 1) {
+ fprintf(stderr, "RCon login failed - unexpected reply id (%" PRIi32 ")\n", id);
+ goto out;
+ } else if (type == RCON_PACKET_LOGIN_FAIL) {
+ fprintf(stderr, "RCon login failed - incorrect password\n");
+ goto out;
+ } else if (type != RCON_PACKET_LOGIN_OK) {
+ fprintf(stderr, "RCon login failed - unexpected reply type (%" PRIi32 ")\n", type);
+ goto out;
+ }
+ free(msg);
+ fprintf(stderr, "RCon login successful\n");
+ create_packet(cfg, rcon, 2, RCON_PACKET_COMMAND, "stop");
+ uring_write(cfg, &rcon->task, rcon->rconbuf, rcon->rconbuflen, rcon_stop_sent);
+
+ } else {
+ fprintf(stderr, "Packet not complete\n");
+ uring_read(cfg, &rcon->task, &rcon->rconbuf + rcon->rconbufdone,
+ sizeof(rcon->rconbuf) - rcon->rconbufdone, 0, rcon_login_reply);
+ }
+
+ return;
+
+out:
+ uring_task_put(cfg, &rcon->task);
+}
+
+static void
+rcon_login_sent(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+
+ fprintf(stderr, "%s: result %i\n", __func__, res);
+ if (res < 0) {
+ uring_task_put(cfg, &rcon->task);
+ return;
+ }
+
+ rcon->rconbufdone += res;
+ if (rcon->rconbufdone < rcon->rconbuflen) {
+ uring_write(cfg, &rcon->task, rcon->rconbuf + rcon->rconbufdone,
+ rcon->rconbuflen - rcon->rconbufdone, rcon_login_sent);
+ return;
+ }
+
+ fprintf(stderr, "%s: login sent\n", __func__);
+ rcon->rconbufdone = 0;
+ rcon->rconbuflen = 0;
+ uring_read(cfg, &rcon->task, &rcon->rconbuf, sizeof(rcon->rconbuf), 0, rcon_login_reply);
+}
+
+static void rcon_connect_next_rcon(struct cfg *cfg, struct rcon *rcon);
+
+static void
+rcon_connected(struct cfg *cfg, struct uring_task *task, int res)
+{
+ struct rcon *rcon = container_of(task, struct rcon, task);
+
+ fprintf(stderr, "%s: connected %i\n", __func__, res);
+ if (res < 0) {
+ rcon_connect_next_rcon(cfg, rcon);
+ return;
+ }
+
+ create_packet(cfg, rcon, 1, RCON_PACKET_LOGIN, rcon->server->rcon_password);
+ uring_write(cfg, &rcon->task, rcon->rconbuf, rcon->rconbuflen, rcon_login_sent);
+}
+
+/* FIXME: Parts of this could be shared with proxy.c, probably in server.c */
+static void
+rcon_connect_next_rcon(struct cfg *cfg, struct rcon *rcon)
+{
+ struct sockaddr_in46 *remote, *tmp;
+ struct server *scfg = rcon->server;
+ int sfd;
+ unsigned i = 0;
+
+again:
+ remote = NULL;
+ list_for_each_entry(tmp, &scfg->rcons, list) {
+ if (i == rcon->next_rcon) {
+ remote = tmp;
+ break;
+ }
+ i++;
+ }
+
+ if (!remote) {
+ fprintf(stderr, "No more rcon addresses to attempt\n");
+ uring_task_put(cfg, &rcon->task);
+ return;
+ }
+
+ rcon->next_rcon++;
+ rcon->rcon = *remote;
+ sockaddr_to_str(&rcon->rcon, rcon->rconstr, sizeof(rcon->rconstr));
+ fprintf(stderr, "%s: attempting rcon connection to %s (len %u)\n",
+ scfg->name, rcon->rconstr, rcon->rcon.addrlen);
+
+ sfd = socket(rcon->rcon.storage.ss_family, SOCK_STREAM, 0);
+ if (sfd < 0) {
+ perror("socket");
+ goto again;
+ }
+
+ uring_task_set_fd(&rcon->task, sfd);
+ uring_connect(cfg, &rcon->task, &rcon->rcon, rcon_connected);
+}
+
+void
+rcon_init(struct cfg *cfg, struct server *server)
+{
+ struct rcon *rcon;
+
+ if (!server)
+ return;
+
+ if (list_empty(&server->rcons) || !server->rcon_password)
+ return;
+
+ rcon = zmalloc(sizeof(*rcon));
+ if (!rcon)
+ perrordie("malloc");
+
+ uring_task_init(&rcon->task, "rcon", &server->task, rcon_free);
+ rcon->server = server;
+ server->rcon = rcon;
+
+ rcon_connect_next_rcon(cfg, rcon);
+}