#include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "minecctl.h" #include "minecctl-rcon.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 void 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); else if (use_colors) info("%s%s%s", ANSI_GREY, reply, ANSI_NORMAL); else info("%s", reply); } 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; } void 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 = get_default_server(cfg); fd = rcon_login(cfg, server); if (fd < 0) return; 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, >ime)) info("In-game days: %u", gtime); if (get_one_status(fd, buf, sizeof(buf), "time query gametime", 1, "The time is %u", &reply, >ime)) 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, >ime)) 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"); } void do_ping(_unused_ struct cfg *cfg) { die("Not implemented"); } 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 void stop_one_server(struct cfg *cfg, struct server *server) { int fd; fd = rcon_login(cfg, server); if (fd < 0) return; if (cfg->force_stop) { unsigned current; if (!get_player_count(fd, ¤t, NULL)) { error("%s: unable to get player count, not stopping", server->name); return; } else if (current > 0) { error("%s: has active players (use -f to force)", server->name); return; } } info("%s: sending stop command", server->name); send_cmd(fd, "stop"); close(fd); } void do_stop(struct cfg *cfg) { struct server *server; server = get_default_server(cfg); stop_one_server(cfg, server); } void do_stop_all(struct cfg *cfg) { struct server *server; list_for_each_entry(server, &cfg->servers, list) { read_server_config(server); stop_one_server(cfg, server); } } void do_pcount(struct cfg *cfg) { int fd; unsigned current, max; struct server *server; server = get_default_server(cfg); fd = rcon_login(cfg, server); if (fd < 0) return; if (get_player_count(fd, ¤t, &max)) info("Players: %u/%u", current, max); else die("Failed to get player count"); } void do_console(struct cfg *cfg) { char *prompt; char *cmd; int fd; struct server *server; server = get_default_server(cfg); fd = rcon_login(cfg, server); if (fd < 0) return; 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; xfree(cmd); } xfree(cmd); } void do_command(struct cfg *cfg) { int fd; struct server *server; server = get_default_server(cfg); fd = rcon_login(cfg, server); if (fd < 0) return; send_cmd(fd, cfg->cmdstr); }