mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-07-02 07:26:38 +02:00
d2ccba80c5
The LoadPersistent*()/SavePersistent*() functions caused moddata to be tagged with ->unloaded=1. Though it seems it caused no real issues this is not good... we now properly tag them as 0 and the like. Also did a code cleanup / overhaul on that system as well. For other ModData we now handle the case where a module is loaded with with a newer version and that newer version is no longer having certain moddata, eg the name changed or it no longer needs it. KNOWN ISSUE: Unfortunately we cannot call the free function for the old moddata that is no longer being handled by the newer version of the module, since the module is already unloaded. So this will result in a memory leak, but not in a crash. KNOWN ISSUE: Similarly, for SavePersistentPointer() there is a free function, again this is called just fine if the module is permanently unloaded but NOT if the module is reloaded with the same name and no longer is interested in the persistent pointer object. Again, here too, that would result in a memory leak but not in a crash. Fortunately the "known issues" are rare. Fixing these is impossible with the current module API because modules are unloaded after MOD_TEST and before MOD_INIT, and only after MOD_INIT we know which moddata is handled by the new version of the module. To change that we would need to keep the old module around until after MOD_INIT of the new module (so we can call free functions in the old module), but that means delaying the MOD_UNLOAD for the old modules until after MOD_INIT of the new modules, which changes the sequence too much that i don't dare to do that. For example, it would mean a database save routine in the old module would only be called after MOD_INIT finished in the new module, which may be unexpected since right now MOD_UNLOAD is called before MOD_INIT and maybe the db loading is done in MOD_INIT, which would need to be moved to MOD_LOAD. That's just one example, there may be others. I think such a change can only be done on a major UnrealIRCd version change, so we will have to live this for now. As said, fortunately it is a corner case.
11970 lines
317 KiB
C
11970 lines
317 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);
|
|
static int _conf_proxy (ConfigFile *conf, ConfigEntry *ce);
|
|
|
|
/*
|
|
* Validation commands
|
|
*/
|
|
|
|
static int _test_admin (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_me (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_files (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_oper (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_operclass (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_class (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_drpass (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_ulines (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_include (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_tld (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_listen (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_allow (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_except (ConfigFile *conf, ConfigEntry *ce);
|
|
static int _test_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);
|
|
static int _test_proxy (ConfigFile *conf, ConfigEntry *ce);
|
|
|
|
/* This MUST be alphabetized */
|
|
static ConfigCommand _ConfigCommands[] = {
|
|
{ "admin", _conf_admin, _test_admin },
|
|
{ "alias", _conf_alias, _test_alias },
|
|
{ "allow", _conf_allow, _test_allow },
|
|
{ "ban", _conf_ban, _test_ban },
|
|
{ "blacklist-module", NULL, NULL },
|
|
{ "class", _conf_class, _test_class },
|
|
{ "deny", _conf_deny, _test_deny },
|
|
{ "drpass", _conf_drpass, _test_drpass },
|
|
{ "except", _conf_except, _test_except },
|
|
{ "files", _conf_files, _test_files },
|
|
{ "help", _conf_help, _test_help },
|
|
{ "include", NULL, _test_include },
|
|
{ "link", _conf_link, _test_link },
|
|
{ "listen", _conf_listen, _test_listen },
|
|
{ "loadmodule", NULL, _test_loadmodule},
|
|
{ "log", config_run_log, config_test_log },
|
|
{ "me", _conf_me, _test_me },
|
|
{ "official-channels", _conf_offchans, _test_offchans },
|
|
{ "oper", _conf_oper, _test_oper },
|
|
{ "operclass", _conf_operclass, _test_operclass },
|
|
{ "proxy", _conf_proxy, _test_proxy },
|
|
{ "require", _conf_require, _test_require },
|
|
{ "secret", _conf_secret, _test_secret },
|
|
{ "security-group", _conf_security_group, _test_security_group },
|
|
{ "set", _conf_set, _test_set },
|
|
{ "sni", _conf_sni, _test_sni },
|
|
{ "tld", _conf_tld, _test_tld },
|
|
{ "ulines", _conf_ulines, _test_ulines },
|
|
{ "vhost", _conf_vhost, _test_vhost },
|
|
{ "webirc", _conf_proxy, _test_proxy },
|
|
};
|
|
|
|
/* This MUST be alphabetized */
|
|
static NameValue _ListenerFlags[] = {
|
|
{ LISTENER_CLIENTSONLY, "clientsonly"},
|
|
{ LISTENER_DEFER_ACCEPT, "defer-accept"},
|
|
{ LISTENER_SERVERSONLY, "serversonly"},
|
|
{ LISTENER_TLS, "ssl"},
|
|
{ LISTENER_NORMAL, "standard"},
|
|
{ LISTENER_TLS, "tls"},
|
|
};
|
|
|
|
/* This MUST be alphabetized */
|
|
static NameValue _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;
|
|
ConfigItem_proxy *conf_proxy = NULL;
|
|
|
|
Secret *secrets = NULL;
|
|
|
|
Configuration iConf;
|
|
Configuration tempiConf;
|
|
BestPractices bestpractices;
|
|
MODVAR ConfigFile *conf = NULL;
|
|
extern NameValueList *config_defines;
|
|
MODVAR int ipv6_disabled = 0;
|
|
MODVAR Client *remote_rehash_client = NULL;
|
|
MODVAR json_t *json_rehash_log = NULL;
|
|
MODVAR DynamicSetBlock unknown_users_set;
|
|
MODVAR DynamicSetBlock dynamic_set;
|
|
|
|
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;
|
|
|
|
long long central_spamfilter_last_download = 0;
|
|
|
|
int add_config_resource(const char *resource, int type, ConfigEntry *ce);
|
|
ConfigResource *find_config_resource(const char *resource);
|
|
void resource_download_complete(const char *url, const char *file, const char *memory, int memory_len, 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);
|
|
int modules_default_conf_modified(const char *filebuf);
|
|
int config_item_allowed_for_config_file(const char *resource, const char *item);
|
|
void remove_config_tkls(int flag);
|
|
|
|
/** 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 */
|
|
}
|
|
|
|
/** Convert a set::spamfilter::show-message-content-on-hit value */
|
|
SpamfilterShowMessageContentOnHit spamfilter_show_message_content_on_hit_strtoval(const char *s)
|
|
{
|
|
if (!strcmp(s, "always"))
|
|
return SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_ALWAYS;
|
|
if (!strcmp(s, "channel-only"))
|
|
return SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_CHANNEL_ONLY;
|
|
if (!strcmp(s, "never"))
|
|
return SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_NEVER;
|
|
return 0;
|
|
}
|
|
|
|
/* 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;
|
|
}
|
|
buf[ret] = '\0';
|
|
close(fd);
|
|
|
|
if (str_ends_with_case_insensitive(filename, "/modules.default.conf") &&
|
|
modules_default_conf_modified(buf))
|
|
{
|
|
safe_free(buf);
|
|
return NULL;
|
|
}
|
|
add_entropy_configfile(&sb, buf);
|
|
cfptr = config_parse(displayname, buf);
|
|
safe_free(buf);
|
|
return cfptr;
|
|
}
|
|
|
|
void config_free(ConfigFile *cfptr)
|
|
{
|
|
ConfigFile *nptr;
|
|
|
|
for(;cfptr;cfptr=nptr)
|
|
{
|
|
nptr = cfptr->next;
|
|
if (cfptr->items)
|
|
config_entry_free_all(cfptr->items);
|
|
safe_free(cfptr->filename);
|
|
safe_free(cfptr);
|
|
}
|
|
}
|
|
|
|
/** Remove quotes so that 'hello \"all\" \\ lala' becomes 'hello "all" \ lala' */
|
|
void unreal_del_quotes(char *i)
|
|
{
|
|
char *o;
|
|
|
|
for (o = i; *i; i++)
|
|
{
|
|
if (*i == '\\')
|
|
{
|
|
if ((i[1] == '\\') || (i[1] == '"'))
|
|
{
|
|
i++; /* skip original \ */
|
|
if (*i == '\0')
|
|
break;
|
|
}
|
|
}
|
|
*o++ = *i;
|
|
}
|
|
*o = '\0';
|
|
}
|
|
|
|
/** Add quotes to a line, eg some"thing becomes some\"thing - extended version */
|
|
int unreal_add_quotes_r(const char *i, char *o, size_t len)
|
|
{
|
|
if (len == 0)
|
|
return 0;
|
|
|
|
len--; /* reserve room for nul byte */
|
|
|
|
if (len == 0)
|
|
{
|
|
*o = '\0';
|
|
return 0;
|
|
}
|
|
|
|
for (; *i; i++)
|
|
{
|
|
if ((*i == '"') || (*i == '\\')) /* only " and \ need to be quoted */
|
|
{
|
|
if (len < 2)
|
|
break;
|
|
*o++ = '\\';
|
|
*o++ = *i;
|
|
len -= 2;
|
|
} else
|
|
{
|
|
if (len == 0)
|
|
break;
|
|
*o++ = *i;
|
|
len--;
|
|
}
|
|
}
|
|
*o = '\0';
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Add quotes to a line, eg some"thing becomes some\"thing */
|
|
const char *unreal_add_quotes(const char *str)
|
|
{
|
|
static char qbuf[2048];
|
|
|
|
*qbuf = '\0';
|
|
unreal_add_quotes_r(str, qbuf, sizeof(qbuf));
|
|
return qbuf;
|
|
}
|
|
|
|
ConfigFile *config_parse(const char *filename, char *confdata)
|
|
{
|
|
return config_parse_with_offset(filename, confdata, 0);
|
|
}
|
|
|
|
/* This is the internal parser, made by Chris Behrens & Fred Jacobs <2005.
|
|
* Enhanced (or mutilated) by Bram Matthys over the years (2015-2019).
|
|
*/
|
|
ConfigFile *config_parse_with_offset(const char *filename, char *confdata, unsigned int line_offset)
|
|
{
|
|
char *ptr;
|
|
char *start;
|
|
int linenumber = 1+line_offset;
|
|
int errors = 0;
|
|
int n;
|
|
ConfigEntry *curce;
|
|
ConfigEntry **lastce;
|
|
ConfigEntry *cursection;
|
|
ConfigFile *curcf;
|
|
int preprocessor_level = 0;
|
|
ConditionalConfig *cc, *cc_list = NULL;
|
|
|
|
curcf = safe_alloc(sizeof(ConfigFile));
|
|
safe_strdup(curcf->filename, filename);
|
|
lastce = &(curcf->items);
|
|
curce = NULL;
|
|
cursection = NULL;
|
|
/* Replace \r's with spaces .. ugly ugly -Stskeeps */
|
|
for (ptr=confdata; *ptr; ptr++)
|
|
if (*ptr == '\r')
|
|
*ptr = ' ';
|
|
|
|
for(ptr=confdata;*ptr;ptr++)
|
|
{
|
|
switch(*ptr)
|
|
{
|
|
case ';':
|
|
if (!curce)
|
|
{
|
|
config_status("%s:%i Ignoring extra semicolon\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;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
/* Something like /tmp/xyz */
|
|
goto processchar;
|
|
}
|
|
break;
|
|
case '\'':
|
|
if (curce)
|
|
curce->escaped = 1;
|
|
/* fallthrough */
|
|
case '\"':
|
|
if (curce && curce->line_number != linenumber && cursection)
|
|
{
|
|
config_error("%s:%i: Missing semicolon (';') at end of line. "
|
|
"Line %d must end with a ; character\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:
|
|
processchar:
|
|
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;
|
|
}
|
|
|
|
/** Free set::best-practices after reading config */
|
|
void free_best_practices(void)
|
|
{
|
|
memset(&bestpractices, 0, sizeof(bestpractices));
|
|
}
|
|
|
|
/** Initialize and set default values for set::best-practices */
|
|
void init_best_practices(void)
|
|
{
|
|
memset(&bestpractices, 0, sizeof(bestpractices));
|
|
bestpractices.hashed_passwords = 1;
|
|
}
|
|
|
|
void free_iConf(Configuration *i)
|
|
{
|
|
FloodSettings *f, *f_next;
|
|
|
|
safe_free(i->link_bindip);
|
|
safe_free(i->kline_address);
|
|
safe_free(i->gline_address);
|
|
safe_free(i->oper_snomask);
|
|
safe_free(i->oper_auto_join_chans);
|
|
safe_free(i->allow_user_stats);
|
|
// allow_user_stats_ext is freed elsewhere
|
|
free_tls_options(i->tls_options);
|
|
i->tls_options = NULL;
|
|
safe_free(i->tls_options);
|
|
safe_free_multiline(i->plaintext_policy_user_message);
|
|
safe_free_multiline(i->plaintext_policy_oper_message);
|
|
safe_free(i->outdated_tls_policy_user_message);
|
|
safe_free(i->outdated_tls_policy_oper_message);
|
|
safe_free(i->restrict_channelmodes);
|
|
safe_free(i->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);
|
|
safe_free_all_ban_actions(i->handshake_data_flood_ban_action);
|
|
safe_free(i->central_spamfilter_url);
|
|
safe_free(i->central_spamfilter_feed);
|
|
free_security_group(i->central_spamfilter_except);
|
|
// anti-flood:
|
|
for (f = i->floodsettings; f; f = f_next)
|
|
{
|
|
f_next = f->next;
|
|
free_floodsettings(f);
|
|
}
|
|
i->floodsettings = NULL;
|
|
}
|
|
|
|
/** Set default set { } block settings. Note that some of these settings
|
|
* have been moved to config_setdynamicdefaultsettings() if they use the
|
|
* new dynamic set system which has per-security-group overrides (eg.
|
|
* max-channels-per-user).
|
|
*/
|
|
void config_setdefaultsettings(Configuration *i)
|
|
{
|
|
char tmp[512];
|
|
|
|
safe_strdup(i->oper_snomask, OPER_SNOMASKS);
|
|
i->server_notice_colors = 1;
|
|
i->server_notice_show_event = 1;
|
|
i->ident_read_timeout = 7;
|
|
i->ident_connect_timeout = 3;
|
|
i->ban_version_tkl_time = 86400; /* 1d */
|
|
i->spamfilter_ban_time = 86400; /* 1d */
|
|
i->spamfilter_utf8 = 1;
|
|
safe_strdup(i->spamfilter_ban_reason, "Spam/advertising");
|
|
safe_strdup(i->spamfilter_virus_help_channel, "#help");
|
|
i->spamfilter_detectslow_warn = 250;
|
|
i->spamfilter_detectslow_fatal = 500;
|
|
i->spamfilter_stop_on_first_match = 0;
|
|
i->spamfilter_show_message_content_on_hit = SPAMFILTER_SHOW_MESSAGE_CONTENT_ON_HIT_ALWAYS;
|
|
i->maxdccallow = 10;
|
|
safe_strdup(i->channel_command_prefix, "`!.");
|
|
i->check_target_nick_bans = 1;
|
|
i->maxbans = 60;
|
|
safe_strdup(i->level_on_join, "o");
|
|
i->watch_away_notification = 1;
|
|
i->uhnames = 1;
|
|
i->ping_cookie = 1;
|
|
i->ping_warning = 15; /* default ping warning notices 15 seconds */
|
|
i->default_ipv6_clone_mask = 64;
|
|
nicklengths.min = i->min_nick_length = 0; /* 0 means no minimum required */
|
|
nicklengths.max = i->nick_length = NICKLEN;
|
|
i->topic_length = 360;
|
|
i->away_length = 307;
|
|
i->kick_length = 307;
|
|
i->quit_length = 307;
|
|
safe_strdup(i->link_bindip, "*");
|
|
safe_strdup(i->cloak_prefix, "Clk");
|
|
if (!ipv6_capable())
|
|
DISABLE_IPV6 = 1;
|
|
safe_strdup(i->prefix_quit, "Quit");
|
|
i->max_unknown_connections_per_ip = 3;
|
|
i->handshake_timeout = 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 = safe_alloc(sizeof(BanAction));
|
|
i->handshake_data_flood_ban_action->action = BAN_ACT_ZLINE;
|
|
i->handshake_data_flood_ban_time = 600;
|
|
// (targetflood is in the targetflood module)
|
|
/* - known-users */
|
|
config_parse_flood_generic("3:60", i, "known-users", FLD_NICK); /* NICK flood protection: max 3 per 60s */
|
|
config_parse_flood_generic("3:90", i, "known-users", FLD_JOIN); /* JOIN flood protection: max 3 per 90s */
|
|
config_parse_flood_generic("3:90", i, "known-users", FLD_VHOST); /* MODE -x flood protection: max 3 per 90s */
|
|
config_parse_flood_generic("4:120", i, "known-users", FLD_AWAY); /* AWAY flood protection: max 4 per 120s */
|
|
config_parse_flood_generic("4:60", i, "known-users", FLD_INVITE); /* INVITE flood protection: max 4 per 60s */
|
|
config_parse_flood_generic("4:120", i, "known-users", FLD_KNOCK); /* KNOCK protection: max 4 per 120s */
|
|
config_parse_flood_generic("10:15", i, "known-users", FLD_CONVERSATIONS); /* 10 users, new user every 15s */
|
|
config_parse_flood_generic("180:750", i, "known-users", FLD_LAG_PENALTY); /* 180 bytes / 750 msec */
|
|
/* - 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_USER_HOST;
|
|
i->ban_setter = SETTER_NICK_USER_HOST;
|
|
i->ban_setter_sync = 1;
|
|
i->allowed_channelchars = ALLOWED_CHANNELCHARS_UTF8;
|
|
i->automatic_ban_target = BAN_TARGET_IP;
|
|
i->manual_ban_target = BAN_TARGET_HOST;
|
|
i->hide_idle_time = HIDE_IDLE_TIME_OPER_USERMODE;
|
|
i->who_limit = 100;
|
|
i->named_extended_bans = 1;
|
|
i->high_connection_rate = 1000;
|
|
safe_strdup(i->central_spamfilter_url, "https://spamfilter.unrealircd.org/spamfilter/v6/$feed/central_spamfilter.conf");
|
|
safe_strdup(i->central_spamfilter_feed, "standard");
|
|
i->central_spamfilter_refresh_time = 3600;
|
|
i->central_spamfilter_enabled = 0;
|
|
i->central_spamfilter_except = safe_alloc(sizeof(SecurityGroup));
|
|
i->central_spamfilter_except->reputation_score = 2016; /* 7 days unregged, or 3.5 days identified */
|
|
unreal_add_mask_string(&i->central_spamfilter_except->mask, "*.irccloud.com");
|
|
i->dns_client_timeout = DNS_DEFAULT_CLIENT_TIMEOUT;
|
|
i->dns_client_retry = DNS_DEFAULT_CLIENT_RETRIES;
|
|
i->dns_dnsbl_timeout = DNS_DEFAULT_DNSBL_TIMEOUT;
|
|
i->dns_dnsbl_retry = DNS_DEFAULT_DNSBL_RETRIES;
|
|
}
|
|
|
|
/* Some settings have been moved to here - we (re)set some defaults */
|
|
void config_setdynamicdefaultsettings()
|
|
{
|
|
DynamicSetBlock *s;
|
|
/* Free/reset stuff first: */
|
|
free_dynamic_set_block(&dynamic_set);
|
|
free_dynamic_set_block(&unknown_users_set);
|
|
s = &dynamic_set;
|
|
/* Set defaults: */
|
|
dynamic_set_number(s, SET_MAX_CHANNELS_PER_USER, 10);
|
|
dynamic_set_number(s, SET_MODES_ON_CONNECT, set_usermode("+ixw"));
|
|
}
|
|
|
|
/** Similar to config_setdefaultsettings but this one is applied *AFTER*
|
|
* the entire configuration has been ran (sometimes this is the only way it can be done..).
|
|
* NOTE: iConf is thus already populated with (non-default) values. Only overwrite if necessary!
|
|
*/
|
|
void postconf_defaults(void)
|
|
{
|
|
TKL *tk;
|
|
char *encoded;
|
|
FloodSettings *fld;
|
|
|
|
if (!iConf.modes_on_join_set)
|
|
{
|
|
/* We could not do this in config_setdefaultsettings()
|
|
* because the channel mode modules were not initialized yet.
|
|
*/
|
|
conf_channelmodes("+nt", &iConf.modes_on_join);
|
|
}
|
|
if (!iConf.plaintext_policy_user_message)
|
|
{
|
|
/* The message depends on whether it's reject or warn.. */
|
|
if (iConf.plaintext_policy_user == POLICY_DENY)
|
|
addmultiline(&iConf.plaintext_policy_user_message, "Insecure connection. Please reconnect using TLS.");
|
|
else if (iConf.plaintext_policy_user == POLICY_WARN)
|
|
addmultiline(&iConf.plaintext_policy_user_message, "WARNING: Insecure connection. Please consider using TLS.");
|
|
}
|
|
|
|
if (!iConf.plaintext_policy_oper_message)
|
|
{
|
|
/* The message depends on whether it's reject or warn.. */
|
|
if (iConf.plaintext_policy_oper == POLICY_DENY)
|
|
{
|
|
addmultiline(&iConf.plaintext_policy_oper_message, "You need to use a secure connection (TLS) in order to /OPER.");
|
|
addmultiline(&iConf.plaintext_policy_oper_message, "See https://www.unrealircd.org/docs/FAQ#oper-requires-tls");
|
|
}
|
|
else if (iConf.plaintext_policy_oper == POLICY_WARN)
|
|
addmultiline(&iConf.plaintext_policy_oper_message, "WARNING: You /OPER'ed up from an insecure connection. Please consider using TLS.");
|
|
}
|
|
|
|
if (!iConf.outdated_tls_policy_user_message)
|
|
{
|
|
/* The message depends on whether it's reject or warn.. */
|
|
if (iConf.outdated_tls_policy_user == POLICY_DENY)
|
|
safe_strdup(iConf.outdated_tls_policy_user_message, "Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
|
|
else if (iConf.outdated_tls_policy_user == POLICY_WARN)
|
|
safe_strdup(iConf.outdated_tls_policy_user_message, "WARNING: Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
|
|
}
|
|
|
|
if (!iConf.outdated_tls_policy_oper_message)
|
|
{
|
|
/* The message depends on whether it's reject or warn.. */
|
|
if (iConf.outdated_tls_policy_oper == POLICY_DENY)
|
|
safe_strdup(iConf.outdated_tls_policy_oper_message, "Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
|
|
else if (iConf.outdated_tls_policy_oper == POLICY_WARN)
|
|
safe_strdup(iConf.outdated_tls_policy_oper_message, "WARNING: Your IRC client is using an outdated TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client.");
|
|
}
|
|
|
|
postconf_defaults_log_block();
|
|
}
|
|
|
|
/* Needed for set::options::allow-part-if-shunned,
|
|
* we can't just make it CMD_SHUN and do a ALLOW_PART_IF_SHUNNED in
|
|
* cmd_part itself because that will also block internal calls (like sapart). -- Syzop
|
|
*/
|
|
static void do_weird_shun_stuff()
|
|
{
|
|
RealCommand *cmptr;
|
|
|
|
if ((cmptr = find_command_simple("PART")))
|
|
{
|
|
if (ALLOW_PART_IF_SHUNNED)
|
|
cmptr->flags |= CMD_SHUN;
|
|
else
|
|
cmptr->flags &= ~CMD_SHUN;
|
|
}
|
|
}
|
|
|
|
/** Various things that are done at the very end after the configuration file
|
|
* has been read and almost all values have been set. This is to deal with
|
|
* things like adding a default log { } block if there is none and that kind
|
|
* of things.
|
|
* This function is called by config_test(), both on boot and on rehash.
|
|
*/
|
|
void postconf(void)
|
|
{
|
|
postconf_defaults();
|
|
do_weird_shun_stuff();
|
|
isupport_init(); /* for all the 005 values that changed.. */
|
|
tls_check_expiry(NULL);
|
|
|
|
#if OPENSSL_VERSION_NUMBER >= 0x10101000L
|
|
if (loop.rehashing)
|
|
reinit_tls();
|
|
#endif
|
|
}
|
|
|
|
int isanyserverlinked(void)
|
|
{
|
|
return !list_empty(&server_list);
|
|
}
|
|
|
|
void applymeblock(void)
|
|
{
|
|
if (!conf_me)
|
|
return; /* uh-huh? */
|
|
|
|
/* Info text may always change, just wouldn't show up on other servers, that's all.. */
|
|
strlcpy(me.info, conf_me->info, sizeof(me.info));
|
|
|
|
/* Name can only be set once (on boot) */
|
|
if (!*me.name)
|
|
strlcpy(me.name, conf_me->name, sizeof(me.name));
|
|
else if (strcmp(me.name, conf_me->name))
|
|
{
|
|
config_warn("You changed the servername (me::name). "
|
|
"This change will NOT be effective unless you restart the IRC Server.");
|
|
}
|
|
|
|
if (!*me.id)
|
|
strlcpy(me.id, conf_me->sid, sizeof(me.id));
|
|
}
|
|
|
|
/** Run config test and all post config tests. */
|
|
int config_test_all(void)
|
|
{
|
|
if ((efunctions_check() < 0) || (config_test_blocks() < 0) || (callbacks_check() < 0) ||
|
|
reloadable_perm_module_unloaded() || !tls_tests() || !log_tests())
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
special_delayed_unloading();
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Process all loadmodule directives in all includes.
|
|
* This was previously done at the same time as 'include' was called but
|
|
* that was too early now that we have blacklist-module, so moved here.
|
|
* @retval 1 on success, 0 on any failed loadmodule directive.
|
|
*/
|
|
int config_loadmodules(void)
|
|
{
|
|
ConfigFile *cfptr;
|
|
ConfigEntry *ce;
|
|
ConfigItem_blacklist_module *blm, *blm_next;
|
|
|
|
int fatal_ret = 0, ret;
|
|
|
|
for (cfptr = conf; cfptr; cfptr = cfptr->next)
|
|
{
|
|
if (config_verbose > 1)
|
|
config_status("Testing %s", cfptr->filename);
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
if (!strcmp(ce->name, "loadmodule"))
|
|
{
|
|
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;
|
|
loop.rehashing = 0;
|
|
Unload_all_testing_modules();
|
|
free_all_config_resources();
|
|
config_free(conf);
|
|
conf = NULL;
|
|
free_iConf(&tempiConf);
|
|
free_best_practices();
|
|
#ifdef _WIN32
|
|
if (!loop.rehashing)
|
|
win_error(); /* GUI popup */
|
|
#endif
|
|
}
|
|
|
|
int config_read_start(void)
|
|
{
|
|
int ret;
|
|
|
|
config_status("Loading IRCd configuration..");
|
|
loop.config_load_failed = 0;
|
|
|
|
if (conf)
|
|
{
|
|
config_error("%s:%i - Someone forgot to clean up", __FILE__, __LINE__);
|
|
return -1;
|
|
}
|
|
|
|
/* We set this to 1 because otherwise we may call rehash_internal()
|
|
* already from config_read_file() which is too soon (race).
|
|
*/
|
|
loop.rehash_download_busy = 1;
|
|
add_config_resource(configfile, RESOURCE_INCLUDE, NULL);
|
|
ret = config_read_file(configfile, configfile);
|
|
loop.rehash_download_busy = 0;
|
|
if (ret < 0)
|
|
{
|
|
config_load_failed();
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int is_config_read_finished(void)
|
|
{
|
|
ConfigResource *rs;
|
|
|
|
if (loop.rehash_download_busy)
|
|
return 0;
|
|
|
|
for (rs = config_resources; rs; rs = rs->next)
|
|
{
|
|
if (rs->type & RESOURCE_DLQUEUED)
|
|
{
|
|
//config_status("Waiting for %s...", rs->url);
|
|
return 0;
|
|
}
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int config_test(void)
|
|
{
|
|
char *old_pid_file = NULL;
|
|
|
|
if (loop.config_load_failed)
|
|
{
|
|
/* An error was already printed to the user.
|
|
* This happens in case of a failed loaded remote URL
|
|
*/
|
|
config_load_failed();
|
|
return -1;
|
|
}
|
|
|
|
config_status("Testing IRCd configuration..");
|
|
loop.config_status = CONFIG_STATUS_TEST;
|
|
|
|
memset(&tempiConf, 0, sizeof(iConf));
|
|
init_best_practices();
|
|
memset(&settings, 0, sizeof(settings));
|
|
memset(&requiredstuff, 0, sizeof(requiredstuff));
|
|
memset(&nicklengths, 0, sizeof(nicklengths));
|
|
config_setdefaultsettings(&tempiConf);
|
|
clicap_pre_rehash();
|
|
log_pre_rehash();
|
|
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;
|
|
config_setdynamicdefaultsettings();
|
|
if (config_run_blocks() < 0)
|
|
{
|
|
config_error("Bad case of config errors. Server will now die. This really shouldn't happen");
|
|
#ifdef _WIN32
|
|
if (!loop.rehashing)
|
|
win_error();
|
|
#endif
|
|
abort();
|
|
}
|
|
|
|
applymeblock();
|
|
|
|
if (old_pid_file && strcmp(old_pid_file, conf_files->pid_file))
|
|
{
|
|
write_pidfile();
|
|
unlink(old_pid_file);
|
|
}
|
|
safe_free(old_pid_file);
|
|
|
|
config_free(conf);
|
|
conf = NULL;
|
|
if (loop.rehashing)
|
|
{
|
|
/* loop.config_status = CONFIG_STATUS_LOAD is done by module_loadall() */
|
|
module_loadall();
|
|
RunHook(HOOKTYPE_REHASH_COMPLETE);
|
|
}
|
|
loop.config_status = CONFIG_STATUS_POSTLOAD;
|
|
postconf();
|
|
unreal_log(ULOG_INFO, "config", "CONFIG_LOADED", NULL, "Configuration loaded");
|
|
if (bestpractices.hashed_passwords_hits /* || .... || .... */ )
|
|
{
|
|
unreal_log(ULOG_INFO, "config", "BEST_PRACTICES", NULL,
|
|
"Your config has NO errors, but you received some best practices tips above, in summary:");
|
|
if (bestpractices.hashed_passwords_hits)
|
|
{
|
|
unreal_log(ULOG_INFO, "config", "BEST_PRACTICES_HASHED_PASSWORDS_INFO", NULL,
|
|
"* Use hashed passwords, see https://www.unrealircd.org/docs/Authentication_types "
|
|
"to learn more about this.");
|
|
}
|
|
unreal_log(ULOG_INFO, "config", "BEST_PRACTICES_POST_INFO", NULL,
|
|
"It is recommended you follow best practices, but if you want to hide "
|
|
"such suggestions see "
|
|
"https://www.unrealircd.org/docs/Set_block#set::best-practices");
|
|
}
|
|
unload_all_unused_mtag_handlers();
|
|
free_best_practices();
|
|
return 0;
|
|
}
|
|
|
|
void config_parse_and_queue_urls(ConfigEntry *ce)
|
|
{
|
|
for (; ce; ce = ce->next)
|
|
{
|
|
if (loop.config_load_failed)
|
|
break;
|
|
if (ce->name && !strcmp(ce->name, "include"))
|
|
continue; /* handled elsewhere */
|
|
if (ce->value && !ce->escaped && url_is_valid(ce->value))
|
|
add_config_resource(ce->value, 0, ce);
|
|
if (ce->items)
|
|
config_parse_and_queue_urls(ce->items);
|
|
}
|
|
}
|
|
|
|
/**
|
|
* Read configuration file into ConfigEntry items and add it to the 'conf'
|
|
* list. This checks the file for parse errors, but doesn't do much
|
|
* otherwise. Only: module blacklist checking and checking for "include"
|
|
* items to see if we need to read and parse more configuration files
|
|
* that are included from this one.
|
|
*
|
|
* One _must_ call add_config_resource() before calling config_read_file().
|
|
* This way, include recursion may be detected and reported to the user
|
|
* as an error instead of causing the IRCd to hang in an infinite
|
|
* recursion, eat up memory, and eventually overflow its stack ;-).
|
|
*
|
|
* @param filename the file where the conf may be read from
|
|
* @param display_name The path or URL used to refer to this file.
|
|
* (mostly to support remote includes' URIs for recursive include detection).
|
|
* @return 1 on success, a negative number on error
|
|
*/
|
|
int config_read_file(const char *filename, const char *display_name)
|
|
{
|
|
ConfigFile *cfptr, *cfptr2, **cfptr3;
|
|
ConfigEntry *ce;
|
|
ConfigResource *rs;
|
|
int ret;
|
|
int counter;
|
|
|
|
if (config_verbose > 0)
|
|
config_status("Loading config file %s ..", filename);
|
|
|
|
need_operclass_permissions_upgrade = 0;
|
|
|
|
/* Check if we're accidentally including a file a second
|
|
* time. We should expect to find one entry in this list: the
|
|
* entry for our current file.
|
|
* Note that no user should be able to trigger this, this
|
|
* can only happen if we have buggy code somewhere.
|
|
*/
|
|
counter = 0;
|
|
for (rs = config_resources; rs; rs = rs->next)
|
|
{
|
|
#ifndef _WIN32
|
|
if (rs->file && !strcmp(filename, rs->file))
|
|
#else
|
|
if (rs->file && !strcasecmp(filename, rs->file))
|
|
#endif
|
|
{
|
|
counter ++;
|
|
continue;
|
|
}
|
|
if (rs->url && !strcmp(display_name, rs->url))
|
|
{
|
|
counter ++;
|
|
continue;
|
|
}
|
|
}
|
|
if (counter > 1)
|
|
{
|
|
unreal_log(ULOG_ERROR, "config", "CONFIG_BUG_DUPLICATE_RESOURCE", NULL,
|
|
"[BUG] Config file $file has been loaded $counter times. "
|
|
"This should not happen. Someone forgot to call "
|
|
"add_config_resource() or check its return value!",
|
|
log_data_string("file", filename),
|
|
log_data_integer("counter", counter));
|
|
return -1;
|
|
}
|
|
/* end include recursion checking code */
|
|
|
|
if ((cfptr = config_load(filename, display_name)))
|
|
{
|
|
for (cfptr3 = &conf, cfptr2 = conf; cfptr2; cfptr2 = cfptr2->next)
|
|
cfptr3 = &cfptr2->next;
|
|
*cfptr3 = cfptr;
|
|
|
|
if (config_verbose > 1)
|
|
config_status("Loading module blacklist in %s", filename);
|
|
|
|
preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_INITIAL);
|
|
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
if (!strcmp(ce->name, "blacklist-module"))
|
|
_test_blacklist_module(cfptr, ce);
|
|
|
|
preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_SECONDARY);
|
|
|
|
/* Load urls */
|
|
config_parse_and_queue_urls(cfptr->items);
|
|
|
|
if(loop.config_load_failed) /* something bad happened while processing urls */
|
|
return -1;
|
|
|
|
/* Load includes */
|
|
if (config_verbose > 1)
|
|
config_status("Searching through %s for include files..", filename);
|
|
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
if (!strcmp(ce->name, "include"))
|
|
{
|
|
if (ce->conditional_config)
|
|
{
|
|
config_error("%s:%d: Currently you cannot have an 'include' statement "
|
|
"within an @if block, sorry. However, you CAN do it the other "
|
|
"way around, that is: put the @if within the included file itself.",
|
|
ce->file->filename, ce->line_number);
|
|
return -1;
|
|
}
|
|
if (!config_item_allowed_for_config_file(cfptr->filename, NULL))
|
|
{
|
|
config_error("%s:%d: You cannot use 'include' from a restricted config file.",
|
|
ce->file->filename, ce->line_number);
|
|
return -1;
|
|
}
|
|
ret = _conf_include(cfptr, ce);
|
|
if (ret < 0)
|
|
return ret;
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
else
|
|
{
|
|
unreal_log(ULOG_ERROR, "config", "CONFIG_LOAD_FILE_FAILED", NULL,
|
|
"Could not load configuration file: $resource",
|
|
log_data_string("resource", display_name),
|
|
log_data_string("filename", filename));
|
|
#ifdef _WIN32
|
|
if (!strcmp(filename, "conf/unrealircd.conf"))
|
|
{
|
|
if (file_exists("unrealircd.conf"))
|
|
{
|
|
config_error("Note that 'unrealircd.conf' now belongs in the 'conf' subdirectory! (So move it to there)");
|
|
} else {
|
|
config_error("New to UnrealIRCd? Be sure to read https://www.unrealircd.org/docs/Installing_%%28Windows%%29");
|
|
}
|
|
}
|
|
#endif
|
|
return -1;
|
|
}
|
|
}
|
|
|
|
/** Remove all TKL's that were added by the config file(s).
|
|
* This is done after config passed testing and right before
|
|
* adding the (new) entries.
|
|
*/
|
|
void remove_config_tkls(int flag)
|
|
{
|
|
TKL *tk, *tk_next;
|
|
int index, index2;
|
|
|
|
/* IP hashed TKL list */
|
|
for (index = 0; index < TKLIPHASHLEN1; index++)
|
|
{
|
|
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
|
|
{
|
|
for (tk = tklines_ip_hash[index][index2]; tk; tk = tk_next)
|
|
{
|
|
tk_next = tk->next;
|
|
if (tk->flags & flag)
|
|
tkl_del_line(tk);
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Generic TKL list */
|
|
for (index = 0; index < TKLISTLEN; index++)
|
|
{
|
|
for (tk = tklines[index]; tk; tk = tk_next)
|
|
{
|
|
tk_next = tk->next;
|
|
if (tk->flags & flag)
|
|
tkl_del_line(tk);
|
|
}
|
|
}
|
|
}
|
|
|
|
void free_proxy_block(ConfigItem_proxy *e)
|
|
{
|
|
free_security_group(e->mask);
|
|
if (e->auth)
|
|
Auth_FreeAuthConfig(e->auth);
|
|
DelListItem(e, conf_proxy);
|
|
safe_free(e->name);
|
|
safe_free(e);
|
|
}
|
|
|
|
void free_all_proxy_blocks(void)
|
|
{
|
|
ConfigItem_proxy *proxy_ptr, *next;
|
|
|
|
for (proxy_ptr = conf_proxy; proxy_ptr; proxy_ptr = next)
|
|
{
|
|
next = proxy_ptr->next;
|
|
free_proxy_block(proxy_ptr);
|
|
}
|
|
conf_proxy = NULL;
|
|
}
|
|
|
|
void config_rehash()
|
|
{
|
|
ConfigItem_oper *oper_ptr;
|
|
ConfigItem_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);
|
|
free_all_ban_actions(ban_ptr->action);
|
|
DelListItem(ban_ptr, conf_ban);
|
|
safe_free(ban_ptr);
|
|
}
|
|
}
|
|
for (listen_ptr = conf_listen; listen_ptr; listen_ptr = listen_ptr->next)
|
|
{
|
|
if (!(listen_ptr->options & LISTENER_CONTROL))
|
|
listen_ptr->flag.temporary = 1;
|
|
}
|
|
for (tld_ptr = conf_tld; tld_ptr; tld_ptr = (ConfigItem_tld *) next)
|
|
{
|
|
next = (ListStruct *)tld_ptr->next;
|
|
safe_free(tld_ptr->motd_file);
|
|
safe_free(tld_ptr->rules_file);
|
|
safe_free(tld_ptr->smotd_file);
|
|
safe_free(tld_ptr->opermotd_file);
|
|
safe_free(tld_ptr->botmotd_file);
|
|
|
|
free_motd(&tld_ptr->motd);
|
|
free_motd(&tld_ptr->rules);
|
|
free_motd(&tld_ptr->smotd);
|
|
free_motd(&tld_ptr->opermotd);
|
|
free_motd(&tld_ptr->botmotd);
|
|
|
|
free_security_group(tld_ptr->match);
|
|
|
|
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(TKL_FLAG_CONFIG);
|
|
|
|
for (deny_version_ptr = conf_deny_version; deny_version_ptr; deny_version_ptr = (ConfigItem_deny_version *) next) {
|
|
next = (ListStruct *)deny_version_ptr->next;
|
|
safe_free(deny_version_ptr->mask);
|
|
safe_free(deny_version_ptr->version);
|
|
safe_free(deny_version_ptr->flags);
|
|
DelListItem(deny_version_ptr, conf_deny_version);
|
|
safe_free(deny_version_ptr);
|
|
}
|
|
for (deny_channel_ptr = conf_deny_channel; deny_channel_ptr; deny_channel_ptr = (ConfigItem_deny_channel *) next)
|
|
{
|
|
next = (ListStruct *)deny_channel_ptr->next;
|
|
safe_free(deny_channel_ptr->redirect);
|
|
safe_free(deny_channel_ptr->channel);
|
|
safe_free(deny_channel_ptr->reason);
|
|
safe_free(deny_channel_ptr->class);
|
|
DelListItem(deny_channel_ptr, conf_deny_channel);
|
|
free_security_group(deny_channel_ptr->match);
|
|
safe_free(deny_channel_ptr);
|
|
}
|
|
|
|
for (allow_channel_ptr = conf_allow_channel; allow_channel_ptr; allow_channel_ptr = (ConfigItem_allow_channel *) next)
|
|
{
|
|
next = (ListStruct *)allow_channel_ptr->next;
|
|
safe_free(allow_channel_ptr->channel);
|
|
safe_free(allow_channel_ptr->class);
|
|
DelListItem(allow_channel_ptr, conf_allow_channel);
|
|
free_security_group(allow_channel_ptr->match);
|
|
safe_free(allow_channel_ptr);
|
|
}
|
|
|
|
if (conf_drpass)
|
|
{
|
|
Auth_FreeAuthConfig(conf_drpass->restartauth);
|
|
conf_drpass->restartauth = NULL;
|
|
Auth_FreeAuthConfig(conf_drpass->dieauth);
|
|
conf_drpass->dieauth = NULL;
|
|
safe_free(conf_drpass);
|
|
}
|
|
for (alias_ptr = conf_alias; alias_ptr; alias_ptr = (ConfigItem_alias *)next) {
|
|
RealCommand *cmptr = find_command(alias_ptr->alias, 0);
|
|
ConfigItem_alias_format *fmt;
|
|
next = (ListStruct *)alias_ptr->next;
|
|
safe_free(alias_ptr->nick);
|
|
if (cmptr)
|
|
CommandDelX(NULL, cmptr);
|
|
safe_free(alias_ptr->alias);
|
|
if (alias_ptr->format && (alias_ptr->type == ALIAS_COMMAND)) {
|
|
for (fmt = (ConfigItem_alias_format *) alias_ptr->format; fmt; fmt = (ConfigItem_alias_format *) next2)
|
|
{
|
|
next2 = (ListStruct *)fmt->next;
|
|
safe_free(fmt->format);
|
|
safe_free(fmt->nick);
|
|
safe_free(fmt->parameters);
|
|
unreal_delete_match(fmt->expr);
|
|
DelListItem(fmt, alias_ptr->format);
|
|
safe_free(fmt);
|
|
}
|
|
}
|
|
DelListItem(alias_ptr, conf_alias);
|
|
safe_free(alias_ptr);
|
|
}
|
|
for (help_ptr = conf_help; help_ptr; help_ptr = (ConfigItem_help *)next) {
|
|
MOTDLine *text;
|
|
next = (ListStruct *)help_ptr->next;
|
|
safe_free(help_ptr->command);
|
|
while (help_ptr->text) {
|
|
text = help_ptr->text->next;
|
|
safe_free(help_ptr->text->line);
|
|
safe_free(help_ptr->text);
|
|
help_ptr->text = text;
|
|
}
|
|
DelListItem(help_ptr, conf_help);
|
|
safe_free(help_ptr);
|
|
}
|
|
for (os_ptr = iConf.allow_user_stats_ext; os_ptr; os_ptr = (OperStat *)next)
|
|
{
|
|
next = (ListStruct *)os_ptr->next;
|
|
safe_free(os_ptr->flag);
|
|
safe_free(os_ptr);
|
|
}
|
|
iConf.allow_user_stats_ext = NULL;
|
|
for (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);
|
|
|
|
free_all_proxy_blocks();
|
|
|
|
/*
|
|
reset conf_files -- should this be in its own function? no, because
|
|
it's only used here
|
|
*/
|
|
safe_free(conf_files->motd_file);
|
|
safe_free(conf_files->smotd_file);
|
|
safe_free(conf_files->opermotd_file);
|
|
safe_free(conf_files->svsmotd_file);
|
|
safe_free(conf_files->botmotd_file);
|
|
safe_free(conf_files->rules_file);
|
|
safe_free(conf_files->pid_file);
|
|
safe_free(conf_files->tune_file);
|
|
/*
|
|
Don't free conf_files->pid_file here; the old value is used to determine if
|
|
the pidfile location has changed and write_pidfile() needs to be called
|
|
again.
|
|
*/
|
|
safe_free(conf_files);
|
|
conf_files = NULL;
|
|
}
|
|
|
|
int config_post_test()
|
|
{
|
|
#define Error(x) { config_error((x)); errors++; }
|
|
int errors = 0;
|
|
Hook *h;
|
|
|
|
if (!requiredstuff.conf_me)
|
|
Error("me {} block is missing");
|
|
if (!requiredstuff.conf_admin)
|
|
Error("admin {} block is missing");
|
|
if (!requiredstuff.conf_listen)
|
|
Error("listen {} block is missing");
|
|
if (!settings.has_kline_address)
|
|
Error("set::kline-address is missing");
|
|
if (!settings.has_default_server)
|
|
Error("set::default-server is missing");
|
|
if (!settings.has_network_name)
|
|
Error("set::network-name is missing");
|
|
if (!settings.has_help_channel)
|
|
Error("set::help-channel is missing");
|
|
if (nicklengths.min > nicklengths.max)
|
|
Error("set::nick-length is smaller than set::min-nick-length");
|
|
|
|
for (h = Hooks[HOOKTYPE_CONFIGPOSTTEST]; h; h = h->next)
|
|
{
|
|
int value, errs = 0;
|
|
if (h->owner && !(h->owner->flags & MODFLAG_TESTING) &&
|
|
!(h->owner->options & MOD_OPT_PERM))
|
|
continue;
|
|
value = (*(h->func.intfunc))(&errs);
|
|
if (value == -1)
|
|
{
|
|
errors += errs;
|
|
break;
|
|
}
|
|
if (value == -2)
|
|
errors += errs;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/** Make the "read" config the "live" config */
|
|
void config_switchover(void)
|
|
{
|
|
free_iConf(&iConf);
|
|
memcpy(&iConf, &tempiConf, sizeof(iConf));
|
|
memset(&tempiConf, 0, sizeof(tempiConf));
|
|
log_blocks_switchover();
|
|
}
|
|
|
|
/** Priority of config blocks during CONFIG_TEST stage */
|
|
static const char *config_test_priority_blocks[] =
|
|
{
|
|
"me",
|
|
"secret",
|
|
"log", /* "log" needs to be before "set" in CONFIG_TEST */
|
|
"security-group",
|
|
"set",
|
|
"class",
|
|
};
|
|
|
|
/** Priority of config blocks during CONFIG_RUN stage */
|
|
static const char *config_run_priority_blocks[] =
|
|
{
|
|
"me",
|
|
"secret",
|
|
"security-group",
|
|
"set",
|
|
"log", /* "log" needs to be after "set" in CONFIG_RUN */
|
|
"class",
|
|
};
|
|
|
|
int config_item_allowed_for_config_file(const char *resource, const char *item)
|
|
{
|
|
ConfigResource *rs = find_config_resource(resource);
|
|
if (!rs)
|
|
{
|
|
/* Special hardcoded handling for central spamfilter */
|
|
if (!strcmp(resource, "central_spamfilter.conf"))
|
|
{
|
|
if (!strcmp(item, "spamfilter") ||
|
|
!strcmp(item, "ban"))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
/* This should never happen */
|
|
#ifdef DEBUGMODE
|
|
abort();
|
|
#else
|
|
return 1; /* Fallback to unrestricted */
|
|
#endif
|
|
}
|
|
|
|
if (rs->restrict_config == NULL)
|
|
return 1; /* No restrictions */
|
|
|
|
if (item == NULL)
|
|
return 0;
|
|
|
|
if (find_name_list(rs->restrict_config, item))
|
|
return 1;
|
|
return 0;
|
|
}
|
|
|
|
int config_test_blocks_generic(ConfigFile *cfptr, int skip_priority_blocks, int normalconf)
|
|
{
|
|
ConfigEntry *ce;
|
|
int errors = 0;
|
|
int i;
|
|
Hook *h;
|
|
ConfigCommand *cc;
|
|
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
if (skip_priority_blocks)
|
|
{
|
|
char skip = 0;
|
|
for (i=0; i < ARRAY_SIZEOF(config_test_priority_blocks); i++)
|
|
{
|
|
if (!strcmp(ce->name, config_test_priority_blocks[i]))
|
|
{
|
|
skip = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (skip)
|
|
continue;
|
|
}
|
|
|
|
if (!config_item_allowed_for_config_file(cfptr->filename, ce->name))
|
|
continue;
|
|
|
|
if ((cc = config_binary_search(ce->name))) {
|
|
if (cc->testfunc)
|
|
errors += (cc->testfunc(cfptr, ce));
|
|
}
|
|
else
|
|
{
|
|
int used = 0;
|
|
for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
|
|
{
|
|
int value, errs = 0;
|
|
if (normalconf)
|
|
{
|
|
if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
|
|
&& !(h->owner->options & MOD_OPT_PERM))
|
|
{
|
|
continue;
|
|
}
|
|
}
|
|
value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN,&errs);
|
|
if (value == 2)
|
|
used = 1;
|
|
if (value == 1)
|
|
{
|
|
used = 1;
|
|
break;
|
|
}
|
|
if (value == -1)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
break;
|
|
}
|
|
if (value == -2)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
}
|
|
|
|
}
|
|
if (!used)
|
|
{
|
|
config_error("%s:%i: unknown directive %s",
|
|
ce->file->filename, ce->line_number,
|
|
ce->name);
|
|
errors++;
|
|
if (strchr(ce->name, ':'))
|
|
{
|
|
config_error("You cannot use :: in a directive, you have to write them out. "
|
|
"For example 'set::auto-join #something' needs to be written as: "
|
|
"set { auto-join \"#something\"; }");
|
|
config_error("See also https://www.unrealircd.org/docs/Set_block#Syntax_used_in_this_documentation");
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
int config_test_blocks()
|
|
{
|
|
ConfigEntry *ce;
|
|
ConfigFile *cfptr;
|
|
ConfigCommand *cc;
|
|
int errors = 0;
|
|
int i;
|
|
Hook *h;
|
|
|
|
invalid_snomasks_encountered = 0;
|
|
|
|
/* Stage 1: first the priority blocks, in the order as specified
|
|
* in config_test_priority_blocks[]
|
|
*/
|
|
for (i=0; i < ARRAY_SIZEOF(config_test_priority_blocks); i++)
|
|
{
|
|
const char *config_block = config_test_priority_blocks[i];
|
|
cc = config_binary_search(config_block);
|
|
if (!cc)
|
|
abort(); /* internal fuckup */
|
|
for (cfptr = conf; cfptr; cfptr = cfptr->next)
|
|
{
|
|
if (!config_item_allowed_for_config_file(cfptr->filename, config_block))
|
|
continue;
|
|
if (config_verbose > 1)
|
|
config_status("Running %s", cfptr->filename);
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
if (!strcmp(ce->name, config_block))
|
|
{
|
|
int n = cc->testfunc(cfptr, ce);
|
|
errors += n;
|
|
if (!strcmp(config_block, "secret") && (n == 0))
|
|
{
|
|
/* Yeah special case: secret { } blocks we run
|
|
* immediately here.
|
|
*/
|
|
_conf_secret(cfptr, ce);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Stage 2: now all the other config blocks */
|
|
for (cfptr = conf; cfptr; cfptr = cfptr->next)
|
|
{
|
|
if (config_verbose > 1)
|
|
config_status("Running %s", cfptr->filename);
|
|
errors += config_test_blocks_generic(cfptr, 1, 1);
|
|
}
|
|
|
|
errors += config_post_test();
|
|
|
|
if (errors > 0)
|
|
{
|
|
config_error("%i errors encountered", errors);
|
|
}
|
|
|
|
if (invalid_snomasks_encountered)
|
|
{
|
|
config_error("It seems your set::snomask-on-oper and/or oper::snomask needs to be updated. Are you perhaps upgrading from an older version to UnrealIRCd 6?");
|
|
config_error("See https://www.unrealircd.org/docs/Upgrading_from_5.x#Update_your_snomasks");
|
|
}
|
|
|
|
return (errors > 0 ? -1 : 1);
|
|
}
|
|
|
|
/* This returns the # of processed blocks, NOT errors */
|
|
int config_run_blocks_generic(ConfigFile *cfptr, int skip_priority_blocks)
|
|
{
|
|
ConfigEntry *ce;
|
|
int processed = 0;
|
|
int i;
|
|
Hook *h;
|
|
ConfigCommand *cc;
|
|
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
if (skip_priority_blocks)
|
|
{
|
|
char skip = 0;
|
|
for (i=0; i < ARRAY_SIZEOF(config_run_priority_blocks); i++)
|
|
{
|
|
if (!strcmp(ce->name, config_run_priority_blocks[i]))
|
|
{
|
|
skip = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (skip)
|
|
continue;
|
|
}
|
|
|
|
if (!config_item_allowed_for_config_file(cfptr->filename, ce->name))
|
|
continue;
|
|
|
|
if ((cc = config_binary_search(ce->name)))
|
|
{
|
|
if (cc->conffunc)
|
|
{
|
|
cc->conffunc(cfptr, ce);
|
|
processed++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
int value;
|
|
for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
|
|
{
|
|
value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN);
|
|
if (value == 1)
|
|
{
|
|
processed++;
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
return processed;
|
|
}
|
|
|
|
int config_run_blocks(void)
|
|
{
|
|
ConfigEntry *ce;
|
|
ConfigFile *cfptr;
|
|
ConfigCommand *cc;
|
|
int errors = 0;
|
|
int i;
|
|
Hook *h;
|
|
ConfigItem_allow *allow;
|
|
|
|
/* Stage 1: first the priority blocks, in the order as specified
|
|
* in config_run_priority_blocks[]
|
|
*/
|
|
for (i=0; i < ARRAY_SIZEOF(config_run_priority_blocks); i++)
|
|
{
|
|
const char *config_block = config_run_priority_blocks[i];
|
|
cc = config_binary_search(config_block);
|
|
if (!cc)
|
|
abort(); /* internal fuckup */
|
|
if (!strcmp(config_block, "secret"))
|
|
continue; /* yeah special case, we already processed the run part in test for these */
|
|
for (cfptr = conf; cfptr; cfptr = cfptr->next)
|
|
{
|
|
if (config_verbose > 1)
|
|
config_status("Running %s", cfptr->filename);
|
|
for (ce = cfptr->items; ce; ce = ce->next)
|
|
{
|
|
if (!config_item_allowed_for_config_file(cfptr->filename, ce->name))
|
|
continue;
|
|
if (!strcmp(ce->name, config_block))
|
|
{
|
|
if (cc->conffunc(cfptr, ce) < 0)
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
/* Stage 2: now all the other config blocks */
|
|
for (cfptr = conf; cfptr; cfptr = cfptr->next)
|
|
{
|
|
if (config_verbose > 1)
|
|
config_status("Running %s", cfptr->filename);
|
|
config_run_blocks_generic(cfptr, 1);
|
|
}
|
|
|
|
listen_cleanup();
|
|
loop.do_bancheck = 1;
|
|
config_switchover();
|
|
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_esc(dchannel->channel, name))
|
|
{
|
|
if (dchannel->class && strcmp(client->local->class->name, dchannel->class))
|
|
continue;
|
|
if (dchannel->match && !user_allowed_by_security_group(client, dchannel->match))
|
|
continue;
|
|
break; /* MATCH deny channel { } */
|
|
}
|
|
}
|
|
|
|
if (dchannel)
|
|
{
|
|
/* Check exceptions... ('allow channel') */
|
|
for (achannel = conf_allow_channel; achannel; achannel = achannel->next)
|
|
{
|
|
if (match_esc(achannel->channel, name))
|
|
{
|
|
if (achannel->class && strcmp(client->local->class->name, achannel->class))
|
|
continue;
|
|
if (achannel->match && !user_allowed_by_security_group(client, achannel->match))
|
|
continue;
|
|
break; /* MATCH allow channel { } */
|
|
}
|
|
}
|
|
if (achannel)
|
|
return NULL; /* Matches an 'allow channel' - so not forbidden */
|
|
else
|
|
return dchannel;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
void init_dynconf(void)
|
|
{
|
|
memset(&iConf, 0, sizeof(iConf));
|
|
memset(&tempiConf, 0, sizeof(iConf));
|
|
init_dynamic_set_block(&unknown_users_set);
|
|
init_dynamic_set_block(&dynamic_set);
|
|
}
|
|
|
|
const char *pretty_time_val_r(char *buf, size_t buflen, long timeval)
|
|
{
|
|
if (timeval == 0)
|
|
return "0";
|
|
|
|
buf[0] = 0;
|
|
|
|
if (timeval/86400)
|
|
snprintf(buf, buflen, "%ldd", timeval/86400);
|
|
if ((timeval/3600) % 24)
|
|
snprintf(buf+strlen(buf), buflen-strlen(buf), "%ldh", (timeval/3600)%24);
|
|
if ((timeval/60)%60)
|
|
snprintf(buf+strlen(buf), buflen-strlen(buf), "%ldm", (timeval/60)%60);
|
|
if ((timeval%60))
|
|
snprintf(buf+strlen(buf), buflen-strlen(buf), "%lds", timeval%60);
|
|
|
|
return buf;
|
|
}
|
|
|
|
const char *pretty_time_val(long timeval)
|
|
{
|
|
static char buf[512];
|
|
return pretty_time_val_r(buf, sizeof(buf), timeval);
|
|
}
|
|
|
|
/* This converts a relative path to an absolute path, but only if necessary. */
|
|
void convert_to_absolute_path(char **path, const char *reldir)
|
|
{
|
|
char *s;
|
|
|
|
if (!*path || !**path)
|
|
return; /* NULL or empty */
|
|
|
|
if (strstr(*path, "://"))
|
|
return; /* URL: don't touch */
|
|
|
|
#ifdef _WIN32
|
|
if (!strncmp(*path, "cache/", 6))
|
|
return; /* downloaded from URL: don't touch (is only relative path on Windows) */
|
|
#endif
|
|
|
|
if ((**path == '/') || (**path == '\\'))
|
|
return; /* already absolute path */
|
|
|
|
if (!strncmp(*path, reldir, strlen(reldir)))
|
|
return; /* already contains reldir */
|
|
|
|
s = safe_alloc(strlen(reldir) + strlen(*path) + 2);
|
|
sprintf(s, "%s/%s", reldir, *path); /* safe, see line above */
|
|
safe_free(*path);
|
|
*path = s;
|
|
}
|
|
|
|
/* Similar to convert_to_absolute_path() but returns a duplicated string.
|
|
* Don't forget to free!
|
|
*/
|
|
char *convert_to_absolute_path_duplicate(char *path, char *reldir)
|
|
{
|
|
char *xpath = strdup(path);
|
|
convert_to_absolute_path(&xpath, reldir);
|
|
return xpath;
|
|
}
|
|
|
|
/*
|
|
* Actual config parser funcs
|
|
*/
|
|
|
|
int _conf_include(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
int ret = 0;
|
|
#ifdef GLOBH
|
|
glob_t files;
|
|
int i;
|
|
#elif defined(_WIN32)
|
|
HANDLE hFind;
|
|
WIN32_FIND_DATA FindData;
|
|
char cPath[MAX_PATH], *cSlash = NULL, *path;
|
|
#endif
|
|
if (!ce->value)
|
|
{
|
|
config_status("%s:%i: include: no filename given",
|
|
ce->file->filename,
|
|
ce->line_number);
|
|
return -1;
|
|
}
|
|
|
|
convert_to_absolute_path(&ce->value, CONFDIR);
|
|
|
|
if (str_ends_with_case_sensitive(ce->value, "central_spamfilter.conf"))
|
|
{
|
|
config_status("%s:%d: you cannot include 'central_spamfilter.conf', "
|
|
"this is a special name that cannot be used.",
|
|
ce->file->filename, ce->line_number);
|
|
return -1;
|
|
}
|
|
|
|
if (url_is_valid(ce->value))
|
|
{
|
|
add_config_resource(ce->value, RESOURCE_INCLUDE, ce);
|
|
return 0;
|
|
}
|
|
#if !defined(_WIN32) && !defined(_AMIGA) && !defined(OSXTIGER) && DEFAULT_PERMISSIONS != 0
|
|
(void)chmod(ce->value, DEFAULT_PERMISSIONS);
|
|
#endif
|
|
#ifdef GLOBH
|
|
#if defined(__OpenBSD__) && defined(GLOB_LIMIT)
|
|
glob(ce->value, GLOB_NOSORT|GLOB_NOCHECK|GLOB_LIMIT, NULL, &files);
|
|
#else
|
|
glob(ce->value, GLOB_NOSORT|GLOB_NOCHECK, NULL, &files);
|
|
#endif
|
|
if (!files.gl_pathc) {
|
|
globfree(&files);
|
|
config_status("%s:%i: include %s: invalid file given",
|
|
ce->file->filename, ce->line_number,
|
|
ce->value);
|
|
return -1;
|
|
}
|
|
for (i = 0; i < files.gl_pathc; i++)
|
|
{
|
|
if (add_config_resource(files.gl_pathv[i], RESOURCE_INCLUDE, ce))
|
|
{
|
|
ret = config_read_file(files.gl_pathv[i], files.gl_pathv[i]);
|
|
if (ret < 0)
|
|
{
|
|
globfree(&files);
|
|
return ret;
|
|
}
|
|
}
|
|
}
|
|
globfree(&files);
|
|
#elif defined(_WIN32)
|
|
memset(cPath, 0, MAX_PATH);
|
|
if (strchr(ce->value, '/') || strchr(ce->value, '\\')) {
|
|
strlcpy(cPath,ce->value,MAX_PATH);
|
|
cSlash=cPath+strlen(cPath);
|
|
while(*cSlash != '\\' && *cSlash != '/' && cSlash > cPath)
|
|
cSlash--;
|
|
*(cSlash+1)=0;
|
|
}
|
|
if ( (hFind = FindFirstFile(ce->value, &FindData)) == INVALID_HANDLE_VALUE )
|
|
{
|
|
config_status("%s:%i: include %s: invalid file given",
|
|
ce->file->filename, ce->line_number,
|
|
ce->value);
|
|
return -1;
|
|
}
|
|
if (cPath) {
|
|
path = safe_alloc(strlen(cPath) + strlen(FindData.cFileName)+1);
|
|
strcpy(path, cPath);
|
|
strcat(path, FindData.cFileName);
|
|
|
|
if (add_config_resource(path, RESOURCE_INCLUDE, ce))
|
|
{
|
|
ret = config_read_file(path, path);
|
|
safe_free(path);
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (add_config_resource(FindData.cFileName, RESOURCE_INCLUDE, ce))
|
|
ret = config_read_file(FindData.cFileName, FindData.cFileName);
|
|
}
|
|
if (ret < 0)
|
|
{
|
|
FindClose(hFind);
|
|
return ret;
|
|
}
|
|
|
|
ret = 0;
|
|
while (FindNextFile(hFind, &FindData) != 0) {
|
|
if (cPath) {
|
|
path = safe_alloc(strlen(cPath) + strlen(FindData.cFileName)+1);
|
|
strcpy(path,cPath);
|
|
strcat(path,FindData.cFileName);
|
|
|
|
if (add_config_resource(path, RESOURCE_INCLUDE, ce))
|
|
{
|
|
ret = config_read_file(path, path);
|
|
safe_free(path);
|
|
if (ret < 0)
|
|
break;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
if (add_config_resource(FindData.cFileName, RESOURCE_INCLUDE, ce))
|
|
ret = config_read_file(FindData.cFileName, FindData.cFileName);
|
|
}
|
|
}
|
|
FindClose(hFind);
|
|
if (ret < 0)
|
|
return ret;
|
|
#else
|
|
if (add_config_resource(ce->value, RESOURCE_INCLUDE, ce))
|
|
ret = config_read_file(ce->value, ce->value);
|
|
return ret;
|
|
#endif
|
|
return 1;
|
|
}
|
|
|
|
int _test_include(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int _conf_admin(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
ConfigItem_admin *ca;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
ca = safe_alloc(sizeof(ConfigItem_admin));
|
|
if (!conf_admin)
|
|
conf_admin_tail = ca;
|
|
safe_strdup(ca->line, cep->name);
|
|
AddListItem(ca, conf_admin);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int _test_admin(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
|
|
if (requiredstuff.conf_admin)
|
|
{
|
|
config_warn_duplicate(ce->file->filename, ce->line_number, "admin");
|
|
return 0;
|
|
}
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (strlen(cep->name) > 500)
|
|
{
|
|
config_error("%s:%i: oversized data in admin block",
|
|
cep->file->filename,
|
|
cep->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
requiredstuff.conf_admin = 1;
|
|
return errors;
|
|
}
|
|
|
|
int _conf_me(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
|
|
if (!conf_me)
|
|
conf_me = safe_alloc(sizeof(ConfigItem_me));
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "name"))
|
|
{
|
|
safe_strdup(conf_me->name, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "info"))
|
|
{
|
|
safe_strdup(conf_me->info, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "sid"))
|
|
{
|
|
safe_strdup(conf_me->sid, cep->value);
|
|
}
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int _test_me(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
char has_name = 0, has_info = 0, has_sid = 0;
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
|
|
if (requiredstuff.conf_me)
|
|
{
|
|
config_warn_duplicate(ce->file->filename, ce->line_number, "me");
|
|
return 0;
|
|
}
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (config_is_blankorempty(cep, "me"))
|
|
continue;
|
|
|
|
/* me::name */
|
|
if (!strcmp(cep->name, "name"))
|
|
{
|
|
if (has_name)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "me::name");
|
|
continue;
|
|
}
|
|
has_name = 1;
|
|
if (!strchr(cep->value, '.'))
|
|
{
|
|
config_error("%s:%i: illegal me::name, must be fully qualified hostname",
|
|
cep->file->filename,
|
|
cep->line_number);
|
|
errors++;
|
|
}
|
|
if (strlen(cep->value) > HOSTLEN)
|
|
{
|
|
config_error("%s:%i: illegal me::name, must be less or equal to %i characters",
|
|
cep->file->filename,
|
|
cep->line_number, HOSTLEN);
|
|
errors++;
|
|
}
|
|
if (!valid_server_name(cep->value))
|
|
{
|
|
config_error("%s:%i: illegal me::name contains invalid character(s) [only a-z, 0-9, _, -, . are allowed]",
|
|
cep->file->filename,
|
|
cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
/* me::info */
|
|
else if (!strcmp(cep->name, "info"))
|
|
{
|
|
char *p;
|
|
char valid = 0;
|
|
if (has_info)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "me::info");
|
|
continue;
|
|
}
|
|
has_info = 1;
|
|
if (strlen(cep->value) > (REALLEN-1))
|
|
{
|
|
config_error("%s:%i: too long me::info, must be max. %i characters",
|
|
cep->file->filename, cep->line_number,
|
|
REALLEN-1);
|
|
errors++;
|
|
}
|
|
|
|
/* Valid me::info? Any data except spaces is ok */
|
|
for (p=cep->value; *p; p++)
|
|
{
|
|
if (*p != ' ')
|
|
{
|
|
valid = 1;
|
|
break;
|
|
}
|
|
}
|
|
if (!valid)
|
|
{
|
|
config_error("%s:%i: empty me::info, should be a server description.",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "numeric"))
|
|
{
|
|
config_error("%s:%i: me::numeric has been removed, you must now specify a Server ID (SID) instead. "
|
|
"Edit your configuration file and change 'numeric' to 'sid' and make up "
|
|
"a server id of exactly 3 characters, starting with a digit, eg: \"001\" or \"0AB\".",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
else if (!strcmp(cep->name, "sid"))
|
|
{
|
|
if (has_sid)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "me::sid");
|
|
continue;
|
|
}
|
|
has_sid = 1;
|
|
|
|
if (!valid_sid(cep->value))
|
|
{
|
|
config_error("%s:%i: me::sid must be 3 characters long, begin with a number, "
|
|
"and the 2nd and 3rd character must be a number or uppercase letter. "
|
|
"Example: \"001\" and \"0AB\" is good. \"AAA\" and \"0ab\" are bad. ",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
|
|
if (!isdigit(*cep->value))
|
|
{
|
|
config_error("%s:%i: me::sid must be 3 characters long and begin with a number",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
/* Unknown entry */
|
|
else
|
|
{
|
|
config_error_unknown(ce->file->filename, ce->line_number,
|
|
"me", cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
if (!has_name)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number, "me::name");
|
|
errors++;
|
|
}
|
|
if (!has_info)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number, "me::info");
|
|
errors++;
|
|
}
|
|
if (!has_sid)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number, "me::sid");
|
|
errors++;
|
|
}
|
|
requiredstuff.conf_me = 1;
|
|
return errors;
|
|
}
|
|
|
|
/*
|
|
* The files {} block
|
|
*/
|
|
int _conf_files(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
|
|
if (!conf_files)
|
|
{
|
|
conf_files = safe_alloc(sizeof(ConfigItem_files));
|
|
|
|
/* set defaults */
|
|
safe_strdup(conf_files->motd_file, MPATH);
|
|
safe_strdup(conf_files->rules_file, RPATH);
|
|
safe_strdup(conf_files->smotd_file, SMPATH);
|
|
safe_strdup(conf_files->botmotd_file, BPATH);
|
|
safe_strdup(conf_files->opermotd_file, OPATH);
|
|
safe_strdup(conf_files->svsmotd_file, VPATH);
|
|
|
|
safe_strdup(conf_files->pid_file, IRCD_PIDFILE);
|
|
safe_strdup(conf_files->tune_file, IRCDTUNE);
|
|
|
|
/* we let actual files get read in later by the motd caching mechanism */
|
|
}
|
|
/*
|
|
* hack to allow initialization of conf_files (above) when there is no files block in
|
|
* CPATH. The caller calls _conf_files(NULL, NULL); to do this. We return here because
|
|
* the for loop's initialization of cep would segfault otherwise. We return 1 because
|
|
* if config_run_blocks() calls us with a NULL ce, it's got a bug...but we can't detect that.
|
|
*/
|
|
if (!ce)
|
|
return 1;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "motd"))
|
|
safe_strdup(conf_files->motd_file, cep->value);
|
|
else if (!strcmp(cep->name, "shortmotd"))
|
|
safe_strdup(conf_files->smotd_file, cep->value);
|
|
else if (!strcmp(cep->name, "opermotd"))
|
|
safe_strdup(conf_files->opermotd_file, cep->value);
|
|
else if (!strcmp(cep->name, "svsmotd"))
|
|
safe_strdup(conf_files->svsmotd_file, cep->value);
|
|
else if (!strcmp(cep->name, "botmotd"))
|
|
safe_strdup(conf_files->botmotd_file, cep->value);
|
|
else if (!strcmp(cep->name, "rules"))
|
|
safe_strdup(conf_files->rules_file, cep->value);
|
|
else if (!strcmp(cep->name, "tunefile"))
|
|
safe_strdup(conf_files->tune_file, cep->value);
|
|
else if (!strcmp(cep->name, "pidfile"))
|
|
safe_strdup(conf_files->pid_file, cep->value);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int _test_files(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
char has_motd = 0, has_smotd = 0, has_rules = 0;
|
|
char has_botmotd = 0, has_opermotd = 0, has_svsmotd = 0;
|
|
char has_pidfile = 0, has_tunefile = 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
/* files::motd */
|
|
if (!strcmp(cep->name, "motd"))
|
|
{
|
|
if (has_motd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::motd");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
config_test_openfile(cep, O_RDONLY, 0, "files::motd", 0, 1);
|
|
has_motd = 1;
|
|
}
|
|
/* files::smotd */
|
|
else if (!strcmp(cep->name, "shortmotd"))
|
|
{
|
|
if (has_smotd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::shortmotd");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
config_test_openfile(cep, O_RDONLY, 0, "files::shortmotd", 0, 1);
|
|
has_smotd = 1;
|
|
}
|
|
/* files::rules */
|
|
else if (!strcmp(cep->name, "rules"))
|
|
{
|
|
if (has_rules)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::rules");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
config_test_openfile(cep, O_RDONLY, 0, "files::rules", 0, 1);
|
|
has_rules = 1;
|
|
}
|
|
/* files::botmotd */
|
|
else if (!strcmp(cep->name, "botmotd"))
|
|
{
|
|
if (has_botmotd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::botmotd");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
config_test_openfile(cep, O_RDONLY, 0, "files::botmotd", 0, 1);
|
|
has_botmotd = 1;
|
|
}
|
|
/* files::opermotd */
|
|
else if (!strcmp(cep->name, "opermotd"))
|
|
{
|
|
if (has_opermotd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::opermotd");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
config_test_openfile(cep, O_RDONLY, 0, "files::opermotd", 0, 1);
|
|
has_opermotd = 1;
|
|
}
|
|
/* files::svsmotd
|
|
* This config stuff should somehow be inside of modules/svsmotd.c!!!... right?
|
|
*/
|
|
else if (!strcmp(cep->name, "svsmotd"))
|
|
{
|
|
if (has_svsmotd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::svsmotd");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
/* svsmotd can't be a URL because we have to be able to write to it */
|
|
config_test_openfile(cep, O_RDONLY, 0, "files::svsmotd", 0, 0);
|
|
has_svsmotd = 1;
|
|
}
|
|
/* files::pidfile */
|
|
else if (!strcmp(cep->name, "pidfile"))
|
|
{
|
|
if (has_pidfile)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::pidfile");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, PERMDATADIR);
|
|
errors += config_test_openfile(cep, O_WRONLY | O_CREAT, 0600, "files::pidfile", 1, 0);
|
|
has_pidfile = 1;
|
|
}
|
|
/* files::tunefile */
|
|
else if (!strcmp(cep->name, "tunefile"))
|
|
{
|
|
if (has_tunefile)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "files::tunefile");
|
|
continue;
|
|
}
|
|
convert_to_absolute_path(&cep->value, PERMDATADIR);
|
|
errors += config_test_openfile(cep, O_RDWR | O_CREAT, 0600, "files::tunefile", 1, 0);
|
|
has_tunefile = 1;
|
|
}
|
|
/* <random directive here> */
|
|
else
|
|
{
|
|
config_error("%s:%d: Unknown directive: \"%s\" in files {}", cep->file->filename,
|
|
cep->line_number, cep->name);
|
|
errors ++;
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/*
|
|
* The operclass {} block parser
|
|
*/
|
|
|
|
OperClassACLEntry* _conf_parseACLEntry(ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
OperClassACLEntry *entry = NULL;
|
|
entry = safe_alloc(sizeof(OperClassACLEntry));
|
|
|
|
if (!strcmp(ce->name,"allow"))
|
|
entry->type = OPERCLASSENTRY_ALLOW;
|
|
else
|
|
entry->type = OPERCLASSENTRY_DENY;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
OperClassACLEntryVar *var = safe_alloc(sizeof(OperClassACLEntryVar));
|
|
safe_strdup(var->name, cep->name);
|
|
if (cep->value)
|
|
{
|
|
safe_strdup(var->value, cep->value);
|
|
}
|
|
AddListItem(var,entry->variables);
|
|
}
|
|
|
|
return entry;
|
|
}
|
|
|
|
OperClassACL* _conf_parseACL(const char *name, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
OperClassACL *acl = NULL;
|
|
|
|
acl = safe_alloc(sizeof(OperClassACL));
|
|
safe_strdup(acl->name, name);
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "deny") || !strcmp(cep->name, "allow"))
|
|
{
|
|
OperClassACLEntry *entry = _conf_parseACLEntry(cep);
|
|
AddListItem(entry,acl->entries);
|
|
}
|
|
else {
|
|
OperClassACL *subAcl = _conf_parseACL(cep->name,cep);
|
|
AddListItem(subAcl,acl->acls);
|
|
}
|
|
}
|
|
|
|
return acl;
|
|
}
|
|
|
|
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++;
|
|
} else
|
|
if (!valid_operclass_name(ce->value))
|
|
{
|
|
config_error("%s:%d: operclass name may only contain alphanumerical characters and "
|
|
"characters _-",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
}
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "parent"))
|
|
{
|
|
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, 1) < 0)
|
|
errors++;
|
|
|
|
if (ce->value && cep->value &&
|
|
!strcmp(ce->value, "bobsmith") &&
|
|
(!strcmp(cep->value, "test") || !strcmp(cep->value, "$argon2id..etc..")))
|
|
{
|
|
config_error("%s:%i: please change the the name and password of the "
|
|
"default 'bobsmith' oper block",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
}
|
|
continue;
|
|
}
|
|
/* 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, 1) < 0)
|
|
errors++;
|
|
}
|
|
else
|
|
{
|
|
config_error_unknown(cep->file->filename,
|
|
cep->line_number, "oper", cep->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
|
|
if (has_auto_login && has_broad_match)
|
|
{
|
|
config_error("%s:%i: your oper block for '%s' has auto-login but is completely unrestricted (mask *@*)!",
|
|
ce->file->filename, ce->line_number, ce->value);
|
|
errors++;
|
|
} else
|
|
if (!has_password && has_broad_match)
|
|
{
|
|
config_error("%s:%i: your oper block for '%s' has no password and is completely unrestricted (mask *@*)!",
|
|
ce->file->filename, ce->line_number, ce->value);
|
|
errors++;
|
|
}
|
|
|
|
/* The rest should NOT be in an 'else'... */
|
|
if (has_password && has_auto_login)
|
|
{
|
|
config_error("%s:%i: You have auto-login enabled for your oper block '%s' but you also have a password set. "
|
|
"Remove the password if you want to use auto-login.",
|
|
ce->file->filename, ce->line_number, ce->value);
|
|
errors++;
|
|
}
|
|
if (!has_mask && !has_match)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"oper::match");
|
|
errors++;
|
|
}
|
|
if (has_mask && has_match)
|
|
{
|
|
config_error("%s:%d: You cannot have both ::mask and ::match. "
|
|
"You should only use oper::match.",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
}
|
|
if (!has_class)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"oper::class");
|
|
errors++;
|
|
}
|
|
if (!has_operclass)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"oper::operclass");
|
|
errors++;
|
|
}
|
|
|
|
return errors;
|
|
|
|
}
|
|
|
|
ProxyType proxy_type_string_to_value(const char *s)
|
|
{
|
|
if (!strcmp(s, "webirc"))
|
|
return PROXY_WEBIRC;
|
|
else if (!strcmp(s, "old"))
|
|
return PROXY_WEBIRC_PASS;
|
|
else if (!strcmp(s, "web"))
|
|
return PROXY_WEB;
|
|
return 0;
|
|
}
|
|
|
|
int _test_proxy(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
char has_mask = 0; /* mandatory */
|
|
char has_password = 0; /* mandatory */
|
|
char has_type = 0;
|
|
ProxyType proxy_type = 0;
|
|
|
|
if (!strcmp(ce->name, "webirc"))
|
|
{
|
|
proxy_type = PROXY_WEBIRC;
|
|
} else {
|
|
if (!ce->value)
|
|
{
|
|
config_error("%s:%i: proxy { } blocks need to have a name, like proxy myproxy { }",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
} else
|
|
if (!security_group_valid_name(ce->value))
|
|
{
|
|
config_error("%s:%i: the name of the proxy block may only contain a-z, A-Z, 0-9, _ and -. "
|
|
"Your block name is invalid: '%s'",
|
|
ce->file->filename, ce->line_number, ce->value);
|
|
errors++;
|
|
}
|
|
}
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
/* This one can have no value so needs to be at the top */
|
|
if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
|
|
{
|
|
if (cep->value || cep->items)
|
|
{
|
|
has_mask = 1;
|
|
test_match_block(conf, cep, &errors);
|
|
}
|
|
} else
|
|
if (!cep->value)
|
|
{
|
|
config_error_empty(cep->file->filename, cep->line_number,
|
|
"proxy", cep->name);
|
|
errors++;
|
|
} else
|
|
if (!strcmp(cep->name, "password"))
|
|
{
|
|
if (has_password)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "proxy::password");
|
|
continue;
|
|
}
|
|
has_password = 1;
|
|
if (Auth_CheckError(cep, 0) < 0)
|
|
errors++;
|
|
}
|
|
else if (!strcmp(cep->name, "type"))
|
|
{
|
|
if (has_type)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "proxy::type");
|
|
}
|
|
has_type = 1;
|
|
proxy_type = proxy_type_string_to_value(cep->value);
|
|
if (proxy_type == 0)
|
|
{
|
|
config_error("%s:%i: unknown proxy::type '%s'. "
|
|
"Only the following types are supported: 'webirc', 'old' or 'web'.",
|
|
cep->file->filename, cep->line_number, cep->value);
|
|
errors++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
config_error_unknown(cep->file->filename, cep->line_number,
|
|
"webirc", cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
if (!has_mask)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"webirc::mask");
|
|
errors++;
|
|
}
|
|
|
|
if (!has_password && (proxy_type == PROXY_WEBIRC))
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"webirc::password");
|
|
errors++;
|
|
}
|
|
|
|
if (has_password && (proxy_type == PROXY_WEBIRC_PASS))
|
|
{
|
|
config_error("%s:%i: webirc block has type set to 'old' but has a password set. "
|
|
"Passwords are not used with type 'old'. Either remove the password or "
|
|
"use the 'webirc' method instead.",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
}
|
|
|
|
if (!has_type && !proxy_type)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number, "proxy::type");
|
|
errors++;
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
int _conf_proxy(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
ConfigItem_proxy *proxy = NULL;
|
|
|
|
proxy = safe_alloc(sizeof(ConfigItem_proxy));
|
|
proxy->type = PROXY_WEBIRC; /* default */
|
|
|
|
safe_strdup(proxy->name, ce->value); // could be NULL if webirc
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "mask") || !strcmp(cep->name, "match"))
|
|
conf_match_block(conf, cep, &proxy->mask);
|
|
else if (!strcmp(cep->name, "password"))
|
|
proxy->auth = AuthBlockToAuthConfig(cep);
|
|
else if (!strcmp(cep->name, "type"))
|
|
proxy->type = proxy_type_string_to_value(cep->value);
|
|
}
|
|
|
|
AddListItem(proxy, conf_proxy);
|
|
|
|
/* For proxy type web, we automatically add the host to except ban { }
|
|
* for blacklist, connect-flood, handshake-data-flood
|
|
*/
|
|
if (proxy->type == PROXY_WEB)
|
|
{
|
|
SecurityGroup *sg = duplicate_security_group(proxy->mask);
|
|
tkl_add_banexception(TKL_EXCEPTION, "-", "-", sg, "proxy { } block",
|
|
"-config-", 0, TStime(), 0, "bcd", TKL_FLAG_CONFIG);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/*
|
|
* The class {} block parser
|
|
*/
|
|
int _conf_class(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep, *cep2;
|
|
ConfigItem_class *class;
|
|
unsigned char isnew = 0;
|
|
|
|
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) < 0)
|
|
errors++;
|
|
continue;
|
|
}
|
|
/* drpass::die */
|
|
else if (!strcmp(cep->name, "die"))
|
|
{
|
|
if (has_die)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "drpass::die");
|
|
continue;
|
|
}
|
|
has_die = 1;
|
|
if (Auth_CheckError(cep, 0) < 0)
|
|
errors++;
|
|
continue;
|
|
}
|
|
/* Unknown */
|
|
else
|
|
{
|
|
config_error_unknown(cep->file->filename, cep->line_number,
|
|
"drpass", cep->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/*
|
|
* The ulines {} block parser
|
|
*/
|
|
int _conf_ulines(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
ConfigItem_ulines *ca;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
ca = safe_alloc(sizeof(ConfigItem_ulines));
|
|
safe_strdup(ca->servername, cep->name);
|
|
AddListItem(ca, conf_ulines);
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int _test_ulines(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
/* No check needed */
|
|
return 0;
|
|
}
|
|
|
|
int _conf_tld(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
ConfigItem_tld *ca;
|
|
|
|
ca = safe_alloc(sizeof(ConfigItem_tld));
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
|
|
conf_match_block(conf, cep, &ca->match);
|
|
else if (!strcmp(cep->name, "motd"))
|
|
{
|
|
safe_strdup(ca->motd_file, cep->value);
|
|
read_motd(cep->value, &ca->motd);
|
|
}
|
|
else if (!strcmp(cep->name, "shortmotd"))
|
|
{
|
|
safe_strdup(ca->smotd_file, cep->value);
|
|
read_motd(cep->value, &ca->smotd);
|
|
}
|
|
else if (!strcmp(cep->name, "opermotd"))
|
|
{
|
|
safe_strdup(ca->opermotd_file, cep->value);
|
|
read_motd(cep->value, &ca->opermotd);
|
|
}
|
|
else if (!strcmp(cep->name, "botmotd"))
|
|
{
|
|
safe_strdup(ca->botmotd_file, cep->value);
|
|
read_motd(cep->value, &ca->botmotd);
|
|
}
|
|
else if (!strcmp(cep->name, "rules"))
|
|
{
|
|
safe_strdup(ca->rules_file, cep->value);
|
|
read_motd(cep->value, &ca->rules);
|
|
}
|
|
else if (!strcmp(cep->name, "options"))
|
|
{
|
|
ConfigEntry *cepp;
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "ssl") || !strcmp(cepp->name, "tls"))
|
|
ca->options |= TLD_TLS;
|
|
else if (!strcmp(cepp->name, "remote"))
|
|
ca->options |= TLD_REMOTE;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "channel"))
|
|
safe_strdup(ca->channel, cep->value);
|
|
}
|
|
AddListItem(ca, conf_tld);
|
|
return 1;
|
|
}
|
|
|
|
int _test_tld(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
int fd = -1;
|
|
char has_mask = 0, has_match = 0, has_motd = 0, has_rules = 0, has_shortmotd = 0;
|
|
char has_channel = 0, has_opermotd = 0, has_botmotd = 0, has_options = 0;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!cep->value && strcmp(cep->name, "options") && strcmp(cep->name, "mask") && strcmp(cep->name, "match"))
|
|
{
|
|
config_error_empty(cep->file->filename, cep->line_number,
|
|
"tld", cep->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
/* tld::mask */
|
|
if (!strcmp(cep->name, "mask"))
|
|
{
|
|
if (cep->value || cep->items)
|
|
{
|
|
has_mask = 1;
|
|
test_match_block(conf, cep, &errors);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "match"))
|
|
{
|
|
if (cep->value || cep->items)
|
|
{
|
|
has_match = 1;
|
|
test_match_block(conf, cep, &errors);
|
|
}
|
|
}
|
|
/* tld::motd */
|
|
else if (!strcmp(cep->name, "motd"))
|
|
{
|
|
if (has_motd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "tld::motd");
|
|
continue;
|
|
}
|
|
has_motd = 1;
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
if (((fd = open(cep->value, O_RDONLY)) == -1))
|
|
{
|
|
config_error("%s:%i: tld::motd: %s: %s",
|
|
cep->file->filename, cep->line_number,
|
|
cep->value, strerror(errno));
|
|
errors++;
|
|
}
|
|
else
|
|
close(fd);
|
|
}
|
|
/* tld::rules */
|
|
else if (!strcmp(cep->name, "rules"))
|
|
{
|
|
if (has_rules)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "tld::rules");
|
|
continue;
|
|
}
|
|
has_rules = 1;
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
if (((fd = open(cep->value, O_RDONLY)) == -1))
|
|
{
|
|
config_error("%s:%i: tld::rules: %s: %s",
|
|
cep->file->filename, cep->line_number,
|
|
cep->value, strerror(errno));
|
|
errors++;
|
|
}
|
|
else
|
|
close(fd);
|
|
}
|
|
/* tld::channel */
|
|
else if (!strcmp(cep->name, "channel"))
|
|
{
|
|
if (has_channel)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "tld::channel");
|
|
continue;
|
|
}
|
|
has_channel = 1;
|
|
}
|
|
/* tld::shortmotd */
|
|
else if (!strcmp(cep->name, "shortmotd"))
|
|
{
|
|
if (has_shortmotd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "tld::shortmotd");
|
|
continue;
|
|
}
|
|
has_shortmotd = 1;
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
if (((fd = open(cep->value, O_RDONLY)) == -1))
|
|
{
|
|
config_error("%s:%i: tld::shortmotd: %s: %s",
|
|
cep->file->filename, cep->line_number,
|
|
cep->value, strerror(errno));
|
|
errors++;
|
|
}
|
|
else
|
|
close(fd);
|
|
}
|
|
/* tld::opermotd */
|
|
else if (!strcmp(cep->name, "opermotd"))
|
|
{
|
|
if (has_opermotd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "tld::opermotd");
|
|
continue;
|
|
}
|
|
has_opermotd = 1;
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
if (((fd = open(cep->value, O_RDONLY)) == -1))
|
|
{
|
|
config_error("%s:%i: tld::opermotd: %s: %s",
|
|
cep->file->filename, cep->line_number,
|
|
cep->value, strerror(errno));
|
|
errors++;
|
|
}
|
|
else
|
|
close(fd);
|
|
}
|
|
/* tld::botmotd */
|
|
else if (!strcmp(cep->name, "botmotd"))
|
|
{
|
|
if (has_botmotd)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "tld::botmotd");
|
|
continue;
|
|
}
|
|
has_botmotd = 1;
|
|
convert_to_absolute_path(&cep->value, CONFDIR);
|
|
if (((fd = open(cep->value, O_RDONLY)) == -1))
|
|
{
|
|
config_error("%s:%i: tld::botmotd: %s: %s",
|
|
cep->file->filename, cep->line_number,
|
|
cep->value, strerror(errno));
|
|
errors++;
|
|
}
|
|
else
|
|
close(fd);
|
|
}
|
|
/* tld::options */
|
|
else if (!strcmp(cep->name, "options")) {
|
|
ConfigEntry *cep2;
|
|
|
|
if (has_options)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "tld::options");
|
|
continue;
|
|
}
|
|
has_options = 1;
|
|
|
|
for (cep2 = cep->items; cep2; cep2 = cep2->next)
|
|
{
|
|
if (strcmp(cep2->name, "ssl") &&
|
|
strcmp(cep2->name, "tls") &&
|
|
strcmp(cep2->name, "remote"))
|
|
{
|
|
config_error_unknownopt(cep2->file->filename,
|
|
cep2->line_number, "tld", cep2->name);
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
else
|
|
{
|
|
config_error_unknown(cep->file->filename, cep->line_number,
|
|
"tld", cep->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
if (!has_mask && !has_match)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"tld::match");
|
|
errors++;
|
|
}
|
|
if (has_mask && has_match)
|
|
{
|
|
config_error("%s:%d: You cannot have both ::mask and ::match. "
|
|
"You should only use %s::match.",
|
|
ce->file->filename, ce->line_number, ce->name);
|
|
errors++;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
/* Helper for _conf_listen() */
|
|
void conf_listen_configure(const char *ip, int port, SocketType socket_type, int options, ConfigEntry *ce, ConfigEntry *tlsconfig)
|
|
{
|
|
ConfigItem_listen *listen;
|
|
ConfigEntry *cep, *cepp;
|
|
Hook *h;
|
|
char isnew = 0;
|
|
|
|
if (!(listen = find_listen(ip, port, socket_type)))
|
|
{
|
|
listen = safe_alloc(sizeof(ConfigItem_listen));
|
|
if (socket_type == SOCKET_TYPE_UNIX)
|
|
{
|
|
safe_strdup(listen->file, ip);
|
|
} else {
|
|
safe_strdup(listen->ip, ip);
|
|
listen->port = port;
|
|
}
|
|
listen->fd = -1;
|
|
listen->socket_type = socket_type;
|
|
isnew = 1;
|
|
}
|
|
|
|
if (listen->options & LISTENER_BOUND)
|
|
options |= LISTENER_BOUND;
|
|
listen->options = options;
|
|
|
|
if (isnew)
|
|
AddListItem(listen, conf_listen);
|
|
|
|
/* Reset all settings of the current listener (free and set defaults): */
|
|
listen->flag.temporary = 0;
|
|
listen->start_handshake = start_of_normal_client_handshake;
|
|
if (listen->ssl_ctx)
|
|
{
|
|
SSL_CTX_free(listen->ssl_ctx);
|
|
listen->ssl_ctx = NULL;
|
|
}
|
|
if (listen->tls_options)
|
|
{
|
|
free_tls_options(listen->tls_options);
|
|
listen->tls_options = NULL;
|
|
}
|
|
safe_free(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;
|
|
}
|
|
}
|
|
}
|
|
|
|
RunHook(HOOKTYPE_CONFIG_LISTENER, listen);
|
|
}
|
|
|
|
int _conf_listen(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep, *cepp;
|
|
ConfigEntry *tlsconfig = NULL;
|
|
char *file = NULL;
|
|
char *ip = NULL;
|
|
char *spoof_ip = NULL;
|
|
int start=0, end=0, port;
|
|
int listener_flags =0;
|
|
Hook *h;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "file"))
|
|
{
|
|
convert_to_absolute_path(&cep->value, PERMDATADIR);
|
|
file = cep->value;
|
|
} else
|
|
if (!strcmp(cep->name, "mode"))
|
|
{
|
|
// Handled elsewhere, but need to be caught here as noop
|
|
} else
|
|
if (!strcmp(cep->name, "ip"))
|
|
{
|
|
ip = cep->value;
|
|
} else
|
|
if (!strcmp(cep->name, "spoof-ip"))
|
|
{
|
|
spoof_ip = cep->value;
|
|
} else
|
|
if (!strcmp(cep->name, "port"))
|
|
{
|
|
port_range(cep->value, &start, &end);
|
|
if ((start < 0) || (start > 65535) || (end < 0) || (end > 65535))
|
|
return -1; /* this is already validated in _test_listen, but okay.. */
|
|
} else
|
|
if (!strcmp(cep->name, "options"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
long v;
|
|
if ((v = nv_find_by_name(_ListenerFlags, cepp->name)))
|
|
{
|
|
listener_flags |= v;
|
|
} else {
|
|
for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
|
|
{
|
|
int value = (*(h->func.intfunc))(conf, cepp, CONFIG_LISTEN_OPTIONS);
|
|
if (value == 1)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
} else
|
|
if (!strcmp(cep->name, "ssl-options") || !strcmp(cep->name, "tls-options"))
|
|
{
|
|
tlsconfig = cep;
|
|
} else
|
|
{
|
|
for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
|
|
{
|
|
int value = (*(h->func.intfunc))(conf, cep, CONFIG_LISTEN);
|
|
if (value == 1)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
|
|
/* UNIX domain socket */
|
|
if (file)
|
|
{
|
|
conf_listen_configure(file, 0, SOCKET_TYPE_UNIX, listener_flags, ce, tlsconfig);
|
|
return 1;
|
|
}
|
|
|
|
for (port = start; port <= end; port++)
|
|
{
|
|
/* First deal with IPv4 */
|
|
if (!strchr(ip, ':'))
|
|
conf_listen_configure(ip, port, SOCKET_TYPE_IPV4, listener_flags, ce, tlsconfig);
|
|
|
|
/* Then deal with IPv6 (if available/enabled) */
|
|
if (!DISABLE_IPV6 && (strchr(ip, ':') || (*ip == '*')))
|
|
conf_listen_configure(ip, port, SOCKET_TYPE_IPV6, listener_flags, ce, tlsconfig);
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
int _test_listen(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
ConfigEntry *cepp;
|
|
int errors = 0;
|
|
char has_file = 0, has_ip = 0, has_port = 0, has_options = 0, port_6667 = 0, has_spoof_ip = 0;
|
|
char *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) < 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) < 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) < 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"))
|
|
parse_ban_action_config(cep, &ca->action);
|
|
}
|
|
AddListItem(ca, conf_ban);
|
|
return 0;
|
|
}
|
|
|
|
int _test_ban(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
Hook *h;
|
|
char type = 0;
|
|
char has_mask = 0, has_action = 0, has_reason = 0;
|
|
|
|
if (!ce->value)
|
|
{
|
|
config_error("%s:%i: ban without type",
|
|
ce->file->filename, ce->line_number);
|
|
return 1;
|
|
}
|
|
else if (!strcmp(ce->value, "server"))
|
|
{}
|
|
else if (!strcmp(ce->value, "realname"))
|
|
{}
|
|
else if (!strcmp(ce->value, "version"))
|
|
type = 'v';
|
|
else
|
|
{
|
|
int used = 0;
|
|
for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
|
|
{
|
|
int value, errs = 0;
|
|
if (h->owner && !(h->owner->flags & MODFLAG_TESTING)
|
|
&& !(h->owner->options & MOD_OPT_PERM))
|
|
continue;
|
|
value = (*(h->func.intfunc))(conf,ce,CONFIG_BAN, &errs);
|
|
if (value == 2)
|
|
used = 1;
|
|
if (value == 1)
|
|
{
|
|
used = 1;
|
|
break;
|
|
}
|
|
if (value == -1)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
break;
|
|
}
|
|
if (value == -2)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
}
|
|
}
|
|
if (!used) {
|
|
config_error("%s:%i: unknown ban type %s",
|
|
ce->file->filename, ce->line_number,
|
|
ce->value);
|
|
return 1;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (config_is_blankorempty(cep, "ban"))
|
|
{
|
|
errors++;
|
|
continue;
|
|
}
|
|
if (!strcmp(cep->name, "mask"))
|
|
{
|
|
if (has_mask)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "ban::mask");
|
|
continue;
|
|
}
|
|
has_mask = 1;
|
|
}
|
|
else if (!strcmp(cep->name, "reason"))
|
|
{
|
|
if (has_reason)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "ban::reason");
|
|
continue;
|
|
}
|
|
has_reason = 1;
|
|
}
|
|
else if (!strcmp(cep->name, "action"))
|
|
{
|
|
if (has_action)
|
|
{
|
|
config_warn_duplicate(cep->file->filename,
|
|
cep->line_number, "ban::action");
|
|
}
|
|
has_action = 1;
|
|
errors += test_ban_action_config(cep);
|
|
}
|
|
}
|
|
|
|
if (!has_mask)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"ban::mask");
|
|
errors++;
|
|
}
|
|
if (!has_reason)
|
|
{
|
|
config_error_missing(ce->file->filename, ce->line_number,
|
|
"ban::reason");
|
|
errors++;
|
|
}
|
|
if (has_action && type != 'v')
|
|
{
|
|
config_error("%s:%d: ban::action specified even though type is not 'version'",
|
|
ce->file->filename, ce->line_number);
|
|
errors++;
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
int _conf_require(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
Hook *h;
|
|
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;
|
|
int n;
|
|
|
|
if (ce->value)
|
|
return config_set_security_group(conf, ce);
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "kline-address")) {
|
|
safe_strdup(tempiConf.kline_address, cep->value);
|
|
}
|
|
if (!strcmp(cep->name, "gline-address")) {
|
|
safe_strdup(tempiConf.gline_address, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "modes-on-oper")) {
|
|
tempiConf.oper_modes = (long) set_usermode(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "modes-on-join")) {
|
|
conf_channelmodes(cep->value, &tempiConf.modes_on_join);
|
|
tempiConf.modes_on_join_set = 1;
|
|
}
|
|
else if (!strcmp(cep->name, "snomask-on-oper")) {
|
|
safe_strdup(tempiConf.oper_snomask, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "server-notice-colors")) {
|
|
tempiConf.server_notice_colors = config_checkval(cep->value, CFG_YESNO);
|
|
}
|
|
else if (!strcmp(cep->name, "server-notice-show-event")) {
|
|
tempiConf.server_notice_show_event = config_checkval(cep->value, CFG_YESNO);
|
|
}
|
|
else if (!strcmp(cep->name, "level-on-join")) {
|
|
const char *res = channellevel_to_string(cep->value); /* 'halfop', etc */
|
|
if (!res)
|
|
{
|
|
/* This check needs to be here, in config run, because
|
|
* now the channel modules are initialized and we know
|
|
* which ones are available. This same information is
|
|
* not available during config test, so we can't test
|
|
* for it there like we normally do.
|
|
*/
|
|
if (!valid_channel_access_mode_letter(*cep->value))
|
|
{
|
|
config_warn("%s:%d: set::level-on-join: Unknown mode (access level) '%c'. "
|
|
"That mode does not exist or is not a valid access mode "
|
|
"like vhoaq.",
|
|
cep->file->filename, cep->line_number,
|
|
*cep->value);
|
|
config_warn("Falling back to to set::level-on-join none; now. "
|
|
"This is probably not what you want!!!");
|
|
}
|
|
res = cep->value; /* if we reach this.. then it is a single letter */
|
|
}
|
|
safe_strdup(tempiConf.level_on_join, res);
|
|
}
|
|
else if (!strcmp(cep->name, "who-limit")) {
|
|
tempiConf.who_limit = atol(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "maxbans")) {
|
|
tempiConf.maxbans = atol(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "silence-limit")) {
|
|
tempiConf.silence_limit = atol(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "oper-auto-join")) {
|
|
safe_strdup(tempiConf.oper_auto_join_chans, cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "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-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, "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") || !strcmp(cep4->name, "action"))
|
|
parse_ban_action_config(cep4, &tempiConf.handshake_data_flood_ban_action);
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "away-flood"))
|
|
{
|
|
config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_AWAY);
|
|
}
|
|
else if (!strcmp(ceppp->name, "nick-flood"))
|
|
{
|
|
config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_NICK);
|
|
}
|
|
else if (!strcmp(ceppp->name, "vhost-flood"))
|
|
{
|
|
config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_VHOST);
|
|
}
|
|
else if (!strcmp(ceppp->name, "join-flood"))
|
|
{
|
|
config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_JOIN);
|
|
}
|
|
else if (!strcmp(ceppp->name, "invite-flood"))
|
|
{
|
|
config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_INVITE);
|
|
}
|
|
else if (!strcmp(ceppp->name, "knock-flood"))
|
|
{
|
|
config_parse_flood_generic(ceppp->value, &tempiConf, cepp->name, FLD_KNOCK);
|
|
}
|
|
else if (!strcmp(ceppp->name, "lag-penalty"))
|
|
{
|
|
lag_penalty = atoi(ceppp->value);
|
|
}
|
|
else if (!strcmp(ceppp->name, "lag-penalty-bytes"))
|
|
{
|
|
lag_penalty_bytes = config_checkval(ceppp->value, CFG_SIZE);
|
|
if (lag_penalty_bytes <= 0)
|
|
lag_penalty_bytes = INT_MAX;
|
|
}
|
|
else if (!strcmp(ceppp->name, "connect-flood"))
|
|
{
|
|
int cnt, period;
|
|
config_parse_flood(ceppp->value, &cnt, &period);
|
|
tempiConf.throttle_count = cnt;
|
|
tempiConf.throttle_period = period;
|
|
}
|
|
else if (!strcmp(ceppp->name, "max-concurrent-conversations"))
|
|
{
|
|
/* We use a hack here to make it fit our storage format */
|
|
char buf[64];
|
|
int users=0;
|
|
long every=0;
|
|
for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
|
|
{
|
|
if (!strcmp(cep4->name, "users"))
|
|
{
|
|
users = atoi(cep4->value);
|
|
} else
|
|
if (!strcmp(cep4->name, "new-user-every"))
|
|
{
|
|
every = config_checkval(cep4->value, CFG_TIME);
|
|
}
|
|
}
|
|
snprintf(buf, sizeof(buf), "%d:%ld", users, every);
|
|
config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_CONVERSATIONS);
|
|
}
|
|
}
|
|
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(cepp->name, "show-message-content-on-hit"))
|
|
{
|
|
tempiConf.spamfilter_show_message_content_on_hit = spamfilter_show_message_content_on_hit_strtoval(cepp->value);
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "central-spamfilter"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "url"))
|
|
safe_strdup(tempiConf.central_spamfilter_url, cepp->value);
|
|
else if (!strcmp(cepp->name, "refresh-time"))
|
|
tempiConf.central_spamfilter_refresh_time = config_checkval(cepp->value, CFG_TIME);
|
|
else if (!strcmp(cepp->name, "verbose"))
|
|
tempiConf.central_spamfilter_verbose = atoi(cepp->value);
|
|
else if (!strcmp(cepp->name, "enabled"))
|
|
tempiConf.central_spamfilter_enabled = config_checkval(cepp->value, CFG_YESNO);
|
|
else if (!strcmp(cepp->name, "feed"))
|
|
safe_strdup(tempiConf.central_spamfilter_feed, cepp->value);
|
|
else if (!strcmp(cepp->name, "except"))
|
|
conf_match_block(conf, cepp, &tempiConf.central_spamfilter_except);
|
|
else if (!strcmp(cepp->name, "limit-ban-action"))
|
|
tempiConf.central_spamfilter_limit_ban_action = banact_stringtoval(cepp->value);
|
|
else if (!strcmp(cepp->name, "limit-ban-time"))
|
|
tempiConf.central_spamfilter_limit_ban_time = config_checkval(cepp->value, CFG_TIME);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "default-bantime"))
|
|
{
|
|
tempiConf.default_bantime = config_checkval(cep->value,CFG_TIME);
|
|
}
|
|
else if (!strcmp(cep->name, "ban-version-tkl-time"))
|
|
{
|
|
tempiConf.ban_version_tkl_time = config_checkval(cep->value,CFG_TIME);
|
|
}
|
|
else if (!strcmp(cep->name, "min-nick-length")) {
|
|
int v = atoi(cep->value);
|
|
tempiConf.min_nick_length = v;
|
|
}
|
|
else if (!strcmp(cep->name, "nick-length")) {
|
|
int v = atoi(cep->value);
|
|
tempiConf.nick_length = v;
|
|
}
|
|
else if (!strcmp(cep->name, "topic-length")) {
|
|
int v = atoi(cep->value);
|
|
tempiConf.topic_length = v;
|
|
}
|
|
else if (!strcmp(cep->name, "away-length")) {
|
|
int v = atoi(cep->value);
|
|
tempiConf.away_length = v;
|
|
}
|
|
else if (!strcmp(cep->name, "kick-length")) {
|
|
int v = atoi(cep->value);
|
|
tempiConf.kick_length = v;
|
|
}
|
|
else if (!strcmp(cep->name, "quit-length")) {
|
|
int v = atoi(cep->value);
|
|
tempiConf.quit_length = v;
|
|
}
|
|
else if (!strcmp(cep->name, "ssl") || !strcmp(cep->name, "tls")) {
|
|
/* no need to alloc tempiConf.tls_options since config_defaults() already ensures it exists */
|
|
conf_tlsblock(conf, cep, tempiConf.tls_options);
|
|
}
|
|
else if (!strcmp(cep->name, "plaintext-policy"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "user"))
|
|
tempiConf.plaintext_policy_user = policy_strtoval(cepp->value);
|
|
else if (!strcmp(cepp->name, "oper"))
|
|
tempiConf.plaintext_policy_oper = policy_strtoval(cepp->value);
|
|
else if (!strcmp(cepp->name, "server"))
|
|
tempiConf.plaintext_policy_server = policy_strtoval(cepp->value);
|
|
else if (!strcmp(cepp->name, "user-message"))
|
|
addmultiline(&tempiConf.plaintext_policy_user_message, cepp->value);
|
|
else if (!strcmp(cepp->name, "oper-message"))
|
|
addmultiline(&tempiConf.plaintext_policy_oper_message, cepp->value);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "outdated-tls-policy"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "user"))
|
|
tempiConf.outdated_tls_policy_user = policy_strtoval(cepp->value);
|
|
else if (!strcmp(cepp->name, "oper"))
|
|
tempiConf.outdated_tls_policy_oper = policy_strtoval(cepp->value);
|
|
else if (!strcmp(cepp->name, "server"))
|
|
tempiConf.outdated_tls_policy_server = policy_strtoval(cepp->value);
|
|
else if (!strcmp(cepp->name, "user-message"))
|
|
safe_strdup(tempiConf.outdated_tls_policy_user_message, cepp->value);
|
|
else if (!strcmp(cepp->name, "oper-message"))
|
|
safe_strdup(tempiConf.outdated_tls_policy_oper_message, cepp->value);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "default-ipv6-clone-mask"))
|
|
{
|
|
tempiConf.default_ipv6_clone_mask = atoi(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "hide-list")) {
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "deny-channel"))
|
|
{
|
|
tempiConf.hide_list = 1;
|
|
/* if we would expand this later then change this to a bitmask or struct or whatever */
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "max-unknown-connections-per-ip"))
|
|
{
|
|
tempiConf.max_unknown_connections_per_ip = atoi(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "handshake-timeout"))
|
|
{
|
|
tempiConf.handshake_timeout = config_checkval(cep->value, CFG_TIME);
|
|
}
|
|
else if (!strcmp(cep->name, "sasl-timeout"))
|
|
{
|
|
tempiConf.sasl_timeout = config_checkval(cep->value, CFG_TIME);
|
|
}
|
|
else if (!strcmp(cep->name, "handshake-delay"))
|
|
{
|
|
tempiConf.handshake_delay = config_checkval(cep->value, CFG_TIME);
|
|
}
|
|
else if (!strcmp(cep->name, "handshake-boot-delay"))
|
|
{
|
|
tempiConf.handshake_boot_delay = config_checkval(cep->value, CFG_TIME);
|
|
}
|
|
else if (!strcmp(cep->name, "automatic-ban-target"))
|
|
{
|
|
tempiConf.automatic_ban_target = ban_target_strtoval(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "manual-ban-target"))
|
|
{
|
|
tempiConf.manual_ban_target = ban_target_strtoval(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "reject-message"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "too-many-connections"))
|
|
safe_strdup(tempiConf.reject_message_too_many_connections, cepp->value);
|
|
else if (!strcmp(cepp->name, "server-full"))
|
|
safe_strdup(tempiConf.reject_message_server_full, cepp->value);
|
|
else if (!strcmp(cepp->name, "unauthorized"))
|
|
safe_strdup(tempiConf.reject_message_unauthorized, cepp->value);
|
|
else if (!strcmp(cepp->name, "kline"))
|
|
safe_strdup(tempiConf.reject_message_kline, cepp->value);
|
|
else if (!strcmp(cepp->name, "gline"))
|
|
safe_strdup(tempiConf.reject_message_gline, cepp->value);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "topic-setter"))
|
|
{
|
|
if (!strcmp(cep->value, "nick"))
|
|
tempiConf.topic_setter = SETTER_NICK;
|
|
else if (!strcmp(cep->value, "nick-user-host"))
|
|
tempiConf.topic_setter = SETTER_NICK_USER_HOST;
|
|
}
|
|
else if (!strcmp(cep->name, "ban-setter"))
|
|
{
|
|
if (!strcmp(cep->value, "nick"))
|
|
tempiConf.ban_setter = SETTER_NICK;
|
|
else if (!strcmp(cep->value, "nick-user-host"))
|
|
tempiConf.ban_setter = SETTER_NICK_USER_HOST;
|
|
}
|
|
else if (!strcmp(cep->name, "ban-setter-sync") || !strcmp(cep->name, "ban-setter-synch"))
|
|
{
|
|
tempiConf.ban_setter_sync = config_checkval(cep->value, CFG_YESNO);
|
|
}
|
|
else if (!strcmp(cep->name, "part-instead-of-quit-on-comment-change"))
|
|
{
|
|
tempiConf.part_instead_of_quit_on_comment_change = config_checkval(cep->value, CFG_YESNO);
|
|
}
|
|
else if (!strcmp(cep->name, "broadcast-channel-messages"))
|
|
{
|
|
if (!strcmp(cep->value, "auto"))
|
|
tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_AUTO;
|
|
else if (!strcmp(cep->value, "always"))
|
|
tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_ALWAYS;
|
|
else if (!strcmp(cep->value, "never"))
|
|
tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_NEVER;
|
|
}
|
|
else if (!strcmp(cep->name, "allowed-channelchars"))
|
|
{
|
|
tempiConf.allowed_channelchars = allowed_channelchars_strtoval(cep->value);
|
|
}
|
|
else if (!strcmp(cep->name, "hide-idle-time"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "policy"))
|
|
tempiConf.hide_idle_time = hideidletime_strtoval(cepp->value);
|
|
}
|
|
} else if (!strcmp(cep->name, "limit-svscmds"))
|
|
{
|
|
if (!strcmp(cep->value, "ulines"))
|
|
tempiConf.limit_svscmds = LIMIT_SVSCMDS_ULINES;
|
|
else
|
|
tempiConf.limit_svscmds = LIMIT_SVSCMDS_SERVERS;
|
|
} else if (!strcmp(cep->name, "high-connection-rate"))
|
|
{
|
|
tempiConf.high_connection_rate = atoi(cep->value);
|
|
} else if (!strcmp(cep->name, "best-practices"))
|
|
{
|
|
/* This is handled in config test already (there is no other way) */
|
|
} else if (!strcmp(cep->name, "dns"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "client"))
|
|
{
|
|
for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
|
|
{
|
|
if (!strcmp(ceppp->name, "timeout"))
|
|
tempiConf.dns_client_timeout = atoi(ceppp->value);
|
|
else if (!strcmp(ceppp->name, "retry"))
|
|
tempiConf.dns_client_retry = atoi(ceppp->value);
|
|
}
|
|
} else
|
|
if (!strcmp(cepp->name, "dnsbl"))
|
|
{
|
|
for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
|
|
{
|
|
if (!strcmp(ceppp->name, "timeout"))
|
|
tempiConf.dns_dnsbl_timeout = atoi(ceppp->value);
|
|
else if (!strcmp(ceppp->name, "retry"))
|
|
tempiConf.dns_dnsbl_retry = atoi(ceppp->value);
|
|
}
|
|
}
|
|
}
|
|
} else if (config_set_dynamic_set_block_item(conf, &dynamic_set, cep))
|
|
{
|
|
/* Handled by config_set_dynamic_set_block_item - nothing to do here */
|
|
} else
|
|
{
|
|
/* Call modules that hook HOOKTYPE_CONFIGRUN */
|
|
int value;
|
|
for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next)
|
|
{
|
|
value = (*(h->func.intfunc))(conf,cep,CONFIG_SET);
|
|
if (value == 1)
|
|
break;
|
|
}
|
|
}
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
int _test_set(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep, *cepp, *ceppp, *cep4;
|
|
int tempi;
|
|
int errors = 0;
|
|
int n;
|
|
Hook *h;
|
|
|
|
if (ce->value)
|
|
return test_set_security_group(conf, ce);
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "kline-address")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, kline_address, "kline-address");
|
|
if (!strchr(cep->value, '@') && !strchr(cep->value, ':'))
|
|
{
|
|
config_error("%s:%i: set::kline-address must be an e-mail or an URL",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
else if (match_simple("*@unrealircd.com", cep->value) || match_simple("*@unrealircd.org",cep->value) || match_simple("unreal-*@lists.sourceforge.net",cep->value))
|
|
{
|
|
config_error("%s:%i: set::kline-address may not be an UnrealIRCd Team address",
|
|
cep->file->filename, cep->line_number);
|
|
errors++; continue;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "gline-address")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, gline_address, "gline-address");
|
|
if (!strchr(cep->value, '@') && !strchr(cep->value, ':'))
|
|
{
|
|
config_error("%s:%i: set::gline-address must be an e-mail or an URL",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
else if (match_simple("*@unrealircd.com", cep->value) || match_simple("*@unrealircd.org",cep->value) || match_simple("unreal-*@lists.sourceforge.net",cep->value))
|
|
{
|
|
config_error("%s:%i: set::gline-address may not be an UnrealIRCd Team address",
|
|
cep->file->filename, cep->line_number);
|
|
errors++; continue;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "modes-on-join")) {
|
|
char *c;
|
|
struct ChMode temp;
|
|
memset(&temp, 0, sizeof(temp));
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, modes_on_join, "modes-on-join");
|
|
for (c = cep->value; *c; c++)
|
|
{
|
|
if (*c == ' ')
|
|
break; /* don't check the parameter ;p */
|
|
switch (*c)
|
|
{
|
|
case 'q':
|
|
case 'a':
|
|
case 'o':
|
|
case 'h':
|
|
case 'v':
|
|
case 'b':
|
|
case 'e':
|
|
case 'I':
|
|
config_error("%s:%i: set::modes-on-join may not contain +%c",
|
|
cep->file->filename, cep->line_number, *c);
|
|
errors++;
|
|
break;
|
|
}
|
|
}
|
|
/* We can't really verify much here.
|
|
* The channel mode modules have not been initialized
|
|
* yet at this point, so we can't really verify much
|
|
* here.
|
|
*/
|
|
}
|
|
else if (!strcmp(cep->name, "modes-on-oper")) {
|
|
char *p;
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, modes_on_oper, "modes-on-oper");
|
|
for (p = cep->value; *p; p++)
|
|
if (strchr("orzS", *p))
|
|
{
|
|
config_error("%s:%i: set::modes-on-oper may not include mode '%c'",
|
|
cep->file->filename, cep->line_number, *p);
|
|
errors++;
|
|
}
|
|
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, "who-limit")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, who_limit, "who-limit");
|
|
if (!config_checkval(cep->value,CFG_SIZE))
|
|
{
|
|
config_error("%s:%i: set::who-limit: value must be at least 1",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "maxbans")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, maxbans, "maxbans");
|
|
}
|
|
else if (!strcmp(cep->name, "maxbanlength")) {
|
|
config_warn("%s:%d: set::maxbanlength no longer exists as it was not deemed useful, this setting is now ignored.",
|
|
cep->file->filename, cep->line_number);
|
|
}
|
|
else if (!strcmp(cep->name, "silence-limit")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, silence_limit, "silence-limit");
|
|
}
|
|
else if (!strcmp(cep->name, "oper-auto-join")) {
|
|
CheckNull(cep);
|
|
CheckDuplicate(cep, oper_auto_join, "oper-auto-join");
|
|
}
|
|
else if (!strcmp(cep->name, "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, "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-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, "maxchannelsperuser"))
|
|
{
|
|
CheckNull(ceppp);
|
|
}
|
|
else if (!strcmp(ceppp->name, "unknown-flood-amount") ||
|
|
!strcmp(ceppp->name, "unknown-flood-bantime"))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::%s: this setting has been moved. "
|
|
"See https://www.unrealircd.org/docs/Anti-flood_settings#handshake-data-flood",
|
|
ceppp->file->filename, ceppp->line_number, ceppp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
else if (!strcmp(ceppp->name, "handshake-data-flood"))
|
|
{
|
|
for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
|
|
{
|
|
if (!strcmp(cep4->name, "amount"))
|
|
{
|
|
long v;
|
|
CheckNull(cep4);
|
|
v = config_checkval(cep4->value, CFG_SIZE);
|
|
if (v < 1024)
|
|
{
|
|
config_error("%s:%i: set::anti-flood::handshake-data-flood::amount must be at least 1024 bytes",
|
|
cep4->file->filename, cep4->line_number);
|
|
errors++;
|
|
}
|
|
} else
|
|
if (!strcmp(cep4->name, "ban-action") || !strcmp(cep4->name, "action"))
|
|
{
|
|
CheckNull(cep4);
|
|
errors += test_ban_action_config(cep4);
|
|
} else
|
|
if (!strcmp(cep4->name, "ban-time"))
|
|
{
|
|
CheckNull(cep4);
|
|
} else
|
|
{
|
|
config_error_unknownopt(cep4->file->filename,
|
|
cep4->line_number, "set::anti-flood::handshake-data-flood",
|
|
cep4->name);
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "away-count"))
|
|
{
|
|
int temp = atol(ceppp->value);
|
|
CheckNull(ceppp);
|
|
if (temp < 1 || temp > 255)
|
|
{
|
|
config_error("%s:%i: set::anti-flood::away-count must be between 1 and 255",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "away-period"))
|
|
{
|
|
CheckNull(ceppp);
|
|
int temp = config_checkval(ceppp->value, CFG_TIME);
|
|
if (temp < 10)
|
|
{
|
|
config_error("%s:%i: set::anti-flood::away-period must be greater than 9",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "away-flood"))
|
|
{
|
|
int cnt, period;
|
|
CheckNull(ceppp);
|
|
if (!config_parse_flood(ceppp->value, &cnt, &period) ||
|
|
(cnt < 1) || (cnt > 255) || (period < 10))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::away-flood error. Syntax is '<count>:<period>' (eg 5:60), "
|
|
"count should be 1-255, period should be greater than 9",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "nick-flood"))
|
|
{
|
|
int cnt, period;
|
|
CheckNull(ceppp);
|
|
if (!config_parse_flood(ceppp->value, &cnt, &period) ||
|
|
(cnt < 1) || (cnt > 255) || (period < 5))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::nick-flood error. Syntax is '<count>:<period>' (eg 5:60), "
|
|
"count should be 1-255, period should be greater than 4",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "vhost-flood"))
|
|
{
|
|
int cnt, period;
|
|
CheckNull(ceppp);
|
|
if (!config_parse_flood(ceppp->value, &cnt, &period) ||
|
|
(cnt < 1) || (cnt > 255) || (period < 5))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::vhost-flood error. Syntax is '<count>:<period>' (eg 5:60), "
|
|
"count should be 1-255, period should be greater than 4",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "join-flood"))
|
|
{
|
|
int cnt, period;
|
|
CheckNull(ceppp);
|
|
|
|
if (!config_parse_flood(ceppp->value, &cnt, &period) ||
|
|
(cnt < 1) || (cnt > 255) || (period < 5))
|
|
{
|
|
config_error("%s:%i: join-flood error. Syntax is '<count>:<period>' (eg 5:60), "
|
|
"count should be 1-255, period should be greater than 4",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "invite-flood"))
|
|
{
|
|
int cnt, period;
|
|
CheckNull(ceppp);
|
|
if (!config_parse_flood(ceppp->value, &cnt, &period) ||
|
|
(cnt < 1) || (cnt > 255) || (period < 5))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::invite-flood error. Syntax is '<count>:<period>' (eg 5:60), "
|
|
"count should be 1-255, period should be greater than 4",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "knock-flood"))
|
|
{
|
|
int cnt, period;
|
|
CheckNull(ceppp);
|
|
if (!config_parse_flood(ceppp->value, &cnt, &period) ||
|
|
(cnt < 1) || (cnt > 255) || (period < 5))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::knock-flood error. Syntax is '<count>:<period>' (eg 5:60), "
|
|
"count should be 1-255, period should be greater than 4",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "lag-penalty"))
|
|
{
|
|
int v;
|
|
CheckNull(ceppp);
|
|
v = atoi(ceppp->value);
|
|
has_lag_penalty = 1;
|
|
if ((v < 0) || (v > 10000))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::%s::lag-penalty: value is in milliseconds and should be between 0 and 10000",
|
|
ceppp->file->filename, ceppp->line_number, cepp->name);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "lag-penalty-bytes"))
|
|
{
|
|
has_lag_penalty_bytes = 1;
|
|
CheckNull(ceppp);
|
|
}
|
|
else if (!strcmp(ceppp->name, "connect-flood"))
|
|
{
|
|
int cnt, period;
|
|
CheckNull(ceppp);
|
|
if (strcmp(cepp->name, "everyone"))
|
|
{
|
|
config_error("%s:%i: connect-flood must be in the set::anti-flood::everyone block, not anywhere else.",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
continue;
|
|
}
|
|
if (!config_parse_flood(ceppp->value, &cnt, &period) ||
|
|
(cnt < 1) || (cnt > 255) || (period < 1) || (period > 3600))
|
|
{
|
|
config_error("%s:%i: set::anti-flood::connect-flood: Syntax is '<count>:<period>' (eg 5:60), "
|
|
"count should be 1-255, period should be 1-3600",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(ceppp->name, "maxchannelsperuser"))
|
|
{
|
|
CheckNull(ceppp);
|
|
}
|
|
else
|
|
{
|
|
config_error_unknownopt(ceppp->file->filename,
|
|
ceppp->line_number, "set::anti-flood",
|
|
ceppp->name);
|
|
errors++;
|
|
}
|
|
}
|
|
if (has_lag_penalty+has_lag_penalty_bytes == 1)
|
|
{
|
|
config_error("%s:%i: set::anti-flood::%s: if you use lag-penalty then you must also add an lag-penalty-bytes item (and vice-versa)",
|
|
cepp->file->filename, cepp->line_number, cepp->name);
|
|
errors++;
|
|
}
|
|
}
|
|
/* Now the warnings: */
|
|
if (anti_flood_old == 1)
|
|
{
|
|
config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible. "
|
|
"Your custom anti-flood settings have NOT been read.",
|
|
cep->file->filename, cep->line_number);
|
|
config_warn("See https://www.unrealircd.org/docs/Anti-flood_settings for the new block style,");
|
|
config_warn("OR: simply remove all the anti-flood options from the conf to get rid of this "
|
|
"warning and use the built-in defaults.");
|
|
} else
|
|
if (anti_flood_old_and_default == 1)
|
|
{
|
|
config_warn("%s:%d: the set::anti-flood block has been reorganized to be more flexible.",
|
|
cep->file->filename, cep->line_number);
|
|
config_warn("To fix this warning, delete the anti-flood block from your configuration file "
|
|
"(file %s around line %d), this will make UnrealIRCd use the built-in defaults.",
|
|
cep->file->filename, cep->line_number);
|
|
config_warn("If you want to learn more about the new functionality you can visit "
|
|
"https://www.unrealircd.org/docs/Anti-flood_settings");
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "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"))
|
|
{
|
|
tempiConf.spamfilter_utf8 = config_checkval(cepp->value, CFG_YESNO);
|
|
} else
|
|
if (!strcmp(cepp->name, "show-message-content-on-hit"))
|
|
{
|
|
if (!spamfilter_show_message_content_on_hit_strtoval(cepp->value))
|
|
{
|
|
config_error("%s:%d: set::spamfilter::show-message-content-on-hit: unknown value '%s', "
|
|
"must be one of: always, never, channel-only.",
|
|
cepp->file->filename, cepp->line_number, cepp->value);
|
|
errors++;
|
|
}
|
|
} else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::spamfilter",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "central-spamfilter"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "except"))
|
|
{
|
|
test_match_block(conf, cepp, &errors);
|
|
} else
|
|
if (!cepp->value)
|
|
{
|
|
CheckNull(cepp);
|
|
} else
|
|
if (!strcmp(cepp->name, "url"))
|
|
{
|
|
} else
|
|
if (!strcmp(cepp->name, "refresh-time"))
|
|
{
|
|
int v = config_checkval(cepp->value, CFG_TIME);
|
|
#ifndef DEBUGMODE
|
|
if (v < 180)
|
|
{
|
|
config_error("%s:%i: set::central-spamfilter::refresh-time needs to be more than 3 minutes",
|
|
cepp->file->filename, cepp->line_number);
|
|
errors++;
|
|
}
|
|
#endif
|
|
} else
|
|
if (!strcmp(cepp->name, "verbose"))
|
|
{
|
|
} else
|
|
if (!strcmp(cepp->name, "enabled"))
|
|
{
|
|
} else
|
|
if (!strcmp(cepp->name, "feed"))
|
|
{
|
|
if (strcmp(cepp->value, "testing") && strcmp(cepp->value, "standard") && strcmp(cepp->value, "fast"))
|
|
{
|
|
config_error("%s:%i: set::central-spamfilter::feed needs to be one of: "
|
|
"'fast' (for early access to new spamfilters) or 'standard' (the default).",
|
|
cepp->file->filename, cepp->line_number);
|
|
errors++;
|
|
}
|
|
} else
|
|
if (!strcmp(cepp->name, "limit-ban-action"))
|
|
{
|
|
int n = banact_stringtoval(cepp->value);
|
|
if (n == 0)
|
|
{
|
|
config_error("%s:%i: set::central-spamfilter::limit-ban-action: unknown ban action '%s'",
|
|
cepp->file->filename, cepp->line_number, cepp->value);
|
|
errors++;
|
|
} else
|
|
if ((n == BAN_ACT_SET) || (n == BAN_ACT_REPORT))
|
|
{
|
|
config_error("%s:%i: set::central-spamfilter::limit-ban-action: you cannot use 'set' or 'report' here",
|
|
cepp->file->filename, cepp->line_number);
|
|
errors++;
|
|
}
|
|
} else
|
|
if (!strcmp(cepp->name, "limit-ban-time"))
|
|
{
|
|
if (config_checkval(cepp->value, CFG_TIME) < 1)
|
|
{
|
|
config_error("%s:%i: set::central-spamfilter::limit-ban-time is zero or less (invalid)",
|
|
cepp->file->filename, cepp->line_number);
|
|
errors++;
|
|
}
|
|
} else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::central-spamfilter",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "default-bantime"))
|
|
{
|
|
long x;
|
|
CheckDuplicate(cep, default_bantime, "default-bantime");
|
|
CheckNull(cep);
|
|
x = config_checkval(cep->value,CFG_TIME);
|
|
if ((x < 0) > (x > 2000000000))
|
|
{
|
|
config_error("%s:%i: set::default-bantime: value '%ld' out of range",
|
|
cep->file->filename, cep->line_number, x);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "ban-version-tkl-time")) {
|
|
long x;
|
|
CheckDuplicate(cep, ban_version_tkl_time, "ban-version-tkl-time");
|
|
CheckNull(cep);
|
|
x = config_checkval(cep->value,CFG_TIME);
|
|
if ((x < 0) > (x > 2000000000))
|
|
{
|
|
config_error("%s:%i: set::ban-version-tkl-time: value '%ld' out of range",
|
|
cep->file->filename, cep->line_number, x);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "min-nick-length")) {
|
|
int v;
|
|
CheckDuplicate(cep, min_nick_length, "min-nick-length");
|
|
CheckNull(cep);
|
|
v = atoi(cep->value);
|
|
if ((v <= 0) || (v > NICKLEN))
|
|
{
|
|
config_error("%s:%i: set::min-nick-length: value '%d' out of range (should be 1-%d)",
|
|
cep->file->filename, cep->line_number, v, NICKLEN);
|
|
errors++;
|
|
}
|
|
else
|
|
nicklengths.min = v;
|
|
}
|
|
else if (!strcmp(cep->name, "nick-length")) {
|
|
int v;
|
|
CheckDuplicate(cep, nick_length, "nick-length");
|
|
CheckNull(cep);
|
|
v = atoi(cep->value);
|
|
if ((v <= 0) || (v > NICKLEN))
|
|
{
|
|
config_error("%s:%i: set::nick-length: value '%d' out of range (should be 1-%d)",
|
|
cep->file->filename, cep->line_number, v, NICKLEN);
|
|
errors++;
|
|
}
|
|
else
|
|
nicklengths.max = v;
|
|
}
|
|
else if (!strcmp(cep->name, "topic-length")) {
|
|
int v;
|
|
CheckNull(cep);
|
|
v = atoi(cep->value);
|
|
if ((v <= 0) || (v > MAXTOPICLEN))
|
|
{
|
|
config_error("%s:%i: set::topic-length: value '%d' out of range (should be 1-%d)",
|
|
cep->file->filename, cep->line_number, v, MAXTOPICLEN);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "away-length")) {
|
|
int v;
|
|
CheckNull(cep);
|
|
v = atoi(cep->value);
|
|
if ((v <= 0) || (v > MAXAWAYLEN))
|
|
{
|
|
config_error("%s:%i: set::away-length: value '%d' out of range (should be 1-%d)",
|
|
cep->file->filename, cep->line_number, v, MAXAWAYLEN);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "kick-length")) {
|
|
int v;
|
|
CheckNull(cep);
|
|
v = atoi(cep->value);
|
|
if ((v <= 0) || (v > MAXKICKLEN))
|
|
{
|
|
config_error("%s:%i: set::kick-length: value '%d' out of range (should be 1-%d)",
|
|
cep->file->filename, cep->line_number, v, MAXKICKLEN);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "quit-length")) {
|
|
int v;
|
|
CheckNull(cep);
|
|
v = atoi(cep->value);
|
|
if ((v <= 0) || (v > MAXQUITLEN))
|
|
{
|
|
config_error("%s:%i: set::quit-length: value '%d' out of range (should be 1-%d)",
|
|
cep->file->filename, cep->line_number, v, MAXQUITLEN);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "ssl") || !strcmp(cep->name, "tls")) {
|
|
test_tlsblock(conf, cep, &errors);
|
|
}
|
|
else if (!strcmp(cep->name, "plaintext-policy"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "user") ||
|
|
!strcmp(cepp->name, "oper") ||
|
|
!strcmp(cepp->name, "server"))
|
|
{
|
|
Policy policy;
|
|
CheckNull(cepp);
|
|
policy = policy_strtoval(cepp->value);
|
|
if (!policy)
|
|
{
|
|
config_error("%s:%i: set::plaintext-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'",
|
|
cepp->file->filename, cepp->line_number, cepp->name);
|
|
errors++;
|
|
}
|
|
} else if (!strcmp(cepp->name, "user-message") ||
|
|
!strcmp(cepp->name, "oper-message"))
|
|
{
|
|
CheckNull(cepp);
|
|
} else {
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::plaintext-policy",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "outdated-tls-policy"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "user") ||
|
|
!strcmp(cepp->name, "oper") ||
|
|
!strcmp(cepp->name, "server"))
|
|
{
|
|
Policy policy;
|
|
CheckNull(cepp);
|
|
policy = policy_strtoval(cepp->value);
|
|
if (!policy)
|
|
{
|
|
config_error("%s:%i: set::outdated-tls-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'",
|
|
cepp->file->filename, cepp->line_number, cepp->name);
|
|
errors++;
|
|
}
|
|
} else if (!strcmp(cepp->name, "user-message") ||
|
|
!strcmp(cepp->name, "oper-message"))
|
|
{
|
|
CheckNull(cepp);
|
|
} else {
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::outdated-tls-policy",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "default-ipv6-clone-mask"))
|
|
{
|
|
/* keep this in sync with _test_allow() */
|
|
int ipv6mask;
|
|
CheckNull(cep);
|
|
ipv6mask = atoi(cep->value);
|
|
if (ipv6mask == 0)
|
|
{
|
|
config_error("%s:%d: set::default-ipv6-clone-mask given a value of zero. This cannnot be correct, as it would treat all IPv6 hosts as one host.",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
if (ipv6mask > 128)
|
|
{
|
|
config_error("%s:%d: set::default-ipv6-clone-mask was set to %d. The maximum value is 128.",
|
|
cep->file->filename, cep->line_number,
|
|
ipv6mask);
|
|
errors++;
|
|
}
|
|
if (ipv6mask <= 32)
|
|
{
|
|
config_warn("%s:%d: set::default-ipv6-clone-mask was given a very small value.",
|
|
cep->file->filename, cep->line_number);
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "hide-list")) {
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "deny-channel"))
|
|
{
|
|
} else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::hide-list",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "max-unknown-connections-per-ip")) {
|
|
int v;
|
|
CheckNull(cep);
|
|
v = atoi(cep->value);
|
|
if (v < 1)
|
|
{
|
|
config_error("%s:%i: set::max-unknown-connections-per-ip: value should be at least 1.",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "handshake-timeout")) {
|
|
int v;
|
|
CheckNull(cep);
|
|
v = config_checkval(cep->value, CFG_TIME);
|
|
if (v < 5)
|
|
{
|
|
config_error("%s:%i: set::handshake-timeout: value should be at least 5 seconds.",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "sasl-timeout")) {
|
|
int v;
|
|
CheckNull(cep);
|
|
v = config_checkval(cep->value, CFG_TIME);
|
|
if (v < 5)
|
|
{
|
|
config_error("%s:%i: set::sasl-timeout: value should be at least 5 seconds.",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "handshake-delay"))
|
|
{
|
|
int v;
|
|
CheckNull(cep);
|
|
v = config_checkval(cep->value, CFG_TIME);
|
|
if (v >= 10)
|
|
{
|
|
config_error("%s:%i: set::handshake-delay: value should be less than 10 seconds.",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "handshake-boot-delay"))
|
|
{
|
|
int v;
|
|
CheckNull(cep);
|
|
v = config_checkval(cep->value, CFG_TIME);
|
|
if (v > 15)
|
|
{
|
|
config_error("%s:%i: set::handshake-boot-delay: value cannot be more than 15 seconds.",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "ban-include-username"))
|
|
{
|
|
config_error("%s:%i: set::ban-include-username is no longer supported. "
|
|
"Use set { automatic-ban-target userip; }; instead.",
|
|
cep->file->filename, cep->line_number);
|
|
config_error("See https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target "
|
|
"for more information and options.");
|
|
errors++;
|
|
}
|
|
else if (!strcmp(cep->name, "automatic-ban-target"))
|
|
{
|
|
CheckNull(cep);
|
|
if (!ban_target_strtoval(cep->value))
|
|
{
|
|
config_error("%s:%i: set::automatic-ban-target: value '%s' is not recognized. "
|
|
"See https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target",
|
|
cep->file->filename, cep->line_number, cep->value);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "manual-ban-target"))
|
|
{
|
|
CheckNull(cep);
|
|
if (!ban_target_strtoval(cep->value))
|
|
{
|
|
config_error("%s:%i: set::manual-ban-target: value '%s' is not recognized. "
|
|
"See https://www.unrealircd.org/docs/Set_block#set::manual-ban-target",
|
|
cep->file->filename, cep->line_number, cep->value);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "reject-message"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
CheckNull(cepp);
|
|
if (!strcmp(cepp->name, "password-mismatch"))
|
|
;
|
|
else if (!strcmp(cepp->name, "too-many-connections"))
|
|
;
|
|
else if (!strcmp(cepp->name, "server-full"))
|
|
;
|
|
else if (!strcmp(cepp->name, "unauthorized"))
|
|
;
|
|
else if (!strcmp(cepp->name, "kline"))
|
|
;
|
|
else if (!strcmp(cepp->name, "gline"))
|
|
;
|
|
else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::reject-message",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "topic-setter"))
|
|
{
|
|
CheckNull(cep);
|
|
if (strcmp(cep->value, "nick") && strcmp(cep->value, "nick-user-host"))
|
|
{
|
|
config_error("%s:%i: set::topic-setter: value should be 'nick' or 'nick-user-host'",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "ban-setter"))
|
|
{
|
|
CheckNull(cep);
|
|
if (strcmp(cep->value, "nick") && strcmp(cep->value, "nick-user-host"))
|
|
{
|
|
config_error("%s:%i: set::ban-setter: value should be 'nick' or 'nick-user-host'",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "ban-setter-sync") || !strcmp(cep->name, "ban-setter-synch"))
|
|
{
|
|
CheckNull(cep);
|
|
}
|
|
else if (!strcmp(cep->name, "part-instead-of-quit-on-comment-change"))
|
|
{
|
|
CheckNull(cep);
|
|
}
|
|
else if (!strcmp(cep->name, "broadcast-channel-messages"))
|
|
{
|
|
CheckNull(cep);
|
|
if (strcmp(cep->value, "auto") &&
|
|
strcmp(cep->value, "always") &&
|
|
strcmp(cep->value, "never"))
|
|
{
|
|
config_error("%s:%i: set::broadcast-channel-messages: value should be 'auto', 'always' or 'never'",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "allowed-channelchars"))
|
|
{
|
|
CheckNull(cep);
|
|
if (!allowed_channelchars_strtoval(cep->value))
|
|
{
|
|
config_error("%s:%i: set::allowed-channelchars: value should be one of: 'ascii', 'utf8' or 'any'",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "hide-idle-time"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
CheckNull(cepp);
|
|
if (!strcmp(cepp->name, "policy"))
|
|
{
|
|
if (!hideidletime_strtoval(cepp->value))
|
|
{
|
|
config_error("%s:%i: set::hide-idle-time::policy: value should be one of: 'never', 'always', 'usermode' or 'oper-usermode'",
|
|
cepp->file->filename, cepp->line_number);
|
|
errors++;
|
|
}
|
|
}
|
|
else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::hide-idle-time",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
} else if (!strcmp(cep->name, "limit-svscmds"))
|
|
{
|
|
CheckNull(cep);
|
|
if (strcmp(cep->value, "servers") && strcmp(cep->value, "ulines"))
|
|
{
|
|
config_error("%s:%i: set::limit-svscmds: value must be one of: 'servers' or 'ulines'",
|
|
cep->file->filename, cep->line_number);
|
|
errors++;
|
|
}
|
|
} else if (!strcmp(cep->name, "high-connection-rate"))
|
|
{
|
|
CheckNull(cep);
|
|
} else if (!strcmp(cep->name, "best-practices"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
CheckNull(cepp);
|
|
if (!strcmp(cepp->name, "hashed-passwords"))
|
|
{
|
|
bestpractices.hashed_passwords = config_checkval(cepp->value, CFG_YESNO);
|
|
} else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::best-practices",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
} else if (!strcmp(cep->name, "dns"))
|
|
{
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
if (!strcmp(cepp->name, "client") || !strcmp(cepp->name, "dnsbl"))
|
|
{
|
|
for (ceppp = cepp->items; ceppp; ceppp = ceppp->next)
|
|
{
|
|
CheckNull(ceppp);
|
|
if (!strcmp(ceppp->name, "timeout"))
|
|
{
|
|
int v = atoi(ceppp->value);
|
|
if ((v < 1000) || (v > 10000))
|
|
{
|
|
config_error("%s:%i: the timeout needs to be in milliseconds and in the range 1000-10000.",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
} else
|
|
if (!strcmp(ceppp->name, "retry"))
|
|
{
|
|
int v = atoi(ceppp->value);
|
|
if ((v < 0) || (v > 5))
|
|
{
|
|
config_error("%s:%i: retry value needs to be in range 0-5.",
|
|
ceppp->file->filename, ceppp->line_number);
|
|
errors++;
|
|
}
|
|
} else
|
|
{
|
|
config_error_unknown(ceppp->file->filename,
|
|
ceppp->line_number, "set::dns::..::",
|
|
ceppp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
config_error_unknown(cepp->file->filename,
|
|
cepp->line_number, "set::dns",
|
|
cepp->name);
|
|
errors++;
|
|
continue;
|
|
}
|
|
}
|
|
} else if ((n = test_dynamic_set_block_item(conf, NULL, cep)) >= 0)
|
|
{
|
|
/* Handled by test_dynamic_set_block_item:
|
|
* return value 0: success (mean we do errors += 0)
|
|
* return value >0: some error
|
|
*/
|
|
errors += n;
|
|
} else /* n<0 from above */
|
|
{
|
|
int used = 0;
|
|
for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next)
|
|
{
|
|
int value, errs = 0;
|
|
if (h->owner && !(h->owner->flags & MODFLAG_TESTING) &&
|
|
!(h->owner->options & MOD_OPT_PERM))
|
|
continue;
|
|
value = (*(h->func.intfunc))(conf,cep,CONFIG_SET, &errs);
|
|
if (value == 2)
|
|
used = 1;
|
|
if (value == 1)
|
|
{
|
|
used = 1;
|
|
break;
|
|
}
|
|
if (value == -1)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
break;
|
|
}
|
|
if (value == -2)
|
|
{
|
|
used = 1;
|
|
errors += errs;
|
|
}
|
|
}
|
|
if (!used) {
|
|
config_error("%s:%i: unknown directive set::%s",
|
|
cep->file->filename, cep->line_number,
|
|
cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
}
|
|
return errors;
|
|
}
|
|
|
|
int _conf_loadmodule(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
const char *ret;
|
|
if (!ce->value)
|
|
{
|
|
config_status("%s:%i: loadmodule without filename",
|
|
ce->file->filename, ce->line_number);
|
|
return -1;
|
|
}
|
|
|
|
if (is_blacklisted_module(ce->value))
|
|
{
|
|
/* config_warn("%s:%i: Module '%s' is blacklisted, not loading",
|
|
ce->file->filename, ce->line_number, ce->value); */
|
|
return 1;
|
|
}
|
|
|
|
if ((ret = Module_Create(ce->value))) {
|
|
config_error("%s:%i: loadmodule %s: failed to load: %s",
|
|
ce->file->filename, ce->line_number,
|
|
ce->value, ret);
|
|
return -1;
|
|
}
|
|
return 1;
|
|
}
|
|
|
|
int _test_loadmodule(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
return 0;
|
|
}
|
|
|
|
int _test_blacklist_module(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
const char *path;
|
|
ConfigItem_blacklist_module *m;
|
|
|
|
if (!ce->value)
|
|
{
|
|
config_status("%s:%i: blacklist-module: no module name given to blacklist",
|
|
ce->file->filename, ce->line_number);
|
|
return -1;
|
|
}
|
|
|
|
path = Module_TransformPath(ce->value);
|
|
|
|
m = safe_alloc(sizeof(ConfigItem_blacklist_module));
|
|
safe_strdup(m->name, ce->value);
|
|
AddListItem(m, conf_blacklist_module);
|
|
|
|
return 0;
|
|
}
|
|
|
|
int is_blacklisted_module(const char *name)
|
|
{
|
|
const char *path = Module_TransformPath(name);
|
|
ConfigItem_blacklist_module *m;
|
|
|
|
for (m = conf_blacklist_module; m; m = m->next)
|
|
if (match_simple(m->name, name) || match_simple(m->name, path))
|
|
return 1;
|
|
|
|
return 0;
|
|
}
|
|
|
|
void start_listeners(void)
|
|
{
|
|
ConfigItem_listen *listener;
|
|
int failed = 0, ports_bound = 0;
|
|
char boundmsg_ipv4[512], 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();
|
|
dns_check_for_changes();
|
|
}
|
|
|
|
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 *memory, int memory_len, 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();
|
|
clicap_check_for_changes();
|
|
umodes_check_for_changes();
|
|
charsys_check_for_changes();
|
|
|
|
/* Remove central spamfilter rules upon set::central-spamfilter::enabled no; */
|
|
if (iConf.central_spamfilter_enabled == 0)
|
|
remove_config_tkls(TKL_FLAG_CENTRAL_SPAMFILTER);
|
|
|
|
/* Clear everything now that we are done */
|
|
loop.rehashing = 0;
|
|
remote_rehash_client = NULL;
|
|
procio_post_rehash(failure);
|
|
json_object_set_new(json_rehash_log, "success", json_boolean(failure ? 0 : 1));
|
|
RunHook(HOOKTYPE_REHASH_LOG, failure, json_rehash_log);
|
|
|
|
loop.config_status = CONFIG_STATUS_COMPLETE;
|
|
return 1;
|
|
}
|
|
|
|
void link_cleanup(ConfigItem_link *link_ptr)
|
|
{
|
|
safe_free(link_ptr->servername);
|
|
free_security_group(link_ptr->incoming.match);
|
|
Auth_FreeAuthConfig(link_ptr->auth);
|
|
safe_free(link_ptr->outgoing.file);
|
|
safe_free(link_ptr->outgoing.bind_ip);
|
|
safe_free(link_ptr->outgoing.hostname);
|
|
safe_free(link_ptr->hub);
|
|
safe_free(link_ptr->leaf);
|
|
if (link_ptr->ssl_ctx)
|
|
{
|
|
SSL_CTX_free(link_ptr->ssl_ctx);
|
|
link_ptr->ssl_ctx = NULL;
|
|
}
|
|
if (link_ptr->tls_options)
|
|
{
|
|
free_tls_options(link_ptr->tls_options);
|
|
link_ptr->tls_options = NULL;
|
|
}
|
|
}
|
|
|
|
void delete_linkblock(ConfigItem_link *link_ptr)
|
|
{
|
|
if (link_ptr->class)
|
|
{
|
|
link_ptr->class->xrefcount--;
|
|
/* Perhaps the class is temporary too and we need to free it... */
|
|
if (link_ptr->class->flag.temporary &&
|
|
!link_ptr->class->clients && !link_ptr->class->xrefcount)
|
|
{
|
|
delete_classblock(link_ptr->class);
|
|
link_ptr->class = NULL;
|
|
}
|
|
}
|
|
link_cleanup(link_ptr);
|
|
DelListItem(link_ptr, conf_link);
|
|
safe_free(link_ptr);
|
|
}
|
|
|
|
void delete_classblock(ConfigItem_class *class_ptr)
|
|
{
|
|
safe_free(class_ptr->name);
|
|
DelListItem(class_ptr, conf_class);
|
|
safe_free(class_ptr);
|
|
}
|
|
|
|
void free_webserver(WebServer *webserver)
|
|
{
|
|
safe_free(webserver);
|
|
}
|
|
|
|
void listen_cleanup()
|
|
{
|
|
ConfigItem_listen *listener, *listener_next;
|
|
|
|
for (listener = conf_listen; listener; listener = listener_next)
|
|
{
|
|
listener_next = listener->next;
|
|
if (listener->flag.temporary)
|
|
{
|
|
/* First close the listener FD so no new clients can connect */
|
|
if (listener->fd >= 0)
|
|
close_listener(listener);
|
|
if (listener->clients == 0)
|
|
{
|
|
/* Zero clients, so can be completely freed */
|
|
DelListItem(listener, conf_listen);
|
|
safe_free(listener->ip);
|
|
safe_free(listener->file);
|
|
safe_free(listener->spoof_ip);
|
|
free_tls_options(listener->tls_options);
|
|
/* listener->ssl_ctx is already freed by close_listener() */
|
|
safe_free_webserver(listener->webserver);
|
|
safe_free(listener);
|
|
} else {
|
|
/* Still has clients */
|
|
if (listener->webserver)
|
|
{
|
|
/* These now point to old module addresses */
|
|
listener->webserver->handle_request = NULL;
|
|
listener->webserver->handle_body = NULL;
|
|
/* Ask modules if they can figure out the handler */
|
|
RunHook(HOOKTYPE_CONFIG_LISTENER, listener);
|
|
/* If not... free the webserver listener.
|
|
* TODO: it would be even better if we kill all clients,
|
|
* it would be more explicit and clear.
|
|
*/
|
|
if ((listener->webserver->handle_request == NULL) ||
|
|
(listener->webserver->handle_body == NULL))
|
|
{
|
|
safe_free_webserver(listener->webserver);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
ConfigResource *find_config_resource(const char *resource)
|
|
{
|
|
ConfigResource *rs;
|
|
|
|
for (rs = config_resources; rs; rs = rs->next)
|
|
{
|
|
#ifdef _WIN32
|
|
if (rs->file && !strcasecmp(resource, rs->file))
|
|
return rs;
|
|
#else
|
|
if (rs->file && !strcmp(resource, rs->file))
|
|
return rs;
|
|
#endif
|
|
if (rs->url && !strcasecmp(resource, rs->url))
|
|
return rs;
|
|
}
|
|
return NULL;
|
|
}
|
|
|
|
/* Add configuration resource to list.
|
|
* For files this doesn't do terribly much, except that you can use
|
|
* the return value to judge on whether you should call config_read_file() or not.
|
|
* For urls this adds the resource to the list of links to be downloaded.
|
|
* @param resource File or URL of the resource
|
|
* @param type A RESOURCE_ type such as RESOURCE_INCLUDE
|
|
* @param ce The ConfigEntry where the add_config_resource() happened
|
|
* for, such as the include block, etc.
|
|
* @returns 0 if the file is already on our list (so no need to load it!)
|
|
*/
|
|
int add_config_resource(const char *resource, int type, ConfigEntry *ce)
|
|
{
|
|
ConfigResource *rs;
|
|
ConfigEntryWrapper *wce;
|
|
ConfigEntry *cep;
|
|
|
|
if (config_verbose)
|
|
config_status("add_config_resource() for '%s", resource);
|
|
|
|
wce = safe_alloc(sizeof(ConfigEntryWrapper));
|
|
wce->ce = ce;
|
|
|
|
rs = find_config_resource(resource);
|
|
if (rs)
|
|
{
|
|
/* Existing entry, add us to the list of
|
|
* items who are interested in this resource ;)
|
|
*/
|
|
AddListItem(wce, rs->wce);
|
|
return 0;
|
|
}
|
|
|
|
/* New entry */
|
|
rs = safe_alloc(sizeof(ConfigResource));
|
|
rs->wce = wce;
|
|
AddListItem(rs, config_resources);
|
|
|
|
/* restrict-config handling */
|
|
if (ce)
|
|
{
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
if (!strcmp(cep->name, "restrict-config"))
|
|
{
|
|
ConfigEntry *cepp;
|
|
for (cepp = cep->items; cepp; cepp = cepp->next)
|
|
{
|
|
add_name_list(rs->restrict_config, cepp->name);
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
if (!url_is_valid(resource))
|
|
{
|
|
safe_strdup(rs->file, resource);
|
|
} else {
|
|
const char *cache_file;
|
|
time_t modtime;
|
|
|
|
safe_strdup(rs->url, resource);
|
|
rs->type = type|RESOURCE_REMOTE|RESOURCE_DLQUEUED;
|
|
|
|
cache_file = unreal_mkcache(rs->url);
|
|
modtime = unreal_getfilemodtime(cache_file);
|
|
if (modtime > 0)
|
|
{
|
|
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, 0, 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);
|
|
free_entire_name_list(rs->restrict_config);
|
|
DelListItem(rs, config_resources);
|
|
safe_free(rs);
|
|
}
|
|
}
|
|
|
|
int tls_tests(void)
|
|
{
|
|
if (have_tls_listeners == 0)
|
|
{
|
|
config_error("Your server is not listening on any TLS ports.");
|
|
config_status("Add this to your unrealircd.conf: listen { ip %s; port 6697; options { tls; }; };",
|
|
port_6667_ip ? port_6667_ip : "*");
|
|
config_status("See https://www.unrealircd.org/docs/FAQ#no-tls-ports");
|
|
return 0;
|
|
}
|
|
|
|
return 1;
|
|
}
|
|
|
|
/** Check if the user attempts to unload (eg: by commenting out) a module
|
|
* that is currently loaded and is tagged as MOD_OPT_PERM_RELOADABLE
|
|
* (in other words: a module that allows re-loading but not un-loading)
|
|
*/
|
|
int reloadable_perm_module_unloaded(void)
|
|
{
|
|
Module *m, *m2;
|
|
extern Module *Modules;
|
|
int ret = 0;
|
|
|
|
for (m = Modules; m; m = m->next)
|
|
{
|
|
if ((m->options & MOD_OPT_PERM_RELOADABLE) && (m->flags & MODFLAG_LOADED))
|
|
{
|
|
/* For each module w/MOD_OPT_PERM_RELOADABLE that is currently fully loaded... */
|
|
int found = 0;
|
|
for (m2 = Modules; m2; m2 = m2->next)
|
|
{
|
|
if ((m != m2) && !strcmp(m->header->name, m2->header->name))
|
|
found = 1;
|
|
}
|
|
if (!found)
|
|
{
|
|
config_error("Attempt to unload module '%s' is not permitted. Module is permanent and reloadable only.", m->header->name);
|
|
ret = 1;
|
|
/* we don't return straight away so the user gets to see all errors and not just one */
|
|
}
|
|
}
|
|
}
|
|
|
|
return ret;
|
|
}
|
|
|
|
const char *link_generator_spkifp(TLSOptions *tlsoptions)
|
|
{
|
|
SSL_CTX *ctx;
|
|
SSL *ssl;
|
|
X509 *cert;
|
|
|
|
ctx = init_ctx(tlsoptions, 1);
|
|
if (!ctx)
|
|
exit(1);
|
|
ssl = SSL_new(ctx);
|
|
if (!ssl)
|
|
exit(1);
|
|
cert = SSL_get_certificate(ssl);
|
|
return spki_fingerprint_ex(cert);
|
|
}
|
|
|
|
void link_generator(void)
|
|
{
|
|
ConfigItem_listen *lstn;
|
|
TLSOptions *tlsopt = iConf.tls_options; /* never null */
|
|
int port = 0;
|
|
char *ip = NULL;
|
|
const char *spkifp;
|
|
|
|
for (lstn = conf_listen; lstn; lstn = lstn->next)
|
|
{
|
|
if ((lstn->options & LISTENER_SERVERSONLY) &&
|
|
(lstn->options & LISTENER_TLS))
|
|
{
|
|
if (lstn->tls_options)
|
|
tlsopt = lstn->tls_options;
|
|
port = lstn->port;
|
|
if (strcmp(lstn->ip, "*"))
|
|
ip = lstn->ip;
|
|
/* else NULL */
|
|
break;
|
|
}
|
|
}
|
|
|
|
if (!port)
|
|
{
|
|
printf("You don't have any listen { } blocks that are serversonly (and have tls enabled).\n");
|
|
printf("It is recommended to have at least one. Add this to your configuration file:\n");
|
|
printf("listen { ip *; port 6900; options { tls; serversonly; }; };\n");
|
|
exit(1);
|
|
}
|
|
|
|
spkifp = link_generator_spkifp(tlsopt);
|
|
if (!spkifp)
|
|
{
|
|
printf("Could not calculate spkifp. Maybe you have uncommon TLS options set? Odd...\n");
|
|
exit(1);
|
|
}
|
|
|
|
printf("\n");
|
|
printf("Add the following link block to the unrealircd.conf on the OTHER side of the link\n");
|
|
printf("(so NOT in the unrealircd.conf on THIS machine). Here it is, just copy-paste:\n");
|
|
printf("################################################################################\n");
|
|
printf("link %s {\n"
|
|
" incoming {\n"
|
|
" mask *;\n"
|
|
" }\n"
|
|
" outgoing {\n"
|
|
" hostname %s;\n"
|
|
" port %d;\n"
|
|
" options { tls; autoconnect; }\n"
|
|
" }\n"
|
|
" password \"%s\" { spkifp; }\n"
|
|
" class servers;\n"
|
|
"}\n",
|
|
conf_me->name,
|
|
ip ? ip : conf_me->name,
|
|
port,
|
|
spkifp);
|
|
printf("################################################################################\n");
|
|
exit(0);
|
|
}
|
|
|
|
/** Checks if the user modified 'modules.default.conf'
|
|
* despite the BIG comment "DO NOT EDIT THIS FILE!"
|
|
* and the explanation. Sigh.........
|
|
*/
|
|
int modules_default_conf_modified(const char *filebuf)
|
|
{
|
|
char buf[512];
|
|
char *p;
|
|
char *conf_version = buf + 4;
|
|
|
|
/* Put the first line in 'buf' */
|
|
strlcpy(buf, filebuf, sizeof(buf));
|
|
stripcrlf(buf);
|
|
|
|
// The line should start with '/* [VERSION]'.
|
|
// If it doesn't then it is from pre-6.1.1, so reject.
|
|
if (!str_starts_with_case_sensitive(buf, "/* ["))
|
|
{
|
|
conf_version = "before 6.1.1";
|
|
} else {
|
|
p = strchr(conf_version, ']');
|
|
if (p)
|
|
*p = '\0';
|
|
if (!strcmp(VERSIONONLY, conf_version))
|
|
return 0; /* exact match, done! */
|
|
}
|
|
|
|
unreal_log(ULOG_ERROR, "config", "CONFIG_BAD_MODULES_DEFAULT_CONF", NULL,
|
|
"Your 'modules.default.conf' is from $your_conf_version and "
|
|
"not the one shipped with UnrealIRCd $version.\n"
|
|
"* Maybe you accidentally copied an old modules.default.conf from a previous installation?\n"
|
|
"* Or you deliberately modified modules.default.conf? That is not allowed,"
|
|
" see the BIG warning at the beginning of that file.\n"
|
|
"Solution: re-run 'make install' and then try to REHASH or boot again.\n",
|
|
log_data_string("your_conf_version", conf_version),
|
|
log_data_string("version", VERSIONONLY));
|
|
return 1;
|
|
}
|
|
|
|
/*** START OF DYNAMIC SET CONFIG STUFF (that can be overridden by security group) ***/
|
|
|
|
void init_dynamic_set_block(DynamicSetBlock *s)
|
|
{
|
|
memset(s, 0, sizeof(DynamicSetBlock));
|
|
}
|
|
|
|
void free_dynamic_set_block(DynamicSetBlock *s)
|
|
{
|
|
safe_free(s->settings[SET_AUTO_JOIN].string);
|
|
safe_free(s->settings[SET_STATIC_QUIT].string);
|
|
safe_free(s->settings[SET_STATIC_PART].string);
|
|
safe_free(s->settings[SET_RESTRICT_USERMODES].string);
|
|
}
|
|
|
|
void dynamic_set_string(DynamicSetBlock *s, int settingname, const char *value)
|
|
{
|
|
s->isset[settingname] = 1;
|
|
safe_strdup(s->settings[settingname].string, value);
|
|
}
|
|
|
|
void dynamic_set_number(DynamicSetBlock *s, int settingname, long long value)
|
|
{
|
|
s->isset[settingname] = 1;
|
|
s->settings[settingname].number = value;
|
|
}
|
|
|
|
#undef CheckNull
|
|
#define CheckNull(x) if ((!(x)->value) || (!(*((x)->value)))) { config_error("%s:%i: missing parameter", (x)->file->filename, (x)->line_number); return 1; }
|
|
|
|
int test_dynamic_set_block_item(ConfigFile *conf, const char *security_group, ConfigEntry *cep)
|
|
{
|
|
int errors = 0;
|
|
|
|
if (!strcmp(cep->name, "modes-on-connect")) {
|
|
char *p;
|
|
CheckNull(cep);
|
|
for (p = cep->value; *p; p++)
|
|
if (strchr("orzSHqtW", *p))
|
|
{
|
|
config_error("%s:%i: set::modes-on-connect may not include mode '%c'",
|
|
cep->file->filename, cep->line_number, *p);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "auto-join")) {
|
|
CheckNull(cep);
|
|
}
|
|
else if (!strcmp(cep->name, "maxchannelsperuser") || !strcmp(cep->name, "max-channels-per-user")) {
|
|
int i;
|
|
CheckNull(cep);
|
|
i = atoi(cep->value);
|
|
if (i < 1)
|
|
{
|
|
config_error("%s:%i: set::%s must be > 0",
|
|
cep->file->filename,
|
|
cep->line_number,
|
|
cep->name);
|
|
errors++;
|
|
}
|
|
}
|
|
else if (!strcmp(cep->name, "static-quit")) {
|
|
CheckNull(cep);
|
|
}
|
|
else if (!strcmp(cep->name, "static-part")) {
|
|
CheckNull(cep);
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-usermodes"))
|
|
{
|
|
char *p;
|
|
|
|
CheckNull(cep);
|
|
for (p = cep->value; *p; p++)
|
|
{
|
|
if ((*p == '+') || (*p == '-'))
|
|
{
|
|
config_status("%s:%i: warning: set::restrict-usermodes: should only contain modechars, no + or -.\n",
|
|
cep->file->filename, cep->line_number);
|
|
break;
|
|
}
|
|
}
|
|
} else
|
|
{
|
|
/* Unknown option */
|
|
return -1;
|
|
}
|
|
return errors;
|
|
}
|
|
#undef CheckNull
|
|
|
|
int config_set_dynamic_set_block_item(ConfigFile *conf, DynamicSetBlock *s, ConfigEntry *cep)
|
|
{
|
|
if (!strcmp(cep->name, "modes-on-connect")) {
|
|
long v = set_usermode(cep->value);
|
|
dynamic_set_number(s, SET_MODES_ON_CONNECT, v);
|
|
return 1;
|
|
}
|
|
else if (!strcmp(cep->name, "auto-join")) {
|
|
dynamic_set_string(s, SET_AUTO_JOIN, cep->value);
|
|
return 1;
|
|
}
|
|
else if (!strcmp(cep->name, "maxchannelsperuser") || !strcmp(cep->name, "max-channels-per-user")) {
|
|
dynamic_set_number(s, SET_MAX_CHANNELS_PER_USER, atoi(cep->value));
|
|
return 1;
|
|
}
|
|
else if (!strcmp(cep->name, "static-quit")) {
|
|
dynamic_set_string(s, SET_STATIC_QUIT, cep->value);
|
|
return 1;
|
|
}
|
|
else if (!strcmp(cep->name, "static-part")) {
|
|
dynamic_set_string(s, SET_STATIC_PART, cep->value);
|
|
return 1;
|
|
}
|
|
else if (!strcmp(cep->name, "restrict-usermodes")) {
|
|
int i;
|
|
char buf[512];
|
|
char *p = buf;
|
|
/* The data should be something like 'Gw' or something,
|
|
* but just in case users use '+Gw' then ignore the + (and -).
|
|
*/
|
|
for (i=0; i < MIN(strlen(cep->value),sizeof(buf)-1); i++)
|
|
if ((cep->value[i] != '+') && (cep->value[i] != '-'))
|
|
*p++ = cep->value[i];
|
|
*p = '\0';
|
|
dynamic_set_string(s, SET_RESTRICT_USERMODES, cep->value);
|
|
return 1;
|
|
}
|
|
return 0; /* unknown item, not taken */
|
|
}
|
|
|
|
int test_set_security_group(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
int errors = 0;
|
|
int i;
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
int n = test_dynamic_set_block_item(conf, ce->value, cep);
|
|
if (n == -1)
|
|
{
|
|
config_error("%s:%i: set::%s is not allowed in a set <security group> { } block or is an unrecognized option",
|
|
cep->file->filename, cep->line_number, ce->value);
|
|
errors++;
|
|
} else
|
|
if (n > 0)
|
|
{
|
|
errors += n;
|
|
}
|
|
}
|
|
|
|
return errors;
|
|
}
|
|
|
|
int config_set_security_group(ConfigFile *conf, ConfigEntry *ce)
|
|
{
|
|
ConfigEntry *cep;
|
|
DynamicSetBlock *block = NULL;
|
|
SecurityGroup *s;
|
|
|
|
if (!strcmp(ce->value, "unknown-users"))
|
|
block = &unknown_users_set;
|
|
else if ((s = find_security_group(ce->value)))
|
|
block = &s->settings;
|
|
|
|
if (!block)
|
|
{
|
|
config_warn("set %s { } block encountered but security-group %s does not exist. Settings ignored",
|
|
ce->value, ce->value);
|
|
return 0;
|
|
}
|
|
|
|
for (cep = ce->items; cep; cep = cep->next)
|
|
{
|
|
config_set_dynamic_set_block_item(conf, block, cep);
|
|
}
|
|
return 0;
|
|
}
|
|
|
|
/** Get the appropriate settings block for this user.
|
|
* @param client The client, should be locally connected.
|
|
* @param opt The flood option we are interested in
|
|
* @returns The FloodSettings for this user, never returns NULL.
|
|
*/
|
|
DynamicSetOption *get_setting_for_user(Client *client, SetOption opt)
|
|
{
|
|
SecurityGroup *sg;
|
|
int in_known_users = 0;
|
|
|
|
/* Go through all security groups by order of priority
|
|
* (eg: first "known-users", then "unknown-users").
|
|
* For each of these:
|
|
* - Check if a set::anti-flood::xxxx block exists for this group
|
|
* - Check if the limit is non-zero (eg there is any limit set)
|
|
* If any of these are false then we continue with next block
|
|
* that matches.
|
|
*/
|
|
|
|
for (sg = securitygroups; sg; sg = sg->next)
|
|
{
|
|
/* Special caching for 'known-users' */
|
|
if (!strcmp(sg->name, "known-users"))
|
|
{
|
|
in_known_users = user_allowed_by_security_group(client, sg);
|
|
if (in_known_users)
|
|
{
|
|
if (sg->settings.isset[opt])
|
|
return &sg->settings.settings[opt];
|
|
}
|
|
} else
|
|
if (user_allowed_by_security_group(client, sg) &&
|
|
sg->settings.isset[opt])
|
|
{
|
|
return &sg->settings.settings[opt];
|
|
}
|
|
}
|
|
|
|
/* Not found, then maybe the user is in unknown-users?
|
|
* Use the caching info from above.
|
|
*/
|
|
if (!in_known_users && unknown_users_set.isset[opt])
|
|
return &unknown_users_set.settings[opt];
|
|
|
|
/* If we are still here, then fallback to the generic set:: setting */
|
|
return &dynamic_set.settings[opt];
|
|
}
|
|
|
|
/** Get configuration that applies to the user - a number value */
|
|
long long get_setting_for_user_number(Client *client, SetOption opt)
|
|
{
|
|
return get_setting_for_user(client, opt)->number;
|
|
}
|
|
|
|
/** Get configuration that applies to the user - a string config value */
|
|
const char *get_setting_for_user_string(Client *client, SetOption opt)
|
|
{
|
|
return get_setting_for_user(client, opt)->string;
|
|
}
|
|
|
|
/*** END OF DYNAMIC SET CONFIG STUFF (that can be overridden by security group) ***/
|
|
|
|
/*** Central spamfilter ***/
|
|
int central_spamfilter_downloading = 0;
|
|
|
|
#define CENTRAL_SPAMFILTER_CACHE_TIME 0
|
|
|
|
int count_central_spamfilter_rules(void)
|
|
{
|
|
int count = 0;
|
|
TKL *tkl;
|
|
|
|
for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
|
|
if (IsCentralSpamfilter(tkl))
|
|
count++;
|
|
|
|
return count;
|
|
}
|
|
|
|
void central_spamfilter_download_complete(const char *url, const char *file, const char *memory, int memory_len, const char *errorbuf, int cached, void *rs_key)
|
|
{
|
|
ConfigFile *cfptr;
|
|
int errors;
|
|
int num_rules, active_rules;
|
|
|
|
central_spamfilter_downloading = 0;
|
|
|
|
/* Race condition: REHASH (with this new setting) and this download complete callback could cross */
|
|
if (iConf.central_spamfilter_enabled == 0)
|
|
return;
|
|
|
|
if (iConf.central_spamfilter_verbose > 2)
|
|
{
|
|
unreal_log(ULOG_INFO, "central-spamfilter", "CENTRAL_SPAMFILTER_STATUS", NULL,
|
|
"Processing central spamfilter rules...");
|
|
}
|
|
|
|
if (errorbuf)
|
|
{
|
|
config_error("Central spamfilter URL fetch failed (this could be a temporary problem): %s: %s", url, errorbuf);
|
|
return;
|
|
}
|
|
|
|
if (!(cfptr = config_load(file, "central_spamfilter.conf")))
|
|
{
|
|
unreal_log(ULOG_ERROR, "central-spamfilter", "CENTRAL_SPAMFILTER_LOAD_FAILED", NULL,
|
|
"[Central spamfilter] Failed to load"); // Where is the REASON ???
|
|
return;
|
|
}
|
|
|
|
preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_INITIAL);
|
|
preprocessor_resolve_conditionals_ce(&cfptr->items, PREPROCESSOR_PHASE_SECONDARY);
|
|
errors = config_test_blocks_generic(cfptr, 0, 0);
|
|
if (errors)
|
|
{
|
|
unreal_log(ULOG_ERROR, "central-spamfilter", "CENTRAL_SPAMFILTER_LOAD_FAILED", NULL,
|
|
"[Central spamfilter] Errors in the central central_spamfilter.conf -- not loaded.");
|
|
config_free(cfptr);
|
|
return;
|
|
}
|
|
|
|
/* Remove all current central spamfilters */
|
|
remove_config_tkls(TKL_FLAG_CENTRAL_SPAMFILTER);
|
|
|
|
/* And load the new ones... */
|
|
num_rules = config_run_blocks_generic(cfptr, 0);
|
|
active_rules = count_central_spamfilter_rules();
|
|
|
|
if (iConf.central_spamfilter_verbose > 2)
|
|
{
|
|
unreal_log(ULOG_INFO, "central-spamfilter", "CENTRAL_SPAMFILTER_STATUS", NULL,
|
|
"Central spamfilter loaded (active rules: $active_rules, skipped: $skipped_rules)",
|
|
log_data_integer("active_rules", active_rules),
|
|
log_data_integer("skipped_rules", num_rules - active_rules));
|
|
}
|
|
config_free(cfptr);
|
|
}
|
|
|
|
void central_spamfilter_start_download(void)
|
|
{
|
|
char url[512];
|
|
NameValuePrioList *nvp = NULL;
|
|
|
|
if (central_spamfilter_downloading)
|
|
return;
|
|
|
|
if (iConf.central_spamfilter_verbose > 2)
|
|
{
|
|
unreal_log(ULOG_INFO, "central-spamfilter", "CENTRAL_SPAMFILTER_STATUS", NULL,
|
|
"Starting download of central spamfilter rules...");
|
|
}
|
|
|
|
central_spamfilter_downloading = 1;
|
|
|
|
/* Build the URL */
|
|
add_nvplist(&nvp, 0, "feed", iConf.central_spamfilter_feed);
|
|
buildvarstring_nvp(iConf.central_spamfilter_url, url, sizeof(url), nvp, 0);
|
|
safe_free_nvplist(nvp);
|
|
|
|
/* Start HTTPS request */
|
|
download_file_async(url, CENTRAL_SPAMFILTER_CACHE_TIME, central_spamfilter_download_complete, NULL, NULL, DOWNLOAD_MAX_REDIRECTS);
|
|
}
|
|
|
|
EVENT(central_spamfilter_download_evt)
|
|
{
|
|
if (iConf.central_spamfilter_enabled == 0)
|
|
return;
|
|
if ((central_spamfilter_last_download == 0) || (TStime() - central_spamfilter_last_download > iConf.central_spamfilter_refresh_time))
|
|
{
|
|
central_spamfilter_last_download = TStime();
|
|
central_spamfilter_start_download();
|
|
}
|
|
}
|
|
|
|
/*** End of central spamfilter ***/
|
|
|
|
/** Return full name of the config item, eg set::something::priority */
|
|
const char *config_item_name(ConfigEntry *ce)
|
|
{
|
|
static char buf[512];
|
|
char tmp[512];
|
|
|
|
*buf = '\0';
|
|
for (; ce; ce = ce->parent)
|
|
{
|
|
if (*buf)
|
|
{
|
|
strlcpy(tmp, buf, sizeof(tmp));
|
|
snprintf(buf, sizeof(buf), "%s::%s", ce->name, tmp);
|
|
} else {
|
|
strlcpy(buf, ce->name, sizeof(buf));
|
|
}
|
|
}
|
|
return buf;
|
|
}
|