[BACK]Return to config.c CVS log [TXT][DIR] Up to [local] / acopm / src

File: [local] / acopm / src / config.c (download)

Revision 1.1, Sat May 8 15:42:17 2021 UTC (3 years ago) by bountyht
Branch point for: MAIN

Initial revision

/*
 * Copyright (C) 2017 Aaron M. D. Jones <aaronmdjones@gmail.com>
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following
 * conditions are met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * 3. Neither the name of the copyright holder nor the names of its
 *    contributors may be used to endorse or promote products derived from
 *    this software without specific prior written permission.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED.
 *
 * IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY
 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
 * DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS
 * OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION)
 * HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT,
 * STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
 * ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
 * POSSIBILITY OF SUCH DAMAGE.
 */

#include "acopm-common.h"

#include <assert.h> /* assert() */
#include <errno.h> /* errno */
#include <netdb.h> /* getaddrinfo() */
#include <string.h> /* memset(), strerror() */
#include <strings.h> /* strcasecmp() */
#include <time.h> /* time_t */

#include <arpa/inet.h> /* inet_ntop() */
#include <netinet/in.h> /* struct sockaddr */

#include <libconfig.h> /* config_*(), config_t, config_setting_t, LIBCONFIG_* */

#include "check.h" /* acopm_check_add_exemption() */
#include "config.h" /* decls for own functions */
#include "daemon.h" /* acopm_daemon_set() */
#include "vars.h" /* global variable definitions */

#include "irc/client.h" /* acopm_irc_*() */
#include "dnsbl/client.h" /* acopm_dnsbl_*() */
#include "dnsbl/domain.h" /* acopm_dnsbl_*() */
#include "dnsbl/match.h" /* acopm_dnsbl_*() */
#include "proxy/port.h" /* acopm_proxy_*() */
#include "utils/bufconn.h" /* acopm_conn_*() */
#include "utils/log.h" /* acopm_log_*() */

#if defined(LIBCONFIG_VER_MAJOR) && defined(LIBCONFIG_VER_MINOR) && defined(LIBCONFIG_VER_REVISION)
#  if (LIBCONFIG_VER_MAJOR <= 1)
#    if (LIBCONFIG_VER_MINOR == 4)
#      if (LIBCONFIG_VER_REVISION >= 9)
#        define LIBCONFIG_SUPPORTS_SET_INCLUDE_DIR /* v1.4.9 */
#      endif
#    else
#      if (LIBCONFIG_VER_MINOR >= 5)
#        define LIBCONFIG_SUPPORTS_SET_INCLUDE_DIR /* v1.5.* */
#      endif
#    endif
#  else
#    define LIBCONFIG_SUPPORTS_SET_INCLUDE_DIR /* v2.*.* */
#  endif
#endif

typedef void (*acopm_config_irc_str_setfn)(struct acopm_irc_client *, const char *);

static inline bool acopm_attr_inline
acopm_config_parse_irc_strfn(config_setting_t *const restrict server, const char *const restrict name,
                             const acopm_config_irc_str_setfn irc_str_set_func, const bool optional)
{
	const char *value = NULL;
	if (! config_setting_lookup_string(server, name, &value) || ! value || ! *value)
	{
		if (! optional)
			(void) acopm_log_error("Required option server::%s missing from configuration", name);

		return optional;
	}

	(void) irc_str_set_func(acopm_irc_client, value);
	return true;
}

static inline bool acopm_attr_inline
acopm_config_parse_strbuf(config_setting_t *const restrict item, const char *const restrict name,
                          char *const restrict buf, const size_t buf_length, const bool optional)
{
	char prefix[256];
	(void) acopm_strset_buf(prefix, config_setting_name(item));

	const char *value = NULL;
	if (! config_setting_lookup_string(item, name, &value) || ! value || ! *value)
	{
		if (! optional)
			(void) acopm_log_error("Required option %s::%s missing from configuration", prefix, name);

		return optional;
	}

	(void) memset(buf, 0x00, buf_length);
	(void) acopm_strset_len(buf, buf_length, value);
	return true;
}

