From 7a8bee84bd1f41bf349f9077617e20c3560b7c62 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Tue, 23 Jun 2020 15:19:44 +0200 Subject: Split out rcon protocol handling to separate file --- meson.build | 1 + rcon-protocol.c | 185 +++++++++++++++++++++++++++++++++++++++++++++++++ rcon-protocol.h | 38 ++++++++++ server-rcon.c | 211 +++++++++----------------------------------------------- 4 files changed, 258 insertions(+), 177 deletions(-) create mode 100644 rcon-protocol.c create mode 100644 rcon-protocol.h diff --git a/meson.build b/meson.build index ca38341..3d82301 100644 --- a/meson.build +++ b/meson.build @@ -40,6 +40,7 @@ mcproxy_sources = [ 'server-proxy.c', 'server-config.c', 'server-rcon.c', + 'rcon-protocol.c', 'announce.c', 'config-parser.c', 'idle.c', diff --git a/rcon-protocol.c b/rcon-protocol.c new file mode 100644 index 0000000..0ea1245 --- /dev/null +++ b/rcon-protocol.c @@ -0,0 +1,185 @@ +#include +#include +#include +#include + +#include "rcon-protocol.h" + +static int32_t +read_int(const char **pos, size_t *len) +{ + uint32_t val; + const char *p; + + if (!pos || !*pos) + return 0; + + if (len && *len < RCON_INT_LEN) + 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 += RCON_INT_LEN; + if (len) + *len -= RCON_INT_LEN; + + return (int32_t)val; +} + +static void +write_int(char **pos, size_t *len, int32_t orig) +{ + uint32_t val = (uint32_t)orig; + char *p; + + if (!pos || !*pos) + return; + + p = *pos; + p[0] = (val >> 0) & 0xff; + p[1] = (val >> 8) & 0xff; + p[2] = (val >> 16) & 0xff; + p[3] = (val >> 24) & 0xff; + + *pos += RCON_INT_LEN; + if (len) + *len += RCON_INT_LEN; +} + +static void +write_str(char **pos, size_t *len, const char *str, size_t slen) +{ + if (!pos || !*pos || !str || *str == '\0' || slen < 1) + return; + + memcpy(*pos, str, slen); + *pos += slen; + if (len) + *len += slen; +} + +static void +write_end(char **pos, size_t *len) +{ + char *p; + + if (!pos || !*pos) + return; + + p = *pos; + p[0] = 0x00; + p[1] = 0x00; + + *pos += RCON_END_LEN; + if (len) + *len += RCON_END_LEN; +} + +bool +rcon_protocol_create_packet(char *buf, size_t len, size_t *rlen, int32_t reqid, + enum rcon_packet_type type, const char *msg) +{ + size_t plen, msglen; + char *pos; + + if (!buf || !rlen || !msg || msg[0] == '\0') + return false; + + msglen = strlen(msg); + if (len < rcon_protocol_packet_len(msglen)) + return false; + + /* Body */ + pos = buf + RCON_HDR_LEN; + plen = RCON_HDR_LEN; + write_int(&pos, &plen, reqid); + write_int(&pos, &plen, type); + write_str(&pos, &plen, msg, msglen); + write_end(&pos, &plen); + + /* Header (length of body) */ + pos = buf; + write_int(&pos, NULL, plen - RCON_HDR_LEN); + + *rlen = plen; + return true; +} + +/* + * Returns: + * < 0 = error + * 0 = packet not complete + * > 0 = packet complete (length) + */ +int32_t +rcon_protocol_packet_complete(const char *buf, size_t len) +{ + const char *pos; + int32_t plen; + + if (!buf) + return -1; + + if (len < RCON_PKT_MIN_LEN) + return 0; + + pos = buf; + plen = read_int(&pos, NULL) + RCON_HDR_LEN; + + if (plen < RCON_HDR_LEN) + /* Read a negative int */ + return -1; + + if (len < plen) + return 0; + else + return plen; +} + +bool +rcon_protocol_read_packet(const char *buf, size_t len, int32_t *id, int32_t *type, + const char **rmsg, const char **error) +{ + const char *pos; + int32_t plen; + + if (!error) + return false; + + if (!buf || !id || !type || !rmsg) { + *error = "invalid arguments"; + return false; + } + + plen = rcon_protocol_packet_complete(buf, len); + *error = "invalid packet length"; + if (plen < RCON_PKT_MIN_LEN) + return false; + + pos = buf + RCON_HDR_LEN; + len -= RCON_HDR_LEN; + + *id = read_int(&pos, &len); + *type = read_int(&pos, &len); + *rmsg = NULL; + + if (len < RCON_END_LEN) + return false; + else if (len > RCON_END_LEN) { + *rmsg = pos; + pos += len - RCON_END_LEN; + len = RCON_END_LEN; + } + + if (pos[0] != '\0' || pos[1] != '\0') { + *error = "invalid trailer"; + return false; + } + + *error = NULL; + return true; +} diff --git a/rcon-protocol.h b/rcon-protocol.h new file mode 100644 index 0000000..fd83d79 --- /dev/null +++ b/rcon-protocol.h @@ -0,0 +1,38 @@ +#ifndef foorconprotocolhfoo +#define foorconprotocolhfoo + +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, +}; + +#define RCON_INT_LEN 4 + +#define RCON_END_LEN 2 + +#define RCON_HDR_LEN 4 + +/* header + reqid + type + end */ +#define RCON_PKT_MIN_LEN (RCON_HDR_LEN + 2 * RCON_INT_LEN + RCON_END_LEN) + +static inline size_t +rcon_protocol_packet_len(size_t msglen) +{ + /* header + reqid + type + msg + end */ + return (RCON_PKT_MIN_LEN + msglen); +} + +bool rcon_protocol_create_packet(char *buf, size_t len, size_t *rlen, + int32_t reqid, enum rcon_packet_type type, + const char *msg); + +int32_t rcon_protocol_packet_complete(const char *buf, size_t len); + +bool rcon_protocol_read_packet(const char *buf, size_t len, int32_t *id, + int32_t *type, const char **rmsg, + const char **error); + +#endif diff --git a/server-rcon.c b/server-rcon.c index 710d969..1f8ef70 100644 --- a/server-rcon.c +++ b/server-rcon.c @@ -14,181 +14,42 @@ #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); -} +#include "rcon-protocol.h" static int rcon_packet_complete(struct uring_task *task, int res) { - char *pos; - size_t len; - int32_t plen; - - assert_return(task, 0); + assert_return(task, -EINVAL); 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; + return rcon_protocol_packet_complete(task->tbuf->buf, task->tbuf->len); } static bool -rcon_read_packet(struct uring_task_buf *tbuf, int32_t *id, - int32_t *type, char **rmsg) +rcon_check_reply(struct uring_task *task, int res, int32_t *id, + int32_t *type, const char **msg) { - 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; - } + struct server *server = container_of(task, struct server, rcon_task); + const char *error; - if (len < 2) { - error("short message"); - return false; + if (res < 0) { + error("rcon(%s): reading reply failed, res: %i", + server->name, res); + goto error; } - if (pos[0] != '\0' || pos[1] != '\0') { - error("invalid trailer"); - return false; + if (!rcon_protocol_read_packet(task->tbuf->buf, task->tbuf->len, + id, type, msg, &error)) { + error("rcon(%s): failed to parse packet: %s", + server->name, error); + goto error; } - debug(DBG_RCON, "response - len: %" PRIi32 ", id: %" PRIi32 - ", type: %" PRIi32 ", msg: %s", - plen, *id, *type, *rmsg); - return true; + +error: + uring_task_close_fd(task); + return false; } static void @@ -197,18 +58,13 @@ 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; + const 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 (!rcon_check_reply(task, res, &id, &type, &msg)) + return; if (id != 2) { error("rcon(%s): stop cmd failed, reply id (%" PRIi32 ")", @@ -251,18 +107,13 @@ 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; + const 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 (!rcon_check_reply(task, res, &id, &type, &msg)) + return; if (id != 1) { error("rcon(%s): login failed, reply id (%" PRIi32 ")", @@ -279,7 +130,9 @@ rcon_login_reply(struct uring_task *task, int res) } debug(DBG_RCON, "rcon(%s): login successful", server->name); - rcon_create_packet(task->tbuf, 2, RCON_PACKET_COMMAND, "stop"); + rcon_protocol_create_packet(task->tbuf->buf, sizeof(task->tbuf->buf), + &task->tbuf->len, 2, RCON_PACKET_COMMAND, + "stop"); uring_tbuf_write(task, rcon_stop_sent); return; @@ -319,7 +172,11 @@ rcon_connected_cb(struct connection *conn, bool connected) return; } - rcon_create_packet(&server->rcon_tbuf, 1, RCON_PACKET_LOGIN, server->rcon_password); + rcon_protocol_create_packet(server->rcon_tbuf.buf, + sizeof(server->rcon_tbuf.buf), + &server->rcon_tbuf.len, 1, + RCON_PACKET_LOGIN, + server->rcon_password); uring_tbuf_write(&server->rcon_task, rcon_login_sent); } -- cgit v1.2.3