summaryrefslogtreecommitdiff
path: root/minecproxy/server-config.c
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-23 20:56:22 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-23 20:56:22 +0200
commitea053d96f7e89e053d4af8d39b04c5428760345f (patch)
tree8182ca73675ad3933b0f38cb48a99c69101309b4 /minecproxy/server-config.c
parent8c27290245b7bcc7cd2f72f3b4a7562294b43bbe (diff)
Big renaming, move some more functionality to shared lib
Diffstat (limited to 'minecproxy/server-config.c')
-rw-r--r--minecproxy/server-config.c580
1 files changed, 580 insertions, 0 deletions
diff --git a/minecproxy/server-config.c b/minecproxy/server-config.c
new file mode 100644
index 0000000..549cf16
--- /dev/null
+++ b/minecproxy/server-config.c
@@ -0,0 +1,580 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdbool.h>
+#include <sys/inotify.h>
+#include <dirent.h>
+#include <errno.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include "main.h"
+#include "uring.h"
+#include "config-parser.h"
+#include "server.h"
+#include "server-config.h"
+
+static void
+scfg_dns_cb(struct dns_async *dns, bool (*server_cb)(struct server *, struct saddr *))
+{
+ struct server *server;
+ struct sockaddr_in *in4;
+ struct sockaddr_in6 *in6;
+ struct saddr *saddr;
+ struct addrinfo *results = NULL, *ai;
+ int r;
+
+ assert_return(dns && dns->priv && server_cb);
+
+ server = dns->priv;
+ debug(DBG_DNS, "called, dns: %p, name: %s, server: %p, server->name: %s",
+ dns, dns->name, server, server->name);
+
+ r = gai_error(&dns->gcb);
+ if (r == EAI_INPROGRESS) {
+ /* This shouldn't happen, assume we'll get called again */
+ error("called with request in progress");
+ return;
+ } else if (r == EAI_CANCELED) {
+ /* The server must be in the process of going away */
+ goto out;
+ } else if (r < 0) {
+ error("DNS lookup of %s:%s failed: %s",
+ dns->name, dns->port, gai_strerror(r));
+ goto out;
+ }
+
+ results = dns->gcb.ar_result;
+
+ for (ai = results; ai; ai = ai->ai_next) {
+ saddr = zmalloc(sizeof(*saddr));
+ if (!saddr) {
+ error("DNS lookup of %s:%s failed: %m", dns->name, dns->port);
+ goto out;
+ }
+
+ switch (ai->ai_family) {
+ case AF_INET:
+ in4 = (struct sockaddr_in *)ai->ai_addr;
+ saddr_set_ipv4(saddr, in4->sin_addr.s_addr, in4->sin_port);
+ server_cb(server, saddr);
+ break;
+
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *)ai->ai_addr;
+ saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port);
+ server_cb(server, saddr);
+ break;
+
+ default:
+ error("getaddrinfo(%s:%s): unknown address family (%i)",
+ dns->name, dns->port, ai->ai_family);
+ xfree(saddr);
+ break;
+ }
+ }
+
+out:
+ freeaddrinfo(results);
+ list_del(&dns->list);
+ xfree(dns);
+ uring_task_put(&server->task);
+ server_commit(server);
+}
+
+static void
+scfg_local_dns_cb(struct dns_async *dns)
+{
+ assert_return(dns);
+
+ scfg_dns_cb(dns, server_add_local);
+}
+
+static void
+scfg_remote_dns_cb(struct dns_async *dns)
+{
+ assert_return(dns);
+
+ scfg_dns_cb(dns, server_add_remote);
+}
+
+static void
+scfg_rcon_dns_cb(struct dns_async *dns)
+{
+ assert_return(dns);
+
+ scfg_dns_cb(dns, server_add_rcon);
+}
+
+enum scfg_keys {
+ SCFG_KEY_INVALID = 0,
+ SCFG_KEY_TYPE,
+ SCFG_KEY_NAME,
+ SCFG_KEY_PORT,
+ SCFG_KEY_LOCAL,
+ SCFG_KEY_REMOTE,
+ SCFG_KEY_IDLE_TIMEOUT,
+ SCFG_KEY_STOP_METHOD,
+ SCFG_KEY_START_METHOD,
+ SCFG_KEY_STOP_EXEC,
+ SCFG_KEY_START_EXEC,
+ SCFG_KEY_RCON,
+ SCFG_KEY_RCON_PASSWORD,
+ SCFG_KEY_SYSTEMD_SERVICE,
+};
+
+struct cfg_key_value_map scfg_key_map[] = {
+ {
+ .key_name = "type",
+ .key_value = SCFG_KEY_TYPE,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = "name",
+ .key_value = SCFG_KEY_NAME,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = "port",
+ .key_value = SCFG_KEY_PORT,
+ .value_type = CFG_VAL_TYPE_UINT16,
+ }, {
+ .key_name = "local",
+ .key_value = SCFG_KEY_LOCAL,
+ .value_type = CFG_VAL_TYPE_ASYNC_ADDRS,
+ }, {
+ .key_name = "remote",
+ .key_value = SCFG_KEY_REMOTE,
+ .value_type = CFG_VAL_TYPE_ASYNC_ADDRS,
+ }, {
+ .key_name = "idle_timeout",
+ .key_value = SCFG_KEY_IDLE_TIMEOUT,
+ .value_type = CFG_VAL_TYPE_UINT16,
+ }, {
+ .key_name = "stop_method",
+ .key_value = SCFG_KEY_STOP_METHOD,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = "start_method",
+ .key_value = SCFG_KEY_START_METHOD,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = "stop_exec",
+ .key_value = SCFG_KEY_STOP_EXEC,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = "start_exec",
+ .key_value = SCFG_KEY_START_EXEC,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = "rcon",
+ .key_value = SCFG_KEY_RCON,
+ .value_type = CFG_VAL_TYPE_ASYNC_ADDRS,
+ }, {
+ .key_name = "rcon_password",
+ .key_value = SCFG_KEY_RCON_PASSWORD,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = "systemd_service",
+ .key_value = SCFG_KEY_SYSTEMD_SERVICE,
+ .value_type = CFG_VAL_TYPE_STRING,
+ }, {
+ .key_name = NULL,
+ .key_value = SCFG_KEY_INVALID,
+ .value_type = CFG_VAL_TYPE_INVALID,
+ }
+};
+
+static bool
+handle_dns(struct server *server, const char *type,
+ struct cfg_value *value, dns_cb_t *async_cb,
+ bool (*sync_cb)(struct server *, struct saddr *))
+{
+ struct saddr *saddr, *tmp;
+ struct dns_async *dns;
+
+ assert_return(server && type && value && async_cb && sync_cb, false);
+
+ switch (value->type) {
+ case CFG_VAL_TYPE_ADDRS:
+ debug(DBG_DNS, "%s: got immediate addrs", type);
+
+ list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) {
+ list_del(&saddr->list);
+ sync_cb(server, saddr);
+ }
+ return true;
+
+ case CFG_VAL_TYPE_ASYNC_ADDRS:
+ debug(DBG_DNS, "%s: doing async lookup of DNS record: %p",
+ type, value->dns_async);
+
+ dns = value->dns_async;
+ dns->cb = async_cb;
+ dns->priv = server;
+ list_add(&dns->list, &server->dnslookups);
+ uring_task_get(&server->task);
+ return true;
+
+ default:
+ return false;
+ }
+}
+
+static void
+scfg_parse(struct server *server)
+{
+ char *pos;
+
+ assert_return(server);
+
+ pos = server->tbuf.buf;
+
+ if (!config_parse_header(server->name, "server", &pos))
+ return;
+
+ while (true) {
+ int key;
+ const char *keyname;
+ struct cfg_value value;
+
+ if (!config_parse_line(server->name, &pos, scfg_key_map,
+ &key, &keyname, &value))
+ break;
+
+ if (key == SCFG_KEY_INVALID)
+ break;
+
+ debug(DBG_CFG, "%s: key %s", server->name, keyname);
+
+ switch (key) {
+
+ case SCFG_KEY_TYPE:
+ if (streq(value.str, "proxy")) {
+ if (!server_set_type(server, SERVER_TYPE_PROXY))
+ return;
+ } else if (streq(value.str, "announce")) {
+ if (!server_set_type(server, SERVER_TYPE_ANNOUNCE))
+ return;
+ }
+ break;
+
+ case SCFG_KEY_NAME:
+ if (!server_set_pretty_name(server, value.str))
+ return;
+ break;
+
+ case SCFG_KEY_PORT:
+ if (!server_set_port(server, value.uint16))
+ return;
+ break;
+
+ case SCFG_KEY_LOCAL:
+ if (!handle_dns(server, "local", &value,
+ scfg_local_dns_cb, server_add_local))
+ return;
+ break;
+
+ case SCFG_KEY_REMOTE:
+ if (!handle_dns(server, "remote", &value,
+ scfg_remote_dns_cb, server_add_remote))
+ return;
+ break;
+
+ case SCFG_KEY_IDLE_TIMEOUT:
+ if (!server_set_idle_timeout(server, value.uint16))
+ return;
+ break;
+
+ case SCFG_KEY_STOP_METHOD:
+ if (streq(value.str, "exec")) {
+ if (server_set_stop_method(server, SERVER_STOP_METHOD_EXEC))
+ break;
+ } else if (streq(value.str, "rcon")) {
+ if (server_set_stop_method(server, SERVER_STOP_METHOD_RCON))
+ break;
+ } else if (streq(value.str, "systemd")) {
+ if (server_set_stop_method(server, SERVER_STOP_METHOD_SYSTEMD))
+ break;
+ }
+ return;
+
+ case SCFG_KEY_START_METHOD:
+ if (streq(value.str, "exec")) {
+ if (server_set_start_method(server, SERVER_START_METHOD_EXEC))
+ break;
+ } else if (streq(value.str, "systemd")) {
+ if (server_set_start_method(server, SERVER_START_METHOD_SYSTEMD))
+ break;
+ }
+ return;
+
+ case SCFG_KEY_STOP_EXEC:
+ if (!server_set_stop_exec(server, value.str))
+ return;
+ break;
+
+ case SCFG_KEY_START_EXEC:
+ if (!server_set_start_exec(server, value.str))
+ return;
+ break;
+
+ case SCFG_KEY_RCON:
+ if (!handle_dns(server, "rcon", &value,
+ scfg_rcon_dns_cb, server_add_rcon))
+ return;
+ break;
+
+ case SCFG_KEY_RCON_PASSWORD:
+ if (!server_set_rcon_password(server, value.str))
+ return;
+ break;
+
+ case SCFG_KEY_SYSTEMD_SERVICE:
+ if (!server_set_systemd_service(server, value.str))
+ return;
+ break;
+
+ case SCFG_KEY_INVALID:
+ default:
+ break;
+ }
+ }
+}
+
+static void
+scfg_read_cb(struct uring_task *task, int res)
+{
+ struct server *server = container_of(task, struct server, task);
+
+ assert_return(task);
+ assert_task_alive(DBG_CFG, task);
+
+ if (res <= 0) {
+ error("error reading config file for %s: %s",
+ server->name, strerror(-res));
+ server_delete(server);
+ }
+
+ debug(DBG_CFG, "%s: parsing cfg (%i bytes)", server->name, res);
+ uring_task_close_fd(&server->task);
+ scfg_parse(server);
+ server_commit(server);
+}
+
+static void
+scfg_open_cb(struct uring_task *task, int res)
+{
+ struct server *server = container_of(task, struct server, task);
+
+ assert_return(task);
+ assert_task_alive(DBG_CFG, task);
+
+ if (res < 0) {
+ error("open(%s) failed: %s", server->name, strerror(-res));
+ server_delete(server);
+ return;
+ }
+
+ debug(DBG_CFG, "reading server cfg %s (fd %i)", server->name, res);
+ uring_task_set_fd(&server->task, res);
+ uring_tbuf_read_until_eof(&server->task, scfg_read_cb);
+}
+
+static bool
+scfg_valid_filename(const char *name)
+{
+ const char *suffix;
+
+ if (empty_str(name))
+ return false;
+ if (name[0] == '.')
+ return false;
+ if ((suffix = strrchr(name, '.')) == NULL)
+ return false;
+ if (!streq(suffix, ".server"))
+ return false;
+
+ return true;
+}
+
+struct server_cfg_monitor {
+ struct uring_task task;
+ char buf[4096] __attribute__((aligned(__alignof__(struct inotify_event))));
+};
+
+static void
+scfgm_free(struct uring_task *task)
+{
+ struct server_cfg_monitor *scfgm = container_of(task,
+ struct server_cfg_monitor,
+ task);
+
+ assert_return(task);
+
+ debug(DBG_CFG, "called");
+ xfree(scfgm);
+ cfg->server_cfg_monitor = NULL;
+}
+
+static void
+inotify_event_dump(const struct inotify_event *event)
+{
+ assert_return(event);
+
+ debug(DBG_CFG, "inotify event:");
+ debug(DBG_CFG, " * WD : %i", event->wd);
+ debug(DBG_CFG, " * Cookie : %" PRIu32, event->cookie);
+ debug(DBG_CFG, " * Length : %" PRIu32, event->len);
+ debug(DBG_CFG, " * Name : %s", event->name);
+ debug(DBG_CFG, " * Mask : %" PRIu32, event->mask);
+ if (event->mask & IN_ACCESS)
+ debug(DBG_CFG, "\tIN_ACCESS");
+ else if(event->mask & IN_MODIFY)
+ debug(DBG_CFG, "\tIN_MODIFY");
+ else if(event->mask & IN_ATTRIB)
+ debug(DBG_CFG, "\tIN_ATTRIB");
+ else if(event->mask & IN_CLOSE_WRITE)
+ debug(DBG_CFG, "\tIN_CLOSE_WRITE");
+ else if(event->mask & IN_CLOSE_NOWRITE)
+ debug(DBG_CFG, "\tIN_CLOSE_NOWRITE");
+ else if(event->mask & IN_OPEN)
+ debug(DBG_CFG, "\tIN_OPEN");
+ else if(event->mask & IN_MOVED_FROM)
+ debug(DBG_CFG, "\tIN_MOVED_FROM");
+ else if(event->mask & IN_MOVED_TO)
+ debug(DBG_CFG, "\tIN_MOVED_TO");
+ else if(event->mask & IN_CREATE)
+ debug(DBG_CFG, "\tIN_CREATE");
+ else if(event->mask & IN_DELETE)
+ debug(DBG_CFG, "\tIN_DELETE");
+ else if(event->mask & IN_DELETE_SELF)
+ debug(DBG_CFG, "\tIN_DELETE_SELF");
+ else if(event->mask & IN_MOVE_SELF)
+ debug(DBG_CFG, "\tIN_MOVE_SELF");
+ else if(event->mask & IN_UNMOUNT)
+ debug(DBG_CFG, "\tIN_UNMOUNT");
+ else if(event->mask & IN_Q_OVERFLOW)
+ debug(DBG_CFG, "\tIN_Q_OVERFLOW");
+ else if(event->mask & IN_IGNORED)
+ debug(DBG_CFG, "\tIN_IGNORED");
+}
+
+static void
+inotify_cb(struct uring_task *task, int res)
+{
+ struct server_cfg_monitor *scfgm = container_of(task,
+ struct server_cfg_monitor,
+ task);
+ const struct inotify_event *event;
+ char *ptr;
+ struct server *server;
+
+ assert_return(task);
+ assert_task_alive(DBG_CFG, task);
+
+ if (res <= 0) {
+ error("inotify_read: %i", res);
+ return;
+ }
+
+ for (ptr = scfgm->buf; ptr < scfgm->buf + res; ptr += sizeof(struct inotify_event) + event->len) {
+ event = (const struct inotify_event *)ptr;
+
+ if (debug_enabled(DBG_CFG))
+ inotify_event_dump(event);
+
+ if (event->mask & (IN_IGNORED | IN_MOVE_SELF | IN_DELETE_SELF | IN_UNMOUNT))
+ die("configuration directory gone, exiting");
+
+ if (event->mask & IN_Q_OVERFLOW) {
+ error("inotify queue overflow");
+ continue;
+ }
+
+ if (!scfg_valid_filename(event->name))
+ continue;
+
+ if (event->mask & (IN_MOVED_FROM | IN_DELETE))
+ server_delete_by_name(event->name);
+ else if (event->mask & (IN_MOVED_TO | IN_CREATE | IN_CLOSE_WRITE)) {
+ server = server_new(event->name);
+ verbose("New server config file detected: %s", server->name);
+ uring_openat(&server->task, server->name, scfg_open_cb);
+ } else
+ error("inotify: unknown event: 0x%08x", event->mask);
+ }
+
+ uring_read(&scfgm->task, scfgm->buf, sizeof(scfgm->buf), inotify_cb);
+}
+
+void
+server_cfg_monitor_refdump()
+{
+ assert_return_silent(cfg->server_cfg_monitor);
+
+ uring_task_refdump(&cfg->server_cfg_monitor->task);
+}
+
+void
+server_cfg_monitor_delete()
+{
+ assert_return(cfg->server_cfg_monitor);
+
+ debug(DBG_CFG, "closing fd %i", cfg->server_cfg_monitor->task.fd);
+ uring_task_destroy(&cfg->server_cfg_monitor->task);
+ cfg->server_cfg_monitor = NULL;
+}
+
+void
+server_cfg_monitor_init()
+{
+ int ifd;
+ int iwd;
+ struct server_cfg_monitor *scfgm;
+ DIR *dir;
+ struct dirent *dent;
+ struct server *server;
+
+ assert_return(!cfg->server_cfg_monitor);
+
+ scfgm = zmalloc(sizeof(*scfgm));
+ if (!scfgm)
+ die("malloc: %m");
+
+ ifd = inotify_init1(IN_CLOEXEC);
+ if (ifd < 0)
+ die("inotify_init1: %m");
+
+ /* ln = IN_CREATE, cp/vi/mv = IN_CREATE, IN_OPEN, IN_CLOSE_WRITE */
+ iwd = inotify_add_watch(ifd, ".",
+ IN_CLOSE_WRITE | IN_DELETE | IN_CREATE |
+ IN_DELETE_SELF | IN_MOVE_SELF | IN_MOVED_TO |
+ IN_MOVED_FROM | IN_DONT_FOLLOW |
+ IN_EXCL_UNLINK | IN_ONLYDIR );
+ if (iwd < 0)
+ die("inotify_add_watch: %m");
+
+ uring_task_init(&scfgm->task, "server-config-monitor", uring_parent(), scfgm_free);
+ uring_task_set_fd(&scfgm->task, ifd);
+ cfg->server_cfg_monitor = scfgm;
+ uring_read(&scfgm->task, scfgm->buf, sizeof(scfgm->buf), inotify_cb);
+
+ dir = opendir(".");
+ if (!dir)
+ die("opendir(%s): %m", cfg->cfg_dir);
+
+ while ((dent = readdir(dir)) != NULL) {
+ if (dent->d_type != DT_REG && dent->d_type != DT_UNKNOWN)
+ continue;
+ if (!scfg_valid_filename(dent->d_name))
+ continue;
+
+ server = server_new(dent->d_name);
+ if (server)
+ uring_openat(&server->task, server->name, scfg_open_cb);
+ }
+
+ closedir(dir);
+}
+