static struct sockaddr_storage *
acopm_config_resolve_bind_addr(const char *const restrict addr, const sa_family_t family)
{
	assert(addr != NULL);
	assert(family == AF_INET || family == AF_INET6);

	struct addrinfo hints;
	(void) memset(&hints, 0x00, sizeof hints);

	hints.ai_flags = AI_NUMERICSERV | AI_PASSIVE;
	hints.ai_family = family;
	hints.ai_socktype = SOCK_STREAM;
	hints.ai_protocol = IPPROTO_TCP;

	struct addrinfo *gai_addr = NULL;
	const int gai_errno = getaddrinfo(addr, "0", &hints, &gai_addr);

	if (gai_errno != 0)
	{
		(void) acopm_log_error("%s: getaddrinfo: %s", __func__, gai_strerror(gai_errno));
		return NULL;
	}
	if (! gai_addr)
	{
		(void) acopm_log_error("%s: getaddrinfo: no addresses", __func__);
		return NULL;
	}

	struct sockaddr_storage *const copy = calloc(1, sizeof *copy);
	if (! copy)
	{
		(void) acopm_log_error("%s: calloc: %s", __func__, strerror(errno));
		return NULL;
	}

	(void) memcpy(copy, gai_addr->ai_addr, (size_t) gai_addr->ai_addrlen);
	(void) freeaddrinfo(gai_addr);
	return copy;
}



static inline bool acopm_attr_inline
acopm_config_parse_options_daemon(config_setting_t *const restrict options)
{
	int value = 0;
	if (config_setting_lookup_bool(options, "daemonise", &value) && value)
		(void) acopm_daemon_set();

	return true;
}

static inline bool acopm_attr_inline
acopm_config_parse_options_conn_regexp(config_setting_t *const restrict options)
{
	int value = 0;
	if (config_setting_lookup_bool(options, "conn_regexp", &value) && value)
		(void) acopm_irc_set_conn_is_regexp(acopm_irc_client, true);
	else
		(void) acopm_irc_set_conn_is_regexp(acopm_irc_client, false);

	return true;
}

static inline bool acopm_attr_inline
acopm_config_parse_options_logfile(config_setting_t *const restrict options)
{
	if (acopm_log_file_is_set())
		return true;

	const char *value = NULL;
	if (! config_setting_lookup_string(options, "logfile", &value) || ! value || ! *value)
		return true;

	return acopm_log_set_file(value);
}

static inline bool acopm_attr_inline
acopm_config_parse_options_logmask(config_setting_t *const restrict options)
{
	if (acopm_log_mask_is_set())
		return true;

	long long int value;
	if (! config_setting_lookup_int64(options, "logmask", &value))
		return true;

	if (value < ACOPM_LOGLVL_RANGE_MIN || value > ACOPM_LOGLVL_RANGE_MAX)
	{
		(void) acopm_log_warning("Option logmask has invalid value %lld", value);
		return true;
	}

	(void) acopm_log_set_mask((unsigned int) value);
	(void) acopm_log_info("Logmask set to %lld", value);
	return true;
}

static inline bool acopm_attr_inline
acopm_config_parse_options(config_t *const restrict config)
{
	config_setting_t *options = NULL;

	if (! (options = config_lookup(config, "options")))
		return true;

	if (! acopm_config_parse_options_daemon(options))
		return false;

	if (! acopm_config_parse_options_conn_regexp(options))
		return false;

	if (! acopm_config_parse_options_logfile(options))
		return false;

	if (! acopm_config_parse_options_logmask(options))
		return false;

	return true;
}



static inline bool acopm_attr_inline
acopm_config_parse_server_host(config_setting_t *const restrict server)
{
	const char *hostname = NULL;
	bool hostname_configured = false;
	config_setting_t *hostnames = NULL;

	if (! (hostnames = config_setting_get_member(server, "hostnames")))
	{
		(void) acopm_log_error("Required list server::hostnames missing or invalid");
		return false;
	}
	for (int idx = 0; (hostname = config_setting_get_string_elem(hostnames, idx)) != NULL; idx++)
	{
		if (! *hostname)
			(void) acopm_log_warning("%s: invalid hostname -- ignoring", __func__);
		else if (! acopm_irc_add_host(acopm_irc_client, hostname))
			(void) acopm_log_error("%s: adding hostname %s failed -- ignoring", __func__, hostname);
		else
			hostname_configured = true;
	}

	if (! hostname_configured)
		(void) acopm_log_error("%s: no hostnames configured", __func__);

	return hostname_configured;
}

static inline bool acopm_attr_inline
acopm_config_parse_server_port(config_setting_t *const restrict server)
{
	long long int port;

	if (! config_setting_lookup_int64(server, "port", &port))
	{
		(void) acopm_log_error("Required option server::port missing from configuration file");
		return false;
	}
	if (port <= 0 || port > UINT16_MAX)
	{
		(void) acopm_log_error("Required option server::port has invalid value %lld", port);
		return false;
	}

	(void) acopm_irc_set_port(acopm_irc_client, (uint16_t) port);
	return true;
}

