summaryrefslogtreecommitdiff
path: root/shared
diff options
context:
space:
mode:
Diffstat (limited to 'shared')
-rw-r--r--shared/config-parser.c490
-rw-r--r--shared/config-parser.h59
-rw-r--r--shared/meson.build1
3 files changed, 550 insertions, 0 deletions
diff --git a/shared/config-parser.c b/shared/config-parser.c
new file mode 100644
index 0000000..9c89cf2
--- /dev/null
+++ b/shared/config-parser.c
@@ -0,0 +1,490 @@
+#define _GNU_SOURCE
+#include <stdio.h>
+#include <ctype.h>
+#include <string.h>
+#include <stdbool.h>
+#include <errno.h>
+#include <unistd.h>
+#include <arpa/inet.h>
+#include <inttypes.h>
+
+#include "utils.h"
+#include "config-parser.h"
+
+static void
+eat_whitespace_and_comments(char **pos)
+{
+ assert_return(pos && *pos);
+
+ while (true) {
+ while (isspace(**pos))
+ (*pos)++;
+
+ if (**pos != '#')
+ return;
+
+ while (**pos != '\r' && **pos != '\n' && **pos != '\0')
+ (*pos)++;
+ }
+}
+
+static char *
+get_line(char **pos)
+{
+ char *begin, *end;
+
+ assert_return(pos && *pos, NULL);
+
+ begin = *pos;
+ while (isspace(*begin))
+ begin++;
+
+ if (*begin == '\0')
+ return NULL;
+
+ end = begin;
+ while (*end != '\n' && *end != '\0')
+ end++;
+
+ if (*end == '\0')
+ *pos = end;
+ else
+ *pos = end + 1;
+
+ while (isspace(*end)) {
+ *end = '\0';
+ end--;
+ }
+
+ return begin;
+}
+
+static bool
+dnslookup(const char *name, uint16_t port, struct cfg_value *rvalue, bool async)
+{
+ struct sockaddr_in *in4;
+ struct sockaddr_in6 *in6;
+ struct dns_async tmp;
+ struct dns_async *dns;
+ int mode = async ? GAI_NOWAIT : GAI_WAIT;
+ struct addrinfo *results = NULL, *ai;
+ struct saddr *saddr = NULL;
+ bool rv = false;
+ int r;
+
+ assert_return(!empty_str(name) && strlen(name) < sizeof(dns->name) && port > 0 && rvalue, false);
+
+ if (async) {
+ rvalue->type = CFG_VAL_TYPE_ASYNC_ADDRS;
+ rvalue->dns_async = NULL;
+ dns = zmalloc(sizeof(*dns));
+ if (!dns) {
+ error("async DNS lookup of %s failed: %m", name);
+ goto out;
+ }
+ debug(DBG_DNS, "doing async DNS lookup of %s: %p", name, dns);
+ } else {
+ memset(&tmp, 0, sizeof(tmp));
+ dns = &tmp;
+ debug(DBG_DNS, "doing sync DNS lookup of %s", name);
+ }
+
+ sprintf(dns->name, "%s", name);
+ sprintf(dns->port, "%" PRIu16, port);
+
+ dns->req.ai_family = AF_UNSPEC;
+ dns->req.ai_socktype = SOCK_STREAM;
+ dns->req.ai_protocol = 0;
+ dns->req.ai_flags = AI_NUMERICSERV;
+
+ dns->sev.sigev_notify = SIGEV_SIGNAL;
+ dns->sev.sigev_signo = SIGUSR1;
+ dns->sev.sigev_value.sival_ptr = dns;
+
+ dns->gcb.ar_name = dns->name;
+ dns->gcb.ar_service = dns->port;
+ dns->gcb.ar_request = &dns->req;
+
+ struct gaicb *gcbs[] = { &dns->gcb };
+
+ r = getaddrinfo_a(mode, gcbs, ARRAY_SIZE(gcbs), &dns->sev);
+ if (r != 0) {
+ error("getaddrinfo(%s:%" PRIu16 "): %s", name, port, gai_strerror(r));
+ goto out;
+ }
+
+ if (async) {
+ rvalue->dns_async = dns;
+ rv = true;
+ goto out;
+ }
+
+ results = dns->gcb.ar_result;
+
+ for (ai = results; ai; ai = ai->ai_next) {
+ saddr = zmalloc(sizeof(*saddr));
+ if (!saddr) {
+ error("sync DNS lookup of %s failed: %m", name);
+ 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);
+ error("addrstr: %s", saddr->addrstr);
+ list_add(&saddr->list, &rvalue->saddrs);
+ break;
+
+ case AF_INET6:
+ in6 = (struct sockaddr_in6 *)ai->ai_addr;
+ saddr_set_ipv6(saddr, &in6->sin6_addr, in6->sin6_port);
+ error("addrstr: %s", saddr->addrstr);
+ list_add(&saddr->list, &rvalue->saddrs);
+ break;
+
+ default:
+ error("getaddrinfo(%s:%s): unknown address family (%i)",
+ dns->name, dns->port, ai->ai_family);
+ xfree(saddr);
+ break;
+ }
+ }
+
+ rv = true;
+
+out:
+ freeaddrinfo(results);
+ return rv;
+}
+
+static bool
+strtosockaddrs(const char *str, struct cfg_value *rvalue, bool async)
+{
+ struct saddr *saddr;
+ uint16_t port;
+ char *tmp;
+ struct list_head *list;
+ unsigned naddrs = 0;
+
+ assert_return(!empty_str(str) && rvalue, false);
+
+ rvalue->type = CFG_VAL_TYPE_ADDRS;
+ list = &rvalue->saddrs;
+ list_init(list);
+
+ if (*str == '[') {
+ /* IPv6, [a:b:c...h]:p or [*]:p */
+ debug(DBG_CFG, "attempting to parse IPv6 addr (%s)", str);
+
+ str++;
+ tmp = strchr(str, ']');
+ if (!tmp)
+ goto error;
+ *tmp = '\0';
+
+ saddr = zmalloc(sizeof(*saddr));
+ if (!saddr)
+ goto error;
+
+ /* early list_add to make sure saddr is free():d on error */
+ list_add(&saddr->list, list);
+
+ if (streq(str, "*"))
+ saddr->in6.sin6_addr = in6addr_any;
+ else if (inet_pton(AF_INET6, str, &saddr->in6.sin6_addr) <= 0)
+ goto error;
+
+ tmp++;
+ if (*tmp != ':')
+ goto error;
+
+ tmp++;
+ if (strtou16_strict(tmp, &port) < 0)
+ goto error;
+
+ saddr_set_ipv6(saddr, NULL, htons(port));
+ naddrs++;
+
+ } else if (*str == '*') {
+ /* IPv4, *:p */
+ debug(DBG_CFG, "attempting to parse IPv4 addr (%s)", str);
+
+ str++;
+ if (*str != ':')
+ goto error;
+
+ str++;
+ if (strtou16_strict(str, &port) < 0)
+ goto error;
+
+ saddr = zmalloc(sizeof(*saddr));
+ if (!saddr)
+ goto error;
+
+ saddr_set_ipv4(saddr, INADDR_ANY, htons(port));
+ list_add(&saddr->list, list);
+ naddrs++;
+
+ } else if ((tmp = strchr(str, ':'))) {
+ /* IPv4, a.b.c.d:p or IPv4/6 hostname:p */
+ debug(DBG_CFG, "attempting to parse IPv4 addr or hostname (%s)", str);
+
+ *tmp = '\0';
+ tmp++;
+ if (strtou16_strict(tmp, &port) < 0)
+ goto error;
+
+ saddr = zmalloc(sizeof(*saddr));
+ if (!saddr)
+ goto error;
+
+ if (inet_pton(AF_INET, str, &saddr->in4.sin_addr) > 0) {
+ debug(DBG_CFG, "got an IPv4:port (%s:%" PRIu16 ")", str, port);
+ saddr_set_ipv4(saddr, saddr->in4.sin_addr.s_addr, htons(port));
+ list_add(&saddr->list, list);
+ naddrs++;
+ goto success;
+ }
+
+ xfree(saddr);
+ debug(DBG_CFG, "maybe got a hostname:port (%s:%" PRIu16 ")", str, port);
+ if (!dnslookup(str, port, rvalue, async))
+ goto error;
+
+ } else if (strtou16_strict(tmp, &port) == 0) {
+ /* Port */
+ debug(DBG_CFG, "attempting to parse a port number (%s)", str);
+
+ saddr = zmalloc(sizeof(*saddr));
+ if (!saddr)
+ goto error;
+
+ saddr_set_ipv6(saddr, &in6addr_any, htons(port));
+ list_add(&saddr->list, list);
+ naddrs++;
+
+ saddr = zmalloc(sizeof(*saddr));
+ if (!saddr)
+ goto error;
+
+ saddr_set_ipv4(saddr, INADDR_ANY, htons(port));
+ list_add(&saddr->list, list);
+ naddrs++;
+
+ } else {
+ /* Unknown */
+ error("unable to parse address: %s", str);
+ goto error;
+ }
+
+success:
+ switch (rvalue->type) {
+ case CFG_VAL_TYPE_ADDRS:
+ if (list_empty(list) || naddrs == 0) {
+ error("empty address list");
+ return false;
+ }
+
+ debug(DBG_CFG, "parsed to %u addresses", naddrs);
+ return true;
+
+ case CFG_VAL_TYPE_ASYNC_ADDRS:
+ debug(DBG_CFG, "looking up address asynchronously");
+ return true;
+
+ default:
+ error("invalid rvalue type");
+ rvalue->type = CFG_VAL_TYPE_INVALID;
+ break;
+ }
+
+error:
+ if (rvalue->type == CFG_VAL_TYPE_ADDRS && !list_empty(list)) {
+ struct saddr *tmp;
+
+ list_for_each_entry_safe(saddr, tmp, list, list) {
+ list_del(&saddr->list);
+ xfree(saddr);
+ }
+ }
+ return false;
+}
+
+/* Returns true if there's data left to parse in buf */
+bool
+config_parse_line(const char *filename, char **buf,
+ struct cfg_key_value_map *kvmap, int *rkey,
+ const char **rkeyname, struct cfg_value *rvalue)
+{
+ char *line, *tmp, *key;
+ int i;
+
+ assert_return(buf && *buf && kvmap && rkey && rkeyname && rvalue, false);
+
+ eat_whitespace_and_comments(buf);
+ line = get_line(buf);
+ if (!line)
+ return false;
+
+ debug(DBG_CFG, "%s: parsing config line: %s", filename, line);
+
+ tmp = line;
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ goto error;
+
+ key = tmp;
+ while (*tmp != '\0' && !isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ goto error;
+
+ *tmp = '\0';
+ tmp++;
+
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp != '=')
+ goto error;
+
+ tmp++;
+ while (isspace(*tmp))
+ tmp++;
+
+ if (*tmp == '\0')
+ goto error;
+
+ for (i = 0; kvmap[i].key_name; i++) {
+ if (!streq(kvmap[i].key_name, key))
+ continue;
+
+ switch (kvmap[i].value_type) {
+
+ case CFG_VAL_TYPE_STRING:
+ rvalue->type = CFG_VAL_TYPE_STRING;
+ rvalue->str = tmp;
+ break;
+
+ case CFG_VAL_TYPE_UINT16: {
+ uint16_t v;
+
+ if (strtou16_strict(tmp, &v) < 0)
+ goto error;
+
+ rvalue->type = CFG_VAL_TYPE_UINT16;
+ rvalue->uint16 = v;
+ break;
+ }
+
+ case CFG_VAL_TYPE_ADDRS:
+ if (!strtosockaddrs(tmp, rvalue, false))
+ goto error;
+
+ if (rvalue->type != CFG_VAL_TYPE_ADDRS) {
+ error("invalid type returned from strtosockaddrs");
+ goto error;
+ }
+
+ if (list_empty(&rvalue->saddrs)) {
+ error("empty address list");
+ goto error;
+ }
+ break;
+
+ case CFG_VAL_TYPE_ASYNC_ADDRS:
+ if (!strtosockaddrs(tmp, rvalue, true))
+ goto error;
+
+ switch (rvalue->type) {
+ case CFG_VAL_TYPE_ADDRS:
+ if (list_empty(&rvalue->saddrs)) {
+ error("empty address list");
+ goto error;
+ }
+ break;
+
+ case CFG_VAL_TYPE_ASYNC_ADDRS:
+ if (!rvalue->dns_async) {
+ error("dns_async not set");
+ goto error;
+ }
+ break;
+
+ default:
+ error("invalid type returned from strtosockaddrs");
+ goto error;
+ }
+
+ break;
+
+ case CFG_VAL_TYPE_BOOL:
+ if (strcaseeq(tmp, "yes") || strcaseeq(tmp, "true")) {
+ rvalue->type = CFG_VAL_TYPE_BOOL;
+ rvalue->boolean = true;
+ } else if (strcaseeq(tmp, "no") || strcaseeq(tmp, "false")) {
+ rvalue->type = CFG_VAL_TYPE_BOOL;
+ rvalue->boolean = false;
+ } else {
+ error("invalid boolean value (%s)", tmp);
+ goto error;
+ }
+ break;
+
+ case CFG_VAL_TYPE_INVALID:
+ /* fall through */
+ default:
+ goto error;
+ }
+
+ /* sanity check */
+ if ((rvalue->type != kvmap[i].value_type) &&
+ ((kvmap[i].value_type != CFG_VAL_TYPE_ASYNC_ADDRS) &&
+ (rvalue->type != CFG_VAL_TYPE_ADDRS))) {
+ error("rvalue->type != kvmap->type");
+ goto error;
+ }
+
+ *rkey = kvmap[i].key_value;
+ *rkeyname = kvmap[i].key_name;
+ return true;
+ }
+
+error:
+ /* FIXME: the line is already mangled here, a line number would be nice */
+ error("%s: invalid config line: %s", filename, line);
+ rvalue->type = CFG_VAL_TYPE_INVALID;
+ *rkey = 0;
+ *rkeyname = NULL;
+ return true;
+}
+
+bool
+config_parse_header(const char *filename, const char *title, char **buf)
+{
+ char *line;
+
+ assert_return(!empty_str(filename) && !empty_str(title) && buf && *buf, false);
+
+ eat_whitespace_and_comments(buf);
+
+ line = get_line(buf);
+ if (!line) {
+ error("%s: missing header in configuration file", filename);
+ return false;
+ } else {
+ char titlehdr[strlen(title) + 3];
+
+ sprintf(titlehdr, "[%s]", title);
+ if (!streq(line, titlehdr)) {
+ error("%s: incorrect header in configuration file", filename);
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/shared/config-parser.h b/shared/config-parser.h
new file mode 100644
index 0000000..3a117a3
--- /dev/null
+++ b/shared/config-parser.h
@@ -0,0 +1,59 @@
+#ifndef fooconfigparserhfoo
+#define fooconfigparserhfoo
+
+#define _GNU_SOURCE
+#include <sys/types.h>
+#include <sys/socket.h>
+#include <netdb.h>
+#include <signal.h>
+
+enum cfg_value_type {
+ CFG_VAL_TYPE_INVALID,
+ CFG_VAL_TYPE_STRING,
+ CFG_VAL_TYPE_UINT16,
+ CFG_VAL_TYPE_ADDRS,
+ CFG_VAL_TYPE_ASYNC_ADDRS,
+ CFG_VAL_TYPE_BOOL,
+};
+
+struct dns_async;
+
+typedef void (dns_cb_t)(struct dns_async *);
+
+struct dns_async {
+ char name[FQDN_STR_LEN + 1];
+ char port[PORT_STR_LEN + 1];
+ struct addrinfo req;
+ struct gaicb gcb;
+ struct sigevent sev;
+ dns_cb_t *cb;
+ void *priv;
+ struct list_head list;
+};
+
+struct cfg_key_value_map {
+ const char *key_name;
+ int key_value;
+ enum cfg_value_type value_type;
+};
+
+struct cfg_value {
+ enum cfg_value_type type;
+ union {
+ const char *str;
+ uint16_t uint16;
+ struct list_head saddrs;
+ struct dns_async *dns_async;
+ bool boolean;
+ };
+};
+
+bool config_parse_line(const char *filename, char **buf,
+ struct cfg_key_value_map *kvmap,
+ int *rkey, const char **rkeyname,
+ struct cfg_value *rvalue);
+
+bool config_parse_header(const char *filename,
+ const char *title, char **buf);
+
+#endif
diff --git a/shared/meson.build b/shared/meson.build
index ccf502e..e4e4f29 100644
--- a/shared/meson.build
+++ b/shared/meson.build
@@ -1,5 +1,6 @@
srcs_libshared = [
'rcon-protocol.c',
+ 'config-parser.c',
'utils.c',
]