From b56b003fc13a4e12f97c6cfd5dd650e928d6e016 Mon Sep 17 00:00:00 2001
From: David Härdeman <david@hardeman.nu>
Date: Sun, 12 Jul 2020 00:00:26 +0200
Subject: Teach minecctl to split things into data and cfg dir, also read
 config variables from the server.properties file

---
 minecctl/minecctl.c      |  17 ++--
 minecctl/minecctl.h      |   4 +-
 minecctl/misc-commands.c | 136 ++++++++++++--------------
 minecctl/misc.h          |   4 +-
 minecctl/rcon-commands.c |  36 +++----
 minecctl/server.c        | 244 +++++++++++++++++++++++++++++++++++++----------
 6 files changed, 281 insertions(+), 160 deletions(-)

(limited to 'minecctl')

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");
 
-- 
cgit v1.2.3