static bool
acopm_config_parse_server_bind4(config_setting_t *const restrict server)
{
	const char *value = NULL;
	if (! config_setting_lookup_string(server, "bind4_addr", &value) || ! value || ! *value)
		return true;

	struct sockaddr_storage *const addr = acopm_config_resolve_bind_addr(value, AF_INET);
	if (! addr)
		return false;

	const bool result = acopm_irc_set_bind4(acopm_irc_client, (struct sockaddr_in *) addr);
	(void) free(addr);
	return result;
}

static bool
acopm_config_parse_server_bind6(config_setting_t *const restrict server)
{
	const char *value = NULL;
	if (! config_setting_lookup_string(server, "bind6_addr", &value) || ! value || ! *value)
		return true;

	struct sockaddr_storage *const addr = acopm_config_resolve_bind_addr(value, AF_INET6);
	if (! addr)
		return false;

	const bool result = acopm_irc_set_bind6(acopm_irc_client, (struct sockaddr_in6 *) addr);
	(void) free(addr);
	return result;
}

static inline bool acopm_attr_inline
acopm_config_parse_server_acct(config_setting_t *const restrict server)
{
	const char *value = NULL;
	if (! config_setting_lookup_string(server, "acctuser", &value) || ! value || ! *value)
		return true;

	char *const user = strdup(value);

	if (! config_setting_lookup_string(server, "acctpass", &value) || ! value || ! *value)
	{
		(void) acopm_irc_set_acct(acopm_irc_client, user, NULL);
		(void) free(user);
		return true;
	}

	(void) acopm_irc_set_acct(acopm_irc_client, user, value);
	(void) free(user);
	return true;
}

static inline bool acopm_attr_inline
acopm_config_parse_server_oper(config_setting_t *const restrict server)
{
	const char *value = NULL;
	if (! config_setting_lookup_string(server, "operuser", &value) || ! value || ! *value)
	{
		(void) acopm_log_error("Required option server::operuser missing from configuration file");
		return false;
	}

	char *const user = strdup(value);

	if (! config_setting_lookup_string(server, "operpass", &value) || ! value || ! *value)
	{
		(void) acopm_log_error("Required option server::operpass missing from configuration file");
		(void) free(user);
		return false;
	}

	(void) acopm_irc_set_oper(acopm_irc_client, user, value);
	(void) free(user);
	return true;
}

static inline bool acopm_attr_inline
acopm_config_parse_server_chan(config_setting_t *const restrict server)
{
	char *logchan_name = NULL;
	char *logchan_key = NULL;
	long long int logchan_delay = 0;

	const char *value;
	if (config_setting_lookup_string(server, "logchan_name", &value) && value && *value)
		logchan_name = strdup(value);
	if (config_setting_lookup_string(server, "logchan_key", &value) && value && *value)
		logchan_key = strdup(value);

	if (config_setting_lookup_int64(server, "logchan_delay", &logchan_delay))
	{
		if (logchan_delay < 0)
			logchan_delay = 0;
		if (logchan_delay > 10)
			logchan_delay = 10;
	}

	(void) acopm_irc_set_chan(acopm_irc_client, logchan_name, logchan_key, (unsigned int) logchan_delay);
	(void) free(logchan_name);
	(void) free(logchan_key);
	return true;
}

static inline bool acopm_attr_inline
acopm_config_parse_server_conn(config_setting_t *const restrict server)
{
	const char *value = NULL;
	if (! config_setting_lookup_string(server, "conn_fmt", &value) || ! value || ! *value)
	{
		(void) acopm_log_error("Required option server::conn_fmt missing from configuration file");
		return false;
	}

	return acopm_irc_set_conn(acopm_irc_client, value);
}

#ifdef BUILD_WITH_MBEDTLS

