summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-24 00:06:17 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-24 00:06:17 +0200
commit460b10553ac898232bfc5444e335dfa938690d1b (patch)
tree1fa33fd1abb1407d0197de8db905062a61522086
parentb02e89b8b82f0534fd1490a8780ac89d707181b6 (diff)
Make minecctl actually do something
-rw-r--r--minecctl/meson.build1
-rw-r--r--minecctl/minecctl.c322
-rw-r--r--minecproxy/misc.c2
-rw-r--r--minecproxy/server-config.c80
-rw-r--r--shared/config-parser.c11
-rw-r--r--shared/server-config-options.h84
-rw-r--r--shared/utils.h3
7 files changed, 401 insertions, 102 deletions
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 <stdio.h>
#include <stdlib.h>
#include <stdint.h>
#include <inttypes.h>
+#include <stdarg.h>
+#include <unistd.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+#include <errno.h>
#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 <stdbool.h>
#include <stdlib.h>
#include <linux/if_packet.h>
+#include <sys/socket.h>
+#include <netinet/in.h>
+#include <netinet/ip.h>
extern unsigned debug_mask;