summaryrefslogtreecommitdiff
path: root/minecctl
diff options
context:
space:
mode:
Diffstat (limited to 'minecctl')
-rw-r--r--minecctl/meson.build1
-rw-r--r--minecctl/minecctl.c322
2 files changed, 304 insertions, 19 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);
}