diff options
author | David Härdeman <david@hardeman.nu> | 2020-07-12 00:00:26 +0200 |
---|---|---|
committer | David Härdeman <david@hardeman.nu> | 2020-07-12 00:00:26 +0200 |
commit | b56b003fc13a4e12f97c6cfd5dd650e928d6e016 (patch) | |
tree | a68b48604bccd9a96225bf1c3be21fc05a022b6b | |
parent | e6fdfd4c4c753fe3a06edc4ae3b767c57c10d3f7 (diff) |
Teach minecctl to split things into data and cfg dir, also read config variables from the server.properties file
-rw-r--r-- | config.h.in | 2 | ||||
-rw-r--r-- | examples/meson.build | 2 | ||||
-rw-r--r-- | examples/minecserver@.service | 19 | ||||
-rw-r--r-- | examples/server.properties | 4 | ||||
-rw-r--r-- | meson.build | 2 | ||||
-rw-r--r-- | minecctl/minecctl.c | 17 | ||||
-rw-r--r-- | minecctl/minecctl.h | 4 | ||||
-rw-r--r-- | minecctl/misc-commands.c | 136 | ||||
-rw-r--r-- | minecctl/misc.h | 4 | ||||
-rw-r--r-- | minecctl/rcon-commands.c | 36 | ||||
-rw-r--r-- | minecctl/server.c | 244 | ||||
-rw-r--r-- | minecproxy/main.c | 6 | ||||
-rw-r--r-- | shared/config-parser.c | 44 | ||||
-rw-r--r-- | shared/config-parser.h | 2 | ||||
-rw-r--r-- | shared/utils.c | 22 | ||||
-rw-r--r-- | shared/utils.h | 2 |
16 files changed, 353 insertions, 193 deletions
diff --git a/config.h.in b/config.h.in index d0f028b..19fdccd 100644 --- a/config.h.in +++ b/config.h.in @@ -5,6 +5,8 @@ #define DEFAULT_CFG_DIR @DEFAULT_CFG_DIR@ +#define DEFAULT_DATA_DIR @DEFAULT_DATA_DIR@ + #define DEFAULT_MAIN_CFG_FILE @DEFAULT_MAIN_CFG_FILE@ #define VERSION @VERSION@ diff --git a/examples/meson.build b/examples/meson.build index e50f459..5d3564b 100644 --- a/examples/meson.build +++ b/examples/meson.build @@ -3,11 +3,11 @@ example_files = [ 'eula.txt', 'example.mcserver', + 'minecctl.conf', 'minecproxy.conf', 'minecproxy.service', 'minecserver@.service', 'README.TXT', - 'server.properties', ] xxd = find_program('xxd') diff --git a/examples/minecserver@.service b/examples/minecserver@.service index bb72c5b..40aca86 100644 --- a/examples/minecserver@.service +++ b/examples/minecserver@.service @@ -3,29 +3,30 @@ Description=Minecraft Server %i #Documentation= https://url https://url man:abc Wants=network-online.target After=network-online.target -ConditionFileNotEmpty=%E/minecproxy/config/%i.mcserver -ConditionPathExists=%E/minecproxy/servers/%i/eula.txt -ConditionPathExists=%E/minecproxy/servers/%i/server.properties -ConditionPathExists=%E/minecproxy/servers/%i/server.jar +ConditionFileNotEmpty=%E/minecproxy/%i.mcserver +# Unfortunately $XDG_DATA_DIR has no %E equivalent +ConditionPathExists=%h/.local/share/minecproxy/%i/eula.txt +ConditionPathExists=%h/.local/share/minecproxy/%i/server.properties +ConditionPathExists=%h/.local/share/minecproxy/%i/server.jar [Service] Type=exec ExecStart=/usr/bin/java -Xmx1024M -Xms1024M -jar server.jar --nogui -ExecStop=-/usr/bin/minecctl -c %E/minecproxy/config/ -f stop %i +ExecStop=-/usr/bin/minecctl -f stop %i # Optional: this will autogenerate new servers on the fly -# if this is used, comment out the ConditionPathExists checks above +# if this is used, comment out all the ConditionPathExists checks above #ExecStartPre=/usr/bin/minecctl new %i TimeoutStopSec=120 KillSignal=SIGCONT Restart=on-failure -WorkingDirectory=%E/minecproxy/servers/%i +WorkingDirectory=%h/.local/share/minecproxy/%i Nice=5 LimitCORE=0 NoNewPrivileges=true -KeyringMode=private +KeyringMode=privat PrivateUsers=true ProtectSystem=strict -ReadWritePaths=%E/minecproxy/servers/%i +ReadWritePaths=%h/.local/share/minecproxy/%i PrivateDevices=true ProtectKernelTunables=true ProtectControlGroups=true diff --git a/examples/server.properties b/examples/server.properties index dddb380..c2609cd 100644 --- a/examples/server.properties +++ b/examples/server.properties @@ -3,7 +3,7 @@ # fleshed out version the first time the Minecraft # server is executed. motd=A Minecraft Server -server-port=25565 +server-port=24000 enable-rcon=true -rcon.port=25575 +rcon.port=28000 rcon.password=secret diff --git a/meson.build b/meson.build index a88b523..ffa2eca 100644 --- a/meson.build +++ b/meson.build @@ -39,11 +39,13 @@ ld_flags += cc.get_supported_link_arguments(ld_extra_flags) add_project_link_arguments(ld_flags, language: 'c') sysconfdir = join_paths(get_option('prefix'), get_option('sysconfdir'), meson.project_name()) +localstatedir = join_paths(get_option('prefix'), get_option('localstatedir'), meson.project_name()) mainconfname = meson.project_name() + '.conf' conf = configuration_data() conf.set_quoted('VERSION', '@0@-@VCS_TAG@'.format(meson.project_version())) conf.set_quoted('DEFAULT_CFG_DIR', sysconfdir) +conf.set_quoted('DEFAULT_DATA_DIR', localstatedir) conf.set_quoted('DEFAULT_MAIN_CFG_FILE', mainconfname) inc_config_h = include_directories('.') diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c index 344d3d7..4a6bf0f 100644 --- a/minecctl/minecctl.c +++ b/minecctl/minecctl.c @@ -130,8 +130,8 @@ void dump_config(struct cfg *cfg) info("Configuration"); info("┌────────────"); - info("│ dir_path : %s", cfg->dir_path); - info("│ dir : %p", cfg->dir); + info("│ cfg_dir : %p", cfg->cfg_dir); + info("│ data_dir : %p", cfg->data_dir); info("│ rcon_password : %s", cfg->rcon_password); info("│ rcon_addrstr : %s", cfg->rcon_addrstr); info("│ mc_addrstr : %s", cfg->mc_addrstr); @@ -179,7 +179,6 @@ _noreturn_ static void usage(bool no_error) " -m, --mc-address=ADDR connect to Minecraft server at ADDR\n" " (only relevant for some commands, can also\n" " use environment variable MC_ADDRESS)\n" - " -c, --cfgdir=DIR look for configuration files in DIR\n" " -f, --force stop server even if it has players\n" " -v, --verbose enable extra logging\n" " -d, --debug enable debugging information\n" @@ -430,7 +429,6 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv) { "rcon-password", required_argument, 0, 'p' }, { "rcon-address", required_argument, 0, 'r' }, { "mc-address", required_argument, 0, 'm' }, - { "cfgdir", required_argument, 0, 'c' }, { "verbose", no_argument, 0, 'v' }, { "debug", no_argument, 0, 'd' }, { "force", no_argument, 0, 'f' }, @@ -439,7 +437,7 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv) }; /* clang-format on */ - c = getopt_long(argc, argv, ":p:r:m:c:vdfh", long_options, + c = getopt_long(argc, argv, ":p:r:m:vdfh", long_options, &option_index); if (c == -1) @@ -455,9 +453,6 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv) case 'm': cfg->mc_addrstr = xstrdup(optarg); break; - case 'c': - cfg->dir_path = optarg; - break; case 'v': debug_mask |= DBG_VERBOSE; break; @@ -532,7 +527,9 @@ out: strv_free(cfg.commands); xfree(cfg.rcon_addrstr); xfree(cfg.mc_addrstr); - if (cfg.dir) - closedir(cfg.dir); + if (cfg.cfg_dir) + closedir(cfg.cfg_dir); + if (cfg.data_dir) + closedir(cfg.data_dir); exit(success ? EXIT_SUCCESS : EXIT_FAILURE); } diff --git a/minecctl/minecctl.h b/minecctl/minecctl.h index 5778d13..1d118d7 100644 --- a/minecctl/minecctl.h +++ b/minecctl/minecctl.h @@ -6,8 +6,8 @@ #include <dirent.h> struct cfg { - const char *dir_path; - DIR *dir; + DIR *cfg_dir; + DIR *data_dir; char *rcon_password; char *rcon_addrstr; char *mc_addrstr; diff --git a/minecctl/misc-commands.c b/minecctl/misc-commands.c index f3ddff2..8a54e64 100644 --- a/minecctl/misc-commands.c +++ b/minecctl/misc-commands.c @@ -21,11 +21,11 @@ #include "examples/eula.txt.h" #include "examples/example.mcserver.h" +#include "examples/minecctl.conf.h" #include "examples/minecproxy.conf.h" #include "examples/minecproxy.service.h" #include "examples/minecserver@.service.h" #include "examples/README.TXT.h" -#include "examples/server.properties.h" static bool create_link(int dfd, const char *source, const char *target) { @@ -99,7 +99,7 @@ static bool find_user_service(int xfd, const char *service) return true; } - dfd = open_xdg_shared_dir(); + dfd = open_xdg_data_dir(false); if (dfd >= 0) { if (faccessat(dfd, sub_path, R_OK, 0) == 0) { info("User service %s already installed in " @@ -142,105 +142,92 @@ static bool create_user_service(int xfd, const char *service, return true; } -static bool create_server_cfg(int sfd, int cfd, const char *name, +static bool create_server_cfg(struct cfg *cfg, const char *name, const char *filename, const unsigned char *properties, size_t properties_len) { - _cleanup_close_ int efd = -1; + _cleanup_close_ int sfd = -1; + int cfd, dfd; - efd = open_subdir(sfd, name, true); - if (efd < 0) { - error("Failed to create configuration directory \"%s\"", name); + if (!cfg->cfg_dir || !cfg->data_dir) return false; - } - if (!write_cfg_file(efd, "eula.txt", ___examples_eula_txt, - ___examples_eula_txt_len)) - return false; - - if (!write_cfg_file(efd, "server.properties", - properties, properties_len)) - return false; - - if (!create_link(efd, "server.jar", "../server.jar")) - return false; + cfd = dirfd(cfg->cfg_dir); + dfd = dirfd(cfg->data_dir); if (!write_cfg_file(cfd, filename, ___examples_example_mcserver, ___examples_example_mcserver_len)) return false; - return true; -} - -static bool create_config_tree(int xfd) -{ - _cleanup_close_ int mfd = -1; - _cleanup_close_ int sfd = -1; - _cleanup_close_ int cfd = -1; - - mfd = open_subdir(xfd, "minecproxy", true); - if (mfd < 0) - return false; - - sfd = open_subdir(mfd, "servers", true); - if (sfd < 0) - return false; - - if (!write_cfg_file(sfd, "README.TXT", ___examples_README_TXT, - ___examples_README_TXT_len)) + sfd = open_subdir(dfd, name, true); + if (sfd < 0) { + error("Failed to create server directory \"%s\"", name); return false; + } - cfd = open_subdir(mfd, "config", true); - if (cfd < 0) + if (!write_cfg_file(sfd, "eula.txt", ___examples_eula_txt, + ___examples_eula_txt_len)) return false; - if (!write_cfg_file(cfd, "minecproxy.conf", ___examples_minecproxy_conf, - ___examples_minecproxy_conf_len)) + if (!write_cfg_file(sfd, "server.properties", + properties, properties_len)) return false; - if (!create_server_cfg(sfd, cfd, "example", "example.mcserver", - ___examples_server_properties, - ___examples_server_properties_len)) + if (!create_link(sfd, "server.jar", "../server.jar")) return false; - info("Created configuration in $XDG_CONFIG_HOME/minecproxy"); return true; } -bool do_init(struct cfg *cfg) +bool do_init(_unused_ struct cfg *cfg) { - _cleanup_close_ int xfd = -1; + _cleanup_close_ int xcfd = -1; + _cleanup_close_ int mcfd = -1; + _cleanup_close_ int xdfd = -1; + _cleanup_close_ int mdfd = -1; - if (cfg->dir_path) { - xfd = open(cfg->dir_path, O_PATH | O_CLOEXEC | O_DIRECTORY); - if (xfd < 0) - error("Failed to open dir %s: %m", cfg->dir_path); - } else - xfd = open_xdg_cfg_dir(true); + xcfd = open_xdg_cfg_dir(true); + if (xcfd < 0) + return false; - if (xfd < 0) + mcfd = open_subdir(xcfd, "minecproxy", true); + if (mcfd < 0) return false; - if (!create_config_tree(xfd)) + if (!write_cfg_file(mcfd, "minecproxy.conf", + ___examples_minecproxy_conf, + ___examples_minecproxy_conf_len)) return false; - if (cfg->dir_path) { - info("Option -c used, not creating systemd services"); - return true; - } + if (!write_cfg_file(mcfd, "minecctl.conf", + ___examples_minecctl_conf, + ___examples_minecctl_conf_len)) + return false; - if (!create_user_service(xfd, "minecserver@.service", + if (!create_user_service(xcfd, "minecserver@.service", ___examples_minecserver__service, ___examples_minecserver__service_len)) return false; - if (!create_user_service(xfd, "minecproxy.service", + if (!create_user_service(xcfd, "minecproxy.service", ___examples_minecproxy_service, ___examples_minecproxy_service_len)) return false; + xdfd = open_xdg_data_dir(true); + if (xdfd < 0) + return false; + + mdfd = open_subdir(xdfd, "minecproxy", true); + if (mdfd < 0) + return false; + + if (!write_cfg_file(mdfd, "README.TXT", ___examples_README_TXT, + ___examples_README_TXT_len)) + return false; + return true; } @@ -390,12 +377,12 @@ static bool saddr_port_match(struct list_head *list, uint16_t port) list_for_each_entry(a, list, list) { switch (a->st.ss_family) { case AF_INET: - if (a->in4.sin_port == port) + if (htons(a->in4.sin_port) == port) return true; break; case AF_INET6: - if (a->in6.sin6_port == port) + if (htons(a->in6.sin6_port) == port) return true; break; @@ -446,7 +433,9 @@ static bool select_free_ports(struct cfg *cfg, uint16_t *listen_port, } } - if (!used ) { + if (!used) { + error("Found unused port, %" PRIu16, lport); + if (listen_port && *listen_port == 0) *listen_port = lport; @@ -470,7 +459,6 @@ bool do_new(struct cfg *cfg) struct server *defserver = NULL; _cleanup_free_ unsigned char *properties = NULL; size_t properties_len; - _cleanup_close_ int sfd = -1; uint16_t rcon_port = 0, mc_port = 0; const char *rcon_password = NULL; @@ -480,9 +468,13 @@ bool do_new(struct cfg *cfg) char filename[strlen(name) + STRLEN(".mcserver") + 1]; sprintf(filename, "%s.mcserver", name); - server_load_all_known(cfg); + if (!server_read_all_configs(cfg, false)) { + error("Failed to read all existing server configurations, " + "try running the \"lint\" command."); + return false; + } - if (!cfg->dir) + if (!cfg->cfg_dir || !cfg->data_dir) return false; list_for_each_entry(server, &cfg->servers, list) { @@ -505,17 +497,13 @@ bool do_new(struct cfg *cfg) } else if (cfg->rcon_password) rcon_password = cfg->rcon_password; + dump_config(cfg); + if (!select_free_ports(cfg, NULL, &mc_port, &rcon_port)) { error("Failed to find a free port"); return false; } - sfd = open_subdir(dirfd(cfg->dir), "../servers", false); - if (sfd < 0) { - error("Failed to open configuration directory \"servers\""); - return false; - } - properties = create_mc_properties(name, mc_port, rcon_port, rcon_password, &properties_len); if (!properties) @@ -523,7 +511,7 @@ bool do_new(struct cfg *cfg) error("Created config file (%zu):\n%s", properties_len, properties); - if (!create_server_cfg(sfd, dirfd(cfg->dir), name, filename, properties, + if (!create_server_cfg(cfg, name, filename, properties, properties_len)) return false; diff --git a/minecctl/misc.h b/minecctl/misc.h index d1c6816..02c01ea 100644 --- a/minecctl/misc.h +++ b/minecctl/misc.h @@ -6,9 +6,9 @@ int open_subdir(int dfd, const char *subdir, bool nofail); int open_xdg_dir(const char *envname, const char *altpath, bool nofail); -static inline int open_xdg_shared_dir() +static inline int open_xdg_data_dir(bool nofail) { - return open_xdg_dir("XDG_DATA_HOME", ".local/share", false); + return open_xdg_dir("XDG_DATA_HOME", ".local/share", nofail); } static inline int open_xdg_cfg_dir(bool nofail) diff --git a/minecctl/rcon-commands.c b/minecctl/rcon-commands.c index 44bb286..cb6ea45 100644 --- a/minecctl/rcon-commands.c +++ b/minecctl/rcon-commands.c @@ -21,18 +21,13 @@ static void send_packet(int sfd, const char *buf, size_t len) size_t off = 0; ssize_t r; - while (true) { + for (off = 0; off < len; off += r) { r = write(sfd, buf + off, len - off); if (r < 0) { - if (errno == EINTR) + if (errno == EAGAIN || errno == EINTR) continue; - else - die("Failed to write packet: %m"); + die("Failed to write packet: %m"); } - - off += r; - if (off == len) - break; } } @@ -40,31 +35,26 @@ static void send_packet(int sfd, const char *buf, size_t len) static void read_packet(int sfd, char *buf, size_t len, int32_t *id, int32_t *type, const char **msg) { - size_t off = 0; + size_t off; ssize_t r; const char *error; - while (true) { + for (off = 0; off < len; off += r) { + if (rcon_protocol_packet_complete(buf, off)) + break; + 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 to read reply: %m"); + } else 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)) + if (off >= len) + die("Reply too large %zu and %zu", off, len); + else if (!rcon_protocol_read_packet(buf, off, id, type, msg, &error)) die("Failed to parse response: %s", error); } 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"); diff --git a/minecproxy/main.c b/minecproxy/main.c index c51b01c..d8f33d0 100644 --- a/minecproxy/main.c +++ b/minecproxy/main.c @@ -311,13 +311,15 @@ static void cfg_read() int key; const char *keyname; struct cfg_value value; + const char *error; if (!config_parse_line(path, &pos, mcfg_key_map, &key, &keyname, - &value, false, &lineno)) + &value, false, &lineno, &error)) break; if (key == MCFG_KEY_INVALID) - die("main config file (%s) invalid", path); + die("main config file (%s) invalid: line %u: %s", path, + lineno, error); debug(DBG_CFG, "main cfg: key %s", keyname); diff --git a/shared/config-parser.c b/shared/config-parser.c index f249cda..ea800ad 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -148,7 +148,7 @@ bool scfg_async_dns_start(struct server_config *scfg) } static void scfg_queue_dns(struct server_config *scfg, struct cfg_value *value, - struct list_head *target_list) + struct list_head *target_list, bool any_ok) { struct saddr *saddr, *tmp; struct dns_async *dns; @@ -158,6 +158,8 @@ static void scfg_queue_dns(struct server_config *scfg, struct cfg_value *value, debug(DBG_DNS, "got sync results"); list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) { list_del(&saddr->list); + if (!any_ok) + saddr_any_to_loopback(saddr); list_add(&saddr->list, target_list); } break; @@ -244,8 +246,11 @@ bool scfg_validate(struct server_config *scfg, const char **error) if (list_empty(&scfg->locals)) ERROR("missing local addresses for proxy server"); + /* FIXME: Reinstate once server.properties parsing is implemented */ + /* if (list_empty(&scfg->remotes)) ERROR("missing remote addresses for proxy server"); + */ list_for_each_entry(saddr, &scfg->locals, list) { port = saddr_port(saddr); @@ -323,12 +328,10 @@ static char *systemd_object_path(const char *service) bool scfg_parse(struct server_config *scfg, char *buf, bool async, unsigned *lineno, const char **error) { - char *pos; + char *pos = buf; assert_return(scfg && buf && lineno && error, false); - pos = buf; - *lineno = 0; if (!config_parse_header(SERVER_CFG_HEADER, &pos, lineno)) @@ -340,11 +343,11 @@ bool scfg_parse(struct server_config *scfg, char *buf, bool async, struct cfg_value value; if (!config_parse_line(scfg->filename, &pos, scfg_key_map, &key, - &keyname, &value, async, lineno)) + &keyname, &value, async, lineno, error)) break; if (key == SCFG_KEY_INVALID) - ERROR("invalid line in config file"); + return false; debug(DBG_CFG, "%s: key %s", scfg->filename, keyname); @@ -390,11 +393,11 @@ bool scfg_parse(struct server_config *scfg, char *buf, bool async, break; case SCFG_KEY_LOCAL: - scfg_queue_dns(scfg, &value, &scfg->locals); + scfg_queue_dns(scfg, &value, &scfg->locals, true); break; case SCFG_KEY_REMOTE: - scfg_queue_dns(scfg, &value, &scfg->remotes); + scfg_queue_dns(scfg, &value, &scfg->remotes, false); break; case SCFG_KEY_IDLE_TIMEOUT: @@ -443,7 +446,7 @@ bool scfg_parse(struct server_config *scfg, char *buf, bool async, break; case SCFG_KEY_RCON: - scfg_queue_dns(scfg, &value, &scfg->rcons); + scfg_queue_dns(scfg, &value, &scfg->rcons, false); break; case SCFG_KEY_RCON_PASSWORD: @@ -742,7 +745,7 @@ bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) if (!saddr) goto error; - saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); + saddr_set_ipv4(saddr, htonl(INADDR_ANY), htons(port)); list_add(&saddr->list, list); naddrs++; @@ -776,7 +779,7 @@ bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) if (!cfg_dns_lookup(str, port, rvalue, &naddrs, async)) goto error; - } else if (strtou16_strict(tmp, &port) == 0) { + } else if (strtou16_strict(str, &port) == 0) { /* Port */ debug(DBG_CFG, "attempting to parse a port number (%s)", str); @@ -792,12 +795,13 @@ bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async) if (!saddr) goto error; - saddr_set_ipv4(saddr, INADDR_ANY, htons(port)); + saddr_set_ipv4(saddr, htonl(INADDR_ANY), htons(port)); list_add(&saddr->list, list); naddrs++; } else { /* Unknown */ + debug(DBG_CFG, "cannot parse str (%s)", str); goto error; } @@ -838,15 +842,16 @@ error: bool config_parse_line(const char *filename, char **buf, struct cfg_key_value_map *kvmap, int *rkey, const char **rkeyname, struct cfg_value *rvalue, - bool async_dns, unsigned *lineno) + bool async_dns, unsigned *lineno, const char **error) { char *line, *pos, *key, *value; size_t linelen; int i; - assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue && lineno, - false); + assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue && + lineno && error, false); + *error = NULL; line = get_line(buf, lineno); if (!line) return false; @@ -902,8 +907,10 @@ bool config_parse_line(const char *filename, char **buf, _fallthrough_; case CFG_VAL_TYPE_ADDRS: - if (!strtosockaddrs(value, rvalue, async_dns)) + if (!strtosockaddrs(value, rvalue, async_dns)) { + *error = "Failed to parse address/port line"; goto error; + } switch (rvalue->type) { case CFG_VAL_TYPE_ADDRS: @@ -940,7 +947,7 @@ bool config_parse_line(const char *filename, char **buf, rvalue->type = CFG_VAL_TYPE_BOOL; rvalue->boolean = false; } else { - error("invalid boolean value (%s)", value); + *error = "invalid boolean value"; goto error; } break; @@ -965,7 +972,8 @@ bool config_parse_line(const char *filename, char **buf, return true; error: - error("%s: invalid config line: %u:%s", filename, *lineno, line); + if (!*error) + *error = "invalid line"; rvalue->type = CFG_VAL_TYPE_INVALID; *rkey = 0; *rkeyname = NULL; diff --git a/shared/config-parser.h b/shared/config-parser.h index 7d0595d..129e085 100644 --- a/shared/config-parser.h +++ b/shared/config-parser.h @@ -111,7 +111,7 @@ bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async); bool config_parse_line(const char *filename, char **buf, struct cfg_key_value_map *kvmap, int *rkey, const char **rkeyname, struct cfg_value *rvalue, - bool async_dns, unsigned *lineno); + bool async_dns, unsigned *lineno, const char **error); bool config_parse_header(const char *title, char **buf, unsigned *lineno); diff --git a/shared/utils.c b/shared/utils.c index 6474e30..9bf8cd8 100644 --- a/shared/utils.c +++ b/shared/utils.c @@ -141,6 +141,28 @@ void saddr_set_ipv6(struct saddr *saddr, const struct in6_addr *ip, saddr_set_addrstr(saddr); } +void saddr_any_to_loopback(struct saddr *saddr) +{ + switch (saddr->st.ss_family) { + case AF_INET: + if (saddr->in4.sin_addr.s_addr != htonl(INADDR_ANY)) + return; + saddr->in4.sin_addr.s_addr = htonl(INADDR_LOOPBACK); + break; + + case AF_INET6: + if (memcmp(&saddr->in6.sin6_addr, &in6addr_any, + sizeof(in6addr_any))) + return; + saddr->in6.sin6_addr = in6addr_loopback; + break; + + default: + return; + } + saddr_set_addrstr(saddr); +} + void saddr_set_addrstr(struct saddr *saddr) { // assert_return(saddr); diff --git a/shared/utils.h b/shared/utils.h index c47f9a5..bf1b63a 100644 --- a/shared/utils.h +++ b/shared/utils.h @@ -83,6 +83,8 @@ void saddr_set_ipv4(struct saddr *saddr, in_addr_t ip, in_port_t port); void saddr_set_ipv6(struct saddr *saddr, const struct in6_addr *ip, in_port_t port); +void saddr_any_to_loopback(struct saddr *saddr); + void saddr_set_addrstr(struct saddr *saddr); int strtou16_strict(const char *str, uint16_t *result); |