#include #include #include #include #include #include #include #include #include #include #include "rcon-protocol.h" #include "utils.h" #include "config-parser.h" #include "server-config-options.h" void __debug(_unused_ enum debug_lvl lvl, const char *fmt, ...) { va_list ap; va_start(ap, fmt); vfprintf(stderr, fmt, ap); va_end(ap); } _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; 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); 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]; 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); } 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 (connect(sfd, (struct sockaddr *)&saddr->storage, saddr->addrlen) < 0) { close(sfd); continue; } connected = true; break; } 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); if (!config_parse_header(SERVER_CFG_HEADER, &buf)) die("Unable to parse %s: invalid/missing 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, false)) 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 (!*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); } 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); }