#include #include #include #include #include #include #include #include #include #include #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]; struct uring_task_buf tbuf; }; 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; xfree(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); /* FIXME: Won't the refcount be wrong? */ 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->tbuf.buf[4]; /* Body */ 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); fprintf(stderr, "Created packet (reqid: %" PRIi32 ", type %" PRIi32 ", len %zu, payload: %s)\n", reqid, type, rcon->tbuf.len, msg); } static int packet_complete(struct cfg *cfg, struct uring_task *task, int res) { char *pos = task->tbuf->buf; size_t len = task->tbuf->len; int32_t plen; if (task->tbuf->len < 14) return 0; plen = read_int(&pos, &len); fprintf(stderr, "Reply is %zu bytes, packet size %" PRIi32 "\n", task->tbuf->len, plen + 4); if (task->tbuf->len < plen + 4) return 0; else return 1; } static bool read_packet(struct cfg *cfg, struct rcon *rcon, int32_t *id, int32_t *type, char **rmsg) { char *pos = rcon->tbuf.buf; size_t len = rcon->tbuf.len; 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) { *rmsg = pos; 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; 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); 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; } fprintf(stderr, "%s: stop cmd sent\n", __func__); uring_tbuf_read_until(cfg, &rcon->task, packet_complete, 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; 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; } fprintf(stderr, "RCon login successful\n"); create_packet(cfg, rcon, 2, RCON_PACKET_COMMAND, "stop"); uring_tbuf_write(cfg, &rcon->task, rcon_stop_sent); 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; } fprintf(stderr, "%s: login sent\n", __func__); uring_tbuf_read_until(cfg, &rcon->task, packet_complete, 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_tbuf_write(cfg, &rcon->task, 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); uring_task_set_buf(&rcon->task, &rcon->tbuf); rcon->server = server; server->rcon = rcon; rcon_connect_next_rcon(cfg, rcon); }