From ea053d96f7e89e053d4af8d39b04c5428760345f Mon Sep 17 00:00:00 2001 From: David Härdeman Date: Tue, 23 Jun 2020 20:56:22 +0200 Subject: Big renaming, move some more functionality to shared lib --- minecproxy/server-config.c | 580 +++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 580 insertions(+) create mode 100644 minecproxy/server-config.c (limited to 'minecproxy/server-config.c') 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 +#include +#include +#include +#include +#include +#include +#include +#include +#include + +#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); +} + -- cgit v1.2.3