static inline enum acopm_conn_certfp_method acopm_attr_inline
acopm_config_certfp_enum(const char *const restrict certfp_method)
{
	if (strcasecmp(certfp_method, "CERT-SHA1-HEX") == 0)
		return ACOPM_CONN_CERTFP_METHOD_CERT_SHA1_HEX;
	else if (strcasecmp(certfp_method, "SPKI-SHA1-HEX") == 0)
		return ACOPM_CONN_CERTFP_METHOD_SPKI_SHA1_HEX;
	else if (strcasecmp(certfp_method, "CERT-SHA1-B64") == 0)
		return ACOPM_CONN_CERTFP_METHOD_CERT_SHA1_B64;
	else if (strcasecmp(certfp_method, "SPKI-SHA1-B64") == 0)
		return ACOPM_CONN_CERTFP_METHOD_SPKI_SHA1_B64;
	else if (strcasecmp(certfp_method, "CERT-SHA256-HEX") == 0)
		return ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_256_HEX;
	else if (strcasecmp(certfp_method, "SPKI-SHA256-HEX") == 0)
		return ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_256_HEX;
	else if (strcasecmp(certfp_method, "CERT-SHA256-B64") == 0)
		return ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_256_B64;
	else if (strcasecmp(certfp_method, "SPKI-SHA256-B64") == 0)
		return ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_256_B64;
	else if (strcasecmp(certfp_method, "CERT-SHA512-HEX") == 0)
		return ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_512_HEX;
	else if (strcasecmp(certfp_method, "SPKI-SHA512-HEX") == 0)
		return ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_512_HEX;
	else if (strcasecmp(certfp_method, "CERT-SHA512-B64") == 0)
		return ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_512_B64;
	else if (strcasecmp(certfp_method, "SPKI-SHA512-B64") == 0)
		return ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_512_B64;

	return ACOPM_CONN_CERTFP_METHOD_NONE;
}

static inline bool acopm_attr_inline
acopm_config_certfp_valid(const enum acopm_conn_certfp_method method, const char *const restrict fingerprint)
{
	if (! fingerprint || ! *fingerprint)
		return false;

	const size_t fingerprint_length = strlen(fingerprint);

	switch (method)
	{
		case ACOPM_CONN_CERTFP_METHOD_NONE:
			break;
		case ACOPM_CONN_CERTFP_METHOD_CERT_SHA1_HEX:
		case ACOPM_CONN_CERTFP_METHOD_SPKI_SHA1_HEX:
			return (fingerprint_length == ACOPM_CONN_CERTFP_LENGTH_SHA1_HEX) ? true : false;
		case ACOPM_CONN_CERTFP_METHOD_CERT_SHA1_B64:
		case ACOPM_CONN_CERTFP_METHOD_SPKI_SHA1_B64:
			return (fingerprint_length == ACOPM_CONN_CERTFP_LENGTH_SHA1_B64) ? true : false;
		case ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_256_HEX:
		case ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_256_HEX:
			return (fingerprint_length == ACOPM_CONN_CERTFP_LENGTH_SHA2_256_HEX) ? true : false;
		case ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_256_B64:
		case ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_256_B64:
			return (fingerprint_length == ACOPM_CONN_CERTFP_LENGTH_SHA2_256_B64) ? true : false;
		case ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_512_HEX:
		case ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_512_HEX:
			return (fingerprint_length == ACOPM_CONN_CERTFP_LENGTH_SHA2_512_HEX) ? true : false;
		case ACOPM_CONN_CERTFP_METHOD_CERT_SHA2_512_B64:
		case ACOPM_CONN_CERTFP_METHOD_SPKI_SHA2_512_B64:
			return (fingerprint_length == ACOPM_CONN_CERTFP_LENGTH_SHA2_512_B64) ? true : false;
	}

	abort();
}

