From 460b10553ac898232bfc5444e335dfa938690d1b Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Wed, 24 Jun 2020 00:06:17 +0200 Subject: Make minecctl actually do something --- minecctl/meson.build | 1 + minecctl/minecctl.c | 322 ++++++++++++++++++++++++++++++++++++++--- minecproxy/misc.c | 2 +- minecproxy/server-config.c | 80 +--------- shared/config-parser.c | 11 +- shared/server-config-options.h | 84 +++++++++++ shared/utils.h | 3 + 7 files changed, 401 insertions(+), 102 deletions(-) create mode 100644 shared/server-config-options.h diff --git a/minecctl/meson.build b/minecctl/meson.build index 3490338..ac8210d 100644 --- a/minecctl/meson.build +++ b/minecctl/meson.build @@ -9,6 +9,7 @@ minecctl_deps = [ executable( 'minecctl', minecctl_sources, + link_args: [ '-lanl' ], dependencies: minecctl_deps, ) diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c index e29dcef..d601980 100644 --- a/minecctl/minecctl.c +++ b/minecctl/minecctl.c @@ -1,39 +1,323 @@ +#define _GNU_SOURCE #include #include #include #include +#include +#include +#include +#include +#include +#include #include "rcon-protocol.h" +#include "utils.h" +#include "config-parser.h" +#include "server-config-options.h" -int -main(int argc, char **argv) +void +__debug(enum debug_lvl lvl, const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); +} + +__attribute__((noreturn)) void +__die(const char *fmt, ...) +{ + va_list ap; + + va_start(ap, fmt); + vfprintf(stderr, fmt, ap); + va_end(ap); + + exit(EXIT_FAILURE); +}; + +void * +__zmalloc(const char *fn, int line, size_t size) +{ + void *ptr; + + assert_die(!empty_str(fn) && line > 0 && size > 0, "invalid arguments"); + + ptr = calloc(1, size); + if (!ptr) + die("malloc: %m"); + return ptr; +} + +char * +__xstrdup(const char *fn, int line, const char *s) +{ + char *ptr; + + assert_die(!empty_str(fn) && line > 0 && !empty_str(s), "invalid arguments"); + + ptr = strdup(s); + if (!ptr) + die("strdup: %m"); + return ptr; +} + +char * +__xstrndup(const char *fn, int line, const char *s, size_t n) +{ + char *ptr; + + assert_die(!empty_str(fn) && line > 0 && !empty_str(s) && n > 0, "invalid arguments"); + + ptr = strndup(s, n); + if (ptr) + die("strdup: %m"); + return ptr; +} + +void +__xfree(const char *fn, int line, void *ptr) +{ + assert_die(!empty_str(fn) && line > 0, "invalid arguments"); + + free(ptr); +} + +static void +send_packet(int sfd, const char *buf, size_t len) +{ + size_t off = 0; + ssize_t r; + + while (true) { + r = write(sfd, buf + off, len - off); + if (r < 0) { + if (errno == EINTR) + continue; + else + die("Failed to write packet: %m"); + } + + off += r; + if (off == len) + break; + } +} + +static void +read_packet(int sfd, char *buf, size_t len, int32_t *id, int32_t *type, const char **msg) +{ + size_t off = 0; + size_t r; + const char *error; + + while (true) { + r = read(sfd, buf + off, len - off); + if (r < 0) { + if (errno == EINTR) + continue; + else + die("Failed to read reply: %m"); + } + + if (r == 0) + die("Failed, connection closed"); + + off += r; + if (rcon_protocol_packet_complete(buf, off)) + break; + + if (off >= len) + die("Reply too large %zu and %zu", off, len); + } + + if (!rcon_protocol_read_packet(buf, off, id, type, msg, &error)) + die("Failed to parse response: %s", error); +} + +static void +send_msg(int sfd, char *buf, size_t len, enum rcon_packet_type type, + const char *msg, enum rcon_packet_type *rtype, const char **reply) +{ + static unsigned rcon_packet_id = 1; + size_t plen; + int32_t id; + + if (!rcon_protocol_create_packet(buf, len, &plen, + rcon_packet_id, type, msg)) + die("Failed to create rcon packet"); + + send_packet(sfd, buf, plen); + read_packet(sfd, buf, len, &id, rtype, reply); + + if (id != rcon_packet_id) + die("Invalid reply id"); + + rcon_packet_id++; +} + +static void +send_login(int sfd, const char *password) +{ + char buf[4096]; + int32_t rtype; + const char *reply; + + send_msg(sfd, buf, sizeof(buf), RCON_PACKET_LOGIN, password, &rtype, &reply); + + if (rtype == RCON_PACKET_LOGIN_OK) + info("Login ok"); + else if (rtype == RCON_PACKET_LOGIN_FAIL) + die("Login failure, invalid password?"); + else + die("Invalid return code: %" PRIi32, rtype); +} + +static void +send_cmd(int sfd, const char *cmd) { char buf[4096]; - size_t len; - int32_t id, type; - const char *msg, *error; + int32_t rtype; + const char *reply; + + send_msg(sfd, buf, sizeof(buf), RCON_PACKET_COMMAND, "stop", &rtype, &reply); + + if (rtype == RCON_PACKET_RESPONSE) + info("Command (%s) sent, reply: %s", cmd, reply); + else + die("Invalid return code: %" PRIi32, rtype); +} - fprintf(stderr, "Started\n"); +static int +connect_any(struct list_head *addrs) +{ + struct saddr *saddr; + bool connected = false; + int sfd; + + list_for_each_entry(saddr, addrs, list) { + sfd = socket(saddr->storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); + if (sfd < 0) + die("socket: %m"); + + socket_set_low_latency(sfd, true, true, true); - if (!rcon_protocol_create_packet(buf, sizeof(buf), &len, - 1, RCON_PACKET_LOGIN, - "test")) { - fprintf(stderr, "Failed to create packet\n"); - exit(EXIT_FAILURE); + if (connect(sfd, (struct sockaddr *)&saddr->storage, saddr->addrlen) < 0) { + close(sfd); + continue; + } + + connected = true; + break; } - if (!rcon_protocol_packet_complete(buf, len)) { - fprintf(stderr, "Packet not complete\n"); - exit(EXIT_FAILURE); + if (!connected) + die("Failed to connect to remote host"); + + return sfd; +} + +static void +parse_config(char *buf, const char *filename, + const char **password, struct list_head *addrs) +{ + *password = NULL; + list_init(addrs); + + /* FIXME: filename argument is superfluous */ + if (!config_parse_header(filename, SERVER_CFG_HEADER, &buf)) + die("Unable to parse %s: invalid header", filename); + + /* FIXME: this will cause superflous DNS lookups of other cfg entries */ + while (true) { + int key; + const char *keyname; + struct cfg_value value; + + if (!config_parse_line(filename, &buf, scfg_key_map, + &key, &keyname, &value)) + break; + + switch (key) { + case SCFG_KEY_RCON: + list_replace(&value.saddrs, addrs); + break; + case SCFG_KEY_RCON_PASSWORD: + *password = value.str; + break; + default: + continue; + } + + if (*password && !list_empty(addrs)) + break; } - if (!rcon_protocol_read_packet(buf, len, &id, &type, &msg, &error)) { - fprintf(stderr, "Packet parsing failed: %s\n", error); - exit(EXIT_FAILURE); + if (!*password) + die("rcon password not found in %s", filename); + + if (list_empty(addrs)) + die("rcon address not found in %s", filename); +} + +static void +read_file(const char *filename, char *buf, size_t len) +{ + int fd; + size_t off = 0; + ssize_t r; + + fd = open(filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) + die("Failed to open %s: %m", filename); + + while (true) { + r = read(fd, buf + off, len - off - 1); + if (r < 0) + die("Failed to read %s: %m", filename); + else if (r == 0) + break; + + off += r; + if (off == len) + die("Failed to read %s: file too large", filename); } - fprintf(stderr, "Packet - id: %" PRIi32 ", type: %" PRIi32 ", msg: %s\n", - id, type, msg); + buf[off] = '\0'; + close(fd); +} + +int +main(int argc, char **argv) +{ + char buf[4096]; + const char *password; + const char *filename; + struct list_head addrs; + struct saddr *saddr; + int fd; + + debug_mask = DBG_ERROR | DBG_INFO; + + if (argc != 2) + die("Usage: minecctl CFGFILE"); + + filename = argv[1]; + + read_file(filename, buf, sizeof(buf)); + + parse_config(buf, filename, &password, &addrs); + + info("Password: %s", password); + list_for_each_entry(saddr, &addrs, list) + info("Address: %s", saddr->addrstr); + + fd = connect_any(&addrs); + + send_login(fd, password); + + send_cmd(fd, "stop"); exit(EXIT_SUCCESS); } diff --git a/minecproxy/misc.c b/minecproxy/misc.c index f954618..152de1a 100644 --- a/minecproxy/misc.c +++ b/minecproxy/misc.c @@ -250,7 +250,7 @@ again: sfd = socket(conn->remote.storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); if (sfd < 0) { - error("socket: %m"); + debug(DBG_SRV, "socket: %m"); goto again; } diff --git a/minecproxy/server-config.c b/minecproxy/server-config.c index 549cf16..4e16155 100644 --- a/minecproxy/server-config.c +++ b/minecproxy/server-config.c @@ -15,6 +15,7 @@ #include "config-parser.h" #include "server.h" #include "server-config.h" +#include "server-config-options.h" static void scfg_dns_cb(struct dns_async *dns, bool (*server_cb)(struct server *, struct saddr *)) @@ -108,83 +109,6 @@ scfg_rcon_dns_cb(struct dns_async *dns) scfg_dns_cb(dns, server_add_rcon); } -enum scfg_keys { - SCFG_KEY_INVALID = 0, - SCFG_KEY_TYPE, - SCFG_KEY_NAME, - SCFG_KEY_PORT, - SCFG_KEY_LOCAL, - SCFG_KEY_REMOTE, - SCFG_KEY_IDLE_TIMEOUT, - SCFG_KEY_STOP_METHOD, - SCFG_KEY_START_METHOD, - SCFG_KEY_STOP_EXEC, - SCFG_KEY_START_EXEC, - SCFG_KEY_RCON, - SCFG_KEY_RCON_PASSWORD, - SCFG_KEY_SYSTEMD_SERVICE, -}; - -struct cfg_key_value_map scfg_key_map[] = { - { - .key_name = "type", - .key_value = SCFG_KEY_TYPE, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "name", - .key_value = SCFG_KEY_NAME, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "port", - .key_value = SCFG_KEY_PORT, - .value_type = CFG_VAL_TYPE_UINT16, - }, { - .key_name = "local", - .key_value = SCFG_KEY_LOCAL, - .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, - }, { - .key_name = "remote", - .key_value = SCFG_KEY_REMOTE, - .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, - }, { - .key_name = "idle_timeout", - .key_value = SCFG_KEY_IDLE_TIMEOUT, - .value_type = CFG_VAL_TYPE_UINT16, - }, { - .key_name = "stop_method", - .key_value = SCFG_KEY_STOP_METHOD, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "start_method", - .key_value = SCFG_KEY_START_METHOD, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "stop_exec", - .key_value = SCFG_KEY_STOP_EXEC, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "start_exec", - .key_value = SCFG_KEY_START_EXEC, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "rcon", - .key_value = SCFG_KEY_RCON, - .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, - }, { - .key_name = "rcon_password", - .key_value = SCFG_KEY_RCON_PASSWORD, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = "systemd_service", - .key_value = SCFG_KEY_SYSTEMD_SERVICE, - .value_type = CFG_VAL_TYPE_STRING, - }, { - .key_name = NULL, - .key_value = SCFG_KEY_INVALID, - .value_type = CFG_VAL_TYPE_INVALID, - } -}; - static bool handle_dns(struct server *server, const char *type, struct cfg_value *value, dns_cb_t *async_cb, @@ -230,7 +154,7 @@ scfg_parse(struct server *server) pos = server->tbuf.buf; - if (!config_parse_header(server->name, "server", &pos)) + if (!config_parse_header(server->name, SERVER_CFG_HEADER, &pos)) return; while (true) { diff --git a/shared/config-parser.c b/shared/config-parser.c index 9c89cf2..1c9979e 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -60,7 +60,8 @@ get_line(char **pos) } static bool -dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, bool async) +dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, + unsigned *naddrs, bool async) { struct sockaddr_in *in4; struct sockaddr_in6 *in6; @@ -132,15 +133,17 @@ dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, bool async) case AF_INET: in4 = (struct sockaddr_in *)ai->ai_addr; saddr_set_ipv4(saddr, in4->sin_addr.s_addr, in4->sin_port); - error("addrstr: %s", saddr->addrstr); + debug(DBG_CFG, "addrstr: %s", saddr->addrstr); list_add(&saddr->list, &rvalue->saddrs); + (*naddrs)++; break; case AF_INET6: in6 = (struct sockaddr_in6 *)ai->ai_addr; saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); - error("addrstr: %s", saddr->addrstr); + debug(DBG_CFG, "addrstr: %s", saddr->addrstr); list_add(&saddr->list, &rvalue->saddrs); + (*naddrs)++; break; default: @@ -249,7 +252,7 @@ strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) xfree(saddr); debug(DBG_CFG, "maybe got a hostname:port (%s:%" PRIu16 ")", str, port); - if (!dnslookup(str, port, rvalue, async)) + if (!dnslookup(str, port, rvalue, &naddrs, async)) goto error; } else if (strtou16_strict(tmp, &port) == 0) { diff --git a/shared/server-config-options.h b/shared/server-config-options.h new file mode 100644 index 0000000..acedb7a --- /dev/null +++ b/shared/server-config-options.h @@ -0,0 +1,84 @@ +#ifndef fooserverconfigoptionshfoo +#define fooserverconfigoptionshfoo + +#define SERVER_CFG_HEADER "server" + +enum scfg_keys { + SCFG_KEY_INVALID = 0, + SCFG_KEY_TYPE, + SCFG_KEY_NAME, + SCFG_KEY_PORT, + SCFG_KEY_LOCAL, + SCFG_KEY_REMOTE, + SCFG_KEY_IDLE_TIMEOUT, + SCFG_KEY_STOP_METHOD, + SCFG_KEY_START_METHOD, + SCFG_KEY_STOP_EXEC, + SCFG_KEY_START_EXEC, + SCFG_KEY_RCON, + SCFG_KEY_RCON_PASSWORD, + SCFG_KEY_SYSTEMD_SERVICE, +}; + +struct cfg_key_value_map scfg_key_map[] = { + { + .key_name = "type", + .key_value = SCFG_KEY_TYPE, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "name", + .key_value = SCFG_KEY_NAME, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "port", + .key_value = SCFG_KEY_PORT, + .value_type = CFG_VAL_TYPE_UINT16, + }, { + .key_name = "local", + .key_value = SCFG_KEY_LOCAL, + .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, + }, { + .key_name = "remote", + .key_value = SCFG_KEY_REMOTE, + .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, + }, { + .key_name = "idle_timeout", + .key_value = SCFG_KEY_IDLE_TIMEOUT, + .value_type = CFG_VAL_TYPE_UINT16, + }, { + .key_name = "stop_method", + .key_value = SCFG_KEY_STOP_METHOD, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "start_method", + .key_value = SCFG_KEY_START_METHOD, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "stop_exec", + .key_value = SCFG_KEY_STOP_EXEC, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "start_exec", + .key_value = SCFG_KEY_START_EXEC, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "rcon", + .key_value = SCFG_KEY_RCON, + .value_type = CFG_VAL_TYPE_ASYNC_ADDRS, + }, { + .key_name = "rcon_password", + .key_value = SCFG_KEY_RCON_PASSWORD, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = "systemd_service", + .key_value = SCFG_KEY_SYSTEMD_SERVICE, + .value_type = CFG_VAL_TYPE_STRING, + }, { + .key_name = NULL, + .key_value = SCFG_KEY_INVALID, + .value_type = CFG_VAL_TYPE_INVALID, + } +}; + +#endif + diff --git a/shared/utils.h b/shared/utils.h index df728b2..3ad603b 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -6,6 +6,9 @@ #include #include #include +#include +#include +#include extern unsigned debug_mask; -- cgit v1.2.3