diff options
author | David Härdeman <david@hardeman.nu> | 2020-06-30 08:10:04 +0200 |
---|---|---|
committer | David Härdeman <david@hardeman.nu> | 2020-06-30 08:10:04 +0200 |
commit | a89a0f918925a662503c1bcb28bdb06ab9b7ef25 (patch) | |
tree | 733b4c1ff841f99eeb3840c5948fef6c5bf76109 | |
parent | 8f29a4d23dd13a80aa26951b17e2a71f4e651cb1 (diff) |
Share config parsing fully between server and cmdline tool
-rw-r--r-- | config.h.in | 6 | ||||
-rw-r--r-- | minecctl/mc-commands.c | 4 | ||||
-rw-r--r-- | minecctl/minecctl.c | 16 | ||||
-rw-r--r-- | minecctl/misc-commands.c | 4 | ||||
-rw-r--r-- | minecctl/rcon-commands.c | 14 | ||||
-rw-r--r-- | minecctl/server.c | 87 | ||||
-rw-r--r-- | minecctl/server.h | 7 | ||||
-rw-r--r-- | minecproxy/server-config.c | 268 | ||||
-rw-r--r-- | minecproxy/server-proxy.c | 10 | ||||
-rw-r--r-- | minecproxy/server-rcon.c | 9 | ||||
-rw-r--r-- | minecproxy/server.c | 388 | ||||
-rw-r--r-- | minecproxy/server.h | 73 | ||||
-rw-r--r-- | minecproxy/systemd.c | 76 | ||||
-rw-r--r-- | minecproxy/systemd.h | 2 | ||||
-rw-r--r-- | shared/config-parser.c | 449 | ||||
-rw-r--r-- | shared/config-parser.h | 52 |
16 files changed, 702 insertions, 763 deletions
diff --git a/config.h.in b/config.h.in index bf3bd99..0a93079 100644 --- a/config.h.in +++ b/config.h.in @@ -26,3 +26,9 @@ #define SERVER_CONFIG_FILE_SUFFIX "mcserver" +#define SYSTEMD_DBUS_SERVICE "org.freedesktop.systemd1" + +#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.systemd1.Unit" + +#define SYSTEMD_DBUS_PATH_PREFIX "/org/freedesktop/systemd1/unit/" + diff --git a/minecctl/mc-commands.c b/minecctl/mc-commands.c index d4d2846..424eae9 100644 --- a/minecctl/mc-commands.c +++ b/minecctl/mc-commands.c @@ -19,14 +19,14 @@ bool do_mc_pcount(struct cfg *cfg, unsigned *online, unsigned *max) server = server_get_default(cfg); - fd = connect_any(&server->mc_addrs, true); + fd = connect_any(&server->scfg.remotes, true); if (fd < 0) { error("%s: unable to connect", server->name); return false; } /* FIXME: connect_any needs to indicate the address it used */ - saddr = list_first_entry(&server->mc_addrs, struct saddr, list); + saddr = list_first_entry(&server->scfg.remotes, struct saddr, list); if (!saddr) { error("No saddr"); goto out; diff --git a/minecctl/minecctl.c b/minecctl/minecctl.c index 3d21ae6..231b150 100644 --- a/minecctl/minecctl.c +++ b/minecctl/minecctl.c @@ -44,13 +44,13 @@ static void dump_config(struct cfg *cfg) list_for_each_entry(server, &cfg->servers, list) { info(" * server"); info(" name : %s", server->name); - info(" filename : %s", server->filename); - info(" rcon_password : %s", server->rcon_password); + info(" filename : %s", server->scfg.filename); + info(" rcon_password : %s", server->scfg.rcon_password); info(" file_read : %s", server->file_read ? "yes" : "no"); - list_for_each_entry(saddr, &server->rcon_addrs, list) + list_for_each_entry(saddr, &server->scfg.rcons, list) info(" * rcon addr : %s", saddr->addrstr); - list_for_each_entry(saddr, &server->mc_addrs, list) + list_for_each_entry(saddr, &server->scfg.remotes, list) info(" * mc addr : %s", saddr->addrstr); } @@ -133,10 +133,10 @@ static bool create_server_from_cmdline_args(struct cfg *cfg) if (!cfg->rcon_addrstr && !cfg->mc_addrstr) return false; - server = server_new(); + server = server_new(NULL); if (cfg->rcon_addrstr) { - if (!str_to_addrs(cfg->rcon_addrstr, &server->rcon_addrs)) + if (!str_to_addrs(cfg->rcon_addrstr, &server->scfg.rcons)) goto error; server->name = cfg->rcon_addrstr; @@ -144,7 +144,7 @@ static bool create_server_from_cmdline_args(struct cfg *cfg) } if (cfg->mc_addrstr) { - if (!str_to_addrs(cfg->mc_addrstr, &server->mc_addrs)) + if (!str_to_addrs(cfg->mc_addrstr, &server->scfg.remotes)) goto error; if (!server->name) @@ -156,7 +156,7 @@ static bool create_server_from_cmdline_args(struct cfg *cfg) } if (cfg->rcon_password) { - server->rcon_password = cfg->rcon_password; + server->scfg.rcon_password = cfg->rcon_password; cfg->rcon_password = NULL; } diff --git a/minecctl/misc-commands.c b/minecctl/misc-commands.c index 52797da..f20cac5 100644 --- a/minecctl/misc-commands.c +++ b/minecctl/misc-commands.c @@ -9,9 +9,9 @@ bool do_list(struct cfg *cfg) { struct server *server; - /* server->filename check excludes servers created from cmdline */ + /* server->scfg.filename check excludes servers created from cmdline */ list_for_each_entry(server, &cfg->servers, list) - if (server->filename) + if (server->scfg.filename) info("%s", server->name); return true; diff --git a/minecctl/rcon-commands.c b/minecctl/rcon-commands.c index 5f98b72..2f1687b 100644 --- a/minecctl/rcon-commands.c +++ b/minecctl/rcon-commands.c @@ -110,30 +110,30 @@ static int rcon_login(struct cfg *cfg, struct server *server) assert_die(cfg && server, "invalid arguments"); - if (list_empty(&server->rcon_addrs)) { + if (list_empty(&server->scfg.rcons)) { error("%s: rcon address unknown", server->name); goto error; } - fd = connect_any(&server->rcon_addrs, true); + fd = connect_any(&server->scfg.rcons, true); if (fd < 0) { error("%s: unable to connect", server->name); goto error; } - if (!server->rcon_password) - server->rcon_password = ask_password(); + if (!server->scfg.rcon_password) + server->scfg.rcon_password = ask_password(); - if (!server->rcon_password) { + if (!server->scfg.rcon_password) { error("%s: can't login - password missing", server->name); goto error; } - send_msg(fd, buf, sizeof(buf), RCON_PACKET_LOGIN, server->rcon_password, + send_msg(fd, buf, sizeof(buf), RCON_PACKET_LOGIN, server->scfg.rcon_password, &rtype, &reply); explicit_bzero(buf, sizeof(buf)); - free_password(&server->rcon_password); + free_password(&server->scfg.rcon_password); if (rtype == RCON_PACKET_LOGIN_OK) verbose("%s: login ok", server->name); diff --git a/minecctl/server.c b/minecctl/server.c index aca69a0..21df7f4 100644 --- a/minecctl/server.c +++ b/minecctl/server.c @@ -7,8 +7,6 @@ #include "minecctl.h" #include "server.h" #include "misc.h" -#include "config-parser.h" -#include "server-config-options.h" void server_read_config(struct cfg *cfg, struct server *server) { @@ -17,9 +15,8 @@ void server_read_config(struct cfg *cfg, struct server *server) ssize_t r; int dfd; int fd; - char *pos = buf; - if (!server || !server->filename || server->file_read) + if (!server || !server->scfg.filename || server->file_read) return; server->file_read = true; @@ -28,73 +25,39 @@ void server_read_config(struct cfg *cfg, struct server *server) if (dfd < 0) die("Failed to open %s: %m", cfg->cfgdir); - fd = openat(dfd, server->filename, O_RDONLY | O_CLOEXEC); + fd = openat(dfd, server->scfg.filename, O_RDONLY | O_CLOEXEC); if (fd < 0) - die("Failed to open %s: %m", server->filename); + die("Failed to open %s: %m", server->scfg.filename); close(dfd); while (true) { r = read(fd, buf + off, sizeof(buf) - off - 1); if (r < 0) - die("Failed to read %s: %m", server->filename); + die("Failed to read %s: %m", server->scfg.filename); else if (r == 0) break; off += r; if (off == sizeof(buf) - 1) die("Failed to read %s: file too large", - server->filename); + server->scfg.filename); } buf[off] = '\0'; close(fd); - if (!config_parse_header(SERVER_CFG_HEADER, &pos)) - die("Unable to parse %s: invalid/missing header", - server->filename); + if (!scfg_parse(&server->scfg, buf, NULL)) + die("Unable to parse %s", server->scfg.filename); - /* FIXME: this will cause superflous DNS lookups of other cfg entries */ - while (true) { - int key; - const char *keyname; - struct cfg_value value; - - if (!config_parse_line(server->filename, &pos, scfg_key_map, - &key, &keyname, &value, false)) - break; - - switch (key) { - case SCFG_KEY_RCON: - if (!list_empty(&server->rcon_addrs)) - die("rcon address defined twice in %s", - server->filename); - list_replace(&value.saddrs, &server->rcon_addrs); - break; - case SCFG_KEY_RCON_PASSWORD: - if (server->rcon_password) - die("rcon password defined twice in %s", - server->filename); - server->rcon_password = xstrdup(value.str); - break; - case SCFG_KEY_REMOTE: - if (!list_empty(&server->mc_addrs)) - die("rcon address defined twice in %s", - server->filename); - list_replace(&value.saddrs, &server->mc_addrs); - default: - continue; - } - } - - if (!server->rcon_password) - verbose("rcon password not found in %s", server->filename); + if (!server->scfg.rcon_password) + verbose("rcon password not found in %s", server->scfg.filename); - if (list_empty(&server->rcon_addrs)) - verbose("rcon address not found in %s", server->filename); + if (list_empty(&server->scfg.rcons)) + verbose("rcon address not found in %s", server->scfg.filename); - if (list_empty(&server->mc_addrs)) - verbose("mc server address not found in %s", server->filename); + if (list_empty(&server->scfg.remotes)) + verbose("mc server address not found in %s", server->scfg.filename); } struct server *server_get_default(struct cfg *cfg) @@ -144,8 +107,7 @@ void server_load_all_known(struct cfg *cfg) if (!is_valid_server_config_filename(dent, NULL)) continue; - server = server_new(); - server->filename = xstrdup(dent->d_name); + server = server_new(dent->d_name); suffix = strrchr(dent->d_name, '.'); assert_die(suffix, "Error parsing filename"); @@ -168,32 +130,17 @@ void server_free_all(struct cfg *cfg) void server_free(struct server *server) { - struct saddr *saddr, *tmp; - + scfg_delete(&server->scfg); xfree(server->name); - xfree(server->filename); - free_password(&server->rcon_password); - - list_for_each_entry_safe(saddr, tmp, &server->rcon_addrs, list) { - list_del(&saddr->list); - xfree(saddr); - } - - list_for_each_entry_safe(saddr, tmp, &server->mc_addrs, list) { - list_del(&saddr->list); - xfree(saddr); - } - xfree(server); } -struct server *server_new() +struct server *server_new(const char *filename) { struct server *server; server = zmalloc(sizeof(*server)); - INIT_LIST_HEAD(&server->rcon_addrs); - INIT_LIST_HEAD(&server->mc_addrs); + scfg_init(&server->scfg, filename); INIT_LIST_HEAD(&server->list); return server; diff --git a/minecctl/server.h b/minecctl/server.h index 89ee44c..a0c0a76 100644 --- a/minecctl/server.h +++ b/minecctl/server.h @@ -3,13 +3,12 @@ #include <stdbool.h> +#include "config-parser.h" + struct server { bool file_read; char *name; - char *filename; - char *rcon_password; - struct list_head rcon_addrs; - struct list_head mc_addrs; + struct server_config scfg; struct list_head list; }; diff --git a/minecproxy/server-config.c b/minecproxy/server-config.c index 71463ee..6fdd73a 100644 --- a/minecproxy/server-config.c +++ b/minecproxy/server-config.c @@ -14,262 +14,7 @@ #include "config-parser.h" #include "server.h" #include "server-config.h" -#include "server-config-options.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); -} - -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_CFG_HEADER, &pos)) { - verbose("%s: missing/invalid header", server->name); - 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, true)) - 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; - } - } -} +#include "config.h" static void scfg_read_cb(struct uring_task *task, int res) { @@ -286,7 +31,14 @@ static void scfg_read_cb(struct uring_task *task, int res) debug(DBG_CFG, "%s: parsing cfg (%i bytes)", server->name, res); uring_task_close_fd(&server->task); - scfg_parse(server); + + if (!scfg_parse(&server->scfg, server->tbuf.buf, + server_async_dns_update)) { + error("%s: failed to parse config file", server->name); + server_delete(server); + return; + } + server_commit(server); } @@ -403,7 +155,7 @@ static void inotify_cb(struct uring_task *task, int res) continue; if (event->mask & (IN_MOVED_FROM | IN_DELETE)) - server_delete_by_name(event->name); + server_delete_by_filename(event->name); else if (event->mask & (IN_MOVED_TO | IN_CREATE | IN_CLOSE_WRITE)) { server = server_new(event->name); diff --git a/minecproxy/server-proxy.c b/minecproxy/server-proxy.c index b80a220..787934d 100644 --- a/minecproxy/server-proxy.c +++ b/minecproxy/server-proxy.c @@ -351,7 +351,7 @@ static void proxy_connect_timer_cb(struct ptimer_task *ptask) return; proxy->connecting = true; - connect_any(&proxy->servertask, &proxy->server->remotes, + connect_any(&proxy->servertask, &proxy->server->scfg.remotes, &proxy->server_conn, proxy_connected_cb); } @@ -412,8 +412,8 @@ struct server_proxy *proxy_new(struct server *server, struct saddr *client, } proxy->connecting = true; - connect_any(&proxy->servertask, &server->remotes, &proxy->server_conn, - proxy_connected_cb); + connect_any(&proxy->servertask, &server->scfg.remotes, + &proxy->server_conn, proxy_connected_cb); return proxy; @@ -448,9 +448,9 @@ static void local_accept(struct uring_task *task, int res) verbose("%s: incoming proxy connection: %s -> %s", server->name, local->client.addrstr, local->local.addrstr); - if (list_empty(&server->remotes)) { + if (list_empty(&server->scfg.remotes)) { /* This shouldn't be possible, checked before opening local */ - error("server->remotes empty!"); + error("server->scfg.remotes empty!"); uring_close(&local->task, res); goto out; } diff --git a/minecproxy/server-rcon.c b/minecproxy/server-rcon.c index 9562aa9..9ae49ef 100644 --- a/minecproxy/server-rcon.c +++ b/minecproxy/server-rcon.c @@ -168,7 +168,8 @@ static void rcon_connected_cb(struct connection *conn, bool connected) rcon_protocol_create_packet(server->rcon_tbuf.buf, sizeof(server->rcon_tbuf.buf), &server->rcon_tbuf.len, 1, - RCON_PACKET_LOGIN, server->rcon_password); + RCON_PACKET_LOGIN, + server->scfg.rcon_password); uring_tbuf_write(&server->rcon_task, rcon_login_sent); } @@ -183,11 +184,11 @@ static void rcon_free(struct uring_task *task) void rcon_stop(struct server *server) { - assert_return(server && !list_empty(&server->rcons) && - !empty_str(server->rcon_password)); + assert_return(server && !list_empty(&server->scfg.rcons) && + !empty_str(server->scfg.rcon_password)); assert_task_alive(DBG_RCON, &server->rcon_task); - connect_any(&server->rcon_task, &server->rcons, &server->rcon_conn, + connect_any(&server->rcon_task, &server->scfg.rcons, &server->rcon_conn, rcon_connected_cb); } diff --git a/minecproxy/server.c b/minecproxy/server.c index 8c521cf..81d148b 100644 --- a/minecproxy/server.c +++ b/minecproxy/server.c @@ -14,26 +14,12 @@ #include "uring.h" #include "ptimer.h" #include "server.h" +#include "server-config.h" #include "server-proxy.h" #include "server-rcon.h" -#include "config-parser.h" #include "idle.h" #include "systemd.h" -static bool set_property(struct server *server, char **property, - const char *value) -{ - assert_return(server && !*property && !empty_str(value), false); - - *property = xstrdup(value); - if (!*property) { - error("strdup: %m"); - return false; - } - - return true; -} - void server_refdump(struct server *server) { struct server_local *local; @@ -45,7 +31,7 @@ void server_refdump(struct server *server) uring_task_refdump(&server->exec_task); uring_task_refdump(&server->ann_task); uring_task_refdump(&server->idle_task); - list_for_each_entry(local, &server->locals, list) + list_for_each_entry(local, &server->listenings, list) local_refdump(local); list_for_each_entry(proxy, &server->proxys, list) proxy_refdump(proxy); @@ -60,12 +46,6 @@ static void server_free(struct uring_task *task) debug(DBG_SRV, "freeing server %s (%p)", server->name, server); list_del(&server->list); - xfree(server->pretty_name); - xfree(server->start_exec); - xfree(server->stop_exec); - xfree(server->systemd_service); - xfree(server->systemd_obj); - xfree(server->rcon_password); xfree(server->name); xfree(server); } @@ -74,10 +54,6 @@ void server_delete(struct server *server) { struct server_local *local, *ltmp; struct server_proxy *proxy, *ptmp; - struct saddr *remote; - struct saddr *rcon; - struct saddr *tmp; - struct dns_async *dns, *dtmp; assert_return(server); @@ -86,40 +62,28 @@ void server_delete(struct server *server) rcon_delete(server); - list_for_each_entry_safe(local, ltmp, &server->locals, list) + list_for_each_entry_safe(local, ltmp, &server->listenings, list) local_delete(local); list_for_each_entry_safe(proxy, ptmp, &server->proxys, list) proxy_delete(proxy); - list_for_each_entry_safe(rcon, tmp, &server->rcons, list) { - list_del(&rcon->list); - xfree(rcon); - } - - list_for_each_entry_safe(remote, tmp, &server->remotes, list) { - list_del(&remote->list); - xfree(remote); - } - - list_for_each_entry_safe(dns, dtmp, &server->dnslookups, list) - gai_cancel(&dns->gcb); - uring_task_destroy(&server->idle_task); uring_poll_cancel(&server->exec_task); uring_task_put(&server->exec_task); uring_task_destroy(&server->task); uring_task_put(&server->ann_task); + scfg_delete(&server->scfg); } -void server_delete_by_name(const char *name) +void server_delete_by_filename(const char *filename) { struct server *server; - assert_return(!empty_str(name)); + assert_return(!empty_str(filename)); list_for_each_entry(server, &cfg->servers, list) { - if (streq(server->name, name)) { + if (streq(server->scfg.filename, filename)) { server_delete(server); return; } @@ -135,7 +99,7 @@ static void server_dump(struct server *server) assert_return(server); verbose("Server %s:", server->name); - switch (server->type) { + switch (server->scfg.type) { case SERVER_TYPE_ANNOUNCE: verbose(" * Type: announce"); break; @@ -147,24 +111,24 @@ static void server_dump(struct server *server) break; } verbose(" * Name: %s", - server->pretty_name ? server->pretty_name : "<undefined>"); - verbose(" * Announce port: %" PRIu16, server->announce_port); + server->scfg.pretty_name ? server->scfg.pretty_name : "<undefined>"); + verbose(" * Announce port: %" PRIu16, server->scfg.announce_port); - if (!list_empty(&server->locals)) { + if (!list_empty(&server->listenings)) { verbose(" * Local:"); - list_for_each_entry(local, &server->locals, list) + list_for_each_entry(local, &server->listenings, list) verbose(" * %s", local->local.addrstr); } - if (!list_empty(&server->remotes)) { + if (!list_empty(&server->scfg.remotes)) { verbose(" * Remote:"); - list_for_each_entry(remote, &server->remotes, list) + list_for_each_entry(remote, &server->scfg.remotes, list) verbose(" * %s", remote->addrstr); } - if (!list_empty(&server->rcons)) { + if (!list_empty(&server->scfg.rcons)) { verbose(" * RCon:"); - list_for_each_entry(rcon, &server->rcons, list) + list_for_each_entry(rcon, &server->scfg.rcons, list) verbose(" * %s", rcon->addrstr); } @@ -253,7 +217,7 @@ static bool server_check_running(struct server *server) assert_return(server, false); /* FIXME: other methods, rcon? */ - if (server->systemd_service) { + if (server->scfg.systemd_service) { verbose("%s: checking if systemd service is running", server->name); if (systemd_service_running(server)) { @@ -273,14 +237,16 @@ bool server_start(struct server *server) assert_return(server, false); assert_task_alive_or(DBG_SRV, &server->task, return false); - switch (server->start_method) { + switch (server->scfg.start_method) { case SERVER_START_METHOD_EXEC: - verbose("Starting server %s via external cmd", server->name); - return server_exec(server, server->start_exec); + verbose("Starting server %s via external cmd", + server->name); + return server_exec(server, server->scfg.start_exec); case SERVER_START_METHOD_SYSTEMD: - verbose("Starting server %s via systemd (%s)", server->name, - server->systemd_service); + verbose("Starting server %s via systemd (%s)", + server->name, + server->scfg.systemd_service); if (systemd_service_start(server)) { server->state = SERVER_STATE_RUNNING; @@ -301,14 +267,16 @@ bool server_stop(struct server *server) assert_return(server, false); assert_task_alive_or(DBG_SRV, &server->task, return false); - switch (server->stop_method) { + switch (server->scfg.stop_method) { case SERVER_STOP_METHOD_EXEC: - verbose("Stopping server %s via external cmd", server->name); - return server_exec(server, server->stop_exec); + verbose("Stopping server %s via external cmd", + server->name); + return server_exec(server, server->scfg.stop_exec); case SERVER_STOP_METHOD_SYSTEMD: - verbose("Stopping server %s via systemd (%s)", server->name, - server->systemd_service); + verbose("Stopping server %s via systemd (%s)", + server->name, + server->scfg.systemd_service); if (systemd_service_stop(server)) { server->state = SERVER_STATE_STOPPED; return true; @@ -340,7 +308,8 @@ void server_set_active_players(struct server *server, int count) assert_return(server); assert_task_alive(DBG_IDLE, &server->idle_task); - debug(DBG_IDLE, "%s: currently %i active players", server->name, count); + debug(DBG_IDLE, "%s: currently %i active players", + server->name, count); if (count < 0) return; @@ -351,12 +320,24 @@ void server_set_active_players(struct server *server, int count) else if (count == 0) server->idle_count++; - if (server->idle_count > server->idle_timeout) { + if (server->idle_count > server->scfg.idle_timeout) { verbose("stopping idle server %s", server->name); server_stop(server); } } +void server_async_dns_update(struct server_config *scfg, bool done) +{ + struct server *server = container_of(scfg, struct server, scfg); + + if (done) { + uring_task_put(&server->task); + server_commit(server); + } else { + uring_task_get(&server->task); + } +} + static void server_idle_connected_cb(struct connection *conn, bool connected) { struct server *server = container_of(conn, struct server, idle_conn); @@ -385,10 +366,10 @@ bool server_idle_check(struct server *server) server->state == SERVER_STATE_DEAD) return false; - if (server->idle_timeout < 1) + if (server->scfg.idle_timeout < 1) return false; - if (list_empty(&server->remotes)) + if (list_empty(&server->scfg.remotes)) return false; if (!list_empty(&server->proxys)) { @@ -396,8 +377,8 @@ bool server_idle_check(struct server *server) return true; } - connect_any(&server->idle_task, &server->remotes, &server->idle_conn, - server_idle_connected_cb); + connect_any(&server->idle_task, &server->scfg.remotes, + &server->idle_conn, server_idle_connected_cb); return true; } @@ -440,7 +421,7 @@ bool server_announce(struct server *server, int fd) bool server_commit(struct server *server) { - struct server_local *local; + struct saddr *saddr, *tmp; uint16_t port; int r; @@ -457,70 +438,67 @@ bool server_commit(struct server *server) return false; } - if (!list_empty(&server->dnslookups)) { + if (!list_empty(&server->scfg.dnslookups)) { debug(DBG_SRV, "called with pending DNS requests"); return true; } - if (server->stop_method == SERVER_STOP_METHOD_RCON && - list_empty(&server->rcons)) { + if (server->scfg.stop_method == SERVER_STOP_METHOD_RCON && + list_empty(&server->scfg.rcons)) { error("%s: rcon stop method missing rcon address", server->name); return false; } - if (server->stop_method == SERVER_STOP_METHOD_RCON && - !server->rcon_password) { + if (server->scfg.stop_method == SERVER_STOP_METHOD_RCON && + !server->scfg.rcon_password) { error("%s: rcon stop method missing rcon password", server->name); return false; } - if ((server->start_method == SERVER_START_METHOD_SYSTEMD || - server->stop_method == SERVER_STOP_METHOD_SYSTEMD) && - !server->systemd_service) { + if ((server->scfg.start_method == SERVER_START_METHOD_SYSTEMD || + server->scfg.stop_method == SERVER_STOP_METHOD_SYSTEMD) && + !server->scfg.systemd_service) { error("%s: systemd start/stop method missing systemd service", server->name); return false; } - if (server->systemd_service && !server->systemd_obj) { - server->systemd_obj = - systemd_object_path(server->systemd_service); - if (!server->systemd_obj) { - error("%s: failed to create systemd object path (%s)", - server->name, server->systemd_service); - return false; - } + if ((server->scfg.systemd_service && !server->scfg.systemd_obj) || + (!server->scfg.systemd_service && server->scfg.systemd_obj)) { + error("%s: systemd service but no object path or vice versa", + server->name); + return false; } - if (server->idle_timeout > 0 && - server->stop_method == SERVER_STOP_METHOD_UNDEFINED) { + if (server->scfg.idle_timeout > 0 && + server->scfg.stop_method == SERVER_STOP_METHOD_UNDEFINED) { error("%s: idle_timeout set but missing stop method", server->name); return false; } - switch (server->type) { + switch (server->scfg.type) { case SERVER_TYPE_ANNOUNCE: - if (server->announce_port < 1) { + if (server->scfg.announce_port == 0) { error("%s: missing announce port", server->name); return false; } - if (server->start_method != SERVER_START_METHOD_UNDEFINED) { + if (server->scfg.start_method != SERVER_START_METHOD_UNDEFINED) { error("%s: can't set start_method for announce server", server->name); return false; } - if (!list_empty(&server->locals)) { + if (!list_empty(&server->scfg.locals)) { error("%s: can't set local addresses for announce server", server->name); return false; } - if (!list_empty(&server->remotes)) { + if (!list_empty(&server->scfg.remotes)) { error("%s: can't set remote addresses for announce server", server->name); return false; @@ -529,42 +507,42 @@ bool server_commit(struct server *server) break; case SERVER_TYPE_PROXY: - if (server->announce_port >= 1) { + if (server->scfg.announce_port == 0) { error("%s: can't set announce port for proxy server", server->name); return false; } - if (list_empty(&server->locals)) { + if (list_empty(&server->scfg.locals)) { error("%s: missing local addresses for proxy server", server->name); return false; } - if (list_empty(&server->remotes)) { + if (list_empty(&server->scfg.remotes)) { error("%s: missing remote addresses for proxy server", server->name); return false; } - list_for_each_entry(local, &server->locals, list) { - port = saddr_port(&local->local); + list_for_each_entry(saddr, &server->scfg.locals, list) { + port = saddr_port(saddr); if (port == 0) { error("%s: invalid local port", server->name); return false; } - if (server->announce_port < 1) - server->announce_port = port; + if (server->scfg.announce_port == 0) + server->scfg.announce_port = port; - if (server->announce_port != port) { + if (server->scfg.announce_port != port) { error("%s: multiple local ports", server->name); return false; } } - if (server->announce_port < 1) { + if (server->scfg.announce_port == 0) { error("%s: can't determine which port to announce", server->name); return false; @@ -577,7 +555,7 @@ bool server_commit(struct server *server) return false; } - if (!server->pretty_name) { + if (!server->scfg.pretty_name) { char *suffix; suffix = strrchr(server->name, '.'); @@ -586,9 +564,9 @@ bool server_commit(struct server *server) return false; } - server->pretty_name = + server->scfg.pretty_name = xstrndup(server->name, suffix - server->name); - if (!server->pretty_name) { + if (!server->scfg.pretty_name) { error("failed to create display name: %s", server->name); return false; @@ -596,8 +574,9 @@ bool server_commit(struct server *server) } r = snprintf(server->ann_buf.buf, sizeof(server->ann_buf.buf), - "[MOTD]%s[/MOTD][AD]%" PRIu16 "[/AD]", server->pretty_name, - server->announce_port); + "[MOTD]%s[/MOTD][AD]%" PRIu16 "[/AD]", + server->scfg.pretty_name, + server->scfg.announce_port); if (r < 1 || r >= sizeof(server->ann_buf.buf)) { error("%s: unable to create announce msg: %i\n", server->name, r); @@ -609,8 +588,15 @@ bool server_commit(struct server *server) * available before this is called */ server_dump(server); - list_for_each_entry(local, &server->locals, list) + list_for_each_entry_safe(saddr, tmp, &server->scfg.locals, list) { + struct server_local *local; + + /* FIXME: error checks */ + list_del(&saddr->list); + local = local_new(server, saddr); local_open(local); + list_add(&local->list, &server->listenings); + } server->state = SERVER_STATE_CFG_OK; @@ -620,177 +606,48 @@ bool server_commit(struct server *server) return true; } -bool server_add_remote(struct server *server, struct saddr *remote) -{ - assert_return(server && remote, false); - assert_task_alive_or(DBG_SRV, &server->task, return false); - - debug(DBG_SRV, "adding remote: %s", remote->addrstr); - list_add(&remote->list, &server->remotes); - return true; -} - -bool server_add_local(struct server *server, struct saddr *saddr) -{ - struct server_local *local; - - assert_return(server && saddr, false); - assert_task_alive_or(DBG_SRV, &server->task, return false); - - local = local_new(server, saddr); - if (!local) - return false; - - list_add(&local->list, &server->locals); - return true; -} - -bool server_add_rcon(struct server *server, struct saddr *rcon) -{ - assert_return(server && rcon, false); - assert_task_alive_or(DBG_SRV, &server->task, return false); - - debug(DBG_SRV, "adding rcon: %s", rcon->addrstr); - list_add(&rcon->list, &server->rcons); - return true; -} - -bool server_set_rcon_password(struct server *server, const char *password) -{ - assert_return(server && !empty_str(password), false); - - return set_property(server, &server->rcon_password, password); -} - -bool server_set_systemd_service(struct server *server, const char *service) -{ - const char *suffix; - char *tmp; - - assert_return(server && !empty_str(service) && !server->systemd_service, - false); - - suffix = strrchr(service, '.'); - if (!suffix || !streq(suffix, ".service")) { - tmp = zmalloc(strlen(service) + STRLEN(".service") + 1); - if (tmp) - sprintf(tmp, "%s.service", service); - } else - tmp = xstrdup(service); - - if (!tmp) { - error("malloc/strdup: %m"); - return false; - } - - server->systemd_service = tmp; - return true; -} - -bool server_set_stop_method(struct server *server, - enum server_stop_method stop_method) -{ - assert_return(server->stop_method == SERVER_STOP_METHOD_UNDEFINED && - stop_method != SERVER_STOP_METHOD_UNDEFINED, - false); - - server->stop_method = stop_method; - return true; -} - -bool server_set_start_method(struct server *server, - enum server_start_method start_method) -{ - assert_return(server->start_method == SERVER_START_METHOD_UNDEFINED && - start_method != SERVER_START_METHOD_UNDEFINED, - false); - - server->start_method = start_method; - return true; -} - -bool server_set_stop_exec(struct server *server, const char *cmd) -{ - assert_return(server && !empty_str(cmd), false); - - return set_property(server, &server->stop_exec, cmd); -} - -bool server_set_start_exec(struct server *server, const char *cmd) -{ - assert_return(server && !empty_str(cmd), false); - - return set_property(server, &server->start_exec, cmd); -} - -bool server_set_idle_timeout(struct server *server, uint16_t timeout) -{ - assert_return(server && timeout > 0 && server->idle_timeout == 0, - false); - - server->idle_timeout = timeout; - return true; -} - -bool server_set_port(struct server *server, uint16_t port) -{ - assert_return(server && port > 0 && server->announce_port == 0, false); - - server->announce_port = htons(port); - return true; -} - -bool server_set_type(struct server *server, enum server_type type) -{ - assert_return(server && type != SERVER_TYPE_UNDEFINED, false); - - switch (type) { - case SERVER_TYPE_ANNOUNCE: - server->type = SERVER_TYPE_ANNOUNCE; - break; - case SERVER_TYPE_PROXY: - server->type = SERVER_TYPE_PROXY; - break; - default: - return false; - } - - return true; -} - -bool server_set_pretty_name(struct server *server, const char *pretty_name) -{ - assert_return(server && !empty_str(pretty_name), false); - - return set_property(server, &server->pretty_name, pretty_name); -} - -struct server *server_new(const char *name) +struct server *server_new(const char *filename) { struct server *server; + const char *suffix; - assert_return(!empty_str(name), NULL); + assert_return(!empty_str(filename), NULL); list_for_each_entry(server, &cfg->servers, list) { - if (!streq(name, server->name)) + if (!streq(filename, server->name)) continue; - error("attempt to add duplicate server: %s", name); + error("attempt to add duplicate server: %s", filename); return server; } - verbose("Adding server %s", name); + verbose("Adding server %s", filename); server = zmalloc(sizeof(*server)); if (!server) { error("malloc: %m"); return NULL; } + suffix = strrchr(filename, '.'); + if (!suffix || suffix == filename) { + error("invalid filename: %s", filename); + xfree(server); + return NULL; + } + + server->name = xstrndup(filename, suffix - filename); + if (!server->name) { + error("failed to create server name: %s", filename); + xfree(server); + return NULL; + } + + if (!scfg_init(&server->scfg, filename)) { + xfree(server->name); + xfree(server); + return NULL; + } + server->state = SERVER_STATE_INIT; - server->type = SERVER_TYPE_UNDEFINED; - server->name = xstrdup(name); - server->stop_method = SERVER_STOP_METHOD_UNDEFINED; - server->start_method = SERVER_START_METHOD_UNDEFINED; - server->idle_timeout = 0; uring_task_init(&server->task, server->name, uring_parent(), server_free); @@ -811,11 +668,8 @@ struct server *server_new(const char *name) rcon_init(server); - INIT_LIST_HEAD(&server->remotes); - INIT_LIST_HEAD(&server->locals); + INIT_LIST_HEAD(&server->listenings); INIT_LIST_HEAD(&server->proxys); - INIT_LIST_HEAD(&server->rcons); - INIT_LIST_HEAD(&server->dnslookups); list_add(&server->list, &cfg->servers); return server; diff --git a/minecproxy/server.h b/minecproxy/server.h index 937fa67..7201ba6 100644 --- a/minecproxy/server.h +++ b/minecproxy/server.h @@ -1,6 +1,8 @@ #ifndef fooserverhfoo #define fooserverhfoo +#include "config-parser.h" + /* clang-format off */ enum server_state { SERVER_STATE_INIT = 0, @@ -11,51 +13,17 @@ enum server_state { }; /* clang-format on */ -enum server_type { - SERVER_TYPE_UNDEFINED, - SERVER_TYPE_ANNOUNCE, - SERVER_TYPE_PROXY, -}; - -enum server_stop_method { - SERVER_STOP_METHOD_UNDEFINED, - SERVER_STOP_METHOD_RCON, - SERVER_STOP_METHOD_SYSTEMD, - SERVER_STOP_METHOD_EXEC, -}; - -enum server_start_method { - SERVER_START_METHOD_UNDEFINED, - SERVER_START_METHOD_SYSTEMD, - SERVER_START_METHOD_EXEC, -}; - struct server { - enum server_type type; char *name; - char *pretty_name; - uint16_t announce_port; - struct list_head locals; - struct list_head remotes; + struct server_config scfg; + struct list_head listenings; struct list_head proxys; - struct list_head rcons; - struct list_head dnslookups; enum server_state state; - enum server_stop_method stop_method; - enum server_start_method start_method; - /* For calling external start/stop executables */ - char *stop_exec; - char *start_exec; struct uring_task exec_task; - /* For systemd services */ - char *systemd_service; - char *systemd_obj; - /* For rcon connections */ - char *rcon_password; struct connection rcon_conn; struct uring_task rcon_task; struct uring_task_buf rcon_tbuf; @@ -68,7 +36,6 @@ struct server { struct uring_task idle_task; struct connection idle_conn; struct uring_task_buf idle_buf; - unsigned idle_timeout; unsigned idle_count; /* For reading config files */ @@ -82,7 +49,7 @@ void server_refdump(struct server *server); void server_delete(struct server *server); -void server_delete_by_name(const char *name); +void server_delete_by_filename(const char *filename); bool server_start(struct server *server); @@ -90,40 +57,14 @@ bool server_stop(struct server *server); void server_set_active_players(struct server *server, int count); +void server_async_dns_update(struct server_config *scfg, bool done); + bool server_idle_check(struct server *server); bool server_announce(struct server *server, int fd); bool server_commit(struct server *server); -bool server_add_remote(struct server *server, struct saddr *remote); - -bool server_add_local(struct server *server, struct saddr *saddr); - -bool server_add_rcon(struct server *server, struct saddr *rcon); - -bool server_set_rcon_password(struct server *server, const char *password); - -bool server_set_systemd_service(struct server *server, const char *service); - -bool server_set_stop_method(struct server *server, - enum server_stop_method stop_method); - -bool server_set_start_method(struct server *server, - enum server_start_method start_method); - -bool server_set_stop_exec(struct server *server, const char *cmd); - -bool server_set_start_exec(struct server *server, const char *cmd); - -bool server_set_idle_timeout(struct server *server, uint16_t timeout); - -bool server_set_port(struct server *server, uint16_t port); - -bool server_set_type(struct server *server, enum server_type type); - -bool server_set_pretty_name(struct server *server, const char *pretty_name); - struct server *server_new(const char *name); #endif diff --git a/minecproxy/systemd.c b/minecproxy/systemd.c index a973a55..96ff50f 100644 --- a/minecproxy/systemd.c +++ b/minecproxy/systemd.c @@ -5,59 +5,7 @@ #include "main.h" #include "server.h" #include "systemd.h" - -#define SYSTEMD_DBUS_SERVICE "org.freedesktop.systemd1" -#define SYSTEMD_DBUS_INTERFACE "org.freedesktop.systemd1.Unit" -#define SYSTEMD_DBUS_PATH_PREFIX "/org/freedesktop/systemd1/unit/" - -static inline char tohex(uint8_t val) -{ - static const char hex[] = "0123456789abcdef"; - - return hex[val & 0x0f]; -} - -/* - * Creates an escaped D-Bus object path for a given systemd service - * - * Escaping rules are documented here: - * https://dbus.freedesktop.org/doc/dbus-specification.html - * - * Essentially, everyting but a-z, A-Z, 0-9 is replaced by _xx where xx is - * the hexadecimal value of the character. - * - * Example: minecraft@world1.service -> minecraft_40world1_2eservice - */ -char *systemd_object_path(const char *service) -{ - char *r; - char *d; - const char *s; - - assert_return(service && !empty_str(service), NULL); - - r = zmalloc(STRLEN(SYSTEMD_DBUS_PATH_PREFIX) + strlen(service) * 3 + 1); - if (!r) - return NULL; - - memcpy(r, SYSTEMD_DBUS_PATH_PREFIX, STRLEN(SYSTEMD_DBUS_PATH_PREFIX)); - d = r + STRLEN(SYSTEMD_DBUS_PATH_PREFIX); - - for (s = service; *s; s++) { - if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || - (*s >= '0' && *s <= '9')) { - *(d++) = *s; - continue; - } - - *(d++) = '_'; - *(d++) = tohex(*s >> 4); - *(d++) = tohex(*s); - } - - *d = '\0'; - return r; -} +#include "config.h" void systemd_delete() { @@ -106,17 +54,17 @@ bool systemd_service_running(struct server *server) bool running = false; int r; - assert_return(server && bus && server->systemd_service && - server->systemd_obj, + assert_return(server && bus && server->scfg.systemd_service && + server->scfg.systemd_obj, false); r = sd_bus_get_property_string(bus, SYSTEMD_DBUS_SERVICE, - server->systemd_obj, + server->scfg.systemd_obj, SYSTEMD_DBUS_INTERFACE, "ActiveState", &error, &status); if (r < 0) { error("failed to get status for service %s (%s): %s", - server->systemd_service, server->systemd_obj, + server->scfg.systemd_service, server->scfg.systemd_obj, error.message); goto out; } @@ -124,10 +72,10 @@ bool systemd_service_running(struct server *server) if (streq(status, "active")) { running = true; debug(DBG_SYSD, "systemd service %s (%s) is active", - server->systemd_service, server->systemd_obj); + server->scfg.systemd_service, server->scfg.systemd_obj); } else debug(DBG_SYSD, "systemd service %s (%s) is not active", - server->systemd_service, server->systemd_obj); + server->scfg.systemd_service, server->scfg.systemd_obj); out: free(status); @@ -144,16 +92,16 @@ static bool systemd_service_action(struct server *server, const char *action) bool performed = false; int r; - assert_return(server && bus && server->systemd_service && - server->systemd_obj && action, + assert_return(server && bus && server->scfg.systemd_service && + server->scfg.systemd_obj && action, false); - r = sd_bus_call_method(bus, SYSTEMD_DBUS_SERVICE, server->systemd_obj, + r = sd_bus_call_method(bus, SYSTEMD_DBUS_SERVICE, server->scfg.systemd_obj, SYSTEMD_DBUS_INTERFACE, action, &error, &m, "s", "fail"); if (r < 0) { error("failed to perform action %s on systemd service %s: %s", - action, server->systemd_service, error.message); + action, server->scfg.systemd_service, error.message); goto out; } @@ -164,7 +112,7 @@ static bool systemd_service_action(struct server *server, const char *action) } verbose("action %s queued for service %s", action, - server->systemd_service); + server->scfg.systemd_service); performed = true; out: diff --git a/minecproxy/systemd.h b/minecproxy/systemd.h index d455044..fb9e3a4 100644 --- a/minecproxy/systemd.h +++ b/minecproxy/systemd.h @@ -1,8 +1,6 @@ #ifndef foosystemdhfoo #define foosystemdhfoo -char *systemd_object_path(const char *service); - void systemd_delete(); bool systemd_service_running(struct server *server); diff --git a/shared/config-parser.c b/shared/config-parser.c index 13b26e1..ee30156 100644 --- a/shared/config-parser.c +++ b/shared/config-parser.c @@ -11,8 +11,456 @@ #include "utils.h" #include "config-parser.h" +#include "server-config-options.h" #include "config.h" +static void handle_dns_reply(struct server_config *scfg, enum scfg_keys type, + struct saddr *saddr) +{ + switch (type) { + case SCFG_KEY_LOCAL: + list_add(&saddr->list, &scfg->locals); + break; + case SCFG_KEY_REMOTE: + list_add(&saddr->list, &scfg->remotes); + break; + case SCFG_KEY_RCON: + list_add(&saddr->list, &scfg->rcons); + break; + default: + error("invalid DNS reply type"); + break; + } +} + +static void scfg_dns_cb(struct dns_async *dns, enum scfg_keys type) +{ + struct server_config *scfg; + struct sockaddr_in *in4; + struct sockaddr_in6 *in6; + struct saddr *saddr; + struct addrinfo *results = NULL, *ai; + int r; + + assert_return(dns); + + scfg = dns->priv; + debug(DBG_DNS, "called, dns: %p, name: %s", dns, dns->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); + break; + + case AF_INET6: + in6 = (struct sockaddr_in6 *)ai->ai_addr; + saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port); + break; + + default: + error("getaddrinfo(%s:%s): unknown address family (%i)", + dns->name, dns->port, ai->ai_family); + xfree(saddr); + continue; + } + + handle_dns_reply(scfg, type, saddr); + } + +out: + freeaddrinfo(results); + list_del(&dns->list); + if (dns->notification_cb) + dns->notification_cb(scfg, true); + xfree(dns); +} + +static void scfg_dns_local_cb(struct dns_async *dns) +{ + scfg_dns_cb(dns, SCFG_KEY_LOCAL); +} + +static void scfg_dns_remote_cb(struct dns_async *dns) +{ + scfg_dns_cb(dns, SCFG_KEY_REMOTE); +} + +static void scfg_dns_rcon_cb(struct dns_async *dns) +{ + scfg_dns_cb(dns, SCFG_KEY_RCON); +} + +static bool handle_dns(struct server_config *scfg, enum scfg_keys type, + struct cfg_value *value, + notification_cb_t notification_cb) +{ + struct saddr *saddr, *tmp; + struct dns_async *dns; + + assert_return(scfg && type && value, false); + + switch (value->type) { + case CFG_VAL_TYPE_ADDRS: + debug(DBG_DNS, "%i: got immediate addrs", type); + + list_for_each_entry_safe(saddr, tmp, &value->saddrs, list) { + list_del(&saddr->list); + handle_dns_reply(scfg, type, saddr); + } + return true; + + case CFG_VAL_TYPE_ASYNC_ADDRS: + debug(DBG_DNS, "doing async lookup of DNS record: %p", + value->dns_async); + + dns = value->dns_async; + switch (type) { + case SCFG_KEY_LOCAL: + dns->cb = scfg_dns_local_cb; + break; + case SCFG_KEY_REMOTE: + dns->cb = scfg_dns_remote_cb; + break; + case SCFG_KEY_RCON: + dns->cb = scfg_dns_rcon_cb; + break; + default: + error("invalid DNS lookup type"); + return false; + } + + if (!!notification_cb) { + error("async DNS lookup received but not requested"); + xfree(dns); + return false; + } + + dns->priv = scfg; + dns->notification_cb = notification_cb; + list_add(&dns->list, &scfg->dnslookups); + dns->notification_cb(scfg, false); + return true; + + default: + return false; + } +} + +static inline char tohex(uint8_t val) +{ + static const char hex[] = "0123456789abcdef"; + + return hex[val & 0x0f]; +} + +/* + * Creates an escaped D-Bus object path for a given systemd service + * + * Escaping rules are documented here: + * https://dbus.freedesktop.org/doc/dbus-specification.html + * + * Essentially, everyting but a-z, A-Z, 0-9 is replaced by _xx where xx is + * the hexadecimal value of the character. + * + * Example: minecraft@world1.service -> minecraft_40world1_2eservice + */ +static char *systemd_object_path(const char *service) +{ + char *r; + char *d; + const char *s; + + assert_return(service && !empty_str(service), NULL); + + r = zmalloc(STRLEN(SYSTEMD_DBUS_PATH_PREFIX) + strlen(service) * 3 + 1); + if (!r) + return NULL; + + memcpy(r, SYSTEMD_DBUS_PATH_PREFIX, STRLEN(SYSTEMD_DBUS_PATH_PREFIX)); + d = r + STRLEN(SYSTEMD_DBUS_PATH_PREFIX); + + for (s = service; *s; s++) { + if ((*s >= 'a' && *s <= 'z') || (*s >= 'A' && *s <= 'Z') || + (*s >= '0' && *s <= '9')) { + *(d++) = *s; + continue; + } + + *(d++) = '_'; + *(d++) = tohex(*s >> 4); + *(d++) = tohex(*s); + } + + *d = '\0'; + return r; +} + +#define INVALID(fmt, ...) \ +do { \ + verbose("%s: " fmt, scfg->filename __VA_OPT__(,) __VA_ARGS__); \ + valid = false; \ +} while(0) + +bool scfg_parse(struct server_config *scfg, char *buf, + notification_cb_t notification_cb) +{ + char *pos; + bool valid = true; + + assert_return(scfg && buf, false); + + pos = buf; + + if (!config_parse_header(SERVER_CFG_HEADER, &pos)) { + INVALID("missing/invalid header"); + goto out; + } + + while (true) { + int key; + const char *keyname; + struct cfg_value value; + + if (!config_parse_line(scfg->filename, &pos, scfg_key_map, &key, + &keyname, &value, !!notification_cb)) + break; + + /* FIXME: Improve error reporting */ + if (key == SCFG_KEY_INVALID) { + INVALID("invalid line in config file"); + continue; + } + + debug(DBG_CFG, "%s: key %s", scfg->filename, keyname); + + switch (key) { + case SCFG_KEY_TYPE: + if (scfg->type != SERVER_TYPE_UNDEFINED) + INVALID("type defined multiple times"); + else if (streq(value.str, "proxy")) + scfg->type = SERVER_TYPE_PROXY; + else if (streq(value.str, "announce")) + scfg->type = SERVER_TYPE_ANNOUNCE; + else + INVALID("unknown type: %s", value.str); + break; + + case SCFG_KEY_NAME: + if (scfg->pretty_name) + INVALID("name defined multiple times"); + else if (empty_str(value.str)) + INVALID("name is an empty string"); + else if (!(scfg->pretty_name = xstrdup(value.str))) + INVALID("strdup: %m"); + break; + + case SCFG_KEY_PORT: + if (scfg->announce_port != 0) + INVALID("port defined multiple times"); + else + scfg->announce_port = value.uint16; + break; + + case SCFG_KEY_LOCAL: + if (!handle_dns(scfg, SCFG_KEY_LOCAL, &value, + notification_cb)) + INVALID("handle_dns failed"); + break; + + case SCFG_KEY_REMOTE: + if (!handle_dns(scfg, SCFG_KEY_REMOTE, &value, + notification_cb)) + INVALID("handle_dns failed"); + break; + + case SCFG_KEY_IDLE_TIMEOUT: + if (scfg->idle_timeout != 0) + INVALID("idle_timeout already set"); + else + scfg->idle_timeout = value.uint16; + break; + + case SCFG_KEY_STOP_METHOD: + if (scfg->stop_method != SERVER_STOP_METHOD_UNDEFINED) + INVALID("stop method defined multiple times"); + else if (streq(value.str, "exec")) + scfg->stop_method = SERVER_STOP_METHOD_EXEC; + else if (streq(value.str, "rcon")) + scfg->stop_method = SERVER_STOP_METHOD_RCON; + else if (streq(value.str, "systemd")) + scfg->stop_method = SERVER_STOP_METHOD_SYSTEMD; + else + INVALID("unknown stop method: %s", value.str); + break; + + case SCFG_KEY_START_METHOD: + if (scfg->start_method != SERVER_START_METHOD_UNDEFINED) + INVALID("start method defined multiple times"); + else if (streq(value.str, "exec")) + scfg->start_method = SERVER_START_METHOD_EXEC; + else if (streq(value.str, "systemd")) + scfg->start_method = SERVER_START_METHOD_SYSTEMD; + else + INVALID("unknown start method: %s", value.str); + break; + + case SCFG_KEY_STOP_EXEC: + if (scfg->stop_exec) + INVALID("stop exec defined multiple times"); + else if (!(scfg->stop_exec = xstrdup(value.str))) + INVALID("strdup: %m"); + break; + + case SCFG_KEY_START_EXEC: + if (scfg->start_exec) + INVALID("start exec defined multiple times"); + else if (!(scfg->start_exec = xstrdup(value.str))) + INVALID("strdup: %m"); + break; + + case SCFG_KEY_RCON: + if (!handle_dns(scfg, SCFG_KEY_RCON, &value, + notification_cb)) + INVALID("handle_dns failed"); + break; + + case SCFG_KEY_RCON_PASSWORD: + if (scfg->rcon_password) + INVALID("rcon_password defined multiple times"); + else if (!(scfg->rcon_password = xstrdup(value.str))) + INVALID("strdup: %m"); + break; + + case SCFG_KEY_SYSTEMD_SERVICE: + if (scfg->systemd_service) + INVALID("systemd_service defined multiple times"); + else { + const char *suffix; + char *tmp; + + suffix = strrchr(value.str, '.'); + if (!suffix || !streq(suffix, ".service")) { + tmp = zmalloc(strlen(value.str) + + STRLEN(".service") + 1); + if (tmp) + sprintf(tmp, "%s.service", + value.str); + } else + tmp = xstrdup(value.str); + + if (!tmp) { + INVALID("malloc/strdup: %m"); + break; + } + + scfg->systemd_service = tmp; + scfg->systemd_obj = + systemd_object_path(scfg->systemd_service); + if (!scfg->systemd_obj) + INVALID("object_path: %m"); + } + + break; + + case SCFG_KEY_INVALID: + default: + INVALID("invalid line"); + break; + } + } + +out: + return valid; +} + +void scfg_delete(struct server_config *scfg) +{ + struct saddr *saddr, *tmp; + struct dns_async *dns; + + xfree(scfg->filename); + xfree(scfg->pretty_name); + xfree(scfg->stop_exec); + xfree(scfg->start_exec); + /* FIXME: use free_password */ + xfree(scfg->rcon_password); + xfree(scfg->systemd_service); + xfree(scfg->systemd_obj); + + list_for_each_entry_safe(saddr, tmp, &scfg->remotes, list) { + list_del(&saddr->list); + xfree(saddr); + } + + list_for_each_entry_safe(saddr, tmp, &scfg->rcons, list) { + list_del(&saddr->list); + xfree(saddr); + } + + list_for_each_entry_safe(saddr, tmp, &scfg->locals, list) { + list_del(&saddr->list); + xfree(saddr); + } + + list_for_each_entry(dns, &scfg->dnslookups, list) + gai_cancel(&dns->gcb); +} + +bool scfg_init(struct server_config *scfg, const char *filename) +{ + assert_return(scfg, false); + + if (filename) { + scfg->filename = xstrdup(filename); + if (!scfg->filename) { + error("strdup: %m"); + return false; + } + } + scfg->type = SERVER_TYPE_UNDEFINED; + scfg->pretty_name = NULL; + scfg->announce_port = 0; + scfg->idle_timeout = 0; + scfg->stop_method = SERVER_STOP_METHOD_UNDEFINED; + scfg->start_method = SERVER_START_METHOD_UNDEFINED; + scfg->stop_exec = NULL; + scfg->start_exec = NULL; + scfg->rcon_password = NULL; + scfg->systemd_service = NULL; + scfg->systemd_obj = NULL; + INIT_LIST_HEAD(&scfg->remotes); + INIT_LIST_HEAD(&scfg->locals); + INIT_LIST_HEAD(&scfg->rcons); + INIT_LIST_HEAD(&scfg->dnslookups); + return true; +} + static void eat_whitespace_and_comments(char **pos) { assert_return(pos && *pos); @@ -126,6 +574,7 @@ static bool dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, results = dns->gcb.ar_result; + /* FIXME: parts can be shared with the async cb? */ for (ai = results; ai; ai = ai->ai_next) { saddr = zmalloc(sizeof(*saddr)); if (!saddr) { diff --git a/shared/config-parser.h b/shared/config-parser.h index 771ece1..2220221 100644 --- a/shared/config-parser.h +++ b/shared/config-parser.h @@ -7,6 +7,44 @@ #include <signal.h> #include <dirent.h> +enum server_type { + SERVER_TYPE_UNDEFINED, + SERVER_TYPE_ANNOUNCE, + SERVER_TYPE_PROXY, +}; + +enum server_stop_method { + SERVER_STOP_METHOD_UNDEFINED, + SERVER_STOP_METHOD_RCON, + SERVER_STOP_METHOD_SYSTEMD, + SERVER_STOP_METHOD_EXEC, +}; + +enum server_start_method { + SERVER_START_METHOD_UNDEFINED, + SERVER_START_METHOD_SYSTEMD, + SERVER_START_METHOD_EXEC, +}; + +struct server_config { + char *filename; + enum server_type type; + char *pretty_name; + uint16_t announce_port; + unsigned idle_timeout; + enum server_stop_method stop_method; + enum server_start_method start_method; + char *stop_exec; + char *start_exec; + char *rcon_password; + char *systemd_service; + char *systemd_obj; + struct list_head locals; + struct list_head remotes; + struct list_head rcons; + struct list_head dnslookups; +}; + enum cfg_value_type { CFG_VAL_TYPE_INVALID, CFG_VAL_TYPE_STRING, @@ -16,9 +54,7 @@ enum cfg_value_type { CFG_VAL_TYPE_BOOL, }; -struct dns_async; - -typedef void(dns_cb_t)(struct dns_async *); +typedef void (*notification_cb_t)(struct server_config *scfg, bool done); struct dns_async { char name[FQDN_STR_LEN + 1]; @@ -26,7 +62,8 @@ struct dns_async { struct addrinfo req; struct gaicb gcb; struct sigevent sev; - dns_cb_t *cb; + void (*cb)(struct dns_async *); + notification_cb_t notification_cb; void *priv; struct list_head list; }; @@ -48,6 +85,13 @@ struct cfg_value { }; }; +bool scfg_parse(struct server_config *scfg, char *buf, + notification_cb_t notification_cb); + +void scfg_delete(struct server_config *scfg); + +bool scfg_init(struct server_config *scfg, const char *filename); + bool strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async); bool config_parse_line(const char *filename, char **buf, |