static bool
acopm_config_parse_server_tls(config_setting_t *const restrict server)
{
	enum acopm_conn_certfp_method certfp_enum = ACOPM_CONN_CERTFP_METHOD_NONE;
	bool result = false;

	int use_tls;
	if (! config_setting_lookup_bool(server, "use_tls", &use_tls) || ! use_tls)
	{
		(void) acopm_irc_set_tls(acopm_irc_client, false, certfp_enum);
		return true;
	}

	config_setting_t *certfp_values = NULL;

	const char *value = NULL;
	char *cc_file = NULL;
	char *ck_file = NULL;

	if (config_setting_lookup_string(server, "cc_file", &value) && value && ! (cc_file = strdup(value)))
		(void) acopm_log_warning("%s: strdup: %s (ignoring cc_file)", __func__, strerror(errno));
	if (config_setting_lookup_string(server, "ck_file", &value) && value && ! (ck_file = strdup(value)))
		(void) acopm_log_warning("%s: strdup: %s (ignoring ck_file)", __func__, strerror(errno));

	if (config_setting_lookup_string(server, "certfp_method", &value) && value)
	{
		if ((certfp_enum = acopm_config_certfp_enum(value)) == ACOPM_CONN_CERTFP_METHOD_NONE)
		{
			(void) acopm_log_error("Required option server::certfp_method has invalid value %s", value);
			goto end;
		}
		else
			(void) acopm_irc_set_tls(acopm_irc_client, true, certfp_enum);
	}
	else
	{
		(void) acopm_log_error("Required option server::certfp_method is missing");
		goto end;
	}

	if (! (certfp_values = config_setting_get_member(server, "certfp_values")))
	{
		(void) acopm_log_error("Required list server::certfp_values missing or invalid");
		goto end;
	}

	if (cc_file && *cc_file && ck_file && *ck_file)
		(void) acopm_irc_set_tls_cert(acopm_irc_client, cc_file, ck_file);

	for (int idx = 0; (value = config_setting_get_string_elem(certfp_values, idx)) != NULL; idx++)
	{
		if (! acopm_config_certfp_valid(certfp_enum, value))
			(void) acopm_log_warning("%s: invalid fingerprint %s for configured method -- ignoring", __func__, value);
		else if (acopm_irc_add_tls_certfp(acopm_irc_client, value))
			result = true;
	}
	if (! result)
		(void) acopm_log_error("%s: no valid fingerprints given", __func__);

end:
	(void) free(cc_file);
	(void) free(ck_file);
	return result;
}

#endif /* BUILD_WITH_MBEDTLS */

static bool
acopm_config_parse_server(config_setting_t *const restrict server)
{
	assert(server != NULL);

	if (! acopm_config_parse_server_host(server))
		return false;
	if (! acopm_config_parse_server_port(server))
		return false;
	if (! acopm_config_parse_server_bind4(server))
		return false;
	if (! acopm_config_parse_server_bind6(server))
		return false;
	if (! acopm_config_parse_server_acct(server))
		return false;
	if (! acopm_config_parse_server_oper(server))
		return false;
	if (! acopm_config_parse_server_chan(server))
		return false;
	if (! acopm_config_parse_server_conn(server))
		return false;

	if (! acopm_config_parse_irc_strfn(server, "nickname", acopm_irc_set_nick, false))
		return false;
	if (! acopm_config_parse_irc_strfn(server, "realname", acopm_irc_set_real, true))
		return false;
	if (! acopm_config_parse_irc_strfn(server, "username", acopm_irc_set_user, false))
		return false;
	if (! acopm_config_parse_irc_strfn(server, "password", acopm_irc_set_pass, true))
		return false;
	if (! acopm_config_parse_irc_strfn(server, "command", acopm_irc_set_pcmd, true))
		return false;
	if (! acopm_config_parse_irc_strfn(server, "away", acopm_irc_set_away, true))
		return false;
	if (! acopm_config_parse_irc_strfn(server, "mode", acopm_irc_set_mode, true))
		return false;
	if (! acopm_config_parse_strbuf(server, "client_notice", client_notice_msg, sizeof client_notice_msg, true))
		return false;
	if (! acopm_config_parse_strbuf(server, "dnsbl_hit_cmd", dnsbl_hit_cmd, sizeof dnsbl_hit_cmd, true))
		return false;
	if (! acopm_config_parse_strbuf(server, "proxy_hit_cmd", proxy_hit_cmd, sizeof proxy_hit_cmd, true))
		return false;

#ifdef BUILD_WITH_MBEDTLS
	if (! acopm_config_parse_server_tls(server))
		return false;
#endif

	return true;
}



static bool
acopm_config_parse_blacklist_nameservers(config_setting_t *const restrict nameservers)
{
	const char *nameserver;
	for (int x = 0; (nameserver = config_setting_get_string_elem(nameservers, x)) != NULL; x++)
		(void) acopm_dnsbl_add_nameserver(acopm_dnsbl_client, nameserver);

	return true;
}

static bool
acopm_config_parse_blacklist_domain_match(config_setting_t *const restrict match,
                                          struct acopm_dnsbl_domain *const restrict domain)
{
	struct acopm_dnsbl_match *const newmatch = acopm_dnsbl_match_new(domain);
	if (! newmatch)
		return false;

