#include #include #include #include #include #include #include #include #include #include #include "utils.h" #include "minecctl.h" #include "minecctl-rcon.h" #include "rcon-protocol.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) { char buf[4096]; int32_t rtype; const char *reply; int fd; assert_die(cfg, "invalid arguments"); if (!cfg->password) cfg->password = ask_password(); if (!cfg->password) die("Can't login - no password"); fd = connect_any(&cfg->addrs, false); send_msg(fd, buf, sizeof(buf), RCON_PACKET_LOGIN, cfg->password, &rtype, &reply); /* An rcon password isn't exactly super-secret, but can't hurt */ explicit_bzero(buf, sizeof(buf)); explicit_bzero(cfg->password, strlen(cfg->password)); xfree(cfg->password); cfg->password = NULL; 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); return fd; } 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'; } static void get_info(int fd, char *buf, size_t buflen, const char *query, const char **reply) { int32_t rtype; send_msg(fd, buf, buflen, RCON_PACKET_COMMAND, query, &rtype, reply); if (rtype != RCON_PACKET_RESPONSE) die("Invalid return code: %" PRIi32, rtype); } /* 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, ...) { va_list ap; int r; get_info(fd, buf, len, cmd, reply); 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; assert_die(cfg, "invalid arguments"); fd = rcon_login(cfg); 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; if (get_one_status(fd, buf, sizeof(buf), "list", 2, "There are %u of a max %u players online", &reply, current, max)) return true; else return false; } void do_stop(struct cfg *cfg) { int fd; assert_die(cfg, "invalid arguments"); fd = rcon_login(cfg); if (cfg->force_stop) { unsigned current, _unused_ max; /* FIXME: args optional */ if (!get_player_count(fd, ¤t, &max)) { error("Unable to get player count"); return; } else if (current > 0) { error("Not stopping server, there are active players" " (use -f to force stop)"); return; } } send_cmd(fd, "stop"); } void do_stop_all(_unused_ struct cfg *cfg) { /* struct server *server; int fd; list_for_each_entry(server, &cfg->known_servers, list) { read_server_config(server->filename); */ die("Not implemented"); } void do_pcount(struct cfg *cfg) { int fd; unsigned current, max; assert_die(cfg, "invalid arguments"); fd = rcon_login(cfg); 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; const char *sname; int fd; assert_die(cfg, "invalid arguments"); fd = rcon_login(cfg); if (cfg->server) sname = cfg->server->shortname; else if (cfg->addrstr) sname = cfg->addrstr; else die("can't find server name"); prompt = alloca(strlen(program_invocation_short_name) + STRLEN(" (") + strlen(sname) + STRLEN("): ") + 1); sprintf(prompt, "%s (%s): ", program_invocation_short_name, sname); 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; assert_die(cfg, "invalid arguments"); fd = rcon_login(cfg); send_cmd(fd, cfg->cmdstr); }