summaryrefslogtreecommitdiff
path: root/minecctl/rcon-commands.c
diff options
context:
space:
mode:
Diffstat (limited to 'minecctl/rcon-commands.c')
-rw-r--r--minecctl/rcon-commands.c470
1 files changed, 470 insertions, 0 deletions
diff --git a/minecctl/rcon-commands.c b/minecctl/rcon-commands.c
new file mode 100644
index 0000000..02b970f
--- /dev/null
+++ b/minecctl/rcon-commands.c
@@ -0,0 +1,470 @@
+#include <stdio.h>
+#include <unistd.h>
+#include <errno.h>
+#include <ctype.h>
+#include <readline/readline.h>
+#include <readline/history.h>
+#include <alloca.h>
+#include <inttypes.h>
+#include <string.h>
+#include <stdarg.h>
+
+#include "utils.h"
+#include "minecctl.h"
+#include "rcon-commands.h"
+#include "server.h"
+#include "rcon-protocol.h"
+#include "misc.h"
+
+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;
+ }
+}
+
+/* Note: msg is null-terminated due to the mc protocol trailer */
+static void
+read_packet(int sfd, char *buf, size_t len, int32_t *id, int32_t *type, const char **msg)
+{
+ size_t off = 0;
+ ssize_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 uint32_t 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);
+
+ /* FIXME: this should be shared */
+ if (type == RCON_PACKET_LOGIN) {
+ if (*rtype != RCON_PACKET_LOGIN_OK)
+ die("Invalid reply id");
+
+ if (id == RCON_PACKET_LOGIN_FAIL)
+ *rtype = RCON_PACKET_LOGIN_FAIL;
+ else if (id != rcon_packet_id)
+ die("Invalid reply id");
+ } else {
+ if (id != rcon_packet_id)
+ die("Invalid reply");
+ }
+
+ rcon_packet_id++;
+}
+
+static int
+rcon_login(struct cfg *cfg, struct server *server)
+{
+ char buf[4096];
+ int32_t rtype;
+ const char *reply;
+ int fd = -1;
+
+ assert_die(cfg && server, "invalid arguments");
+
+ if (list_empty(&server->rcon_addrs)) {
+ error("%s: rcon address unknown", server->name);
+ goto error;
+ }
+
+ fd = connect_any(&server->rcon_addrs, true);
+ if (fd < 0) {
+ error("%s: unable to connect", server->name);
+ goto error;
+ }
+
+ if (!server->rcon_password)
+ server->rcon_password = ask_password();
+
+ if (!server->rcon_password) {
+ error("%s: can't login - password missing", server->name);
+ goto error;
+ }
+
+ send_msg(fd, buf, sizeof(buf), RCON_PACKET_LOGIN, server->rcon_password,
+ &rtype, &reply);
+
+ explicit_bzero(buf, sizeof(buf));
+ free_password(&server->rcon_password);
+
+ if (rtype == RCON_PACKET_LOGIN_OK)
+ verbose("%s: login ok", server->name);
+ else if (rtype == RCON_PACKET_LOGIN_FAIL) {
+ info("%s: login failure, invalid password?", server->name);
+ goto error;
+ } else {
+ error("%s: invalid return code: %" PRIi32, server->name, rtype);
+ goto error;
+ }
+
+ return fd;
+
+error:
+ close(fd);
+ return -1;
+}
+
+static bool
+send_cmd(int sfd, const char *cmd)
+{
+ char buf[4096];
+ int32_t rtype;
+ const char *reply;
+
+ send_msg(sfd, buf, sizeof(buf), RCON_PACKET_COMMAND, cmd, &rtype, &reply);
+
+ if (rtype != RCON_PACKET_RESPONSE) {
+ die("Invalid return code: %" PRIi32, rtype);
+ return false;
+ }
+
+
+ if (use_colors)
+ info("%s%s%s", ANSI_GREY, reply, ANSI_NORMAL);
+ else
+ info("%s", reply);
+
+ return true;
+}
+
+static void
+eat_whitespace(char **pos)
+{
+ char *end;
+ size_t len;
+
+ while(isspace(**pos))
+ (*pos)++;
+
+ len = strlen(*pos);
+ if (len == 0)
+ return;
+
+ end = *pos + len - 1;
+ while (isspace(*end))
+ end--;
+ end++;
+ *end = '\0';
+}
+
+/* midnight = 18000 */
+#define MCTIME_OFFSET 6000
+#define MCTIME_PER_DAY 24000
+#define MCTIME_PER_HOUR 1000
+#define MIN_PER_HOUR 60
+
+static inline unsigned
+mctime_days(unsigned mctime) {
+ return (mctime / MCTIME_PER_DAY);
+}
+
+static inline unsigned
+mctime_hh(unsigned mctime) {
+ return (mctime % MCTIME_PER_DAY) / MCTIME_PER_HOUR;
+}
+
+static inline unsigned
+mctime_mm(unsigned mctime) {
+ return ((mctime % MCTIME_PER_HOUR) * MIN_PER_HOUR) / MCTIME_PER_HOUR;
+}
+
+static bool
+get_one_status(int fd, char *buf, size_t len, const char *cmd,
+ size_t argc, const char *replyscan, const char **reply, ...)
+{
+ int32_t rtype;
+ va_list ap;
+ int r;
+
+ send_msg(fd, buf, len, RCON_PACKET_COMMAND, cmd, &rtype, reply);
+ if (rtype != RCON_PACKET_RESPONSE)
+ die("Invalid return code: %" PRIi32, rtype);
+
+ va_start(ap, reply);
+ r = vsscanf(*reply, replyscan, ap);
+ va_end(ap);
+
+ if (r == argc)
+ return true;
+ else
+ return false;
+}
+
+bool
+do_status(struct cfg *cfg) {
+ char buf[4096];
+ char tbuf[4096];
+ const char *reply;
+ unsigned cplayers, maxplayers, gtime;
+ unsigned epacks, apacks;
+ unsigned bannedplayers, bannedips;
+ int fd;
+ struct server *server;
+
+ server = server_get_default(cfg);
+ fd = rcon_login(cfg, server);
+ if (fd < 0)
+ return false;
+
+ if (get_one_status(fd, buf, sizeof(buf), "seed", 1,
+ "Seed : [ %[^]]]", &reply, tbuf))
+ info("Seed: %s", tbuf);
+
+ if (get_one_status(fd, buf, sizeof(buf), "difficulty", 1,
+ "The difficulty is %s", &reply, tbuf))
+ info("Difficulty: %s", tbuf);
+
+ if (get_one_status(fd, buf, sizeof(buf), "list", 2,
+ "There are %u of a max %u players online",
+ &reply, &cplayers, &maxplayers))
+ info("Players: %u/%u", cplayers, maxplayers);
+
+ if (get_one_status(fd, buf, sizeof(buf), "time query day", 1,
+ "The time is %u", &reply, &gtime))
+ info("In-game days: %u", gtime);
+
+ if (get_one_status(fd, buf, sizeof(buf), "time query gametime", 1,
+ "The time is %u", &reply, &gtime))
+ info("World age: %ud:%02uh:%02um",
+ mctime_days(gtime), mctime_hh(gtime), mctime_mm(gtime));
+
+ if (get_one_status(fd, buf, sizeof(buf), "time query daytime", 1,
+ "The time is %u", &reply, &gtime))
+ info("Current in-game time: %02uh:%02um",
+ mctime_hh(gtime + MCTIME_OFFSET), mctime_mm(gtime + MCTIME_OFFSET));
+
+ if (get_one_status(fd, buf, sizeof(buf), "datapack list enabled", 2,
+ "There are %u data packs enabled: %[^\n]", &reply, &epacks, tbuf))
+ info("Enabled data packs (%u): %s", epacks, tbuf);
+
+ if (get_one_status(fd, buf, sizeof(buf), "datapack list available", 2,
+ "There are %u data packs available : %[^\n]", &reply, &apacks, tbuf))
+ info("Available data packs (%u): %s", apacks, tbuf);
+ else if (streq(reply, "There are no more data packs available"))
+ info("Available data packs: none");
+
+ if (get_one_status(fd, buf, sizeof(buf), "banlist players", 1,
+ "There are %u bans", &reply, &bannedplayers))
+ info("Banned players: %u", bannedplayers);
+ else if (streq(reply, "There are no bans"))
+ info("Banned players: 0");
+
+ if (get_one_status(fd, buf, sizeof(buf), "banlist ips", 1,
+ "There are %u bans", &reply, &bannedips))
+ info("Banned IPs: %u", bannedips);
+ else if (streq(reply, "There are no bans"))
+ info("Banned IPs: 0");
+
+ return true;
+}
+
+bool
+do_ping(_unused_ struct cfg *cfg) {
+ die("Not implemented");
+ return false;
+}
+
+static bool
+get_player_count(int fd, unsigned *current, unsigned *max)
+{
+ char buf[4096];
+ const char *reply;
+ unsigned c, m;
+
+ if (!get_one_status(fd, buf, sizeof(buf), "list", 2,
+ "There are %u of a max %u players online",
+ &reply, &c, &m))
+ return false;
+
+ if (current)
+ *current = c;
+
+ if (max)
+ *max = m;
+
+ return true;
+}
+
+static bool
+stop_one_server(struct cfg *cfg, struct server *server)
+{
+ int fd;
+ bool rv;
+
+ fd = rcon_login(cfg, server);
+ if (fd < 0)
+ return false;
+
+ if (cfg->force_stop) {
+ unsigned current;
+
+ if (!get_player_count(fd, &current, NULL)) {
+ error("%s: unable to get player count, not stopping",
+ server->name);
+ return false;
+ } else if (current > 0) {
+ error("%s: has active players (use -f to force)",
+ server->name);
+ return false;
+ }
+ }
+
+ info("%s: sending stop command", server->name);
+ rv = send_cmd(fd, "stop");
+ close(fd);
+
+ return rv;
+}
+
+bool
+do_stop(struct cfg *cfg) {
+ struct server *server;
+
+ server = server_get_default(cfg);
+ return stop_one_server(cfg, server);
+}
+
+bool
+do_stop_all(struct cfg *cfg) {
+ struct server *server;
+
+ list_for_each_entry(server, &cfg->servers, list) {
+ server_read_config(cfg, server);
+ stop_one_server(cfg, server);
+ }
+
+ return true;
+}
+
+bool
+do_pcount(struct cfg *cfg) {
+ int fd;
+ unsigned current, max;
+ struct server *server;
+
+ server = server_get_default(cfg);
+ fd = rcon_login(cfg, server);
+ if (fd < 0)
+ return false;
+
+ if (get_player_count(fd, &current, &max)) {
+ info("Players: %u/%u", current, max);
+ return true;
+ } else {
+ die("Failed to get player count");
+ return false;
+ }
+}
+
+bool
+do_console(struct cfg *cfg)
+{
+ char *prompt;
+ char *cmd;
+ int fd;
+ struct server *server;
+
+ server = server_get_default(cfg);
+ fd = rcon_login(cfg, server);
+ if (fd < 0)
+ return false;
+
+ prompt = alloca(strlen(program_invocation_short_name) +
+ STRLEN(" (") + strlen(server->name) + STRLEN("): ") + 1);
+ sprintf(prompt, "%s (%s): ", program_invocation_short_name,
+ server->name);
+
+ while (true) {
+ char *tmp;
+
+ cmd = readline(prompt);
+ if (!cmd)
+ break;
+
+ tmp = cmd;
+ eat_whitespace(&tmp);
+ if (*tmp == '\0') {
+ xfree(cmd);
+ continue;
+ }
+
+ if (streq(tmp, "q") || streq(tmp, "quit") ||
+ streq(tmp, "/q") || streq(tmp, "/quit"))
+ break;
+
+ send_cmd(fd, tmp);
+
+ if (streq(tmp, "stop") || streq(tmp, "/stop"))
+ /* The server waits for us to close the connection */
+ break;
+
+ free(cmd);
+ }
+
+ xfree(cmd);
+ return true;
+}
+
+bool
+do_command(struct cfg *cfg) {
+ int fd;
+ struct server *server;
+
+ server = server_get_default(cfg);
+ fd = rcon_login(cfg, server);
+ if (fd < 0)
+ return false;
+
+ return send_cmd(fd, cfg->cmdstr);
+}
+