	const char *addr = NULL;
	uint32_t addr_bin;
	if (! config_setting_lookup_string(match, "addr", &addr) || ! addr || ! *addr)
	{
		(void) acopm_log_warning("Ignoring match for DNSBL (no address) at line %u",
		                         config_setting_source_line(match));

		(void) acopm_dnsbl_match_free(newmatch);
		return false;
	}
	if (inet_pton(AF_INET, addr, &addr_bin) != 1)
	{
		(void) acopm_log_warning("Ignoring match for DNSBL (inet_pton: %s) at line %u",
		                         strerror(errno), config_setting_source_line(match));

		(void) acopm_dnsbl_match_free(newmatch);
		return false;
	}

	(void) acopm_dnsbl_match_set_addr(newmatch, addr_bin);

	const char *desc;
	if (config_setting_lookup_string(match, "desc", &desc))
		(void) acopm_dnsbl_match_set_desc(newmatch, desc);

	return acopm_dnsbl_match_add(newmatch);
}

static inline bool acopm_attr_inline
acopm_config_parse_blacklist_domain(config_setting_t *const restrict domain)
{
	struct acopm_dnsbl_domain *const newdomain = acopm_dnsbl_domain_new(acopm_dnsbl_client);
	if (! newdomain)
		return false;

	long long int family;
	if (! config_setting_lookup_int64(domain, "family", &family))
	{
		(void) acopm_log_warning("Ignoring DNSBL (no family) at line %u",
		                         config_setting_source_line(domain));

		(void) acopm_dnsbl_domain_free(newdomain);
		return false;
	}
	else if (family == 4)
		(void) acopm_dnsbl_domain_set_type(newdomain, ACOPM_DNSBL_AFTYPE_IPV4);
	else if (family == 6)
		(void) acopm_dnsbl_domain_set_type(newdomain, ACOPM_DNSBL_AFTYPE_IPV6);
	else if (family == 10)
		(void) acopm_dnsbl_domain_set_type(newdomain, ACOPM_DNSBL_AFTYPE_BOTH);
	else
	{
		(void) acopm_log_warning("Ignoring DNSBL (invalid family %lld) at line %u",
		                         family, config_setting_source_line(domain));

		(void) acopm_dnsbl_domain_free(newdomain);
		return false;
	}

	const char *host = NULL;
	if (! config_setting_lookup_string(domain, "hostname", &host) || ! host || ! *host)
	{
		(void) acopm_log_warning("Ignoring DNSBL (no hostname) at line %u",
		                         config_setting_source_line(domain));

		(void) acopm_dnsbl_domain_free(newdomain);
		return false;
	}

	(void) acopm_dnsbl_domain_set_host(newdomain, host);

	config_setting_t *const matches = config_setting_get_member(domain, "matches");
	if (matches)
	{
		config_setting_t *match;
		for (unsigned int x = 0; (match = config_setting_get_elem(matches, x)) != NULL; x++)
			(void) acopm_config_parse_blacklist_domain_match(match, newdomain);
	}

	return acopm_dnsbl_domain_add(newdomain);
}

static inline bool acopm_attr_inline
acopm_config_parse_blacklist_domains(config_setting_t *const restrict domains)
{
	config_setting_t *domain;
	for (unsigned int x = 0; (domain = config_setting_get_elem(domains, x)) != NULL; x++)
		(void) acopm_config_parse_blacklist_domain(domain);

	return true;
}

static bool
acopm_config_parse_blacklist(config_setting_t *const restrict blacklist)
{
	assert(blacklist != NULL);

	long long int dns_timeout;
	if (config_setting_lookup_int64(blacklist, "dns_timeout", &dns_timeout))
	{
		if (dns_timeout < ACOPM_DNSBL_LOOKUP_TIMEOUT_MIN)
			dns_timeout = ACOPM_DNSBL_LOOKUP_TIMEOUT_MIN;
		if (dns_timeout > ACOPM_DNSBL_LOOKUP_TIMEOUT_MAX)
			dns_timeout = ACOPM_DNSBL_LOOKUP_TIMEOUT_MAX;

		(void) acopm_dnsbl_set_timeout(acopm_dnsbl_client, (unsigned int) dns_timeout);
	}

	config_setting_t *const nameservers = config_setting_get_member(blacklist, "nameservers");
	config_setting_t *const domains = config_setting_get_member(blacklist, "domains");

	if (! nameservers)
	{
		(void) acopm_log_error("Required section blacklist::nameservers missing from configuration");
		return false;
	}
	if (! domains)
	{
		(void) acopm_log_error("Required section blacklist::domains missing from configuration");
		return false;
	}

	if (! acopm_config_parse_blacklist_nameservers(nameservers))
		return false;
	if (! acopm_config_parse_blacklist_domains(domains))
		return false;

	return true;
}



