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;
}