#include #include #include #include #include #include #include #include #include #include #include #include "main.h" #include "uring.h" #include "server.h" #include "server-rcon.h" 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 rcon_create_packet(struct uring_task_buf *tbuf, int32_t reqid, enum rcon_packet_type type, const char *msg) { char *pos; assert_return(tbuf && !empty_str(msg)); /* Body */ pos = &tbuf->buf[4]; tbuf->len = 4; write_int(&pos, &tbuf->len, reqid); write_int(&pos, &tbuf->len, type); write_str(&pos, &tbuf->len, msg); write_end(&pos, &tbuf->len); /* Header (length of body) */ pos = &tbuf->buf[0]; write_int(&pos, NULL, tbuf->len - 4); debug(DBG_RCON, "created packet (reqid: %" PRIi32 ", type %" PRIi32 ", len %zu, payload: %s)", reqid, type, tbuf->len, msg); } static int rcon_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 uring_task_buf *tbuf, int32_t *id, int32_t *type, char **rmsg) { char *pos; size_t len; int32_t plen; assert_return(tbuf && id && type && rmsg, false); pos = tbuf->buf; len = 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 server *server = container_of(task, struct server, rcon_task); int32_t id; int32_t type; char *msg; assert_return(task); assert_task_alive(DBG_RCON, task); if (res < 0) { error("rcon(%s): reading stop cmd reply failed, res: %i", server->name, res); goto out; } rcon_read_packet(task->tbuf, &id, &type, &msg); if (id != 2) { error("rcon(%s): stop cmd failed, reply id (%" PRIi32 ")", server->name, id); goto out; } else if (type != RCON_PACKET_RESPONSE) { error("rcon(%s): stop cmd failed, reply type (%" PRIi32 ")", server->name, type); goto out; } verbose("rcon(%s): stop command sent, reply: %s", server->name, msg); out: uring_task_close_fd(task); } static void rcon_stop_sent(struct uring_task *task, int res) { struct server *server = container_of(task, struct server, rcon_task); assert_return(task); assert_task_alive(DBG_RCON, task); if (res != task->tbuf->len) { error("rcon(%s): sending stop cmd failed, res: %i", server->name, res); uring_task_close_fd(task); return; } debug(DBG_RCON, "rcon(%s): stop cmd sent", server->name); uring_tbuf_read_until(task, rcon_packet_complete, rcon_stop_reply); } static void rcon_login_reply(struct uring_task *task, int res) { struct server *server = container_of(task, struct server, rcon_task); int32_t id; int32_t type; char *msg; assert_return(task); assert_task_alive(DBG_RCON, task); if (res < 0) { error("rcon(%s): reading login reply failed, res: %i", server->name, res); goto error; } rcon_read_packet(task->tbuf, &id, &type, &msg); if (id != 1) { error("rcon(%s): login failed, reply id (%" PRIi32 ")", server->name, id); goto error; } else if (type == RCON_PACKET_LOGIN_FAIL) { error("rcon(%s): login failed, incorrect password", server->name); goto error; } else if (type != RCON_PACKET_LOGIN_OK) { error("rcon(%s): login failed, reply type (%" PRIi32 ")", server->name, type); goto error; } debug(DBG_RCON, "rcon(%s): login successful", server->name); rcon_create_packet(task->tbuf, 2, RCON_PACKET_COMMAND, "stop"); uring_tbuf_write(task, rcon_stop_sent); return; error: uring_task_close_fd(task); } static void rcon_login_sent(struct uring_task *task, int res) { struct server *server = container_of(task, struct server, rcon_task); assert_return(task); assert_task_alive(DBG_RCON, task); if (res != task->tbuf->len) { error("rcon(%s): sending login failed, res: %i", server->name, res); uring_task_close_fd(task); return; } debug(DBG_RCON, "rcon(%s): login sent", server->name); uring_tbuf_read_until(task, rcon_packet_complete, rcon_login_reply); } static void rcon_connected_cb(struct connection *conn, bool connected) { struct server *server = container_of(conn, struct server, rcon_conn); assert_return(conn); assert_task_alive(DBG_RCON, &server->rcon_task); if (!connected) { error("rcon (%s): connection failed", server->name); return; } rcon_create_packet(&server->rcon_tbuf, 1, RCON_PACKET_LOGIN, server->rcon_password); uring_tbuf_write(&server->rcon_task, rcon_login_sent); } static void rcon_free(struct uring_task *task) { struct server *server = container_of(task, struct server, rcon_task); assert_return(task); debug(DBG_RCON, "task %p, server %s (%p)", task, server->name, server); } void rcon_stop(struct server *server) { assert_return(server && !list_empty(&server->rcons) && !empty_str(server->rcon_password)); assert_task_alive(DBG_RCON, &server->rcon_task); connect_any(&server->rcon_task, &server->rcons, &server->rcon_conn, rcon_connected_cb); } void rcon_refdump(struct server *server) { assert_return(server); uring_task_refdump(&server->rcon_task); } void rcon_delete(struct server *server) { assert_return(server); debug(DBG_RCON, "closing fd %i", server->rcon_task.fd); uring_task_destroy(&server->rcon_task); } void rcon_init(struct server *server) { assert_return(server); uring_task_init(&server->rcon_task, "rcon", &server->task, rcon_free); uring_task_set_buf(&server->rcon_task, &server->rcon_tbuf); }