From 06a2edfc72a894054ed710d338504242f15d6071 Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Fri, 26 Jun 2020 03:23:41 +0200 Subject: Make the server handling in minecctl saner --- minecctl/minecctl.c | 468 +++++++++++++++++++--------------------------------- 1 file changed, 167 insertions(+), 301 deletions(-) (limited to 'minecctl/minecctl.c') diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c index 7c01736..c8cc34d 100644 --- a/minecctl/minecctl.c +++ b/minecctl/minecctl.c @@ -2,7 +2,6 @@ #include #include #include -#include #include #include #include @@ -19,143 +18,68 @@ #include "minecctl-rcon.h" #include "config-parser.h" #include "server-config-options.h" +#include "misc.h" #include "config.h" static struct cfg *cfg = NULL; -bool use_colors = false; - -/* FIXME: Can be shared */ static void -set_use_colors() -{ - int fd; - const char *e; - - if (getenv("NO_COLOR")) - return; - - fd = fileno(stderr); - if (fd < 0) - return; - - if (!isatty(fd)) - return; - - e = getenv("TERM"); - if (!e) - return; - - if (streq(e, "dumb")) - return; - - use_colors = true; -} - -void -__debug(_unused_ enum debug_lvl lvl, const char *fmt, ...) -{ - va_list ap; - - if (use_colors) { - if (lvl & DBG_ERROR) - fprintf(stderr, ANSI_RED); - else if (!(lvl & (DBG_INFO | DBG_VERBOSE))) - fprintf(stderr, ANSI_GREY); - } - - va_start(ap, fmt); - vfprintf(stderr, fmt, ap); - va_end(ap); - - if (use_colors && !(lvl & (DBG_INFO | DBG_VERBOSE))) - fprintf(stderr, ANSI_NORMAL); -} - -_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) +dump_config() { - void *ptr; - - assert_die(!empty_str(fn) && line > 0 && size > 0, "invalid arguments"); - - ptr = calloc(1, size); - if (!ptr) - die("malloc: %m"); - return ptr; + info("Configuration:"); + info("password : %s", cfg->password); + info("cfgdir : %s", cfg->cfgdir); + info("addrstr : %s", cfg->addrstr); + info("cmdstr : %s", cfg->cmdstr); + info("cmd : %p", cfg->cmd); + info("force stop : %s", cfg->force_stop ? "yes" : "no"); + info("servers : %sempty", list_empty(&cfg->servers) ? "" : "not "); + /* FIXME: dump servers */ } -char * -__xstrdup(const char *fn, int line, const char *s) +void +read_server_config(struct server *server) { - 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 buf[4096]; + size_t off = 0; + ssize_t r; + int dfd; + int fd; + char *pos = buf; -char * -__xstrndup(const char *fn, int line, const char *s, size_t n) -{ - char *ptr; + if (!server || !server->filename || server->file_read) + return; - assert_die(!empty_str(fn) && line > 0 && !empty_str(s) && n > 0, "invalid arguments"); + server->file_read = true; - ptr = strndup(s, n); - if (ptr) - die("strdup: %m"); - return ptr; -} + dfd = open(cfg->cfgdir, O_DIRECTORY | O_PATH | O_CLOEXEC); + if (dfd < 0) + die("Failed to open %s: %m", cfg->cfgdir); -void -__xfree(const char *fn, int line, void *ptr) -{ - assert_die(!empty_str(fn) && line > 0, "invalid arguments"); + fd = openat(dfd, server->filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) + die("Failed to open %s: %m", server->filename); - free(ptr); -} + close(dfd); -static void -dump_config() -{ - assert_die(cfg, "cfg not set"); + while (true) { + r = read(fd, buf + off, sizeof(buf) - off - 1); + if (r < 0) + die("Failed to read %s: %m", server->filename); + else if (r == 0) + break; - info("Configuration:"); - info("password : %s", cfg->password); - info("cfgdir : %s", cfg->cfgdir); - info("addrstr : %s", cfg->addrstr); - info("cmdstr : %s", cfg->cmdstr); - info("server : %p", cfg->server); - info("cmd : %p", cfg->cmd); - info("force stop : %s", cfg->force_stop ? "yes" : "no"); - info("addrs : %sempty", list_empty(&cfg->addrs) ? "" : "not "); - info("mcaddrs : %sempty", list_empty(&cfg->mcaddrs) ? "" : "not "); - info("known_servers : %sempty", list_empty(&cfg->addrs) ? "" : "not "); -} + off += r; + if (off == sizeof(buf) - 1) + die("Failed to read %s: file too large", server->filename); + } -static void -parse_server_config(char *buf, const char *filename) -{ - assert_die(buf && filename && cfg, "invalid arguments"); + buf[off] = '\0'; + close(fd); - if (!config_parse_header(SERVER_CFG_HEADER, &buf)) - die("Unable to parse %s: invalid/missing header", filename); + if (!config_parse_header(SERVER_CFG_HEADER, &pos)) + die("Unable to parse %s: invalid/missing header", + server->filename); /* FIXME: this will cause superflous DNS lookups of other cfg entries */ while (true) { @@ -163,82 +87,41 @@ parse_server_config(char *buf, const char *filename) const char *keyname; struct cfg_value value; - if (!config_parse_line(filename, &buf, scfg_key_map, + if (!config_parse_line(server->filename, &pos, scfg_key_map, &key, &keyname, &value, false)) break; switch (key) { case SCFG_KEY_RCON: - if (!list_empty(&cfg->addrs)) - die("rcon address defined twice in %s", filename); - list_replace(&value.saddrs, &cfg->addrs); + if (!list_empty(&server->rcon_addrs)) + die("rcon address defined twice in %s", + server->filename); + list_replace(&value.saddrs, &server->rcon_addrs); break; case SCFG_KEY_RCON_PASSWORD: - if (!cfg->password) - cfg->password = xstrdup(value.str); + if (server->rcon_password) + die("rcon password defined twice in %s", + server->filename); + server->rcon_password = xstrdup(value.str); break; case SCFG_KEY_REMOTE: - if (!list_empty(&cfg->mcaddrs)) - die("rcon address defined twice in %s", filename); - list_replace(&value.saddrs, &cfg->mcaddrs); + if (!list_empty(&server->mc_addrs)) + die("rcon address defined twice in %s", + server->filename); + list_replace(&value.saddrs, &server->mc_addrs); default: continue; } - - if (cfg->password && - !list_empty(&cfg->addrs) && - !list_empty(&cfg->mcaddrs)) - break; } - if (!cfg->password) - verbose("rcon password not found in %s", filename); + if (!server->rcon_password) + verbose("rcon password not found in %s", server->filename); - if (list_empty(&cfg->addrs)) - verbose("rcon address not found in %s", filename); + if (list_empty(&server->rcon_addrs)) + verbose("rcon address not found in %s", server->filename); - if (list_empty(&cfg->mcaddrs)) - verbose("mc server address not found in %s", filename); -} - -static void -read_server_config() -{ - char buf[4096]; - size_t off = 0; - ssize_t r; - int dfd; - int fd; - - assert_die(cfg && cfg->server && cfg->cfgdir, "invalid arguments"); - - dfd = open(cfg->cfgdir, O_DIRECTORY | O_PATH | O_CLOEXEC); - if (dfd < 0) - die("Failed to open %s: %m", cfg->cfgdir); - - fd = openat(dfd, cfg->server->filename, O_RDONLY | O_CLOEXEC); - if (fd < 0) - die("Failed to open %s: %m", cfg->server->filename); - - close(dfd); - - while (true) { - r = read(fd, buf + off, sizeof(buf) - off - 1); - if (r < 0) - die("Failed to read %s: %m", cfg->server->filename); - else if (r == 0) - break; - - off += r; - if (off == sizeof(buf) - 1) - die("Failed to read %s: file too large", cfg->server->filename); - } - - buf[off] = '\0'; - close(fd); - - parse_server_config(buf, cfg->server->filename); - dump_config(); + if (list_empty(&server->mc_addrs)) + verbose("mc server address not found in %s", server->filename); } _noreturn_ static void @@ -279,32 +162,21 @@ usage(bool no_error) exit(no_error ? EXIT_FAILURE : EXIT_SUCCESS); } -static char * -strv_join(char * const *strv) +static struct server * +allocate_server() { - size_t len = 0; - char *r, *to; - - for (unsigned i = 0; strv[i]; i++) - len += strlen(strv[i]) + 1; - - if (len == 0) - return NULL; - - r = zmalloc(len); - to = r; + struct server *server; - for (unsigned i = 0; strv[i]; i++) { - if (i > 0) - *(to++) = ' '; - to = stpcpy(to, strv[i]); - } + server = zmalloc(sizeof(*server)); + list_init(&server->rcon_addrs); + list_init(&server->mc_addrs); + list_init(&server->list); - return r; + return server; } static void -get_known_servers() +get_servers() { struct dirent *dent; DIR *dir; @@ -322,7 +194,7 @@ get_known_servers() if (!is_valid_server_config_filename(dent, NULL)) continue; - server = zmalloc(sizeof(*server)); + server = allocate_server(); server->filename = xstrdup(dent->d_name); suffix = strrchr(dent->d_name, '.'); @@ -330,112 +202,116 @@ get_known_servers() *suffix = '\0'; server->shortname = xstrdup(dent->d_name); - list_add(&server->list, &cfg->known_servers); + list_add(&server->list, &cfg->servers); } closedir(dir); } -static void -do_list(struct cfg *cfg) -{ - struct server *server; - - list_for_each_entry(server, &cfg->known_servers, list) - info("%s", server->shortname); -} - -static void -set_server(const char *name) +static bool +str_to_addrs(const char *str, struct list_head *list) { - struct server *server; + struct cfg_value value; + char *tmp = NULL; + bool rv = false; + + /* strtosockaddrs mangles the input string */ + tmp = xstrdup(str); + if (!strtosockaddrs(tmp, &value, false)) { + error("Unable to parse address: %s", str); + goto out; + } - assert_die(cfg, "invalid arguments"); - assert_die(!cfg->server, "can't set server twice"); + if (value.type != CFG_VAL_TYPE_ADDRS) { + error("Unexpected return value from strtosockaddrs"); + goto out; + } - list_for_each_entry(server, &cfg->known_servers, list) { - if (streq(name, server->shortname)) { - cfg->server = server; - return; - } + if (list_empty(&value.saddrs)) { + error("Found no valid addresses for %s", str); + goto out; } - error("\"%s\" is not a known server or command", name); - usage(false); + list_replace(&value.saddrs, list); + rv = true; + +out: + xfree(tmp); + return rv; } -char * -ask_password() +static bool +create_server_from_cmdline_args() { - struct termios old, new; - char *password = NULL; - size_t len = 0; - ssize_t r; + struct server *server; - assert_die(cfg, "invalid arguments"); + if (!cfg->addrstr && !cfg->mcaddrstr) + return false; - if (!isatty(STDIN_FILENO)) - return NULL; + server = allocate_server(); - if (tcgetattr(STDIN_FILENO, &old) < 0) - return NULL; + if (cfg->addrstr) { + if (!str_to_addrs(cfg->addrstr, &server->rcon_addrs)) + goto error; - new = old; - new.c_lflag &= ~ECHO; - new.c_lflag |= ICANON; - if (tcsetattr(STDIN_FILENO, TCSAFLUSH, &new) < 0) - return NULL; + server->shortname = cfg->addrstr; + cfg->addrstr = NULL; + } - fprintf(stderr, "Password: "); - r = getline(&password, &len, stdin); + if (cfg->mcaddrstr) { + if (!str_to_addrs(cfg->mcaddrstr, &server->mc_addrs)) + goto error; - tcsetattr(STDIN_FILENO, TCSAFLUSH, &old); + if (!server->shortname) + server->shortname = cfg->mcaddrstr; + else + xfree(cfg->mcaddrstr); - if (r < 0) { - info("Error in getline: %m"); - clearerr(stdin); - free(password); - return NULL; + cfg->mcaddrstr = NULL; } - while (r > 0 && password[r - 1] == '\n') - password[--r] = '\0'; + if (cfg->password) { + server->rcon_password = cfg->password; + cfg->password = NULL; + } - return password; + list_add(&server->list, &cfg->servers); + return true; + +error: + /* FIXME: free addrs */ + xfree(server->shortname); + xfree(server); + return false; } -int -connect_any(struct list_head *addrs, bool may_fail) +static void +do_list(struct cfg *cfg) { - struct saddr *saddr; - bool connected = false; - int sfd; + struct server *server; - if (list_empty(addrs)) - die("No address to connect to"); + list_for_each_entry(server, &cfg->servers, list) + info("%s", server->shortname); +} - list_for_each_entry(saddr, addrs, list) { - sfd = socket(saddr->storage.ss_family, SOCK_STREAM | SOCK_CLOEXEC, 0); - if (sfd < 0) - die("socket: %m"); +static void +set_server(const char *name) +{ + struct server *server; - socket_set_low_latency(sfd, true, true, true); + assert_die(cfg, "invalid arguments"); - if (connect(sfd, (struct sockaddr *)&saddr->storage, saddr->addrlen) < 0) { - close(sfd); - continue; + list_for_each_entry(server, &cfg->servers, list) { + if (streq(name, server->shortname)) { + /* This puts the chosen server first in the list */ + list_del(&server->list); + list_add(&server->list, &cfg->servers); + return; } - - connected = true; - break; } - if (!connected && may_fail) - return -1; - else if (!connected) - die("Failed to connect to remote host"); - else - return sfd; + error("\"%s\" is not a known server or command", name); + usage(false); } static inline void @@ -561,7 +437,6 @@ parse_cmdline(int argc, char * const *argv) usage(false); } - list_init(&cfg->addrs); cfg->cfgdir = DEFAULT_CFG_DIR; while (true) { @@ -632,52 +507,43 @@ parse_cmdline(int argc, char * const *argv) int main(int argc, char **argv) { + int rv = EXIT_FAILURE; + debug_mask = DBG_ERROR | DBG_INFO; set_use_colors(); cfg = zmalloc(sizeof(*cfg)); - list_init(&cfg->addrs); - list_init(&cfg->mcaddrs); - list_init(&cfg->known_servers); + list_init(&cfg->servers); parse_cmdline(argc, argv); - get_known_servers(); + get_servers(); parse_command(&argv[optind]); - if (!cfg->cmd) - die("Failed to parse command"); - - if (cfg->server) { - read_server_config(); - - } else if (cfg->addrstr) { - struct cfg_value value; - - /* FIXME: create a struct server here, fill in details like name */ - if (!strtosockaddrs(cfg->addrstr, &value, false)) - die("Unable to parse address: %s", cfg->addrstr); - - if (value.type != CFG_VAL_TYPE_ADDRS) - die("Unexpected return value from strtosockaddrs"); - - if (list_empty(&value.saddrs)) - die("Found no valid addresses for %s", cfg->addrstr); - - list_replace(&value.saddrs, &cfg->addrs); + if (!cfg->cmd) { + error("Failed to parse command"); + goto out; } + if (cfg->addrstr || cfg->mcaddrstr) + if (!create_server_from_cmdline_args()) + goto out; + + /* FIXME: Should return bool */ cfg->cmd(cfg); + rv = EXIT_SUCCESS; + +out: + /* FIXME: Not enough cleanup */ if (cfg->password) explicit_bzero(cfg->password, strlen(cfg->password)); - xfree(cfg->password); xfree(cfg->addrstr); xfree(cfg->mcaddrstr); xfree(cfg); - exit(EXIT_SUCCESS); + exit(rv); } -- cgit v1.2.3