static bool
acopm_config_parse_proxyscan_list(config_setting_t *const restrict list, const enum acopm_proxy_type type)
{
	assert(list != NULL);
	assert(type != ACOPM_PROXY_TYPE_NONE);

	config_setting_t *port;

	bool port_given = false;

	for (unsigned int x = 0; (port = config_setting_get_elem(list, x)) != NULL; x++)
	{
		const long long int portnum = config_setting_get_int64(port);

		if (portnum < ACOPM_PROXY_PORT_NUM_MIN || portnum > ACOPM_PROXY_PORT_NUM_MAX)
		{
			(void) acopm_log_warning("Ignoring port with invalid value %lld at line %u", portnum,
			                         config_setting_source_line(port));
			continue;
		}

		struct acopm_proxy_port *const newport = acopm_proxy_port_new(acopm_proxy_client);

		(void) acopm_proxy_port_set_type(newport, type);
		(void) acopm_proxy_port_set_port(newport, (uint16_t) portnum);

		if (acopm_proxy_port_add(newport))
			port_given = true;
	}

	return port_given;
}

static bool
acopm_config_parse_proxyscan(config_setting_t *const restrict proxyscan)
{
	assert(proxyscan != NULL);

	(void) acopm_config_parse_strbuf(proxyscan, "proxy_hit_exe", proxy_hit_exe, sizeof proxy_hit_exe, true);
	(void) acopm_config_parse_strbuf(proxyscan, "proxy_hit_log", proxy_hit_log, sizeof proxy_hit_log, true);
	(void) acopm_config_parse_strbuf(proxyscan, "proxy_hit_var1", proxy_hit_var1, sizeof proxy_hit_var1, true);
	(void) acopm_config_parse_strbuf(proxyscan, "proxy_hit_var2", proxy_hit_var2, sizeof proxy_hit_var2, true);
	(void) acopm_config_parse_strbuf(proxyscan, "proxy_hit_var3", proxy_hit_var3, sizeof proxy_hit_var3, true);
	(void) acopm_config_parse_strbuf(proxyscan, "proxy_hit_var4", proxy_hit_var4, sizeof proxy_hit_var4, true);
	(void) acopm_config_parse_strbuf(proxyscan, "proxy_hit_var5", proxy_hit_var5, sizeof proxy_hit_var5, true);

	config_setting_t *member = NULL;
	bool list_given = false;

	if ((member = config_setting_get_member(proxyscan, "connect_timeout")))
	{
		const long long int value = config_setting_get_int(member);

		if (value < ACOPM_PROXY_CONNECT_TIMEOUT_MIN || value > ACOPM_PROXY_CONNECT_TIMEOUT_MAX)
		{
			(void) acopm_log_warning("Ignoring connect_timeout with invalid value %lld at line %u",
			                         value, config_setting_source_line(member));
		}
		else
			(void) acopm_proxy_set_connect_timeout(acopm_proxy_client, (time_t) value);
	}
	if ((member = config_setting_get_member(proxyscan, "data_timeout")))
	{
		const long long int value = config_setting_get_int(member);

		if (value < ACOPM_PROXY_DATA_TIMEOUT_MIN || value > ACOPM_PROXY_DATA_TIMEOUT_MAX)
		{
			(void) acopm_log_warning("Ignoring data_timeout with invalid value %lld at line %u",
			                         value, config_setting_source_line(member));
		}
		else
			(void) acopm_proxy_set_data_timeout(acopm_proxy_client, (time_t) value);
	}
	if ((member = config_setting_get_member(proxyscan, "recvbuf_sz")))
	{
		const long long int value = config_setting_get_int(member);

		if (value < ACOPM_PROXY_RECVBUF_SZ_MIN || value > ACOPM_PROXY_RECVBUF_SZ_MAX)
		{
			(void) acopm_log_warning("Ignoring recvbuf_sz with invalid value %lld at line %u",
			                         value, config_setting_source_line(member));
		}
		else
			(void) acopm_proxy_set_recvbuf_sz(acopm_proxy_client, (size_t) value);
	}
	if ((member = config_setting_get_member(proxyscan, "sendbuf_sz")))
	{
		const long long int value = config_setting_get_int(member);

		if (value < ACOPM_PROXY_SENDBUF_SZ_MIN || value > ACOPM_PROXY_SENDBUF_SZ_MAX)
		{
			(void) acopm_log_warning("Ignoring sendbuf_sz with invalid value %lld at line %u",
			                         value, config_setting_source_line(member));
		}
		else
			(void) acopm_proxy_set_sendbuf_sz(acopm_proxy_client, (size_t) value);
	}
	if ((member = config_setting_get_member(proxyscan, "username")))
	{
		const char *const value = config_setting_get_string(member);

		if (! value || ! *value)
		{
			(void) acopm_log_warning("Ignoring username with invalid value at line %u",
			                         config_setting_source_line(member));
		}
		else
			(void) acopm_proxy_set_username(acopm_proxy_client, value);
	}
	if ((member = config_setting_get_member(proxyscan, "port")))
	{
		list_given = true;

		if (! acopm_config_parse_proxyscan_list(member, ACOPM_PROXY_TYPE_PORT))
			return false;
	}
	if ((member = config_setting_get_member(proxyscan, "socks4")))
	{
		list_given = true;

		if (! acopm_config_parse_proxyscan_list(member, ACOPM_PROXY_TYPE_SOCKS4))
			return false;
	}
	if ((member = config_setting_get_member(proxyscan, "socks5")))
	{
		list_given = true;

		if (! acopm_config_parse_proxyscan_list(member, ACOPM_PROXY_TYPE_SOCKS5))
			return false;
	}
	if ((member = config_setting_get_member(proxyscan, "http")))
	{
		list_given = true;

		if (! acopm_config_parse_proxyscan_list(member, ACOPM_PROXY_TYPE_HTTP_CONNECT))
			return false;
	}
	if ((member = config_setting_get_member(proxyscan, "https")))
	{
#ifdef BUILD_WITH_MBEDTLS
		list_given = true;

		if (! acopm_config_parse_proxyscan_list(member, ACOPM_PROXY_TYPE_HTTPS_CONNECT))
			return false;
#else
		(void) acopm_log_warning("%s: program not built with TLS support, cannot scan for HTTPS proxies", __func__);
#endif
	}

	if (! list_given)
		(void) acopm_log_error("Specifying a proxyscan section without any proxy types is pointless");

	return list_given;
}



