diff options
Diffstat (limited to 'minecctl/server.c')
-rw-r--r-- | minecctl/server.c | 244 |
1 files changed, 195 insertions, 49 deletions
diff --git a/minecctl/server.c b/minecctl/server.c index 91e7842..3743526 100644 --- a/minecctl/server.c +++ b/minecctl/server.c @@ -3,6 +3,7 @@ #include <sys/types.h> #include <sys/stat.h> #include <fcntl.h> +#include <errno.h> #include "shared/utils.h" #include "minecctl.h" @@ -12,37 +13,39 @@ #define INVALID(msg) do { *error = (msg); return false; } while(0) -static DIR *open_cfg_dir(const char *cmdline_path) +static DIR *__open_dir(int (*base_dir)(bool nofail), const char *fallback) { - int dfd, mfd; + _cleanup_close_ int xfd = -1; + _cleanup_close_ int mfd = -1; DIR *dir; - if (cmdline_path) { - dir = opendir(cmdline_path); - if (!dir) - error("opendir(%s): %m", cmdline_path); - return dir; - } - /* First, attempt per-user config dir... */ - dfd = open_xdg_cfg_dir(false); - if (dfd >= 0) { - mfd = openat(dfd, "minecproxy/config", - O_CLOEXEC | O_DIRECTORY | O_RDONLY); - close(dfd); - if (mfd >= 0) { - dir = fdopendir(mfd); - if (!dir) - error("fdopendir: %m"); - return dir; - } - } + xfd = base_dir(false); + if (xfd < 0) + goto fallback; - /* ...and fallback on the system dir */ - dir = opendir(DEFAULT_CFG_DIR); - if (!dir) - error("Failed to open configuration directory: %m"); + mfd = openat(xfd, "minecproxy", O_CLOEXEC | O_DIRECTORY | O_RDONLY); + if (mfd < 0) + goto fallback; + + dir = fdopendir(mfd); + if (dir) + mfd = -1; return dir; + + /* ...and fallback on the system dir */ +fallback: + return opendir(fallback); +} + +static inline DIR *open_cfg_dir() +{ + return __open_dir(open_xdg_cfg_dir, DEFAULT_CFG_DIR); +} + +static inline DIR *open_data_dir() +{ + return __open_dir(open_xdg_data_dir, DEFAULT_DATA_DIR); } void server_load_all_known(struct cfg *cfg) @@ -53,13 +56,20 @@ void server_load_all_known(struct cfg *cfg) return; cfg->server_list_loaded = true; - cfg->dir = open_cfg_dir(cfg->dir_path); - if (!cfg->dir) { + + cfg->cfg_dir = open_cfg_dir(); + if (!cfg->cfg_dir) { error("Failed to open config directory"); return; } - while ((dent = readdir(cfg->dir))) { + cfg->data_dir = open_data_dir(); + if (!cfg->data_dir) { + error("Failed to open server directory"); + return; + } + + while ((dent = readdir(cfg->cfg_dir))) { struct server *server; char *suffix; @@ -77,13 +87,149 @@ void server_load_all_known(struct cfg *cfg) } } +static bool read_file(int dfd, const char *filename, char *buf, size_t len, + const char **error) +{ + _cleanup_close_ int fd = -1; + size_t off; + ssize_t r; + + fd = openat(dfd, filename, O_RDONLY | O_CLOEXEC); + if (fd < 0) + INVALID("failed to open file"); + + for (off = 0; off < len; off += r) { + r = read(fd, buf + off, len - off); + if (r < 0) { + if (errno == EAGAIN || errno == EINTR) + continue; + INVALID("failed to read file"); + } else if (r == 0) + break; + } + + if (off >= len - 1) + INVALID("file too large"); + + buf[off] = '\0'; + return true; +} + +enum sprop_keys { + SPROP_KEY_INVALID = 0, + SPROP_KEY_SERVER_PORT, + SPROP_KEY_RCON_PORT, + SPROP_KEY_RCON_PASSWORD, +}; + +struct cfg_key_value_map sprop_key_map[] = { + { + .key_name = "server-port", + .key_value = SPROP_KEY_SERVER_PORT, + .value_type = CFG_VAL_TYPE_ADDRS, + }, + { + .key_name = "rcon.port", + .key_value = SPROP_KEY_RCON_PORT, + .value_type = CFG_VAL_TYPE_ADDRS, + }, + { + .key_name = "rcon.password", + .key_value = SPROP_KEY_RCON_PASSWORD, + .value_type = CFG_VAL_TYPE_STRING, + } +}; + +static void sprop_parse(struct server_config *scfg, char *buf, + unsigned *lineno, const char **error) +{ + char *pos = buf; + struct saddr *saddr, *tmp; + + *lineno = 0; + + while (true) { + int key; + const char *keyname; + struct cfg_value value; + + if (!config_parse_line("server.properties", &pos, sprop_key_map, + &key, &keyname, &value, false, lineno, + error)) + break; + + switch (key) { + case SPROP_KEY_SERVER_PORT: + error("Got a server port"); + + /* FIXME: these should use scfg_queue_dns */ + if (value.type != CFG_VAL_TYPE_ADDRS) { + error("Got async DNS results!?"); + break; + } + + if (!list_empty(&scfg->remotes)) { + error("mc server address set both in %s and " + "server.properties", scfg->filename); + break; + } + + list_for_each_entry_safe(saddr, tmp, &value.saddrs, list) { + list_del(&saddr->list); + list_add(&saddr->list, &scfg->remotes); + } + + break; + + case SPROP_KEY_RCON_PORT: + error("Got a rcon port"); + + if (value.type != CFG_VAL_TYPE_ADDRS) { + error("Got async DNS results!?"); + break; + } + + if (!list_empty(&scfg->rcons)) { + error("rcon address set both in %s and " + "server.properties", scfg->filename); + break; + } + + list_for_each_entry_safe(saddr, tmp, &value.saddrs, list) { + list_del(&saddr->list); + list_add(&saddr->list, &scfg->rcons); + } + + break; + + case SPROP_KEY_RCON_PASSWORD: + error("Got an rcon password"); + + if (scfg->rcon_password) { + error("rcon password set both in %s and " + "server.properties (%smatching)", + scfg->filename, + streq(scfg->rcon_password, value.str) ? + "" : "not"); + break; + } + + scfg->rcon_password = xstrdup(value.str); + break; + + case SPROP_KEY_INVALID: + _fallthrough_; + default: + break; + } + } +} + bool server_read_config(struct cfg *cfg, struct server *server, unsigned *lineno, const char **error) { + _cleanup_close_ int dfd = -1; char buf[4096]; - size_t off = 0; - ssize_t r; - int fd; if (!error) return false; @@ -100,34 +246,34 @@ bool server_read_config(struct cfg *cfg, struct server *server, if (server->file_read) return true; - if (!cfg->dir) + if (!cfg->cfg_dir) INVALID("Configuration directory not opened"); + if (!cfg->data_dir) + INVALID("Server directory not opened"); + *lineno = 0; server->file_read = true; - fd = openat(dirfd(cfg->dir), server->scfg.filename, O_RDONLY | O_CLOEXEC); - if (fd < 0) - INVALID("failed to open configuration file"); + if (!read_file(dirfd(cfg->cfg_dir), server->scfg.filename, buf, + sizeof(buf), error)) + return false; - while (true) { - r = read(fd, buf + off, sizeof(buf) - off - 1); - if (r < 0) - INVALID("failed to read configuration file"); - else if (r == 0) - break; + if (!scfg_parse(&server->scfg, buf, false, lineno, error)) + return false; - off += r; - if (off == sizeof(buf) - 1) - INVALID("invalid, file too large"); - } + /* fill in missing parameters from server.properties */ + dfd = open_subdir(dirfd(cfg->data_dir), server->name, false); + if (dfd < 0) + goto out; - buf[off] = '\0'; - close(fd); + if (!read_file(dfd, "server.properties", buf, sizeof(buf), + error)) + goto out; - if (!scfg_parse(&server->scfg, buf, NULL, lineno, error)) - return false; + sprop_parse(&server->scfg, buf, lineno, error); +out: if (!server->scfg.rcon_password) verbose("rcon password not set"); |