mirror of
git://git.acid.vegas/unrealircd.git
synced 2024-12-26 16:26:38 +00:00
10970 lines
288 KiB
C
10970 lines
288 KiB
C
/*
|
|
* 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_vhost (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);
|
|
|
|
/*
|
|
* 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_vhost (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);
|
|
|
|
/* 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 },
|
|
{ "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 },
|
|
{ "vhost", _conf_vhost, _test_vhost },
|
|
};
|
|
|
|
/* 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 _LinkFlags[] = {
|
|
{ CONNECT_AUTO, "autoconnect" },
|
|
{ CONNECT_INSECURE, "insecure" },
|
|
{ CONNECT_QUARANTINE, "quarantine"},
|
|
{ CONNECT_TLS, "ssl" },
|
|
{ CONNECT_TLS, "tls" },
|
|
};
|
|
|
|
/* 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);
|
|
|
|
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_vhost *conf_vhost = 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;
|
|
Secret *secrets = NULL;
|
|
|
|
MODVAR Configuration iConf;
|
|
MODVAR Configuration tempiConf;
|
|
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 int config_error_flag = 0;
|
|
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 add_config_resource(const char *resource, int type, ConfigEntry *ce);
|
|
void resource_download_complete(const char *url, const char *file, const char *errorbuf, int cached, void *rs_key);
|
|
void free_all_config_resources(void);
|
|
int rehash_internal(Client *client);
|
|
int is_blacklisted_module(const char *name);
|
|
|
|
/** 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;
|
|
}
|
|
|
|
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",
|
|
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;
|
|
}
|
|
|
|
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\n", filename, strerror(errno));
|
|
return NULL;
|
|
}
|
|
if (fstat(fd, &sb) == -1)
|
|
{
|
|
config_error("Couldn't fstat \"%s\": %s\n", 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\"\n", filename);
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
ret = read(fd, buf, sb.st_size);
|
|
if (ret != sb.st_size)
|
|
{
|
|
config_error("Error reading \"%s\": %s\n", filename,
|
|
ret == -1 ? strerror(errno) : strerror(EFAULT));
|
|
safe_free(buf);
|
|
close(fd);
|
|
return NULL;
|
|
}
|
|
/* Just me or could this cause memory corrupted when ret <0 ? */
|
|
buf[ret] = '\0';
|
|
close(fd);
|
|
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\n",
|
|
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.\n",
|
|
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.\n",
|
|
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.\n",
|
|
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;
|
|
}
|
|
}
|
|
if (!*ptr)
|
|
{
|
|
config_error("%s:%i Comment on line %d does not end\n",
|
|
filename, commentstart, commentstart);
|
|
errors++;
|
|
config_entry_free_all(curce);
|
|
config_free(curcf);
|
|
return NULL;
|
|
}
|
|
}
|
|
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\n",
|
|
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\n",
|
|
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?\n",
|
|
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;
|
|
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_ENDIF)
|
|
{
|
|
if (preprocessor_level == 0)
|
|
{
|
|
config_error("%s:%i: @endif unexpected. There was no preciding unclosed @if.",
|
|
filename, linenumber);
|
|
errors++;
|
|
}
|
|
preprocessor_cc_free_level(&cc_list, preprocessor_level);
|
|
preprocessor_level--;
|
|
} else
|
|
if (n == PREPROCESSOR_ERROR)
|
|
{
|
|
errors++;
|
|
goto breakout;
|
|
}
|
|
|
|
if (!*ptr)
|
|
goto breakout; /* special case, since we don't want the for loop to ptr++ */
|
|
|
|
break;
|
|
default:
|
|
if ((*ptr == '*') && (*(ptr+1) == '/'))
|
|
{
|
|
config_status("%s:%i: Ignoring extra end comment\n",
|
|
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?\n",
|
|
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 }; ?\n",
|
|
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 };\n", 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\n",
|
|
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?\n",
|
|
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 }; ?\n",
|
|
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;
|
|
}
|
|
|
|
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);
|
|
/* We cannot live with this */
|
|
config_error_flag = 1;
|
|
}
|
|
|
|
void config_error_missing(const char *filename, int line, const char *entry)
|
|
{
|
|
config_error("%s:%d: %s is missing", filename, line, entry);
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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->auto_join_chans);
|
|
safe_free(i->oper_auto_join_chans);
|
|
safe_free(i->allow_user_stats);
|
|
// allow_user_stats_ext is freed elsewhere
|
|
safe_free(i->static_quit);
|
|
safe_free(i->static_part);
|
|
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_usermodes);
|
|
safe_free(i->restrict_channelmodes);
|
|
safe_free(i->restrict_extendedbans);
|
|
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);
|
|
// spamexcept is freed elsewhere
|
|
safe_free(i->spamexcept_line);
|
|
safe_free(i->reject_message_too_many_connections);
|
|
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->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);
|
|
// anti-flood:
|
|
for (f = i->floodsettings; f; f = f_next)
|
|
{
|
|
f_next = f->next;
|
|
free_floodsettings(f);
|
|
}
|
|
i->floodsettings = NULL;
|
|
}
|
|
|
|
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->ban_version_tkl_time = 86400; /* 1d */
|
|
i->spamfilter_ban_time = 86400; /* 1d */
|
|
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 = 1;
|
|
i->maxchannelsperuser = 10;
|
|
i->maxdccallow = 10;
|
|
safe_strdup(i->channel_command_prefix, "`!.");
|
|
i->conn_modes = set_usermode("+ixw");
|
|
i->check_target_nick_bans = 1;
|
|
i->maxbans = 60;
|
|
i->maxbanlength = 2048;
|
|
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 = 30;
|
|
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 = 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 */
|
|
/* - 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 */
|
|
|
|
/* TLS options */
|
|
i->tls_options = safe_alloc(sizeof(TLSOptions));
|
|
snprintf(tmp, sizeof(tmp), "%s/tls/server.cert.pem", CONFDIR);
|
|
safe_strdup(i->tls_options->certificate_file, tmp);
|
|
snprintf(tmp, sizeof(tmp), "%s/tls/server.key.pem", CONFDIR);
|
|
safe_strdup(i->tls_options->key_file, tmp);
|
|
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 */
|
|
#ifdef HAS_SSL_CTX_SET1_CURVES_LIST
|
|
safe_strdup(i->tls_options->ecdh_curves, UNREALIRCD_DEFAULT_ECDH_CURVES);
|
|
#endif
|
|
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->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");
|
|
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;
|
|
i->ban_setter = SETTER_NICK;
|
|
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;
|
|
}
|
|
|
|
/** 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;
|
|
|
|
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();
|
|
}
|
|
|
|
void postconf_fixes(void)
|
|
{
|
|
/* If set::topic-setter is set to "nick-user-host" then the
|
|
* maximum topic length becomes shorter.
|
|
*/
|
|
if ((iConf.topic_setter == SETTER_NICK_USER_HOST) &&
|
|
(iConf.topic_length > 340))
|
|
{
|
|
config_warn("set::topic-length adjusted from %d to 340, which is the maximum because "
|
|
"set::topic-setter is set to 'nick-user-host'.", iConf.topic_length);
|
|
iConf.topic_length = 340;
|
|
}
|
|
}
|
|
|
|
/* 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();
|
|
postconf_fixes();
|
|
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
|
|
}
|
|
|
|
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 ((config_test_blocks() < 0) || (callbacks_check() < 0) || (efunctions_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"))
|
|
{
|
|
if (ce->conditional_config)
|
|
{
|
|
config_error("%s:%d: Currently you cannot have a 'loadmodule' statement "
|
|
"within an @if block, sorry.",
|
|
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;
|
|
Unload_all_testing_modules();
|
|
free_all_config_resources();
|
|
config_free(conf);
|
|
conf = NULL;
|
|
free_iConf(&tempiConf);
|
|
#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;
|
|
}
|
|
|
|
/* 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));
|
|
memset(&settings, 0, sizeof(settings));
|
|
memset(&requiredstuff, 0, sizeof(requiredstuff));
|
|
memset(&nicklengths, 0, sizeof(nicklengths));
|
|
config_setdefaultsettings(&tempiConf);
|
|
clicap_pre_rehash();
|
|
log_pre_rehash();
|
|
free_config_defines();
|
|
|
|
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;
|
|
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);
|
|
}
|
|
loop.config_status = CONFIG_STATUS_POSTLOAD;
|
|
postconf();
|
|
unreal_log(ULOG_INFO, "config", "CONFIG_LOADED", NULL, "Configuration loaded");
|
|
unload_all_unused_mtag_handlers();
|
|
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;
|
|
}
|
|
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(void)
|
|
{
|
|
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 & TKL_FLAG_CONFIG)
|
|
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 & TKL_FLAG_CONFIG)
|
|
tkl_del_line(tk);
|
|
}
|
|
}
|
|
}
|
|
|
|
void config_rehash()
|
|
{
|
|
ConfigItem_oper *oper_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_vhost *vhost_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;
|
|
SpamExcept *spamex_ptr;
|
|
|
|
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);
|
|
Auth_FreeAuthConfig(oper_ptr->auth);
|
|
free_security_group(oper_ptr->match);
|
|
DelListItem(oper_ptr, conf_oper);
|
|
for (s = oper_ptr->swhois; s; s = s_next)
|
|
{
|
|
s_next = s->next;
|
|
safe_free(s->line);
|
|
safe_free(s->setby);
|
|
safe_free(s);
|
|
}
|
|
safe_free(oper_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);
|
|
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);
|
|
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);
|
|
|
|
DelListItem(tld_ptr, conf_tld);
|
|
safe_free(tld_ptr);
|
|
}
|
|
for (vhost_ptr = conf_vhost; vhost_ptr; vhost_ptr = (ConfigItem_vhost *) next)
|
|
{
|
|
SWhois *s, *s_next;
|
|
|
|
next = (ListStruct *)vhost_ptr->next;
|
|
|
|
safe_free(vhost_ptr->login);
|
|
Auth_FreeAuthConfig(vhost_ptr->auth);
|
|
safe_free(vhost_ptr->virthost);
|
|
safe_free(vhost_ptr->virtuser);
|
|
free_security_group(vhost_ptr->match);
|
|
for (s = vhost_ptr->swhois; s; s = s_next)
|
|
{
|
|
s_next = s->next;
|
|
safe_free(s->line);
|
|
safe_free(s->setby);
|
|
safe_free(s);
|
|
}
|
|
DelListItem(vhost_ptr, conf_vhost);
|
|
safe_free(vhost_ptr);
|
|
}
|
|
|
|
remove_config_tkls();
|
|
|
|
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 (spamex_ptr = iConf.spamexcept; spamex_ptr; spamex_ptr = (SpamExcept *)next)
|
|
{
|
|
next = (ListStruct *)spamex_ptr->next;
|
|
safe_free(spamex_ptr);
|
|
}
|
|
iConf.spamexcept = 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);
|
|
|
|
/*
|
|
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();
|
|
}
|
|
|
|
/** 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 */
|
|
"set",
|
|
"class",
|
|
};
|
|
|
|
/** Priority of config blocks during CONFIG_RUN stage */
|
|
static const char *config_run_priority_blocks[] =
|
|
{
|
|
"me",
|
|
"secret",
|
|
"set",
|
|
"log", /* "log" needs to be after "set" in CONFIG_RUN */
|
|
"class",
|
|
};
|
|
|
|
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_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);
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
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 ((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 (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");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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);
|
|
}
|
|
|
|
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 (!strcmp(ce->name, config_block))
|
|
{
|
|
if (cc->conffunc(cfptr, ce) < 0)
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* 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);
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
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 ((cc = config_binary_search(ce->name))) {
|
|
if ((cc->conffunc) && (cc->conffunc(cfptr, ce) < 0))
|
|
errors++;
|
|
}
|
|
else
|
|
{
|
|
int value;
|
|
for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
|
|
{
|
|
value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN);
|
|
if (value == 1)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
close_unbound_listeners();
|
|
listen_cleanup();
|
|
close_unbound_listeners();
|
|
loop.do_bancheck = 1;
|
|
config_switchover();
|
|
update_throttling_timer_settings();
|
|
|
|
/* 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;
|
|
}
|
|
|
|
ConfigItem_vhost *find_vhost(const char *name)
|
|
{
|
|
ConfigItem_vhost *vhost;
|
|
|
|
for (vhost = conf_vhost; vhost; vhost = vhost->next)
|
|
{
|
|
if (!strcmp(name, vhost->login))
|
|
return vhost;
|
|
}
|
|
|
|
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_simple(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_simple(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));
|
|
}
|
|
|
|
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 (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;
|
|
}
|
|
|
|
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)
|
|
{
|
|
char 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++;
|
|
}
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "parent"))
|
|
{
|
|
if (has_parent)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "operclass::parent");
|
|
continue;
|
|
}
|
|
has_parent = 1;
|
|
continue;
|
|
} else
|
|
if (!strcmp(cep->name, "permissions"))
|
|
{
|
|
if (has_permissions)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "oper::permissions");
|
|
continue;
|
|
}
|
|
has_permissions = 1;
|
|
continue;
|
|
} 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++;
|
|
continue;
|
|
}
|
|
}
|
|
|
|
if (!has_permissions)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"oper::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"))
|
|
oper->auth = AuthBlockToAuthConfig(cep);
|
|
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-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"))
|
|
{
|
|
if (has_password)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "oper::password");
|
|
continue;
|
|
}
|
|
has_password = 1;
|
|
if (Auth_CheckError(cep) < 0)
|
|
errors++;
|
|
|
|
if (ce->value && cep->value &&
|
|
!strcmp(ce->value, "bobsmith") &&
|
|
!strcmp(cep->value, "test"))
|
|
{
|
|
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;
|
|
}
|
|
/* 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 (!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-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"))
|
|
{
|
|
if (has_password)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "oper::password");
|
|
continue;
|
|
}
|
|
has_password = 1;
|
|
if (Auth_CheckError(cep) < 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;
|
|
|
|
}
|
|
|
|
/*
|
|
* The class {} block parser
|
|
*/
|
|
int _conf_class(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep, *cep2;
|
|
ConfigItem_class *class;
|
|
unsigned char isnew = 0;
|
|
|
|
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;
|
|
}
|
|
}
|
|
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;
|
|
|
|
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)
|
|
{
|
|
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"))
|
|
{
|
|
if (conf_drpass->restartauth)
|
|
Auth_FreeAuthConfig(conf_drpass->restartauth);
|
|
|
|
conf_drpass->restartauth = AuthBlockToAuthConfig(cep);
|
|
}
|
|
else if (!strcmp(cep->name, "die"))
|
|
{
|
|
if (conf_drpass->dieauth)
|
|
Auth_FreeAuthConfig(conf_drpass->dieauth);
|
|
|
|
conf_drpass->dieauth = AuthBlockToAuthConfig(cep);
|
|
}
|
|
}
|
|
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)
|
|
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)
|
|
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(listen->websocket_forward);
|
|
safe_free(listen->webserver);
|
|
|
|
/* 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;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
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 *file = NULL;
|
|
char *ip = NULL;
|
|
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 (!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 */
|
|
}
|
|
}
|
|
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;
|
|
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"))
|
|
{
|
|
int start = 0, end = 0;
|
|
|
|
has_port = 1;
|
|
|
|
port_range(cep->value, &start, &end);
|
|
if (start == end)
|
|
{
|
|
if ((start < 1) || (start > 65535))
|
|
{
|
|
config_error("%s:%i: listen: illegal port (must be 1..65535)",
|
|
cep->file->filename, cep->line_number);
|
|
return 1;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (end < start)
|
|
{
|
|
config_error("%s:%i: listen: illegal port range end value is less than starting value",
|
|
cep->file->filename, cep->line_number);
|
|
return 1;
|
|
}
|
|
if (end - 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, start, end,
|
|
end - start + 1, end - start + 1);
|
|
return 1;
|
|
}
|
|
if ((start < 1) || (start > 65535) || (end < 1) || (end > 65535))
|
|
{
|
|
config_error("%s:%i: listen: illegal port range values must be between 1 and 65535",
|
|
cep->file->filename, cep->line_number);
|
|
return 1;
|
|
}
|
|
}
|
|
|
|
if ((6667 >= start) && (6667 <= 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);
|
|
|
|
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->ipv6_clone_mask = tempiConf.default_ipv6_clone_mask;
|
|
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"))
|
|
allow->auth = AuthBlockToAuthConfig(cep);
|
|
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, "maxperip"))
|
|
allow->maxperip = atoi(cep->value);
|
|
else if (!strcmp(cep->name, "global-maxperip"))
|
|
allow->global_maxperip = atoi(cep->value);
|
|
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, "ipv6-clone-mask"))
|
|
{
|
|
/*
|
|
* If this item isn't set explicitly by the
|
|
* user, the value will temporarily be
|
|
* zero. Defaults are applied in config_run_blocks().
|
|
*/
|
|
allow->ipv6_clone_mask = 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;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Default: global-maxperip = maxperip+1 */
|
|
if (allow->global_maxperip == 0)
|
|
allow->global_maxperip = allow->maxperip+1;
|
|
|
|
/* global-maxperip < maxperip makes no sense */
|
|
if (allow->global_maxperip < allow->maxperip)
|
|
allow->global_maxperip = allow->maxperip;
|
|
|
|
AddListItem(allow, conf_allow);
|
|
return 1;
|
|
}
|
|
|
|
int _test_allow(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep, *cepp;
|
|
int errors = 0;
|
|
Hook *h;
|
|
char has_ip = 0, has_hostname = 0, has_mask = 0, has_match = 0;
|
|
char has_maxperip = 0, has_global_maxperip = 0, has_password = 0, has_class = 0;
|
|
char 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)
|
|
{
|
|
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, "maxperip"))
|
|
{
|
|
int v = atoi(cep->value);
|
|
if (has_maxperip)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "allow::maxperip");
|
|
continue;
|
|
}
|
|
has_maxperip = 1;
|
|
if ((v <= 0) || (v > 1000000))
|
|
{
|
|
config_error("%s:%i: allow::maxperip with illegal value (must be 1-1000000)",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "global-maxperip"))
|
|
{
|
|
int v = atoi(cep->value);
|
|
if (has_global_maxperip)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "allow::global-maxperip");
|
|
continue;
|
|
}
|
|
has_global_maxperip = 1;
|
|
if ((v <= 0) || (v > 1000000))
|
|
{
|
|
config_error("%s:%i: allow::global-maxperip with illegal value (must be 1-1000000)",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "ipv6-clone-mask"))
|
|
{
|
|
/* keep this in sync with _test_set() */
|
|
int ipv6mask;
|
|
ipv6mask = atoi(cep->value);
|
|
if (ipv6mask == 0)
|
|
{
|
|
config_error("%s:%d: allow::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: allow::ipv6-clone-mask was given a very small value.",
|
|
cep->file->filename, cep->line_number);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "password"))
|
|
{
|
|
if (has_password)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "allow::password");
|
|
continue;
|
|
}
|
|
has_password = 1;
|
|
/* some auth check stuff? */
|
|
if (Auth_CheckError(cep) < 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++;
|
|
}
|
|
|
|
if (!has_maxperip)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"allow::maxperip");
|
|
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;
|
|
}
|
|
|
|
/*
|
|
* vhost {} block parser
|
|
*/
|
|
int _conf_vhost(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigItem_vhost *vhost;
|
|
ConfigEntry *cep, *cepp;
|
|
vhost = safe_alloc(sizeof(ConfigItem_vhost));
|
|
vhost->match = safe_alloc(sizeof(SecurityGroup));
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "vhost"))
|
|
{
|
|
char *user, *host;
|
|
user = strtok(cep->value, "@");
|
|
host = strtok(NULL, "");
|
|
if (!host)
|
|
safe_strdup(vhost->virthost, user);
|
|
else
|
|
{
|
|
safe_strdup(vhost->virtuser, user);
|
|
safe_strdup(vhost->virthost, host);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "login"))
|
|
safe_strdup(vhost->login, cep->value);
|
|
else if (!strcmp(cep->name, "password"))
|
|
vhost->auth = AuthBlockToAuthConfig(cep);
|
|
else if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
|
|
{
|
|
conf_match_block(conf, cep, &vhost->match);
|
|
}
|
|
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, "vhost");
|
|
AddListItem(s, vhost->swhois);
|
|
}
|
|
} else
|
|
if (cep->value)
|
|
{
|
|
s = safe_alloc(sizeof(SWhois));
|
|
safe_strdup(s->line, cep->value);
|
|
safe_strdup(s->setby, "vhost");
|
|
AddListItem(s, vhost->swhois);
|
|
}
|
|
}
|
|
}
|
|
AddListItem(vhost, conf_vhost);
|
|
return 1;
|
|
}
|
|
|
|
int _test_vhost(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
int errors = 0;
|
|
ConfigEntry *cep;
|
|
char has_vhost = 0, has_login = 0, has_password = 0, has_mask = 0, has_match = 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "vhost"))
|
|
{
|
|
char *at, *tmp, *host;
|
|
if (has_vhost)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "vhost::vhost");
|
|
continue;
|
|
}
|
|
has_vhost = 1;
|
|
if (!cep->value)
|
|
{
|
|
config_error_empty(cep->file->filename,
|
|
cep->line_number, "vhost", "vhost");
|
|
errors++;
|
|
continue;
|
|
}
|
|
if (!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++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "login"))
|
|
{
|
|
if (has_login)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "vhost::login");
|
|
}
|
|
has_login = 1;
|
|
if (!cep->value)
|
|
{
|
|
config_error_empty(cep->file->filename,
|
|
cep->line_number, "vhost", "login");
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "password"))
|
|
{
|
|
if (has_password)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "vhost::password");
|
|
}
|
|
has_password = 1;
|
|
if (!cep->value)
|
|
{
|
|
config_error_empty(cep->file->filename,
|
|
cep->line_number, "vhost", "password");
|
|
errors++;
|
|
continue;
|
|
}
|
|
if (Auth_CheckError(cep) < 0)
|
|
errors++;
|
|
}
|
|
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, "swhois"))
|
|
{
|
|
/* multiple is ok */
|
|
}
|
|
else
|
|
{
|
|
config_error_unknown(cep->file->filename, cep->line_number,
|
|
"vhost", cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
if (!has_vhost)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::vhost");
|
|
errors++;
|
|
}
|
|
if (!has_login)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::login");
|
|
errors++;
|
|
|
|
}
|
|
if (!has_password)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::password");
|
|
errors++;
|
|
}
|
|
if (!has_mask && !has_match)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"vhost::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;
|
|
}
|
|
|
|
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(_LinkFlags, 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"))
|
|
link->auth = AuthBlockToAuthConfig(cep);
|
|
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;
|
|
}
|
|
|
|
/** Helper function for erroring on duplicate items.
|
|
*/
|
|
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;
|
|
}
|
|
|
|
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;
|
|
|
|
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 (!strcmp(ceppp->name, "autoconnect"))
|
|
;
|
|
else if (!strcmp(ceppp->name, "ssl") || !strcmp(ceppp->name, "tls"))
|
|
;
|
|
else if (!strcmp(ceppp->name, "insecure"))
|
|
;
|
|
else
|
|
{
|
|
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"))
|
|
{
|
|
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"))
|
|
{
|
|
config_detect_duplicate(&has_password, cep, &errors);
|
|
if (Auth_CheckError(cep) < 0)
|
|
{
|
|
errors++;
|
|
} else {
|
|
AuthConfig *auth = AuthBlockToAuthConfig(cep);
|
|
/* 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;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "options"))
|
|
{
|
|
config_detect_duplicate(&has_options, cep, &errors);
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "quarantine"))
|
|
;
|
|
else
|
|
{
|
|
config_error("%s:%d: link::options only has one possible option ('quarantine', 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"))
|
|
ca->action = banact_stringtoval(cep->value);
|
|
}
|
|
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;
|
|
if (!banact_stringtoval(cep->value))
|
|
{
|
|
config_error("%s:%i: ban::action has unknown action type '%s'",
|
|
cep->file->filename, cep->line_number,
|
|
cep->value);
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
|
|
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;
|
|
char *usermask = NULL;
|
|
char *hostmask = 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, "mask"))
|
|
{
|
|
char buf[512], *p;
|
|
strlcpy(buf, cep->value, sizeof(buf));
|
|
p = strchr(buf, '@');
|
|
if (p)
|
|
{
|
|
*p++ = '\0';
|
|
safe_strdup(usermask, buf);
|
|
safe_strdup(hostmask, p);
|
|
} else {
|
|
safe_strdup(hostmask, cep->value);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "reason"))
|
|
safe_strdup(reason, cep->value);
|
|
}
|
|
|
|
if (!usermask)
|
|
safe_strdup(usermask, "*");
|
|
|
|
if (!reason)
|
|
safe_strdup(reason, "-");
|
|
|
|
tkl_add_serverban(TKL_KILL, usermask, hostmask, reason, "-config-", 0, TStime(), 1, TKL_FLAG_CONFIG);
|
|
safe_free(usermask);
|
|
safe_free(hostmask);
|
|
safe_free(reason);
|
|
return 0;
|
|
}
|
|
|
|
int _test_require(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
Hook *h;
|
|
char has_mask = 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 (config_is_blankorempty(cep, "require"))
|
|
{
|
|
errors++;
|
|
continue;
|
|
}
|
|
if (!strcmp(cep->name, "mask"))
|
|
{
|
|
if (has_mask)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "require::mask");
|
|
continue;
|
|
}
|
|
has_mask = 1;
|
|
}
|
|
else if (!strcmp(cep->name, "reason"))
|
|
{
|
|
if (has_reason)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "require::reason");
|
|
continue;
|
|
}
|
|
has_reason = 1;
|
|
}
|
|
}
|
|
|
|
if (!has_mask)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"require::mask");
|
|
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;
|
|
|
|
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, "ecdh-curves"))
|
|
{
|
|
CheckNull(cepp);
|
|
#ifndef HAS_SSL_CTX_SET1_CURVES_LIST
|
|
config_error("ecdh-curves specified but your OpenSSL/LibreSSL library does not "
|
|
"support setting curves manually by name. Either upgrade to a "
|
|
"newer library version or remove the 'ecdh-curves' directive "
|
|
"from your configuration file");
|
|
errors++;
|
|
#endif
|
|
}
|
|
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);
|
|
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
|
|
{
|
|
config_error("%s:%i: unknown directive %s",
|
|
cepp->file->filename, cepp->line_number,
|
|
config_var(cepp));
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
*totalerrors += errors;
|
|
}
|
|
|
|
void free_tls_options(TLSOptions *tlsoptions)
|
|
{
|
|
if (!tlsoptions)
|
|
return;
|
|
|
|
safe_free(tlsoptions->certificate_file);
|
|
safe_free(tlsoptions->key_file);
|
|
safe_free(tlsoptions->trusted_ca_file);
|
|
safe_free(tlsoptions->ciphers);
|
|
safe_free(tlsoptions->ciphersuites);
|
|
safe_free(tlsoptions->ecdh_curves);
|
|
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)
|
|
{
|
|
safe_strdup(tlsoptions->certificate_file, tempiConf.tls_options->certificate_file);
|
|
safe_strdup(tlsoptions->key_file, tempiConf.tls_options->key_file);
|
|
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->ecdh_curves, tempiConf.tls_options->ecdh_curves);
|
|
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;
|
|
}
|
|
|
|
/* 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, "ecdh-curves"))
|
|
{
|
|
safe_strdup(tlsoptions->ecdh_curves, 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);
|
|
safe_strdup(tlsoptions->certificate_file, cepp->value);
|
|
}
|
|
else if (!strcmp(cepp->name, "key"))
|
|
{
|
|
convert_to_absolute_path(&cepp->value, CONFDIR);
|
|
safe_strdup(tlsoptions->key_file, 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);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
int _conf_set(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep, *cepp, *ceppp, *cep4;
|
|
Hook *h;
|
|
|
|
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-connect")) {
|
|
tempiConf.conn_modes = (long) set_usermode(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, "static-quit")) {
|
|
safe_strdup(tempiConf.static_quit, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "static-part")) {
|
|
safe_strdup(tempiConf.static_part, cep->value);
|
|
}
|
|
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, "maxbanlength")) {
|
|
tempiConf.maxbanlength = atol(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "silence-limit")) {
|
|
tempiConf.silence_limit = atol(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "auto-join")) {
|
|
safe_strdup(tempiConf.auto_join_chans, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "oper-auto-join")) {
|
|
safe_strdup(tempiConf.oper_auto_join_chans, 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, "channel-command-prefix")) {
|
|
safe_strdup(tempiConf.channel_command_prefix, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-usermodes")) {
|
|
int i;
|
|
char *p = safe_alloc(strlen(cep->value) + 1), *x = p;
|
|
/* The data should be something like 'Gw' or something,
|
|
* but just in case users use '+Gw' then ignore the + (and -).
|
|
*/
|
|
for (i=0; i < strlen(cep->value); i++)
|
|
if ((cep->value[i] != '+') && (cep->value[i] != '-'))
|
|
*x++ = cep->value[i];
|
|
*x = '\0';
|
|
tempiConf.restrict_usermodes = p;
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-channelmodes")) {
|
|
int i;
|
|
char *p = safe_alloc(strlen(cep->value) + 1), *x = p;
|
|
/* The data should be something like 'GL' or something,
|
|
* but just in case users use '+GL' then ignore the + (and -).
|
|
*/
|
|
for (i=0; i < strlen(cep->value); i++)
|
|
if ((cep->value[i] != '+') && (cep->value[i] != '-'))
|
|
*x++ = cep->value[i];
|
|
*x = '\0';
|
|
tempiConf.restrict_channelmodes = p;
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-extendedbans")) {
|
|
safe_strdup(tempiConf.restrict_extendedbans, 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, "maxchannelsperuser")) {
|
|
tempiConf.maxchannelsperuser = atoi(cep->value);
|
|
}
|
|
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")) {
|
|
tempiConf.hide_ban_reason = 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"))
|
|
tempiConf.handshake_data_flood_ban_action = banact_stringtoval(cep4->value);
|
|
}
|
|
}
|
|
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);
|
|
}
|
|
}
|
|
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, "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"))
|
|
{
|
|
char *name, *p;
|
|
SpamExcept *e;
|
|
safe_strdup(tempiConf.spamexcept_line, cepp->value);
|
|
for (name = strtoken(&p, cepp->value, ","); name; name = strtoken(&p, NULL, ","))
|
|
{
|
|
if (*name == ' ')
|
|
name++;
|
|
if (*name)
|
|
{
|
|
e = safe_alloc(sizeof(SpamExcept) + strlen(name));
|
|
strcpy(e->name, name);
|
|
AddListItem(e, tempiConf.spamexcept);
|
|
}
|
|
}
|
|
}
|
|
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(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, "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, "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
|
|
{
|
|
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;
|
|
Hook *h;
|
|
|
|
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-connect")) {
|
|
char *p;
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, modes_on_connect, "modes-on-connect");
|
|
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, "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++;
|
|
}
|
|
set_usermode(cep->value);
|
|
}
|
|
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, "static-quit")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, static_quit, "static-quit");
|
|
}
|
|
else if (!strcmp(cep->name, "static-part")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, static_part, "static-part");
|
|
}
|
|
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")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, maxbanlength, "maxbanlength");
|
|
}
|
|
else if (!strcmp(cep->name, "silence-limit")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, silence_limit, "silence-limit");
|
|
}
|
|
else if (!strcmp(cep->name, "auto-join")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, auto_join, "auto-join");
|
|
}
|
|
else if (!strcmp(cep->name, "oper-auto-join")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, oper_auto_join, "oper-auto-join");
|
|
}
|
|
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, "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, "maxchannelsperuser")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, maxchannelsperuser, "maxchannelsperuser");
|
|
tempi = atoi(cep->value);
|
|
if (tempi < 1)
|
|
{
|
|
config_error("%s:%i: set::maxchannelsperuser must be > 0",
|
|
cep->file->filename,
|
|
cep->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
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, "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");
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-usermodes"))
|
|
{
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, restrict_usermodes, "restrict-usermodes");
|
|
if (cep->name) {
|
|
int warn = 0;
|
|
char *p;
|
|
for (p = cep->value; *p; p++)
|
|
if ((*p == '+') || (*p == '-'))
|
|
warn = 1;
|
|
if (warn) {
|
|
config_status("%s:%i: warning: set::restrict-usermodes: should only contain modechars, no + or -.\n",
|
|
cep->file->filename, cep->line_number);
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-channelmodes"))
|
|
{
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, restrict_channelmodes, "restrict-channelmodes");
|
|
if (cep->name) {
|
|
int warn = 0;
|
|
char *p;
|
|
for (p = cep->value; *p; p++)
|
|
if ((*p == '+') || (*p == '-'))
|
|
warn = 1;
|
|
if (warn) {
|
|
config_status("%s:%i: warning: set::restrict-channelmodes: should only contain modechars, no + or -.\n",
|
|
cep->file->filename, cep->line_number);
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-extendedbans"))
|
|
{
|
|
CheckDuplicate(cep, restrict_extendedbans, "restrict-extendedbans");
|
|
CheckNull(cep);
|
|
}
|
|
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, "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"))
|
|
{
|
|
CheckNull(cep4);
|
|
if (!banact_stringtoval(cep4->value))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::handshake-data-flood::ban-action has unknown action type '%s'",
|
|
cep4->file->filename, cep4->line_number,
|
|
cep4->value);
|
|
errors++;
|
|
}
|
|
} 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
|
|
{
|
|
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, "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)
|
|
{
|
|
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
|
|
if (!strcmp(cepp->name, "except"))
|
|
{
|
|
CheckDuplicate(cepp, spamfilter_except, "spamfilter::except");
|
|
} 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"))
|
|
{
|
|
} else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::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, "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, "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
|
|
{
|
|
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], boundmsg_ipv6[512];
|
|
int last_errno = 0;
|
|
|
|
*boundmsg_ipv4 = *boundmsg_ipv6 = '\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)
|
|
{
|
|
unreal_log(ULOG_INFO, "listen", "LISTEN_ADDED", NULL,
|
|
"UnrealIRCd is now also listening on $listen_ip:$listen_port",
|
|
log_data_string("listen_ip", listener->ip),
|
|
log_data_integer("listen_port", listener->port));
|
|
} 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;
|
|
// TODO: show unix domain sockets ;)
|
|
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 (!*boundmsg_ipv4)
|
|
strlcpy(boundmsg_ipv4, "<none>", sizeof(boundmsg_ipv4));
|
|
if (!*boundmsg_ipv6)
|
|
strlcpy(boundmsg_ipv6, "<none>", sizeof(boundmsg_ipv6));
|
|
|
|
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",
|
|
log_data_string("ipv4_port_list", boundmsg_ipv4),
|
|
log_data_string("ipv6_port_list", boundmsg_ipv6));
|
|
}
|
|
}
|
|
|
|
/* Actually use configuration */
|
|
void config_run(void)
|
|
{
|
|
loop.config_status = CONFIG_STATUS_POSTLOAD;
|
|
extcmodes_check_for_changes();
|
|
start_listeners();
|
|
if (!loop.booted)
|
|
add_proc_io_server();
|
|
free_all_config_resources();
|
|
}
|
|
|
|
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;
|
|
}
|
|
|
|
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")) {
|
|
if (!strcmp(cepp->value, "services"))
|
|
format->type = ALIAS_SERVICES;
|
|
else if (!strcmp(cepp->value, "stats"))
|
|
format->type = ALIAS_STATS;
|
|
else if (!strcmp(cepp->value, "normal"))
|
|
format->type = ALIAS_NORMAL;
|
|
else if (!strcmp(cepp->value, "channel"))
|
|
format->type = ALIAS_CHANNEL;
|
|
else if (!strcmp(cepp->value, "real"))
|
|
format->type = ALIAS_REAL;
|
|
}
|
|
}
|
|
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")) {
|
|
if (!strcmp(cep->value, "services"))
|
|
alias->type = ALIAS_SERVICES;
|
|
else if (!strcmp(cep->value, "stats"))
|
|
alias->type = ALIAS_STATS;
|
|
else if (!strcmp(cep->value, "normal"))
|
|
alias->type = ALIAS_NORMAL;
|
|
else if (!strcmp(cep->value, "channel"))
|
|
alias->type = ALIAS_CHANNEL;
|
|
else if (!strcmp(cep->value, "command"))
|
|
alias->type = ALIAS_COMMAND;
|
|
}
|
|
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;
|
|
char 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 (!strcmp(cepp->value, "services"))
|
|
;
|
|
else if (!strcmp(cepp->value, "stats"))
|
|
;
|
|
else if (!strcmp(cepp->value, "normal"))
|
|
;
|
|
else if (!strcmp(cepp->value, "channel"))
|
|
;
|
|
else if (!strcmp(cepp->value, "real"))
|
|
;
|
|
else
|
|
{
|
|
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;
|
|
if (!strcmp(cep->value, "services"))
|
|
;
|
|
else if (!strcmp(cep->value, "stats"))
|
|
;
|
|
else if (!strcmp(cep->value, "normal"))
|
|
;
|
|
else if (!strcmp(cep->value, "channel"))
|
|
;
|
|
else if (!strcmp(cep->value, "command"))
|
|
type = 'c';
|
|
else {
|
|
config_error("%s:%i: unknown alias type",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "spamfilter"))
|
|
;
|
|
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 == 'c')
|
|
{
|
|
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 != 'c')
|
|
{
|
|
config_error("%s:%d: alias::format specified when type is not 'command'",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
}
|
|
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;
|
|
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(const char *url, const char *file, const char *errorbuf, int cached, void *rs_key)
|
|
{
|
|
ConfigResource *rs = (ConfigResource *)rs_key;
|
|
|
|
rs->type &= ~RESOURCE_DLQUEUED;
|
|
|
|
if (config_verbose)
|
|
config_status("resource_download_complete() for %s [%s]", url, errorbuf?errorbuf:"success");
|
|
|
|
if (!file && !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(url)),
|
|
log_data_string("error_message", errorbuf));
|
|
safe_strdup(rs->file, rs->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(url)),
|
|
log_data_string("error_message", 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 (cached)
|
|
{
|
|
/* Copy from cache */
|
|
safe_strdup(rs->file, rs->cache_file);
|
|
} else {
|
|
/* Copy to cache */
|
|
const char *cache_file = unreal_mkcache(url);
|
|
unreal_copyfileex(file, cache_file, 1);
|
|
safe_strdup(rs->file, cache_file);
|
|
}
|
|
}
|
|
|
|
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 */
|
|
|
|
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_moddata(); -- this will crash
|
|
clicap_check_for_changes();
|
|
umodes_check_for_changes();
|
|
charsys_check_for_changes();
|
|
|
|
/* 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);
|
|
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 listen_cleanup()
|
|
{
|
|
int i = 0;
|
|
ConfigItem_listen *listen_ptr, *next;
|
|
|
|
for (listen_ptr = conf_listen; listen_ptr; listen_ptr = next)
|
|
{
|
|
next = listen_ptr->next;
|
|
if (listen_ptr->flag.temporary && !listen_ptr->clients)
|
|
{
|
|
safe_free(listen_ptr->ip);
|
|
free_tls_options(listen_ptr->tls_options);
|
|
DelListItem(listen_ptr, conf_listen);
|
|
safe_free(listen_ptr->webserver);
|
|
safe_free(listen_ptr->websocket_forward);
|
|
safe_free(listen_ptr);
|
|
i++;
|
|
}
|
|
}
|
|
|
|
if (i)
|
|
close_unbound_listeners();
|
|
}
|
|
|
|
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;
|
|
|
|
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);
|
|
|
|
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)
|
|
{
|
|
safe_strdup(rs->cache_file, cache_file); /* Cached copy is available */
|
|
/* Check if there is an "url-refresh" argument */
|
|
ConfigEntry *cep, *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 */
|
|
//config_status("DEBUG: using cached copy due to url-refresh %ld", refresh_time);
|
|
resource_download_complete(rs->url, NULL, NULL, 1, 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;
|
|
}
|
|
}
|
|
download_file_async(rs->url, modtime, resource_download_complete, (void *)rs, NULL, 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);
|
|
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, "*"))
|
|
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);
|
|
}
|