summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-07-11 18:07:27 +0200
committerDavid Härdeman <david@hardeman.nu>2020-07-11 18:07:27 +0200
commite6fdfd4c4c753fe3a06edc4ae3b767c57c10d3f7 (patch)
tree9d75b0009acc15cb5ed085f968a24b221266b44e
parent3dc8d84af1753b41fe37b3b3954731379dc579aa (diff)
Flesh out the new command a bit more
-rw-r--r--examples/minecserver@.service37
-rw-r--r--examples/server.properties10
-rw-r--r--minecctl/minecctl.c6
-rw-r--r--minecctl/minecctl.h6
-rw-r--r--minecctl/misc-commands.c352
-rw-r--r--shared/utils.h15
6 files changed, 341 insertions, 85 deletions
diff --git a/examples/minecserver@.service b/examples/minecserver@.service
index e14ed81..bb72c5b 100644
--- a/examples/minecserver@.service
+++ b/examples/minecserver@.service
@@ -1 +1,36 @@
-This is an example service
+[Unit]
+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
+
+[Service]
+Type=exec
+ExecStart=/usr/bin/java -Xmx1024M -Xms1024M -jar server.jar --nogui
+ExecStop=-/usr/bin/minecctl -c %E/minecproxy/config/ -f stop %i
+# Optional: this will autogenerate new servers on the fly
+# if this is used, comment out the ConditionPathExists checks above
+#ExecStartPre=/usr/bin/minecctl new %i
+TimeoutStopSec=120
+KillSignal=SIGCONT
+Restart=on-failure
+WorkingDirectory=%E/minecproxy/servers/%i
+Nice=5
+LimitCORE=0
+NoNewPrivileges=true
+KeyringMode=private
+PrivateUsers=true
+ProtectSystem=strict
+ReadWritePaths=%E/minecproxy/servers/%i
+PrivateDevices=true
+ProtectKernelTunables=true
+ProtectControlGroups=true
+RestrictAddressFamilies=AF_UNIX AF_INET AF_INET6
+RestrictNamespaces=true
+LockPersonality=true
+RestrictSUIDSGID=true
+PrivateTmp=true
diff --git a/examples/server.properties b/examples/server.properties
index f992c40..dddb380 100644
--- a/examples/server.properties
+++ b/examples/server.properties
@@ -1 +1,9 @@
-This is an example server.properties file
+#Minecraft server properties
+# This is a partial file, it will be replaced with a
+# fleshed out version the first time the Minecraft
+# server is executed.
+motd=A Minecraft Server
+server-port=25565
+enable-rcon=true
+rcon.port=25575
+rcon.password=secret
diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c
index 3b1da80..344d3d7 100644
--- a/minecctl/minecctl.c
+++ b/minecctl/minecctl.c
@@ -497,6 +497,12 @@ static void parse_cmdline(struct cfg *cfg, int argc, char *const *argv)
int main(int argc, char *const *argv)
{
struct cfg cfg = {
+ .listen_port_min = 20000,
+ .listen_port_max = 23999,
+ .mc_port_min = 24000,
+ .mc_port_max = 27999,
+ .rcon_port_min = 28000,
+ .rcon_port_max = 31999,
.servers = LIST_HEAD_INIT(cfg.servers),
};
bool success = false;
diff --git a/minecctl/minecctl.h b/minecctl/minecctl.h
index 46c616a..5778d13 100644
--- a/minecctl/minecctl.h
+++ b/minecctl/minecctl.h
@@ -12,6 +12,12 @@ struct cfg {
char *rcon_addrstr;
char *mc_addrstr;
char **commands;
+ uint16_t listen_port_min;
+ uint16_t listen_port_max;
+ uint16_t mc_port_min;
+ uint16_t mc_port_max;
+ uint16_t rcon_port_min;
+ uint16_t rcon_port_max;
bool force_stop;
bool default_set;
bool server_list_loaded;
diff --git a/minecctl/misc-commands.c b/minecctl/misc-commands.c
index 34cbedf..f3ddff2 100644
--- a/minecctl/misc-commands.c
+++ b/minecctl/misc-commands.c
@@ -7,6 +7,8 @@
#include <fcntl.h>
#include <string.h>
#include <errno.h>
+#include <sys/random.h>
+#include <inttypes.h>
#include "shared/utils.h"
#include "minecctl.h"
@@ -35,10 +37,11 @@ static bool create_link(int dfd, const char *source, const char *target)
return true;
}
-static bool write_cfg_file(int dfd, const char *name, unsigned char *content,
+static bool write_cfg_file(int dfd, const char *name,
+ const unsigned char *content,
size_t len)
{
- int fd;
+ _cleanup_close_ int fd;
ssize_t done = 0, r;
bool rv = true;
@@ -61,13 +64,12 @@ static bool write_cfg_file(int dfd, const char *name, unsigned char *content,
done += r;
}
- close(fd);
return rv;
}
static bool find_user_service(int xfd, const char *service)
{
- int dfd;
+ _cleanup_close_ int dfd = -1;
/* FIXME: Make this a macro, make paths #defines */
char sub_path[STRLEN("systemd/user/") + strlen(service) + 1];
char etc_path[STRLEN("/etc/systemd/user/") + strlen(service) + 1];
@@ -102,10 +104,8 @@ static bool find_user_service(int xfd, const char *service)
if (faccessat(dfd, sub_path, R_OK, 0) == 0) {
info("User service %s already installed in "
"$XDG_DATA_HOME/systemd/user/", service);
- close(dfd);
return true;
}
- close(dfd);
}
if (access(usr_path, R_OK) == 0) {
@@ -120,118 +120,98 @@ static bool find_user_service(int xfd, const char *service)
static bool create_user_service(int xfd, const char *service,
unsigned char *content, size_t len)
{
- int sfd, ufd = -1;
- bool rv = false;
+ _cleanup_close_ int sfd = -1;
+ _cleanup_close_ int ufd = -1;
if (find_user_service(xfd, service))
return true;
sfd = open_subdir(xfd, "systemd", true);
if (sfd < 0)
- goto out;
+ return false;
ufd = open_subdir(sfd, "user", true);
if (ufd < 0)
- goto out;
+ return false;
if (!write_cfg_file(ufd, service, content, len))
- goto out;
+ return false;
info("Created user service file $XDG_CONFIG_HOME/systemd/user/%s",
service);
- rv = true;
-
-out:
- if (ufd >= 0)
- close(ufd);
- if (sfd >= 0)
- close(sfd);
- return rv;
+ return true;
}
static bool create_server_cfg(int sfd, int cfd, const char *name,
- const char *filename)
+ const char *filename,
+ const unsigned char *properties,
+ size_t properties_len)
{
- int efd = -1;
- bool rv = false;
+ _cleanup_close_ int efd = -1;
efd = open_subdir(sfd, name, true);
if (efd < 0) {
error("Failed to create configuration directory \"%s\"", name);
- goto out;
+ return false;
}
if (!write_cfg_file(efd, "eula.txt", ___examples_eula_txt,
___examples_eula_txt_len))
- goto out;
+ return false;
if (!write_cfg_file(efd, "server.properties",
- ___examples_server_properties,
- ___examples_server_properties_len))
- goto out;
+ properties, properties_len))
+ return false;
if (!create_link(efd, "server.jar", "../server.jar"))
- goto out;
+ return false;
if (!write_cfg_file(cfd, filename,
___examples_example_mcserver,
___examples_example_mcserver_len))
- goto out;
-
- rv = true;
-
-out:
- if (efd >= 0)
- close(efd);
+ return false;
- return rv;
+ return true;
}
static bool create_config_tree(int xfd)
{
- int mfd = -1, sfd = -1, cfd = -1;
- bool rv = false;
+ _cleanup_close_ int mfd = -1;
+ _cleanup_close_ int sfd = -1;
+ _cleanup_close_ int cfd = -1;
mfd = open_subdir(xfd, "minecproxy", true);
if (mfd < 0)
- goto out;
+ return false;
sfd = open_subdir(mfd, "servers", true);
if (sfd < 0)
- goto out;
+ return false;
if (!write_cfg_file(sfd, "README.TXT", ___examples_README_TXT,
___examples_README_TXT_len))
- goto out;
+ return false;
cfd = open_subdir(mfd, "config", true);
if (cfd < 0)
- goto out;
+ return false;
if (!write_cfg_file(cfd, "minecproxy.conf", ___examples_minecproxy_conf,
___examples_minecproxy_conf_len))
- goto out;
+ return false;
- if (!create_server_cfg(sfd, cfd, "example", "example.mcserver"))
- goto out;
+ if (!create_server_cfg(sfd, cfd, "example", "example.mcserver",
+ ___examples_server_properties,
+ ___examples_server_properties_len))
+ return false;
info("Created configuration in $XDG_CONFIG_HOME/minecproxy");
- rv = true;
-
-out:
- if (mfd >= 0)
- close(mfd);
- if (sfd >= 0)
- close(sfd);
- if (cfd >= 0)
- close(cfd);
- return rv;
+ return true;
}
bool do_init(struct cfg *cfg)
{
- int xfd;
- bool rv = false;
+ _cleanup_close_ int xfd = -1;
if (cfg->dir_path) {
xfd = open(cfg->dir_path, O_PATH | O_CLOEXEC | O_DIRECTORY);
@@ -241,32 +221,27 @@ bool do_init(struct cfg *cfg)
xfd = open_xdg_cfg_dir(true);
if (xfd < 0)
- goto out;
+ return false;
if (!create_config_tree(xfd))
- goto out;
+ return false;
if (cfg->dir_path) {
info("Option -c used, not creating systemd services");
- rv = true;
- goto out;
+ return true;
}
if (!create_user_service(xfd, "minecserver@.service",
___examples_minecserver__service,
___examples_minecserver__service_len))
- goto out;
+ return false;
if (!create_user_service(xfd, "minecproxy.service",
___examples_minecproxy_service,
___examples_minecproxy_service_len))
- goto out;
+ return false;
- rv = true;
-out:
- if (xfd >= 0)
- close(xfd);
- return rv;
+ return true;
}
static bool valid_name(const char *name)
@@ -300,12 +275,204 @@ static bool valid_name(const char *name)
return true;
}
+/* FIXME: Share this */
+static inline char tohex(uint8_t val)
+{
+ static const char hex[] = "0123456789abcdef";
+
+ return hex[val & 0x0f];
+}
+
+static bool generate_random_password(char *buf, size_t len)
+{
+ size_t written;
+ ssize_t r;
+ const char *s;
+ char *d;
+
+ if (len < 3)
+ return false;
+
+ char rnd[(len - 1) / 2];
+
+ for (written = 0; written < sizeof(rnd); written += r) {
+ r = getrandom(rnd + written, sizeof(rnd) - written, 0);
+ if (r < 0) {
+ if (errno == EAGAIN || errno == EINTR)
+ continue;
+
+ error("getrandom failure: %m");
+ return false;
+ }
+ }
+
+ for (s = rnd, d = buf; s < (rnd + sizeof(rnd)); s++) {
+ *(d++) = tohex(*s >> 4);
+ *(d++) = tohex(*s);
+ }
+ *d = '\0';
+
+ info("Hex = %s", buf);
+ return true;
+}
+
+static unsigned char *create_mc_properties(const char *name, uint16_t mc_port,
+ uint16_t rcon_port,
+ const char *rcon_password,
+ size_t *len)
+{
+ char hexrnd[16 + 1];
+ char *properties;
+ int r;
+
+ if (!rcon_password) {
+ if (!generate_random_password(hexrnd, sizeof(hexrnd))) {
+ error("Failed to generate random password");
+ return NULL;
+ }
+ }
+
+ /* FIXME: server-ip= */
+ r = asprintf(&properties,
+ "#Minecraft server properties\n"
+ "# This is a partial file, it will be replaced with a\n"
+ "# fleshed out version the first time the Minecraft\n"
+ "# server is executed.\n"
+ "motd=%s\n"
+ "server-port=%" PRIu16 "\n"
+ "enable-rcon=true\n"
+ "rcon.port=%" PRIu16 "\n"
+ "rcon.password=%s",
+ name, mc_port, rcon_port,
+ rcon_password ? rcon_password : hexrnd);
+ if (r < 0) {
+ error("asprintf failed: %m");
+ return NULL;
+ }
+
+ if (len)
+ *len = r;
+ return (unsigned char *)properties;
+}
+
+static uint16_t get_port(struct list_head *list)
+{
+ struct saddr *saddr;
+
+ if (!list || list_empty(list))
+ return 0;
+
+ list_for_each_entry(saddr, list, list) {
+ switch (saddr->st.ss_family) {
+ case AF_INET:
+ if (saddr->in4.sin_addr.s_addr == htonl(INADDR_LOOPBACK) ||
+ saddr->in4.sin_addr.s_addr == htonl(INADDR_ANY) ||
+ saddr->in4.sin_addr.s_addr == htonl(INADDR_BROADCAST))
+ return htons(saddr->in4.sin_port);
+ break;
+ case AF_INET6:
+ if (!memcmp(&saddr->in6.sin6_addr, &in6addr_any,
+ sizeof(saddr->in6.sin6_addr)) ||
+ !memcmp(&saddr->in6.sin6_addr, &in6addr_loopback,
+ sizeof(saddr->in6.sin6_addr)))
+ return htons(saddr->in6.sin6_port);
+ break;
+ }
+ }
+
+ return 0;
+}
+
+static bool saddr_port_match(struct list_head *list, uint16_t port)
+{
+ struct saddr *a;
+
+ list_for_each_entry(a, list, list) {
+ switch (a->st.ss_family) {
+ case AF_INET:
+ if (a->in4.sin_port == port)
+ return true;
+ break;
+
+ case AF_INET6:
+ if (a->in6.sin6_port == port)
+ return true;
+ break;
+
+ default:
+ break;
+ }
+ }
+
+ return false;
+}
+
+static bool select_free_ports(struct cfg *cfg, uint16_t *listen_port,
+ uint16_t *mc_port, uint16_t *rcon_port)
+{
+ uint16_t lport = cfg->listen_port_min;
+ uint16_t mport = cfg->mc_port_min;
+ uint16_t rport = cfg->rcon_port_min;
+ struct server *server;
+ bool used;
+
+ for (lport = cfg->listen_port_min,
+ mport = cfg->mc_port_min,
+ rport = cfg->rcon_port_min;
+ lport <= cfg->listen_port_max &&
+ mport <= cfg->mc_port_max &&
+ rport <= cfg->rcon_port_max;
+ lport++, mport++, rport++) {
+
+ used = false;
+
+ list_for_each_entry(server, &cfg->servers, list) {
+ if (!server->scfg.filename)
+ continue;
+
+ if (saddr_port_match(&server->scfg.locals, lport)) {
+ used = true;
+ break;
+ }
+
+ if (saddr_port_match(&server->scfg.remotes, mport)) {
+ used = true;
+ break;
+ }
+
+ if (saddr_port_match(&server->scfg.rcons, rport)) {
+ used = true;
+ break;
+ }
+ }
+
+ if (!used ) {
+ if (listen_port && *listen_port == 0)
+ *listen_port = lport;
+
+ if (mc_port && *mc_port == 0)
+ *mc_port = mport;
+
+ if (rcon_port && *rcon_port == 0)
+ *rcon_port = rport;
+
+ return true;
+ }
+ }
+
+ return false;
+}
+
bool do_new(struct cfg *cfg)
{
const char *name = cfg->commands[0];
struct server *server;
- int sfd = -1;
- bool rv = false;
+ 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;
if (!valid_name(name))
return false;
@@ -316,33 +483,52 @@ bool do_new(struct cfg *cfg)
server_load_all_known(cfg);
if (!cfg->dir)
- goto out;
+ return false;
list_for_each_entry(server, &cfg->servers, list) {
- if (streq(server->name, name)) {
+ if (!server->scfg.filename) {
+ defserver = server;
+ continue;
+ }
+
+ if (streq(server->scfg.filename, filename)) {
error("Server \"%s\" already exists", name);
- goto out;
+ return false;
}
}
+ if (defserver) {
+ /* FIXME: check list empty and tailor error msg */
+ rcon_port = get_port(&defserver->scfg.rcons);
+ mc_port = get_port(&defserver->scfg.remotes);
+ rcon_password = defserver->scfg.rcon_password;
+ } else if (cfg->rcon_password)
+ rcon_password = cfg->rcon_password;
+
+ 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\"");
- goto out;
+ return false;
}
- if (!create_server_cfg(sfd, dirfd(cfg->dir), name, filename))
- goto out;
-
- info("Created server configuration %s", name);
+ properties = create_mc_properties(name, mc_port, rcon_port,
+ rcon_password, &properties_len);
+ if (!properties)
+ return false;
- rv = true;
+ error("Created config file (%zu):\n%s", properties_len, properties);
-out:
- if (sfd >= 0)
- close(sfd);
+ if (!create_server_cfg(sfd, dirfd(cfg->dir), name, filename, properties,
+ properties_len))
+ return false;
- return rv;
+ info("Created server configuration %s", name);
+ return true;
}
bool do_list(struct cfg *cfg)
diff --git a/shared/utils.h b/shared/utils.h
index 9404323..c47f9a5 100644
--- a/shared/utils.h
+++ b/shared/utils.h
@@ -10,6 +10,7 @@
#include <sys/socket.h>
#include <netinet/in.h>
#include <netinet/ip.h>
+#include <unistd.h>
extern unsigned debug_mask;
@@ -127,4 +128,18 @@ static inline bool strcaseeq(const char *a, const char *b)
#define DIV_ROUND_UP(n, d) (((n) + (d)-1) / (d))
+#define _cleanup_(x) __attribute__((cleanup(x)))
+
+static inline void closep(int *fd)
+{
+ if (*fd && *fd >= 0)
+ close(*fd);
+}
+#define _cleanup_close_ _cleanup_(closep)
+
+static inline void freep(void *p) {
+ xfree(*(void**)p);
+}
+#define _cleanup_free_ _cleanup_(freep)
+
#endif