/*
 *   Unreal Internet Relay Chat Daemon, src/conf.c
 *   (C) 1998-2000 Chris Behrens & Fred Jacobs (comstud, moogle)
 *   (C) 2000-2002 Carsten V. Munk and the UnrealIRCd Team
 *
 *   This program is free software; you can redistribute it and/or modify
 *   it under the terms of the GNU General Public License as published by
 *   the Free Software Foundation; either version 1, or (at your option)
 *   any later version.
 *
 *   This program is distributed in the hope that it will be useful,
 *   but WITHOUT ANY WARRANTY; without even the implied warranty of
 *   MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
 *   GNU General Public License for more details.
 *
 *   You should have received a copy of the GNU General Public License
 *   along with this program; if not, write to the Free Software
 *   Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
 */

#include "unrealircd.h"

/*
 * Some typedefs..
*/
typedef struct ConfigCommand ConfigCommand;
struct ConfigCommand
{
	char	*name;
	int	(*conffunc)(ConfigFile *conf, ConfigEntry *ce);
	int 	(*testfunc)(ConfigFile *conf, ConfigEntry *ce);
};


/* Config commands */

static int	_conf_admin		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_me		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_files		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_oper		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_operclass		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_class		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_drpass		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_ulines		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_include		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_tld		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_listen		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_allow		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_except		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_link		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_ban		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_set		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_deny		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_deny_channel	(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_deny_version	(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_require		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_allow_channel	(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_loadmodule	(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_alias		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_help		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_offchans		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_sni		(ConfigFile *conf, ConfigEntry *ce);
extern int	_conf_security_group	(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_secret		(ConfigFile *conf, ConfigEntry *ce);
static int	_conf_proxy		(ConfigFile *conf, ConfigEntry *ce);

/*
 * Validation commands
*/

static int	_test_admin		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_me		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_files		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_oper		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_operclass		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_class		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_drpass		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_ulines		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_include		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_tld		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_listen		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_allow		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_except		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_link		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_ban		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_require		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_set		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_deny		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_allow_channel	(ConfigFile *conf, ConfigEntry *ce);
static int	_test_loadmodule	(ConfigFile *conf, ConfigEntry *ce);
static int	_test_blacklist_module	(ConfigFile *conf, ConfigEntry *ce);
static int	_test_alias		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_help		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_offchans		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_sni		(ConfigFile *conf, ConfigEntry *ce);
extern int	_test_security_group	(ConfigFile *conf, ConfigEntry *ce);
static int	_test_secret		(ConfigFile *conf, ConfigEntry *ce);
static int	_test_proxy		(ConfigFile *conf, ConfigEntry *ce);

/* This MUST be alphabetized */
static ConfigCommand _ConfigCommands[] = {
	{ "admin", 		_conf_admin,		_test_admin 	},
	{ "alias",		_conf_alias,		_test_alias	},
	{ "allow",		_conf_allow,		_test_allow	},
	{ "ban", 		_conf_ban,		_test_ban	},
	{ "blacklist-module",	NULL,		 	NULL },
	{ "class", 		_conf_class,		_test_class	},
	{ "deny",		_conf_deny,		_test_deny	},
	{ "drpass",		_conf_drpass,		_test_drpass	},
	{ "except",		_conf_except,		_test_except	},
	{ "files",		_conf_files,		_test_files	},
	{ "help",		_conf_help,		_test_help	},
	{ "include",		NULL,	  		_test_include	},
	{ "link", 		_conf_link,		_test_link	},
	{ "listen", 		_conf_listen,		_test_listen	},
	{ "loadmodule",		NULL,		 	_test_loadmodule},
	{ "log",		config_run_log,		config_test_log	},
	{ "me", 		_conf_me,		_test_me	},
	{ "official-channels", 	_conf_offchans,		_test_offchans	},
	{ "oper", 		_conf_oper,		_test_oper	},
	{ "operclass",		_conf_operclass,	_test_operclass	},
	{ "proxy", 		_conf_proxy,		_test_proxy	},
	{ "require", 		_conf_require,		_test_require	},
	{ "secret",		_conf_secret,		_test_secret	},
	{ "security-group",	_conf_security_group,	_test_security_group	},
	{ "set",		_conf_set,		_test_set	},
	{ "sni",		_conf_sni,		_test_sni	},
	{ "tld",		_conf_tld,		_test_tld	},
	{ "ulines",		_conf_ulines,		_test_ulines	},
	{ "webirc", 		_conf_proxy,		_test_proxy	},
};

/* This MUST be alphabetized */
static NameValue _ListenerFlags[] = {
	{ LISTENER_CLIENTSONLY,  "clientsonly"},
	{ LISTENER_DEFER_ACCEPT, "defer-accept"},
	{ LISTENER_SERVERSONLY,  "serversonly"},
	{ LISTENER_TLS, 	 "ssl"},
	{ LISTENER_NORMAL, 	 "standard"},
	{ LISTENER_TLS, 	 "tls"},
};

/* This MUST be alphabetized */
static NameValue _LinkOutgoingFlags[] = {
	{ CONNECT_OUTGOING_AUTO,	"autoconnect" },
	{ CONNECT_OUTGOING_INSECURE,	"insecure" },
	{ CONNECT_QUARANTINE, "quarantine"},
	{ CONNECT_OUTGOING_TLS, "ssl" },
	{ CONNECT_OUTGOING_TLS, "tls" },
};

/* This MUST be alphabetized */
static NameValue _LinkFlags[] = {
	{ CONNECT_NO_CERTIFICATE_VERIFICATION, "no-certificate-verification"},
	{ CONNECT_QUARANTINE, "quarantine"},
};

/* This MUST be alphabetized */
static NameValue _TLSFlags[] = {
	{ TLSFLAG_FAILIFNOCERT, "fail-if-no-clientcert" },
	{ TLSFLAG_DISABLECLIENTCERT, "no-client-certificate" },
	{ TLSFLAG_NOSTARTTLS, "no-starttls" },
};

struct {
	unsigned conf_me : 1;
	unsigned conf_admin : 1;
	unsigned conf_listen : 1;
} requiredstuff;
struct {
	int min, max;
} nicklengths;
struct SetCheck settings;
/*
 * Utilities
*/

void	port_range(const char *string, int *start, int *end);
long	config_checkval(const char *value, unsigned short flags);

/*
 * Parser
*/

ConfigFile		*config_load(const char *filename, const char *displayname);
void			config_free(ConfigFile *cfptr);
ConfigFile		*config_parse_with_offset(const char *filename, char *confdata, unsigned int line_offset);
ConfigFile	 	*config_parse(const char *filename, char *confdata);
ConfigEntry		*config_find_entry(ConfigEntry *ce, const char *name);

extern void add_entropy_configfile(struct stat *st, const char *buf);
extern void unload_all_unused_umodes(void);
extern void unload_all_unused_extcmodes(void);
extern void unload_all_unused_extbans(void);
extern void unload_all_unused_caps(void);
extern void unload_all_unused_history_backends(void);
extern void unload_all_unused_rpc_handlers(void);
extern void unload_all_unused_apicallbacks(void);

int reloadable_perm_module_unloaded(void);
int tls_tests(void);

/* Conf sub-sub-functions */
void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors);
void conf_tlsblock(ConfigFile *conf, ConfigEntry *cep, TLSOptions *tlsoptions);
void free_tls_options(TLSOptions *tlsoptions);

/*
 * Config parser (IRCd)
*/
int			config_read_file(const char *filename, const char *display_name);
void			config_rehash(void);
int			config_run_blocks(void);
int	config_test_blocks();

/*
 * Configuration linked lists
*/
ConfigItem_me		*conf_me = NULL;
ConfigItem_files	*conf_files = NULL;
ConfigItem_class 	*conf_class = NULL;
ConfigItem_class	*default_class = NULL;
ConfigItem_admin 	*conf_admin = NULL;
ConfigItem_admin	*conf_admin_tail = NULL;
ConfigItem_drpass	*conf_drpass = NULL;
ConfigItem_ulines	*conf_ulines = NULL;
ConfigItem_tld		*conf_tld = NULL;
ConfigItem_oper		*conf_oper = NULL;
ConfigItem_operclass	*conf_operclass = NULL;
ConfigItem_listen	*conf_listen = NULL;
ConfigItem_sni		*conf_sni = NULL;
ConfigItem_allow	*conf_allow = NULL;
ConfigItem_link		*conf_link = NULL;
ConfigItem_ban		*conf_ban = NULL;
ConfigItem_deny_channel *conf_deny_channel = NULL;
ConfigItem_allow_channel *conf_allow_channel = NULL;
ConfigItem_deny_version *conf_deny_version = NULL;
ConfigItem_alias	*conf_alias = NULL;
ConfigResource	*config_resources = NULL;
ConfigItem_blacklist_module	*conf_blacklist_module = NULL;
ConfigItem_help		*conf_help = NULL;
ConfigItem_offchans	*conf_offchans = NULL;
ConfigItem_proxy	*conf_proxy = NULL;

Secret			*secrets = NULL;

Configuration		iConf;
Configuration		tempiConf;
BestPractices		bestpractices;
MODVAR ConfigFile		*conf = NULL;
extern NameValueList *config_defines;
MODVAR int ipv6_disabled = 0;
MODVAR Client *remote_rehash_client = NULL;
MODVAR json_t *json_rehash_log = NULL;
MODVAR DynamicSetBlock unknown_users_set;
MODVAR DynamicSetBlock dynamic_set;

int			config_verbose = 0;

int need_operclass_permissions_upgrade = 0;
int invalid_snomasks_encountered = 0;
int have_tls_listeners = 0;
char *port_6667_ip = NULL;
int has_client_port = 0;

long long central_spamfilter_last_download = 0;

int add_config_resource(const char *resource, int type, ConfigEntry *ce);
ConfigResource *find_config_resource(const char *resource);
void resource_download_complete(OutgoingWebRequest *request, OutgoingWebResponse *response);
void resource_download_complete_parse(ConfigResource *rs);
void free_all_config_resources(void);
int rehash_internal(Client *client);
int is_blacklisted_module(const char *name);
int modules_default_conf_modified(const char *filebuf);
int config_item_allowed_for_config_file(const char *resource, const char *item);
void remove_config_tkls(int flag);
void free_operclass_struct(OperClass *o);

/** Return the printable string of a 'cep' location, such as set::something::xyz */
const char *config_var(ConfigEntry *cep)
{
	static char buf[256];
	ConfigEntry *e;
	char *elem[16];
	int numel = 0, i;

	if (!cep)
		return "";

	buf[0] = '\0';

	/* First, walk back to the top */
	for (e = cep; e; e = e->parent)
	{
		elem[numel++] = e->name;
		if (numel == 15)
			break;
	}

	/* Now construct the xxx::yyy::zzz string */
	for (i = numel-1; i >= 0; i--)
	{
		strlcat(buf, elem[i], sizeof(buf));
		if (i > 0)
			strlcat(buf, "::", sizeof(buf));
	}

	return buf;
}

/** Detect and report duplicate configuration directives.
 * On first call (when *var is 0), sets *var to 1 and returns 0.
 * On subsequent calls (when *var is already 1), reports a
 * configuration error, increments the error counter, and returns 1.
 * @param var		Pointer to a tracking variable (initialize to 0)
 * @param ce		The configuration entry being checked
 * @param errors	Pointer to the error counter
 * @returns 1 if this is a duplicate, 0 otherwise
 *
 * Example usage:
 * @code
 * int has_vhost = 0, has_password = 0;
 * int errors = 0;
 * for (cep = ce->items; cep; cep = cep->next)
 * {
 *     if (!strcmp(cep->name, "vhost"))
 *         config_detect_duplicate(&has_vhost, cep, &errors);
 *     else if (!strcmp(cep->name, "password"))
 *         config_detect_duplicate(&has_password, cep, &errors);
 * }
 * @endcode
 */
int config_detect_duplicate(int *var, ConfigEntry *ce, int *errors)
{
	if (*var)
	{
		config_error("%s:%d: Duplicate %s directive",
			ce->file->filename, ce->line_number,
			ce->name);
		(*errors)++;
		return 1;
	} else {
		*var = 1;
	}
	return 0;
}

void port_range(const char *string, int *start, int *end)
{
	char buf[256];
	char *c;
	strlcpy(buf, string, sizeof(buf));
	c = strchr(buf, '-');
	if (!c)
	{
		int tmp = atoi(string);
		*start = tmp;
		*end = tmp;
		return;
	}
	*c = '\0';
	*start = atoi(string);
	*end = atoi((c+1));
	*c = '-';
}

/** Parses '5:60s' config values.
 * orig: original string
 * times: pointer to int, first value (before the :)
 * period: pointer to int, second value (after the :) in seconds
 * RETURNS: 0 for parse error, 1 if ok.
 * REMARK: times&period should be ints!
 */
int config_parse_flood(const char *orig, int *times, int *period)
{
	char buf[256];
	char *x;

	strlcpy(buf, orig, sizeof(buf));

	*times = *period = 0;
	x = strchr(buf, ':');
	/* 'blah', ':blah', '1:' */
	if (!x || (x == buf) || (*(x+1) == '\0'))
		return 0;

	*x = '\0';
	*times = atoi(buf);
	*period = config_checkval(x+1, CFG_TIME);
	*x = ':'; /* restore */
	return 1;
}

/** Find an anti-flood settings block by name.
 * @param name		The name of the set::anti-flood block
 * @returns The FloodSettings block if found, or NULL if not found.
 */
FloodSettings *find_floodsettings_block_ex(Configuration *conf, const char *name)
{
	FloodSettings *f;

	for (f = conf->floodsettings; f; f = f->next)
		if (!strcmp(f->name, name))
			return f;

	return NULL;
}

/** Find an anti-flood settings block by name.
 * @param name		The name of the set::anti-flood block
 * @returns The FloodSettings block if found, or NULL if not found.
 */
FloodSettings *find_floodsettings_block(const char *name)
{
	return find_floodsettings_block_ex(&iConf, name);
}

/** Check if 'name' is in the array 'list'.
 * @param name		The name to check
 * @param list		The char *list[] with the list of valid names.
 * @returns 1 if found, 0 if not
 * @note The array in list must end in a NULL element!
 */
int text_in_array(const char *name, const char *list[])
{
	int i;

	for (i=0; list[i]; i++)
		if (!strcmp(name, list[i]))
			return 1;

	return 0; /* Not found */
}

int flood_option_is_old(const char *name)
{
	const char *opts[] =
	{
		"max-concurrent-conversations",
		"unknown-flood-amount",
		"unknown-flood-bantime",
		"handshake-data-flood",
		"away-count",
		"away-period",
		"away-flood",
		"nick-flood",
		"join-flood",
		"invite-flood",
		"knock-flood",
		"connect-flood",
		"target-flood",
		"multiline",
		NULL
	};

	return text_in_array(name, opts);
}

int flood_option_is_for_everyone(const char *name)
{
	const char *opts[] =
	{
		"connect-flood",
		"handshake-data-flood",
		"unknown-flood",
		"target-flood",
		NULL
	};

	return text_in_array(name, opts);
}

/** Free a FloodSettings struct */
void free_floodsettings(FloodSettings *f)
{
	safe_free(f->name);
	safe_free(f);
}

/** Parses a value like '5:60s' into a flood setting that we can store.
 * @param str		The string to parse (eg: '5:60s')
 * @param settings	The FloodSettings block to store the result in
 * @param opt		The option (eg: FLD_AWAY)
 * @returns 1 if OK, 0 for parse error.
 */
int config_parse_flood_generic(const char *str, Configuration *conf, char *blockname, FloodOption opt)
{
	char buf[64], *p;
	FloodSettings *settings = find_floodsettings_block_ex(conf, blockname);

	/* Create a new anti-flood block if it doesn't exist */
	if (!settings)
	{
		settings = safe_alloc(sizeof(FloodSettings));
		safe_strdup(settings->name, blockname);
		AddListItem(settings, conf->floodsettings);
	}

	if (!strcmp(str, "unlimited") || !strcmp(str, "max"))
	{
		settings->limit[opt] = -1;
		settings->period[opt] = 0;
		return 1;
	}

	/* Work on a copy so we don't destroy 'str' */
	strlcpy(buf, str, sizeof(buf));

	p = strchr(buf, ':');

	/* 'blah', ':blah', '1:' */
	if (!p || (p == buf) || (*(p+1) == '\0'))
		return 0;

	*p++ = '\0';

	settings->limit[opt] = atoi(buf);
	settings->period[opt] = config_checkval(p, CFG_TIME);

	return 1;
}

/** Convert a configuration value string to a long, based on the type.
 * @param orig		The original string value
 * @param flags		The type of value: CFG_TIME, CFG_SIZE, or CFG_YESNO
 * @returns The converted numeric value
 */
long config_checkval(const char *orig, unsigned short flags)
{
	char *value;
	char *text;
	long ret = 0;

	/* Handle empty strings early, since we use +1 later in the code etc. */
	if (BadPtr(orig))
		return 0;

	value = raw_strdup(orig);
	if (flags == CFG_YESNO) {
		for (text = value; *text; text++) {
			if (!isalnum(*text))
				continue;
			if (tolower(*text) == 'y' || (tolower(*text) == 'o' &&
			    tolower(*(text+1)) == 'n') || *text == '1' || tolower(*text) == 't') {
				ret = 1;
				break;
			}
		}
	}
	else if (flags == CFG_SIZE) {
		int mfactor = 1;
		char *sz;
		for (text = value; *text; text++) {
			if (isalpha(*text)) {
				if (tolower(*text) == 'k')
					mfactor = 1024;
				else if (tolower(*text) == 'm')
					mfactor = 1048576;
				else if (tolower(*text) == 'g')
					mfactor = 1073741824;
				else
					mfactor = 1;
				sz = text;
				while (isalpha(*text))
					text++;

				*sz = 0;
				while (sz-- > value && *sz) {
					if (isspace(*sz))
						*sz = 0;
					if (!isdigit(*sz))
						break;
				}
				ret += atoi(sz+1)*mfactor;
				if (*text == '\0') {
					text++;
					break;
				}
			}
		}
		mfactor = 1;
		sz = text;
		sz--; /* -1 because we are PAST the end of the string */
		while (sz-- > value) {
			if (isspace(*sz))
				*sz = 0;
			if (!isdigit(*sz))
				break;
		}
		ret += atoi(sz+1)*mfactor;
	}
	else if (flags == CFG_TIME) {
		int mfactor = 1;
		char *sz;
		for (text = value; *text; text++) {
			if (isalpha(*text)) {
				if (tolower(*text) == 'w')
					mfactor = 604800;
				else if (tolower(*text) == 'd')
					mfactor = 86400;
				else if (tolower(*text) == 'h')
					mfactor = 3600;
				else if (tolower(*text) == 'm')
					mfactor = 60;
				else
					mfactor = 1;
				sz = text;
				while (isalpha(*text))
					text++;

				*sz = 0;
				while (sz-- > value && *sz) {
					if (isspace(*sz))
						*sz = 0;
					if (!isdigit(*sz))
						break;
				}
				ret += atoi(sz+1)*mfactor;
				if (*text == '\0') {
					text++;
					break;
				}
			}
		}
		mfactor = 1;
		sz = text;
		sz--; /* -1 because we are PAST the end of the string */
		while (sz-- > value) {
			if (isspace(*sz))
				*sz = 0;
			if (!isdigit(*sz))
				break;
		}
		ret += atoi(sz+1)*mfactor;
	}
	safe_free(value);
	return ret;
}

/** Free configuration setting for set::modes-on-join */
void free_conf_channelmodes(struct ChMode *store)
{
	int i;

	for (i=0; i <= 255; i++)
		safe_free(store->extparams[i]);

	memset(store, 0, sizeof(struct ChMode));
}

/* Set configuration, used for set::modes-on-join */
void conf_channelmodes(const char *modes, struct ChMode *store)
{
	Cmode *cm;
	const char *m;
	char *params = strchr(modes, ' ');
	char *parambuf = NULL;
	const char *param = NULL;
	const char *param_in;
	char *save = NULL;
	int found;

	/* Free existing parameters first (no inheritance) */
	free_conf_channelmodes(store);

	if (params)
	{
		params++;
		safe_strdup(parambuf, params);
		param = strtoken(&save, parambuf, " ");
	}

	for (m = modes; *m && *m != ' '; m++)
	{
		if (*m == '+')
			continue;

		if (*m == '-')
		{
			/* When a channel is created it has no modes, so just ignore if the
			 * user asks us to unset anything -- codemastr
			 */
			while (*m && *m != '+')
				m++;
			continue;
		}

		found = 0;
		for (cm=channelmodes; cm; cm = cm->next)
		{
			if (!(cm->letter))
				continue;
			if (*m == cm->letter)
			{
				found = 1;
				if (cm->paracount)
				{
					if (!param)
					{
						config_warn("set::modes-on-join '%s'. Parameter missing for mode %c.", modes, *m);
						break;
					}
					param_in = param; /* save it */
					param = cm->conv_param(param, NULL, NULL);
					if (!param)
					{
						config_warn("set::modes-on-join '%s'. Parameter for mode %c is invalid (%s).", modes, *m, param_in);
						break; /* invalid parameter fmt, do not set mode. */
					}
					safe_strdup(store->extparams[cm->letter], param);
					/* Get next parameter */
					param = strtoken(&save, NULL, " ");
				}
				store->extmodes |= cm->mode;
				break;
			}
		}
		if (!found)
			config_warn("set::modes-on-join '%s'. Channel mode %c not found.", modes, *m);
	}
	safe_free(parambuf);
}

void chmode_str(struct ChMode *modes, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size)
{
	Cmode *cm;

	if (!(mbuf_size && pbuf_size))
		return;

	*pbuf = 0;
	*mbuf++ = '+';

	for (cm=channelmodes; cm; cm = cm->next)
	{
		if (!(cm->letter))
			continue;

		if (modes->extmodes & cm->mode)
		{
			if (mbuf_size)
			{
				*mbuf++ = cm->letter;
				if (!--mbuf_size)
				{
					*--mbuf=0;
					break;
				}
			}
			if (cm->paracount)
			{
				strlcat(pbuf, modes->extparams[cm->letter], pbuf_size);
				strlcat(pbuf, " ", pbuf_size);
			}
		}
	}
	*mbuf=0;
}

const char *channellevel_to_string(const char *s)
{
	/* Requested at http://bugs.unrealircd.org/view.php?id=3852 */
	if (!strcmp(s, "none"))
		return "";
	if (!strcmp(s, "voice"))
		return "v";
	if (!strcmp(s, "halfop"))
		return "h";
	if (!strcmp(s, "op") || !strcmp(s, "chanop"))
		return "o";
	if (!strcmp(s, "protect") || !strcmp(s, "chanprot") || !strcmp(s, "chanadmin") || !strcmp(s, "admin"))
		return "a";
	if (!strcmp(s, "owner") || !strcmp(s, "chanowner"))
		return "q";

	return NULL; /* unknown or unsupported */
}

Policy policy_strtoval(const char *s)
{
	if (!s)
		return 0;

	if (!strcmp(s, "allow"))
		return POLICY_ALLOW;

	if (!strcmp(s, "warn"))
		return POLICY_WARN;

	if (!strcmp(s, "deny"))
		return POLICY_DENY;

	return 0;
}

const char *policy_valtostr(Policy policy)
{
	if (policy == POLICY_ALLOW)
		return "allow";
	if (policy == POLICY_WARN)
		return "warn";
	if (policy == POLICY_DENY)
		return "deny";
	return "???";
}

char policy_valtochar(Policy policy)
{
	if (policy == POLICY_ALLOW)
		return 'a';
	if (policy == POLICY_WARN)
		return 'w';
	if (policy == POLICY_DENY)
		return 'd';
	return '?';
}

AllowedChannelChars allowed_channelchars_strtoval(const char *str)
{
	if (!strcmp(str, "ascii"))
		return ALLOWED_CHANNELCHARS_ASCII;
	else if (!strcmp(str, "utf8"))
		return ALLOWED_CHANNELCHARS_UTF8;
	else if (!strcmp(str, "any"))
		return ALLOWED_CHANNELCHARS_ANY;
	return 0;
}

const char *allowed_channelchars_valtostr(AllowedChannelChars v)
{
	switch(v)
	{
		case ALLOWED_CHANNELCHARS_ASCII:
			return "ascii";
		case ALLOWED_CHANNELCHARS_UTF8:
			return "utf8";
		case ALLOWED_CHANNELCHARS_ANY:
			return "any";
		default:
			/* Not possible */
			abort();
			return "NOTREACHED"; /* Windows.. */
	}
}

/* Used for set::automatic-ban-target and set::manual-ban-target */
BanTarget ban_target_strtoval(const char *str)
{
	if (!strcmp(str, "ip"))
		return BAN_TARGET_IP;
	else if (!strcmp(str, "userip"))
		return BAN_TARGET_USERIP;
	else if (!strcmp(str, "host"))
		return BAN_TARGET_HOST;
	else if (!strcmp(str, "userhost"))
		return BAN_TARGET_USERHOST;
	else if (!strcmp(str, "account"))
		return BAN_TARGET_ACCOUNT;
	else if (!strcmp(str, "certfp"))
		return BAN_TARGET_CERTFP;
	return 0; /* invalid */
}

/* Used for set::automatic-ban-target and set::manual-ban-target */
const char *ban_target_valtostr(BanTarget v)
{
	switch(v)
	{
		case BAN_TARGET_IP:
			return "ip";
		case BAN_TARGET_USERIP:
			return "userip";
		case BAN_TARGET_HOST:
			return "host";
		case BAN_TARGET_USERHOST:
			return "userhost";
		case BAN_TARGET_ACCOUNT:
			return "account";
		case BAN_TARGET_CERTFP:
			return "certfp";
		default:
			return "???";
	}
}

HideIdleTimePolicy hideidletime_strtoval(const char *str)
{
	if (!strcmp(str, "never"))
		return HIDE_IDLE_TIME_NEVER;
	else if (!strcmp(str, "always"))
		return HIDE_IDLE_TIME_ALWAYS;
	else if (!strcmp(str, "usermode"))
		return HIDE_IDLE_TIME_USERMODE;
	else if (!strcmp(str, "oper-usermode"))
		return HIDE_IDLE_TIME_OPER_USERMODE;
	return 0;
}

const char *hideidletime_valtostr(HideIdleTimePolicy v)
{
	switch(v)
	{
		case HIDE_IDLE_TIME_NEVER:
			return "never";
		case HIDE_IDLE_TIME_ALWAYS:
			return "always";
		case HIDE_IDLE_TIME_USERMODE:
			return "usermode";
		case HIDE_IDLE_TIME_OPER_USERMODE:
			return "oper-usermode";
		default:
			return "INVALID";
	}
}

ConfigFile *config_load(const char *filename, const char *displayname)
{
	struct stat sb;
	int			fd;
	int			ret;
	char		*buf = NULL;
	ConfigFile	*cfptr;

	if (!displayname)
		displayname = filename;

#ifndef _WIN32
	fd = open(filename, O_RDONLY);
#else
	fd = open(filename, O_RDONLY|O_BINARY);
#endif
	if (fd == -1)
	{
		config_error("Couldn't open \"%s\": %s", filename, strerror(errno));
		return NULL;
	}
	if (fstat(fd, &sb) == -1)
	{
		config_error("Couldn't fstat \"%s\": %s", filename, strerror(errno));
		close(fd);
		return NULL;
	}
	if (!sb.st_size)
	{
		/* Workaround for empty files */
		cfptr = config_parse(filename, " ");
		close(fd);
		return cfptr;
	}
	buf = safe_alloc(sb.st_size+1);
	if (buf == NULL)
	{
		config_error("Out of memory trying to load \"%s\"", filename);
		close(fd);
		return NULL;
	}
	ret = read(fd, buf, sb.st_size);
	if (ret != sb.st_size)
	{
		config_error("Error reading \"%s\": %s", filename,
			ret == -1 ? strerror(errno) : strerror(EFAULT));
		safe_free(buf);
		close(fd);
		return NULL;
	}
	buf[ret] = '\0';
	close(fd);

	if (str_ends_with_case_insensitive(filename, "/modules.default.conf") &&
	    modules_default_conf_modified(buf))
	{
		safe_free(buf);
		return NULL;
	}
	add_entropy_configfile(&sb, buf);
	cfptr = config_parse(displayname, buf);
	safe_free(buf);
	return cfptr;
}

void config_free(ConfigFile *cfptr)
{
	ConfigFile	*nptr;

	for(;cfptr;cfptr=nptr)
	{
		nptr = cfptr->next;
		if (cfptr->items)
			config_entry_free_all(cfptr->items);
		safe_free(cfptr->filename);
		safe_free(cfptr);
	}
}

/** Remove quotes so that 'hello \"all\" \\ lala' becomes 'hello "all" \ lala' */
void unreal_del_quotes(char *i)
{
	char *o;

	for (o = i; *i; i++)
	{
		if (*i == '\\')
		{
			if ((i[1] == '\\') || (i[1] == '"'))
			{
				i++; /* skip original \ */
				if (*i == '\0')
					break;
			}
		}
		*o++ = *i;
	}
	*o = '\0';
}

/** Add quotes to a line, eg some"thing becomes some\"thing - extended version */
int unreal_add_quotes_r(const char *i, char *o, size_t len)
{
	if (len == 0)
		return 0;
	
	len--; /* reserve room for nul byte */

	if (len == 0)
	{
		*o = '\0';
		return 0;
	}
	
	for (; *i; i++)
	{
		if ((*i == '"') || (*i == '\\')) /* only " and \ need to be quoted */
		{
			if (len < 2)
				break;
			*o++ = '\\';
			*o++ = *i;
			len -= 2;
		} else
		{
			if (len == 0)
				break;
			*o++ = *i;
			len--;
		}
	}
	*o = '\0';
	
	return 1;
}	

/** Add quotes to a line, eg some"thing becomes some\"thing */
const char *unreal_add_quotes(const char *str)
{
	static char qbuf[2048];
	
	*qbuf = '\0';
	unreal_add_quotes_r(str, qbuf, sizeof(qbuf));
	return qbuf;
}

ConfigFile *config_parse(const char *filename, char *confdata)
{
	return config_parse_with_offset(filename, confdata, 0);
}

/* This is the internal parser, made by Chris Behrens & Fred Jacobs <2005.
 * Enhanced (or mutilated) by Bram Matthys over the years (2015-2019).
 */
ConfigFile *config_parse_with_offset(const char *filename, char *confdata, unsigned int line_offset)
{
	char		*ptr;
	char		*start;
	int		linenumber = 1+line_offset;
	int errors = 0;
	int n;
	ConfigEntry	*curce;
	ConfigEntry	**lastce;
	ConfigEntry	*cursection;
	ConfigFile	*curcf;
	int preprocessor_level = 0;
	ConditionalConfig *cc, *cc_list = NULL;

	curcf = safe_alloc(sizeof(ConfigFile));
	safe_strdup(curcf->filename, filename);
	lastce = &(curcf->items);
	curce = NULL;
	cursection = NULL;
	/* Replace \r's with spaces .. ugly ugly -Stskeeps */
	for (ptr=confdata; *ptr; ptr++)
		if (*ptr == '\r')
			*ptr = ' ';

	for(ptr=confdata;*ptr;ptr++)
	{
		switch(*ptr)
		{
			case ';':
				if (!curce)
				{
					config_status("%s:%i Ignoring extra semicolon",
						filename, linenumber);
					break;
				}
				*lastce = curce;
				lastce = &(curce->next);
				curce->file_position_end = (ptr - confdata);
				curce = NULL;
				break;
			case '{':
				if (!curce)
				{
					config_error("%s:%i: New section start detected on line %d but the section has no name. "
					             "Sections should start with a name like 'oper {' or 'set {'.",
							filename, linenumber, linenumber);
					errors++;
					continue;
				}
				else if (curce->items)
				{
					config_error("%s:%i: New section start but previous section did not end properly. "
					             "Check line %d and the line(s) before, you are likely missing a '};' there.",
							filename, linenumber, linenumber);
					errors++;
					continue;
				}
				curce->section_linenumber = linenumber;
				lastce = &(curce->items);
				cursection = curce;
				curce = NULL;
				break;
			case '}':
				if (curce)
				{
					config_error("%s:%i: Missing semicolon (';') before close brace. Check line %d and the line(s) before.",
						filename, linenumber, linenumber);
					config_entry_free_all(curce);
					config_free(curcf);
					errors++;
					return NULL;
				}
				else if (!cursection)
				{
					config_error("%s:%i: You have a close brace ('};') too many. "
					              "Check line %d AND the lines above it from the previous block.",
						filename, linenumber, linenumber);
					errors++;
					continue;
				}
				curce = cursection;
				cursection->file_position_end = (ptr - confdata);
				cursection = cursection->parent;
				if (!cursection)
					lastce = &(curcf->items);
				else
					lastce = &(cursection->items);
				for(;*lastce;lastce = &((*lastce)->next))
					continue;
				if (*(ptr+1) != ';')
				{
					/* Simulate closing ; so you can get away with } instead of ugly }; */
					*lastce = curce;
					lastce = &(curce->next);
					curce->file_position_end = (ptr - confdata);
					curce = NULL;
				}
				break;
			case '#':
				ptr++;
				while(*ptr && (*ptr != '\n'))
					 ptr++;
				if (!*ptr)
					break;
				ptr--;
				continue;
			case '/':
				if (*(ptr+1) == '/')
				{
					ptr += 2;
					while(*ptr && (*ptr != '\n'))
						ptr++;
					if (!*ptr)
						break;
					ptr--; /* grab the \n on next loop thru */
					continue;
				}
				else if (*(ptr+1) == '*')
				{
					int commentstart = linenumber;

					for(ptr+=2;*ptr;ptr++)
					{
						if (*ptr == '\n')
						{
							linenumber++;
						} else
						if ((*ptr == '*') && (*(ptr+1) == '/'))
						{
							ptr++;
							break;
						} else if ((*ptr == '/') && (*(ptr+1) == '*'))
						{
							config_warn("%s:%i nested comments are not supported (comment started at line %d)",
								filename, linenumber, commentstart);
						}
					}
					if (!*ptr)
					{
						config_error("%s:%i Comment on line %d does not end",
							filename, commentstart, commentstart);
						errors++;
						config_entry_free_all(curce);
						config_free(curcf);
						return NULL;
					}
				}
				else
				{
					/* Something like /tmp/xyz */
					goto processchar;
				}
				break;
			case '\'':
				if (curce)
					curce->escaped = 1;
				/* fallthrough */
			case '\"':
				if (curce && curce->line_number != linenumber && cursection)
				{
					config_error("%s:%i: Missing semicolon (';') at end of line. "
					             "Line %d must end with a ; character",
						filename, curce->line_number, curce->line_number);
					errors++;

					*lastce = curce;
					lastce = &(curce->next);
					curce->file_position_end = (ptr - confdata);
					curce = NULL;
				}

				start = ++ptr;
				for(;*ptr;ptr++)
				{
					if (*ptr == '\\')
					{
						if (strchr("\\\"'", ptr[1]))
						{
							/* \\ or \" in config file (escaped) */
							ptr++; /* skip */
							continue;
						}
					}
					else if (*ptr == '\n')
						break;
					else if (curce && curce->escaped && (*ptr == '\''))
						break;
					else if ((!curce || !curce->escaped) && (*ptr == '"'))
						break;
				}
				if (!*ptr || (*ptr == '\n'))
				{
					config_error("%s:%i: Unterminated quote found",
							filename, linenumber);
					errors++;
					config_entry_free_all(curce);
					config_free(curcf);
					return NULL;
				}
				if (curce)
				{
					if (curce->value)
					{
						config_error("%s:%i: Extra data detected. Perhaps missing a ';' or one too many?",
							filename, linenumber);
						errors++;
					}
					else
					{
						safe_strldup(curce->value, start, ptr-start+1);
						preprocessor_replace_defines(&curce->value, curce);
						unreal_del_quotes(curce->value);
					}
				}
				else
				{
					curce = safe_alloc(sizeof(ConfigEntry));
					curce->line_number = linenumber;
					curce->file = curcf;
					curce->parent = cursection;
					curce->file_position_start = (start - confdata);
					safe_strldup(curce->name, start, ptr-start+1);
					preprocessor_replace_defines(&curce->name, curce);
					unreal_del_quotes(curce->name);
					preprocessor_cc_duplicate_list(cc_list, &curce->conditional_config);
				}
				break;
			case '\n':
				linenumber++;
				/* fall through */
			case '\t':
			case ' ':
			case '=':
			case '\r':
				break;
			case '@':
				/* Preprocessor item, such as @if, @define, etc. */
				start = ptr;
				for (;*ptr; ptr++)
				{
					if (*ptr == '\n')
						break;
				}
				cc = NULL;
				/* If we are inside a false @if block, skip directives
				 * that have side effects (@define, @error, @warning).
				 * We must still process @if/@else/@endif for nesting.
				 */
				if (cc_list &&
				    !preprocessor_resolve_if(cc_list, PREPROCESSOR_PHASE_INITIAL) &&
				    strncmp(start, "@if ", 4) &&
				    strncmp(start, "@else", 5) &&
				    strncmp(start, "@endif", 6))
				{
					linenumber++;
					break;
				}
				n = parse_preprocessor_item(start, ptr, filename, linenumber, &cc);
				linenumber++;
				if (n == PREPROCESSOR_IF)
				{
					preprocessor_level++;
					cc->priority = preprocessor_level;
					AddListItem(cc, cc_list);
				} else
				if (n == PREPROCESSOR_ELSE)
				{
					ConditionalConfig *cc_item;
					if (preprocessor_level == 0)
					{
						config_error("%s:%i: @else unexpected. There was no preceding unclosed @if.",
							filename, linenumber);
						errors++;
					} else
					{
						/* Find the condition at the current nesting level and flip it */
						for (cc_item = cc_list; cc_item; cc_item = cc_item->next)
						{
							if (cc_item->priority == preprocessor_level)
							{
								if (cc_item->had_else)
								{
									config_error("%s:%i: duplicate @else for the same @if block.",
										filename, linenumber);
									errors++;
									goto breakout;
								}
								cc_item->negative = cc_item->negative ? 0 : 1;
								cc_item->had_else = 1;
								break;
							}
						}
					}
				} else
				if (n == PREPROCESSOR_ENDIF)
				{
					if (preprocessor_level == 0)
					{
						config_error("%s:%i: @endif unexpected. There was no preceding unclosed @if.",
							filename, linenumber);
						errors++;
					}
					preprocessor_cc_free_level(&cc_list, preprocessor_level);
					preprocessor_level--;
				} else
				if (n == PREPROCESSOR_ERROR)
				{
					errors++;
					goto breakout;
				} else
				if (n == PREPROCESSOR_USER_ERROR)
				{
					errors++;
				}
				/* PREPROCESSOR_USER_WARNING: nothing to do, message already printed */

				if (!*ptr)
					goto breakout; /* special case, since we don't want the for loop to ptr++ */

				break;
			default:
			processchar:
				if ((*ptr == '*') && (*(ptr+1) == '/'))
				{
					config_status("%s:%i: Ignoring extra end comment", filename, linenumber);
					config_status("WARNING: Starting with UnrealIRCd 4.2.1 a /*-style comment stops as soon as the first */ is encountered. "
					              "See https://www.unrealircd.org/docs/FAQ#Nesting_comments for more information.");
					ptr++;
					break;
				}
				start = ptr;
				for(;*ptr;ptr++)
				{
					if ((*ptr == ' ') /*|| (*ptr == '=')*/ || (*ptr == '\t') || (*ptr == '\n') || (*ptr == ';'))
						break;
				}
				if (!*ptr)
				{
					if (curce)
						config_error("%s: End of file reached but directive or block at line %i did not end properly. "
									 "Perhaps a missing ; (semicolon) somewhere?",
							filename, curce->line_number);
					else if (cursection)
						config_error("%s: End of file reached but the section which starts at line %i did never end properly. "
									 "Perhaps a missing }; ?",
								filename, cursection->section_linenumber);
					else
						config_error("%s: Unexpected end of file. Some line or block did not end properly. "
						             "Look for any missing } and };", filename);
					errors++;
					config_entry_free_all(curce);
					config_free(curcf);
					return NULL;
				}
				if (curce)
				{
					if (curce->value)
					{
						config_error("%s:%i: Extra data detected. Check for a missing ; character at or around line %d",
							filename, linenumber, linenumber-1);
						errors++;
					}
					else
					{
						safe_strldup(curce->value, start, ptr-start+1);
						preprocessor_replace_defines(&curce->value, curce);
					}
				}
				else
				{
					curce = safe_alloc(sizeof(ConfigEntry));
					memset(curce, 0, sizeof(ConfigEntry));
					curce->line_number = linenumber;
					curce->file = curcf;
					curce->parent = cursection;
					curce->file_position_start = (start - confdata);
					safe_strldup(curce->name, start, ptr-start+1);
					preprocessor_replace_defines(&curce->name, curce);
					if (curce->conditional_config)
						abort();
					preprocessor_cc_duplicate_list(cc_list, &curce->conditional_config);
				}
				if ((*ptr == ';') || (*ptr == '\n'))
					ptr--;
				break;
		} /* switch */
		if (!*ptr) /* This IS possible. -- Syzop */
			break;
	} /* for */
breakout:
	if (curce)
	{
		config_error("%s: End of file reached but directive or block at line %i did not end properly. "
		             "Perhaps a missing ; (semicolon) somewhere?",
			filename, curce->line_number);
		errors++;
		config_entry_free_all(curce);
	}
	else if (cursection)
	{
		config_error("%s: End of file reached but the section which starts at line %i did never end properly. "
		             "Perhaps a missing }; ?",
				filename, cursection->section_linenumber);
		errors++;
	}

	if (errors)
	{
		config_free(curcf);
		return NULL;
	}
	return curcf;
}

/** Free a ConfigEntry struct, all it's children, and all it's next entries.
 * Consider calling config_entry_free() instead of this one.. or at least
 * check which one of the two you actually need ;)
 */
void config_entry_free_all(ConfigEntry *ce)
{
	ConfigEntry	*nptr;

	for(;ce;ce=nptr)
	{
		nptr = ce->next;
		if (ce->items)
			config_entry_free_all(ce->items);
		safe_free(ce->name);
		safe_free(ce->value);
		if (ce->conditional_config)
			preprocessor_cc_free_list(ce->conditional_config);
		safe_free(ce);
	}
}

/** Free a specific ConfigEntry struct (and it's children).
 * Caller must ensure that the entry is not in the linked list anymore.
 */
void config_entry_free(ConfigEntry *ce)
{
	if (ce->items)
		config_entry_free_all(ce->items);
	safe_free(ce->name);
	safe_free(ce->value);
	if (ce->conditional_config)
		preprocessor_cc_free_list(ce->conditional_config);
	safe_free(ce);
}

ConfigEntry *config_find_entry(ConfigEntry *ce, const char *name)
{
	ConfigEntry *cep;

	for (cep = ce; cep; cep = cep->next)
		if (cep->name && !strcmp(cep->name, name))
			break;
	return cep;
}

/** Report a configuration error.
 * Used in HOOKTYPE_CONFIGTEST handlers to report issues.
 * @param format	printf-style format string
 * @param ...		Format arguments
 */
void config_error(FORMAT_STRING(const char *format), ...)
{
	va_list		ap;
	char		buffer[1024];
	char		*ptr;

	va_start(ap, format);
	vsnprintf(buffer, sizeof(buffer), format, ap);
	va_end(ap);
	if ((ptr = strchr(buffer, '\n')) != NULL)
		*ptr = '\0';
	unreal_log_raw(ULOG_ERROR, "config", "CONFIG_ERROR_GENERIC", NULL, buffer);
}

/** Report a missing required configuration entry.
 * @param filename	The configuration file name
 * @param line		The line number
 * @param entry		The missing entry (eg "set::something" or "deny link::reason")
 */
void config_error_missing(const char *filename, int line, const char *entry)
{
	config_error("%s:%d: %s is missing", filename, line, entry);
}

/** Report an unknown configuration directive.
 * @param filename	The configuration file name
 * @param line		The line number
 * @param block		The configuration block name
 * @param entry		The unknown directive name
 */
void config_error_unknown(const char *filename, int line, const char *block,
	const char *entry)
{
	config_error("%s:%d: Unknown directive '%s::%s'", filename, line, block, entry);
}

void config_error_unknownflag(const char *filename, int line, const char *block,
	const char *entry)
{
	config_error("%s:%d: Unknown %s flag '%s'", filename, line, block, entry);
}

void config_error_unknownopt(const char *filename, int line, const char *block,
	const char *entry)
{
	config_error("%s:%d: Unknown %s option '%s'", filename, line, block, entry);
}

void config_error_noname(const char *filename, int line, const char *block)
{
	config_error("%s:%d: %s block has no name", filename, line, block);
}

void config_error_blank(const char *filename, int line, const char *block)
{
	config_error("%s:%d: Blank %s entry", filename, line, block);
}

void config_error_empty(const char *filename, int line, const char *block,
	const char *entry)
{
	config_error("%s:%d: %s::%s specified without a value",
		filename, line, block, entry);
}

void config_status(FORMAT_STRING(const char *format), ...)
{
	va_list		ap;
	char		buffer[1024];
	char		*ptr;

	va_start(ap, format);
	vsnprintf(buffer, 1023, format, ap);
	va_end(ap);
	if ((ptr = strchr(buffer, '\n')) != NULL)
		*ptr = '\0';
	unreal_log_raw(ULOG_INFO, "config", "CONFIG_INFO_GENERIC", NULL, buffer);
}

/** Report a non-fatal configuration warning.
 * @param format	printf-style format string
 * @param ...		Format arguments
 */
void config_warn(FORMAT_STRING(const char *format), ...)
{
	va_list		ap;
	char		buffer[1024];
	char		*ptr;

	va_start(ap, format);
	vsnprintf(buffer, 1023, format, ap);
	va_end(ap);
	if ((ptr = strchr(buffer, '\n')) != NULL)
		*ptr = '\0';
	unreal_log_raw(ULOG_WARNING, "config", "CONFIG_WARNING_GENERIC", NULL, buffer);
}

void config_warn_duplicate(const char *filename, int line, const char *entry)
{
	config_warn("%s:%d: Duplicate %s directive", filename, line, entry);
}

/* returns 1 if the test fails */
int config_test_openfile(ConfigEntry *cep, int flags, mode_t mode, const char *entry, int fatal, int allow_url)
{
	int fd;

	if (!cep->value)
	{
		if (fatal)
			config_error("%s:%i: %s: <no file specified>: no file specified",
				     cep->file->filename,
				     cep->line_number,
				     entry);
		else

			config_warn("%s:%i: %s: <no file specified>: no file specified",
				    cep->file->filename,
				    cep->line_number,
				    entry);
		return 1;
	}

	/* There's not much checking that can be done for asynchronously downloaded files */
	if (url_is_valid(cep->value))
	{
		if (allow_url)
			return 0;

		/* but we can check if a URL is used wrongly :-) */
		config_warn("%s:%i: %s: %s: URL used where not allowed",
			    cep->file->filename,
			    cep->line_number,
			    entry, cep->value);
		if (fatal)
			return 1;
		else
			return 0;
	}

	/*
	 * Make sure that files are created with the correct mode. This is
	 * because we don't feel like unlink()ing them...which would require
	 * stat()ing them to make sure that we don't delete existing ones
	 * and that we deal with all of the bugs that come with complexity.
	 * The only files we may be creating are the tunefile and pidfile so far.
	 */
	if (flags & O_CREAT)
		fd = open(cep->value, flags, mode);
	else
		fd = open(cep->value, flags);
	if (fd == -1)
	{
		if (fatal)
			config_error("%s:%i: %s: %s: %s",
				     cep->file->filename,
				     cep->line_number,
				     entry,
				     cep->value,
				     strerror(errno));
		else
			config_warn("%s:%i: %s: %s: %s",
				     cep->file->filename,
				     cep->line_number,
				     entry,
				     cep->value,
				     strerror(errno));
		return 1;
	}
	close(fd);
	return 0;
}

/** Check if a configuration entry has a blank or empty value.
 * If so, report the error via config_error_empty().
 * @param cep		The configuration entry to check
 * @param block		The configuration block name (for the error message)
 * @returns 1 if the value is blank or empty, 0 otherwise
 */
int config_is_blankorempty(ConfigEntry *cep, const char *block)
{
	if (!cep->value)
	{
		config_error_empty(cep->file->filename, cep->line_number, block,
			cep->name);
		return 1;
	}
	return 0;
}

ConfigCommand *config_binary_search(const char *cmd) {
	int start = 0;
	int stop = ARRAY_SIZEOF(_ConfigCommands)-1;
	int mid;
	while (start <= stop) {
		mid = (start+stop)/2;
		if (smycmp(cmd,_ConfigCommands[mid].name) < 0) {
			stop = mid-1;
		}
		else if (strcmp(cmd,_ConfigCommands[mid].name) == 0) {
			return &_ConfigCommands[mid];
		}
		else
			start = mid+1;
	}
	return NULL;
}

/** Free set::best-practices after reading config */
void free_best_practices(void)
{
	memset(&bestpractices, 0, sizeof(bestpractices));
}

/** Initialize and set default values for set::best-practices */
void init_best_practices(void)
{
	memset(&bestpractices, 0, sizeof(bestpractices));
	bestpractices.hashed_passwords = 1;
	bestpractices.trusted_cert = 1;
	bestpractices.trusted_cert_valid_hostname = 1;
	bestpractices.listen_tls_only = 1;
}

void free_iConf(Configuration *i)
{
	FloodSettings *f, *f_next;

	safe_free(i->link_bindip);
	safe_free(i->kline_address);
	safe_free(i->gline_address);
	safe_free(i->oper_snomask);
	safe_free(i->oper_auto_join_chans);
	safe_free(i->oper_vhost);
	safe_free(i->allow_user_stats);
	// allow_user_stats_ext is freed elsewhere
	free_tls_options(i->tls_options);
	i->tls_options = NULL;
	safe_free(i->tls_options);
	safe_free_multiline(i->plaintext_policy_user_message);
	safe_free_multiline(i->plaintext_policy_oper_message);
	safe_free(i->outdated_tls_policy_user_message);
	safe_free(i->outdated_tls_policy_oper_message);
	safe_free(i->restrict_channelmodes);
	safe_free(i->channel_command_prefix);
	safe_free(i->level_on_join);
	safe_free(i->spamfilter_ban_reason);
	safe_free(i->spamfilter_virus_help_channel);
	safe_free_security_group(i->spamfilter_except);
	safe_free(i->spamexcept_line);
	safe_free(i->reject_message_too_many_connections);
	safe_free(i->reject_message_too_many_connections_ipv6_range);
	safe_free(i->reject_message_too_many_new_connections_ipv6_range);
	safe_free(i->reject_message_server_full);
	safe_free(i->reject_message_unauthorized);
	safe_free(i->reject_message_kline);
	safe_free(i->reject_message_gline);
	safe_free(i->network_name);
	safe_free(i->network_name_005);
	safe_free(i->network_icon);
	safe_free(i->default_server);
	safe_free(i->services_name);
	safe_free(i->cloak_prefix);
	safe_free(i->prefix_quit);
	safe_free(i->helpchan);
	safe_free(i->stats_server);
	safe_free(i->sasl_server);
	safe_free_all_ban_actions(i->handshake_data_flood_ban_action);
	safe_free(i->central_spamfilter_url);
	safe_free(i->central_spamfilter_feed);
	free_security_group(i->central_spamfilter_except);
	// anti-flood:
	for (f = i->floodsettings; f; f = f_next)
	{
		f_next = f->next;
		free_floodsettings(f);
	}
	i->floodsettings = NULL;

	free_log_throttle_config(i->log_throttle);
	i->log_throttle = NULL;

	/* And zero out everything, too easy to make a mistake above. */
	memset(i, 0, sizeof(Configuration));
}

/** Set default set { } block settings. Note that some of these settings
 * have been moved to config_setdynamicdefaultsettings() if they use the
 * new dynamic set system which has per-security-group overrides (eg.
 * max-channels-per-user).
 */
void config_setdefaultsettings(Configuration *i)
{
	char tmp[512];

	safe_strdup(i->oper_snomask, OPER_SNOMASKS);
	i->server_notice_colors = 1;
	i->server_notice_show_event = 1;
	i->ident_read_timeout = 7;
	i->ident_connect_timeout = 3;
	i->hide_ban_reason = HIDE_BAN_REASON_AUTO;
	i->ban_version_tkl_time = 86400; /* 1d */
	i->spamfilter_ban_time = 86400; /* 1d */
	i->spamfilter_utf8 = 1;
	safe_strdup(i->spamfilter_ban_reason, "Spam/advertising");
	safe_strdup(i->spamfilter_virus_help_channel, "#help");
	i->spamfilter_detectslow_warn = 250;
	i->spamfilter_detectslow_fatal = 500;
	i->spamfilter_stop_on_first_match = 0;
	i->spamfilter_show_message_content_on_hit = SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_ALWAYS;
	i->maxdccallow = 10;
	safe_strdup(i->channel_command_prefix, "`!.");
	i->check_target_nick_bans = 1;
	i->maxbans = 60;
	safe_strdup(i->level_on_join, "o");
	i->watch_away_notification = 1;
	i->uhnames = 1;
	i->ping_cookie = 1;
	i->ping_warning = 15; /* default ping warning notices 15 seconds */
	i->default_ipv6_clone_mask = 64;
	nicklengths.min = i->min_nick_length = 0; /* 0 means no minimum required */
	nicklengths.max = i->nick_length = NICKLEN;
	i->topic_length = 360;
	i->away_length = 307;
	i->kick_length = 307;
	i->quit_length = 307;
	safe_strdup(i->link_bindip, "*");
	safe_strdup(i->cloak_prefix, "Clk");
	if (!ipv6_capable())
		DISABLE_IPV6 = 1;
	safe_strdup(i->prefix_quit, "Quit");
	i->max_unknown_connections_per_ip = 3;
	i->handshake_timeout = 40;
	i->sasl_timeout = 15;
	i->handshake_delay = -1;
	i->broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_AUTO;

	/* Flood options */
	/* - everyone */
	i->throttle_count = 3; i->throttle_period = 60; /* throttle protection: max 3 per 60s */
	i->handshake_data_flood_amount = 4096;
	i->handshake_data_flood_ban_action = safe_alloc(sizeof(BanAction));
	i->handshake_data_flood_ban_action->action = BAN_ACT_ZLINE;
	i->handshake_data_flood_ban_time = 600;
	// (targetflood is in the targetflood module)
	/* - known-users */
	config_parse_flood_generic("3:60", i, "known-users", FLD_NICK); /* NICK flood protection: max 3 per 60s */
	config_parse_flood_generic("3:90", i, "known-users", FLD_JOIN); /* JOIN flood protection: max 3 per 90s */
	config_parse_flood_generic("3:90", i, "known-users", FLD_VHOST); /* MODE -x flood protection: max 3 per 90s */
	config_parse_flood_generic("4:120", i, "known-users", FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
	config_parse_flood_generic("4:60", i, "known-users", FLD_INVITE); /* INVITE flood protection: max 4 per 60s */
	config_parse_flood_generic("4:120", i, "known-users", FLD_KNOCK); /* KNOCK protection: max 4 per 120s */
	config_parse_flood_generic("10:15", i, "known-users", FLD_CONVERSATIONS); /* 10 users, new user every 15s */
	config_parse_flood_generic("180:750", i, "known-users", FLD_LAG_PENALTY); /* 180 bytes / 750 msec */
	config_parse_flood_generic("15:5250", i, "known-users", FLD_MULTILINE); /* max-lines=15, max-bytes=5250 */
	/* - unknown-users */
	config_parse_flood_generic("2:60", i, "unknown-users", FLD_NICK); /* NICK flood protection: max 2 per 60s */
	config_parse_flood_generic("2:90", i, "unknown-users", FLD_JOIN); /* JOIN flood protection: max 2 per 90s */
	config_parse_flood_generic("2:90", i, "unknown-users", FLD_VHOST); /* MODE -x flood protection: max 2 per 90s */
	config_parse_flood_generic("4:120", i, "unknown-users", FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
	config_parse_flood_generic("2:60", i, "unknown-users", FLD_INVITE); /* INVITE flood protection: max 2 per 60s */
	config_parse_flood_generic("2:120", i, "unknown-users", FLD_KNOCK); /* KNOCK protection: max 2 per 120s */
	config_parse_flood_generic("4:15", i, "unknown-users", FLD_CONVERSATIONS); /* 4 users, new user every 15s */
	config_parse_flood_generic("90:1000", i, "unknown-users", FLD_LAG_PENALTY); /* 90 bytes / 1000 msec */
	config_parse_flood_generic("7:1500", i, "unknown-users", FLD_MULTILINE); /* max-lines=7, max-bytes=1500 */

	add_log_throttle_config(&i->log_throttle, "CONNTHROTTLE_IPV6_LIMIT", 100, 60, 0);
	add_log_throttle_config(&i->log_throttle, "MAXPERIP_LIMIT", 100, 60, 0);
	add_log_throttle_config(&i->log_throttle, "BUG_CT_CHECK_DRIFT", 5, 60, 0);
	add_log_throttle_config(&i->log_throttle, "BUG_CT_CHECK_NO_BUCKET", 5, 60, 0);
	add_log_throttle_config(&i->log_throttle, "BUG_CT_CHECK_NO_IP", 5, 60, 0);
	add_log_throttle_config(&i->log_throttle, "BUG_CT_BUCKET_MISSING", 5, 60, 0);
	add_log_throttle_config(&i->log_throttle, "BUG_CT_NEGATIVE_COUNTER", 5, 60, 0);
	add_log_throttle_config(&i->log_throttle, "BUG_DECREASE_IPUSERS_BUCKET", 5, 60, 0);

	/* TLS options */
	i->tls_options = safe_alloc(sizeof(TLSOptions));
	snprintf(tmp, sizeof(tmp), "%s/tls/curl-ca-bundle.crt", CONFDIR);
	safe_strdup(i->tls_options->trusted_ca_file, tmp);
	safe_strdup(i->tls_options->ciphers, UNREALIRCD_DEFAULT_CIPHERS);
	safe_strdup(i->tls_options->ciphersuites, UNREALIRCD_DEFAULT_CIPHERSUITES);
	i->tls_options->protocols = TLS_PROTOCOL_TLSV1_2|TLS_PROTOCOL_TLSV1_3; /* TLSv1.2 & TLSv1.3 */
	safe_strdup(i->tls_options->outdated_protocols, "TLSv1,TLSv1.1");
	/* the following may look strange but "AES*" matches all
	 * AES ciphersuites that do not have Forward Secrecy.
	 * Any decent client using AES will use ECDHE-xx-AES.
	 */
	safe_strdup(i->tls_options->outdated_ciphers, "AES*,RC4*,DES*");
	i->tls_options->certificate_expiry_notification = 1;
	i->plaintext_policy_user = POLICY_ALLOW;
	i->plaintext_policy_oper = POLICY_DENY;
	i->plaintext_policy_server = POLICY_DENY;

	i->outdated_tls_policy_user = POLICY_WARN;
	i->outdated_tls_policy_oper = POLICY_DENY;
	i->outdated_tls_policy_server = POLICY_DENY;

	safe_strdup(i->reject_message_too_many_connections, "Too many connections from your IP [maxperip]");
	safe_strdup(i->reject_message_too_many_connections_ipv6_range, "Too many connections from your IPv6 range ($prefix_addr/$prefix_len) [maxperip]");
	safe_strdup(i->reject_message_too_many_new_connections_ipv6_range, "Too many new connections from this IPv6 range ($prefix_addr/$prefix_len) [connthrottle]");
	safe_strdup(i->reject_message_server_full, "This server is full");
	safe_strdup(i->reject_message_unauthorized, "You are not authorized to connect to this server");
	safe_strdup(i->reject_message_kline, "You are not welcome on this server. $bantype: $banreason. Email $klineaddr for more information.");
	safe_strdup(i->reject_message_gline, "You are not welcome on this network. $bantype: $banreason. Email $glineaddr for more information.");

	i->topic_setter = SETTER_NICK_USER_HOST;
	i->ban_setter = SETTER_NICK_USER_HOST;
	i->ban_setter_sync = 1;
	i->allowed_channelchars = ALLOWED_CHANNELCHARS_UTF8;
	i->automatic_ban_target = BAN_TARGET_IP;
	i->manual_ban_target = BAN_TARGET_HOST;
	i->hide_idle_time = HIDE_IDLE_TIME_OPER_USERMODE;
	i->who_limit = 100;
	i->named_extended_bans = 1;
	i->high_connection_rate = 1000;
	safe_strdup(i->central_spamfilter_url, DEFAULT_CENTRAL_SPAMFILTER_URL_OPEN_ACCESS);
	safe_strdup(i->central_spamfilter_feed, DEFAULT_CENTRAL_SPAMFILTER_FEED);
	i->central_spamfilter_refresh_time = 3600;
	i->central_spamfilter_enabled = 0;
	i->central_spamfilter_except = safe_alloc(sizeof(SecurityGroup));
	i->central_spamfilter_except->reputation_score = 2016; /* 7 days unregged, or 3.5 days identified */
	unreal_add_mask_string(&i->central_spamfilter_except->mask, "*.irccloud.com");
	i->dns_client_timeout = DNS_DEFAULT_CLIENT_TIMEOUT;
	i->dns_client_retry = DNS_DEFAULT_CLIENT_RETRIES;
	i->dns_dnsbl_timeout = DNS_DEFAULT_DNSBL_TIMEOUT;
	i->dns_dnsbl_retry = DNS_DEFAULT_DNSBL_RETRIES;
	i->send_isupport_updates = 0; /* Off for now, will be turned on by default later after more testing */
	i->allow_setident = 0;
	i->allow_setname = 1;
}

/* Some settings have been moved to here - we (re)set some defaults */
void config_setdynamicdefaultsettings()
{
	DynamicSetBlock *s;
	/* Free/reset stuff first: */
	free_dynamic_set_block(&dynamic_set);
	free_dynamic_set_block(&unknown_users_set);
	s = &dynamic_set;
	/* Set defaults: */
	dynamic_set_number(s, SET_MAX_CHANNELS_PER_USER, 10);
	dynamic_set_number(s, SET_MODES_ON_CONNECT, set_usermode("+ixw"));
}

/** Similar to config_setdefaultsettings but this one is applied *AFTER*
 * the entire configuration has been ran (sometimes this is the only way it can be done..).
 * NOTE: iConf is thus already populated with (non-default) values. Only overwrite if necessary!
 */
void postconf_defaults(void)
{
	TKL *tk;
	char *encoded;
	FloodSettings *fld;

	if (!iConf.modes_on_join_set)
	{
		/* We could not do this in config_setdefaultsettings()
		 * because the channel mode modules were not initialized yet.
		 */
		conf_channelmodes("+nt", &iConf.modes_on_join);
	}
	if (!iConf.plaintext_policy_user_message)
	{
		/* The message depends on whether it's reject or warn.. */
		if (iConf.plaintext_policy_user == POLICY_DENY)
			addmultiline(&iConf.plaintext_policy_user_message, "Insecure connection. Please reconnect using TLS.");
		else if (iConf.plaintext_policy_user == POLICY_WARN)
			addmultiline(&iConf.plaintext_policy_user_message, "WARNING: Insecure connection. Please consider using TLS.");
	}

	if (!iConf.plaintext_policy_oper_message)
	{
		/* The message depends on whether it's reject or warn.. */
		if (iConf.plaintext_policy_oper == POLICY_DENY)
		{
			addmultiline(&iConf.plaintext_policy_oper_message, "You need to use a secure connection (TLS) in order to /OPER.");
			addmultiline(&iConf.plaintext_policy_oper_message, "See https://www.unrealircd.org/docs/FAQ#oper-requires-tls");
		}
		else if (iConf.plaintext_policy_oper == POLICY_WARN)
			addmultiline(&iConf.plaintext_policy_oper_message, "WARNING: You /OPER'ed up from an insecure connection. Please consider using TLS.");
	}

	if (!iConf.outdated_tls_policy_user_message)
	{
		/* The message depends on whether it's reject or warn.. */
		if (iConf.outdated_tls_policy_user == POLICY_DENY)
			safe_strdup(iConf.outdated_tls_policy_user_message, "Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
		else if (iConf.outdated_tls_policy_user == POLICY_WARN)
			safe_strdup(iConf.outdated_tls_policy_user_message, "WARNING: Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
	}

	if (!iConf.outdated_tls_policy_oper_message)
	{
		/* The message depends on whether it's reject or warn.. */
		if (iConf.outdated_tls_policy_oper == POLICY_DENY)
			safe_strdup(iConf.outdated_tls_policy_oper_message, "Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
		else if (iConf.outdated_tls_policy_oper == POLICY_WARN)
			safe_strdup(iConf.outdated_tls_policy_oper_message, "WARNING: Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
	}

	postconf_defaults_log_block();
}

/* Needed for set::options::allow-part-if-shunned,
 * we can't just make it CMD_SHUN and do a ALLOW_PART_IF_SHUNNED in
 * cmd_part itself because that will also block internal calls (like sapart). -- Syzop
 */
static void do_weird_shun_stuff()
{
RealCommand *cmptr;

	if ((cmptr = find_command_simple("PART")))
	{
		if (ALLOW_PART_IF_SHUNNED)
			cmptr->flags |= CMD_SHUN;
		else
			cmptr->flags &= ~CMD_SHUN;
	}
}

/** Various things that are done at the very end after the configuration file
 * has been read and almost all values have been set. This is to deal with
 * things like adding a default log { } block if there is none and that kind
 * of things.
 * This function is called by config_test(), both on boot and on rehash.
 */
void postconf(void)
{
	postconf_defaults();
	do_weird_shun_stuff();
	isupport_init(); /* for all the 005 values that changed.. */
	tls_check_expiry(NULL);

#if OPENSSL_VERSION_NUMBER >= 0x10101000L
	if (loop.rehashing)
		reinit_tls();
#endif
	if (bestpractices.trusted_cert && has_client_port)
	{
		if (!has_any_trusted_cert())
		{
			unreal_log(ULOG_ADVICE, "config", "BEST_PRACTICES_TRUSTED_CERT", NULL,
				   "You don't have any valid SSL/TLS certificate that is issued by a trusted Certificate Authority.\n"
				   "It is highly recommended to use a 'real certificate'. To get a free one, see: "
				   "https://www.unrealircd.org/docs/Using_Let's_Encrypt_with_UnrealIRCd");
			bestpractices.trusted_cert_hits++;
		} else
		if (bestpractices.trusted_cert_valid_hostname && !has_any_trusted_cert_with_correct_hostname())
		{
			unreal_log(ULOG_ADVICE, "config", "BEST_PRACTICES_TRUSTED_CERT_VALID_HOSTNAME", NULL,
			           "You have an SSL/TLS certificate that is issued by a trusted Certificate Authority "
			           "(which is good). However, it is not valid for hostname '$servername'. "
			           "It is recommended for the certificate (or at least one of them) to be "
			           "valid for your server name, for clients that connect to that name. ",
			           log_data_string("servername", me.name));
			bestpractices.trusted_cert_valid_hostname_hits++;
		}
	}
	if (bestpractices.listen_tls_only &&
	    bestpractices.listen_nontls_port &&
	    (iConf.plaintext_policy_user != POLICY_DENY))
	{
		unreal_log(ULOG_ADVICE, "config", "BEST_PRACTICES_PLAINTEXT_PORT", NULL,
		           "You have at least one IRC plaintext port open to users (such as $port). "
		           "Nowadays, everyone should be using SSL/TLS (on port 6697). "
		           "See https://www.unrealircd.org/docs/Use_TLS.",
		           log_data_integer("port", bestpractices.listen_nontls_port));
		bestpractices.listen_nontls_port_hits++;
	}
}

int isanyserverlinked(void)
{
	return !list_empty(&server_list);
}

void applymeblock(void)
{
	if (!conf_me)
		return; /* uh-huh? */

	/* Info text may always change, just wouldn't show up on other servers, that's all.. */
	strlcpy(me.info, conf_me->info, sizeof(me.info));

	/* Name can only be set once (on boot) */
	if (!*me.name)
		strlcpy(me.name, conf_me->name, sizeof(me.name));
	else if (strcmp(me.name, conf_me->name))
	{
		config_warn("You changed the servername (me::name). "
		            "This change will NOT be effective unless you restart the IRC Server.");
	}

	if (!*me.id)
		strlcpy(me.id, conf_me->sid, sizeof(me.id));
}

/** Run config test and all post config tests. */
int config_test_all(void)
{
	if ((efunctions_check() < 0) || (config_test_blocks() < 0) || (callbacks_check() < 0) || 
	    reloadable_perm_module_unloaded() || !tls_tests() || !log_tests())
	{
		return 0;
	}

	special_delayed_unloading();

	return 1;
}

/** Process all loadmodule directives in all includes.
 * This was previously done at the same time as 'include' was called but
 * that was too early now that we have blacklist-module, so moved here.
 * @retval 1 on success, 0 on any failed loadmodule directive.
 */
int config_loadmodules(void)
{
	ConfigFile *cfptr;
	ConfigEntry *ce;
	ConfigItem_blacklist_module *blm, *blm_next;

	int fatal_ret = 0, ret;

	for (cfptr = conf; cfptr; cfptr = cfptr->next)
	{
		if (config_verbose > 1)
			config_status("Testing %s", cfptr->filename);
		for (ce = cfptr->items; ce; ce = ce->next)
		{
			if (!strcmp(ce->name, "loadmodule"))
			{
				ConditionalConfig *cc;
				for (cc = ce->conditional_config; cc; cc = cc->next)
				{
					if (cc->condition == IF_MODULE_LOADED)
					{
						config_error("%s:%d: You cannot use @if module-loaded() around a 'loadmodule' statement.",
							     ce->file->filename, ce->line_number);
						return 0;
					}
				}
				ret = _conf_loadmodule(cfptr, ce);
				if (ret < fatal_ret)
					fatal_ret = ret; /* lowest wins */
			}
		}
	}

	/* Let's free the blacklist-module list here as well */
	for (blm = conf_blacklist_module; blm; blm = blm_next)
	{
		blm_next = blm->next;
		safe_free(blm->name);
		safe_free(blm);
	}
	conf_blacklist_module = NULL;
	/* End of freeing code */

	/* If any loadmodule returned a fatal (-1) error code then we return fail status (0) */
	if (fatal_ret < 0)
		return 0; /* FAIL */
	return 1; /* SUCCESS */
}

/** Reject the configuration load.
 * This is called both from boot and from rehash.
 */
void config_load_failed(void)
{
	if (conf)
		unreal_log(ULOG_ERROR, "config", "CONFIG_NOT_LOADED", NULL, "IRCd configuration failed to load");
	loop.config_status = CONFIG_STATUS_ROLLBACK;
	loop.rehashing = 0;
	Unload_all_testing_modules();
	free_all_config_resources();
	config_free(conf);
	conf = NULL;
	free_iConf(&tempiConf);
	free_best_practices();
#ifdef _WIN32
	if (!loop.rehashing)
		win_error(); /* GUI popup */
#endif
}

int config_read_start(void)
{
	int ret;

	config_status("Loading IRCd configuration..");
	loop.config_load_failed = 0;

	if (conf)
	{
		config_error("%s:%i - Someone forgot to clean up", __FILE__, __LINE__);
		return -1;
	}

	init_config_defines();

	/* We set this to 1 because otherwise we may call rehash_internal()
	 * already from config_read_file() which is too soon (race).
	 */
	loop.rehash_download_busy = 1;
	add_config_resource(configfile, RESOURCE_INCLUDE, NULL);
	ret = config_read_file(configfile, configfile);
	loop.rehash_download_busy = 0;
	if (ret < 0)
	{
		config_load_failed();
		return -1;
	}
	return 1;
}

int is_config_read_finished(void)
{
	ConfigResource *rs;

	if (loop.rehash_download_busy)
		return 0;

	for (rs = config_resources; rs; rs = rs->next)
	{
		if (rs->type & RESOURCE_DLQUEUED)
		{
			//config_status("Waiting for %s...", rs->url);
			return 0;
		}
	}

	return 1;
}

int config_test(void)
{
	char *old_pid_file = NULL;

	if (loop.config_load_failed)
	{
		/* An error was already printed to the user.
		 * This happens in case of a failed loaded remote URL
		 */
		config_load_failed();
		return -1;
	}

	config_status("Testing IRCd configuration..");
	loop.config_status = CONFIG_STATUS_TEST;

	memset(&tempiConf, 0, sizeof(iConf));
	init_best_practices();
	memset(&settings, 0, sizeof(settings));
	memset(&requiredstuff, 0, sizeof(requiredstuff));
	memset(&nicklengths, 0, sizeof(nicklengths));
	config_setdefaultsettings(&tempiConf);
	clicap_pre_rehash();
	log_pre_rehash();
	has_client_port = 0;

	if (!config_loadmodules())
	{
		config_load_failed();
		return -1;
	}

	loop.config_status = CONFIG_STATUS_POSTTEST;

	preprocessor_resolve_conditionals_all(PREPROCESSOR_PHASE_MODULE);

	if (!config_test_all())
	{
		config_error("IRCd configuration failed to pass testing");
		config_load_failed();
		return -1;
	}
	loop.config_status = CONFIG_STATUS_PRE_INIT;
	callbacks_switchover();
	efunctions_switchover();
	set_targmax_defaults();
	set_security_group_defaults();
	if (loop.rehashing)
	{
		Hook *h;
		safe_strdup(old_pid_file, conf_files->pid_file);
		unrealdns_delasyncconnects();
		config_rehash();
		/* Notify permanent modules of the rehash */
		for (h = Hooks[HOOKTYPE_REHASH]; h; h = h->next)
		{
			if (!h->owner)
				continue;
			if (!(h->owner->options & MOD_OPT_PERM))
				continue;
			(*(h->func.intfunc))();
		}
		/* Last step: */
		Unload_all_loaded_modules();
	}
	config_pre_run_log();

	loop.config_status = CONFIG_STATUS_INIT;
	Init_all_testing_modules();

	loop.config_status = CONFIG_STATUS_RUN_CONFIG;
	config_setdynamicdefaultsettings();
	if (config_run_blocks() < 0)
	{
		config_error("Bad case of config errors. Server will now die. This really shouldn't happen");
#ifdef _WIN32
		if (!loop.rehashing)
			win_error();
#endif
		abort();
	}

	applymeblock();

	if (old_pid_file && strcmp(old_pid_file, conf_files->pid_file))
	{
		write_pidfile();
		unlink(old_pid_file);
	}
	safe_free(old_pid_file);

	config_free(conf);
	conf = NULL;
	if (loop.rehashing)
	{
		/* loop.config_status = CONFIG_STATUS_LOAD is done by module_loadall() */
		module_loadall();
		RunHook(HOOKTYPE_REHASH_COMPLETE);
	} else
	if (!loop.booted)
	{
		/* This was moved from src/ircd.c to here, since we need TLS initialized
		 * before we run the best practices tests in postconf().
		 */
		if (!init_tls())
		{
			config_error("Failed to load TLS (see errors above). UnrealIRCd can not start.");
			config_load_failed();
			return -1;
		}
	}
	loop.config_status = CONFIG_STATUS_POSTLOAD;
	postconf();
	unreal_log(ULOG_INFO, "config", "CONFIG_LOADED", NULL, "Configuration loaded");
	if (bestpractices.hashed_passwords_hits ||
	    bestpractices.trusted_cert_hits ||
	    bestpractices.trusted_cert_valid_hostname_hits ||
	    bestpractices.listen_nontls_port_hits)
	{
		unreal_log(ULOG_ADVICE, "config", "BEST_PRACTICES", NULL,
		           "Your config has NO errors, but you received some best practices tips above.");
		if (bestpractices.hashed_passwords_hits)
		{
			unreal_log(ULOG_ADVICE, "config", "BEST_PRACTICES_HASHED_PASSWORDS_INFO", NULL,
			           "* Use hashed passwords, see https://www.unrealircd.org/docs/Authentication_types "
			           "to learn more about this.");
		}
		unreal_log(ULOG_ADVICE, "config", "BEST_PRACTICES_POST_INFO", NULL,
		           "It is recommended for you to follow best practices, but if you want to hide "
		           "such suggestions see "
		           "https://www.unrealircd.org/docs/Set_block#set::best-practices");
	}
	unload_all_unused_mtag_handlers();
	free_best_practices();
	return 0;
}

void config_parse_and_queue_urls(ConfigEntry *ce)
{
	for (; ce; ce = ce->next)
	{
		if (loop.config_load_failed)
			break;
		if (ce->name && !strcmp(ce->name, "include"))
			continue; /* handled elsewhere */
		if (ce->value && !ce->escaped && url_is_valid(ce->value))
			add_config_resource(ce->value, 0, ce);
		if (ce->items)
			config_parse_and_queue_urls(ce->items);
	}
}

/**
 * Read configuration file into ConfigEntry items and add it to the 'conf'
 * list. This checks the file for parse errors, but doesn't do much
 * otherwise. Only: module blacklist checking and checking for "include"
 * items to see if we need to read and parse more configuration files
 * that are included from this one.
 *
 * One _must_ call add_config_resource() before calling config_read_file().
 * This way, include recursion may be detected and reported to the user
 * as an error instead of causing the IRCd to hang in an infinite
 * recursion, eat up memory, and eventually overflow its stack ;-).
 *
 * @param filename the file where the conf may be read from
 * @param display_name The path or URL used to refer to this file.
 *        (mostly to support remote includes' URIs for recursive include detection).
 * @return 1 on success, a negative number on error
 */
int config_read_file(const char *filename, const char *display_name)
{
	ConfigFile 	*cfptr, *cfptr2, **cfptr3;
	ConfigEntry 	*ce;
	ConfigResource *rs;
	int ret;
	int counter;

	if (config_verbose > 0)
		config_status("Loading config file %s ..", filename);

	need_operclass_permissions_upgrade = 0;

	/* Check if we're accidentally including a file a second
	 * time. We should expect to find one entry in this list: the
	 * entry for our current file.
	 * Note that no user should be able to trigger this, this
	 * can only happen if we have buggy code somewhere.
	 */
	counter = 0;
	for (rs = config_resources; rs; rs = rs->next)
	{
#ifndef _WIN32
		if (rs->file && !strcmp(filename, rs->file))
#else
		if (rs->file && !strcasecmp(filename, rs->file))
#endif
		{
			counter ++;
			continue;
		}
		if (rs->url && !strcmp(display_name, rs->url))
		{
			counter ++;
			continue;
		}
	}
	if (counter > 1)
	{
		unreal_log(ULOG_ERROR, "config", "CONFIG_BUG_DUPLICATE_RESOURCE", NULL,
		           "[BUG] Config file $file has been loaded $counter times. "
		           "This should not happen. Someone forgot to call "
		           "add_config_resource() or check its return value!",
		           log_data_string("file", filename),
		           log_data_integer("counter", counter));
		return -1;
	}
	/* end include recursion checking code */

	if ((cfptr = config_load(filename, display_name)))
	{
		for (cfptr3 = &conf, cfptr2 = conf; cfptr2; cfptr2 = cfptr2->next)
			cfptr3 = &cfptr2->next;
		*cfptr3 = cfptr;

		if (config_verbose > 1)
			config_status("Loading module blacklist in %s", filename);

		preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_INITIAL);

		for (ce = cfptr->items; ce; ce = ce->next)
			if (!strcmp(ce->name, "blacklist-module"))
				 _test_blacklist_module(cfptr, ce);

		preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_SECONDARY);

		/* Load urls */
		config_parse_and_queue_urls(cfptr->items);

		if(loop.config_load_failed) /* something bad happened while processing urls */
			return -1;

		/* Load includes */
		if (config_verbose > 1)
			config_status("Searching through %s for include files..", filename);

		for (ce = cfptr->items; ce; ce = ce->next)
		{
			if (!strcmp(ce->name, "include"))
			{
				if (ce->conditional_config)
				{
					config_error("%s:%d: Currently you cannot have an 'include' statement "
					             "within an @if block, sorry. However, you CAN do it the other "
					             "way around, that is: put the @if within the included file itself.",
					             ce->file->filename, ce->line_number);
					return -1;
				}
				if (!config_item_allowed_for_config_file(cfptr->filename, NULL))
				{
					config_error("%s:%d: You cannot use 'include' from a restricted config file.",
					             ce->file->filename, ce->line_number);
					return -1;
				}
				ret = _conf_include(cfptr, ce);
				if (ret < 0)
					return ret;
			}
		}
		return 1;
	}
	else
	{
		unreal_log(ULOG_ERROR, "config", "CONFIG_LOAD_FILE_FAILED", NULL,
		           "Could not load configuration file: $resource",
		           log_data_string("resource", display_name),
		           log_data_string("filename", filename));
#ifdef _WIN32
		if (!strcmp(filename, "conf/unrealircd.conf"))
		{
			if (file_exists("unrealircd.conf"))
			{
				config_error("Note that 'unrealircd.conf' now belongs in the 'conf' subdirectory! (So move it to there)");
			} else {
				config_error("New to UnrealIRCd? Be sure to read https://www.unrealircd.org/docs/Installing_%%28Windows%%29");
			}
		}
#endif
		return -1;
	}
}

/** Remove all TKL's that were added by the config file(s).
 * This is done after config passed testing and right before
 * adding the (new) entries.
 */
void remove_config_tkls(int flag)
{
	TKL *tk, *tk_next;
	int index, index2;

	/* IP hashed TKL list */
	for (index = 0; index < TKLIPHASHLEN1; index++)
	{
		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
		{
			for (tk = tklines_ip_hash[index][index2]; tk; tk = tk_next)
			{
				tk_next = tk->next;
				if (tk->flags & flag)
					tkl_del_line(tk);
			}
		}
	}

	/* Generic TKL list */
	for (index = 0; index < TKLISTLEN; index++)
	{
		for (tk = tklines[index]; tk; tk = tk_next)
		{
			tk_next = tk->next;
			if (tk->flags & flag)
				tkl_del_line(tk);
		}
	}
}

void free_proxy_block(ConfigItem_proxy *e)
{
	free_security_group(e->mask);
	if (e->auth)
		Auth_FreeAuthConfig(e->auth);
	DelListItem(e, conf_proxy);
	safe_free(e->name);
	safe_free(e);
}

void free_all_proxy_blocks(void)
{
	ConfigItem_proxy *proxy_ptr, *next;

	for (proxy_ptr = conf_proxy; proxy_ptr; proxy_ptr = next)
	{
		next = proxy_ptr->next;
		free_proxy_block(proxy_ptr);
	}
	conf_proxy = NULL;
}

void config_rehash()
{
	ConfigItem_oper			*oper_ptr;
	ConfigItem_operclass		*operclass_ptr;
	ConfigItem_class 		*class_ptr;
	ConfigItem_ulines 		*uline_ptr;
	ConfigItem_allow 		*allow_ptr;
	ConfigItem_ban 			*ban_ptr;
	ConfigItem_link 		*link_ptr;
	ConfigItem_listen	 	*listen_ptr;
	ConfigItem_tld			*tld_ptr;
	ConfigItem_deny_channel		*deny_channel_ptr;
	ConfigItem_allow_channel	*allow_channel_ptr;
	ConfigItem_admin		*admin_ptr;
	ConfigItem_deny_version		*deny_version_ptr;
	ConfigItem_alias		*alias_ptr;
	ConfigItem_help			*help_ptr;
	ConfigItem_offchans		*of_ptr;
	ConfigItem_sni			*sni;
	OperStat 			*os_ptr;
	ListStruct 	*next, *next2;

	USE_BAN_VERSION = 0;

	for (admin_ptr = conf_admin; admin_ptr; admin_ptr = (ConfigItem_admin *)next)
	{
		next = (ListStruct *)admin_ptr->next;
		safe_free(admin_ptr->line);
		DelListItem(admin_ptr, conf_admin);
		safe_free(admin_ptr);
	}

	for (oper_ptr = conf_oper; oper_ptr; oper_ptr = (ConfigItem_oper *)next)
	{
		SWhois *s, *s_next;
		next = (ListStruct *)oper_ptr->next;
		safe_free(oper_ptr->name);
		safe_free(oper_ptr->snomask);
		safe_free(oper_ptr->operclass);
		safe_free(oper_ptr->vhost);
		safe_free(oper_ptr->auto_join);
		Auth_FreeAuthConfig(oper_ptr->auth);
		free_security_group(oper_ptr->match);
		for (s = oper_ptr->swhois; s; s = s_next)
		{
			s_next = s->next;
			safe_free(s->line);
			safe_free(s->setby);
			safe_free(s);
		}
		DelListItem(oper_ptr, conf_oper);
		safe_free(oper_ptr);
	}

	for (operclass_ptr = conf_operclass; operclass_ptr; operclass_ptr = (ConfigItem_operclass *)next)
	{
		next = (ListStruct *)operclass_ptr->next;
		free_operclass_struct(operclass_ptr->classStruct);
		DelListItem(operclass_ptr, conf_operclass);
		safe_free(operclass_ptr);
	}

	for (link_ptr = conf_link; link_ptr; link_ptr = (ConfigItem_link *) next)
	{
		next = (ListStruct *)link_ptr->next;
		if (link_ptr->refcount == 0)
		{
			delete_linkblock(link_ptr);
		}
		else
		{
			link_ptr->flag.temporary = 1;
		}
	}
	for (class_ptr = conf_class; class_ptr; class_ptr = (ConfigItem_class *) next)
	{
		next = (ListStruct *)class_ptr->next;
		if (class_ptr->flag.permanent == 1)
			continue;
		class_ptr->flag.temporary = 1;
		/* We'll wipe it out when it has no clients */
		if (!class_ptr->clients && !class_ptr->xrefcount)
		{
			delete_classblock(class_ptr);
		}
	}
	for (uline_ptr = conf_ulines; uline_ptr; uline_ptr = (ConfigItem_ulines *) next)
	{
		next = (ListStruct *)uline_ptr->next;
		/* We'll wipe it out when it has no clients */
		safe_free(uline_ptr->servername);
		DelListItem(uline_ptr, conf_ulines);
		safe_free(uline_ptr);
	}
	for (allow_ptr = conf_allow; allow_ptr; allow_ptr = (ConfigItem_allow *) next)
	{
		next = (ListStruct *)allow_ptr->next;
		free_security_group(allow_ptr->match);
		Auth_FreeAuthConfig(allow_ptr->auth);
		safe_free(allow_ptr->server);
		DelListItem(allow_ptr, conf_allow);
		safe_free(allow_ptr);
	}
	/* Free ban realname { }, ban server { } and ban version { } */
	for (ban_ptr = conf_ban; ban_ptr; ban_ptr = (ConfigItem_ban *) next)
	{
		next = (ListStruct *)ban_ptr->next;
		if (ban_ptr->flag.type2 == CONF_BAN_TYPE_CONF || ban_ptr->flag.type2 == CONF_BAN_TYPE_TEMPORARY)
		{
			safe_free(ban_ptr->mask);
			safe_free(ban_ptr->reason);
			free_all_ban_actions(ban_ptr->action);
			DelListItem(ban_ptr, conf_ban);
			safe_free(ban_ptr);
		}
	}
	for (listen_ptr = conf_listen; listen_ptr; listen_ptr = listen_ptr->next)
	{
		if (!(listen_ptr->options & LISTENER_CONTROL))
			listen_ptr->flag.temporary = 1;
	}
	for (tld_ptr = conf_tld; tld_ptr; tld_ptr = (ConfigItem_tld *) next)
	{
		next = (ListStruct *)tld_ptr->next;
		safe_free(tld_ptr->motd_file);
		safe_free(tld_ptr->rules_file);
		safe_free(tld_ptr->smotd_file);
		safe_free(tld_ptr->opermotd_file);
		safe_free(tld_ptr->botmotd_file);

		free_motd(&tld_ptr->motd);
		free_motd(&tld_ptr->rules);
		free_motd(&tld_ptr->smotd);
		free_motd(&tld_ptr->opermotd);
		free_motd(&tld_ptr->botmotd);

		free_security_group(tld_ptr->match);
		safe_free(tld_ptr->channel);

		DelListItem(tld_ptr, conf_tld);
		safe_free(tld_ptr);
	}

	remove_config_tkls(TKL_FLAG_CONFIG);

	for (deny_version_ptr = conf_deny_version; deny_version_ptr; deny_version_ptr = (ConfigItem_deny_version *) next) {
		next = (ListStruct *)deny_version_ptr->next;
		safe_free(deny_version_ptr->mask);
		safe_free(deny_version_ptr->version);
		safe_free(deny_version_ptr->flags);
		DelListItem(deny_version_ptr, conf_deny_version);
		safe_free(deny_version_ptr);
	}
	for (deny_channel_ptr = conf_deny_channel; deny_channel_ptr; deny_channel_ptr = (ConfigItem_deny_channel *) next)
	{
		next = (ListStruct *)deny_channel_ptr->next;
		safe_free(deny_channel_ptr->redirect);
		safe_free(deny_channel_ptr->channel);
		safe_free(deny_channel_ptr->reason);
		safe_free(deny_channel_ptr->class);
		DelListItem(deny_channel_ptr, conf_deny_channel);
		free_security_group(deny_channel_ptr->match);
		safe_free(deny_channel_ptr);
	}

	for (allow_channel_ptr = conf_allow_channel; allow_channel_ptr; allow_channel_ptr = (ConfigItem_allow_channel *) next)
	{
		next = (ListStruct *)allow_channel_ptr->next;
		safe_free(allow_channel_ptr->channel);
		safe_free(allow_channel_ptr->class);
		DelListItem(allow_channel_ptr, conf_allow_channel);
		free_security_group(allow_channel_ptr->match);
		safe_free(allow_channel_ptr);
	}

	if (conf_drpass)
	{
		Auth_FreeAuthConfig(conf_drpass->restartauth);
		conf_drpass->restartauth = NULL;
		Auth_FreeAuthConfig(conf_drpass->dieauth);
		conf_drpass->dieauth = NULL;
		safe_free(conf_drpass);
	}
	for (alias_ptr = conf_alias; alias_ptr; alias_ptr = (ConfigItem_alias *)next) {
		RealCommand *cmptr = find_command(alias_ptr->alias, 0);
		ConfigItem_alias_format *fmt;
		next = (ListStruct *)alias_ptr->next;
		safe_free(alias_ptr->nick);
		if (cmptr)
			CommandDelX(NULL, cmptr);
		safe_free(alias_ptr->alias);
		if (alias_ptr->format && (alias_ptr->type == ALIAS_COMMAND)) {
			for (fmt = (ConfigItem_alias_format *) alias_ptr->format; fmt; fmt = (ConfigItem_alias_format *) next2)
			{
				next2 = (ListStruct *)fmt->next;
				safe_free(fmt->format);
				safe_free(fmt->nick);
				safe_free(fmt->parameters);
				unreal_delete_match(fmt->expr);
				DelListItem(fmt, alias_ptr->format);
				safe_free(fmt);
			}
		}
		DelListItem(alias_ptr, conf_alias);
		safe_free(alias_ptr);
	}
	for (help_ptr = conf_help; help_ptr; help_ptr = (ConfigItem_help *)next) {
		MOTDLine *text;
		next = (ListStruct *)help_ptr->next;
		safe_free(help_ptr->command);
		while (help_ptr->text) {
			text = help_ptr->text->next;
			safe_free(help_ptr->text->line);
			safe_free(help_ptr->text);
			help_ptr->text = text;
		}
		DelListItem(help_ptr, conf_help);
		safe_free(help_ptr);
	}
	for (os_ptr = iConf.allow_user_stats_ext; os_ptr; os_ptr = (OperStat *)next)
	{
		next = (ListStruct *)os_ptr->next;
		safe_free(os_ptr->flag);
		safe_free(os_ptr);
	}
	iConf.allow_user_stats_ext = NULL;
	for (of_ptr = conf_offchans; of_ptr; of_ptr = (ConfigItem_offchans *)next)
	{
		next = (ListStruct *)of_ptr->next;
		safe_free(of_ptr->topic);
		safe_free(of_ptr);
	}
	conf_offchans = NULL;

	/* Free sni { } blocks */
	for (sni = conf_sni; sni; sni = (ConfigItem_sni *)next)
	{
	    next = (ListStruct *)sni->next;
	    SSL_CTX_free(sni->ssl_ctx);
	    free_tls_options(sni->tls_options);
	    safe_free(sni->name);
	    safe_free(sni);
	}
	conf_sni = NULL;

	free_conf_channelmodes(&iConf.modes_on_join);

	free_all_proxy_blocks();

	/*
	  reset conf_files -- should this be in its own function? no, because
	  it's only used here
	 */
	safe_free(conf_files->motd_file);
	safe_free(conf_files->smotd_file);
	safe_free(conf_files->opermotd_file);
	safe_free(conf_files->svsmotd_file);
	safe_free(conf_files->botmotd_file);
	safe_free(conf_files->rules_file);
	safe_free(conf_files->pid_file);
	safe_free(conf_files->tune_file);
	/*
	   Don't free conf_files->pid_file here; the old value is used to determine if
	   the pidfile location has changed and write_pidfile() needs to be called
	   again.
	*/
	safe_free(conf_files);
	conf_files = NULL;
}

int	config_post_test()
{
#define Error(x) { config_error((x)); errors++; }
	int 	errors = 0;
	Hook *h;

	if (!requiredstuff.conf_me)
		Error("me {} block is missing");
	if (!requiredstuff.conf_admin)
		Error("admin {} block is missing");
	if (!requiredstuff.conf_listen)
		Error("listen {} block is missing");
	if (!settings.has_kline_address)
		Error("set::kline-address is missing");
	if (!settings.has_default_server)
		Error("set::default-server is missing");
	if (!settings.has_network_name)
		Error("set::network-name is missing");
	if (!settings.has_help_channel)
		Error("set::help-channel is missing");
	if (nicklengths.min > nicklengths.max)
		Error("set::nick-length is smaller than set::min-nick-length");

	for (h = Hooks[HOOKTYPE_CONFIGPOSTTEST]; h; h = h->next)
	{
		int value, errs = 0;
		if (h->owner && !(h->owner->flags & MODFLAG_TESTING) &&
		                !(h->owner->options & MOD_OPT_PERM))
			continue;
		value = (*(h->func.intfunc))(&errs);
		if (value == -1)
		{
			errors += errs;
			break;
		}
		if (value == -2)
			errors += errs;
	}
	return errors;
}

/** Make the "read" config the "live" config */
void config_switchover(void)
{
	free_iConf(&iConf);
	memcpy(&iConf, &tempiConf, sizeof(iConf));
	memset(&tempiConf, 0, sizeof(tempiConf));
	log_blocks_switchover();
	log_throttle_rehash();
}

/** Priority of config blocks during CONFIG_TEST stage */
static const char *config_test_priority_blocks[] =
{
	"me",
	"secret",
	"log", /* "log" needs to be before "set" in CONFIG_TEST */
	"security-group",
	"set",
	"class",
};

/** Priority of config blocks during CONFIG_RUN stage */
static const char *config_run_priority_blocks[] =
{
	"me",
	"secret",
	"security-group",
	"set",
	"log", /* "log" needs to be after "set" in CONFIG_RUN */
	"class",
};

int config_item_allowed_for_config_file(const char *resource, const char *item)
{
	ConfigResource *rs = find_config_resource(resource);
	if (!rs)
	{
		/* Special hardcoded handling for central spamfilter */
		if (!strcmp(resource, "central_spamfilter.conf"))
		{
			if (!strcmp(item, "spamfilter") ||
			    !strcmp(item, "ban"))
				return 1;
			return 0;
		}
		/* This should never happen */
#ifdef DEBUGMODE
		abort();
#else
		return 1; /* Fallback to unrestricted */
#endif
	}

	if (rs->restrict_config == NULL)
		return 1; /* No restrictions */

	if (item == NULL)
		return 0;

	if (find_name_list(rs->restrict_config, item))
		return 1;
	return 0;
}

int config_test_blocks_generic(ConfigFile *cfptr, int skip_priority_blocks, int normalconf)
{
	ConfigEntry *ce;
	int errors = 0;
	int i;
	Hook *h;
	ConfigCommand *cc;

	for (ce = cfptr->items; ce; ce = ce->next)
	{
		if (skip_priority_blocks)
		{
			char skip = 0;
			for (i=0; i < ARRAY_SIZEOF(config_test_priority_blocks); i++)
			{
				if (!strcmp(ce->name, config_test_priority_blocks[i]))
				{
					skip = 1;
					break;
				}
			}
			if (skip)
				continue;
		}

		if (!config_item_allowed_for_config_file(cfptr->filename, ce->name))
			continue;

		if ((cc = config_binary_search(ce->name))) {
			if (cc->testfunc)
				errors += (cc->testfunc(cfptr, ce));
		}
		else
		{
			int used = 0;
			for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
			{
				int value, errs = 0;
				if (normalconf)
				{
					if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
					    && !(h->owner->options & MOD_OPT_PERM))
					{
						continue;
					}
				}
				value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN,&errs);
				if (value == 2)
					used = 1;
				if (value == 1)
				{
					used = 1;
					break;
				}
				if (value == -1)
				{
					used = 1;
					errors += errs;
					break;
				}
				if (value == -2)
				{
					used = 1;
					errors += errs;
				}

			}
			if (!used)
			{
				config_error("%s:%i: unknown directive %s",
					ce->file->filename, ce->line_number,
					ce->name);
				errors++;
				if (strchr(ce->name, ':'))
				{
					config_error("You cannot use :: in a directive, you have to write them out. "
						     "For example 'set::auto-join #something' needs to be written as: "
						     "set { auto-join \"#something\"; }");
					config_error("See also https://www.unrealircd.org/docs/Set_block#Syntax_used_in_this_documentation");
				}
			}
		}
	}
	return errors;
}

int config_test_blocks()
{
	ConfigEntry 	*ce;
	ConfigFile	*cfptr;
	ConfigCommand	*cc;
	int		errors = 0;
	int i;
	Hook *h;

	invalid_snomasks_encountered = 0;

	/* Stage 1: first the priority blocks, in the order as specified
	 *          in config_test_priority_blocks[]
	 */
	for (i=0; i < ARRAY_SIZEOF(config_test_priority_blocks); i++)
	{
		const char *config_block = config_test_priority_blocks[i];
		cc = config_binary_search(config_block);
		if (!cc)
			abort(); /* internal fuckup */
		for (cfptr = conf; cfptr; cfptr = cfptr->next)
		{
			if (!config_item_allowed_for_config_file(cfptr->filename, config_block))
				continue;
			if (config_verbose > 1)
				config_status("Running %s", cfptr->filename);
			for (ce = cfptr->items; ce; ce = ce->next)
			{
				if (!strcmp(ce->name, config_block))
				{
					int n = cc->testfunc(cfptr, ce);
					errors += n;
					if (!strcmp(config_block, "secret") && (n == 0))
					{
						/* Yeah special case: secret { } blocks we run
						 * immediately here.
						 */
						_conf_secret(cfptr, ce);
					}
				}
			}
		}
	}

	/* Stage 2: now all the other config blocks */
	for (cfptr = conf; cfptr; cfptr = cfptr->next)
	{
		if (config_verbose > 1)
			config_status("Running %s", cfptr->filename);
		errors += config_test_blocks_generic(cfptr, 1, 1);
	}

	errors += config_post_test();

	if (errors > 0)
	{
		config_error("%i errors encountered", errors);
	}

	if (invalid_snomasks_encountered)
	{
		config_error("It seems your set::snomask-on-oper and/or oper::snomask needs to be updated. Are you perhaps upgrading from an older version to UnrealIRCd 6?");
		config_error("See https://www.unrealircd.org/docs/Upgrading_from_5.x#Update_your_snomasks");
	}

	return (errors > 0 ? -1 : 1);
}

/* This returns the # of processed blocks, NOT errors */
int config_run_blocks_generic(ConfigFile *cfptr, int skip_priority_blocks)
{
	ConfigEntry *ce;
	int processed = 0;
	int i;
	Hook *h;
	ConfigCommand *cc;

	for (ce = cfptr->items; ce; ce = ce->next)
	{
		if (skip_priority_blocks)
		{
			char skip = 0;
			for (i=0; i < ARRAY_SIZEOF(config_run_priority_blocks); i++)
			{
				if (!strcmp(ce->name, config_run_priority_blocks[i]))
				{
					skip = 1;
					break;
				}
			}
			if (skip)
				continue;
		}

		if (!config_item_allowed_for_config_file(cfptr->filename, ce->name))
			continue;

		if ((cc = config_binary_search(ce->name)))
		{
			if (cc->conffunc)
			{
				cc->conffunc(cfptr, ce);
				processed++;
			}
		}
		else
		{
			int value;
			for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
			{
				value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN);
				if (value == 1)
				{
					processed++;
					break;
				}
			}
		}
	}

	return processed;
}

int config_run_blocks(void)
{
	ConfigEntry 	*ce;
	ConfigFile	*cfptr;
	ConfigCommand	*cc;
	int		errors = 0;
	int i;
	Hook *h;
	ConfigItem_allow *allow;

	/* Stage 1: first the priority blocks, in the order as specified
	 *          in config_run_priority_blocks[]
	 */
	for (i=0; i < ARRAY_SIZEOF(config_run_priority_blocks); i++)
	{
		const char *config_block = config_run_priority_blocks[i];
		cc = config_binary_search(config_block);
		if (!cc)
			abort(); /* internal fuckup */
		if (!strcmp(config_block, "secret"))
			continue; /* yeah special case, we already processed the run part in test for these */
		for (cfptr = conf; cfptr; cfptr = cfptr->next)
		{
			if (config_verbose > 1)
				config_status("Running %s", cfptr->filename);
			for (ce = cfptr->items; ce; ce = ce->next)
			{
				if (!config_item_allowed_for_config_file(cfptr->filename, ce->name))
					continue;
				if (!strcmp(ce->name, config_block))
				{
					if (cc->conffunc(cfptr, ce) < 0)
						errors++;
				}
			}
		}
		if (!strcmp(config_block, "set"))
		{
			/* Yeah, this is stupid to have here, it's basically a
			 * just-in-time placement of set::tls::certificate / key.
			 * We can't initialize it in config_setdefaultsettings()
			 * because when a config item is encountered in the config file
			 * we _add to a list_ of certificates/keys, so then we
			 * could never override the default one.
			 * So, instead, it needs to be NULL / empty, and we only
			 * fill it here if it is still NULL.
			 * And we need to do it HERE, directly after processing
			 * of all set blocks, because listen/link/sni/etc (can)
			 * inherit set::tls options, and they should not inherit NULL.
			 * *sigh*... -- Syzop / 2025-12-10
			 */
			if (!tempiConf.tls_options->certificate_files)
			{
				char tmp[512];
				snprintf(tmp, sizeof(tmp), "%s/tls/server.cert.pem", CONFDIR);
				add_name_list(tempiConf.tls_options->certificate_files, tmp);
				snprintf(tmp, sizeof(tmp), "%s/tls/server.key.pem", CONFDIR);
				add_name_list(tempiConf.tls_options->key_files, tmp);
			}
		}
	}

	/* Stage 2: now all the other config blocks */
	for (cfptr = conf; cfptr; cfptr = cfptr->next)
	{
		if (config_verbose > 1)
			config_status("Running %s", cfptr->filename);
		config_run_blocks_generic(cfptr, 1);
	}

	listen_cleanup();
	loop.do_bancheck = 1;
	config_switchover();

	/* initialize conf_files with defaults if the block isn't set: */
	if (!conf_files)
	  _conf_files(NULL, NULL);

	if (errors > 0)
	{
		config_error("%i fatal errors encountered", errors);
	}
	return (errors > 0 ? -1 : 1);
}

/*
 * Service functions
*/

ConfigItem_alias *find_alias(const char *name)
{
	ConfigItem_alias *e;

	if (!name)
		return NULL;

	for (e = conf_alias; e; e = e->next)
	{
		if (!strcasecmp(e->alias, name))
			return e;
	}
	return NULL;
}

ConfigItem_class *find_class(const char *name)
{
	ConfigItem_class *e;

	if (!name)
		return NULL;

	for (e = conf_class; e; e = e->next)
	{
		if (!strcmp(name, e->name))
			return e;
	}
	return NULL;
}


ConfigItem_oper	*find_oper(const char *name)
{
	ConfigItem_oper	*e;

	if (!name)
		return NULL;

	for (e = conf_oper; e; e = e->next)
	{
		if (!strcmp(name, e->name))
			return e;
	}
	return NULL;
}

ConfigItem_operclass *find_operclass(const char *name)
{
	ConfigItem_operclass *e;

	if (!name)
		return NULL;

	for (e = conf_operclass; e; e = e->next)
	{
		if (!strcmp(name,e->classStruct->name))
			return e;
	}
	return NULL;
}

int count_oper_sessions(const char *name)
{
	int count = 0;
	Client *client;

	list_for_each_entry(client, &oper_list, special_node)
	{
		if (client->user->operlogin != NULL && !strcmp(client->user->operlogin, name))
			count++;
	}

	return count;
}

ConfigItem_listen *find_listen(const char *ipmask, int port, SocketType socket_type)
{
	ConfigItem_listen *e;

	if (!ipmask)
		return NULL;

	for (e = conf_listen; e; e = e->next)
	{
		if (e->socket_type != socket_type)
			continue;
		if (e->socket_type == SOCKET_TYPE_UNIX)
		{
			if (!strcmp(e->file, ipmask))
				return e;
		} else
		{
			if ((e->socket_type == socket_type) && (e->port == port) && !strcmp(e->ip, ipmask))
				return e;
		}
	}

	return NULL;
}

/** Find an SNI match.
 * @param name The hostname to look for (eg: irc.xyz.com).
 */
ConfigItem_sni *find_sni(const char *name)
{
	ConfigItem_sni *e;

	if (!name)
		return NULL;

	for (e = conf_sni; e; e = e->next)
	{
        if (match_simple(e->name, name))
            return e;
	}
	return NULL;
}

ConfigItem_ulines *find_uline(const char *host)
{
	ConfigItem_ulines *ulines;

	if (!host)
		return NULL;

	for(ulines = conf_ulines; ulines; ulines = ulines->next)
	{
		if (!strcasecmp(host, ulines->servername))
			return ulines;
	}
	return NULL;
}


ConfigItem_tld *find_tld(Client *client)
{
	ConfigItem_tld *tld;

	for (tld = conf_tld; tld; tld = tld->next)
	{
		if (user_allowed_by_security_group(client, tld->match))
		{
			if ((tld->options & TLD_TLS) && !IsSecureConnect(client))
				continue;
			if ((tld->options & TLD_REMOTE) && MyUser(client))
				continue;
			return tld;
		}
	}

	return NULL;
}

/** Find a link block by server name (but don't check any restrictions like IP or auth) */
ConfigItem_link *find_link(const char *servername)
{
	ConfigItem_link	*link;

	for (link = conf_link; link; link = link->next)
	{
		if (!link->flag.temporary && match_simple(link->servername, servername))
		{
		    return link;
		}
	}
	return NULL;
}

/** Find a ban of type CONF_BAN_*, which is currently only
 * CONF_BAN_SERVER, CONF_BAN_VERSION and CONF_BAN_REALNAME
 */
ConfigItem_ban *find_ban(Client *client, const char *host, short type)
{
	ConfigItem_ban *ban;

	for (ban = conf_ban; ban; ban = ban->next)
	{
		if (ban->flag.type == type)
		{
			if (client)
			{
				if (match_user(ban->mask, client, MATCH_CHECK_REAL))
					return ban;
			}
			else if (match_simple(ban->mask, host))
				return ban;
		}
	}
	return NULL;
}

/** Find a ban of type CONF_BAN_*, which is currently only
 * CONF_BAN_SERVER, CONF_BAN_VERSION and CONF_BAN_REALNAME
 * This is the extended version, only used by cmd_svsnline.
 */
ConfigItem_ban 	*find_banEx(Client *client, const char *host, short type, short type2)
{
	ConfigItem_ban *ban;

	for (ban = conf_ban; ban; ban = ban->next)
	{
		if ((ban->flag.type == type) && (ban->flag.type2 == type2))
		{
			if (client)
			{
				if (match_user(ban->mask, client, MATCH_CHECK_REAL))
					return ban;
			}
			else if (match_simple(ban->mask, host))
				return ban;
		}
	}
	return NULL;
}

/** returns NULL if allowed and struct if denied */
ConfigItem_deny_channel *find_channel_allowed(Client *client, const char *name)
{
	ConfigItem_deny_channel *dchannel;
	ConfigItem_allow_channel *achannel;

	for (dchannel = conf_deny_channel; dchannel; dchannel = dchannel->next)
	{
		if (match_esc(dchannel->channel, name))
		{
			if (dchannel->class && strcmp(client->local->class->name, dchannel->class))
				continue;
			if (dchannel->match && !user_allowed_by_security_group(client, dchannel->match))
				continue;
			break; /* MATCH deny channel { } */
		}
	}

	if (dchannel)
	{
		/* Check exceptions... ('allow channel') */
		for (achannel = conf_allow_channel; achannel; achannel = achannel->next)
		{
			if (match_esc(achannel->channel, name))
			{
				if (achannel->class && strcmp(client->local->class->name, achannel->class))
					continue;
				if (achannel->match && !user_allowed_by_security_group(client, achannel->match))
					continue;
				break; /* MATCH allow channel { } */
			}
		}
		if (achannel)
			return NULL; /* Matches an 'allow channel' - so not forbidden */
		else
			return dchannel;
	}
	return NULL;
}

void init_dynconf(void)
{
	memset(&iConf, 0, sizeof(iConf));
	memset(&tempiConf, 0, sizeof(iConf));
	init_dynamic_set_block(&unknown_users_set);
	init_dynamic_set_block(&dynamic_set);
}

const char *pretty_time_val_r(char *buf, size_t buflen, long timeval)
{
	if (timeval == 0)
		return "0";

	buf[0] = 0;

	if (timeval/86400)
		snprintf(buf, buflen, "%ldd", timeval/86400);
	if ((timeval/3600) % 24)
		snprintf(buf+strlen(buf), buflen-strlen(buf), "%ldh", (timeval/3600)%24);
	if ((timeval/60)%60)
		snprintf(buf+strlen(buf), buflen-strlen(buf), "%ldm", (timeval/60)%60);
	if ((timeval%60))
		snprintf(buf+strlen(buf), buflen-strlen(buf), "%lds", timeval%60);

	return buf;
}

const char *pretty_time_val(long timeval)
{
	static char buf[512];
	return pretty_time_val_r(buf, sizeof(buf), timeval);
}

/* This converts a relative path to an absolute path, but only if necessary. */
void convert_to_absolute_path(char **path, const char *reldir)
{
	char *s;

	if (!*path || !**path)
		return; /* NULL or empty */

	if (strstr(*path, "://"))
		return; /* URL: don't touch */

#ifdef _WIN32
	if (!strncmp(*path, "cache/", 6))
		return; /* downloaded from URL: don't touch (is only relative path on Windows) */
#endif

	if ((**path == '/') || (**path == '\\'))
		return; /* already absolute path */

	if (!strncmp(*path, reldir, strlen(reldir)))
		return; /* already contains reldir */

	s = safe_alloc(strlen(reldir) + strlen(*path) + 2);
	sprintf(s, "%s/%s", reldir, *path); /* safe, see line above */
	safe_free(*path);
	*path = s;
}

/* Similar to convert_to_absolute_path() but returns a duplicated string.
 * Don't forget to free!
 */
char *convert_to_absolute_path_duplicate(char *path, char *reldir)
{
	char *xpath = strdup(path);
	convert_to_absolute_path(&xpath, reldir);
	return xpath;
}

/*
 * Actual config parser funcs
*/

int _conf_include(ConfigFile *conf, ConfigEntry *ce)
{
	int	ret = 0;
#ifdef GLOBH
	glob_t files;
	int i;
#elif defined(_WIN32)
	HANDLE hFind;
	WIN32_FIND_DATA FindData;
	char cPath[MAX_PATH], *cSlash = NULL, *path;
#endif
	if (!ce->value)
	{
		config_status("%s:%i: include: no filename given",
			ce->file->filename,
			ce->line_number);
		return -1;
	}

	convert_to_absolute_path(&ce->value, CONFDIR);

	if (str_ends_with_case_sensitive(ce->value, "central_spamfilter.conf"))
	{
		config_status("%s:%d: you cannot include 'central_spamfilter.conf', "
		              "this is a special name that cannot be used.",
		              ce->file->filename, ce->line_number);
		return -1;
	}

	if (url_is_valid(ce->value))
	{
		add_config_resource(ce->value, RESOURCE_INCLUDE, ce);
		return 0;
	}
#if !defined(_WIN32) && !defined(_AMIGA) && !defined(OSXTIGER) && DEFAULT_PERMISSIONS != 0
	(void)chmod(ce->value, DEFAULT_PERMISSIONS);
#endif
#ifdef GLOBH
#if defined(__OpenBSD__) && defined(GLOB_LIMIT)
	glob(ce->value, GLOB_NOSORT|GLOB_NOCHECK|GLOB_LIMIT, NULL, &files);
#else
	glob(ce->value, GLOB_NOSORT|GLOB_NOCHECK, NULL, &files);
#endif
	if (!files.gl_pathc) {
		globfree(&files);
		config_status("%s:%i: include %s: invalid file given",
			ce->file->filename, ce->line_number,
			ce->value);
		return -1;
	}
	for (i = 0; i < files.gl_pathc; i++)
	{
		if (add_config_resource(files.gl_pathv[i], RESOURCE_INCLUDE, ce))
		{
			ret = config_read_file(files.gl_pathv[i], files.gl_pathv[i]);
			if (ret < 0)
			{
				globfree(&files);
				return ret;
			}
		}
	}
	globfree(&files);
#elif defined(_WIN32)
	memset(cPath, 0, MAX_PATH);
	if (strchr(ce->value, '/') || strchr(ce->value, '\\')) {
		strlcpy(cPath,ce->value,MAX_PATH);
		cSlash=cPath+strlen(cPath);
		while(*cSlash != '\\' && *cSlash != '/' && cSlash > cPath)
			cSlash--;
		*(cSlash+1)=0;
	}
	if ( (hFind = FindFirstFile(ce->value, &FindData)) == INVALID_HANDLE_VALUE )
	{
		config_status("%s:%i: include %s: invalid file given",
			ce->file->filename, ce->line_number,
			ce->value);
		return -1;
	}
	if (cPath) {
		path = safe_alloc(strlen(cPath) + strlen(FindData.cFileName)+1);
		strcpy(path, cPath);
		strcat(path, FindData.cFileName);

		if (add_config_resource(path, RESOURCE_INCLUDE, ce))
		{
			ret = config_read_file(path, path);
			safe_free(path);
		}
	}
	else
	{
		if (add_config_resource(FindData.cFileName, RESOURCE_INCLUDE, ce))
			ret = config_read_file(FindData.cFileName, FindData.cFileName);
	}
	if (ret < 0)
	{
		FindClose(hFind);
		return ret;
	}

	ret = 0;
	while (FindNextFile(hFind, &FindData) != 0) {
		if (cPath) {
			path = safe_alloc(strlen(cPath) + strlen(FindData.cFileName)+1);
			strcpy(path,cPath);
			strcat(path,FindData.cFileName);

			if (add_config_resource(path, RESOURCE_INCLUDE, ce))
			{
				ret = config_read_file(path, path);
				safe_free(path);
				if (ret < 0)
					break;
			}
		}
		else
		{
			if (add_config_resource(FindData.cFileName, RESOURCE_INCLUDE, ce))
				ret = config_read_file(FindData.cFileName, FindData.cFileName);
		}
	}
	FindClose(hFind);
	if (ret < 0)
		return ret;
#else
	if (add_config_resource(ce->value, RESOURCE_INCLUDE, ce))
		ret = config_read_file(ce->value, ce->value);
	return ret;
#endif
	return 1;
}

int	_test_include(ConfigFile *conf, ConfigEntry *ce)
{
	return 0;
}

int	_conf_admin(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigItem_admin *ca;

	for (cep = ce->items; cep; cep = cep->next)
	{
		ca = safe_alloc(sizeof(ConfigItem_admin));
		if (!conf_admin)
			conf_admin_tail = ca;
		safe_strdup(ca->line, cep->name);
		AddListItem(ca, conf_admin);
	}
	return 1;
}

int	_test_admin(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int 	    errors = 0;

	if (requiredstuff.conf_admin)
	{
		config_warn_duplicate(ce->file->filename, ce->line_number, "admin");
		return 0;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (strlen(cep->name) > 500)
		{
			config_error("%s:%i: oversized data in admin block",
				cep->file->filename,
				cep->line_number);
			errors++;
			continue;
		}
	}
	requiredstuff.conf_admin = 1;
	return errors;
}

int	_conf_me(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;

	if (!conf_me)
		conf_me = safe_alloc(sizeof(ConfigItem_me));

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "name"))
		{
			safe_strdup(conf_me->name, cep->value);
		}
		else if (!strcmp(cep->name, "info"))
		{
			safe_strdup(conf_me->info, cep->value);
		}
		else if (!strcmp(cep->name, "sid"))
		{
			safe_strdup(conf_me->sid, cep->value);
		}
	}
	return 1;
}

int	_test_me(ConfigFile *conf, ConfigEntry *ce)
{
	char has_name = 0, has_info = 0, has_sid = 0;
	ConfigEntry *cep;
	int	    errors = 0;

	if (requiredstuff.conf_me)
	{
		config_warn_duplicate(ce->file->filename, ce->line_number, "me");
		return 0;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (config_is_blankorempty(cep, "me"))
			continue;

		/* me::name */
		if (!strcmp(cep->name, "name"))
		{
			if (has_name)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "me::name");
				continue;
			}
			has_name = 1;
			if (!strchr(cep->value, '.'))
			{
				config_error("%s:%i: illegal me::name, must be fully qualified hostname",
					cep->file->filename,
					cep->line_number);
				errors++;
			}
			if (strlen(cep->value) > HOSTLEN)
			{
				config_error("%s:%i: illegal me::name, must be less or equal to %i characters",
					cep->file->filename,
					cep->line_number, HOSTLEN);
				errors++;
			}
			if (!valid_server_name(cep->value))
			{
				config_error("%s:%i: illegal me::name contains invalid character(s) [only a-z, 0-9, _, -, . are allowed]",
					cep->file->filename,
					cep->line_number);
				errors++;
			}
		}
		/* me::info */
		else if (!strcmp(cep->name, "info"))
		{
			char *p;
			char valid = 0;
			if (has_info)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "me::info");
				continue;
			}
			has_info = 1;
			if (strlen(cep->value) > (REALLEN-1))
			{
				config_error("%s:%i: too long me::info, must be max. %i characters",
					cep->file->filename, cep->line_number,
					REALLEN-1);
				errors++;
			}

			/* Valid me::info? Any data except spaces is ok */
			for (p=cep->value; *p; p++)
			{
				if (*p != ' ')
				{
					valid = 1;
					break;
				}
			}
			if (!valid)
			{
				config_error("%s:%i: empty me::info, should be a server description.",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "numeric"))
		{
			config_error("%s:%i: me::numeric has been removed, you must now specify a Server ID (SID) instead. "
			             "Edit your configuration file and change 'numeric' to 'sid' and make up "
			             "a server id of exactly 3 characters, starting with a digit, eg: \"001\" or \"0AB\".",
			             cep->file->filename, cep->line_number);
			errors++;
		}
		else if (!strcmp(cep->name, "sid"))
		{
			if (has_sid)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "me::sid");
				continue;
			}
			has_sid = 1;

			if (!valid_sid(cep->value))
			{
				config_error("%s:%i: me::sid must be 3 characters long, begin with a number, "
				             "and the 2nd and 3rd character must be a number or uppercase letter. "
				             "Example: \"001\" and \"0AB\" is good. \"AAA\" and \"0ab\" are bad. ",
				             cep->file->filename, cep->line_number);
				errors++;
			}

			if (!isdigit(*cep->value))
			{
				config_error("%s:%i: me::sid must be 3 characters long and begin with a number",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		/* Unknown entry */
		else
		{
			config_error_unknown(ce->file->filename, ce->line_number,
				"me", cep->name);
			errors++;
		}
	}
	if (!has_name)
	{
		config_error_missing(ce->file->filename, ce->line_number, "me::name");
		errors++;
	}
	if (!has_info)
	{
		config_error_missing(ce->file->filename, ce->line_number, "me::info");
		errors++;
	}
	if (!has_sid)
	{
		config_error_missing(ce->file->filename, ce->line_number, "me::sid");
		errors++;
	}
	requiredstuff.conf_me = 1;
	return errors;
}

/*
 * The files {} block
 */
int	_conf_files(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;

	if (!conf_files)
	{
		conf_files = safe_alloc(sizeof(ConfigItem_files));

		/* set defaults */
		safe_strdup(conf_files->motd_file, MPATH);
		safe_strdup(conf_files->rules_file, RPATH);
		safe_strdup(conf_files->smotd_file, SMPATH);
		safe_strdup(conf_files->botmotd_file, BPATH);
		safe_strdup(conf_files->opermotd_file, OPATH);
		safe_strdup(conf_files->svsmotd_file, VPATH);

		safe_strdup(conf_files->pid_file, IRCD_PIDFILE);
		safe_strdup(conf_files->tune_file, IRCDTUNE);

		/* we let actual files get read in later by the motd caching mechanism */
	}
	/*
	 * hack to allow initialization of conf_files (above) when there is no files block in
	 * CPATH. The caller calls _conf_files(NULL, NULL); to do this. We return here because
	 * the for loop's initialization of cep would segfault otherwise. We return 1 because
	 * if config_run_blocks() calls us with a NULL ce, it's got a bug...but we can't detect that.
	 */
	if (!ce)
	  return 1;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "motd"))
			safe_strdup(conf_files->motd_file, cep->value);
		else if (!strcmp(cep->name, "shortmotd"))
			safe_strdup(conf_files->smotd_file, cep->value);
		else if (!strcmp(cep->name, "opermotd"))
			safe_strdup(conf_files->opermotd_file, cep->value);
		else if (!strcmp(cep->name, "svsmotd"))
			safe_strdup(conf_files->svsmotd_file, cep->value);
		else if (!strcmp(cep->name, "botmotd"))
			safe_strdup(conf_files->botmotd_file, cep->value);
		else if (!strcmp(cep->name, "rules"))
			safe_strdup(conf_files->rules_file, cep->value);
		else if (!strcmp(cep->name, "tunefile"))
			safe_strdup(conf_files->tune_file, cep->value);
		else if (!strcmp(cep->name, "pidfile"))
			safe_strdup(conf_files->pid_file, cep->value);
	}
	return 1;
}

int	_test_files(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int	    errors = 0;
	char has_motd = 0, has_smotd = 0, has_rules = 0;
	char has_botmotd = 0, has_opermotd = 0, has_svsmotd = 0;
	char has_pidfile = 0, has_tunefile = 0;

	for (cep = ce->items; cep; cep = cep->next)
	{
		/* files::motd */
		if (!strcmp(cep->name, "motd"))
		{
			if (has_motd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::motd");
				continue;
			}
			convert_to_absolute_path(&cep->value, CONFDIR);
			config_test_openfile(cep, O_RDONLY, 0, "files::motd", 0, 1);
			has_motd = 1;
		}
		/* files::smotd */
		else if (!strcmp(cep->name, "shortmotd"))
		{
			if (has_smotd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::shortmotd");
				continue;
			}
			convert_to_absolute_path(&cep->value, CONFDIR);
			config_test_openfile(cep, O_RDONLY, 0, "files::shortmotd", 0, 1);
			has_smotd = 1;
		}
		/* files::rules */
		else if (!strcmp(cep->name, "rules"))
		{
			if (has_rules)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::rules");
				continue;
			}
			convert_to_absolute_path(&cep->value, CONFDIR);
			config_test_openfile(cep, O_RDONLY, 0, "files::rules", 0, 1);
			has_rules = 1;
		}
		/* files::botmotd */
		else if (!strcmp(cep->name, "botmotd"))
		{
			if (has_botmotd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::botmotd");
				continue;
			}
			convert_to_absolute_path(&cep->value, CONFDIR);
			config_test_openfile(cep, O_RDONLY, 0, "files::botmotd", 0, 1);
			has_botmotd = 1;
		}
		/* files::opermotd */
		else if (!strcmp(cep->name, "opermotd"))
		{
			if (has_opermotd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::opermotd");
				continue;
			}
			convert_to_absolute_path(&cep->value, CONFDIR);
			config_test_openfile(cep, O_RDONLY, 0, "files::opermotd", 0, 1);
			has_opermotd = 1;
		}
		/* files::svsmotd
		 * This config stuff should somehow be inside of modules/svsmotd.c!!!... right?
		 */
		else if (!strcmp(cep->name, "svsmotd"))
		{
			if (has_svsmotd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::svsmotd");
				continue;
			}
			convert_to_absolute_path(&cep->value, CONFDIR);
			/* svsmotd can't be a URL because we have to be able to write to it */
			config_test_openfile(cep, O_RDONLY, 0, "files::svsmotd", 0, 0);
			has_svsmotd = 1;
		}
		/* files::pidfile */
		else if (!strcmp(cep->name, "pidfile"))
		{
			if (has_pidfile)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::pidfile");
				continue;
			}
			convert_to_absolute_path(&cep->value, PERMDATADIR);
			errors += config_test_openfile(cep, O_WRONLY | O_CREAT, 0600, "files::pidfile", 1, 0);
			has_pidfile = 1;
		}
		/* files::tunefile */
		else if (!strcmp(cep->name, "tunefile"))
		{
			if (has_tunefile)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "files::tunefile");
				continue;
			}
			convert_to_absolute_path(&cep->value, PERMDATADIR);
			errors += config_test_openfile(cep, O_RDWR | O_CREAT, 0600, "files::tunefile", 1, 0);
			has_tunefile = 1;
		}
		/* <random directive here> */
		else
		{
			config_error("%s:%d: Unknown directive: \"%s\" in files {}", cep->file->filename,
				     cep->line_number, cep->name);
			errors ++;
		}
	}
	return errors;
}

/*
 * The operclass {} block parser
 */

OperClassACLEntry* _conf_parseACLEntry(ConfigEntry *ce)
{
	ConfigEntry *cep;
	OperClassACLEntry *entry = NULL;
	entry = safe_alloc(sizeof(OperClassACLEntry));

	if (!strcmp(ce->name,"allow"))
		entry->type = OPERCLASSENTRY_ALLOW;
	else
		entry->type = OPERCLASSENTRY_DENY;

	for (cep = ce->items; cep; cep = cep->next)
	{
		OperClassACLEntryVar *var = safe_alloc(sizeof(OperClassACLEntryVar));
		safe_strdup(var->name, cep->name);
		if (cep->value)
		{
			safe_strdup(var->value, cep->value);
		}
		AddListItem(var,entry->variables);
	}

	return entry;
}

OperClassACL* _conf_parseACL(const char *name, ConfigEntry *ce)
{
	ConfigEntry *cep;
	OperClassACL *acl = NULL;

	acl = safe_alloc(sizeof(OperClassACL));
	safe_strdup(acl->name, name);

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "deny") || !strcmp(cep->name, "allow"))
		{
			OperClassACLEntry *entry = _conf_parseACLEntry(cep);
			AddListItem(entry,acl->entries);
		}
		else {
			OperClassACL *subAcl = _conf_parseACL(cep->name,cep);
			AddListItem(subAcl,acl->acls);
		}
	}

	return acl;
}

/** Free previously allocated _conf_parseACLEntry() */
void free_acl_entry(OperClassACLEntry *e)
{
	OperClassACLEntryVar *v, *v_next;

	for (v = e->variables; v; v = v_next)
	{
		v_next = v->next;
		safe_free(v->name);
		safe_free(v->value);
		safe_free(v);
	}
	safe_free(e);
}

/** Free previously allocated _conf_parseACL() */
void free_operclass_acls(OperClassACL *acl)
{
	OperClassACL *acl_next;
	OperClassACLEntry *x, *x_next;
	OperClassACL *sub, *sub_next;

	for (; acl; acl = acl_next)
	{
		acl_next = acl->next;
		for (x = acl->entries; x; x = x_next)
		{
			x_next = x->next;
			DelListItem(x, acl->entries);
			free_acl_entry(x);
		}
		acl->entries = NULL;
		for (sub = acl->acls; sub; sub = sub_next)
		{
			sub_next = sub->next;
			DelListItem(sub, acl->acls);
			free_operclass_acls(sub);
		}
		acl->acls = NULL;
		safe_free(acl->name);
		safe_free(acl);
	}
}

void free_operclass_struct(OperClass *o)
{
	free_operclass_acls(o->acls);
	safe_free(o->ISA);
	safe_free(o->name);
	safe_free(o);
}

int	_conf_operclass(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigEntry *cepp;
	ConfigItem_operclass *operClass = NULL;
	operClass = safe_alloc(sizeof(ConfigItem_operclass));
	operClass->classStruct = safe_alloc(sizeof(OperClass));
	safe_strdup(operClass->classStruct->name, ce->value);

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "parent"))
		{
			safe_strdup(operClass->classStruct->ISA, cep->value);
		}
		else if (!strcmp(cep->name, "permissions"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				OperClassACL *acl = _conf_parseACL(cepp->name,cepp);
				AddListItem(acl,operClass->classStruct->acls);
			}
		}
	}

	AddListItem(operClass, conf_operclass);
	return 1;
}

void new_permissions_system(ConfigFile *conf, ConfigEntry *ce)
{
	if (need_operclass_permissions_upgrade)
		return; /* error already shown */

	config_error("%s:%i: UnrealIRCd 4.2.1 and higher have a new operclass permissions system.",
	             ce->file->filename, ce->line_number);
	config_error("Please see https://www.unrealircd.org/docs/FAQ#New_operclass_permissions");
	config_error("(additional errors regarding this are suppressed)");
	/*
	config_error("First of all, operclass::privileges has been renamed to operclass::permissions.");
	config_error("However, the permissions themselves have also been changed. You cannot simply "
	             "rename 'privileges' to 'permissions' and be done with it! ");
	config_error("See https://www.unrealircd.org/docs/Operclass_permissions for the new list of permissions.");
	config_error("Or just use the default operclasses from operclass.default.conf, then no need to change anything."); */
	need_operclass_permissions_upgrade = 1;
}

int 	_test_operclass(ConfigFile *conf, ConfigEntry *ce)
{
	int has_permissions = 0, has_parent = 0;
	ConfigEntry *cep;
	int	errors = 0;

	if (!ce->value)
	{
		config_error_noname(ce->file->filename, ce->line_number, "operclass");
		errors++;
	} else
	if (!valid_operclass_name(ce->value))
	{
		config_error("%s:%d: operclass name may only contain alphanumerical characters and "
		             "characters _-",
		             ce->file->filename, ce->line_number);
		errors++;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "parent"))
		{
			CheckNull(cep);
			config_detect_duplicate(&has_parent, cep, &errors);
			/* A -direct- loop is easy to detect.
			 * We also have code elsewhere to detect loops
			 * like a->b->a->b->a->b.
			 */
			if (ce->value && !strcmp(cep->value, ce->value))
			{
				config_error("%s:%d: operclass %s has parent set to %s (same name).",
				            cep->file->filename, cep->line_number, ce->name, cep->value);
				errors++;
			}
			has_parent = 1;
			continue;
		} else
		if (!strcmp(cep->name, "permissions"))
		{
			config_detect_duplicate(&has_permissions, cep, &errors);
		} else
		if (!strcmp(cep->name, "privileges"))
		{
			new_permissions_system(conf, cep);
			errors++;
			return errors;
		} else
		{
			config_error_unknown(cep->file->filename,
				cep->line_number, "operclass", cep->name);
			errors++;
		}
	}

	if (!has_permissions)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"operclass::permissions");
		errors++;
	}

	return errors;
}

/*
 * The oper {} block parser
*/

int	_conf_oper(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigEntry *cepp;
	ConfigItem_oper *oper = NULL;

	oper =  safe_alloc(sizeof(ConfigItem_oper));
	safe_strdup(oper->name, ce->value);
	oper->match = safe_alloc(sizeof(SecurityGroup));

	/* Inherit some defaults: */
	oper->server_notice_colors = tempiConf.server_notice_colors;
	oper->server_notice_show_event = tempiConf.server_notice_show_event;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "operclass"))
			safe_strdup(oper->operclass, cep->value);
		if (!strcmp(cep->name, "password"))
			AuthBlockToAuthConfig(cep, &oper->auth);
		else if (!strcmp(cep->name, "class"))
		{
			oper->class = find_class(cep->value);
			if (!oper->class || (oper->class->flag.temporary == 1))
			{
				config_status("%s:%i: illegal oper::class, unknown class '%s' using default of class 'default'",
					cep->file->filename, cep->line_number,
					cep->value);
				oper->class = default_class;
			}
		}
		else if (!strcmp(cep->name, "swhois"))
		{
			SWhois *s;
			if (cep->items)
			{
				for (cepp = cep->items; cepp; cepp = cepp->next)
				{
					s = safe_alloc(sizeof(SWhois));
					safe_strdup(s->line, cepp->name);
					safe_strdup(s->setby, "oper");
					AddListItem(s, oper->swhois);
				}
			} else
			if (cep->value)
			{
				s = safe_alloc(sizeof(SWhois));
				safe_strdup(s->line, cep->value);
				safe_strdup(s->setby, "oper");
				AddListItem(s, oper->swhois);
			}
		}
		else if (!strcmp(cep->name, "snomask"))
		{
			safe_strdup(oper->snomask, cep->value);
		}
		else if (!strcmp(cep->name, "server-notice-colors"))
		{
			oper->server_notice_colors = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "server-notice-show-event"))
		{
			oper->server_notice_show_event = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "auto-join"))
		{
			safe_strdup(oper->auto_join, cep->value);
		}
		else if (!strcmp(cep->name, "auto-login"))
		{
			oper->auto_login = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "modes"))
		{
			oper->modes = set_usermode(cep->value);
		}
		else if (!strcmp(cep->name, "require-modes"))
		{
			oper->require_modes = set_usermode(cep->value);
		}
		else if (!strcmp(cep->name, "maxlogins"))
		{
			oper->maxlogins = atoi(cep->value);
		}
		else if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
		{
			conf_match_block(conf, cep, &oper->match);
		}
		else if (!strcmp(cep->name, "vhost"))
		{
			safe_strdup(oper->vhost, cep->value);
		}
	}
	AddListItem(oper, conf_oper);
	return 1;
}

int	_test_oper(ConfigFile *conf, ConfigEntry *ce)
{
	char has_class = 0, has_password = 0, has_snomask = 0;
	char has_modes = 0, has_require_modes = 0, has_mask = 0, has_match = 0, has_broad_match = 0;
	char has_maxlogins = 0, has_operclass = 0, has_vhost = 0, has_auto_login = 0;
	ConfigEntry *cep;
	int errors = 0;

	if (!ce->value)
	{
		config_error_noname(ce->file->filename, ce->line_number, "oper");
		errors++;
	}
	for (cep = ce->items; cep; cep = cep->next)
	{
		/* Regular variables */
		if (!cep->items)
		{
			if (config_is_blankorempty(cep, "oper"))
			{
				errors++;
				continue;
			}
			/* oper::password */
			if (!strcmp(cep->name, "password"))
			{
				has_password = 1;

				if (ce->value && cep->value &&
					!strcmp(ce->value, "bobsmith") &&
					(!strcmp(cep->value, "test") || !strcmp(cep->value, "$argon2id..etc..")))
				{
					config_error("%s:%i: please change the the name and password of the "
								 "default 'bobsmith' oper block",
								 ce->file->filename, ce->line_number);
					errors++;
					continue;
				}

				if (Auth_CheckError(cep, 1) < 0)
					errors++;

				continue;
			}
			/* oper::operclass */
			else if (!strcmp(cep->name, "operclass"))
			{
				if (has_operclass)
				{
					config_warn_duplicate(cep->file->filename,
					cep->line_number, "oper::operclass");
					continue;
				}
				has_operclass = 1;
				continue;
			}
			/* oper::class */
			else if (!strcmp(cep->name, "class"))
			{
				if (has_class)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "oper::class");
					continue;
				}
				has_class = 1;
			}
			/* oper::swhois */
			else if (!strcmp(cep->name, "swhois"))
			{
			}
			/* oper::vhost */
			else if (!strcmp(cep->name, "vhost"))
			{
				if (has_vhost)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "oper::vhost");
					continue;
				}
				if (!potentially_valid_vhost(cep->value))
				{
					config_error("%s:%i: oper::vhost contains illegal characters or is too long: '%s'",
					             cep->file->filename, cep->line_number, cep->value);
					errors++;
				}
				has_vhost = 1;
			}
			/* oper::snomask */
			else if (!strcmp(cep->name, "snomask"))
			{
				char *wrong_snomask;
				if (has_snomask)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "oper::snomask");
					continue;
				}
				if (!is_valid_snomask_string_testing(cep->value, &wrong_snomask))
				{
					config_error("%s:%i: oper::snomask contains unknown snomask letter(s) '%s'",
					             cep->file->filename, cep->line_number, wrong_snomask);
					errors++;
					invalid_snomasks_encountered++;
				}
				has_snomask = 1;
			}
			else if (!strcmp(cep->name, "server-notice-colors"))
			{
			}
			else if (!strcmp(cep->name, "server-notice-show-event"))
			{
			}
			else if (!strcmp(cep->name, "auto-join"))
			{
			}
			else if (!strcmp(cep->name, "auto-login"))
			{
				has_auto_login = config_checkval(cep->value, CFG_YESNO);
			}
			/* oper::modes */
			else if (!strcmp(cep->name, "modes"))
			{
				char *p;
				for (p = cep->value; *p; p++)
					if (strchr("orzS", *p))
					{
						config_error("%s:%i: oper::modes may not include mode '%c'",
							cep->file->filename, cep->line_number, *p);
						errors++;
					}
				if (has_modes)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "oper::modes");
					continue;
				}
				has_modes = 1;
			}
			/* oper::require-modes */
			else if (!strcmp(cep->name, "require-modes"))
			{
				char *p;
				for (p = cep->value; *p; p++)
					if (strchr("o", *p))
					{
						config_warn("%s:%i: oper::require-modes probably shouldn't include mode '%c'",
							cep->file->filename, cep->line_number, *p);
					}
				if (has_require_modes)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "oper::require-modes");
					continue;
				}
				has_require_modes = 1;
			}
			/* oper::maxlogins */
			else if (!strcmp(cep->name, "maxlogins"))
			{
				int l;

				if (has_maxlogins)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "oper::maxlogins");
					continue;
				}
				has_maxlogins = 1;

				l = atoi(cep->value);
				if ((l < 0) || (l > 5000))
				{
					config_error("%s:%i: oper::maxlogins: value out of range (%d) should be 0-5000",
						cep->file->filename, cep->line_number, l);
					errors++;
					continue;
				}
			}
			else if (!strcmp(cep->name, "mask"))
			{
				if (cep->value || cep->items)
				{
					has_mask = 1;
					test_match_block(conf, cep, &errors);
					if (test_match_block_too_broad(conf, cep))
						has_broad_match = 1;
				}
			}
			else if (!strcmp(cep->name, "match"))
			{
				if (cep->value || cep->items)
				{
					has_match = 1;
					test_match_block(conf, cep, &errors);
					if (test_match_block_too_broad(conf, cep))
						has_broad_match = 1;
				}
			}
			else
			{
				config_error_unknown(cep->file->filename,
					cep->line_number, "oper", cep->name);
				errors++;
				continue;
			}
		}
		/* Sections */
		else
		{
			if (!strcmp(cep->name, "swhois"))
			{
				/* ok */
			}
			else if (!strcmp(cep->name, "mask"))
			{
				if (cep->value || cep->items)
				{
					has_mask = 1;
					test_match_block(conf, cep, &errors);
					if (test_match_block_too_broad(conf, cep))
						has_broad_match = 1;
				}
			}
			else if (!strcmp(cep->name, "match"))
			{
				if (cep->value || cep->items)
				{
					has_match = 1;
					test_match_block(conf, cep, &errors);
					if (test_match_block_too_broad(conf, cep))
						has_broad_match = 1;
				}
			}
			else if (!strcmp(cep->name, "password"))
			{
				has_password = 1;
				if (Auth_CheckError(cep, 1) < 0)
					errors++;
			}
			else
			{
				config_error_unknown(cep->file->filename,
					cep->line_number, "oper", cep->name);
				errors++;
				continue;
			}
		}
	}

	if (has_auto_login && has_broad_match)
	{
		config_error("%s:%i: your oper block for '%s' has auto-login but is completely unrestricted (mask *@*)!",
		             ce->file->filename, ce->line_number, ce->value);
		errors++;
	} else
	if (!has_password && has_broad_match)
	{
		config_error("%s:%i: your oper block for '%s' has no password and is completely unrestricted (mask *@*)!",
		             ce->file->filename, ce->line_number, ce->value);
		errors++;
	}

	/* The rest should NOT be in an 'else'... */
	if (has_password && has_auto_login)
	{
		config_error("%s:%i: You have auto-login enabled for your oper block '%s' but you also have a password set. "
		             "Remove the password if you want to use auto-login.",
		             ce->file->filename, ce->line_number, ce->value);
		errors++;
	}
	if (!has_mask && !has_match)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"oper::match");
		errors++;
	}
	if (has_mask && has_match)
	{
		config_error("%s:%d: You cannot have both ::mask and ::match. "
		             "You should only use oper::match.",
		             ce->file->filename, ce->line_number);
		errors++;
	}
	if (!has_class)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"oper::class");
		errors++;
	}
	if (!has_operclass)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"oper::operclass");
		errors++;
	}

	return errors;

}

ProxyType proxy_type_string_to_value(const char *s)
{
	if (!strcmp(s, "webirc"))
		return PROXY_WEBIRC;
	else if (!strcmp(s, "old"))
		return PROXY_WEBIRC_PASS;
	else if (!strcmp(s, "forwarded"))
		return PROXY_FORWARDED;
	else if (!strcmp(s, "x-forwarded"))
		return PROXY_X_FORWARDED;
	else if (!strcmp(s, "cloudflare"))
		return PROXY_CLOUDFLARE;
	return 0;
}

int _test_proxy(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int errors = 0;
	int has_mask = 0; /* mandatory */
	int has_password = 0; /* mandatory */
	int has_type = 0;
	ProxyType proxy_type = 0;

	if (!strcmp(ce->name, "webirc"))
	{
		proxy_type = PROXY_WEBIRC;
	} else {
		if (!ce->value)
		{
			config_error("%s:%i: proxy { } blocks need to have a name, like proxy myproxy { }",
				     ce->file->filename, ce->line_number);
			errors++;
		} else
		if (!security_group_valid_name(ce->value))
		{
			config_error("%s:%i: the name of the proxy block may only contain a-z, A-Z, 0-9, _ and -. "
			             "Your block name is invalid: '%s'",
			             ce->file->filename, ce->line_number, ce->value);
			errors++;
		}
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		/* This one can have no value so needs to be at the top */
		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
		{
			if (cep->value || cep->items)
			{
				has_mask = 1;
				test_match_block(conf, cep, &errors);
			}
		} else
		if (!cep->value)
		{
			config_error_empty(cep->file->filename, cep->line_number,
				"proxy", cep->name);
			errors++;
		} else
		if (!strcmp(cep->name, "password"))
		{
			has_password = 1;
			if (Auth_CheckError(cep, 0) < 0)
				errors++;
		}
		else if (!strcmp(cep->name, "type"))
		{
			if (has_type)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "proxy::type");
			}
			has_type = 1;
			if (!strcmp(cep->value, "web"))
			{
				config_error("%s:%i: type 'web' has been replaced. You now need to specify "
				             "the reverse proxy type explicitly. Instead of 'web', use one of: "
				             "'forwarded', 'x-forwarded' or 'cloudflare'. "
				             "See https://www.unrealircd.org/docs/Proxy_block",
				             cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
			proxy_type = proxy_type_string_to_value(cep->value);
			if (proxy_type == 0)
			{
				config_error("%s:%i: unknown proxy::type '%s'. "
				             "Only the following types are supported: 'webirc', 'old' or 'web'.",
				             cep->file->filename, cep->line_number, cep->value);
				errors++;
			}
		}
		else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"webirc", cep->name);
			errors++;
		}
	}
	if (!has_mask)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"webirc::mask");
		errors++;
	}

	if (!has_password && (proxy_type == PROXY_WEBIRC))
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"webirc::password");
		errors++;
	}
	
	if (has_password && (proxy_type == PROXY_WEBIRC_PASS))
	{
		config_error("%s:%i: webirc block has type set to 'old' but has a password set. "
		             "Passwords are not used with type 'old'. Either remove the password or "
		             "use the 'webirc' method instead.",
		             ce->file->filename, ce->line_number);
		errors++;
	}

	if (!has_type && !proxy_type)
	{
		config_error_missing(ce->file->filename, ce->line_number, "proxy::type");
		errors++;
	}

	return errors;
}

int _conf_proxy(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigItem_proxy *proxy = NULL;

	proxy = safe_alloc(sizeof(ConfigItem_proxy));
	proxy->type = PROXY_WEBIRC; /* default */

	safe_strdup(proxy->name, ce->value); // could be NULL if webirc

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
			conf_match_block(conf, cep, &proxy->mask);
		else if (!strcmp(cep->name, "password"))
			AuthBlockToAuthConfig(cep, &proxy->auth);
		else if (!strcmp(cep->name, "type"))
			proxy->type = proxy_type_string_to_value(cep->value);
	}

	AddListItem(proxy, conf_proxy);

	/* For the web proxy types, we automatically add the host to except ban { }
	 * for blacklist, connect-flood, handshake-data-flood
	 */
	if (IsWebProxy(proxy->type))
	{
		SecurityGroup *sg = duplicate_security_group(proxy->mask);
		tkl_add_banexception(TKL_EXCEPTION, "-", "-", sg, "proxy { } block",
				     "-config-", 0, TStime(), 0, "bcd", TKL_FLAG_CONFIG);
	}

	return 1;
}

/*
 * The class {} block parser
*/
int	_conf_class(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cep2;
	ConfigItem_class *class;
	unsigned char isnew = 0;
	Hook *h;

	if (!(class = find_class(ce->value)))
	{
		class = safe_alloc(sizeof(ConfigItem_class));
		safe_strdup(class->name, ce->value);
		isnew = 1;
	}
	else
	{
		isnew = 0;
		class->flag.temporary = 0;
		class->options = 0; /* RESET OPTIONS */
	}
	safe_strdup(class->name, ce->value);

	class->connfreq = 15; /* default */

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "pingfreq"))
			class->pingfreq = config_checkval(cep->value,CFG_TIME);
		else if (!strcmp(cep->name, "connfreq"))
			class->connfreq = config_checkval(cep->value,CFG_TIME);
		else if (!strcmp(cep->name, "maxclients"))
			class->maxclients = atol(cep->value);
		else if (!strcmp(cep->name, "sendq"))
			class->sendq = config_checkval(cep->value,CFG_SIZE);
		else if (!strcmp(cep->name, "recvq"))
			class->recvq = config_checkval(cep->value,CFG_SIZE);
		else if (!strcmp(cep->name, "options"))
		{
			for (cep2 = cep->items; cep2; cep2 = cep2->next)
				if (!strcmp(cep2->name, "nofakelag"))
					class->options |= CLASS_OPT_NOFAKELAG;
		}
		for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
		{
			int value = (*(h->func.intfunc))(conf, cep, CONFIG_CLASS, class);
			if (value == 1)
				break;
		}
	}
	if (isnew)
		AddListItem(class, conf_class);
	return 1;
}

int	_test_class(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry 	*cep, *cep2;
	int		errors = 0;
	char has_pingfreq = 0, has_connfreq = 0, has_maxclients = 0, has_sendq = 0;
	char has_recvq = 0;
	Hook *h;

	if (!ce->value)
	{
		config_error_noname(ce->file->filename, ce->line_number, "class");
		return 1;
	}
	if (!strcasecmp(ce->value, "default"))
	{
		config_error("%s:%d: Class cannot be named 'default', this class name is reserved for internal use.",
			ce->file->filename, ce->line_number);
		errors++;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		int used_by_module = 0;

		/* First, check if a module knows about this class::something */
		for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
		{
			int value, errs = 0;
			if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
			    && !(h->owner->options & MOD_OPT_PERM))
			{
				continue;
			}
			value = (*(h->func.intfunc))(conf, cep, CONFIG_ALLOW_BLOCK, &errs);
			if (value == 2)
				used_by_module = 1;
			if (value == 1)
			{
				used_by_module = 1;
				break;
			}
			if (value == -1)
			{
				used_by_module = 1;
				errors += errs;
				break;
			}
			if (value == -2)
			{
				used_by_module = 1;
				errors += errs;
			}
		}
		if (used_by_module)
			continue;

		if (!strcmp(cep->name, "options"))
		{
			for (cep2 = cep->items; cep2; cep2 = cep2->next)
			{
#ifdef FAKELAG_CONFIGURABLE
				if (!strcmp(cep2->name, "nofakelag"))
					;
				else
#endif
				{
					config_error("%s:%d: Unknown option '%s' in class::options",
						cep2->file->filename, cep2->line_number, cep2->name);
					errors++;
				}
			}
		}
		else if (config_is_blankorempty(cep, "class"))
		{
			errors++;
			continue;
		}
		/* class::pingfreq */
		else if (!strcmp(cep->name, "pingfreq"))
		{
			int v = config_checkval(cep->value,CFG_TIME);
			if (has_pingfreq)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "class::pingfreq");
				continue;
			}
			has_pingfreq = 1;
			if ((v < 30) || (v > 600))
			{
				config_error("%s:%i: class::pingfreq should be a reasonable value (30-600)",
					cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
		}
		/* class::maxclients */
		else if (!strcmp(cep->name, "maxclients"))
		{
			long l;
			if (has_maxclients)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "class::maxclients");
				continue;
			}
			has_maxclients = 1;
			l = atol(cep->value);
			if ((l < 1) || (l > 1000000))
			{
				config_error("%s:%i: class::maxclients with illegal value",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		/* class::connfreq */
		else if (!strcmp(cep->name, "connfreq"))
		{
			long l;
			if (has_connfreq)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "class::connfreq");
				continue;
			}
			has_connfreq = 1;
			l = config_checkval(cep->value,CFG_TIME);
			if ((l < 5) || (l > 604800))
			{
				config_error("%s:%i: class::connfreq with illegal value (must be >5 and <7d)",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		/* class::sendq */
		else if (!strcmp(cep->name, "sendq"))
		{
			long l;
			if (has_sendq)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "class::sendq");
				continue;
			}
			has_sendq = 1;
			l = config_checkval(cep->value,CFG_SIZE);
			if ((l <= 0) || (l > 2000000000))
			{
				config_error("%s:%i: class::sendq with illegal value",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		/* class::recvq */
		else if (!strcmp(cep->name, "recvq"))
		{
			long l;
			if (has_recvq)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "class::recvq");
				continue;
			}
			has_recvq = 1;
			l = config_checkval(cep->value,CFG_SIZE);
			if ((l < 512) || (l > 32768))
			{
				config_error("%s:%i: class::recvq with illegal value (must be >512 and <32k)",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		/* Unknown */
		else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"class", cep->name);
			errors++;
			continue;
		}
	}
	if (!has_pingfreq)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"class::pingfreq");
		errors++;
	}
	if (!has_maxclients)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"class::maxclients");
		errors++;
	}
	if (!has_sendq)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"class::sendq");
		errors++;
	}

	return errors;
}

int     _conf_drpass(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;

	if (!conf_drpass)
	{
		conf_drpass =  safe_alloc(sizeof(ConfigItem_drpass));
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "restart"))
			AuthBlockToAuthConfig(cep, &conf_drpass->restartauth);
		else if (!strcmp(cep->name, "die"))
			AuthBlockToAuthConfig(cep, &conf_drpass->dieauth);
	}
	return 1;
}

int     _test_drpass(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int errors = 0;
	char has_restart = 0, has_die = 0;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (config_is_blankorempty(cep, "drpass"))
		{
			errors++;
			continue;
		}
		/* drpass::restart */
		if (!strcmp(cep->name, "restart"))
		{
			if (has_restart)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "drpass::restart");
				continue;
			}
			has_restart = 1;
			if (Auth_CheckError(cep, 0) < 0)
				errors++;
			continue;
		}
		/* drpass::die */
		else if (!strcmp(cep->name, "die"))
		{
			if (has_die)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "drpass::die");
				continue;
			}
			has_die = 1;
			if (Auth_CheckError(cep, 0) < 0)
				errors++;
			continue;
		}
		/* Unknown */
		else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"drpass", cep->name);
			errors++;
			continue;
		}
	}
	return errors;
}

/*
 * The ulines {} block parser
*/
int	_conf_ulines(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigItem_ulines *ca;

	for (cep = ce->items; cep; cep = cep->next)
	{
		ca = safe_alloc(sizeof(ConfigItem_ulines));
		safe_strdup(ca->servername, cep->name);
		AddListItem(ca, conf_ulines);
	}
	return 1;
}

int	_test_ulines(ConfigFile *conf, ConfigEntry *ce)
{
	/* No check needed */
	return 0;
}

int     _conf_tld(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigItem_tld *ca;

	ca = safe_alloc(sizeof(ConfigItem_tld));

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
			conf_match_block(conf, cep, &ca->match);
		else if (!strcmp(cep->name, "motd"))
		{
			safe_strdup(ca->motd_file, cep->value);
			read_motd(cep->value, &ca->motd);
		}
		else if (!strcmp(cep->name, "shortmotd"))
		{
			safe_strdup(ca->smotd_file, cep->value);
			read_motd(cep->value, &ca->smotd);
		}
		else if (!strcmp(cep->name, "opermotd"))
		{
			safe_strdup(ca->opermotd_file, cep->value);
			read_motd(cep->value, &ca->opermotd);
		}
		else if (!strcmp(cep->name, "botmotd"))
		{
			safe_strdup(ca->botmotd_file, cep->value);
			read_motd(cep->value, &ca->botmotd);
		}
		else if (!strcmp(cep->name, "rules"))
		{
			safe_strdup(ca->rules_file, cep->value);
			read_motd(cep->value, &ca->rules);
		}
		else if (!strcmp(cep->name, "options"))
		{
			ConfigEntry *cepp;
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
					ca->options |= TLD_TLS;
				else if (!strcmp(cepp->name, "remote"))
					ca->options |= TLD_REMOTE;
			}
		}
		else if (!strcmp(cep->name, "channel"))
			safe_strdup(ca->channel, cep->value);
	}
	AddListItem(ca, conf_tld);
	return 1;
}

int     _test_tld(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int	    errors = 0;
	int	    fd = -1;
	char has_mask = 0, has_match = 0, has_motd = 0, has_rules = 0, has_shortmotd = 0;
	char has_channel = 0, has_opermotd = 0, has_botmotd = 0, has_options = 0;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!cep->value && strcmp(cep->name, "options") && strcmp(cep->name, "mask") && strcmp(cep->name, "match"))
		{
			config_error_empty(cep->file->filename, cep->line_number,
				"tld", cep->name);
			errors++;
			continue;
		}
		/* tld::mask */
		if (!strcmp(cep->name, "mask"))
		{
			if (cep->value || cep->items)
			{
				has_mask = 1;
				test_match_block(conf, cep, &errors);
			}
		}
		else if (!strcmp(cep->name, "match"))
		{
			if (cep->value || cep->items)
			{
				has_match = 1;
				test_match_block(conf, cep, &errors);
			}
		}
		/* tld::motd */
		else if (!strcmp(cep->name, "motd"))
		{
			if (has_motd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "tld::motd");
				continue;
			}
			has_motd = 1;
			convert_to_absolute_path(&cep->value, CONFDIR);
			if (((fd = open(cep->value, O_RDONLY)) == -1))
			{
				config_error("%s:%i: tld::motd: %s: %s",
					cep->file->filename, cep->line_number,
					cep->value, strerror(errno));
				errors++;
			}
			else
				close(fd);
		}
		/* tld::rules */
		else if (!strcmp(cep->name, "rules"))
		{
			if (has_rules)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "tld::rules");
				continue;
			}
			has_rules = 1;
			convert_to_absolute_path(&cep->value, CONFDIR);
			if (((fd = open(cep->value, O_RDONLY)) == -1))
			{
				config_error("%s:%i: tld::rules: %s: %s",
					cep->file->filename, cep->line_number,
					cep->value, strerror(errno));
				errors++;
			}
			else
				close(fd);
		}
		/* tld::channel */
		else if (!strcmp(cep->name, "channel"))
		{
			if (has_channel)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "tld::channel");
				continue;
			}
			has_channel = 1;
		}
		/* tld::shortmotd */
		else if (!strcmp(cep->name, "shortmotd"))
		{
			if (has_shortmotd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "tld::shortmotd");
				continue;
			}
			has_shortmotd = 1;
			convert_to_absolute_path(&cep->value, CONFDIR);
			if (((fd = open(cep->value, O_RDONLY)) == -1))
			{
				config_error("%s:%i: tld::shortmotd: %s: %s",
					cep->file->filename, cep->line_number,
					cep->value, strerror(errno));
				errors++;
			}
			else
				close(fd);
		}
		/* tld::opermotd */
		else if (!strcmp(cep->name, "opermotd"))
		{
			if (has_opermotd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "tld::opermotd");
				continue;
			}
			has_opermotd = 1;
			convert_to_absolute_path(&cep->value, CONFDIR);
			if (((fd = open(cep->value, O_RDONLY)) == -1))
			{
				config_error("%s:%i: tld::opermotd: %s: %s",
					cep->file->filename, cep->line_number,
					cep->value, strerror(errno));
				errors++;
			}
			else
				close(fd);
		}
		/* tld::botmotd */
		else if (!strcmp(cep->name, "botmotd"))
		{
			if (has_botmotd)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "tld::botmotd");
				continue;
			}
			has_botmotd = 1;
			convert_to_absolute_path(&cep->value, CONFDIR);
			if (((fd = open(cep->value, O_RDONLY)) == -1))
			{
				config_error("%s:%i: tld::botmotd: %s: %s",
					cep->file->filename, cep->line_number,
					cep->value, strerror(errno));
				errors++;
			}
			else
				close(fd);
		}
		/* tld::options */
		else if (!strcmp(cep->name, "options")) {
			ConfigEntry *cep2;

			if (has_options)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "tld::options");
				continue;
			}
			has_options = 1;

			for (cep2 = cep->items; cep2; cep2 = cep2->next)
			{
				if (strcmp(cep2->name, "ssl") &&
				    strcmp(cep2->name, "tls") &&
				    strcmp(cep2->name, "remote"))
				{
					config_error_unknownopt(cep2->file->filename,
						cep2->line_number, "tld", cep2->name);
					errors++;
				}
			}
		}
		else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"tld", cep->name);
			errors++;
			continue;
		}
	}
	if (!has_mask && !has_match)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"tld::match");
		errors++;
	}
	if (has_mask && has_match)
	{
		config_error("%s:%d: You cannot have both ::mask and ::match. "
		             "You should only use %s::match.",
		             ce->file->filename, ce->line_number, ce->name);
		errors++;
	}
	return errors;
}

/* Helper for _conf_listen() */
void conf_listen_configure(const char *ip, int port, SocketType socket_type, int options, ConfigEntry *ce, ConfigEntry *tlsconfig)
{
	ConfigItem_listen *listen;
	ConfigEntry *cep, *cepp;
	Hook *h;
	char isnew = 0;

	if (!(listen = find_listen(ip, port, socket_type)))
	{
		listen = safe_alloc(sizeof(ConfigItem_listen));
		if (socket_type == SOCKET_TYPE_UNIX)
		{
			safe_strdup(listen->file, ip);
		} else {
			safe_strdup(listen->ip, ip);
			listen->port = port;
		}
		listen->fd = -1;
		listen->socket_type = socket_type;
		isnew = 1;
	}

	if (listen->options & LISTENER_BOUND)
		options |= LISTENER_BOUND;
	listen->options = options;

	if (isnew)
		AddListItem(listen, conf_listen);

	/* Reset all settings of the current listener (free and set defaults): */
	listen->flag.temporary = 0;
	listen->start_handshake = start_of_normal_client_handshake;
	if (listen->ssl_ctx)
	{
		SSL_CTX_free(listen->ssl_ctx);
		listen->ssl_ctx = NULL;
	}
	if (listen->tls_options)
	{
		free_tls_options(listen->tls_options);
		listen->tls_options = NULL;
	}
	safe_free_webserver(listen->webserver);
	free_entire_name_list(listen->websocket_origin);
	listen->websocket_options = 0;
	// NOTE: duplicate code overlap with listen_cleanup()

	/* Now set the new settings: */
	if (tlsconfig)
	{
		listen->tls_options = safe_alloc(sizeof(TLSOptions));
		conf_tlsblock(conf, tlsconfig, listen->tls_options);
		listen->ssl_ctx = init_ctx(listen->tls_options, 1);
	}

	/* For modules that hook CONFIG_LISTEN and CONFIG_LISTEN_OPTIONS.
	 * Yeah, ugly we have this here..
	 * and again about 100 lines down too.
	 */
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "mode"))
		{
			/* Yeah, we actually do something with this one.. */
			if (cep->value)
				listen->mode = strtol(cep->value, NULL, 8); /* octal */
		}
		else if (!strcmp(cep->name, "spoof-ip"))
			safe_strdup(listen->spoof_ip, cep->value);
		else if (!strcmp(cep->name, "ip"))
			;
		else if (!strcmp(cep->name, "port"))
			;
		else if (!strcmp(cep->name, "options"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				NameValue *ofp;
				if (!nv_find_by_name(_ListenerFlags, cepp->name))
				{
					for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
					{
						int value = (*(h->func.intfunc))(conf, cepp, CONFIG_LISTEN_OPTIONS, listen);
						if (value == 1)
							break;
					}
				}
			}
		} else
		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
			;
		else
		{
			for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
			{
				int value = (*(h->func.intfunc))(conf, cep, CONFIG_LISTEN, listen);
				if (value == 1)
					break;
			}
		}
	}

	RunHook(HOOKTYPE_CONFIG_LISTENER, listen);
}

int	_conf_listen(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp;
	ConfigEntry *tlsconfig = NULL;
	char *file = NULL;
	char *ip = NULL;
	char *spoof_ip = NULL;
	int start=0, end=0, port;
	int listener_flags =0;
	Hook *h;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "file"))
		{
			convert_to_absolute_path(&cep->value, PERMDATADIR);
			file = cep->value;
		} else
		if (!strcmp(cep->name, "mode"))
		{
			// Handled elsewhere, but need to be caught here as noop
		} else
		if (!strcmp(cep->name, "ip"))
		{
			ip = cep->value;
		} else
		if (!strcmp(cep->name, "spoof-ip"))
		{
			spoof_ip = cep->value;
		} else
		if (!strcmp(cep->name, "port"))
		{
			port_range(cep->value, &start, &end);
			if ((start < 0) || (start > 65535) || (end < 0) || (end > 65535))
				return -1; /* this is already validated in _test_listen, but okay.. */
		} else
		if (!strcmp(cep->name, "options"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				long v;
				if ((v = nv_find_by_name(_ListenerFlags, cepp->name)))
				{
					listener_flags |= v;
				} else {
					for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
					{
						int value = (*(h->func.intfunc))(conf, cepp, CONFIG_LISTEN_OPTIONS);
						if (value == 1)
							break;
					}
				}
			}
		} else
		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
		{
			tlsconfig = cep;
		} else
		{
			for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
			{
				int value = (*(h->func.intfunc))(conf, cep, CONFIG_LISTEN);
				if (value == 1)
					break;
			}
		}
	}

	/* UNIX domain socket */
	if (file)
	{
		conf_listen_configure(file, 0, SOCKET_TYPE_UNIX, listener_flags, ce, tlsconfig);
		return 1;
	}

	for (port = start; port <= end; port++)
	{
		/* First deal with IPv4 */
		if (!strchr(ip, ':'))
			conf_listen_configure(ip, port, SOCKET_TYPE_IPV4, listener_flags, ce, tlsconfig);

		/* Then deal with IPv6 (if available/enabled) */
		if (!DISABLE_IPV6 && (strchr(ip, ':') || (*ip == '*')))
			conf_listen_configure(ip, port, SOCKET_TYPE_IPV6, listener_flags, ce, tlsconfig);
	}

	return 1;
}

int	_test_listen(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigEntry *cepp;
	int errors = 0;
	char has_file = 0, has_ip = 0, has_port = 0, has_options = 0, port_6667 = 0, has_spoof_ip = 0;
	char clientport = 1;
	char *file = NULL;
	char *ip = NULL;
	int port_start = 0, port_end = 0, tls_port = 0;
	Hook *h;

	if (ce->value)
	{
		config_error("%s:%i: listen block has a new syntax, see https://www.unrealircd.org/docs/Listen_block",
			ce->file->filename, ce->line_number);
		return 1;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		int used_by_module = 0;

		/* First, check if a module knows about this listen::something */
		for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
		{
			int value, errs = 0;
			if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
			    && !(h->owner->options & MOD_OPT_PERM))
			{
				continue;
			}
			value = (*(h->func.intfunc))(conf, cep, CONFIG_LISTEN, &errs);
			if (value == 2)
				used_by_module = 1;
			if (value == 1)
			{
				used_by_module = 1;
				break;
			}
			if (value == -1)
			{
				used_by_module = 1;
				errors += errs;
				break;
			}
			if (value == -2)
			{
				used_by_module = 1;
				errors += errs;
			}
		}
		if (!strcmp(cep->name, "options"))
		{
			if (has_options)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "listen::options");
				continue;
			}
			has_options = 1;
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "serversonly") ||
				    !strcmp(cepp->name, "rpc"))
					clientport = 0;
				if (!nv_find_by_name(_ListenerFlags, cepp->name))
				{
					/* Check if a module knows about this listen::options::something */
					int used_by_module = 0;
					for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
					{
						int value, errs = 0;
						if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
						    && !(h->owner->options & MOD_OPT_PERM))
						{
							continue;
						}
						value = (*(h->func.intfunc))(conf, cepp, CONFIG_LISTEN_OPTIONS, &errs);
						if (value == 2)
							used_by_module = 1;
						if (value == 1)
						{
							used_by_module = 1;
							break;
						}
						if (value == -1)
						{
							used_by_module = 1;
							errors += errs;
							break;
						}
						if (value == -2)
						{
							used_by_module = 1;
							errors += errs;
						}
					}
					if (!used_by_module)
					{
						config_error_unknownopt(cepp->file->filename,
							cepp->line_number, "listen::options", cepp->name);
						errors++;
						continue;
					}
				}
				if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
				{
					have_tls_listeners = 1; /* for ssl config test */
					tls_port = 1;
				}
			}
		}
		else
		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
		{
			test_tlsblock(conf, cep, &errors);
		}
		else
		if (!cep->value)
		{
			if (!used_by_module)
			{
				config_error_empty(cep->file->filename,
					cep->line_number, "listen", cep->name);
				errors++;
			}
			continue; /* always */
		} else
		if (!strcmp(cep->name, "file"))
		{
			has_file = 1;
			clientport = 0;
			file = cep->value;
		} else
		if (!strcmp(cep->name, "spoof-ip"))
		{
			has_spoof_ip = 1;
			if (!is_valid_ip(cep->value))
			{
				config_error("%s:%i: listen::spoof-ip is not a valid IP address (%s)",
				             cep->file->filename, cep->line_number, cep->value);
				errors++;
			}
		} else
		if (!strcmp(cep->name, "mode"))
		{
			int mode = strtol(cep->value, NULL, 8);
			if ((mode != 0700) && (mode != 0770) && (mode != 0777))
			{
				config_error("%s:%i: listen::mode must be one of: 0700 (user only, the default), "
				             "0770 (user and group readable/writable), or "
				             "0777 (world readable and writable, not recommended).",
				             cep->file->filename, cep->line_number);
				errors++;
			}
		} else
		if (!strcmp(cep->name, "ip"))
		{
			has_ip = 1;

			if (strcmp(cep->value, "*") && !is_valid_ip(cep->value))
			{
				config_error("%s:%i: listen: illegal listen::ip (%s). Must be either '*' or contain a valid IP.",
					cep->file->filename, cep->line_number, cep->value);
				return 1;
			}
			ip = cep->value;
		} else
		if (!strcmp(cep->name, "host"))
		{
			config_error("%s:%i: listen: unknown option listen::host, did you mean listen::ip?",
				cep->file->filename, cep->line_number);
			errors++;
		} else
		if (!strcmp(cep->name, "port"))
		{
			port_start = port_end = 0;

			has_port = 1;

			if (strchr(cep->value, ','))
			{
				config_error("%s:%i: listen::port does not support comma's",
				             cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
			port_range(cep->value, &port_start, &port_end);
			if (port_start == port_end)
			{
				if ((port_start < 1) || (port_start > 65535))
				{
					config_error("%s:%i: listen: illegal port (must be 1..65535)",
						cep->file->filename, cep->line_number);
					errors++;
					continue;
				}
			}
			else
			{
				if (port_end < port_start)
				{
					config_error("%s:%i: listen: illegal port range end value is less than starting value",
						cep->file->filename, cep->line_number);
					errors++;
					continue;
				}
				if (port_end - port_start >= 100)
				{
					config_error("%s:%i: listen: you requested port %d-%d, that's %d ports "
						"(and thus consumes %d sockets) this is probably not what you want.",
						cep->file->filename, cep->line_number, port_start, port_end,
						port_end - port_start + 1, port_end - port_start + 1);
					errors++;
					continue;
				}
				if ((port_start < 1) || (port_start > 65535) || (port_end < 1) || (port_end > 65535))
				{
					config_error("%s:%i: listen: illegal port range values must be between 1 and 65535",
						cep->file->filename, cep->line_number);
					errors++;
					continue;
				}
			}

			if ((6667 >= port_start) && (6667 <= port_end))
				port_6667 = 1;
		} else
		{
			if (!used_by_module)
			{
				config_error_unknown(cep->file->filename, cep->line_number,
					"listen", cep->name);
				errors++;
			}
			continue; /* always */
		}
	}

	if (has_file)
	{
		if (has_ip || has_port)
		{
			config_error("%s:%d: listen block should either have a 'file' (for *NIX domain socket), "
			             "OR have an 'ip' and 'port' (for IPv4/IPv6). You cannot combine both in one listen block.",
			             ce->file->filename, ce->line_number);
			errors++;
		} else {
			// TODO: check if file can be created fresh etc.
		}
	} else
	{
		if (!has_ip)
		{
			config_error("%s:%d: listen block requires an listen::ip",
				ce->file->filename, ce->line_number);
			errors++;
		}

		if (!has_port)
		{
			config_error("%s:%d: listen block requires an listen::port",
				ce->file->filename, ce->line_number);
			errors++;
		}
	}

	if (has_spoof_ip && !has_file)
	{
		config_error("%s:%d: listen::spoof-ip is only valid when listen::file is used (UNIX domain sockets)",
		             ce->file->filename, ce->line_number);
		errors++;
	}

	if (port_6667)
		safe_strdup(port_6667_ip, ip);

	if (clientport && (!ip || (strcmp(ip, "127.0.0.1") && strcmp(ip, "::1"))))
	{
		has_client_port = 1;

		if (!bestpractices.listen_nontls_port && !tls_port && (port_start>0))
			bestpractices.listen_nontls_port = port_start;
	}

	requiredstuff.conf_listen = 1;
	return errors;
}


int	_conf_allow(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp;
	ConfigItem_allow *allow;
	Hook *h;

	if (ce->value)
	{
		if (!strcmp(ce->value, "channel"))
			return (_conf_allow_channel(conf, ce));
		else
		{
			int value;
			for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
			{
				value = (*(h->func.intfunc))(conf, ce, CONFIG_ALLOW);
				if (value == 1)
					break;
			}
			return 0;
		}
	}
	allow = safe_alloc(sizeof(ConfigItem_allow));
	allow->match = safe_alloc(sizeof(SecurityGroup));

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask") || !strcmp(cep->name, "ip") || !strcmp(cep->name, "hostname"))
		{
			conf_match_block(conf, cep, &allow->match);
		}
		else if (!strcmp(cep->name, "password"))
			AuthBlockToAuthConfig(cep, &allow->auth);
		else if (!strcmp(cep->name, "class"))
		{
			allow->class = find_class(cep->value);
			if (!allow->class || (allow->class->flag.temporary == 1))
			{
				config_status("%s:%i: illegal allow::class, unknown class '%s' using default of class 'default'",
					cep->file->filename,
					cep->line_number,
					cep->value);
					allow->class = default_class;
			}
		}
		else if (!strcmp(cep->name, "redirect-server"))
			safe_strdup(allow->server, cep->value);
		else if (!strcmp(cep->name, "redirect-port"))
			allow->port = atoi(cep->value);
		else if (!strcmp(cep->name, "options"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "noident"))
					allow->flags.noident = 1;
				else if (!strcmp(cepp->name, "useip"))
					allow->flags.useip = 1;
				else if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
					allow->flags.tls = 1;
				else if (!strcmp(cepp->name, "reject-on-auth-failure"))
					allow->flags.reject_on_auth_failure = 1;
			}
		}
		for (h = Hooks[HOOKTYPE_CONFIGRUN_EX]; h; h = h->next)
		{
			int value = (*(h->func.intfunc))(conf, cep, CONFIG_ALLOW_BLOCK, allow);
			if (value == 1)
				break;
		}
	}

	AddListItem(allow, conf_allow);
	return 1;
}

int	_test_allow(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp;
	int		errors = 0;
	Hook *h;
	int has_ip = 0, has_hostname = 0, has_mask = 0, has_match = 0;
	int has_password = 0, has_class = 0;
	int has_redirectserver = 0, has_redirectport = 0, has_options = 0;
	int hostname_possible_silliness = 0;

	if (ce->value)
	{
		if (!strcmp(ce->value, "channel"))
			return (_test_allow_channel(conf, ce));
		else
		{
			int used = 0;
			for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
			{
				int value, errs = 0;
				if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
				    && !(h->owner->options & MOD_OPT_PERM))
					continue;
				value = (*(h->func.intfunc))(conf, ce, CONFIG_ALLOW, &errs);
				if (value == 2)
					used = 1;
				if (value == 1)
				{
					used = 1;
					break;
				}
				if (value == -1)
				{
					used = 1;
					errors += errs;
					break;
				}
				if (value == -2)
				{
					used = 1;
					errors += errs;
				}
			}
			if (!used) {
				config_error("%s:%i: allow item with unknown type",
					ce->file->filename, ce->line_number);
				return 1;
			}
			return errors;
		}
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		int used_by_module = 0;

		/* First, check if a module knows about this allow::something */
		for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
		{
			int value, errs = 0;
			if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
			    && !(h->owner->options & MOD_OPT_PERM))
			{
				continue;
			}
			value = (*(h->func.intfunc))(conf, cep, CONFIG_ALLOW_BLOCK, &errs);
			if (value == 2)
				used_by_module = 1;
			if (value == 1)
			{
				used_by_module = 1;
				break;
			}
			if (value == -1)
			{
				used_by_module = 1;
				errors += errs;
				break;
			}
			if (value == -2)
			{
				used_by_module = 1;
				errors += errs;
			}
		}
		if (used_by_module)
			continue;

		if (strcmp(cep->name, "options") &&
		    strcmp(cep->name, "match") &&
		    strcmp(cep->name, "mask") &&
		    config_is_blankorempty(cep, "allow"))
		{
			errors++;
			continue;
		}
		if (!strcmp(cep->name, "ip"))
		{
			if (has_ip)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "allow::ip");
				continue;
			}
			has_ip = 1;
		}
		else if (!strcmp(cep->name, "hostname"))
		{
			if (has_hostname)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "allow::hostname");
				continue;
			}
			has_hostname = 1;
			if (!strcmp(cep->value, "*@*") || !strcmp(cep->value, "*"))
				hostname_possible_silliness = 1;
		}
		else if (!strcmp(cep->name, "mask"))
		{
			has_mask = 1;
			test_match_block(conf, cep, &errors);
		}
		else if (!strcmp(cep->name, "match"))
		{
			has_match = 1;
			test_match_block(conf, cep, &errors);
		}
		else if (!strcmp(cep->name, "ipv6-clone-mask"))
		{
			config_error("%s:%d: allow::ipv6-clone-mask has no effect. "
			             "Use the global set::default-ipv6-clone-mask setting instead.",
			             cep->file->filename, cep->line_number);
			errors++;
		}
		else if (!strcmp(cep->name, "password"))
		{
			has_password = 1;
			if (Auth_CheckError(cep, 0) < 0)
				errors++;
		}
		else if (!strcmp(cep->name, "class"))
		{
			if (has_class)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "allow::class");
				continue;
			}
			has_class = 1;
		}
		else if (!strcmp(cep->name, "redirect-server"))
		{
			if (has_redirectserver)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "allow::redirect-server");
				continue;
			}
			has_redirectserver = 1;
		}
		else if (!strcmp(cep->name, "redirect-port"))
		{
			if (has_redirectport)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "allow::redirect-port");
				continue;
			}
			has_redirectport = 1;
		}
		else if (!strcmp(cep->name, "options"))
		{
			if (has_options)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "allow::options");
				continue;
			}
			has_options = 1;
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "noident"))
				{}
				else if (!strcmp(cepp->name, "useip"))
				{}
				else if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
				{}
				else if (!strcmp(cepp->name, "reject-on-auth-failure"))
				{}
				else if (!strcmp(cepp->name, "sasl"))
				{
					config_error("%s:%d: The option allow::options::sasl no longer exists. "
					             "Please use a require authentication { } block instead, which "
					             "is more flexible and provides the same functionality. See "
					             "https://www.unrealircd.org/docs/Require_authentication_block",
					             cepp->file->filename, cepp->line_number);
					errors++;
				}
				else
				{
					config_error_unknownopt(cepp->file->filename,
						cepp->line_number, "allow", cepp->name);
					errors++;
				}
			}
		}
		else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"allow", cep->name);
			errors++;
			continue;
		}
	}

	if ((has_mask || has_match) && (has_ip || has_hostname))
	{
		config_error("%s:%d: The allow block uses allow::match, but you also have an allow::ip and allow::hostname.",
			ce->file->filename, ce->line_number);
		config_error("Please delete your allow::ip and allow::hostname entries and/or integrate them into allow::match");
	} else
	if (has_ip)
	{
		config_warn("%s:%d: The allow block uses allow::match nowadays. Rename your allow::ip item to allow::match.",
			ce->file->filename, ce->line_number);
		config_warn("See https://www.unrealircd.org/docs/FAQ#allow-mask for more information");
	} else
	if (has_hostname)
	{
		config_warn("%s:%d: The allow block uses allow::match nowadays. Rename your allow::hostname item to allow::match.",
			ce->file->filename, ce->line_number);
		config_warn("See https://www.unrealircd.org/docs/FAQ#allow-mask for more information");
	} else
	if (has_mask && has_match)
	{
		config_error("%s:%d: You cannot have both ::mask and ::match. You should only use allow::match.",
				 ce->file->filename, ce->line_number);
		errors++;
	} else
	if (!has_match && !has_mask)
	{
		config_error("%s:%d: allow block needs an allow::match",
				 ce->file->filename, ce->line_number);
		errors++;
	}

	if (has_ip && has_hostname)
	{
		config_error("%s:%d: allow block has both allow::ip and allow::hostname, this is no longer permitted.",
		             ce->file->filename, ce->line_number);
		config_error("Please integrate your allow::ip and allow::hostname items into a single allow::mask block");
		errors++;
	} else
	if (hostname_possible_silliness)
	{
		config_error("%s:%d: allow block contains 'hostname *;'. This means means that users "
		             "without a valid hostname (unresolved IP's) will be unable to connect. "
		             "You most likely want to use 'mask *;' instead.",
		             ce->file->filename, ce->line_number);
	}

	if (!has_class)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"allow::class");
		errors++;
	}

	return errors;
}

int	_conf_allow_channel(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigItem_allow_channel 	*allow = NULL;
	ConfigEntry 	    	*cep;
	char *class = NULL;
	ConfigEntry *match = NULL;

	/* First, search for ::class, if any */
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "class"))
			class = cep->value;
		else if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
			match = cep;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "channel"))
		{
			/* This way, we permit multiple ::channel items in one allow block */
			allow = safe_alloc(sizeof(ConfigItem_allow_channel));
			safe_strdup(allow->channel, cep->value);
			if (class)
				safe_strdup(allow->class, class);
			if (match)
				conf_match_block(conf, match, &allow->match);
			AddListItem(allow, conf_allow_channel);
		}
	}
	return 1;
}

int	_test_allow_channel(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry	*cep;
	int		errors = 0;
	char		has_match = 0, has_mask = 0, has_channel = 0, has_class = 0;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (config_is_blankorempty(cep, "allow channel"))
		{
			errors++;
			continue;
		}

		if (!strcmp(cep->name, "channel"))
		{
			has_channel = 1;
		}
		else if (!strcmp(cep->name, "class"))
		{

			if (has_class)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "allow channel::class");
				continue;
			}
			has_class = 1;
		}
		else if (!strcmp(cep->name, "match"))
		{
			has_match = 1;
			test_match_block(conf, cep, &errors);
		}
		else if (!strcmp(cep->name, "mask"))
		{
			has_mask = 1;
			test_match_block(conf, cep, &errors);
		}
		else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"allow channel", cep->name);
			errors++;
		}
	}
	if (has_mask && has_match)
	{
		config_error("%s:%d: You cannot have both ::mask and ::match. "
		             "You should only use %s::match.",
		             ce->file->filename, ce->line_number, ce->name);
		errors++;
	}
	if (!has_channel)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"allow channel::channel");
		errors++;
	}
	return errors;
}

int _conf_except(ConfigFile *conf, ConfigEntry *ce)
{
	Hook *h;
	int value;

	for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
	{
		value = (*(h->func.intfunc))(conf,ce,CONFIG_EXCEPT);
		if (value == 1)
			break;
	}

	return 1;
}

int _test_except(ConfigFile *conf, ConfigEntry *ce)
{
	int errors = 0;
	Hook *h;
	int used = 0;

	if (!ce->value)
	{
		config_error("%s:%i: except without type",
			ce->file->filename, ce->line_number);
		return 1;
	}

	if (!strcmp(ce->value, "tkl"))
	{
		config_warn("%s:%i: except tkl { } is now called except ban { }. "
		            "Simply rename the block from 'except tkl' to 'except ban' "
		            "to get rid of this warning.",
		            ce->file->filename, ce->line_number);
		safe_strdup(ce->value, "ban"); /* awww */
	}

	for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
	{
		int value, errs = 0;
		if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
		    && !(h->owner->options & MOD_OPT_PERM))
			continue;
		value = (*(h->func.intfunc))(conf,ce,CONFIG_EXCEPT,&errs);
		if (value == 2)
			used = 1;
		if (value == 1)
		{
			used = 1;
			break;
		}
		if (value == -1)
		{
			used = 1;
			errors += errs;
			break;
		}
		if (value == -2)
		{
			used = 1;
			errors += errs;
		}
	}

	if (!used)
	{
		config_error("%s:%i: unknown except type %s",
			ce->file->filename, ce->line_number,
			ce->value);
		return 1;
	}

	return errors;
}

int	_test_sni(ConfigFile *conf, ConfigEntry *ce)
{
	int errors = 0;
	ConfigEntry *cep;

	if (!ce->value)
	{
		config_error("%s:%i: sni block needs a name, eg: sni irc.xyz.com {",
			ce->file->filename, ce->line_number);
		errors++;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
		{
			test_tlsblock(conf, cep, &errors);
		} else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"sni", cep->name);
			errors++;
			continue;
		}
	}

	return errors;
}

int	_conf_sni(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigEntry *tlsconfig = NULL;
	char *name;
	ConfigItem_sni *sni = NULL;

	name = ce->value;
	if (!name)
		return 0;

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
		{
			tlsconfig = cep;
		}
	}

	if (!tlsconfig)
		return 0;

	sni = safe_alloc(sizeof(ConfigItem_listen));
	safe_strdup(sni->name, name);
	sni->tls_options = safe_alloc(sizeof(TLSOptions));
	conf_tlsblock(conf, tlsconfig, sni->tls_options);
	sni->ssl_ctx = init_ctx(sni->tls_options, 1);
	AddListItem(sni, conf_sni);

	return 1;
}

int     _conf_help(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigItem_help *ca;
	MOTDLine *last = NULL, *temp;
	ca = safe_alloc(sizeof(ConfigItem_help));

	if (!ce->value)
		ca->command = NULL;
	else
		safe_strdup(ca->command, ce->value);

	for (cep = ce->items; cep; cep = cep->next)
	{
		temp = safe_alloc(sizeof(MOTDLine));
		safe_strdup(temp->line, cep->name);
		temp->next = NULL;
		if (!last)
			ca->text = temp;
		else
			last->next = temp;
		last = temp;
	}
	AddListItem(ca, conf_help);
	return 1;

}

int _test_help(ConfigFile *conf, ConfigEntry *ce) {
	int errors = 0;
	ConfigEntry *cep;
	if (!ce->items)
	{
		config_error("%s:%i: empty help block",
			ce->file->filename, ce->line_number);
		return 1;
	}
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (strlen(cep->name) > 500)
		{
			config_error("%s:%i: oversized help item",
				ce->file->filename, ce->line_number);
			errors++;
			continue;
		}
	}
	return errors;
}

int	_conf_link(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp, *ceppp;
	ConfigItem_link *link = NULL;

	link = safe_alloc(sizeof(ConfigItem_link));
	safe_strdup(link->servername, ce->value);

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "incoming"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "match") || !strcmp(cepp->name, "mask"))
				{
					conf_match_block(conf, cepp, &link->incoming.match);
				}
			}
		}
		else if (!strcmp(cep->name, "outgoing"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "bind-ip"))
					safe_strdup(link->outgoing.bind_ip, cepp->value);
				else if (!strcmp(cepp->name, "file"))
					safe_strdup(link->outgoing.file, cepp->value);
				else if (!strcmp(cepp->name, "hostname"))
					safe_strdup(link->outgoing.hostname, cepp->value);
				else if (!strcmp(cepp->name, "port"))
					link->outgoing.port = atoi(cepp->value);
				else if (!strcmp(cepp->name, "options"))
				{
					link->outgoing.options = 0;
					for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
					{
						long v;
						if ((v = nv_find_by_name(_LinkOutgoingFlags, ceppp->name)))
							link->outgoing.options |= v;
					}
				}
				else if (!strcmp(cepp->name, "ssl-options") || !strcmp(cepp->name, "tls-options"))
				{
					link->tls_options = safe_alloc(sizeof(TLSOptions));
					conf_tlsblock(conf, cepp, link->tls_options);
					link->ssl_ctx = init_ctx(link->tls_options, 0);
				}
			}
		}
		else if (!strcmp(cep->name, "password"))
			AuthBlockToAuthConfig(cep, &link->auth);
		else if (!strcmp(cep->name, "hub"))
			safe_strdup(link->hub, cep->value);
		else if (!strcmp(cep->name, "leaf"))
			safe_strdup(link->leaf, cep->value);
		else if (!strcmp(cep->name, "leaf-depth") || !strcmp(cep->name, "leafdepth"))
			link->leaf_depth = atoi(cep->value);
		else if (!strcmp(cep->name, "class"))
		{
			link->class = find_class(cep->value);
			if (!link->class || (link->class->flag.temporary == 1))
			{
				config_status("%s:%i: illegal link::class, unknown class '%s' using default of class 'default'",
					cep->file->filename,
					cep->line_number,
					cep->value);
				link->class = default_class;
			}
			link->class->xrefcount++;
		}
		else if (!strcmp(cep->name, "verify-certificate"))
		{
			link->verify_certificate = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "options"))
		{
			link->options = 0;
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				long v;
				if ((v = nv_find_by_name(_LinkFlags, cepp->name)))
					link->options |= v;
			}
		}
	}

	/* The default is 'hub *', unless you specify leaf or hub manually. */
	if (!link->hub && !link->leaf)
		safe_strdup(link->hub, "*");

	AppendListItem(link, conf_link);
	return 0;
}

int	_test_link(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp, *ceppp;
	int errors = 0;

	int has_incoming = 0, has_incoming_mask = 0, has_incoming_match = 0, has_outgoing = 0, has_outgoing_file = 0;
	int has_outgoing_bind_ip = 0, has_outgoing_hostname = 0, has_outgoing_port = 0;
	int has_outgoing_options = 0, has_hub = 0, has_leaf = 0, has_leaf_depth = 0;
	int has_password = 0, has_class = 0, has_options = 0, has_tls_options = 0;

	if (!ce->value)
	{
		config_error("%s:%i: link without servername. Expected: link servername { ... }",
			ce->file->filename, ce->line_number);
		return 1;

	}

	if (!strchr(ce->value, '.'))
	{
		config_error("%s:%i: link: bogus server name. Expected: link servername { ... }",
			ce->file->filename, ce->line_number);
		return 1;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "incoming"))
		{
			config_detect_duplicate(&has_incoming, cep, &errors);
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "match"))
				{
					if (cepp->value || cepp->items)
					{
						has_incoming_match = 1;
						test_match_block(conf, cepp, &errors);
					} else
					if (config_is_blankorempty(cepp, "link::incoming"))
					{
						errors++;
						continue;
					}
				} else
				if (!strcmp(cepp->name, "mask"))
				{
					if (cepp->value || cepp->items)
					{
						has_incoming_mask = 1;
						test_match_block(conf, cepp, &errors);
					} else
					if (config_is_blankorempty(cepp, "link::incoming"))
					{
						errors++;
						continue;
					}
				}
			}
		}
		else if (!strcmp(cep->name, "outgoing"))
		{
			config_detect_duplicate(&has_outgoing, cep, &errors);
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "bind-ip"))
				{
					if (config_is_blankorempty(cepp, "link::outgoing"))
					{
						errors++;
						continue;
					}
					config_detect_duplicate(&has_outgoing_bind_ip, cepp, &errors);
					// todo: ipv4 vs ipv6
				}
				else if (!strcmp(cepp->name, "file"))
				{
					if (config_is_blankorempty(cepp, "link::outgoing"))
					{
						errors++;
						continue;
					}
					config_detect_duplicate(&has_outgoing_file, cepp, &errors);
				}
				else if (!strcmp(cepp->name, "hostname"))
				{
					if (config_is_blankorempty(cepp, "link::outgoing"))
					{
						errors++;
						continue;
					}
					config_detect_duplicate(&has_outgoing_hostname, cepp, &errors);
					if (strchr(cepp->value, '*') || strchr(cepp->value, '?'))
					{
						config_error("%s:%i: hostname in link::outgoing(!) cannot contain wildcards",
							cepp->file->filename, cepp->line_number);
						errors++;
					}
				}
				else if (!strcmp(cepp->name, "port"))
				{
					if (config_is_blankorempty(cepp, "link::outgoing"))
					{
						errors++;
						continue;
					}
					config_detect_duplicate(&has_outgoing_port, cepp, &errors);
				}
				else if (!strcmp(cepp->name, "options"))
				{
					config_detect_duplicate(&has_outgoing_options, cepp, &errors);
					for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
					{
						if (!nv_find_by_name(_LinkOutgoingFlags, ceppp->name))
						{
							config_error_unknownopt(ceppp->file->filename,
								ceppp->line_number, "link::outgoing", ceppp->name);
							errors++;
						}
					}
				}
				else if (!strcmp(cepp->name, "ssl-options") || !strcmp(cepp->name, "tls-options"))
				{
					config_detect_duplicate(&has_tls_options, cepp, &errors);
					test_tlsblock(conf, cepp, &errors);
				}
				else
				{
					config_error("%s:%d: Unknown directive '%s'",
					             cepp->file->filename, cepp->line_number,
					             config_var(cepp));
					errors++;
				}
			}
		}
		else if (!strcmp(cep->name, "password"))
		{
			has_password = 1;
			if (Auth_CheckError(cep, 0) < 0)
			{
				errors++;
			} else {
				AuthConfig *auth = NULL;
				AuthBlockToAuthConfig(cep, &auth);
				/* hm. would be nicer if handled @auth-system I think. ah well.. */
				if ((auth->type != AUTHTYPE_PLAINTEXT) && (auth->type != AUTHTYPE_TLS_CLIENTCERT) &&
				    (auth->type != AUTHTYPE_TLS_CLIENTCERTFP) && (auth->type != AUTHTYPE_SPKIFP))
				{
					config_error("%s:%i: password in link block should be plaintext OR should be the "
					             "certificate or SPKI fingerprint of the remote link (=better)",
					             cep->file->filename, cep->line_number);
					errors++;
				}
				Auth_FreeAuthConfig(auth);
			}
		}
		else if (!strcmp(cep->name, "hub"))
		{
			if (config_is_blankorempty(cep, "link"))
			{
				errors++;
				continue;
			}
			config_detect_duplicate(&has_hub, cep, &errors);
		}
		else if (!strcmp(cep->name, "leaf"))
		{
			if (config_is_blankorempty(cep, "link"))
			{
				errors++;
				continue;
			}
			config_detect_duplicate(&has_leaf, cep, &errors);
		}
		else if (!strcmp(cep->name, "leaf-depth") || !strcmp(cep->name, "leafdepth"))
		{
			if (config_is_blankorempty(cep, "link"))
			{
				errors++;
				continue;
			}
			config_detect_duplicate(&has_leaf_depth, cep, &errors);
		}
		else if (!strcmp(cep->name, "class"))
		{
			if (config_is_blankorempty(cep, "link"))
			{
				errors++;
				continue;
			}
			config_detect_duplicate(&has_class, cep, &errors);
		}
		else if (!strcmp(cep->name, "ciphers"))
		{
			config_error("%s:%d: link::ciphers has been moved to link::outgoing::ssl-options::ciphers, "
			             "see https://www.unrealircd.org/docs/FAQ#link::ciphers_no_longer_works",
			             cep->file->filename, cep->line_number);
			errors++;
		}
		else if (!strcmp(cep->name, "verify-certificate"))
		{
			if (config_is_blankorempty(cep, "link"))
			{
				errors++;
				continue;
			}
			if (config_checkval(cep->value, CFG_YESNO))
			{
				unreal_log(ULOG_WARNING, "config", "CONFIG_VERIFY_CERTIFICATE_DEPRECATED", NULL,
				           "$file:$line_number: link::verify-certificate should no longer be used because public "
				           "certificate authorities are dropping the 'Client Authentication EKU' from TLS certs, "
				           "see e.g. https://letsencrypt.org/2025/05/14/ending-tls-client-authentication.\n"
				           "We recommend using a 'dual certificate setup' as outlined in "
				           "https://www.unrealircd.org/docs/Using_Let's_Encrypt_with_UnrealIRCd which will give you: "
				           "1) A short-lived certificate from Let's Encrypt on port 6697 and "
				           "2) A long-lived self-signed certificate used for linking on serversonly port 6900.\n"
				           "And then simply use 'spkifp' for linking on port 6900 as outlined in "
				           "https://www.unrealircd.org/docs/Tutorial:_Linking_servers",
				           log_data_string("file", cep->file->filename),
				           log_data_integer("line_number", cep->line_number));
			}
		}
		else if (!strcmp(cep->name, "options"))
		{
			config_detect_duplicate(&has_options, cep, &errors);
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!nv_find_by_name(_LinkFlags, cepp->name))
				{
					config_error("%s:%d: link::options has only two possible options "
					             "('quarantine' and 'no-certificate-verification', rarely used). "
					             "Option '%s' is unrecognized. "
					             "Perhaps you meant to set an outgoing option in link::outgoing::options instead?",
					             cepp->file->filename, cepp->line_number, cepp->name);
					errors++;
				}
			}
		}
		else
		{
			config_error_unknown(cep->file->filename,
			    cep->line_number, "link", cep->name);
			errors++;
			continue;
		}
	}

	if (!has_incoming && !has_outgoing)
	{
		config_error("%s:%d: link block needs at least an incoming or outgoing section.",
			ce->file->filename, ce->line_number);
		errors++;
	}

	if (has_incoming)
	{
		/* If we have an incoming sub-block then we need at least 'mask' and 'password' */
		if (!has_incoming_mask && !has_incoming_match)
		{
			config_error_missing(ce->file->filename, ce->line_number, "link::incoming::match");
			errors++;
		}
		if (has_incoming_mask && has_incoming_match)
		{
			config_error("%s:%d: You cannot have both link::incoming::mask and link::incoming::match. "
				     "You should only use link::incoming::match.",
				     ce->file->filename, ce->line_number);
			errors++;
		}
	}

	if (has_outgoing)
	{
		/* If we have an outgoing sub-block then we need at least a hostname and port or a file */
		if (!has_outgoing_file)
		{
			if (!has_outgoing_hostname)
			{
				config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::hostname");
				errors++;
			}
			if (!has_outgoing_port)
			{
				config_error_missing(ce->file->filename, ce->line_number, "link::outgoing::port");
				errors++;
			}
		}
		else if (has_outgoing_file && (has_outgoing_hostname || has_outgoing_port))
		{
			config_error("%s:%d: link block should either have a 'file' (for *NIX domain socket), "
			             "OR have a 'hostname' and 'port' (for IPv4/IPv6). You cannot combine both in one link block.",
			             ce->file->filename, ce->line_number);
			errors++;
		}
	}

	/* The only other generic options that are required are 'class' and 'password' */
	if (!has_password)
	{
		config_error_missing(ce->file->filename, ce->line_number, "link::password");
		errors++;
	}
	if (!has_class)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"link::class");
		errors++;
	}

	return errors;
}

int     _conf_ban(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	ConfigItem_ban *ca;
	Hook *h;

	ca = safe_alloc(sizeof(ConfigItem_ban));
	if (!strcmp(ce->value, "realname"))
		ca->flag.type = CONF_BAN_REALNAME;
	else if (!strcmp(ce->value, "server"))
		ca->flag.type = CONF_BAN_SERVER;
	else if (!strcmp(ce->value, "version"))
	{
		ca->flag.type = CONF_BAN_VERSION;
		tempiConf.use_ban_version = 1; /* enable CTCP VERSION on connect */
	}
	else {
		int value;
		safe_free(ca); /* ca isn't used, modules have their own list. */
		for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
		{
			value = (*(h->func.intfunc))(conf,ce,CONFIG_BAN);
			if (value == 1)
				break;
		}
		return 0;
	}
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "mask"))
		{
			safe_strdup(ca->mask, cep->value);
		}
		else if (!strcmp(cep->name, "reason"))
			safe_strdup(ca->reason, cep->value);
		else if (!strcmp(cep->name, "action"))
			parse_ban_action_config(cep, &ca->action);
	}
	AddListItem(ca, conf_ban);
	return 0;
}

int     _test_ban(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int	    errors = 0;
	Hook *h;
	char type = 0;
	char has_mask = 0, has_action = 0, has_reason = 0;

	if (!ce->value)
	{
		config_error("%s:%i: ban without type",
			ce->file->filename, ce->line_number);
		return 1;
	}
	else if (!strcmp(ce->value, "server"))
	{}
	else if (!strcmp(ce->value, "realname"))
	{}
	else if (!strcmp(ce->value, "version"))
		type = 'v';
	else
	{
		int used = 0;
		for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
		{
			int value, errs = 0;
			if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
			    && !(h->owner->options & MOD_OPT_PERM))
				continue;
			value = (*(h->func.intfunc))(conf,ce,CONFIG_BAN, &errs);
			if (value == 2)
				used = 1;
			if (value == 1)
			{
				used = 1;
				break;
			}
			if (value == -1)
			{
				used = 1;
				errors += errs;
				break;
			}
			if (value == -2)
			{
				used = 1;
				errors += errs;
			}
		}
		if (!used) {
			config_error("%s:%i: unknown ban type %s",
				ce->file->filename, ce->line_number,
				ce->value);
			return 1;
		}
		return errors;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (config_is_blankorempty(cep, "ban"))
		{
			errors++;
			continue;
		}
		if (!strcmp(cep->name, "mask"))
		{
			if (has_mask)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "ban::mask");
				continue;
			}
			has_mask = 1;
		}
		else if (!strcmp(cep->name, "reason"))
		{
			if (has_reason)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "ban::reason");
				continue;
			}
			has_reason = 1;
		}
		else if (!strcmp(cep->name, "action"))
		{
			if (has_action)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "ban::action");
			}
			has_action = 1;
			errors += test_ban_action_config(cep);
		}
	}

	if (!has_mask)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"ban::mask");
		errors++;
	}
	if (!has_reason)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"ban::reason");
		errors++;
	}
	if (has_action && type != 'v')
	{
		config_error("%s:%d: ban::action specified even though type is not 'version'",
			ce->file->filename, ce->line_number);
		errors++;
	}
	return errors;
}

int _conf_require(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	Hook *h;
	SecurityGroup *match = NULL;
	char *reason = NULL;

	if (strcmp(ce->value, "authentication") && strcmp(ce->value, "sasl"))
	{
		/* Some other block... run modules... */
		int value;
		for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
		{
			value = (*(h->func.intfunc))(conf,ce,CONFIG_REQUIRE);
			if (value == 1)
				break;
		}
		return 0;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
			conf_match_block(conf, cep, &match);
		else if (!strcmp(cep->name, "reason"))
			safe_strdup(reason, cep->value);
	}

	if (!reason)
		safe_strdup(reason, "-");

	tkl_add_serverban(TKL_KILL, NULL, NULL, match, reason, "-config-", 0, TStime(), 1, TKL_FLAG_CONFIG);
	safe_free(reason);
	return 0;
}

int _test_require(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int errors = 0;
	Hook *h;
	char has_mask = 0, has_match = 0, has_reason = 0;

	if (!ce->value)
	{
		config_error("%s:%i: require without type, did you mean 'require authentication'?",
			ce->file->filename, ce->line_number);
		return 1;
	}
	if (!strcmp(ce->value, "authentication"))
	{}
	else if (!strcmp(ce->value, "sasl"))
	{
		config_warn("%s:%i: the 'require sasl' block is now called 'require authentication'",
		            ce->file->filename, ce->line_number);
	}
	else
	{
		int used = 0;
		for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
		{
			int value, errs = 0;
			if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
			    && !(h->owner->options & MOD_OPT_PERM))
				continue;
			value = (*(h->func.intfunc))(conf,ce,CONFIG_REQUIRE, &errs);
			if (value == 2)
				used = 1;
			if (value == 1)
			{
				used = 1;
				break;
			}
			if (value == -1)
			{
				used = 1;
				errors += errs;
				break;
			}
			if (value == -2)
			{
				used = 1;
				errors += errs;
			}
		}
		if (!used) {
			config_error("%s:%i: unknown require type '%s'",
				ce->file->filename, ce->line_number,
				ce->value);
			return 1;
		}
		return errors;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "mask"))
		{
			if (cep->value || cep->items)
			{
				has_mask = 1;
				test_match_block(conf, cep, &errors);
			}
		} else
		if (!strcmp(cep->name, "match"))
		{
			if (cep->value || cep->items)
			{
				has_match = 1;
				test_match_block(conf, cep, &errors);
			}
		} else
		if (config_is_blankorempty(cep, "require"))
		{
			errors++;
			continue;
		}
		else if (!strcmp(cep->name, "reason"))
		{
			if (has_reason)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "require::reason");
				continue;
			}
			has_reason = 1;
		} else
		{
			config_error_unknown(cep->file->filename,
				cep->line_number, "require", cep->name);
			errors++;
			continue;
		}
	}

	if (!has_mask && !has_match)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"require::mask");
		errors++;
	}

	if (has_mask && has_match)
	{
		config_error("%s:%d: You cannot have both ::mask and ::match. "
		             "You should only use require::match.",
		             ce->file->filename, ce->line_number);
		errors++;
	}

	if (!has_reason)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"require::reason");
		errors++;
	}
	return errors;
}

#define CheckDuplicate(cep, name, display) if (settings.has_##name) { config_warn_duplicate((cep)->file->filename, cep->line_number, "set::" display); continue; } else settings.has_##name = 1

void test_tlsblock(ConfigFile *conf, ConfigEntry *cep, int *totalerrors)
{
	ConfigEntry *cepp, *ceppp;
	int errors = 0;
	int tls_keys = 0, tls_certificates = 0;

	for (cepp = cep->items; cepp; cepp = cepp->next)
	{
		if (!strcmp(cepp->name, "renegotiate-timeout"))
		{
		}
		else if (!strcmp(cepp->name, "renegotiate-bytes"))
		{
		}
		else if (!strcmp(cepp->name, "ciphers") || !strcmp(cepp->name, "server-cipher-list"))
		{
			CheckNull(cepp);
		}
		else if (!strcmp(cepp->name, "ciphersuites"))
		{
			CheckNull(cepp);
		}
		else if (!strcmp(cepp->name, "groups") || !strcmp(cepp->name, "ecdh-curves"))
		{
			CheckNull(cepp);
#ifndef HAS_SSL_CTX_SET1_CURVES_LIST
			config_error("%s specified but your OpenSSL/LibreSSL library does not "
			             "support setting groups or curves. Either upgrade to a "
			             "newer library version or remove the '%s' directive "
			             "from your configuration file", cepp->name, cepp->name);
			errors++;
#endif
		}
		else if (!strcmp(cepp->name, "signature-algorithms"))
		{
			CheckNull(cepp);
		}
		else if (!strcmp(cepp->name, "protocols"))
		{
			char copy[512], *p, *name;
			int v = 0;
			int option;
			char modifier;

			CheckNull(cepp);
			strlcpy(copy, cepp->value, sizeof(copy));
			for (name = strtoken(&p, copy, ","); name; name = strtoken(&p, NULL, ","))
			{
				modifier = '\0';
				option = 0;

				if ((*name == '+') || (*name == '-'))
				{
					modifier = *name;
					name++;
				}

				if (!strcasecmp(name, "All"))
					option = TLS_PROTOCOL_ALL;
				else if (!strcasecmp(name, "TLSv1"))
					option = TLS_PROTOCOL_TLSV1;
				else if (!strcasecmp(name, "TLSv1.1"))
					option = TLS_PROTOCOL_TLSV1_1;
				else if (!strcasecmp(name, "TLSv1.2"))
					option = TLS_PROTOCOL_TLSV1_2;
				else if (!strcasecmp(name, "TLSv1.3"))
					option = TLS_PROTOCOL_TLSV1_3;
				else
				{
#ifdef SSL_OP_NO_TLSv1_3
					config_warn("%s:%i: %s: unknown protocol '%s'. "
								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2,TLSv1.3",
								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
#else
					config_warn("%s:%i: %s: unknown protocol '%s'. "
								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2",
								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
#endif
				}

				if (option)
				{
					if (modifier == '\0')
						v = option;
					else if (modifier == '+')
						v |= option;
					else if (modifier == '-')
						v &= ~option;
				}
			}
			if (v == 0)
			{
				config_error("%s:%i: %s: no protocols enabled. Hint: set at least TLSv1.2",
					cepp->file->filename, cepp->line_number, config_var(cepp));
				errors++;
			}
		}
		else if (!strcmp(cepp->name, "certificate") ||
		         !strcmp(cepp->name, "key") ||
		         !strcmp(cepp->name, "trusted-ca-file"))
		{
			char *path;
			CheckNull(cepp);
			if (!strcmp(cepp->name, "key"))
				tls_keys++;
			if (!strcmp(cepp->name, "certificate"))
				tls_certificates++;
			path = convert_to_absolute_path_duplicate(cepp->value, CONFDIR);
			if (!file_exists(path))
			{
				config_error("%s:%i: %s: could not open '%s': %s",
					cepp->file->filename, cepp->line_number, config_var(cepp),
					path, strerror(errno));
				safe_free(path);
				errors++;
			}
			safe_free(path);
		}
		else if (!strcmp(cepp->name, "dh"))
		{
			/* Support for this undocumented option was silently dropped in 5.0.0.
			 * Since 5.0.7 we print a warning about it, since you never know
			 * someone may still have it configured. -- Syzop
			 */
			config_warn("%s:%d: Not reading DH file '%s'. UnrealIRCd does not support old DH(E), we use modern ECDHE/EECDH. "
			            "Just remove the 'dh' directive from your config file to get rid of this warning.",
				cepp->file->filename, cepp->line_number,
				cepp->value ? cepp->value : "");
		}
		else if (!strcmp(cepp->name, "outdated-protocols"))
		{
			char copy[512], *p, *name;
			int v = 0;
			int option;
			char modifier;

			CheckNull(cepp);
			strlcpy(copy, cepp->value, sizeof(copy));
			for (name = strtoken(&p, copy, ","); name; name = strtoken(&p, NULL, ","))
			{
				if (!strcasecmp(name, "All"))
					;
				else if (!strcasecmp(name, "TLSv1"))
					;
				else if (!strcasecmp(name, "TLSv1.1"))
					;
				else if (!strcasecmp(name, "TLSv1.2"))
					;
				else if (!strcasecmp(name, "TLSv1.3"))
					;
				else
				{
#ifdef SSL_OP_NO_TLSv1_3
					config_warn("%s:%i: %s: unknown protocol '%s'. "
								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2,TLSv1.3",
								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
#else
					config_warn("%s:%i: %s: unknown protocol '%s'. "
								 "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2",
								 cepp->file->filename, cepp->line_number, config_var(cepp), name);
#endif
		                }
			}
		}
		else if (!strcmp(cepp->name, "outdated-ciphers"))
		{
			CheckNull(cepp);
		}
		else if (!strcmp(cepp->name, "options"))
		{
			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
			{
				if (!nv_find_by_name(_TLSFlags, ceppp->name))
				{
					config_error("%s:%i: unknown TLS option '%s'",
							 ceppp->file->filename,
							 ceppp->line_number, ceppp->name);
					errors ++;
				}
			}
		}
		else if (!strcmp(cepp->name, "sts-policy"))
		{
			int has_port = 0;
			int has_duration = 0;

			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
			{
				if (!strcmp(ceppp->name, "port"))
				{
					int port;
					CheckNull(ceppp);
					port = atoi(ceppp->value);
					if ((port < 1) || (port > 65535))
					{
						config_error("%s:%i: invalid port number specified in sts-policy::port (%d)",
						             ceppp->file->filename, ceppp->line_number, port);
						errors++;
					}
					has_port = 1;
				}
				else if (!strcmp(ceppp->name, "duration"))
				{
					long duration;
					CheckNull(ceppp);
					duration = config_checkval(ceppp->value, CFG_TIME);
					if (duration < 1)
					{
						config_error("%s:%i: invalid duration specified in sts-policy::duration (%ld seconds)",
						             ceppp->file->filename, ceppp->line_number, duration);
						errors++;
					}
					has_duration = 1;
				}
				else if (!strcmp(ceppp->name, "preload"))
				{
					CheckNull(ceppp);
				}
			}
			if (!has_port)
			{
				config_error("%s:%i: sts-policy block without port",
				             cepp->file->filename, cepp->line_number);
				errors++;
			}
			if (!has_duration)
			{
				config_error("%s:%i: sts-policy block without duration",
				             cepp->file->filename, cepp->line_number);
				errors++;
			}
		}
		else if (!strcmp(cepp->name, "certificate-expiry-notification"))
		{
		}
		else
		{
			config_error("%s:%i: unknown directive %s",
				cepp->file->filename, cepp->line_number,
				config_var(cepp));
			errors++;
		}
	}

	if (tls_keys != tls_certificates)
	{
		config_error("%s:%d: certificate count != key count. "
		             "Each certificate should have a matching key. You cannot have more "
		             "certificates than keys or the other way around.",
		             cep->file->filename, cep->line_number);
		errors++;
	}

	/* If both certificate and key are explicitly set in this block
	 * (not inherited from set::tls), try to actually load them
	 * to see if there are any errors.
	 */
	if (tls_certificates && tls_keys && (tls_certificates == tls_keys))
	{
		TLSOptions *tlsoptions = safe_alloc(sizeof(TLSOptions));
		SSL_CTX *ctx;

		conf_tlsblock(conf, cep, tlsoptions);
		ctx = init_ctx(tlsoptions, 1);
		free_tls_options(tlsoptions);

		if (!ctx)
			errors++;
		else
			SSL_CTX_free(ctx);
	}

	*totalerrors += errors;
}

void free_tls_options(TLSOptions *tlsoptions)
{
	if (!tlsoptions)
		return;

	safe_free_name_list(tlsoptions->certificate_files);
	safe_free_name_list(tlsoptions->key_files);
	safe_free(tlsoptions->trusted_ca_file);
	safe_free(tlsoptions->ciphers);
	safe_free(tlsoptions->ciphersuites);
	safe_free(tlsoptions->groups);
	safe_free(tlsoptions->signature_algorithms);
	safe_free(tlsoptions->outdated_protocols);
	safe_free(tlsoptions->outdated_ciphers);
	memset(tlsoptions, 0, sizeof(TLSOptions));
	safe_free(tlsoptions);
}

void conf_tlsblock(ConfigFile *conf, ConfigEntry *cep, TLSOptions *tlsoptions)
{
	ConfigEntry *cepp, *ceppp;
	NameValue *ofl;

	/* First, inherit settings from set::options::tls */
	if (tlsoptions != tempiConf.tls_options)
	{
		// certificate_files: done at end of function
		// key_files: done at end of function
		safe_strdup(tlsoptions->trusted_ca_file, tempiConf.tls_options->trusted_ca_file);
		tlsoptions->protocols = tempiConf.tls_options->protocols;
		safe_strdup(tlsoptions->ciphers, tempiConf.tls_options->ciphers);
		safe_strdup(tlsoptions->ciphersuites, tempiConf.tls_options->ciphersuites);
		safe_strdup(tlsoptions->groups, tempiConf.tls_options->groups);
		safe_strdup(tlsoptions->signature_algorithms, tempiConf.tls_options->signature_algorithms);
		safe_strdup(tlsoptions->outdated_protocols, tempiConf.tls_options->outdated_protocols);
		safe_strdup(tlsoptions->outdated_ciphers, tempiConf.tls_options->outdated_ciphers);
		tlsoptions->options = tempiConf.tls_options->options;
		tlsoptions->renegotiate_bytes = tempiConf.tls_options->renegotiate_bytes;
		tlsoptions->renegotiate_timeout = tempiConf.tls_options->renegotiate_timeout;
		tlsoptions->sts_port = tempiConf.tls_options->sts_port;
		tlsoptions->sts_duration = tempiConf.tls_options->sts_duration;
		tlsoptions->sts_preload = tempiConf.tls_options->sts_preload;
		tlsoptions->certificate_expiry_notification = tempiConf.tls_options->certificate_expiry_notification;
	}

	/* Now process the options */
	for (cepp = cep->items; cepp; cepp = cepp->next)
	{
		if (!strcmp(cepp->name, "ciphers") || !strcmp(cepp->name, "server-cipher-list"))
		{
			safe_strdup(tlsoptions->ciphers, cepp->value);
		}
		else if (!strcmp(cepp->name, "ciphersuites"))
		{
			safe_strdup(tlsoptions->ciphersuites, cepp->value);
		}
		else if (!strcmp(cepp->name, "groups") || !strcmp(cepp->name, "ecdh-curves"))
		{
			safe_strdup(tlsoptions->groups, cepp->value);
		}
		else if (!strcmp(cepp->name, "signature-algorithms"))
		{
			safe_strdup(tlsoptions->signature_algorithms, cepp->value);
		}
		else if (!strcmp(cepp->name, "protocols"))
		{
			char copy[512], *p, *name;
			int option;
			char modifier;

			strlcpy(copy, cepp->value, sizeof(copy));
			tlsoptions->protocols = 0;
			for (name = strtoken(&p, copy, ","); name; name = strtoken(&p, NULL, ","))
			{
				modifier = '\0';
				option = 0;

				if ((*name == '+') || (*name == '-'))
				{
					modifier = *name;
					name++;
				}

				if (!strcasecmp(name, "All"))
					option = TLS_PROTOCOL_ALL;
				else if (!strcasecmp(name, "TLSv1"))
					option = TLS_PROTOCOL_TLSV1;
				else if (!strcasecmp(name, "TLSv1.1"))
					option = TLS_PROTOCOL_TLSV1_1;
				else if (!strcasecmp(name, "TLSv1.2"))
					option = TLS_PROTOCOL_TLSV1_2;
				else if (!strcasecmp(name, "TLSv1.3"))
					option = TLS_PROTOCOL_TLSV1_3;

				if (option)
				{
					if (modifier == '\0')
						tlsoptions->protocols = option;
					else if (modifier == '+')
						tlsoptions->protocols |= option;
					else if (modifier == '-')
						tlsoptions->protocols &= ~option;
				}
			}
		}
		else if (!strcmp(cepp->name, "certificate"))
		{
			convert_to_absolute_path(&cepp->value, CONFDIR);
			add_name_list(tlsoptions->certificate_files, cepp->value);
		}
		else if (!strcmp(cepp->name, "key"))
		{
			convert_to_absolute_path(&cepp->value, CONFDIR);
			add_name_list(tlsoptions->key_files, cepp->value);
		}
		else if (!strcmp(cepp->name, "trusted-ca-file"))
		{
			convert_to_absolute_path(&cepp->value, CONFDIR);
			safe_strdup(tlsoptions->trusted_ca_file, cepp->value);
		}
		else if (!strcmp(cepp->name, "outdated-protocols"))
		{
			safe_strdup(tlsoptions->outdated_protocols, cepp->value);
		}
		else if (!strcmp(cepp->name, "outdated-ciphers"))
		{
			safe_strdup(tlsoptions->outdated_ciphers, cepp->value);
		}
		else if (!strcmp(cepp->name, "renegotiate-bytes"))
		{
			tlsoptions->renegotiate_bytes = config_checkval(cepp->value, CFG_SIZE);
		}
		else if (!strcmp(cepp->name, "renegotiate-timeout"))
		{
			tlsoptions->renegotiate_timeout = config_checkval(cepp->value, CFG_TIME);
		}
		else if (!strcmp(cepp->name, "options"))
		{
			tlsoptions->options = 0;
			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
			{
				long v = nv_find_by_name(_TLSFlags, ceppp->name);
				tlsoptions->options |= v;
			}
		}
		else if (!strcmp(cepp->name, "sts-policy"))
		{
			/* We do not inherit ::sts-policy if there is a specific block for this one... */
			tlsoptions->sts_port = 0;
			tlsoptions->sts_duration = 0;
			tlsoptions->sts_preload = 0;
			for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
			{
				if (!strcmp(ceppp->name, "port"))
					tlsoptions->sts_port = atoi(ceppp->value);
				else if (!strcmp(ceppp->name, "duration"))
					tlsoptions->sts_duration = config_checkval(ceppp->value, CFG_TIME);
				else if (!strcmp(ceppp->name, "preload"))
					tlsoptions->sts_preload = config_checkval(ceppp->value, CFG_YESNO);
			}
		}
		else if (!strcmp(cepp->name, "certificate-expiry-notification"))
		{
			tlsoptions->certificate_expiry_notification = config_checkval(cepp->value, CFG_YESNO);
		}
	}

	/* Inheritance of these items is at the end of this function and
	 * only if they were not set in the block. The reason for that is
	 * that if there is a 'certificate' and 'key' in a tls options
	 * config blob, it should override, while otherwise it would 'add'
	 * additional certs/keys due to the nature of it being a name list.
	 * So we simply only add these here at the end if they were not set.
	 */
	if (tlsoptions != tempiConf.tls_options)
	{
		if (!tlsoptions->certificate_files)
			tlsoptions->certificate_files = duplicate_name_list(tempiConf.tls_options->certificate_files);
		if (!tlsoptions->key_files)
			tlsoptions->key_files = duplicate_name_list(tempiConf.tls_options->key_files);
	}
}

int	_conf_set(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp, *ceppp, *cep4;
	Hook *h;
	int n;

	if (ce->value)
		return config_set_security_group(conf, ce);

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "kline-address")) {
			safe_strdup(tempiConf.kline_address, cep->value);
		}
		if (!strcmp(cep->name, "gline-address")) {
			safe_strdup(tempiConf.gline_address, cep->value);
		}
		else if (!strcmp(cep->name, "modes-on-oper")) {
			tempiConf.oper_modes = (long) set_usermode(cep->value);
		}
		else if (!strcmp(cep->name, "modes-on-join")) {
			conf_channelmodes(cep->value, &tempiConf.modes_on_join);
			tempiConf.modes_on_join_set = 1;
		}
		else if (!strcmp(cep->name, "snomask-on-oper")) {
			safe_strdup(tempiConf.oper_snomask, cep->value);
		}
		else if (!strcmp(cep->name, "server-notice-colors")) {
			tempiConf.server_notice_colors = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "server-notice-show-event")) {
			tempiConf.server_notice_show_event = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "level-on-join")) {
			const char *res = channellevel_to_string(cep->value); /* 'halfop', etc */
			if (!res)
			{
				/* This check needs to be here, in config run, because
				 * now the channel modules are initialized and we know
				 * which ones are available. This same information is
				 * not available during config test, so we can't test
				 * for it there like we normally do.
				 */
				if (!valid_channel_access_mode_letter(*cep->value))
				{
					config_warn("%s:%d: set::level-on-join: Unknown mode (access level) '%c'. "
					            "That mode does not exist or is not a valid access mode "
					            "like vhoaq.",
					            cep->file->filename, cep->line_number,
					            *cep->value);
					config_warn("Falling back to to set::level-on-join none; now. "
					            "This is probably not what you want!!!");
				}
				res = cep->value; /* if we reach this.. then it is a single letter */
			}
			safe_strdup(tempiConf.level_on_join, res);
		}
		else if (!strcmp(cep->name, "who-limit")) {
			tempiConf.who_limit = atol(cep->value);
		}
		else if (!strcmp(cep->name, "maxbans")) {
			tempiConf.maxbans = atol(cep->value);
		}
		else if (!strcmp(cep->name, "silence-limit")) {
			tempiConf.silence_limit = atol(cep->value);
		}
		else if (!strcmp(cep->name, "oper-auto-join")) {
			safe_strdup(tempiConf.oper_auto_join_chans, cep->value);
		}
		else if (!strcmp(cep->name, "oper-vhost")) {
			safe_strdup(tempiConf.oper_vhost, cep->value);
		}
		else if (!strcmp(cep->name, "check-target-nick-bans")) {
			tempiConf.check_target_nick_bans = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "ping-cookie")) {
			tempiConf.ping_cookie = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "watch-away-notification")) {
			tempiConf.watch_away_notification = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "uhnames")) {
			tempiConf.uhnames = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "allow-userhost-change")) {
			if (!strcasecmp(cep->value, "always"))
				tempiConf.userhost_allowed = UHALLOW_ALWAYS;
			else if (!strcasecmp(cep->value, "never"))
				tempiConf.userhost_allowed = UHALLOW_NEVER;
			else if (!strcasecmp(cep->value, "not-on-channels"))
				tempiConf.userhost_allowed = UHALLOW_NOCHANS;
			else
				tempiConf.userhost_allowed = UHALLOW_REJOIN;
		}
		else if (!strcmp(cep->name, "allow-setident")) {
			tempiConf.allow_setident = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "allow-setname")) {
			tempiConf.allow_setname = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "channel-command-prefix")) {
			safe_strdup(tempiConf.channel_command_prefix, cep->value);
		}
		else if (!strcmp(cep->name, "restrict-channelmodes")) {
			safe_strdup(tempiConf.restrict_channelmodes, cep->value);
		}
		else if (!strcmp(cep->name, "named-extended-bans")) {
			tempiConf.named_extended_bans = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "anti-spam-quit-message-time")) {
			tempiConf.anti_spam_quit_message_time = config_checkval(cep->value,CFG_TIME);
		}
		else if (!strcmp(cep->name, "allow-user-stats")) {
			if (!cep->items)
			{
				safe_strdup(tempiConf.allow_user_stats, cep->value);
			}
			else
			{
				for (cepp = cep->items; cepp; cepp = cepp->next)
				{
					OperStat *os = safe_alloc(sizeof(OperStat));
					safe_strdup(os->flag, cepp->name);
					AddListItem(os, tempiConf.allow_user_stats_ext);
				}
			}
		}
		else if (!strcmp(cep->name, "ping-warning")) {
			tempiConf.ping_warning = atoi(cep->value);
		}
		else if (!strcmp(cep->name, "maxdccallow")) {
			tempiConf.maxdccallow = atoi(cep->value);
		}
		else if (!strcmp(cep->name, "max-targets-per-command"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				int v;
				if (!strcmp(cepp->value, "max"))
					v = MAXTARGETS_MAX;
				else
					v = atoi(cepp->value);
				setmaxtargets(cepp->name, v);
			}
		}
		else if (!strcmp(cep->name, "network-name")) {
			char *tmp;
			safe_strdup(tempiConf.network_name, cep->value);
			for (tmp = cep->value; *cep->value; cep->value++) {
				if (*cep->value == ' ')
					*cep->value='-';
			}
			safe_strdup(tempiConf.network_name_005, tmp);
			cep->value = tmp;
		}
		else if (!strcmp(cep->name, "default-server")) {
			safe_strdup(tempiConf.default_server, cep->value);
		}
		else if (!strcmp(cep->name, "services-server")) {
			safe_strdup(tempiConf.services_name, cep->value);
		}
		else if (!strcmp(cep->name, "sasl-server")) {
			safe_strdup(tempiConf.sasl_server, cep->value);
		}
		else if (!strcmp(cep->name, "stats-server")) {
			safe_strdup(tempiConf.stats_server, cep->value);
		}
		else if (!strcmp(cep->name, "help-channel")) {
			safe_strdup(tempiConf.helpchan, cep->value);
		}
		else if (!strcmp(cep->name, "cloak-prefix") || !strcmp(cep->name, "hiddenhost-prefix")) {
			safe_strdup(tempiConf.cloak_prefix, cep->value);
		}
		else if (!strcmp(cep->name, "hide-ban-reason")) {
			if (!strcmp(cep->value, "yes"))
				tempiConf.hide_ban_reason = HIDE_BAN_REASON_YES;
			else if (!strcmp(cep->value, "no"))
				tempiConf.hide_ban_reason = HIDE_BAN_REASON_NO;
			else if (!strcmp(cep->value, "auto"))
				tempiConf.hide_ban_reason = HIDE_BAN_REASON_AUTO;
		}
		else if (!strcmp(cep->name, "hide-killed-by")) {
			tempiConf.hide_killed_by = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "prefix-quit")) {
			if (!strcmp(cep->value, "0") || !strcmp(cep->value, "no"))
				safe_free(tempiConf.prefix_quit);
			else
				safe_strdup(tempiConf.prefix_quit, cep->value);
		}
		else if (!strcmp(cep->name, "link")) {
			for (cepp = cep->items; cepp; cepp = cepp->next) {
				if (!strcmp(cepp->name, "bind-ip")) {
					safe_strdup(tempiConf.link_bindip, cepp->value);
				}
			}
		}
		else if (!strcmp(cep->name, "anti-flood")) {
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				int lag_penalty = -1;
				int lag_penalty_bytes = -1;
				for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
				{
					/* Check hooks first */
					int used = 0;
					for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
					{
						used = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD);
						if (used == 1)
							break;
					}
					if (used == 1)
						continue; /* module handled it */
					if (used == 2)
						break; /* module handled it and we must stop entire block processing */
					if (!strcmp(ceppp->name, "handshake-data-flood"))
					{
						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
						{
							if (!strcmp(cep4->name, "amount"))
								tempiConf.handshake_data_flood_amount = config_checkval(cep4->value, CFG_SIZE);
							else if (!strcmp(cep4->name, "ban-time"))
								tempiConf.handshake_data_flood_ban_time = config_checkval(cep4->value, CFG_TIME);
							else if (!strcmp(cep4->name, "ban-action") || !strcmp(cep4->name, "action"))
								parse_ban_action_config(cep4, &tempiConf.handshake_data_flood_ban_action);
						}
					}
					else if (!strcmp(ceppp->name, "away-flood"))
					{
						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_AWAY);
					}
					else if (!strcmp(ceppp->name, "nick-flood"))
					{
						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_NICK);
					}
					else if (!strcmp(ceppp->name, "vhost-flood"))
					{
						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_VHOST);
					}
					else if (!strcmp(ceppp->name, "join-flood"))
					{
						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_JOIN);
					}
					else if (!strcmp(ceppp->name, "invite-flood"))
					{
						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_INVITE);
					}
					else if (!strcmp(ceppp->name, "knock-flood"))
					{
						config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_KNOCK);
					}
					else if (!strcmp(ceppp->name, "lag-penalty"))
					{
						lag_penalty = atoi(ceppp->value);
					}
					else if (!strcmp(ceppp->name, "lag-penalty-bytes"))
					{
						lag_penalty_bytes = config_checkval(ceppp->value, CFG_SIZE);
						if (lag_penalty_bytes <= 0)
							lag_penalty_bytes = INT_MAX;
					}
					else if (!strcmp(ceppp->name, "connect-flood"))
					{
						int cnt, period;
						config_parse_flood(ceppp->value, &cnt, &period);
						tempiConf.throttle_count = cnt;
						tempiConf.throttle_period = period;
					}
					else if (!strcmp(ceppp->name, "max-concurrent-conversations"))
					{
						/* We use a hack here to make it fit our storage format */
						char buf[64];
						int users=0;
						long every=0;
						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
						{
							if (!strcmp(cep4->name, "users"))
							{
								users = atoi(cep4->value);
							} else
							if (!strcmp(cep4->name, "new-user-every"))
							{
								every = config_checkval(cep4->value, CFG_TIME);
							}
						}
						snprintf(buf, sizeof(buf), "%d:%ld", users, every);
						config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_CONVERSATIONS);
					}
					else if (!strcmp(ceppp->name, "multiline"))
					{
						/* Same hack: store max-lines in limit, max-bytes in period */
						char buf[64];
						int max_lines = 0;
						int max_bytes = 0;
						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
						{
							if (!strcmp(cep4->name, "max-lines"))
								max_lines = atoi(cep4->value);
							else if (!strcmp(cep4->name, "max-bytes"))
								max_bytes = config_checkval(cep4->value, CFG_SIZE);
						}
						snprintf(buf, sizeof(buf), "%d:%d", max_lines, max_bytes);
						config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_MULTILINE);
					}
				}
				if ((lag_penalty != -1) && (lag_penalty_bytes != -1))
				{
					/* We use a hack here to make it fit our storage format */
					char buf[64];
					snprintf(buf, sizeof(buf), "%d:%d", lag_penalty_bytes, lag_penalty);
					config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_LAG_PENALTY);
				}
			}
		}
		else if (!strcmp(cep->name, "log-throttle"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!cepp->name || !cepp->value)
					continue;
				if (!strcmp(cepp->value, "unlimited") || !strcmp(cepp->value, "max"))
				{
					add_log_throttle_config(&tempiConf.log_throttle, cepp->name, 0, 0, 1);
				} else
				{
					int cnt = 0, period = 0;
					config_parse_flood(cepp->value, &cnt, &period);
					add_log_throttle_config(&tempiConf.log_throttle, cepp->name, cnt, period, 0);
				}
			}
		}
		else if (!strcmp(cep->name, "options")) {
			for (cepp = cep->items; cepp; cepp = cepp->next) {
				if (!strcmp(cepp->name, "hide-ulines")) {
					tempiConf.hide_ulines = 1;
				}
				else if (!strcmp(cepp->name, "flat-map")) {
					tempiConf.flat_map = 1;
				}
				else if (!strcmp(cepp->name, "show-opermotd")) {
					tempiConf.show_opermotd = 1;
				}
				else if (!strcmp(cepp->name, "identd-check")) {
					tempiConf.ident_check = 1;
				}
				else if (!strcmp(cepp->name, "fail-oper-warn")) {
					tempiConf.fail_oper_warn = 1;
				}
				else if (!strcmp(cepp->name, "show-connect-info")) {
					tempiConf.show_connect_info = 1;
				}
				else if (!strcmp(cepp->name, "no-connect-tls-info")) {
					tempiConf.no_connect_tls_info = 1;
				}
				else if (!strcmp(cepp->name, "dont-resolve")) {
					tempiConf.dont_resolve = 1;
				}
				else if (!strcmp(cepp->name, "mkpasswd-for-everyone")) {
					tempiConf.mkpasswd_for_everyone = 1;
				}
				else if (!strcmp(cepp->name, "allow-insane-bans")) {
					tempiConf.allow_insane_bans = 1;
				}
				else if (!strcmp(cepp->name, "allow-part-if-shunned")) {
					tempiConf.allow_part_if_shunned = 1;
				}
				else if (!strcmp(cepp->name, "disable-cap")) {
					tempiConf.disable_cap = 1;
				}
				else if (!strcmp(cepp->name, "disable-ipv6")) {
					/* other code handles this */
				}
			}
		}
		else if (!strcmp(cep->name, "cloak-keys"))
		{
			for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
			{
				int value;
				value = (*(h->func.intfunc))(conf, cep, CONFIG_CLOAKKEYS);
				if (value == 1)
					break;
			}
		}
		else if (!strcmp(cep->name, "ident"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "connect-timeout"))
					tempiConf.ident_connect_timeout = config_checkval(cepp->value,CFG_TIME);
				if (!strcmp(cepp->name, "read-timeout"))
					tempiConf.ident_read_timeout = config_checkval(cepp->value,CFG_TIME);
			}
		}
		else if (!strcmp(cep->name, "spamfilter"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "ban-time"))
					tempiConf.spamfilter_ban_time = config_checkval(cepp->value,CFG_TIME);
				else if (!strcmp(cepp->name, "ban-reason"))
					safe_strdup(tempiConf.spamfilter_ban_reason, cepp->value);
				else if (!strcmp(cepp->name, "virus-help-channel"))
					safe_strdup(tempiConf.spamfilter_virus_help_channel, cepp->value);
				else if (!strcmp(cepp->name, "virus-help-channel-deny"))
					tempiConf.spamfilter_vchan_deny = config_checkval(cepp->value,CFG_YESNO);
				else if (!strcmp(cepp->name, "except"))
				{
					if (cepp->value && !tempiConf.spamfilter_except)
					{
						/* OLD set::spamfilter::except compatibility code when it was "#chan,xyz" */
						char buf[512], *p = NULL, *name;
						strlcpy(buf, cepp->value, sizeof(buf));
						tempiConf.spamfilter_except = safe_alloc(sizeof(SecurityGroup));
						for (name = strtoken(&p, buf, ","); name; name = strtoken(&p, NULL, ","))
						{
							skip_whitespace(&name);
							add_name_list(tempiConf.spamfilter_except->destination, name);
						}
					} else {
						/* New set::spamfilter::except code, where it is a mask item */
						conf_match_block(conf, cepp, &tempiConf.spamfilter_except);
					}
				}
				else if (!strcmp(cepp->name, "detect-slow-warn"))
				{
					tempiConf.spamfilter_detectslow_warn = atol(cepp->value);
				}
				else if (!strcmp(cepp->name, "detect-slow-fatal"))
				{
					tempiConf.spamfilter_detectslow_fatal = atol(cepp->value);
				}
				else if (!strcmp(cepp->name, "stop-on-first-match"))
				{
					tempiConf.spamfilter_stop_on_first_match = config_checkval(cepp->value, CFG_YESNO);
				}
				else if (!strcmp(cepp->name, "utf8"))
				{
					tempiConf.spamfilter_utf8 = config_checkval(cepp->value, CFG_YESNO);
				}
				else if (!strcmp(cepp->name, "show-message-content-on-hit"))
				{
					tempiConf.spamfilter_show_message_content_on_hit = spamfilter_show_message_content_on_hit_strtoval(cepp->value);
				}
			}
		}
		else if (!strcmp(cep->name, "central-spamfilter"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "url"))
					safe_strdup(tempiConf.central_spamfilter_url, cepp->value);
				else if (!strcmp(cepp->name, "refresh-time"))
					tempiConf.central_spamfilter_refresh_time = config_checkval(cepp->value, CFG_TIME);
				else if (!strcmp(cepp->name, "verbose"))
					tempiConf.central_spamfilter_verbose = atoi(cepp->value);
				else if (!strcmp(cepp->name, "enabled"))
					tempiConf.central_spamfilter_enabled = config_checkval(cepp->value, CFG_YESNO);
				else if (!strcmp(cepp->name, "feed"))
					safe_strdup(tempiConf.central_spamfilter_feed, cepp->value);
				else if (!strcmp(cepp->name, "except"))
					conf_match_block(conf, cepp, &tempiConf.central_spamfilter_except);
				else if (!strcmp(cepp->name, "limit-ban-action"))
					tempiConf.central_spamfilter_limit_ban_action = banact_stringtoval(cepp->value);
				else if (!strcmp(cepp->name, "limit-ban-time"))
					tempiConf.central_spamfilter_limit_ban_time = config_checkval(cepp->value, CFG_TIME);
			}
		}
		else if (!strcmp(cep->name, "default-bantime"))
		{
			tempiConf.default_bantime = config_checkval(cep->value,CFG_TIME);
		}
		else if (!strcmp(cep->name, "ban-version-tkl-time"))
		{
			tempiConf.ban_version_tkl_time = config_checkval(cep->value,CFG_TIME);
		}
		else if (!strcmp(cep->name, "min-nick-length")) {
			int v = atoi(cep->value);
			tempiConf.min_nick_length = v;
		}
		else if (!strcmp(cep->name, "nick-length")) {
			int v = atoi(cep->value);
			tempiConf.nick_length = v;
		}
		else if (!strcmp(cep->name, "topic-length")) {
			int v = atoi(cep->value);
			tempiConf.topic_length = v;
		}
		else if (!strcmp(cep->name, "away-length")) {
			int v = atoi(cep->value);
			tempiConf.away_length = v;
		}
		else if (!strcmp(cep->name, "kick-length")) {
			int v = atoi(cep->value);
			tempiConf.kick_length = v;
		}
		else if (!strcmp(cep->name, "quit-length")) {
			int v = atoi(cep->value);
			tempiConf.quit_length = v;
		}
		else if (!strcmp(cep->name, "ssl") || !strcmp(cep->name, "tls")) {
			/* no need to alloc tempiConf.tls_options since config_defaults() already ensures it exists */
			conf_tlsblock(conf, cep, tempiConf.tls_options);
		}
		else if (!strcmp(cep->name, "plaintext-policy"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "user"))
					tempiConf.plaintext_policy_user = policy_strtoval(cepp->value);
				else if (!strcmp(cepp->name, "oper"))
					tempiConf.plaintext_policy_oper = policy_strtoval(cepp->value);
				else if (!strcmp(cepp->name, "server"))
					tempiConf.plaintext_policy_server = policy_strtoval(cepp->value);
				else if (!strcmp(cepp->name, "user-message"))
					addmultiline(&tempiConf.plaintext_policy_user_message, cepp->value);
				else if (!strcmp(cepp->name, "oper-message"))
					addmultiline(&tempiConf.plaintext_policy_oper_message, cepp->value);
			}
		}
		else if (!strcmp(cep->name, "outdated-tls-policy"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "user"))
					tempiConf.outdated_tls_policy_user = policy_strtoval(cepp->value);
				else if (!strcmp(cepp->name, "oper"))
					tempiConf.outdated_tls_policy_oper = policy_strtoval(cepp->value);
				else if (!strcmp(cepp->name, "server"))
					tempiConf.outdated_tls_policy_server = policy_strtoval(cepp->value);
				else if (!strcmp(cepp->name, "user-message"))
					safe_strdup(tempiConf.outdated_tls_policy_user_message, cepp->value);
				else if (!strcmp(cepp->name, "oper-message"))
					safe_strdup(tempiConf.outdated_tls_policy_oper_message, cepp->value);
			}
		}
		else if (!strcmp(cep->name, "default-ipv6-clone-mask"))
		{
			tempiConf.default_ipv6_clone_mask = atoi(cep->value);
		}
		else if (!strcmp(cep->name, "hide-list")) {
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "deny-channel"))
				{
					tempiConf.hide_list = 1;
					/* if we would expand this later then change this to a bitmask or struct or whatever */
				}
			}
		}
		else if (!strcmp(cep->name, "max-unknown-connections-per-ip"))
		{
			tempiConf.max_unknown_connections_per_ip = atoi(cep->value);
		}
		else if (!strcmp(cep->name, "handshake-timeout"))
		{
			tempiConf.handshake_timeout = config_checkval(cep->value, CFG_TIME);
		}
		else if (!strcmp(cep->name, "sasl-timeout"))
		{
			tempiConf.sasl_timeout = config_checkval(cep->value, CFG_TIME);
		}
		else if (!strcmp(cep->name, "handshake-delay"))
		{
			tempiConf.handshake_delay = config_checkval(cep->value, CFG_TIME);
		}
		else if (!strcmp(cep->name, "handshake-boot-delay"))
		{
			tempiConf.handshake_boot_delay = config_checkval(cep->value, CFG_TIME);
		}
		else if (!strcmp(cep->name, "automatic-ban-target"))
		{
			tempiConf.automatic_ban_target = ban_target_strtoval(cep->value);
		}
		else if (!strcmp(cep->name, "manual-ban-target"))
		{
			tempiConf.manual_ban_target = ban_target_strtoval(cep->value);
		}
		else if (!strcmp(cep->name, "reject-message"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "too-many-connections"))
					safe_strdup(tempiConf.reject_message_too_many_connections, cepp->value);
				else if (!strcmp(cepp->name, "too-many-connections-ipv6-range"))
					safe_strdup(tempiConf.reject_message_too_many_connections_ipv6_range, cepp->value);
				else if (!strcmp(cepp->name, "too-many-new-connections-ipv6-range"))
					safe_strdup(tempiConf.reject_message_too_many_new_connections_ipv6_range, cepp->value);
				else if (!strcmp(cepp->name, "server-full"))
					safe_strdup(tempiConf.reject_message_server_full, cepp->value);
				else if (!strcmp(cepp->name, "unauthorized"))
					safe_strdup(tempiConf.reject_message_unauthorized, cepp->value);
				else if (!strcmp(cepp->name, "kline"))
					safe_strdup(tempiConf.reject_message_kline, cepp->value);
				else if (!strcmp(cepp->name, "gline"))
					safe_strdup(tempiConf.reject_message_gline, cepp->value);
			}
		}
		else if (!strcmp(cep->name, "topic-setter"))
		{
			if (!strcmp(cep->value, "nick"))
				tempiConf.topic_setter = SETTER_NICK;
			else if (!strcmp(cep->value, "nick-user-host"))
				tempiConf.topic_setter = SETTER_NICK_USER_HOST;
		}
		else if (!strcmp(cep->name, "ban-setter"))
		{
			if (!strcmp(cep->value, "nick"))
				tempiConf.ban_setter = SETTER_NICK;
			else if (!strcmp(cep->value, "nick-user-host"))
				tempiConf.ban_setter = SETTER_NICK_USER_HOST;
		}
		else if (!strcmp(cep->name, "ban-setter-sync") || !strcmp(cep->name, "ban-setter-synch"))
		{
			tempiConf.ban_setter_sync = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "part-instead-of-quit-on-comment-change"))
		{
			tempiConf.part_instead_of_quit_on_comment_change = config_checkval(cep->value, CFG_YESNO);
		}
		else if (!strcmp(cep->name, "broadcast-channel-messages"))
		{
			if (!strcmp(cep->value, "auto"))
				tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_AUTO;
			else if (!strcmp(cep->value, "always"))
				tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_ALWAYS;
			else if (!strcmp(cep->value, "never"))
				tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_NEVER;
		}
		else if (!strcmp(cep->name, "allowed-channelchars"))
		{
			tempiConf.allowed_channelchars = allowed_channelchars_strtoval(cep->value);
		}
		else if (!strcmp(cep->name, "hide-idle-time"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "policy"))
					tempiConf.hide_idle_time = hideidletime_strtoval(cepp->value);
			}
		} else if (!strcmp(cep->name, "limit-svscmds"))
		{
			if (!strcmp(cep->value, "ulines"))
				tempiConf.limit_svscmds = LIMIT_SVSCMDS_ULINES;
			else
				tempiConf.limit_svscmds = LIMIT_SVSCMDS_SERVERS;
		} else if (!strcmp(cep->name, "high-connection-rate"))
		{
			tempiConf.high_connection_rate = atoi(cep->value);
		} else if (!strcmp(cep->name, "send-isupport-updates"))
		{
			tempiConf.send_isupport_updates = config_checkval(cep->value, CFG_YESNO);
		} else if (!strcmp(cep->name, "best-practices"))
		{
			/* This is handled in config test already (there is no other way) */
		} else if (!strcmp(cep->name, "dns"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "client"))
				{
					for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
					{
						if (!strcmp(ceppp->name, "timeout"))
							tempiConf.dns_client_timeout = atoi(ceppp->value);
						else if (!strcmp(ceppp->name, "retry"))
							tempiConf.dns_client_retry = atoi(ceppp->value);
					}
				} else
				if (!strcmp(cepp->name, "dnsbl"))
				{
					for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
					{
						if (!strcmp(ceppp->name, "timeout"))
							tempiConf.dns_dnsbl_timeout = atoi(ceppp->value);
						else if (!strcmp(ceppp->name, "retry"))
							tempiConf.dns_dnsbl_retry = atoi(ceppp->value);
					}
				}
			}
		} else if (!strcmp(cep->name, "network-icon")) {
			safe_strdup(tempiConf.network_icon, cep->value);
		} else if (config_set_dynamic_set_block_item(conf, &dynamic_set, cep))
		{
			/* Handled by config_set_dynamic_set_block_item - nothing to do here */
		} else
		{
			/* Call modules that hook HOOKTYPE_CONFIGRUN */
			int value;
			for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
			{
				value = (*(h->func.intfunc))(conf,cep,CONFIG_SET);
				if (value == 1)
					break;
			}
		}
	}
	return 0;
}

int	_test_set(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp, *ceppp, *cep4;
	int tempi;
	int errors = 0;
	int n;
	Hook *h;

	if (ce->value)
		return test_set_security_group(conf, ce);

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "kline-address")) {
			CheckNull(cep);
			CheckDuplicate(cep, kline_address, "kline-address");
			if (!strchr(cep->value, '@') && !strchr(cep->value, ':'))
			{
				config_error("%s:%i: set::kline-address must be an e-mail or an URL",
					cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
			else if (match_simple("*@unrealircd.com", cep->value) || match_simple("*@unrealircd.org",cep->value) || match_simple("unreal-*@lists.sourceforge.net",cep->value))
			{
				config_error("%s:%i: set::kline-address may not be an UnrealIRCd Team address",
					cep->file->filename, cep->line_number);
				errors++; continue;
			}
		}
		else if (!strcmp(cep->name, "gline-address")) {
			CheckNull(cep);
			CheckDuplicate(cep, gline_address, "gline-address");
			if (!strchr(cep->value, '@') && !strchr(cep->value, ':'))
			{
				config_error("%s:%i: set::gline-address must be an e-mail or an URL",
					cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
			else if (match_simple("*@unrealircd.com", cep->value) || match_simple("*@unrealircd.org",cep->value) || match_simple("unreal-*@lists.sourceforge.net",cep->value))
			{
				config_error("%s:%i: set::gline-address may not be an UnrealIRCd Team address",
					cep->file->filename, cep->line_number);
				errors++; continue;
			}
		}
		else if (!strcmp(cep->name, "modes-on-join")) {
			char *c;
			struct ChMode temp;
			memset(&temp, 0, sizeof(temp));
			CheckNull(cep);
			CheckDuplicate(cep, modes_on_join, "modes-on-join");
			for (c = cep->value; *c; c++)
			{
				if (*c == ' ')
					break; /* don't check the parameter ;p */
				switch (*c)
				{
					case 'q':
					case 'a':
					case 'o':
					case 'h':
					case 'v':
					case 'b':
					case 'e':
					case 'I':
						config_error("%s:%i: set::modes-on-join may not contain +%c",
							cep->file->filename, cep->line_number, *c);
						errors++;
						break;
				}
			}
			/* We can't really verify much here.
			 * The channel mode modules have not been initialized
			 * yet at this point, so we can't really verify much
			 * here.
			 */
		}
		else if (!strcmp(cep->name, "modes-on-oper")) {
			char *p;
			CheckNull(cep);
			CheckDuplicate(cep, modes_on_oper, "modes-on-oper");
			for (p = cep->value; *p; p++)
				if (strchr("orzS", *p))
				{
					config_error("%s:%i: set::modes-on-oper may not include mode '%c'",
						cep->file->filename, cep->line_number, *p);
					errors++;
				}
		}
		else if (!strcmp(cep->name, "snomask-on-oper")) {
			char *wrong_snomask;
			CheckNull(cep);
			CheckDuplicate(cep, snomask_on_oper, "snomask-on-oper");
			if (!is_valid_snomask_string_testing(cep->value, &wrong_snomask))
			{
				config_error("%s:%i: set::snomask-on-oper contains unknown snomask letter(s) '%s'",
					     cep->file->filename, cep->line_number, wrong_snomask);
				errors++;
				invalid_snomasks_encountered++;
			}
		}
		else if (!strcmp(cep->name, "server-notice-colors")) {
			CheckNull(cep);
		}
		else if (!strcmp(cep->name, "server-notice-show-event")) {
			CheckNull(cep);
		}
		else if (!strcmp(cep->name, "level-on-join")) {
			CheckNull(cep);
			CheckDuplicate(cep, level_on_join, "level-on-join");
			if (!channellevel_to_string(cep->value) && (strlen(cep->value) != 1))
			{
				config_error("%s:%i: set::level-on-join: unknown value '%s', should be one of: "
				             "'none', 'voice', 'halfop', 'op', 'admin', 'owner', or a single letter (eg 'o')",
				             cep->file->filename, cep->line_number, cep->value);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "who-limit")) {
			CheckNull(cep);
			CheckDuplicate(cep, who_limit, "who-limit");
			if (!config_checkval(cep->value,CFG_SIZE))
			{
				config_error("%s:%i: set::who-limit: value must be at least 1",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "maxbans")) {
			CheckNull(cep);
			CheckDuplicate(cep, maxbans, "maxbans");
		}
		else if (!strcmp(cep->name, "maxbanlength")) {
			config_warn("%s:%d: set::maxbanlength no longer exists as it was not deemed useful, this setting is now ignored.",
			            cep->file->filename, cep->line_number);
		}
		else if (!strcmp(cep->name, "silence-limit")) {
			CheckNull(cep);
			CheckDuplicate(cep, silence_limit, "silence-limit");
		}
		else if (!strcmp(cep->name, "oper-auto-join")) {
			CheckNull(cep);
			CheckDuplicate(cep, oper_auto_join, "oper-auto-join");
		}
		else if (!strcmp(cep->name, "oper-vhost")) {
			CheckNull(cep);
			if (!potentially_valid_vhost(cep->value))
			{
				config_error("%s:%i: set::oper-vhost contains illegal characters or is too long: '%s'",
					     cep->file->filename, cep->line_number, cep->value);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "check-target-nick-bans")) {
			CheckNull(cep);
			CheckDuplicate(cep, check_target_nick_bans, "check-target-nick-bans");
		}
		else if (!strcmp(cep->name, "pingpong-warning")) {
			config_error("%s:%i: set::pingpong-warning no longer exists (the warning is always off)",
			             cep->file->filename, cep->line_number);
			errors++;
		}
		else if (!strcmp(cep->name, "ping-cookie")) {
			CheckNull(cep);
			CheckDuplicate(cep, ping_cookie, "ping-cookie");
		}
		else if (!strcmp(cep->name, "watch-away-notification")) {
			CheckNull(cep);
			CheckDuplicate(cep, watch_away_notification, "watch-away-notification");
		}
		else if (!strcmp(cep->name, "uhnames")) {
			CheckNull(cep);
			CheckDuplicate(cep, uhnames, "uhnames");
		}
		else if (!strcmp(cep->name, "channel-command-prefix")) {
			CheckNullAllowEmpty(cep);
			CheckDuplicate(cep, channel_command_prefix, "channel-command-prefix");
		}
		else if (!strcmp(cep->name, "allow-userhost-change")) {
			CheckNull(cep);
			CheckDuplicate(cep, allow_userhost_change, "allow-userhost-change");
			if (strcasecmp(cep->value, "always") &&
			    strcasecmp(cep->value, "never") &&
			    strcasecmp(cep->value, "not-on-channels") &&
			    strcasecmp(cep->value, "force-rejoin"))
			{
				config_error("%s:%i: set::allow-userhost-change is invalid",
					cep->file->filename,
					cep->line_number);
				errors++;
				continue;
			}
		}
		else if (!strcmp(cep->name, "allow-setident")) {
			CheckNull(cep);
			CheckDuplicate(cep, allow_setident, "allow-setident");
		}
		else if (!strcmp(cep->name, "allow-setname")) {
			CheckNull(cep);
			CheckDuplicate(cep, allow_setname, "allow-setname");
		}
		else if (!strcmp(cep->name, "anti-spam-quit-message-time")) {
			CheckNull(cep);
			CheckDuplicate(cep, anti_spam_quit_message_time, "anti-spam-quit-message-time");
		}
		else if (!strcmp(cep->name, "oper-only-stats"))
		{
			config_warn("%s:%d: We no longer use a blacklist for stats (set::oper-only-stats) but "
			             "have a whitelist now instead (set::allow-user-stats). ",
			             cep->file->filename, cep->line_number);
			config_warn("Simply delete the oper-only-stats line from your configuration file %s around line %d to get rid of this warning",
			             cep->file->filename, cep->line_number);
			continue;
		}
		else if (!strcmp(cep->name, "allow-user-stats"))
		{
			CheckDuplicate(cep, allow_user_stats, "allow-user-stats");
			CheckNull(cep);
		}
		else if (!strcmp(cep->name, "ping-warning")) {
			CheckNull(cep);
			CheckDuplicate(cep, ping_warning, "ping-warning");
			tempi = atoi(cep->value);
			/* it is pointless to allow setting higher than 170 */
			if (tempi > 170)
			{
				config_error("%s:%i: set::ping-warning must be < 170",
					cep->file->filename,
					cep->line_number);
				errors++;
				continue;
			}
		}
		else if (!strcmp(cep->name, "maxdccallow")) {
			CheckNull(cep);
			CheckDuplicate(cep, maxdccallow, "maxdccallow");
		}
		else if (!strcmp(cep->name, "max-targets-per-command"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				CheckNull(cepp);
				if (!strcasecmp(cepp->name, "NAMES") || !strcasecmp(cepp->name, "WHOWAS"))
				{
					if (atoi(cepp->value) != 1)
					{
						config_error("%s:%i: set::max-targets-per-command::%s: "
						             "this command is hardcoded at a maximum of 1 "
						             "and cannot be configured to accept more.",
						             cepp->file->filename,
						             cepp->line_number,
						             cepp->name);
						errors++;
					}
				} else
				if (!strcasecmp(cepp->name, "USERHOST") ||
				    !strcasecmp(cepp->name, "USERIP") ||
				    !strcasecmp(cepp->name, "ISON") ||
				    !strcasecmp(cepp->name, "WATCH"))
				{
					if (strcmp(cepp->value, "max"))
					{
						config_error("%s:%i: set::max-targets-per-command::%s: "
						             "this command is hardcoded at a maximum of 'max' "
						             "and cannot be changed. This because it is "
						             "highly discouraged to change it.",
						             cepp->file->filename,
						             cepp->line_number,
						             cepp->name);
						errors++;
					}
				}
				/* Now check the value syntax in general: */
				if (strcmp(cepp->value, "max")) /* anything other than 'max'.. */
				{
					int v = atoi(cepp->value);
					if ((v < 1) || (v > 20))
					{
						config_error("%s:%i: set::max-targets-per-command::%s: "
						             "value should be 1-20 or 'max'",
						             cepp->file->filename,
						             cepp->line_number,
						             cepp->name);
						errors++;
					}
				}
			}
		}
		else if (!strcmp(cep->name, "network-name")) {
			char *p;
			CheckNull(cep);
			CheckDuplicate(cep, network_name, "network-name");
			for (p = cep->value; *p; p++)
				if ((*p < ' ') || (*p > '~'))
				{
					config_error("%s:%i: set::network-name can only contain ASCII characters 33-126. Invalid character = '%c'",
						cep->file->filename, cep->line_number, *p);
					errors++;
					break;
				}
		}
		else if (!strcmp(cep->name, "network-icon")) {
			/* Maximum URL length is (with a few characters margin):
			 * 510 (IRC protocol line) - 55 for the static text (whitespace, ":", "375", "draft/ICON=", ":are supported by this server", etc)
			 * - HOSTLEN (lazy me.name max) - NICKLEN (max nick length)
			 * Which comes down to 360 which should be plenty.
			 */
			int max_url_len = 510 - 55 - HOSTLEN - NICKLEN;

			CheckNull(cep);
			CheckDuplicate(cep, network_icon, "network-icon");
			if ((strncmp(cep->value, "https://", 8) != 0) || !valid_text_nospaces(cep->value)) {
				config_error("%s:%i: set::network-icon URL must be single-quoted and start with 'https://' like 'https://example.com/image.jpg'",
					cep->file->filename, cep->line_number);
				errors++;
			}
			if (strlen(cep->value) > max_url_len) {
				config_error("%s:%i: set::network-icon URL is too long (max %d characters)",
					cep->file->filename, cep->line_number, max_url_len);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "default-server")) {
			CheckNull(cep);
			CheckDuplicate(cep, default_server, "default-server");
		}
		else if (!strcmp(cep->name, "services-server")) {
			CheckNull(cep);
			CheckDuplicate(cep, services_server, "services-server");
		}
		else if (!strcmp(cep->name, "sasl-server")) {
			CheckNull(cep);
			CheckDuplicate(cep, sasl_server, "sasl-server");
		}
		else if (!strcmp(cep->name, "stats-server")) {
			CheckNull(cep);
			CheckDuplicate(cep, stats_server, "stats-server");
		}
		else if (!strcmp(cep->name, "help-channel")) {
			CheckNull(cep);
			CheckDuplicate(cep, help_channel, "help-channel");
		}
		else if (!strcmp(cep->name, "cloak-prefix") || !strcmp(cep->name, "hiddenhost-prefix")) {
			CheckNull(cep);
			CheckDuplicate(cep, hiddenhost_prefix, "cloak-prefix");
			if (strchr(cep->value, ' ') || (*cep->value == ':'))
			{
				config_error("%s:%i: set::cloak-prefix must not contain spaces or be prefixed with ':'",
					cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
		}
		else if (!strcmp(cep->name, "prefix-quit")) {
			CheckNull(cep);
			CheckDuplicate(cep, prefix_quit, "prefix-quit");
		}
		else if (!strcmp(cep->name, "hide-ban-reason")) {
			CheckNull(cep);
			CheckDuplicate(cep, hide_ban_reason, "hide-ban-reason");
			if (strcmp(cep->value, "yes") &&
			    strcmp(cep->value, "no") &&
			    strcmp(cep->value, "auto"))
			{
				config_error("%s:%i: set::hide-ban-reason must be one of: yes, no, auto",
					cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
		}
		else if (!strcmp(cep->name, "hide-killed-by")) {
			CheckNull(cep);
			CheckDuplicate(cep, hide_killed_by, "hide-killed-by");
		}
		else if (!strcmp(cep->name, "restrict-channelmodes"))
		{
			CheckNull(cep);
			CheckDuplicate(cep, restrict_channelmodes, "restrict-channelmodes");
			if (strchr(cep->value, '+') || strchr(cep->value, '-'))
			{
				config_error("%s:%i: set::restrict-channelmodes: may only contain mode characters, no + or -.",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "restrict-extendedbans"))
		{
			config_error("%s:%d: set::restrict-extendedbans has no effect since UnrealIRCd 6.0.0. "
			             "If you don't want a particular extended ban then don't load the module. "
			             "Use something like this: blacklist-module \"extbans/quiet\";",
			             cep->file->filename, cep->line_number);
			errors++;
		}
		else if (!strcmp(cep->name, "named-extended-bans"))
		{
			CheckNull(cep);
		}
		else if (!strcmp(cep->name, "link")) {
					for (cepp = cep->items; cepp; cepp = cepp->next) {
						CheckNull(cepp);
						if (!strcmp(cepp->name, "bind-ip")) {
							CheckDuplicate(cepp, link_bind_ip, "link::bind-ip");
							if (strcmp(cepp->value, "*"))
							{
								if (!is_valid_ip(cepp->value))
								{
									config_error("%s:%i: set::link::bind-ip (%s) is not a valid IP",
										cepp->file->filename, cepp->line_number,
										cepp->value);
									errors++;
									continue;
								}
							}
						}
					}
		}
		else if (!strcmp(cep->name, "throttle")) {
			config_error("%s:%i: set::throttle has been renamed. you now use "
			             "set::anti-flood::connect-flood <connections>:<period>. "
			             "Or just remove the throttle block and you get the default "
			             "of 3 per 60 seconds.",
			             cep->file->filename, cep->line_number);
			errors++;
			continue;
		}
		else if (!strcmp(cep->name, "anti-flood"))
		{
			int anti_flood_old = 0;
			int anti_flood_old_and_default = 0;

			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				int has_lag_penalty = 0;
				int has_lag_penalty_bytes = 0;

				/* Test for old options: */
				if (flood_option_is_old(cepp->name))
				{
					/* Special code if the user is using 100% of the defaults */
					if (cepp->value &&
					    ((!strcmp(cepp->name, "nick-flood") && !strcmp(cepp->value, "3:60")) ||
					     (!strcmp(cepp->name, "connect-flood") && cepp->value && !strcmp(cepp->value, "3:60")) ||
					     (!strcmp(cepp->name, "away-flood") && cepp->value && !strcmp(cepp->value, "4:120"))))
					{
						anti_flood_old_and_default = 1;
					} else
					{
						anti_flood_old = 1;
					}
					continue;
				}

				for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
				{
					int everyone;
					int for_everyone;
					int used = 0;
					Hook *h;

					/* First, check hooks... */
					for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
					{
						int value, errs = 0;
						if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
							&& !(h->owner->options & MOD_OPT_PERM))
							continue;
						value = (*(h->func.intfunc))(conf,ceppp,CONFIG_SET_ANTI_FLOOD,&errs);
						if (value == 2)
						{
							used = 2;
							break;
						} else
						if (value == 1)
						{
							used = 1;
							break;
						} else
						if (value == -1)
						{
							used = 1;
							errors += errs;
							break;
						} else
						if (value == -2)
						{
							used = 2;
							errors += errs;
							break;
						}
					}
					if (used == 1)
						continue; /* module handled it */
					if (used == 2)
						break; /* module handled it and we must stop entire block processing */

					/* Prevent users from using options that belong in "everyone"
					 * at other places, and vice-versa.
					 */
					everyone = !strcmp(cepp->name, "everyone") ? 1 : 0;
					for_everyone = flood_option_is_for_everyone(ceppp->name);
					if (everyone && !for_everyone)
					{
						config_error("%s:%i: %s cannot be in the set::anti-flood::everyone block. "
						             "You can put it in 'known-users' or 'unknown-users' instead.",
							ceppp->file->filename, ceppp->line_number,
							ceppp->name);
						errors++;
						continue;
					} else
					if (!everyone && for_everyone)
					{
						config_error("%s:%i: %s must be in the set::anti-flood::everyone block, not anywhere else.",
							ceppp->file->filename, ceppp->line_number,
							ceppp->name);
						errors++;
						continue;
					}

					/* Now comes the actual config check for each element... */
					if (!strcmp(ceppp->name, "max-concurrent-conversations"))
					{
						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
						{
							CheckNull(cep4);
							if (!strcmp(cep4->name, "users"))
							{
								int v = atoi(cep4->value);
								if ((v < 1) || (v > MAXCCUSERS))
								{
									config_error("%s:%i: set::anti-flood::max-concurrent-conversations::users: "
										     "value should be between 1 and %d",
										     cep4->file->filename, cep4->line_number, MAXCCUSERS);
									errors++;
								}
							} else
							if (!strcmp(cep4->name, "new-user-every"))
							{
								long v = config_checkval(cep4->value, CFG_TIME);
								if ((v < 1) || (v > 120))
								{
									config_error("%s:%i: set::anti-flood::max-concurrent-conversations::new-user-every: "
										     "value should be between 1 and 120 seconds",
										     cep4->file->filename, cep4->line_number);
									errors++;
								}
							} else
							{
								config_error_unknownopt(cep4->file->filename,
									cep4->line_number, "set::anti-flood",
									cep4->name);
								errors++;
							}
						}
						continue; /* required here, due to checknull directly below */
					}
					else if (!strcmp(ceppp->name, "multiline"))
					{
						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
						{
							CheckNull(cep4);
							if (!strcmp(cep4->name, "max-lines"))
							{
								int v = atoi(cep4->value);
								if ((v < 2) || (v > MULTILINE_MAX_CONFIGURABLE_LINES))
								{
									config_error("%s:%i: set::anti-flood::multiline::max-lines: "
										     "value should be between 2 and %d",
										     cep4->file->filename, cep4->line_number,
										     MULTILINE_MAX_CONFIGURABLE_LINES);
									errors++;
								}
							} else
							if (!strcmp(cep4->name, "max-bytes"))
							{
								int v = config_checkval(cep4->value, CFG_SIZE);
								if ((v < 256) || (v > MULTILINE_MAX_CONFIGURABLE_BYTES))
								{
									config_error("%s:%i: set::anti-flood::multiline::max-bytes: "
										     "value should be between 256 and %d",
										     cep4->file->filename, cep4->line_number,
										     MULTILINE_MAX_CONFIGURABLE_BYTES);
									errors++;
								}
							} else
							{
								config_error_unknownopt(cep4->file->filename,
									cep4->line_number, "set::anti-flood::multiline",
									cep4->name);
								errors++;
							}
						}
						continue;
					}
					else if (!strcmp(ceppp->name, "maxchannelsperuser"))
					{
						CheckNull(ceppp);
					}
					else if (!strcmp(ceppp->name, "unknown-flood-amount") ||
						 !strcmp(ceppp->name, "unknown-flood-bantime"))
					{
						config_error("%s:%i: set::anti-flood::%s: this setting has been moved. "
							     "See https://www.unrealircd.org/docs/Anti-flood_settings#handshake-data-flood",
							     ceppp->file->filename, ceppp->line_number, ceppp->name);
						errors++;
						continue;
					}
					else if (!strcmp(ceppp->name, "handshake-data-flood"))
					{
						for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
						{
							if (!strcmp(cep4->name, "amount"))
							{
								long v;
								CheckNull(cep4);
								v = config_checkval(cep4->value, CFG_SIZE);
								if (v < 1024)
								{
									config_error("%s:%i: set::anti-flood::handshake-data-flood::amount must be at least 1024 bytes",
										cep4->file->filename, cep4->line_number);
									errors++;
								}
							} else
							if (!strcmp(cep4->name, "ban-action") || !strcmp(cep4->name, "action"))
							{
								CheckNull(cep4);
								errors += test_ban_action_config(cep4);
							} else
							if (!strcmp(cep4->name, "ban-time"))
							{
								CheckNull(cep4);
							} else
							{
								config_error_unknownopt(cep4->file->filename,
									cep4->line_number, "set::anti-flood::handshake-data-flood",
									cep4->name);
								errors++;
							}
						}
					}
					else if (!strcmp(ceppp->name, "away-count"))
					{
						int temp = atol(ceppp->value);
						CheckNull(ceppp);
						if (temp < 1 || temp > 255)
						{
							config_error("%s:%i: set::anti-flood::away-count must be between 1 and 255",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "away-period"))
					{
						CheckNull(ceppp);
						int temp = config_checkval(ceppp->value, CFG_TIME);
						if (temp < 10)
						{
							config_error("%s:%i: set::anti-flood::away-period must be greater than 9",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "away-flood"))
					{
						int cnt, period;
						CheckNull(ceppp);
						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
						    (cnt < 1) || (cnt > 255) || (period < 10))
						{
							config_error("%s:%i: set::anti-flood::away-flood error. Syntax is '<count>:<period>' (eg 5:60), "
								     "count should be 1-255, period should be greater than 9",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "nick-flood"))
					{
						int cnt, period;
						CheckNull(ceppp);
						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
						    (cnt < 1) || (cnt > 255) || (period < 5))
						{
							config_error("%s:%i: set::anti-flood::nick-flood error. Syntax is '<count>:<period>' (eg 5:60), "
								     "count should be 1-255, period should be greater than 4",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "vhost-flood"))
					{
						int cnt, period;
						CheckNull(ceppp);
						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
						    (cnt < 1) || (cnt > 255) || (period < 5))
						{
							config_error("%s:%i: set::anti-flood::vhost-flood error. Syntax is '<count>:<period>' (eg 5:60), "
								     "count should be 1-255, period should be greater than 4",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "join-flood"))
					{
						int cnt, period;
						CheckNull(ceppp);

						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
						    (cnt < 1) || (cnt > 255) || (period < 5))
						{
							config_error("%s:%i: join-flood error. Syntax is '<count>:<period>' (eg 5:60), "
								     "count should be 1-255, period should be greater than 4",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "invite-flood"))
					{
						int cnt, period;
						CheckNull(ceppp);
						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
						    (cnt < 1) || (cnt > 255) || (period < 5))
						{
							config_error("%s:%i: set::anti-flood::invite-flood error. Syntax is '<count>:<period>' (eg 5:60), "
								     "count should be 1-255, period should be greater than 4",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "knock-flood"))
					{
						int cnt, period;
						CheckNull(ceppp);
						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
						    (cnt < 1) || (cnt > 255) || (period < 5))
						{
							config_error("%s:%i: set::anti-flood::knock-flood error. Syntax is '<count>:<period>' (eg 5:60), "
								     "count should be 1-255, period should be greater than 4",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "lag-penalty"))
					{
						int v;
						CheckNull(ceppp);
						v = atoi(ceppp->value);
						has_lag_penalty = 1;
						if ((v < 0) || (v > 10000))
						{
							config_error("%s:%i: set::anti-flood::%s::lag-penalty: value is in milliseconds and should be between 0 and 10000",
								ceppp->file->filename, ceppp->line_number, cepp->name);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "lag-penalty-bytes"))
					{
						has_lag_penalty_bytes = 1;
						CheckNull(ceppp);
					}
					else if (!strcmp(ceppp->name, "connect-flood"))
					{
						int cnt, period;
						CheckNull(ceppp);
						if (strcmp(cepp->name, "everyone"))
						{
							config_error("%s:%i: connect-flood must be in the set::anti-flood::everyone block, not anywhere else.",
								ceppp->file->filename, ceppp->line_number);
							errors++;
							continue;
						}
						if (!config_parse_flood(ceppp->value, &cnt, &period) ||
						    (cnt < 1) || (cnt > 255) || (period < 1) || (period > 3600))
						{
							config_error("%s:%i: set::anti-flood::connect-flood: Syntax is '<count>:<period>' (eg 5:60), "
								     "count should be 1-255, period should be 1-3600",
								ceppp->file->filename, ceppp->line_number);
							errors++;
						}
					}
					else if (!strcmp(ceppp->name, "maxchannelsperuser"))
					{
						CheckNull(ceppp);
					}
					else
					{
						config_error_unknownopt(ceppp->file->filename,
							ceppp->line_number, "set::anti-flood",
							ceppp->name);
						errors++;
					}
				}
				if (has_lag_penalty+has_lag_penalty_bytes == 1)
				{
					config_error("%s:%i: set::anti-flood::%s: if you use lag-penalty then you must also add an lag-penalty-bytes item (and vice-versa)",
						cepp->file->filename, cepp->line_number, cepp->name);
					errors++;
				}
			}
			/* Now the warnings: */
			if (anti_flood_old == 1)
			{
				config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible. "
				            "Your custom anti-flood settings have NOT been read.",
				            cep->file->filename, cep->line_number);
				config_warn("See https://www.unrealircd.org/docs/Anti-flood_settings for the new block style,");
				config_warn("OR: simply remove all the anti-flood options from the conf to get rid of this "
				            "warning and use the built-in defaults.");
			} else
			if (anti_flood_old_and_default == 1)
			{
				config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible.",
					    cep->file->filename, cep->line_number);
				config_warn("To fix this warning, delete the anti-flood block from your configuration file "
				            "(file %s around line %d), this will make UnrealIRCd use the built-in defaults.",
				            cep->file->filename, cep->line_number);
				config_warn("If you want to learn more about the new functionality you can visit "
				            "https://www.unrealircd.org/docs/Anti-flood_settings");
			}
		}
		else if (!strcmp(cep->name, "log-throttle"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				int cnt = 0, period = 0;
				if (!cepp->name)
					continue;
				if (!cepp->value)
				{
					config_error("%s:%i: set::log-throttle::%s: missing value "
					             "(need 'count:period' or 'unlimited')",
						cepp->file->filename, cepp->line_number, cepp->name);
					errors++;
					continue;
				}
				if (!valid_event_id(cepp->name))
				{
					config_error("%s:%i: set::log-throttle::%s: invalid event_id name "
					             "(must contain only uppercase A-Z, 0-9 and underscores)",
						cepp->file->filename, cepp->line_number, cepp->name);
					errors++;
					continue;
				}
				if (!strcmp(cepp->value, "unlimited") || !strcmp(cepp->value, "max"))
					continue;
				if (!config_parse_flood(cepp->value, &cnt, &period) || (cnt < 1) || (period < 1))
				{
					config_error("%s:%i: set::log-throttle::%s: invalid value '%s'. "
					             "Syntax is '<count>:<period>' (eg 100:60), or 'unlimited'",
						cepp->file->filename, cepp->line_number, cepp->name, cepp->value);
					errors++;
				}
			}
		}
		else if (!strcmp(cep->name, "options")) {
			for (cepp = cep->items; cepp; cepp = cepp->next) {
				if (!strcmp(cepp->name, "hide-ulines"))
				{
					CheckDuplicate(cepp, options_hide_ulines, "options::hide-ulines");
				}
				else if (!strcmp(cepp->name, "flat-map")) {
					CheckDuplicate(cepp, options_flat_map, "options::flat-map");
				}
				else if (!strcmp(cepp->name, "show-opermotd")) {
					CheckDuplicate(cepp, options_show_opermotd, "options::show-opermotd");
				}
				else if (!strcmp(cepp->name, "identd-check")) {
					CheckDuplicate(cepp, options_identd_check, "options::identd-check");
				}
				else if (!strcmp(cepp->name, "fail-oper-warn")) {
					CheckDuplicate(cepp, options_fail_oper_warn, "options::fail-oper-warn");
				}
				else if (!strcmp(cepp->name, "show-connect-info")) {
					CheckDuplicate(cepp, options_show_connect_info, "options::show-connect-info");
				}
				else if (!strcmp(cepp->name, "no-connect-tls-info")) {
					CheckDuplicate(cepp, options_no_connect_tls_info, "options::no-connect-tls-info");
				}
				else if (!strcmp(cepp->name, "dont-resolve")) {
					CheckDuplicate(cepp, options_dont_resolve, "options::dont-resolve");
				}
				else if (!strcmp(cepp->name, "mkpasswd-for-everyone")) {
					CheckDuplicate(cepp, options_mkpasswd_for_everyone, "options::mkpasswd-for-everyone");
				}
				else if (!strcmp(cepp->name, "allow-insane-bans")) {
					CheckDuplicate(cepp, options_allow_insane_bans, "options::allow-insane-bans");
				}
				else if (!strcmp(cepp->name, "allow-part-if-shunned")) {
					CheckDuplicate(cepp, options_allow_part_if_shunned, "options::allow-part-if-shunned");
				}
				else if (!strcmp(cepp->name, "disable-cap")) {
					CheckDuplicate(cepp, options_disable_cap, "options::disable-cap");
				}
				else if (!strcmp(cepp->name, "disable-ipv6")) {
					CheckDuplicate(cepp, options_disable_ipv6, "options::disable-ipv6");
					DISABLE_IPV6 = 1; /* ugly ugly. needs to be done here because at conf runtime is too late. */
				}
				else
				{
					config_error_unknownopt(cepp->file->filename,
						cepp->line_number, "set::options",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "hosts")) {
			config_error("%s:%i: set::hosts has been removed. You can use oper::vhost now.",
				cep->file->filename, cep->line_number);
			errors++;
		}
		else if (!strcmp(cep->name, "cloak-keys"))
		{
			CheckDuplicate(cep, cloak_keys, "cloak-keys");
			for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
			{
				int value, errs = 0;
				if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
				    && !(h->owner->options & MOD_OPT_PERM))
					continue;
				value = (*(h->func.intfunc))(conf, cep, CONFIG_CLOAKKEYS, &errs);

				if (value == 1)
					break;
				if (value == -1)
				{
					errors += errs;
					break;
				}
				if (value == -2)
					errors += errs;
			}
		}
		else if (!strcmp(cep->name, "ident")) {
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				int is_ok = 0;
				CheckNull(cepp);
				if (!strcmp(cepp->name, "connect-timeout"))
				{
					is_ok = 1;
					CheckDuplicate(cepp, ident_connect_timeout, "ident::connect-timeout");
				}
				else if (!strcmp(cepp->name, "read-timeout"))
				{
					is_ok = 1;
					CheckDuplicate(cepp, ident_read_timeout, "ident::read-timeout");
				}
				if (is_ok)
				{
					int v = config_checkval(cepp->value,CFG_TIME);
					if ((v > 60) || (v < 1))
					{
						config_error("%s:%i: set::ident::%s value out of range (%d), should be between 1 and 60.",
							cepp->file->filename, cepp->line_number, cepp->name, v);
						errors++;
						continue;
					}
				} else {
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::ident",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "timesync") || !strcmp(cep->name, "timesynch"))
		{
			config_warn("%s:%i: Timesync support has been removed from UnrealIRCd. "
			            "Please remove any set::timesync blocks you may have.",
			            cep->file->filename, cep->line_number);
			config_warn("Use the time synchronization feature of your OS/distro instead!");
		}
		else if (!strcmp(cep->name, "spamfilter")) {
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "except"))
				{
					if (cepp->value)
					{
						/* Old compatibility code */
						config_warn("%s: %d: set::spamfilter::except is now a mask item. "
						            "Your setting has been read correctly but please update your item "
						            "because future UnrealIRCd versions will make this an error. "
						            "Update your item to use this style: "
						            "except { destination { \"#chan1\"; \"#chan2\"; \"SomeNick\"; } }",
						            cepp->file->filename, cepp->line_number);
						config_warn("For more information, see https://www.unrealircd.org/docs/Set_block#set::spamfilter::except");
					} else {
						test_match_block(conf, cepp, &errors);
					}
					continue; // needed, because we are above the CheckNull and the multiple if's below.
				}
				CheckNull(cepp);
				if (!strcmp(cepp->name, "ban-time"))
				{
					long x;
					CheckDuplicate(cepp, spamfilter_ban_time, "spamfilter::ban-time");
					x = config_checkval(cepp->value,CFG_TIME);
					if ((x < 0) > (x > 2000000000))
					{
						config_error("%s:%i: set::spamfilter:ban-time: value '%ld' out of range",
							cep->file->filename, cep->line_number, x);
						errors++;
						continue;
					}
				} else
				if (!strcmp(cepp->name, "ban-reason"))
				{
					CheckDuplicate(cepp, spamfilter_ban_reason, "spamfilter::ban-reason");

				}
				else if (!strcmp(cepp->name, "virus-help-channel"))
				{
					CheckDuplicate(cepp, spamfilter_virus_help_channel, "spamfilter::virus-help-channel");
					if ((cepp->value[0] != '#') || (strlen(cepp->value) > CHANNELLEN))
					{
						config_error("%s:%i: set::spamfilter:virus-help-channel: "
						             "specified channelname is too long or contains invalid characters (%s)",
						             cep->file->filename, cep->line_number,
						             cepp->value);
						errors++;
						continue;
					}
				} else
				if (!strcmp(cepp->name, "virus-help-channel-deny"))
				{
					CheckDuplicate(cepp, spamfilter_virus_help_channel_deny, "spamfilter::virus-help-channel-deny");
				} else
#ifdef SPAMFILTER_DETECTSLOW
				if (!strcmp(cepp->name, "detect-slow-warn"))
				{
				} else
				if (!strcmp(cepp->name, "detect-slow-fatal"))
				{
				} else
#endif
				if (!strcmp(cepp->name, "stop-on-first-match"))
				{
				} else
				if (!strcmp(cepp->name, "utf8"))
				{
					tempiConf.spamfilter_utf8 = config_checkval(cepp->value, CFG_YESNO);
				} else
				if (!strcmp(cepp->name, "show-message-content-on-hit"))
				{
					if (!spamfilter_show_message_content_on_hit_strtoval(cepp->value))
					{
						config_error("%s:%d: set::spamfilter::show-message-content-on-hit: unknown value '%s', "
						             "must be one of: always, never, channel-only.",
						             cepp->file->filename, cepp->line_number, cepp->value);
						errors++;
					}
				} else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::spamfilter",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "central-spamfilter"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "except"))
				{
					test_match_block(conf, cepp, &errors);
				} else
				if (!cepp->value)
				{
					CheckNull(cepp);
				} else
				if (!strcmp(cepp->name, "url"))
				{
				} else
				if (!strcmp(cepp->name, "refresh-time"))
				{
					int v = config_checkval(cepp->value, CFG_TIME);
#ifndef DEBUGMODE
					if (v < 180)
					{
						config_error("%s:%i: set::central-spamfilter::refresh-time needs to be more than 3 minutes",
							cepp->file->filename, cepp->line_number);
						errors++;
					}
#endif
				} else
				if (!strcmp(cepp->name, "verbose"))
				{
				} else
				if (!strcmp(cepp->name, "enabled"))
				{
				} else
				if (!strcmp(cepp->name, "feed"))
				{
					if (strcmp(cepp->value, "testing") && strcmp(cepp->value, "standard") && strcmp(cepp->value, "fast"))
					{
						config_error("%s:%i: set::central-spamfilter::feed needs to be one of: "
						             "'fast' (for early access to new spamfilters) or 'standard' (the default).",
						             cepp->file->filename, cepp->line_number);
						errors++;
					}
				} else
				if (!strcmp(cepp->name, "limit-ban-action"))
				{
					int n = banact_stringtoval(cepp->value);
					if (n == 0)
					{
						config_error("%s:%i: set::central-spamfilter::limit-ban-action: unknown ban action '%s'",
							cepp->file->filename, cepp->line_number, cepp->value);
						errors++;
					} else
					if ((n == BAN_ACT_SET) || (n == BAN_ACT_REPORT))
					{
						config_error("%s:%i: set::central-spamfilter::limit-ban-action: you cannot use 'set' or 'report' here",
							cepp->file->filename, cepp->line_number);
						errors++;
					}
				} else
				if (!strcmp(cepp->name, "limit-ban-time"))
				{
					if (config_checkval(cepp->value, CFG_TIME) < 1)
					{
						config_error("%s:%i: set::central-spamfilter::limit-ban-time is zero or less (invalid)",
							cepp->file->filename, cepp->line_number);
						errors++;
					}
				} else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::central-spamfilter",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "default-bantime"))
		{
			long x;
			CheckDuplicate(cep, default_bantime, "default-bantime");
			CheckNull(cep);
			x = config_checkval(cep->value,CFG_TIME);
			if ((x < 0) > (x > 2000000000))
			{
				config_error("%s:%i: set::default-bantime: value '%ld' out of range",
					cep->file->filename, cep->line_number, x);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "ban-version-tkl-time")) {
			long x;
			CheckDuplicate(cep, ban_version_tkl_time, "ban-version-tkl-time");
			CheckNull(cep);
			x = config_checkval(cep->value,CFG_TIME);
			if ((x < 0) > (x > 2000000000))
			{
				config_error("%s:%i: set::ban-version-tkl-time: value '%ld' out of range",
					cep->file->filename, cep->line_number, x);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "min-nick-length")) {
			int v;
			CheckDuplicate(cep, min_nick_length, "min-nick-length");
			CheckNull(cep);
			v = atoi(cep->value);
			if ((v <= 0) || (v > NICKLEN))
			{
				config_error("%s:%i: set::min-nick-length: value '%d' out of range (should be 1-%d)",
					cep->file->filename, cep->line_number, v, NICKLEN);
				errors++;
			}
			else
				nicklengths.min = v;
		}
		else if (!strcmp(cep->name, "nick-length")) {
			int v;
			CheckDuplicate(cep, nick_length, "nick-length");
			CheckNull(cep);
			v = atoi(cep->value);
			if ((v <= 0) || (v > NICKLEN))
			{
				config_error("%s:%i: set::nick-length: value '%d' out of range (should be 1-%d)",
					cep->file->filename, cep->line_number, v, NICKLEN);
				errors++;
			}
			else
				nicklengths.max = v;
		}
		else if (!strcmp(cep->name, "topic-length")) {
			int v;
			CheckNull(cep);
			v = atoi(cep->value);
			if ((v <= 0) || (v > MAXTOPICLEN))
			{
				config_error("%s:%i: set::topic-length: value '%d' out of range (should be 1-%d)",
					cep->file->filename, cep->line_number, v, MAXTOPICLEN);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "away-length")) {
			int v;
			CheckNull(cep);
			v = atoi(cep->value);
			if ((v <= 0) || (v > MAXAWAYLEN))
			{
				config_error("%s:%i: set::away-length: value '%d' out of range (should be 1-%d)",
					cep->file->filename, cep->line_number, v, MAXAWAYLEN);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "kick-length")) {
			int v;
			CheckNull(cep);
			v = atoi(cep->value);
			if ((v <= 0) || (v > MAXKICKLEN))
			{
				config_error("%s:%i: set::kick-length: value '%d' out of range (should be 1-%d)",
					cep->file->filename, cep->line_number, v, MAXKICKLEN);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "quit-length")) {
			int v;
			CheckNull(cep);
			v = atoi(cep->value);
			if ((v <= 0) || (v > MAXQUITLEN))
			{
				config_error("%s:%i: set::quit-length: value '%d' out of range (should be 1-%d)",
					cep->file->filename, cep->line_number, v, MAXQUITLEN);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "ssl") || !strcmp(cep->name, "tls")) {
			test_tlsblock(conf, cep, &errors);
		}
		else if (!strcmp(cep->name, "plaintext-policy"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "user") ||
					!strcmp(cepp->name, "oper") ||
					!strcmp(cepp->name, "server"))
				{
					Policy policy;
					CheckNull(cepp);
					policy = policy_strtoval(cepp->value);
					if (!policy)
					{
						config_error("%s:%i: set::plaintext-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'",
							cepp->file->filename, cepp->line_number, cepp->name);
						errors++;
					}
				} else if (!strcmp(cepp->name, "user-message") ||
				           !strcmp(cepp->name, "oper-message"))
				{
					CheckNull(cepp);
				} else {
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::plaintext-policy",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "outdated-tls-policy"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "user") ||
					!strcmp(cepp->name, "oper") ||
					!strcmp(cepp->name, "server"))
				{
					Policy policy;
					CheckNull(cepp);
					policy = policy_strtoval(cepp->value);
					if (!policy)
					{
						config_error("%s:%i: set::outdated-tls-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'",
							cepp->file->filename, cepp->line_number, cepp->name);
						errors++;
					}
				} else if (!strcmp(cepp->name, "user-message") ||
				           !strcmp(cepp->name, "oper-message"))
				{
					CheckNull(cepp);
				} else {
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::outdated-tls-policy",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "default-ipv6-clone-mask"))
		{
			/* keep this in sync with _test_allow() */
			int ipv6mask;
			CheckNull(cep);
			ipv6mask = atoi(cep->value);
			if (ipv6mask == 0)
			{
				config_error("%s:%d: set::default-ipv6-clone-mask given a value of zero. This cannnot be correct, as it would treat all IPv6 hosts as one host.",
					     cep->file->filename, cep->line_number);
				errors++;
			}
			if (ipv6mask > 128)
			{
				config_error("%s:%d: set::default-ipv6-clone-mask was set to %d. The maximum value is 128.",
					     cep->file->filename, cep->line_number,
					     ipv6mask);
				errors++;
			}
			if (ipv6mask <= 32)
			{
				config_warn("%s:%d: set::default-ipv6-clone-mask was given a very small value.",
					    cep->file->filename, cep->line_number);
			}
		}
		else if (!strcmp(cep->name, "hide-list")) {
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "deny-channel"))
				{
				} else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::hide-list",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "max-unknown-connections-per-ip")) {
			int v;
			CheckNull(cep);
			v = atoi(cep->value);
			if (v < 1)
			{
				config_error("%s:%i: set::max-unknown-connections-per-ip: value should be at least 1.",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "handshake-timeout")) {
			int v;
			CheckNull(cep);
			v = config_checkval(cep->value, CFG_TIME);
			if (v < 5)
			{
				config_error("%s:%i: set::handshake-timeout: value should be at least 5 seconds.",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "sasl-timeout")) {
			int v;
			CheckNull(cep);
			v = config_checkval(cep->value, CFG_TIME);
			if (v < 5)
			{
				config_error("%s:%i: set::sasl-timeout: value should be at least 5 seconds.",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "handshake-delay"))
		{
			int v;
			CheckNull(cep);
			v = config_checkval(cep->value, CFG_TIME);
			if (v >= 10)
			{
				config_error("%s:%i: set::handshake-delay: value should be less than 10 seconds.",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "handshake-boot-delay"))
		{
			int v;
			CheckNull(cep);
			v = config_checkval(cep->value, CFG_TIME);
			if (v > 15)
			{
				config_error("%s:%i: set::handshake-boot-delay: value cannot be more than 15 seconds.",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "ban-include-username"))
		{
			config_error("%s:%i: set::ban-include-username is no longer supported. "
			             "Use set { automatic-ban-target userip; }; instead.",
			             cep->file->filename, cep->line_number);
			config_error("See https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target "
			             "for more information and options.");
			errors++;
		}
		else if (!strcmp(cep->name, "automatic-ban-target"))
		{
			CheckNull(cep);
			if (!ban_target_strtoval(cep->value))
			{
				config_error("%s:%i: set::automatic-ban-target: value '%s' is not recognized. "
				             "See https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target",
				             cep->file->filename, cep->line_number, cep->value);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "manual-ban-target"))
		{
			CheckNull(cep);
			if (!ban_target_strtoval(cep->value))
			{
				config_error("%s:%i: set::manual-ban-target: value '%s' is not recognized. "
				             "See https://www.unrealircd.org/docs/Set_block#set::manual-ban-target",
				             cep->file->filename, cep->line_number, cep->value);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "reject-message"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				CheckNull(cepp);
				if (!strcmp(cepp->name, "password-mismatch"))
					;
				else if (!strcmp(cepp->name, "too-many-connections"))
					;
				else if (!strcmp(cepp->name, "too-many-connections-ipv6-range"))
					;
				else if (!strcmp(cepp->name, "too-many-new-connections-ipv6-range"))
					;
				else if (!strcmp(cepp->name, "server-full"))
					;
				else if (!strcmp(cepp->name, "unauthorized"))
					;
				else if (!strcmp(cepp->name, "kline"))
					;
				else if (!strcmp(cepp->name, "gline"))
					;
				else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::reject-message",
						cepp->name);
					errors++;
					continue;
				}
			}
		}
		else if (!strcmp(cep->name, "topic-setter"))
		{
			CheckNull(cep);
			if (strcmp(cep->value, "nick") && strcmp(cep->value, "nick-user-host"))
			{
				config_error("%s:%i: set::topic-setter: value should be 'nick' or 'nick-user-host'",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "ban-setter"))
		{
			CheckNull(cep);
			if (strcmp(cep->value, "nick") && strcmp(cep->value, "nick-user-host"))
			{
				config_error("%s:%i: set::ban-setter: value should be 'nick' or 'nick-user-host'",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "ban-setter-sync") || !strcmp(cep->name, "ban-setter-synch"))
		{
			CheckNull(cep);
		}
		else if (!strcmp(cep->name, "part-instead-of-quit-on-comment-change"))
		{
			CheckNull(cep);
		}
		else if (!strcmp(cep->name, "broadcast-channel-messages"))
		{
			CheckNull(cep);
			if (strcmp(cep->value, "auto") &&
			    strcmp(cep->value, "always") &&
			    strcmp(cep->value, "never"))
			{
				config_error("%s:%i: set::broadcast-channel-messages: value should be 'auto', 'always' or 'never'",
				             cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "allowed-channelchars"))
		{
			CheckNull(cep);
			if (!allowed_channelchars_strtoval(cep->value))
			{
				config_error("%s:%i: set::allowed-channelchars: value should be one of: 'ascii', 'utf8' or 'any'",
				             cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "hide-idle-time"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				CheckNull(cepp);
				if (!strcmp(cepp->name, "policy"))
				{
					if (!hideidletime_strtoval(cepp->value))
					{
						config_error("%s:%i: set::hide-idle-time::policy: value should be one of: 'never', 'always', 'usermode' or 'oper-usermode'",
							     cepp->file->filename, cepp->line_number);
						errors++;
					}
				}
				else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::hide-idle-time",
						cepp->name);
					errors++;
					continue;
				}
			}
		} else if (!strcmp(cep->name, "limit-svscmds"))
		{
			CheckNull(cep);
			if (strcmp(cep->value, "servers") && strcmp(cep->value, "ulines"))
			{
				config_error("%s:%i: set::limit-svscmds: value must be one of: 'servers' or 'ulines'",
				             cep->file->filename, cep->line_number);
				errors++;
			}
		} else if (!strcmp(cep->name, "high-connection-rate"))
		{
			CheckNull(cep);
		} else if (!strcmp(cep->name, "send-isupport-updates"))
		{
			CheckNull(cep);
		} else if (!strcmp(cep->name, "best-practices"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				CheckNull(cepp);
				if (!strcmp(cepp->name, "hashed-passwords"))
				{
					bestpractices.hashed_passwords = config_checkval(cepp->value, CFG_YESNO);
				} else
				if (!strcmp(cepp->name, "trusted-cert"))
				{
					bestpractices.trusted_cert = config_checkval(cepp->value, CFG_YESNO);
				} else
				if (!strcmp(cepp->name, "trusted-cert-valid-hostname"))
				{
					bestpractices.trusted_cert_valid_hostname = config_checkval(cepp->value, CFG_YESNO);
				} else
				if (!strcmp(cepp->name, "listen-nontls-port") || !strcmp(cepp->name, "listen-tls-only"))
				{
					bestpractices.listen_tls_only = config_checkval(cepp->value, CFG_YESNO);
				} else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::best-practices",
						cepp->name);
					errors++;
					continue;
				}
			}
		} else if (!strcmp(cep->name, "dns"))
		{
			for (cepp = cep->items; cepp; cepp = cepp->next)
			{
				if (!strcmp(cepp->name, "client") || !strcmp(cepp->name, "dnsbl"))
				{
					for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
					{
						CheckNull(ceppp);
						if (!strcmp(ceppp->name, "timeout"))
						{
							int v = atoi(ceppp->value);
							if ((v < 1000) || (v > 10000))
							{
								config_error("%s:%i: the timeout needs to be in milliseconds and in the range 1000-10000.",
								             ceppp->file->filename, ceppp->line_number);
								errors++;
							}
						} else
						if (!strcmp(ceppp->name, "retry"))
						{
							int v = atoi(ceppp->value);
							if ((v < 0) || (v > 5))
							{
								config_error("%s:%i: retry value needs to be in range 0-5.",
								             ceppp->file->filename, ceppp->line_number);
								errors++;
							}
						} else
						{
							config_error_unknown(ceppp->file->filename,
								ceppp->line_number, "set::dns::..::",
								ceppp->name);
							errors++;
							continue;
						}
					}
				} else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "set::dns",
						cepp->name);
					errors++;
					continue;
				}
			}
		} else if ((n = test_dynamic_set_block_item(conf, NULL, cep)) >= 0)
		{
			/* Handled by test_dynamic_set_block_item:
			 * return value  0: success (mean we do errors += 0)
			 * return value >0: some error
			 */
			errors += n;
		} else /* n<0 from above */
		{
			int used = 0;
			for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
			{
				int value, errs = 0;
				if (h->owner && !(h->owner->flags & MODFLAG_TESTING) &&
				                !(h->owner->options & MOD_OPT_PERM))
					continue;
				value = (*(h->func.intfunc))(conf,cep,CONFIG_SET, &errs);
				if (value == 2)
					used = 1;
				if (value == 1)
				{
					used = 1;
					break;
				}
				if (value == -1)
				{
					used = 1;
					errors += errs;
					break;
				}
				if (value == -2)
				{
					used = 1;
					errors += errs;
				}
			}
			if (!used) {
				config_error("%s:%i: unknown directive set::%s",
					cep->file->filename, cep->line_number,
					cep->name);
				errors++;
			}
		}
	}
	return errors;
}

int	_conf_loadmodule(ConfigFile *conf, ConfigEntry *ce)
{
	const char *ret;
	if (!ce->value)
	{
		config_status("%s:%i: loadmodule without filename",
			ce->file->filename, ce->line_number);
		return -1;
	}

	if (is_blacklisted_module(ce->value))
	{
		/* config_warn("%s:%i: Module '%s' is blacklisted, not loading",
			ce->file->filename, ce->line_number, ce->value); */
		return 1;
	}

	if ((ret = Module_Create(ce->value))) {
		config_error("%s:%i: loadmodule %s: failed to load: %s",
			ce->file->filename, ce->line_number,
			ce->value, ret);
		return -1;
	}
	return 1;
}

int	_test_loadmodule(ConfigFile *conf, ConfigEntry *ce)
{
	return 0;
}

int	_test_blacklist_module(ConfigFile *conf, ConfigEntry *ce)
{
	const char *path;
	ConfigItem_blacklist_module *m;

	if (!ce->value)
	{
		config_status("%s:%i: blacklist-module: no module name given to blacklist",
			ce->file->filename, ce->line_number);
		return -1;
	}

	path = Module_TransformPath(ce->value);

	m = safe_alloc(sizeof(ConfigItem_blacklist_module));
	safe_strdup(m->name, ce->value);
	AddListItem(m, conf_blacklist_module);

	return 0;
}

int is_blacklisted_module(const char *name)
{
	const char *path = Module_TransformPath(name);
	ConfigItem_blacklist_module *m;

	for (m = conf_blacklist_module; m; m = m->next)
		if (match_simple(m->name, name) || match_simple(m->name, path))
			return 1;

	return 0;
}

void start_listeners(void)
{
	ConfigItem_listen *listener;
	int failed = 0, ports_bound = 0;
	char boundmsg_ipv4[512];
	char boundmsg_ipv6[512];
	char boundmsg_unix[512];
	int last_errno = 0;

	*boundmsg_ipv4 = *boundmsg_ipv6 = *boundmsg_unix = '\0';

	for (listener = conf_listen; listener; listener = listener->next)
	{
		/* Try to bind to any ports that are not yet bound and not marked as temporary */
		if (!(listener->options & LISTENER_BOUND) && !listener->flag.temporary)
		{
			if (add_listener(listener) == -1)
			{
				/* Error already printed upstream */
				failed = 1;
				last_errno = ERRNO;
			} else {
				if (loop.booted)
				{
					if (listener->socket_type == SOCKET_TYPE_UNIX)
					{
						unreal_log(ULOG_INFO, "listen", "LISTEN_ADDED", NULL,
							   "UnrealIRCd is now also listening on $listen_file [$protocol]",
							   log_data_string("listen_file", listener->file),
							   log_data_string("protocol", socket_type_valtostr(listener->socket_type)));
					} else {
						unreal_log(ULOG_INFO, "listen", "LISTEN_ADDED", NULL,
							   "UnrealIRCd is now also listening on $listen_ip:$listen_port [$protocol]",
							   log_data_string("listen_ip", listener->ip),
							   log_data_integer("listen_port", listener->port),
							   log_data_string("protocol", socket_type_valtostr(listener->socket_type)));
					}
				} else {
					switch (listener->socket_type)
					{
						case SOCKET_TYPE_IPV4:
							snprintf(boundmsg_ipv4+strlen(boundmsg_ipv4), sizeof(boundmsg_ipv4)-strlen(boundmsg_ipv4),
								"%s:%d%s, ", listener->ip, listener->port,
								listener->options & LISTENER_TLS ? "(TLS)" : "");
							break;
						case SOCKET_TYPE_IPV6:
							snprintf(boundmsg_ipv6+strlen(boundmsg_ipv6), sizeof(boundmsg_ipv6)-strlen(boundmsg_ipv6),
								"%s:%d%s, ", listener->ip, listener->port,
								listener->options & LISTENER_TLS ? "(TLS)" : "");
							break;
						case SOCKET_TYPE_UNIX:
							snprintf(boundmsg_unix+strlen(boundmsg_unix), sizeof(boundmsg_unix)-strlen(boundmsg_unix),
								"%s%s, ", listener->file,
								listener->options & LISTENER_TLS ? "(TLS)" : "");
							break;
						default:
							break;
					}
				}
			}
		}

		/* NOTE: do not merge this with code above (nor in an else block),
		 * as add_listener() affects this flag.
		 */
		if (listener->options & LISTENER_BOUND)
			ports_bound++;
	}

	if (ports_bound == 0)
	{
#ifdef _WIN32
		if (last_errno == WSAEADDRINUSE)
#else
		if (last_errno == EADDRINUSE)
#endif
		{
			/* We can be specific */
			unreal_log(ULOG_FATAL, "listen", "ALL_LISTEN_PORTS_FAILED", NULL,
				   "Unable to listen on any ports. "
				   "Most likely UnrealIRCd is already running.");
		} else {
			unreal_log(ULOG_FATAL, "listen", "ALL_LISTEN_PORTS_FAILED", NULL,
				   "Unable to listen on any ports. "
				   "Please verify that no other process is using the ports. "
				   "Also, on some IRCd shells you may have to use listen::bind-ip "
				   "with a specific IP assigned to you (rather than \"*\").");
		}
		exit(-1);
	}

	if (failed && !loop.booted)
	{
		unreal_log(ULOG_FATAL, "listen", "SOME_LISTEN_PORTS_FAILED", NULL,
			   "Unable to listen on all ports (some of them succeeded, some of them failed). "
			   "Please verify that no other process is using the port(s). "
			   "Also, on some IRCd shells you may have to use listen::bind-ip "
			   "with a specific IP assigned to you (rather than \"*\").");
		exit(-1);
	}

	if (!loop.booted)
	{
		if (strlen(boundmsg_ipv4) > 2)
			boundmsg_ipv4[strlen(boundmsg_ipv4)-2] = '\0';
		if (strlen(boundmsg_ipv6) > 2)
			boundmsg_ipv6[strlen(boundmsg_ipv6)-2] = '\0';
		if (strlen(boundmsg_unix) > 2)
			boundmsg_unix[strlen(boundmsg_unix)-2] = '\0';

		if (!*boundmsg_ipv4)
			strlcpy(boundmsg_ipv4, "<none>", sizeof(boundmsg_ipv4));
		if (!*boundmsg_ipv6)
			strlcpy(boundmsg_ipv6, "<none>", sizeof(boundmsg_ipv6));
		if (!*boundmsg_unix)
			strlcpy(boundmsg_unix, "<none>", sizeof(boundmsg_unix));

		unreal_log(ULOG_INFO, "listen", "LISTENING", NULL,
		           "UnrealIRCd is now listening on the following addresses/ports:\n"
		           "IPv4: $ipv4_port_list\n"
		           "IPv6: $ipv6_port_list\n"
		           "Unix Sockets: $unix_socket_list\n",
		           log_data_string("ipv4_port_list", boundmsg_ipv4),
		           log_data_string("ipv6_port_list", boundmsg_ipv6),
		           log_data_string("unix_socket_list", boundmsg_unix));
	}
}

/* Actually use configuration */
void config_run(void)
{
	Module *mi;

	loop.config_status = CONFIG_STATUS_POSTLOAD;
	extcmodes_check_for_changes();
	start_listeners();
	if (!loop.booted)
		add_proc_io_server();
	free_all_config_resources();
	dns_check_for_changes();

	for (mi = Modules; mi; mi = mi->next)
		if (!(mi->options & MOD_OPT_OFFICIAL))
			tainted = 99;
}

int	_conf_offchans(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep, *cepp;

	for (cep = ce->items; cep; cep = cep->next)
	{
		ConfigItem_offchans *of = safe_alloc(sizeof(ConfigItem_offchans));
		strlcpy(of->name, cep->name, CHANNELLEN+1);
		for (cepp = cep->items; cepp; cepp = cepp->next)
		{
			if (!strcmp(cepp->name, "topic"))
				safe_strdup(of->topic, cepp->value);
		}
		AddListItem(of, conf_offchans);
	}
	return 0;
}

int	_test_offchans(ConfigFile *conf, ConfigEntry *ce)
{
	int errors = 0;
	ConfigEntry *cep, *cep2;

	if (!ce->items)
	{
		config_error("%s:%i: empty official-channels block",
			ce->file->filename, ce->line_number);
		return 1;
	}

	config_warn("set::official-channels is deprecated. It often does not do what you want. "
	            "You're better of creating a channel, setting all modes, topic, etc. to your liking "
	            "and then making the channel permanent (MODE #channel +P). "
	            "The channel will then be stored in a database to preserve it between restarts.");

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (strlen(cep->name) > CHANNELLEN)
		{
			config_error("%s:%i: official-channels: '%s' name too long (max %d characters).",
				cep->file->filename, cep->line_number, cep->name, CHANNELLEN);
			errors++;
			continue;
		}
		if (!valid_channelname(cep->name))
		{
			config_error("%s:%i: official-channels: '%s' is not a valid channel name.",
				cep->file->filename, cep->line_number, cep->name);
			errors++;
			continue;
		}
		for (cep2 = cep->items; cep2; cep2 = cep2->next)
		{
			if (!cep2->value)
			{
				config_error_empty(cep2->file->filename,
					cep2->line_number, "official-channels",
					cep2->name);
				errors++;
				continue;
			}
			if (!strcmp(cep2->name, "topic"))
			{
				if (strlen(cep2->value) > MAXTOPICLEN)
				{
					config_error("%s:%i: official-channels::%s: topic too long (max %d characters).",
						cep2->file->filename, cep2->line_number, cep->name, MAXTOPICLEN);
					errors++;
					continue;
				}
			} else {
				config_error_unknown(cep2->file->filename,
					cep2->line_number, "official-channels",
					cep2->name);
				errors++;
				continue;
			}
		}
	}
	return errors;
}

/** Convert alias type string to ALIAS_xxx constant.
 * @returns ALIAS_SERVICES, ALIAS_STATS, etc., or 0 for unknown.
 */
static int alias_type_strtoval(const char *s)
{
	if (!strcmp(s, "services"))
		return ALIAS_SERVICES;
	if (!strcmp(s, "stats"))
		return ALIAS_STATS;
	if (!strcmp(s, "normal"))
		return ALIAS_NORMAL;
	if (!strcmp(s, "command"))
		return ALIAS_COMMAND;
	if (!strcmp(s, "channel"))
		return ALIAS_CHANNEL;
	if (!strcmp(s, "real"))
		return ALIAS_REAL;
	return 0;
}

int	_conf_alias(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigItem_alias *alias = NULL;
	ConfigItem_alias_format *format;
	ConfigEntry 	    	*cep, *cepp;
	RealCommand *cmptr;

	if ((cmptr = find_command(ce->value, CMD_ALIAS)))
		CommandDelX(NULL, cmptr);
	if (find_command_simple(ce->value))
	{
		config_warn("%s:%i: Alias '%s' would conflict with command (or server token) '%s', alias not added.",
			ce->file->filename, ce->line_number,
			ce->value, ce->value);
		return 0;
	}
	if ((alias = find_alias(ce->value)))
		DelListItem(alias, conf_alias);
	alias = safe_alloc(sizeof(ConfigItem_alias));
	safe_strdup(alias->alias, ce->value);
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "format")) {
			format = safe_alloc(sizeof(ConfigItem_alias_format));
			safe_strdup(format->format, cep->value);
			format->expr = unreal_create_match(MATCH_PCRE_REGEX, cep->value, NULL);
			if (!format->expr)
				abort(); /* Impossible due to _test_alias earlier */
			for (cepp = cep->items; cepp; cepp = cepp->next) {
				if (!strcmp(cepp->name, "nick") ||
				    !strcmp(cepp->name, "target") ||
				    !strcmp(cepp->name, "command")) {
					safe_strdup(format->nick, cepp->value);
				}
				else if (!strcmp(cepp->name, "parameters")) {
					safe_strdup(format->parameters, cepp->value);
				}
				else if (!strcmp(cepp->name, "type")) {
					format->type = alias_type_strtoval(cepp->value);
				}
			}
			AddListItem(format, alias->format);
		}

		else if (!strcmp(cep->name, "nick") || !strcmp(cep->name, "target"))
		{
			safe_strdup(alias->nick, cep->value);
		}
		else if (!strcmp(cep->name, "type")) {
			alias->type = alias_type_strtoval(cep->value);
		}
		else if (!strcmp(cep->name, "spamfilter"))
			alias->spamfilter = config_checkval(cep->value, CFG_YESNO);
	}
	if (BadPtr(alias->nick) && alias->type != ALIAS_COMMAND) {
		safe_strdup(alias->nick, alias->alias);
	}
	AliasAdd(NULL, alias->alias, cmd_alias, 1, CMD_USER|CMD_ALIAS);

	AddListItem(alias, conf_alias);
	return 0;
}


int _test_alias(ConfigFile *conf, ConfigEntry *ce) {
	int errors = 0;
	ConfigEntry *cep, *cepp;
	char has_type = 0, has_target = 0, has_format = 0, has_spamfilter = 0;
	int type = 0;

	if (!ce->items)
	{
		config_error("%s:%i: empty alias block",
			ce->file->filename, ce->line_number);
		return 1;
	}
	if (!ce->value)
	{
		config_error("%s:%i: alias without name",
			ce->file->filename, ce->line_number);
		errors++;
	}
	else if (!find_command(ce->value, CMD_ALIAS) && find_command(ce->value, 0)) {
		config_status("%s:%i: %s is an existing command, can not add alias",
			ce->file->filename, ce->line_number, ce->value);
		errors++;
	}
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (config_is_blankorempty(cep, "alias"))
		{
			errors++;
			continue;
		}
		if (!strcmp(cep->name, "format")) {
			char *err = NULL;
			Match *expr;
			char has_type = 0, has_target = 0, has_parameters = 0;

			has_format = 1;
			expr = unreal_create_match(MATCH_PCRE_REGEX, cep->value, &err);
			if (!expr)
			{
				config_error("%s:%i: alias::format contains an invalid regex: %s",
					cep->file->filename, cep->line_number, err);
			} else {
				unreal_delete_match(expr);
			}

			for (cepp = cep->items; cepp; cepp = cepp->next) {
				if (config_is_blankorempty(cepp, "alias::format"))
				{
					errors++;
					continue;
				}
				if (!strcmp(cepp->name, "nick") ||
				    !strcmp(cepp->name, "command") ||
				    !strcmp(cepp->name, "target"))
				{
					if (has_target)
					{
						config_warn_duplicate(cepp->file->filename,
							cepp->line_number,
							"alias::format::target");
						continue;
					}
					has_target = 1;
				}
				else if (!strcmp(cepp->name, "type"))
				{
					if (has_type)
					{
						config_warn_duplicate(cepp->file->filename,
							cepp->line_number,
							"alias::format::type");
						continue;
					}
					has_type = 1;
					if (!alias_type_strtoval(cepp->value))
					{
						config_error("%s:%i: unknown alias type",
						cepp->file->filename, cepp->line_number);
						errors++;
					}
				}
				else if (!strcmp(cepp->name, "parameters"))
				{
					if (has_parameters)
					{
						config_warn_duplicate(cepp->file->filename,
							cepp->line_number,
							"alias::format::parameters");
						continue;
					}
					has_parameters = 1;
				}
				else
				{
					config_error_unknown(cepp->file->filename,
						cepp->line_number, "alias::format",
						cepp->name);
					errors++;
				}
			}
			if (!has_target)
			{
				config_error_missing(cep->file->filename,
					cep->line_number, "alias::format::target");
				errors++;
			}
			if (!has_type)
			{
				config_error_missing(cep->file->filename,
					cep->line_number, "alias::format::type");
				errors++;
			}
			if (!has_parameters)
			{
				config_error_missing(cep->file->filename,
					cep->line_number, "alias::format::parameters");
				errors++;
			}
		}
		else if (!strcmp(cep->name, "nick") || !strcmp(cep->name, "target"))
		{
			if (has_target)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "alias::target");
				continue;
			}
			has_target = 1;
		}
		else if (!strcmp(cep->name, "type")) {
			if (has_type)
			{
				config_warn_duplicate(cep->file->filename,
					cep->line_number, "alias::type");
				continue;
			}
			has_type = 1;
			type = alias_type_strtoval(cep->value);
			if (!type)
			{
				config_error("%s:%i: unknown alias type",
					cep->file->filename, cep->line_number);
				errors++;
			}
		}
		else if (!strcmp(cep->name, "spamfilter"))
			has_spamfilter = 1;
		else {
			config_error_unknown(cep->file->filename, cep->line_number,
				"alias", cep->name);
			errors++;
		}
	}
	if (!has_type)
	{
		config_error_missing(ce->file->filename, ce->line_number,
			"alias::type");
		errors++;
	}
	if (!has_format && (type == ALIAS_COMMAND))
	{
		config_error("%s:%d: alias::type is 'command' but no alias::format was specified",
			ce->file->filename, ce->line_number);
		errors++;
	}
	else if (has_format && (type != ALIAS_COMMAND))
	{
		config_error("%s:%d: alias::format specified when type is not 'command'",
			ce->file->filename, ce->line_number);
		errors++;
	}
	if (has_spamfilter && (type == ALIAS_CHANNEL))
	{
		config_warn("%s:%d: alias::spamfilter has no effect for channel aliases, "
			"spamfilter is always checked for channel messages",
			ce->file->filename, ce->line_number);
	}
	return errors;
}

int	_conf_deny(ConfigFile *conf, ConfigEntry *ce)
{
Hook *h;

	if (!strcmp(ce->value, "channel"))
		_conf_deny_channel(conf, ce);
	else if (!strcmp(ce->value, "version"))
		_conf_deny_version(conf, ce);
	else
	{
		int value;
		for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
		{
			value = (*(h->func.intfunc))(conf,ce,CONFIG_DENY);
			if (value == 1)
				break;
		}
		return 0;
	}
	return 0;
}

int	_conf_deny_channel(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigItem_deny_channel 	*deny = NULL;
	ConfigEntry 	    	*cep;

	deny = safe_alloc(sizeof(ConfigItem_deny_channel));
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "channel"))
		{
			safe_strdup(deny->channel, cep->value);
		}
		else if (!strcmp(cep->name, "redirect"))
		{
			safe_strdup(deny->redirect, cep->value);
		}
		else if (!strcmp(cep->name, "reason"))
		{
			safe_strdup(deny->reason, cep->value);
		}
		else if (!strcmp(cep->name, "warn"))
		{
			deny->warn = config_checkval(cep->value,CFG_YESNO);
		}
		else if (!strcmp(cep->name, "class"))
		{
			safe_strdup(deny->class, cep->value);
		}
		else if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
		{
			conf_match_block(conf, cep, &deny->match);
		}
	}
	AddListItem(deny, conf_deny_channel);
	return 0;
}
int	_conf_deny_version(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigItem_deny_version *deny = NULL;
	ConfigEntry 	    	*cep;

	deny = safe_alloc(sizeof(ConfigItem_deny_version));
	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "mask"))
		{
			safe_strdup(deny->mask, cep->value);
		}
		else if (!strcmp(cep->name, "version"))
		{
			safe_strdup(deny->version, cep->value);
		}
		else if (!strcmp(cep->name, "flags"))
		{
			safe_strdup(deny->flags, cep->value);
		}
	}
	AddListItem(deny, conf_deny_version);
	return 0;
}

int     _test_deny(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int	    errors = 0;
	Hook	*h;

	if (!ce->value)
	{
		config_error("%s:%i: deny without type",
			ce->file->filename, ce->line_number);
		return 1;
	}
	if (!strcmp(ce->value, "channel"))
	{
		char has_channel = 0, has_warn = 0, has_reason = 0, has_redirect = 0, has_class = 0;
		char has_mask = 0, has_match = 0;
		for (cep = ce->items; cep; cep = cep->next)
		{
			if (config_is_blankorempty(cep, "deny channel"))
			{
				errors++;
				continue;
			}
			if (!strcmp(cep->name, "channel"))
			{
				if (has_channel)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny channel::channel");
					continue;
				}
				has_channel = 1;
			}
			else if (!strcmp(cep->name, "redirect"))
			{
				if (has_redirect)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny channel::redirect");
					continue;
				}
				has_redirect = 1;
			}
			else if (!strcmp(cep->name, "reason"))
			{
				if (has_reason)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny channel::reason");
					continue;
				}
				has_reason = 1;
			}
			else if (!strcmp(cep->name, "warn"))
			{
				if (has_warn)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny channel::warn");
					continue;
				}
				has_warn = 1;
			}
			else if (!strcmp(cep->name, "class"))
			{
				if (has_class)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny channel::class");
					continue;
				}
				has_class = 1;
			}
			else if (!strcmp(cep->name, "match"))
			{
				has_match = 1;
				test_match_block(conf, cep, &errors);
			}
			else if (!strcmp(cep->name, "mask"))
			{
				has_mask = 1;
				test_match_block(conf, cep, &errors);
			}
			else
			{
				config_error_unknown(cep->file->filename,
					cep->line_number, "deny channel", cep->name);
				errors++;
			}
		}
		if (!has_channel)
		{
			config_error_missing(ce->file->filename, ce->line_number,
				"deny channel::channel");
			errors++;
		}
		if (!has_reason)
		{
			config_error_missing(ce->file->filename, ce->line_number,
				"deny channel::reason");
			errors++;
		}
		if (has_mask && has_match)
		{
			config_error("%s:%d: You cannot have both ::mask and ::match. "
				     "You should only use %s %s::match.",
				     ce->file->filename, ce->line_number, ce->name, ce->value);
			errors++;
		}
	}
	else if (!strcmp(ce->value, "version"))
	{
		char has_mask = 0, has_version = 0, has_flags = 0;
		for (cep = ce->items; cep; cep = cep->next)
		{
			if (config_is_blankorempty(cep, "deny version"))
			{
				errors++;
				continue;
			}
			if (!strcmp(cep->name, "mask"))
			{
				if (has_mask)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny version::mask");
					continue;
				}
				has_mask = 1;
			}
			else if (!strcmp(cep->name, "version"))
			{
				if (has_version)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny version::version");
					continue;
				}
				has_version = 1;
			}
			else if (!strcmp(cep->name, "flags"))
			{
				if (has_flags)
				{
					config_warn_duplicate(cep->file->filename,
						cep->line_number, "deny version::flags");
					continue;
				}
				has_flags = 1;
			}
			else
			{
				config_error_unknown(cep->file->filename,
					cep->line_number, "deny version", cep->name);
				errors++;
			}
		}
		if (!has_mask)
		{
			config_error_missing(ce->file->filename, ce->line_number,
				"deny version::mask");
			errors++;
		}
		if (!has_version)
		{
			config_error_missing(ce->file->filename, ce->line_number,
				"deny version::version");
			errors++;
		}
		if (!has_flags)
		{
			config_error_missing(ce->file->filename, ce->line_number,
				"deny version::flags");
			errors++;
		}
	}
	else
	{
		int used = 0;
		for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
		{
			int value, errs = 0;
			if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
			    && !(h->owner->options & MOD_OPT_PERM))
				continue;
			value = (*(h->func.intfunc))(conf,ce,CONFIG_DENY, &errs);
			if (value == 2)
				used = 1;
			if (value == 1)
			{
				used = 1;
				break;
			}
			if (value == -1)
			{
				used = 1;
				errors += errs;
				break;
			}
			if (value == -2)
			{
				used = 1;
				errors += errs;
			}
		}
		if (!used) {
			config_error("%s:%i: unknown deny type %s",
				ce->file->filename, ce->line_number,
				ce->value);
			return 1;
		}
		return errors;
	}

	return errors;
}

Secret *find_secret(const char *secret_name)
{
	Secret *s;
	for (s = secrets; s; s = s->next)
	{
		if (!strcasecmp(s->name, secret_name))
			return s;
	}
	return NULL;
}

void free_secret_cache(SecretCache *c)
{
	unrealdb_free_config(c->config);
	safe_free(c);
}

void free_secret(Secret *s)
{
	SecretCache *c, *c_next;
	for (c = s->cache; c; c = c_next)
	{
		c_next = c->next;
		DelListItem(c, s->cache);
		free_secret_cache(c);
	}
	safe_free(s->name);
	safe_free_sensitive(s->password);
	safe_free(s);
}

char *_conf_secret_read_password_file(const char *fname)
{
	char *pwd, *err;
	int fd, n;

#ifndef _WIN32
	fd = open(fname, O_RDONLY);
#else
	fd = open(fname, _O_RDONLY|_O_BINARY);
#endif
	if (fd < 0)
	{
		/* This should not happen, as we tested for file exists earlier.. */
		config_error("Could not open file '%s': %s", fname, strerror(errno));
		return NULL;
	}

	pwd = safe_alloc_sensitive(512);
	n = read(fd, pwd, 511);
	if (n <= 0)
	{
		close(fd);
		config_error("Could not read from file '%s': %s", fname, strerror(errno));
		safe_free_sensitive(pwd);
		return NULL;
	}
	close(fd);
	stripcrlf(pwd);
	sodium_stackzero(1024);
	if (!valid_secret_password(pwd, &err))
	{
		config_error("Key from file '%s' does not meet password complexity requirements: %s", fname, err);
		safe_free_sensitive(pwd);
		return NULL;
	}
	return pwd;
}

char *_conf_secret_read_prompt(const char *blockname)
{
	char *pwd, *pwd_prompt;
	char buf[256];

#ifdef _WIN32
	/* FIXME: add windows support? should be possible in GUI no? */
	return NULL;
#else
	snprintf(buf, sizeof(buf), "Enter password for secret '%s': ", blockname);
	pwd_prompt = getpass(buf);
	if (pwd_prompt)
	{
		pwd = safe_alloc_sensitive(512);
		strlcpy(pwd, pwd_prompt, 512);
		memset(pwd_prompt, 0, strlen(pwd_prompt)); // zero password out
		sodium_stackzero(1024);
		return pwd;
	}
	return NULL;
#endif
}

int _test_secret(ConfigFile *conf, ConfigEntry *ce)
{
	int errors = 0;
	int has_password = 0, has_password_file = 0, has_password_prompt = 0;
	ConfigEntry *cep;
	char *err;
	Secret *existing;

	if (!ce->value)
	{
		config_error("%s:%i: secret block needs a name, eg: secret xyz {",
			ce->file->filename, ce->line_number);
		errors++;
		return errors; /* need to return here since we dereference ce->value later.. */
	} else {
		if (!security_group_valid_name(ce->value))
		{
			config_error("%s:%i: secret block name '%s' contains invalid characters or is too long. "
			             "Only letters, numbers, underscore and hyphen are allowed.",
			             ce->file->filename, ce->line_number, ce->value);
			errors++;
		}
	}

	existing = find_secret(ce->value);

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "password"))
		{
			int n;
			if (has_password)
			{
				config_error("%s:%d: you can only have one password here",
				             cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
			has_password = 1;
			CheckNull(cep);
			if (cep->items ||
			    (((n = Auth_AutoDetectHashType(cep->value))) && ((n == AUTHTYPE_BCRYPT) || (n == AUTHTYPE_ARGON2))))
			{
				config_error("%s:%d: you cannot use hashed passwords here, see "
				             "https://www.unrealircd.org/docs/Secret_block#secret-plaintext",
				             cep->file->filename, cep->line_number);
				errors++;
				continue;
			}
			if (!valid_secret_password(cep->value, &err))
			{
				config_error("%s:%d: secret::password does not meet password complexity requirements: %s",
				             cep->file->filename, cep->line_number, err);
				errors++;
			}
		} else
		if (!strcmp(cep->name, "password-file"))
		{
			char *str;
			has_password_file = 1;
			CheckNull(cep);
			convert_to_absolute_path(&cep->value, CONFDIR);
			if (!file_exists(cep->value) && existing && existing->password)
			{
				/* Silently ignore the case where a secret block already
				 * has the password read and now the file is no longer available.
				 * This so secret::password-file can be used only to boot
				 * and then the media (eg: USB stick) can be pulled.
				 */
			} else
			{
				str = _conf_secret_read_password_file(cep->value);
				if (!str)
				{
					config_error("%s:%d: secret::password-file: error reading password from file, see error from above.",
						cep->file->filename, cep->line_number);
					errors++;
				}
				safe_free_sensitive(str);
			}
		} else
		if (!strcmp(cep->name, "password-prompt"))
		{
#ifdef _WIN32
			config_error("%s:%d: secret::password-prompt is not implemented in Windows at the moment, sorry!",
				cep->file->filename, cep->line_number);
			config_error("Choose a different method to enter passwords or use *NIX");
			errors++;
			return errors;
#endif
			has_password_prompt = 1;
			if (loop.booted && !find_secret(ce->value))
			{
				config_error("%s:%d: you cannot add a new secret { } block that uses password-prompt and then /REHASH. "
				             "With 'password-prompt' you can only add such a password on boot.",
				             cep->file->filename, cep->line_number);
				config_error("Either use a different method to enter passwords or restart the IRCd on the console.");
				errors++;
			}
			if (!loop.booted && !running_interactively())
			{
				config_error("ERROR: IRCd is not running interactively, but via a cron job or something similar.");
				config_error("%s:%d: unable to prompt for password since IRCd is not started in a terminal",
					cep->file->filename, cep->line_number);
				config_error("Either use a different method to enter passwords or start the IRCd in a terminal/SSH/..");
			}
		} else
		if (!strcmp(cep->name, "password-url"))
		{
			config_error("%s:%d: secret::password-url is not supported yet in this UnrealIRCd version.",
				cep->file->filename, cep->line_number);
			errors++;
		} else
		{
			config_error_unknown(cep->file->filename, cep->line_number,
				"secret", cep->name);
			errors++;
			continue;
		}
		if (cep->items)
		{
			config_error("%s:%d: secret::%s does not support sub-options (%s)",
				cep->file->filename, cep->line_number,
				cep->name, cep->items->name);
			errors++;
		}
	}

	if (!has_password && !has_password_file && !has_password_prompt)
	{
		config_error("%s:%d: secret { } block must contain 1 of: password OR password-file OR password-prompt",
			ce->file->filename, ce->line_number);
		errors++;
	}

	return errors;
}

/* NOTE: contrary to all other _conf* stuff, this one actually runs during config_test,
 * so during the early CONFIG TEST stage rather than CONFIG RUN.
 * This so all secret { } block configuration is available already during TEST/POSTTEST
 * stage for modules, so they can check if the password is correct or not.
 */
int _conf_secret(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	Secret *s;
	Secret *existing = find_secret(ce->value);

	s = safe_alloc(sizeof(Secret));
	safe_strdup(s->name, ce->value);

	for (cep = ce->items; cep; cep = cep->next)
	{
		if (!strcmp(cep->name, "password"))
		{
			safe_strdup_sensitive(s->password, cep->value);
			destroy_string(cep->value); /* destroy the original */
		} else
		if (!strcmp(cep->name, "password-file"))
		{
			if (!file_exists(cep->value) && existing && existing->password)
			{
				/* Silently ignore the case where a secret block already
				 * has the password read and now the file is no longer available.
				 * This so secret::password-file can be used only to boot
				 * and then the media (eg: USB stick) can be pulled.
				 */
			} else
			{
				s->password = _conf_secret_read_password_file(cep->value);
			}
		} else
		if (!strcmp(cep->name, "password-prompt"))
		{
			if (!loop.booted && running_interactively())
			{
				s->password = _conf_secret_read_prompt(ce->value);
				if (!s->password || !valid_secret_password(s->password, NULL))
				{
					config_error("Invalid password entered on console (does not meet complexity requirements)");
					/* This cannot be the correct password, so exit */
					exit(-1);
				}
			}
		}
	}

	/* This may happen if we run twice, due to destroy_string() earlier: */
	if (BadPtr(s->password))
	{
		free_secret(s);
		return 1;
	}

	/* If there is an existing secret { } block with this name in memory
	 * and it has a different password, then free that secret block
	 */
	if (existing)
	{
		if (!strcmp(s->password, existing->password))
		{
			free_secret(s);
			return 1;
		}
		/* passwords differ, so free the old existing one,
		 * including purging the cache for it.
		 */
		DelListItem(existing, secrets);
		free_secret(existing);
	}
	AddListItem(s, secrets);
	return 1;
}

void resource_download_complete(OutgoingWebRequest *request, OutgoingWebResponse *response)
{
	ConfigResource *rs = (ConfigResource *)request->callback_data;

	rs->type &= ~RESOURCE_DLQUEUED;

	if (config_verbose)
		config_status("resource_download_complete() for %s [%s]", request->url, response->errorbuf?response->errorbuf:"success");

	if (!response->file && !response->cached)
	{
		/* DOWNLOAD FAILED */
		if (rs->cache_file)
		{
			unreal_log(ULOG_ERROR, "config", "DOWNLOAD_FAILED_SOFT", NULL,
				   "$file:$line_number: Failed to download '$url': $error_message\n"
				   "Using a cached copy instead.",
				   log_data_string("file", rs->wce->ce->file->filename),
				   log_data_integer("line_number", rs->wce->ce->line_number),
				   log_data_string("url", displayurl(request->url)),
				   log_data_string("error_message", response->errorbuf));
			safe_strdup(rs->file, rs->cache_file);
		} else
		if (rs->warn_only_on_fail)
		{
			const char *cache_file;
			unreal_log(ULOG_WARNING, "config", "DOWNLOAD_FAILED_WARN", NULL,
				   "$file:$line_number: Failed to download '$url': $error_message\n"
				   "Continuing anyway...",
				   log_data_string("file", rs->wce->ce->file->filename),
				   log_data_integer("line_number", rs->wce->ce->line_number),
				   log_data_string("url", displayurl(request->url)),
				   log_data_string("error_message", response->errorbuf));
			cache_file = unreal_mkcache(request->url);
			unreal_touch(cache_file, 1577880000); /* 2020-01-01 12:00 GMT */
			safe_strdup(rs->file, cache_file);
		} else
		{
			unreal_log(ULOG_ERROR, "config", "DOWNLOAD_FAILED_HARD", NULL,
				   "$file:$line_number: Failed to download '$url': $error_message",
				   log_data_string("file", rs->wce->ce->file->filename),
				   log_data_integer("line_number", rs->wce->ce->line_number),
				   log_data_string("url", displayurl(request->url)),
				   log_data_string("error_message", response->errorbuf));
			/* Set error condition, this so config_read_file() later will stop. */
			loop.config_load_failed = 1;
			/* We keep the other transfers running since they may raise (more) errors.
			 * Which can be helpful so you can differentiate between an error of an
			 * include on one server, or complete lack of internet connectvitity.
			 */
		}
	}
	else
	{
		if (response->cached)
		{
			/* Copy from cache */
			safe_strdup(rs->file, rs->cache_file);
		} else {
			/* Copy to cache */
			const char *cache_file = unreal_mkcache(request->url);
			unreal_copyfileex(response->file, cache_file, 1);
			safe_strdup(rs->file, cache_file);
		}
	}
	resource_download_complete_parse(rs);
}

void resource_download_complete_parse(ConfigResource *rs)
{
	if (rs->file)
	{
		if (rs->type & RESOURCE_INCLUDE)
		{
			if (config_read_file(rs->file, (char *)displayurl(rs->url)) < 0)
				loop.config_load_failed = 1;
		} else {
			ConfigEntryWrapper *wce;
			for (wce = rs->wce; wce; wce = wce->next)
				safe_strdup(wce->ce->value, rs->file); // now information of url is lost, hm!!
		}
	}
}

/** Request to REHASH the configuration file.
 * The rehash will not be done immediately, just scheduled.
 * This means this function can safely be called from modules or
 * other areas.
 * @param client	The client requesting the /REHASH.
 *                      If this is NULL then the rehash was requested
 *                      via a signal to the process or GUI.
 */
void request_rehash(Client *client)
{
	json_t *j;

	if (loop.rehashing)
	{
		if (client)
			sendnotice(client, "A rehash is already in progress");
		return;
	}

	/* Free any old json_rehash_log */
	if (json_rehash_log)
	{
		json_decref(json_rehash_log);
		json_rehash_log = NULL;
	}

	/* Start a new json_rehash_log */
	json_rehash_log = json_object();
	if (client)
		json_expand_client(json_rehash_log, "rehash_client", client, 99);
	j = json_array();
	json_object_set_new(json_rehash_log, "log", j);

	/* Now actually process the rehash request... */
	loop.rehashing = 1;
	loop.rehash_save_client = client;
	config_read_start();
	/* More config reading (or network I/O), and the actual rehash will
	 * happen in "the main loop". See end of SocketLoop() in src/ircd.c.
	 */
}

int rehash_internal(Client *client)
{
	int failure;

	/* Log it here if it is by a signal */
	if (client == NULL)
		unreal_log(ULOG_INFO, "config", "CONFIG_RELOAD", NULL, "Rehashing server configuration file [./unrealircd rehash]");

	loop.rehashing = 2; /* now doing the actual rehash */

	isupport_snapshot();
	failure = config_test();
	if (failure == 0)
		config_run();
	/* TODO: uh.. are we supposed to do all this for a failed rehash too? maybe some but not all? */
	reread_motdsandrules();
	unload_all_unused_umodes();
	unload_all_unused_extcmodes();
	unload_all_unused_extbans();
	unload_all_unused_caps();
	unload_all_unused_history_backends();
	unload_all_unused_rpc_handlers();
	unload_all_unused_apicallbacks();
	unload_all_unused_moddata();
	clicap_check_for_changes();
	umodes_check_for_changes();
	charsys_check_for_changes();
	isupport_check_for_changes();

	/* Remove central spamfilter rules upon set::central-spamfilter::enabled no; */
	if (iConf.central_spamfilter_enabled == 0)
		remove_config_tkls(TKL_FLAG_CENTRAL_SPAMFILTER);

	/* Clear everything now that we are done */
	loop.rehashing = 0;
	remote_rehash_client = NULL;
	procio_post_rehash(failure);
	json_object_set_new(json_rehash_log, "success", json_boolean(failure ? 0 : 1));
	RunHook(HOOKTYPE_REHASH_LOG, failure, json_rehash_log);

	loop.config_status = CONFIG_STATUS_COMPLETE;
	return 1;
}

void link_cleanup(ConfigItem_link *link_ptr)
{
	safe_free(link_ptr->servername);
	free_security_group(link_ptr->incoming.match);
	Auth_FreeAuthConfig(link_ptr->auth);
	safe_free(link_ptr->outgoing.file);
	safe_free(link_ptr->outgoing.bind_ip);
	safe_free(link_ptr->outgoing.hostname);
	safe_free(link_ptr->hub);
	safe_free(link_ptr->leaf);
	safe_free(link_ptr->connect_ip);
	if (link_ptr->ssl_ctx)
	{
		SSL_CTX_free(link_ptr->ssl_ctx);
		link_ptr->ssl_ctx = NULL;
	}
	if (link_ptr->tls_options)
	{
		free_tls_options(link_ptr->tls_options);
		link_ptr->tls_options = NULL;
    }
}

void delete_linkblock(ConfigItem_link *link_ptr)
{
	if (link_ptr->class)
	{
		link_ptr->class->xrefcount--;
		/* Perhaps the class is temporary too and we need to free it... */
		if (link_ptr->class->flag.temporary &&
		    !link_ptr->class->clients && !link_ptr->class->xrefcount)
		{
			delete_classblock(link_ptr->class);
			link_ptr->class = NULL;
		}
	}
	link_cleanup(link_ptr);
	DelListItem(link_ptr, conf_link);
	safe_free(link_ptr);
}

void delete_classblock(ConfigItem_class *class_ptr)
{
	safe_free(class_ptr->name);
	DelListItem(class_ptr, conf_class);
	safe_free(class_ptr);
}

void free_webserver(WebServer *webserver)
{
	safe_free(webserver);
}

void listen_cleanup()
{
	ConfigItem_listen *listener, *listener_next;

	for (listener = conf_listen; listener; listener = listener_next)
	{
		listener_next = listener->next;
		if (listener->flag.temporary)
		{
			/* First close the listener FD so no new clients can connect */
			if (listener->fd >= 0)
				close_listener(listener);
			if (listener->clients == 0)
			{
				/* Zero clients, so can be completely freed */
				DelListItem(listener, conf_listen);
				safe_free(listener->ip);
				safe_free(listener->file);
				safe_free(listener->spoof_ip);
				free_tls_options(listener->tls_options);
				/* listener->ssl_ctx is already freed by close_listener() */
				safe_free_webserver(listener->webserver);
				free_entire_name_list(listener->websocket_origin);
				// NOTE: duplicate code overlap with conf_listen_configure() - but not 100% identical
				safe_free(listener);
			} else {
				/* Still has clients */
				if (listener->webserver)
				{
					/* These now point to old module addresses */
					listener->webserver->handle_request = NULL;
					listener->webserver->handle_body = NULL;
					/* Ask modules if they can figure out the handler */
					RunHook(HOOKTYPE_CONFIG_LISTENER, listener);
					/* If not... free the webserver listener.
					 * TODO: it would be even better if we kill all clients,
					 * it would be more explicit and clear.
					 */
					if ((listener->webserver->handle_request == NULL) ||
					    (listener->webserver->handle_body == NULL))
					{
						safe_free_webserver(listener->webserver);
					}
				}
			}
		}
	}
}

ConfigResource *find_config_resource(const char *resource)
{
	ConfigResource *rs;

	for (rs = config_resources; rs; rs = rs->next)
	{
#ifdef _WIN32
		if (rs->file && !strcasecmp(resource, rs->file))
			return rs;
#else
		if (rs->file && !strcmp(resource, rs->file))
			return rs;
#endif
		if (rs->url && !strcasecmp(resource, rs->url))
			return rs;
	}
	return NULL;
}

/* Add configuration resource to list.
 * For files this doesn't do terribly much, except that you can use
 * the return value to judge on whether you should call config_read_file() or not.
 * For urls this adds the resource to the list of links to be downloaded.
 * @param resource	File or URL of the resource
 * @param type		A RESOURCE_ type such as RESOURCE_INCLUDE
 * @param ce		The ConfigEntry where the add_config_resource() happened
 *			for, such as the include block, etc.
 * @returns 0 if the file is already on our list (so no need to load it!)
 */
int add_config_resource(const char *resource, int type, ConfigEntry *ce)
{
	ConfigResource *rs;
	ConfigEntryWrapper *wce;
	ConfigEntry *cep;

	if (config_verbose)
		config_status("add_config_resource() for '%s", resource);

	wce = safe_alloc(sizeof(ConfigEntryWrapper));
	wce->ce = ce;

	rs = find_config_resource(resource);
	if (rs)
	{
		/* Existing entry, add us to the list of
		 * items who are interested in this resource ;)
		 */
		AddListItem(wce, rs->wce);
		return 0;
	}

	/* New entry */
	rs = safe_alloc(sizeof(ConfigResource));
	rs->wce = wce;
	AddListItem(rs, config_resources);

	/* restrict-config handling */
	if (ce)
	{
		for (cep = ce->items; cep; cep = cep->next)
		{
			if (!strcmp(cep->name, "restrict-config"))
			{
				ConfigEntry *cepp;
				for (cepp = cep->items; cepp; cepp = cepp->next)
				{
					add_name_list(rs->restrict_config, cepp->name);
				}
			}
		}
	}

	if (!url_is_valid(resource))
	{
		safe_strdup(rs->file, resource);
	} else {
		const char *cache_file;
		time_t modtime;

		safe_strdup(rs->url, resource);
		rs->type = type|RESOURCE_REMOTE|RESOURCE_DLQUEUED;

		cache_file = unreal_mkcache(rs->url);
		modtime = unreal_getfilemodtime(cache_file);

		if (modtime > 0)
		{
			/* CACHED COPY IS AVAILABLE */
			ConfigEntry *cep, *prev;
			safe_strdup(rs->cache_file, cache_file); /* Cached copy is available */

			/* Check if there is an "url-refresh" argument */
			prev = NULL;
			for (cep = ce->items; cep; cep = cep->next)
			{
				if (!strcmp(cep->name, "url-refresh"))
				{
					/* First find out the time value of url-refresh... (eg '7d' -> 86400*7) */
					long refresh_time = 0;
					if (cep->value)
						refresh_time = config_checkval(cep->value, CFG_TIME);
					/* Then remove the config item so it is not seen by the rest of unrealircd.
					 * Can't use DelListItem() here as ConfigEntry has no ->prev, only ->next.
					 */
					if (prev)
						prev->next = cep->next; /* (skip over us) */
					else
						ce->items = cep->next; /* (new head) */
					/* ..and free it */
					config_entry_free(cep);
					/* And now check if the current cached copy is recent enough */
					if (TStime() - modtime < refresh_time)
					{
						/* Don't download, use cached copy */
						safe_strdup(rs->file, rs->cache_file);
						rs->type &= ~RESOURCE_DLQUEUED;
						resource_download_complete_parse(rs);
						return 1;
					} else {
						//config_status("DEBUG: requires download attempt, out of date url-refresh %ld < %ld", refresh_time, TStime() - modtime);
					}
					break; // MUST break now as we touched the linked list.
				}
				prev = cep;
			}
		} else {
			/* CACHED COPY IS NOT AVAILABLE */
			ConfigEntry *cep, *prev;
			/* Check if there is an "url-fail" argument */
			prev = NULL;
			for (cep = ce->items; cep; cep = cep->next)
			{
				if (!strcmp(cep->name, "url-fail"))
				{
					if (cep->value)
					{
						if (!strcmp(cep->value, "warn"))
							rs->warn_only_on_fail = 1;
					}

					/* Then remove the config item so it is not seen by the rest of unrealircd.
					 * Can't use DelListItem() here as ConfigEntry has no ->prev, only ->next.
					 */
					if (prev)
						prev->next = cep->next; /* (skip over us) */
					else
						ce->items = cep->next; /* (new head) */
					/* ..and free it */
					config_entry_free(cep);
					break; // MUST break now as we touched the linked list.
				}
				prev = cep;
			}
		}
		download_file_async(rs->url, modtime, resource_download_complete, (void *)rs, DOWNLOAD_MAX_REDIRECTS);
	}
	return 1;
}

void free_all_config_resources(void)
{
	ConfigResource *rs, *next;
	ConfigEntryWrapper *wce, *wce_next;

	for (rs = config_resources; rs; rs = next)
	{
		next = rs->next;
		for (wce = rs->wce; wce; wce = wce_next)
		{
			wce_next = wce->next;
			safe_free(wce);
		}
		rs->wce = NULL;
		if (rs->type & RESOURCE_REMOTE)
		{
			url_cancel_handle_by_callback_data(rs);
			/* Delete the file, but only if it's not a cached version */
			if (rs->file && strncmp(rs->file, CACHEDIR, strlen(CACHEDIR)))
			{
				remove(rs->file);
			}
			safe_free(rs->url);
		}
		safe_free(rs->file);
		safe_free(rs->cache_file);
		free_entire_name_list(rs->restrict_config);
		DelListItem(rs, config_resources);
		safe_free(rs);
	}
}

int tls_tests(void)
{
	if (have_tls_listeners == 0)
	{
		config_error("Your server is not listening on any TLS ports.");
		config_status("Add this to your unrealircd.conf: listen { ip %s; port 6697; options { tls; }; };",
		            port_6667_ip ? port_6667_ip : "*");
		config_status("See https://www.unrealircd.org/docs/FAQ#no-tls-ports");
		return 0;
	}

	return 1;
}

/** Check if the user attempts to unload (eg: by commenting out) a module
 * that is currently loaded and is tagged as MOD_OPT_PERM_RELOADABLE
 * (in other words: a module that allows re-loading but not un-loading)
 */
int reloadable_perm_module_unloaded(void)
{
    Module *m, *m2;
    extern Module *Modules;
    int ret = 0;

	for (m = Modules; m; m = m->next)
	{
		if ((m->options & MOD_OPT_PERM_RELOADABLE) && (m->flags & MODFLAG_LOADED))
		{
			/* For each module w/MOD_OPT_PERM_RELOADABLE that is currently fully loaded... */
			int found = 0;
			for (m2 = Modules; m2; m2 = m2->next)
			{
				if ((m != m2) && !strcmp(m->header->name, m2->header->name))
					found = 1;
			}
			if (!found)
			{
				config_error("Attempt to unload module '%s' is not permitted. Module is permanent and reloadable only.", m->header->name);
				ret = 1;
				/* we don't return straight away so the user gets to see all errors and not just one */
			}
		}
	}

	return ret;
}

const char *link_generator_spkifp(TLSOptions *tlsoptions)
{
	SSL_CTX *ctx;
	SSL *ssl;
	X509 *cert;

	ctx = init_ctx(tlsoptions, 1);
	if (!ctx)
		exit(1);
	ssl = SSL_new(ctx);
	if (!ssl)
		exit(1);
	cert = SSL_get_certificate(ssl);
	return spki_fingerprint_ex(cert);
}

void link_generator(void)
{
	ConfigItem_listen *lstn;
	TLSOptions *tlsopt = iConf.tls_options; /* never null */
	int port = 0;
	char *ip = NULL;
	const char *spkifp;

	for (lstn = conf_listen; lstn; lstn = lstn->next)
	{
		if ((lstn->options & LISTENER_SERVERSONLY) &&
		    (lstn->options & LISTENER_TLS))
		{
			if (lstn->tls_options)
				tlsopt = lstn->tls_options;
			port = lstn->port;
			if (strcmp(lstn->ip, "*") && strcmp(lstn->ip, "127.0.0.1") && strcmp(lstn->ip, "::1"))
				ip = lstn->ip;
			/* else NULL */
			break;
		}
	}

	if (!port)
	{
		printf("You don't have any listen { } blocks that are serversonly (and have tls enabled).\n");
		printf("It is recommended to have at least one. Add this to your configuration file:\n");
		printf("listen { ip *; port 6900; options { tls; serversonly; }; };\n");
		exit(1);
	}

	spkifp = link_generator_spkifp(tlsopt);
	if (!spkifp)
	{
		printf("Could not calculate spkifp. Maybe you have uncommon TLS options set? Odd...\n");
		exit(1);
	}

	printf("\n");
	printf("Add the following link block to the unrealircd.conf on the OTHER side of the link\n");
	printf("(so NOT in the unrealircd.conf on THIS machine). Here it is, just copy-paste:\n");
	printf("################################################################################\n");
	printf("link %s {\n"
	       "    incoming {\n"
	       "        mask *;\n"
	       "    }\n"
	       "    outgoing {\n"
	       "        hostname %s;\n"
	       "        port %d;\n"
	       "        options { tls; autoconnect; }\n"
	       "    }\n"
	       "    password \"%s\" { spkifp; }\n"
	       "    class servers;\n"
	       "}\n",
	       conf_me->name,
	       ip ? ip : conf_me->name,
	       port,
	       spkifp);
	printf("################################################################################\n");
	exit(0);
}

/** Checks if the user modified 'modules.default.conf'
 * despite the BIG comment "DO NOT EDIT THIS FILE!"
 * and the explanation. Sigh.........
 */
int modules_default_conf_modified(const char *filebuf)
{
	char buf[512];
	char *p;
	char *conf_version = buf + 4;

	/* Put the first line in 'buf' */
	strlcpy(buf, filebuf, sizeof(buf));
	stripcrlf(buf);

	// The line should start with '/* [VERSION]'.
	// If it doesn't then it is from pre-6.1.1, so reject.
	if (!str_starts_with_case_sensitive(buf, "/* ["))
	{
		conf_version = "before 6.1.1";
	} else {
		p = strchr(conf_version, ']');
		if (p)
			*p = '\0';
		if (!strcmp(VERSIONONLY, conf_version))
			return 0; /* exact match, done! */
	}

	unreal_log(ULOG_ERROR, "config", "CONFIG_BAD_MODULES_DEFAULT_CONF", NULL,
	           "Your 'modules.default.conf' is from $your_conf_version and "
	           "not the one shipped with UnrealIRCd $version.\n"
	           "* Maybe you accidentally copied an old modules.default.conf from a previous installation?\n"
	           "* Or you deliberately modified modules.default.conf? That is not allowed,"
	           " see the BIG warning at the beginning of that file.\n"
	           "Solution: re-run 'make install' and then try to REHASH or boot again.\n",
	           log_data_string("your_conf_version", conf_version),
	           log_data_string("version", VERSIONONLY));
	return 1;
}

/*** START OF DYNAMIC SET CONFIG STUFF (that can be overridden by security group) ***/

void init_dynamic_set_block(DynamicSetBlock *s)
{
	memset(s, 0, sizeof(DynamicSetBlock));
}

void free_dynamic_set_block(DynamicSetBlock *s)
{
	safe_free(s->settings[SET_AUTO_JOIN].string);
	safe_free(s->settings[SET_STATIC_QUIT].string);
	safe_free(s->settings[SET_STATIC_PART].string);
	safe_free(s->settings[SET_RESTRICT_USERMODES].string);
}

void dynamic_set_string(DynamicSetBlock *s, int settingname, const char *value)
{
	s->isset[settingname] = 1;
	safe_strdup(s->settings[settingname].string, value);
}

void dynamic_set_number(DynamicSetBlock *s, int settingname, long long value)
{
	s->isset[settingname] = 1;
	s->settings[settingname].number = value;
}

#undef CheckNull
#define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); return 1; }

int test_dynamic_set_block_item(ConfigFile *conf, const char *security_group, ConfigEntry *cep)
{
	int errors = 0;

	if (!strcmp(cep->name, "modes-on-connect")) {
		char *p;
		CheckNull(cep);
		for (p = cep->value; *p; p++)
			if (strchr("orzSHqtW", *p))
			{
				config_error("%s:%i: set::modes-on-connect may not include mode '%c'",
					cep->file->filename, cep->line_number, *p);
				errors++;
			}
	}
	else if (!strcmp(cep->name, "auto-join")) {
		CheckNull(cep);
	}
	else if (!strcmp(cep->name, "maxchannelsperuser") || !strcmp(cep->name, "max-channels-per-user")) {
		int i;
		CheckNull(cep);
		i = atoi(cep->value);
		if (i < 1)
		{
			config_error("%s:%i: set::%s must be > 0",
				cep->file->filename,
				cep->line_number,
				cep->name);
			errors++;
		}
	}
	else if (!strcmp(cep->name, "static-quit")) {
		CheckNull(cep);
	}
	else if (!strcmp(cep->name, "static-part")) {
		CheckNull(cep);
	}
	else if (!strcmp(cep->name, "restrict-usermodes"))
	{
		CheckNull(cep);
		if (strchr(cep->value, '+') || strchr(cep->value, '-'))
		{
			config_error("%s:%i: set::restrict-usermodes: may only contain mode characters, no + or -.",
				cep->file->filename, cep->line_number);
			errors++;
		}
	} else
	{
		/* Unknown option */
		return -1;
	}
	return errors;
}
#undef CheckNull

int config_set_dynamic_set_block_item(ConfigFile *conf, DynamicSetBlock *s, ConfigEntry *cep)
{
	if (!strcmp(cep->name, "modes-on-connect")) {
		long v = set_usermode(cep->value);
		dynamic_set_number(s, SET_MODES_ON_CONNECT, v);
		return 1;
	}
	else if (!strcmp(cep->name, "auto-join")) {
		dynamic_set_string(s, SET_AUTO_JOIN, cep->value);
		return 1;
	}
	else if (!strcmp(cep->name, "maxchannelsperuser") || !strcmp(cep->name, "max-channels-per-user")) {
		dynamic_set_number(s, SET_MAX_CHANNELS_PER_USER, atoi(cep->value));
		return 1;
	}
	else if (!strcmp(cep->name, "static-quit")) {
		dynamic_set_string(s, SET_STATIC_QUIT, cep->value);
		return 1;
	}
	else if (!strcmp(cep->name, "static-part")) {
		dynamic_set_string(s, SET_STATIC_PART, cep->value);
		return 1;
	}
	else if (!strcmp(cep->name, "restrict-usermodes")) {
		int i;
		char buf[512];
		char *p = buf;
		/* The data should be something like 'Gw' or something,
		 * but just in case users use '+Gw' then ignore the + (and -).
		 */
		for (i=0; i < MIN(strlen(cep->value),sizeof(buf)-1); i++)
			if ((cep->value[i] != '+') && (cep->value[i] != '-'))
				*p++ = cep->value[i];
		*p = '\0';
		dynamic_set_string(s, SET_RESTRICT_USERMODES, cep->value);
		return 1;
	}
	return 0; /* unknown item, not taken */
}

int test_set_security_group(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	int errors = 0;
	int i;

	for (cep = ce->items; cep; cep = cep->next)
	{
		int n = test_dynamic_set_block_item(conf, ce->value, cep);
		if (n == -1)
		{
			config_error("%s:%i: set::%s is not allowed in a set <security group> { } block or is an unrecognized option",
			             cep->file->filename, cep->line_number, ce->value);
			errors++;
		} else
		if (n > 0)
		{
			errors += n;
		}
	}

	return errors;
}

int config_set_security_group(ConfigFile *conf, ConfigEntry *ce)
{
	ConfigEntry *cep;
	DynamicSetBlock *block = NULL;
	SecurityGroup *s;

	if (!strcmp(ce->value, "unknown-users"))
		block = &unknown_users_set;
	else if ((s = find_security_group(ce->value)))
		block = &s->settings;

	if (!block)
	{
		config_warn("set %s { } block encountered but security-group %s does not exist. Settings ignored",
		            ce->value, ce->value);
		return 0;
	}

	for (cep = ce->items; cep; cep = cep->next)
	{
		config_set_dynamic_set_block_item(conf, block, cep);
	}
	return 0;
}

/** Get the appropriate settings block for this user.
 * @param client	The client, should be locally connected.
 * @param opt		The flood option we are interested in
 * @returns The FloodSettings for this user, never returns NULL.
 */
DynamicSetOption *get_setting_for_user(Client *client, SetOption opt)
{
	SecurityGroup *sg;
	int in_known_users = 0;

	/* Go through all security groups by order of priority
	 * (eg: first "known-users", then "unknown-users").
	 * For each of these:
	 * - Check if a set::anti-flood::xxxx block exists for this group
	 * - Check if the limit is non-zero (eg there is any limit set)
	 * If any of these are false then we continue with next block
	 * that matches.
	 */

	for (sg = securitygroups; sg; sg = sg->next)
	{
		/* Special caching for 'known-users' */
		if (!strcmp(sg->name, "known-users"))
		{
			in_known_users = user_allowed_by_security_group(client, sg);
			if (in_known_users)
			{
				if (sg->settings.isset[opt])
					return &sg->settings.settings[opt];
			}
		} else
		if (user_allowed_by_security_group(client, sg) &&
		    sg->settings.isset[opt])
		{
			return &sg->settings.settings[opt];
		}
	}

	/* Not found, then maybe the user is in unknown-users?
	 * Use the caching info from above.
	 */
	if (!in_known_users && unknown_users_set.isset[opt])
		return &unknown_users_set.settings[opt];

	/* If we are still here, then fallback to the generic set:: setting */
	return &dynamic_set.settings[opt];
}

/** Get configuration that applies to the user - a number value */
long long get_setting_for_user_number(Client *client, SetOption opt)
{
	return get_setting_for_user(client, opt)->number;
}

/** Get configuration that applies to the user - a string config value */
const char *get_setting_for_user_string(Client *client, SetOption opt)
{
	return get_setting_for_user(client, opt)->string;
}

/*** END OF DYNAMIC SET CONFIG STUFF (that can be overridden by security group) ***/

/*** Central spamfilter ***/
int central_spamfilter_downloading = 0;

#define CENTRAL_SPAMFILTER_CACHE_TIME 0

int count_central_spamfilter_rules(void)
{
	int count = 0;
	TKL *tkl;

	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
		if (IsCentralSpamfilter(tkl))
			count++;

	return count;
}

void central_spamfilter_download_complete(OutgoingWebRequest *request, OutgoingWebResponse *response)
{
	ConfigFile *cfptr;
	int errors;
	int num_rules, active_rules;

	central_spamfilter_downloading = 0;

	/* Race condition: REHASH (with this new setting) and this download complete callback could cross */
	if (iConf.central_spamfilter_enabled == 0)
		return;

	if (iConf.central_spamfilter_verbose > 2)
	{
		unreal_log(ULOG_INFO, "central-spamfilter", "CENTRAL_SPAMFILTER_STATUS", NULL,
		           "Processing central spamfilter rules...");
	}

	if (response->errorbuf)
	{
		config_error("Central spamfilter URL fetch failed (this could be a temporary problem): %s: %s", request->url, response->errorbuf);
		return;
	}
	
	if (!(cfptr = config_load(response->file, "central_spamfilter.conf")))
	{
		unreal_log(ULOG_ERROR, "central-spamfilter", "CENTRAL_SPAMFILTER_LOAD_FAILED", NULL,
		           "[Central spamfilter] Failed to load"); // Where is the REASON ???
		return;
	}

	preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_INITIAL);
	preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_SECONDARY);
	errors = config_test_blocks_generic(cfptr, 0, 0);
	if (errors)
	{
		unreal_log(ULOG_ERROR, "central-spamfilter", "CENTRAL_SPAMFILTER_LOAD_FAILED", NULL,
			"[Central spamfilter] Errors in the central central_spamfilter.conf -- not loaded.");
		config_free(cfptr);
		return;
	}

	/* Remove all current central spamfilters */
	remove_config_tkls(TKL_FLAG_CENTRAL_SPAMFILTER);

	/* And load the new ones... */
	num_rules = config_run_blocks_generic(cfptr, 0);
	active_rules = count_central_spamfilter_rules();

	if (iConf.central_spamfilter_verbose > 2)
	{
		unreal_log(ULOG_INFO, "central-spamfilter", "CENTRAL_SPAMFILTER_STATUS", NULL,
		           "Central spamfilter loaded (active rules: $active_rules, skipped: $skipped_rules)",
		           log_data_integer("active_rules", active_rules),
		           log_data_integer("skipped_rules", num_rules - active_rules));
	}
	config_free(cfptr);
}

void central_spamfilter_start_download(void)
{
	char url[512];
	NameValuePrioList *nvp = NULL;
	const char *apikey;
	OutgoingWebRequest *request;

	if (central_spamfilter_downloading)
		return;

	if (iConf.central_spamfilter_verbose > 2)
	{
		unreal_log(ULOG_INFO, "central-spamfilter", "CENTRAL_SPAMFILTER_STATUS", NULL,
		           "Starting download of central spamfilter rules...");
	}

	central_spamfilter_downloading = 1;

	/* Prepare the request */
	request = safe_alloc(sizeof(OutgoingWebRequest));
	request->http_method = HTTP_METHOD_GET;
	request->cachetime = CENTRAL_SPAMFILTER_CACHE_TIME;
	request->callback = central_spamfilter_download_complete;
	request->callback_data = NULL;
	request->max_redirects = DOWNLOAD_MAX_REDIRECTS;
	request->store_in_file = 1;

	/* Build the URL */
	add_nvplist(&nvp, 0, "feed", iConf.central_spamfilter_feed);
	apikey = get_central_api_key();
	if (apikey && !strcmp(iConf.central_spamfilter_url, DEFAULT_CENTRAL_SPAMFILTER_URL_OPEN_ACCESS))
	{
		/* Use the restricted URL */
		buildvarstring_nvp(DEFAULT_CENTRAL_SPAMFILTER_URL_RESTRICTED_ACCESS, url, sizeof(url), nvp, 0);
		add_nvplist(&request->headers, 0, "X-API-Key", apikey);
	} else {
		/* Use the open access URL */
		buildvarstring_nvp(iConf.central_spamfilter_url, url, sizeof(url), nvp, 0);
	}
	safe_free_nvplist(nvp);

	/* Start HTTPS request */
	safe_strdup(request->url, url);
	url_start_async(request);
}

EVENT(central_spamfilter_download_evt)
{
	if (iConf.central_spamfilter_enabled == 0)
		return;
	if ((central_spamfilter_last_download == 0) || (TStime() - central_spamfilter_last_download > iConf.central_spamfilter_refresh_time))
	{
		central_spamfilter_last_download = TStime();
		central_spamfilter_start_download();
	}
}

/*** End of central spamfilter ***/

/** Return full name of the config item, eg set::something::priority */
const char *config_item_name(ConfigEntry *ce)
{
	static char buf[512];
	char tmp[512];

	*buf = '\0';
	for (; ce; ce = ce->parent)
	{
		if (*buf)
		{
			strlcpy(tmp, buf, sizeof(tmp));
			snprintf(buf, sizeof(buf), "%s::%s", ce->name, tmp);
		} else {
			strlcpy(buf, ce->name, sizeof(buf));
		}
	}
	return buf;
}
