summaryrefslogtreecommitdiff
path: root/shared
diff options
context:
space:
mode:
authorDavid Härdeman <david@hardeman.nu>2020-06-30 08:10:04 +0200
committerDavid Härdeman <david@hardeman.nu>2020-06-30 08:10:04 +0200
commita89a0f918925a662503c1bcb28bdb06ab9b7ef25 (patch)
tree733b4c1ff841f99eeb3840c5948fef6c5bf76109 /shared
parent8f29a4d23dd13a80aa26951b17e2a71f4e651cb1 (diff)
Share config parsing fully between server and cmdline tool
Diffstat (limited to 'shared')
-rw-r--r--shared/config-parser.c449
-rw-r--r--shared/config-parser.h52
2 files changed, 497 insertions, 4 deletions
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,