/* SPDX-License-Identifier: GPL-2.0 */ #include #include #include #include #include #include #include #include #include "shared/utils.h" #include "minecctl.h" #include "server.h" #include "misc-commands.h" #include "rcon-commands.h" #include "mc-commands.h" #include "shared/systemd.h" #include "misc.h" #include "examples/eula.txt.h" #include "examples/example.mcserver.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) { if (symlinkat(target, dfd, source) < 0) { error("Unable to create link %s -> %s: %m", source, target); return false; } return true; } static bool write_cfg_file(int dfd, const char *name, unsigned char *content, size_t len) { int fd; ssize_t done = 0, r; bool rv = true; fd = openat(dfd, name, O_WRONLY | O_CLOEXEC | O_CREAT | O_EXCL, 0644); if (fd < 0) { error("Unable to create file %s: %m", name); return false; } while (done < len) { r = write(fd, content + done, len - done); if (r < 0) { if (errno == EAGAIN || errno == EINTR) continue; error("Unable to write file %s: %m", name); rv = false; break; } done += r; } close(fd); return rv; } static bool find_user_service(int xfd, const char *service) { int dfd; /* 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]; char usr_path[STRLEN("/usr/lib/systemd/user/") + strlen(service) + 1]; sprintf(sub_path, "systemd/user/%s", service); sprintf(etc_path, "/etc/systemd/user/%s", service); sprintf(usr_path, "/usr/lib/systemd/user/%s", service); /* * We need to check (in order of precedence): * ~/.config/systemd/user/ - user-created * /etc/systemd/user/ - admin-created * ~/.local/share/systemd/user/ - user-installed packages * /usr/lib/systemd/user/ - system-installed packages */ if (faccessat(xfd, sub_path, R_OK, 0) == 0) { info("User service %s already installed in " "$XDG_CONFIG_HOME/systemd/user/", service); return true; } if (access(etc_path, R_OK) == 0) { info("User service %s already installed in " "/etc/systemd/user/", service); return true; } dfd = open_xdg_shared_dir(); if (dfd >= 0) { 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) { info("User service %s already installed in " "/usr/lib/systemd/user/", service); return true; } return false; } static bool create_user_service(int xfd, const char *service, unsigned char *content, size_t len) { int sfd, ufd = -1; bool rv = false; if (find_user_service(xfd, service)) return true; sfd = open_subdir(xfd, "systemd", true); if (sfd < 0) goto out; ufd = open_subdir(sfd, "user", true); if (ufd < 0) goto out; if (!write_cfg_file(ufd, service, content, len)) goto out; 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; } static bool create_config_tree(int xfd) { int mfd = -1, sfd = -1, efd = -1, cfd = -1; bool rv = false; mfd = open_subdir(xfd, "minecproxy", true); if (mfd < 0) goto out; sfd = open_subdir(mfd, "servers", true); if (sfd < 0) goto out; if (!write_cfg_file(sfd, "README.TXT", ___examples_README_TXT, ___examples_README_TXT_len)) goto out; efd = open_subdir(sfd, "example", true); if (efd < 0) goto out; if (!write_cfg_file(efd, "eula.txt", ___examples_eula_txt, ___examples_eula_txt_len)) goto out; if (!write_cfg_file(efd, "server.properties", ___examples_server_properties, ___examples_server_properties_len)) goto out; if (!create_link(efd, "server.jar", "../server.jar")) goto out; cfd = open_subdir(mfd, "config", true); if (cfd < 0) goto out; if (!write_cfg_file(cfd, "example.mcserver", ___examples_example_mcserver, ___examples_example_mcserver_len)) goto out; if (!write_cfg_file(cfd, "minecproxy.conf", ___examples_minecproxy_conf, ___examples_minecproxy_conf_len)) goto out; info("Created configuration in $XDG_CONFIG_HOME/minecproxy"); rv = true; out: if (mfd >= 0) close(mfd); if (sfd >= 0) close(sfd); if (efd >= 0) close(efd); if (cfd >= 0) close(cfd); return rv; } bool do_init(struct cfg *cfg) { int xfd; bool rv = false; 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); if (xfd < 0) goto out; if (!create_config_tree(xfd)) goto out; if (cfg->dir_path) { info("Option -c used, not creating systemd services"); rv = true; goto out; } if (!create_user_service(xfd, "minecserver@.service", ___examples_minecserver__service, ___examples_minecserver__service_len)) goto out; if (!create_user_service(xfd, "minecproxy.service", ___examples_minecproxy_service, ___examples_minecproxy_service_len)) goto out; rv = true; out: if (xfd >= 0) close(xfd); return rv; } bool do_list(struct cfg *cfg) { struct server *server; server_load_all_known(cfg); /* server->scfg.filename check excludes servers created from cmdline */ list_for_each_entry(server, &cfg->servers, list) if (server->scfg.filename) info("• %s", server->name); return true; } static bool saddr_match(struct list_head *la, struct list_head *lb) { struct saddr *a, *b; list_for_each_entry(a, la, list) { list_for_each_entry(b, lb, list) { if (a->st.ss_family != b->st.ss_family) continue; switch (a->st.ss_family) { case AF_INET: if (memcmp(&a->in4.sin_addr, &b->in4.sin_addr, sizeof(a->in4.sin_addr))) continue; if (a->in4.sin_port != b->in4.sin_port) continue; return true; case AF_INET6: if (memcmp(&a->in6.sin6_addr, &b->in6.sin6_addr, sizeof(a->in6.sin6_addr))) continue; if (a->in6.sin6_port != b->in6.sin6_port) continue; return true; default: continue; } } } return false; } bool do_lint(struct cfg *cfg) { struct server *a, *b; unsigned ia, ib; bool rv = true; rv = server_read_all_configs(cfg, true); dump_config(cfg); ia = 0; list_for_each_entry(a, &cfg->servers, list) { ib = 0; list_for_each_entry(b, &cfg->servers, list) { if (ib <= ia) { ib++; continue; } if (a->scfg.announce_port != 0 && b->scfg.announce_port != 0 && a->scfg.announce_port == b->scfg.announce_port) info("%sNote:%s %s and %s appear to have the " "same announce port", ansi_red, ansi_normal, a->name, b->name); if (saddr_match(&a->scfg.locals, &b->scfg.locals)) info("%sNote:%s %s and %s appear to share at " "least one local address/port pair", ansi_red, ansi_normal, a->name, b->name); if (saddr_match(&a->scfg.remotes, &b->scfg.remotes)) info("%sNote:%s %s and %s appear to share at " "least one remote address/port pair", ansi_red, ansi_normal, a->name, b->name); if (saddr_match(&a->scfg.rcons, &b->scfg.rcons)) info("%sNote:%s %s and %s appear to share at " "least one rcon address/port pair", ansi_red, ansi_normal, a->name, b->name); ib++; } ia++; } return rv; } bool do_pcount(struct cfg *cfg) { unsigned online, max; struct server *server; const char *error; server = server_get_default(cfg); if (!server) return false; if (do_rcon_pcount(cfg, server, &online, &max, &error)) info("Rcon says %u/%u", online, max); if (do_mc_pcount(cfg, server, &online, &max, &error)) info("MC says %u/%u", online, max); return true; } static bool do_one_status(struct cfg *cfg, struct server *server) { unsigned online, max; const char *error; bool rv = true; info("• %s", server->name); if (list_empty(&server->scfg.rcons)) info(" rcon : not configured"); else if (do_rcon_pcount(cfg, server, &online, &max, &error)) info(" rcon : %sok%s", ansi_green, ansi_normal); else { info(" rcon : %sfail%s (%s)", ansi_red, ansi_normal, error); rv = false; } if (list_empty(&server->scfg.remotes)) info(" mc : not configured"); else if (do_mc_pcount(cfg, server, &online, &max, &error)) info(" mc : %sok%s", ansi_green, ansi_normal); else { info(" mc : %sfail%s (%s)", ansi_red, ansi_normal, error); rv = false; } if (!server->scfg.systemd_service || !server->scfg.systemd_obj) info(" systemd service : not configured"); else if (systemd_service_running(&server->scfg, &error)) info(" systemd service : %sactive%s", ansi_green, ansi_normal); else { info(" systemd service : %sfail%s (%s)", ansi_red, ansi_normal, error); rv = false; } return rv; } bool do_status(struct cfg *cfg) { struct server *server; if (cfg->default_set) { server = server_get_default(cfg); if (!server) { error("failed to get default server"); return false; } do_one_status(cfg, server); } else { server_read_all_configs(cfg, false); list_for_each_entry(server, &cfg->servers, list) do_one_status(cfg, server); } systemd_delete(); return true; }