From b02e89b8b82f0534fd1490a8780ac89d707181b6 Mon Sep 17 00:00:00 2001
From: David Härdeman <david@hardeman.nu>
Date: Tue, 23 Jun 2020 21:00:15 +0200
Subject: Move config-parser to shared lib

---
 minecproxy/config-parser.c | 490 ---------------------------------------------
 minecproxy/config-parser.h |  59 ------
 minecproxy/meson.build     |   1 -
 shared/config-parser.c     | 490 +++++++++++++++++++++++++++++++++++++++++++++
 shared/config-parser.h     |  59 ++++++
 shared/meson.build         |   1 +
 6 files changed, 550 insertions(+), 550 deletions(-)
 delete mode 100644 minecproxy/config-parser.c
 delete mode 100644 minecproxy/config-parser.h
 create mode 100644 shared/config-parser.c
 create mode 100644 shared/config-parser.h

diff --git a/minecproxy/config-parser.c b/minecproxy/config-parser.c
deleted file mode 100644
index 9c89cf2..0000000
--- a/minecproxy/config-parser.c
+++ /dev/null
@@ -1,490 +0,0 @@
-#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/minecproxy/config-parser.h b/minecproxy/config-parser.h
deleted file mode 100644
index 3a117a3..0000000
--- a/minecproxy/config-parser.h
+++ /dev/null
@@ -1,59 +0,0 @@
-#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/minecproxy/meson.build b/minecproxy/meson.build
index db6a31b..7023f10 100644
--- a/minecproxy/meson.build
+++ b/minecproxy/meson.build
@@ -7,7 +7,6 @@ minecproxy_sources = [
 	'server-config.c',
 	'server-rcon.c',
 	'announce.c',
-	'config-parser.c',
 	'idle.c',
 	'ptimer.c',
 	'igmp.c',
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',
 ]
 
-- 
cgit v1.2.3