static bool
acopm_config_parse_exempt(config_setting_t *const exempt)
{
	assert(exempt != NULL);

	if (! config_setting_is_list(exempt))
	{
		(void) acopm_log_error("The exempt configuration entry is not a list");
		return false;
	}

	config_setting_t *addr;

	for (unsigned int x = 0; (addr = config_setting_get_elem(exempt, x)) != NULL; x++)
	{
		const char *const addr_str = config_setting_get_string(addr);

		if (! addr_str)
		{
			(void) acopm_log_error("Entry in list at line %u is not a string",
			                       config_setting_source_line(addr));
			return false;
		}

		if (! acopm_check_add_exemption(addr_str))
			return false;
	}

	return true;
}



bool
acopm_config_parse(void)
{
	config_setting_t *section = NULL;
	bool result = false;
	FILE *fh = NULL;

	config_t config;

	(void) acopm_log_info("Parsing config file '%s'", config_file);

	(void) config_init(&config);

#if defined(SYSCONFDIR) && defined(LIBCONFIG_SUPPORTS_SET_INCLUDE_DIR)
	(void) config_set_include_dir(&config, SYSCONFDIR);
#endif

	if (! (fh = fopen(config_file, "r")))
	{
		(void) acopm_log_critical("Error opening config file: %s", strerror(errno));
		goto end;
	}
	if (! config_read(&config, fh))
	{
		(void) acopm_log_critical("Error parsing config file (line %d): %s",
		                          config_error_line(&config), config_error_text(&config));
		goto end;
	}

	if (! acopm_config_parse_options(&config))
		goto end;

	if (! (section = config_lookup(&config, "server")))
	{
		(void) acopm_log_error("Required section server missing from configuration file");
		goto end;
	}
	else if (! acopm_config_parse_server(section))
		goto end;

	if ((section = config_lookup(&config, "blacklist")) && ! acopm_config_parse_blacklist(section))
		goto end;

	if ((section = config_lookup(&config, "proxyscan")) && ! acopm_config_parse_proxyscan(section))
		goto end;

	if ((section = config_lookup(&config, "exempt")) && ! acopm_config_parse_exempt(section))
		goto end;

	result = true;

end:
	if (fh)
		(void) fclose(fh);

	(void) config_destroy(&config);
	return result;
}