/* * 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); }; typedef struct NameValue NameValue; struct NameValue { long flag; char *name; }; /* 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_link (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_log (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); /* * 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_log (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); /* 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", _conf_log, _test_log }, { "me", _conf_me, _test_me }, { "official-channels", _conf_offchans, _test_offchans }, { "oper", _conf_oper, _test_oper }, { "operclass", _conf_operclass, _test_operclass }, { "require", _conf_require, _test_require }, { "set", _conf_set, _test_set }, { "sni", _conf_sni, _test_sni }, { "tld", _conf_tld, _test_tld }, { "ulines", _conf_ulines, _test_ulines }, { "vhost", _conf_vhost, _test_vhost }, }; /* This MUST be alphabetized */ static NameValue _ListenerFlags[] = { { LISTENER_CLIENTSONLY, "clientsonly"}, { LISTENER_DEFER_ACCEPT, "defer-accept"}, { LISTENER_SERVERSONLY, "serversonly"}, { LISTENER_TLS, "ssl"}, { LISTENER_NORMAL, "standard"}, { LISTENER_TLS, "tls"}, }; /* This MUST be alphabetized */ static NameValue _LinkFlags[] = { { CONNECT_AUTO, "autoconnect" }, { CONNECT_INSECURE, "insecure" }, { CONNECT_QUARANTINE, "quarantine"}, { CONNECT_TLS, "ssl" }, { CONNECT_TLS, "tls" }, }; /* This MUST be alphabetized */ static NameValue _LogFlags[] = { { LOG_CHGCMDS, "chg-commands" }, { LOG_CLIENT, "connects" }, { LOG_ERROR, "errors" }, { LOG_KILL, "kills" }, { LOG_KLINE, "kline" }, { LOG_OPER, "oper" }, { LOG_OVERRIDE, "oper-override" }, { LOG_SACMDS, "sadmin-commands" }, { LOG_SERVER, "server-connects" }, { LOG_SPAMFILTER, "spamfilter" }, { LOG_TKL, "tkl" }, }; /* 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(char *string, int *start, int *end); long config_checkval(char *value, unsigned short flags); /* * Parser */ ConfigFile *config_load(char *filename, char *displayname); void config_free(ConfigFile *cfptr); ConfigFile *config_parse_with_offset(char *filename, char *confdata, unsigned int line_offset); ConfigFile *config_parse(char *filename, char *confdata); ConfigEntry *config_find_entry(ConfigEntry *ce, char *name); extern void add_entropy_configfile(struct stat *st, char *buf); extern void unload_all_unused_snomasks(void); extern void unload_all_unused_umodes(void); extern void unload_all_unused_extcmodes(void); extern void unload_all_unused_caps(void); extern void unload_all_unused_history_backends(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 init_conf(char *rootconf, int rehash); int load_conf(char *filename, const char *original_path); void config_rehash(); int config_run(); /* * 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_except *conf_except = 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_link *conf_deny_link = NULL; ConfigItem_deny_version *conf_deny_version = NULL; ConfigItem_log *conf_log = NULL; ConfigItem_alias *conf_alias = NULL; ConfigItem_include *conf_include = NULL; ConfigItem_blacklist_module *conf_blacklist_module = NULL; ConfigItem_help *conf_help = NULL; ConfigItem_offchans *conf_offchans = NULL; MODVAR Configuration iConf; MODVAR Configuration tempiConf; MODVAR ConfigFile *conf = NULL; extern NameValueList *config_defines; MODVAR int ipv6_disabled = 0; MODVAR Client *remote_rehash_client = NULL; MODVAR int config_error_flag = 0; int config_verbose = 0; MODVAR int need_34_upgrade = 0; int need_operclass_permissions_upgrade = 0; int have_tls_listeners = 0; char *port_6667_ip = NULL; void add_include(const char *filename, const char *included_from, int included_from_line); #ifdef USE_LIBCURL void add_remote_include(const char *, const char *, int, const char *, const char *included_from, int included_from_line); void update_remote_include(ConfigItem_include *inc, const char *file, int, const char *errorbuf); int remote_include(ConfigEntry *ce); #endif void unload_notloaded_includes(void); void load_includes(void); void unload_loaded_includes(void); int rehash_internal(Client *client, int sig); int is_blacklisted_module(char *name); /** Return the printable string of a 'cep' location, such as set::something::xyz */ 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->ce_prevlevel) { elem[numel++] = e->ce_varname; 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(char *string, int *start, int *end) { char *c = strchr(string, '-'); 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(char *orig, int *times, int *period) { char *x; *times = *period = 0; x = strchr(orig, ':'); /* 'blah', ':blah', '1:' */ if (!x || (x == orig) || (*(x+1) == '\0')) return 0; *x = '\0'; *times = atoi(orig); *period = config_checkval(x+1, CFG_TIME); *x = ':'; /* restore */ return 1; } long config_checkval(char *orig, unsigned short flags) { char *value = raw_strdup(orig); char *text; long ret = 0; 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; store->mode = 0; store->extmodes = 0; for (i = 0; i < EXTCMODETABLESZ; i++) safe_free(store->extparams[i]); } /* Set configuration, used for set::modes-on-join */ void conf_channelmodes(char *modes, struct ChMode *store, int warn) { CoreChannelModeTable *tab; char *params = strchr(modes, ' '); char *parambuf = NULL; char *param = NULL; char *save = NULL; warn = 0; // warn is broken /* Free existing parameters first (no inheritance) */ free_conf_channelmodes(store); if (params) { params++; safe_strdup(parambuf, params); param = strtoken(&save, parambuf, " "); } for (; *modes && *modes != ' '; modes++) { if (*modes == '+') continue; if (*modes == '-') /* When a channel is created it has no modes, so just ignore if the * user asks us to unset anything -- codemastr */ { while (*modes && *modes != '+') modes++; continue; } for (tab = &corechannelmodetable[0]; tab->mode; tab++) { if (tab->flag == *modes) { if (tab->parameters) { /* INCOMPATIBLE */ break; } store->mode |= tab->mode; break; } } /* Try extcmodes */ if (!tab->mode) { int i; for (i=0; i <= Channelmode_highest; i++) { if (!(Channelmode_Table[i].flag)) continue; if (*modes == Channelmode_Table[i].flag) { if (Channelmode_Table[i].paracount) { if (!param) break; param = Channelmode_Table[i].conv_param(param, NULL); if (!param) break; /* invalid parameter fmt, do not set mode. */ store->extparams[i] = raw_strdup(param); /* Get next parameter */ param = strtoken(&save, NULL, " "); } store->extmodes |= Channelmode_Table[i].mode; break; } } } } safe_free(parambuf); } void chmode_str(struct ChMode *modes, char *mbuf, char *pbuf, size_t mbuf_size, size_t pbuf_size) { CoreChannelModeTable *tab; int i; if (!(mbuf_size && pbuf_size)) return; *pbuf = 0; *mbuf++ = '+'; if (--mbuf_size == 0) return; for (tab = &corechannelmodetable[0]; tab->mode; tab++) { if (modes->mode & tab->mode) { if (!tab->parameters) { *mbuf++ = tab->flag; if (!--mbuf_size) { *--mbuf=0; break; } } } } for (i=0; i <= Channelmode_highest; i++) { if (!(Channelmode_Table[i].flag)) continue; if (modes->extmodes & Channelmode_Table[i].mode) { if (mbuf_size) { *mbuf++ = Channelmode_Table[i].flag; if (!--mbuf_size) { *--mbuf=0; break; } } if (Channelmode_Table[i].paracount) { strlcat(pbuf, modes->extparams[i], pbuf_size); strlcat(pbuf, " ", pbuf_size); } } } *mbuf=0; } int channellevel_to_int(char *s) { /* Requested at http://bugs.unrealircd.org/view.php?id=3852 */ if (!strcmp(s, "none")) return CHFL_DEOPPED; if (!strcmp(s, "voice")) return CHFL_VOICE; if (!strcmp(s, "halfop")) return CHFL_HALFOP; if (!strcmp(s, "op") || !strcmp(s, "chanop")) return CHFL_CHANOP; if (!strcmp(s, "protect") || !strcmp(s, "chanprot")) #ifdef PREFIX_AQ return CHFL_CHANADMIN; #else return CHFL_CHANOP|CHFL_CHANADMIN; #endif if (!strcmp(s, "owner") || !strcmp(s, "chanowner")) #ifdef PREFIX_AQ return CHFL_CHANOWNER; #else return CHFL_CHANOP|CHFL_CHANOWNER; #endif return 0; /* unknown or unsupported */ } /* Channel flag (eg: CHFL_CHANOWNER) to SJOIN symbol (eg: *). * WARNING: Do not confuse SJOIN symbols with prefixes in /NAMES! */ char *chfl_to_sjoin_symbol(int s) { switch(s) { case CHFL_VOICE: return "+"; case CHFL_HALFOP: return "%"; case CHFL_CHANOP: return "@"; case CHFL_CHANADMIN: #ifdef PREFIX_AQ return "~"; #else return "~@"; #endif case CHFL_CHANOWNER: #ifdef PREFIX_AQ return "*"; #else return "*@"; #endif case CHFL_DEOPPED: default: return ""; } /* NOT REACHED */ } char chfl_to_chanmode(int s) { switch(s) { case CHFL_VOICE: return 'v'; case CHFL_HALFOP: return 'h'; case CHFL_CHANOP: return 'o'; case CHFL_CHANADMIN: return 'a'; case CHFL_CHANOWNER: return 'q'; case CHFL_DEOPPED: default: return '\0'; } /* NOT REACHED */ } Policy policy_strtoval(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; } 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(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; } 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(char *str) { if (!strcmp(str, "ip")) return BAN_TARGET_IP; else if (!strcmp(str, "userip")) return BAN_TARGET_USERIP; else if (!strcmp(str, "host")) return BAN_TARGET_HOST; else if (!strcmp(str, "userhost")) return BAN_TARGET_USERHOST; else if (!strcmp(str, "account")) return BAN_TARGET_ACCOUNT; else if (!strcmp(str, "certfp")) return BAN_TARGET_CERTFP; return 0; /* invalid */ } /* Used for set::automatic-ban-target and set::manual-ban-target */ 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(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; } 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(char *filename, char *displayname) { struct stat sb; int fd; int ret; char *buf = NULL; ConfigFile *cfptr; if (!displayname) displayname = filename; #ifndef _WIN32 fd = open(filename, O_RDONLY); #else fd = open(filename, O_RDONLY|O_BINARY); #endif if (fd == -1) { config_error("Couldn't open \"%s\": %s\n", filename, strerror(errno)); return NULL; } if (fstat(fd, &sb) == -1) { config_error("Couldn't fstat \"%s\": %s\n", filename, strerror(errno)); close(fd); return NULL; } if (!sb.st_size) { /* Workaround for empty files */ cfptr = config_parse(filename, " "); close(fd); return cfptr; } buf = safe_alloc(sb.st_size+1); if (buf == NULL) { config_error("Out of memory trying to load \"%s\"\n", filename); close(fd); return NULL; } ret = read(fd, buf, sb.st_size); if (ret != sb.st_size) { config_error("Error reading \"%s\": %s\n", filename, ret == -1 ? strerror(errno) : strerror(EFAULT)); safe_free(buf); close(fd); return NULL; } /* Just me or could this cause memory corrupted when ret <0 ? */ buf[ret] = '\0'; close(fd); add_entropy_configfile(&sb, buf); cfptr = config_parse(displayname, buf); safe_free(buf); return cfptr; } void config_free(ConfigFile *cfptr) { ConfigFile *nptr; for(;cfptr;cfptr=nptr) { nptr = cfptr->cf_next; if (cfptr->cf_entries) config_entry_free_all(cfptr->cf_entries); safe_free(cfptr->cf_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(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 */ char *unreal_add_quotes(char *str) { static char qbuf[2048]; *qbuf = '\0'; unreal_add_quotes_r(str, qbuf, sizeof(qbuf)); return qbuf; } ConfigFile *config_parse(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(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->cf_filename, filename); lastce = &(curcf->cf_entries); 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->ce_next); curce->ce_fileposend = (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->ce_entries) { 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->ce_sectlinenum = linenumber; lastce = &(curce->ce_entries); 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->ce_fileposend = (ptr - confdata); cursection = cursection->ce_prevlevel; if (!cursection) lastce = &(curcf->cf_entries); else lastce = &(cursection->ce_entries); for(;*lastce;lastce = &((*lastce)->ce_next)) continue; if (*(ptr+1) != ';') { /* Simulate closing ; so you can get away with } instead of ugly }; */ *lastce = curce; lastce = &(curce->ce_next); curce->ce_fileposend = (ptr - confdata); curce = NULL; } break; case '#': ptr++; while(*ptr && (*ptr != '\n')) ptr++; if (!*ptr) break; ptr--; continue; case '/': if (*(ptr+1) == '/') { ptr += 2; while(*ptr && (*ptr != '\n')) ptr++; if (!*ptr) break; ptr--; /* grab the \n on next loop thru */ continue; } else if (*(ptr+1) == '*') { int commentstart = linenumber; for(ptr+=2;*ptr;ptr++) { if (*ptr == '\n') { linenumber++; } else if ((*ptr == '*') && (*(ptr+1) == '/')) { ptr++; break; } } if (!*ptr) { config_error("%s:%i Comment on line %d does not end\n", filename, commentstart, commentstart); errors++; config_entry_free_all(curce); config_free(curcf); return NULL; } } break; case '\"': if (curce && curce->ce_varlinenum != linenumber && cursection) { config_error("%s:%i: Missing semicolon (';') at end of line. " "Line %d must end with a ; character\n", filename, curce->ce_varlinenum, curce->ce_varlinenum); errors++; *lastce = curce; lastce = &(curce->ce_next); curce->ce_fileposend = (ptr - confdata); curce = NULL; } start = ++ptr; for(;*ptr;ptr++) { if (*ptr == '\\') { if ((ptr[1] == '\\') || (ptr[1] == '"')) { /* \\ or \" in config file (escaped) */ ptr++; /* skip */ continue; } } else if ((*ptr == '\"') || (*ptr == '\n')) 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->ce_vardata) { config_error("%s:%i: Extra data detected. Perhaps missing a ';' or one too many?\n", filename, linenumber); errors++; } else { safe_strldup(curce->ce_vardata, start, ptr-start+1); preprocessor_replace_defines(&curce->ce_vardata, curce); unreal_del_quotes(curce->ce_vardata); } } else { curce = safe_alloc(sizeof(ConfigEntry)); curce->ce_varlinenum = linenumber; curce->ce_fileptr = curcf; curce->ce_prevlevel = cursection; curce->ce_fileposstart = (start - confdata); safe_strldup(curce->ce_varname, start, ptr-start+1); preprocessor_replace_defines(&curce->ce_varname, curce); unreal_del_quotes(curce->ce_varname); preprocessor_cc_duplicate_list(cc_list, &curce->ce_cond); } break; case '\n': linenumber++; /* fall through */ case '\t': case ' ': case '=': case '\r': break; case '@': /* Preprocessor item, such as @if, @define, etc. */ start = ptr; for (;*ptr; ptr++) { if (*ptr == '\n') break; } cc = NULL; n = parse_preprocessor_item(start, ptr, filename, linenumber, &cc); linenumber++; if (n == PREPROCESSOR_IF) { preprocessor_level++; cc->priority = preprocessor_level; AddListItem(cc, cc_list); } else if (n == PREPROCESSOR_ENDIF) { if (preprocessor_level == 0) { config_error("%s:%i: @endif unexpected. There was no preciding unclosed @if.", filename, linenumber); errors++; } preprocessor_cc_free_level(&cc_list, preprocessor_level); preprocessor_level--; } else if (n == PREPROCESSOR_ERROR) { errors++; goto breakout; } if (!*ptr) goto breakout; /* special case, since we don't want the for loop to ptr++ */ break; default: if ((*ptr == '*') && (*(ptr+1) == '/')) { config_status("%s:%i: Ignoring extra end comment\n", filename, linenumber); config_status("WARNING: Starting with UnrealIRCd 4.2.1 a /*-style comment stops as soon as the first */ is encountered. " "See https://www.unrealircd.org/docs/FAQ#Nesting_comments for more information."); ptr++; break; } start = ptr; for(;*ptr;ptr++) { if ((*ptr == ' ') || (*ptr == '=') || (*ptr == '\t') || (*ptr == '\n') || (*ptr == ';')) break; } if (!*ptr) { if (curce) config_error("%s: End of file reached but directive or block at line %i did not end properly. " "Perhaps a missing ; (semicolon) somewhere?\n", filename, curce->ce_varlinenum); 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->ce_sectlinenum); 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->ce_vardata) { 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->ce_vardata, start, ptr-start+1); preprocessor_replace_defines(&curce->ce_vardata, curce); } } else { curce = safe_alloc(sizeof(ConfigEntry)); memset(curce, 0, sizeof(ConfigEntry)); curce->ce_varlinenum = linenumber; curce->ce_fileptr = curcf; curce->ce_prevlevel = cursection; curce->ce_fileposstart = (start - confdata); safe_strldup(curce->ce_varname, start, ptr-start+1); preprocessor_replace_defines(&curce->ce_varname, curce); if (curce->ce_cond) abort(); // hmm this can be reached? FIXME! preprocessor_cc_duplicate_list(cc_list, &curce->ce_cond); } 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->ce_varlinenum); 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->ce_sectlinenum); 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->ce_next; if (ce->ce_entries) config_entry_free_all(ce->ce_entries); safe_free(ce->ce_varname); safe_free(ce->ce_vardata); if (ce->ce_cond) preprocessor_cc_free_list(ce->ce_cond); 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->ce_entries) config_entry_free_all(ce->ce_entries); safe_free(ce->ce_varname); safe_free(ce->ce_vardata); if (ce->ce_cond) preprocessor_cc_free_list(ce->ce_cond); safe_free(ce); } ConfigEntry *config_find_entry(ConfigEntry *ce, char *name) { ConfigEntry *cep; for (cep = ce; cep; cep = cep->ce_next) if (cep->ce_varname && !strcmp(cep->ce_varname, 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'; #ifdef _WIN32 if (!loop.ircd_booted) win_log("[error] %s", buffer); #endif ircd_log(LOG_ERROR, "config error: %s", buffer); sendto_realops("error: %s", buffer); if (remote_rehash_client) sendnotice(remote_rehash_client, "error: %s", 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'; #ifdef _WIN32 if (!loop.ircd_booted) win_log("* %s", buffer); #endif ircd_log(LOG_ERROR, "%s", buffer); sendto_realops("%s", buffer); if (remote_rehash_client) sendnotice(remote_rehash_client, "%s", 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'; #ifdef _WIN32 if (!loop.ircd_booted) win_log("[warning] %s", buffer); #endif ircd_log(LOG_ERROR, "[warning] %s", buffer); sendto_realops("[warning] %s", buffer); if (remote_rehash_client) sendnotice(remote_rehash_client, "[warning] %s", 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->ce_vardata) { if(fatal) config_error("%s:%i: %s: : no file specified", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, entry); else config_warn("%s:%i: %s: : no file specified", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, entry); return 1; } /* There's not much checking that can be done for asynchronously downloaded files */ #ifdef USE_LIBCURL if(url_is_valid(cep->ce_vardata)) { 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->ce_fileptr->cf_filename, cep->ce_varlinenum, entry, cep->ce_vardata); if(fatal) return 1; else return 0; } #else if (strstr(cep->ce_vardata, "://")) { config_error("%s:%d: %s: UnrealIRCd was not compiled with remote includes support " "so you cannot use URLs here.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, entry); return 1; } #endif /* USE_LIBCURL */ /* * 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->ce_vardata, flags, mode); else fd = open(cep->ce_vardata, flags); if(fd == -1) { if(fatal) config_error("%s:%i: %s: %s: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, entry, cep->ce_vardata, strerror(errno)); else config_warn("%s:%i: %s: %s: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, entry, cep->ce_vardata, strerror(errno)); return 1; } close(fd); return 0; } void config_progress(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'; #ifdef _WIN32 if (!loop.ircd_booted) win_log("* %s", buffer); #endif sendto_realops("%s", buffer); } int config_is_blankorempty(ConfigEntry *cep, const char *block) { if (!cep->ce_vardata) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, block, cep->ce_varname); return 1; } return 0; } ConfigCommand *config_binary_search(char *cmd) { int start = 0; int stop = ARRAY_SIZEOF(_ConfigCommands)-1; int mid; while (start <= stop) { mid = (start+stop)/2; if (smycmp(cmd,_ConfigCommands[mid].name) < 0) { stop = mid-1; } else if (strcmp(cmd,_ConfigCommands[mid].name) == 0) { return &_ConfigCommands[mid]; } else start = mid+1; } return NULL; } void free_iConf(Configuration *i) { safe_free(i->dns_bindip); safe_free(i->link_bindip); safe_free(i->kline_address); safe_free(i->gline_address); safe_free(i->oper_snomask); safe_free(i->auto_join_chans); safe_free(i->oper_auto_join_chans); safe_free(i->allow_user_stats); // allow_user_stats_ext is freed elsewhere safe_free(i->egd_path); safe_free(i->static_quit); safe_free(i->static_part); free_tls_options(i->tls_options); i->tls_options = NULL; safe_free(i->tls_options); safe_free_multiline(i->plaintext_policy_user_message); safe_free_multiline(i->plaintext_policy_oper_message); safe_free(i->outdated_tls_policy_user_message); safe_free(i->outdated_tls_policy_oper_message); safe_free(i->restrict_usermodes); safe_free(i->restrict_channelmodes); safe_free(i->restrict_extendedbans); safe_free(i->channel_command_prefix); safe_free(i->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); // network struct: safe_free(i->network.x_ircnetwork); safe_free(i->network.x_ircnet005); safe_free(i->network.x_defserv); safe_free(i->network.x_services_name); safe_free(i->network.x_hidden_host); safe_free(i->network.x_prefix_quit); safe_free(i->network.x_helpchan); safe_free(i->network.x_stats_server); safe_free(i->network.x_sasl_server); } int config_test(); void config_setdefaultsettings(Configuration *i) { char tmp[512]; i->unknown_flood_amount = 4; i->unknown_flood_bantime = 600; safe_strdup(i->oper_snomask, SNO_DEFOPER); i->ident_read_timeout = 7; i->ident_connect_timeout = 3; i->nick_count = 3; i->nick_period = 60; /* NICK flood protection: max 3 per 60s */ i->away_count = 4; i->away_period = 120; /* AWAY flood protection: max 4 per 120s */ i->invite_count = 4; i->invite_period = 60; /* INVITE flood protection: max 4 per 60s */ i->knock_count = 4; i->knock_period = 120; /* KNOCK protection: max 4 per 120s */ i->throttle_count = 3; i->throttle_period = 60; /* throttle protection: max 3 per 60s */ i->ban_version_tkl_time = 86400; /* 1d */ i->spamfilter_ban_time = 86400; /* 1d */ safe_strdup(i->spamfilter_ban_reason, "Spam/advertising"); safe_strdup(i->spamfilter_virus_help_channel, "#help"); i->spamfilter_detectslow_warn = 250; i->spamfilter_detectslow_fatal = 500; i->spamfilter_stop_on_first_match = 1; i->maxchannelsperuser = 10; i->maxdccallow = 10; safe_strdup(i->channel_command_prefix, "`!."); conf_channelmodes("+nt", &i->modes_on_join, 0); i->check_target_nick_bans = 1; i->maxbans = 60; i->maxbanlength = 2048; i->level_on_join = CHFL_CHANOP; 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->network.x_hidden_host, "Clk"); if (!ipv6_capable()) DISABLE_IPV6 = 1; safe_strdup(i->network.x_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; /* SSL/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_ALL; #ifdef HAS_SSL_CTX_SET1_CURVES_LIST safe_strdup(i->tls_options->ecdh_curves, UNREALIRCD_DEFAULT_ECDH_CURVES); #endif safe_strdup(i->tls_options->outdated_protocols, "TLSv1,TLSv1.1"); /* the following may look strange but "AES*" matches all * AES ciphersuites that do not have Forward Secrecy. * Any decent client using AES will use ECDHE-xx-AES. */ safe_strdup(i->tls_options->outdated_ciphers, "AES*,RC4*,DES*"); i->plaintext_policy_user = POLICY_ALLOW; i->plaintext_policy_oper = POLICY_DENY; i->plaintext_policy_server = POLICY_DENY; i->outdated_tls_policy_user = POLICY_WARN; i->outdated_tls_policy_oper = POLICY_DENY; i->outdated_tls_policy_server = POLICY_DENY; safe_strdup(i->reject_message_too_many_connections, "Too many connections from your IP"); safe_strdup(i->reject_message_server_full, "This server is full"); safe_strdup(i->reject_message_unauthorized, "You are not authorized to connect to this server"); safe_strdup(i->reject_message_kline, "You are not welcome on this server. $bantype: $banreason. Email $klineaddr for more information."); safe_strdup(i->reject_message_gline, "You are not welcome on this network. $bantype: $banreason. Email $glineaddr for more information."); i->topic_setter = SETTER_NICK; i->ban_setter = SETTER_NICK; i->ban_setter_sync = 1; i->max_concurrent_conversations_users = 10; i->max_concurrent_conversations_new_user_every = 15; 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; } static void make_default_logblock(void) { ConfigItem_log *ca = safe_alloc(sizeof(ConfigItem_log)); config_status("No log { } block found -- using default: errors will be logged to 'ircd.log'"); safe_strdup(ca->file, "ircd.log"); convert_to_absolute_path(&ca->file, LOGDIR); ca->flags |= LOG_ERROR; ca->logfd = -1; AddListItem(ca, conf_log); } /** Similar to config_setdefaultsettings but this one is applied *AFTER* * the entire configuration has been ran (sometimes this is the only way it can be done..). * NOTE: iConf is thus already populated with (non-default) values. Only overwrite if necessary! */ void postconf_defaults(void) { TKL *tk; char *encoded; if (!iConf.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 SSL/TLS."); else if (iConf.plaintext_policy_user == POLICY_WARN) addmultiline(&iConf.plaintext_policy_user_message, "WARNING: Insecure connection. Please consider using SSL/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 (SSL/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 SSL/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 SSL/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 SSL/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 SSL/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 SSL/TLS protocol or ciphersuite ($protocol-$cipher). Please upgrade your IRC client."); } /* We got a chicken-and-egg problem here.. antries added without reason or ban-time * field should use the config default (set::spamfilter::ban-reason/ban-time) but * this isn't (or might not) be known yet when parsing spamfilter entries.. * so we do a VERY UGLY mass replace here.. unless someone else has a better idea. */ encoded = unreal_encodespace(SPAMFILTER_BAN_REASON); if (!encoded) abort(); /* hack to trace 'impossible' bug... */ // FIXME: remove this stuff with ~server~, why not just use -config- // which is more meaningful. for (tk = tklines[tkl_hash('q')]; tk; tk = tk->next) { if (tk->type != TKL_NAME) continue; if (!tk->set_by) { if (me.name[0] != '\0') safe_strdup(tk->set_by, me.name); else safe_strdup(tk->set_by, conf_me->name ? conf_me->name : "~server~"); } } for (tk = tklines[tkl_hash('f')]; tk; tk = tk->next) { if (tk->type != TKL_SPAMF) continue; /* global entry or something else.. */ if (!strcmp(tk->ptr.spamfilter->tkl_reason, "")) { safe_strdup(tk->ptr.spamfilter->tkl_reason, encoded); tk->ptr.spamfilter->tkl_duration = SPAMFILTER_BAN_TIME; } /* This one is even more ugly, but our config crap is VERY confusing :[ */ if (!tk->set_by) { if (me.name[0] != '\0') safe_strdup(tk->set_by, me.name); else safe_strdup(tk->set_by, conf_me->name ? conf_me->name : "~server~"); } } if (!conf_log) make_default_logblock(); } void postconf_fixes(void) { /* If set::topic-setter is set to "nick-user-host" then the * maximum topic length becomes shorter. */ if ((iConf.topic_setter == SETTER_NICK_USER_HOST) && (iConf.topic_length > 340)) { config_warn("set::topic-length adjusted from %d to 340, which is the maximum because " "set::topic-setter is set to 'nick-user-host'.", iConf.topic_length); iConf.topic_length = 340; } } /* Needed for set::options::allow-part-if-shunned, * we can't just make it CMD_SHUN and do a ALLOW_PART_IF_SHUNNED in * cmd_part itself because that will also block internal calls (like sapart). -- Syzop */ static void do_weird_shun_stuff() { RealCommand *cmptr; if ((cmptr = find_command_simple("PART"))) { if (ALLOW_PART_IF_SHUNNED) cmptr->flags |= CMD_SHUN; else cmptr->flags &= ~CMD_SHUN; } } /** Various things that are done at the very end after the configuration file * has been read and almost all values have been set. This is to deal with * things like adding a default log { } block if there is none and that kind * of things. * This function is called by init_conf(), both on boot and on rehash. */ void postconf(void) { postconf_defaults(); postconf_fixes(); do_weird_shun_stuff(); isupport_init(); /* for all the 005 values that changed.. */ } 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)); } void upgrade_conf_to_34(void) { config_error("******************************************************************"); config_error("This *seems* an UnrealIRCd 3.2.x configuration file."); #ifdef _WIN32 if (!IsService) config_error("In next screen you will be prompted to automatically upgrade the configuration file(s)."); else { config_error("We offer a configuration file converter to convert 3.2.x conf's to 4.x, however this " "is not available when running as a service. If you want to use it, make UnrealIRCd " "run in GUI mode by running 'unreal uninstall'. Then start UnrealIRCd.exe and when " "it prompts you to convert the configuration click 'Yes'. Check if UnrealIRCd boots properly. " "Once everything is looking good you can run 'unreal install' to make UnrealIRCd run " "as a service again."); /* TODO: make this unnecessary :D */ } #else config_error("To upgrade it to the new 4.x format, run: ./unrealircd upgrade-conf"); #endif config_error("******************************************************************"); /* TODO: win32 may require a different error */ } /** Reset config tests (before running the config test) */ void config_test_reset(void) { } /** Run config test and all post config tests. */ int config_test_all(void) { if ((config_test() < 0) || (callbacks_check() < 0) || (efunctions_check() < 0) || reloadable_perm_module_unloaded() || !tls_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->cf_next) { if (config_verbose > 1) config_status("Testing %s", cfptr->cf_filename); for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) { if (!strcmp(ce->ce_varname, "loadmodule")) { if (ce->ce_cond) { config_error("%s:%d: Currently you cannot have a 'loadmodule' statement " "within an @if block, sorry.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); 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 */ } int init_conf(char *rootconf, int rehash) { char *old_pid_file = NULL; config_status("Loading IRCd configuration.."); if (conf) { config_error("%s:%i - Someone forgot to clean up", __FILE__, __LINE__); return -1; } memset(&tempiConf, 0, sizeof(iConf)); memset(&settings, 0, sizeof(settings)); memset(&requiredstuff, 0, sizeof(requiredstuff)); memset(&nicklengths, 0, sizeof(nicklengths)); config_setdefaultsettings(&tempiConf); clicap_pre_rehash(); free_config_defines(); /* * the rootconf must be listed in the conf_include for include * recursion prevention code and sanity checking code to be * made happy :-). Think of it as us implicitly making an * in-memory config file that looks like: * * include "unrealircd.conf"; */ add_include(rootconf, "[thin air]", -1); if ((load_conf(rootconf, rootconf) > 0) && config_loadmodules()) { preprocessor_resolve_conditionals_all(PREPROCESSOR_PHASE_MODULE); config_test_reset(); if (!config_test_all()) { config_error("IRCd configuration failed to pass testing"); #ifdef _WIN32 if (!rehash) win_error(); #endif Unload_all_testing_modules(); unload_notloaded_includes(); config_free(conf); conf = NULL; free_iConf(&tempiConf); return -1; } callbacks_switchover(); efunctions_switchover(); set_targmax_defaults(); if (rehash) { Hook *h; safe_strdup(old_pid_file, conf_files->pid_file); unrealdns_delasyncconnects(); config_rehash(); Unload_all_loaded_modules(); /* 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))(); } unload_loaded_includes(); } load_includes(); Init_all_testing_modules(); if (config_run() < 0) { config_error("Bad case of config errors. Server will now die. This really shouldn't happen"); #ifdef _WIN32 if (!rehash) win_error(); #endif abort(); } applymeblock(); if (old_pid_file && strcmp(old_pid_file, conf_files->pid_file)) { sendto_ops("pidfile is being rewritten to %s, please delete %s", conf_files->pid_file, old_pid_file); write_pidfile(); } safe_free(old_pid_file); } else { config_error("IRCd configuration failed to load"); Unload_all_testing_modules(); unload_notloaded_includes(); config_free(conf); conf = NULL; free_iConf(&tempiConf); #ifdef _WIN32 if (!rehash) win_error(); #endif return -1; } config_free(conf); conf = NULL; if (rehash) { module_loadall(); RunHook0(HOOKTYPE_REHASH_COMPLETE); } postconf(); config_status("Configuration loaded."); clicap_post_rehash(); return 0; } /** * Processes filename as part of the IRCd's configuration. * * One _must_ call add_include() or add_remote_include() before * calling load_conf(). 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 ;-). (reported by warg). * * This function will set INCLUDE_USED on the config_include list * entry if the config file loaded without error. * * @param filename the file where the conf may be read from * @param original_path 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 load_conf(char *filename, const char *original_path) { ConfigFile *cfptr, *cfptr2, **cfptr3; ConfigEntry *ce; ConfigItem_include *inc, *my_inc; int ret; int counter; if (config_verbose > 0) config_status("Loading config file %s ..", filename); need_34_upgrade = 0; 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. */ counter = 0; my_inc = NULL; for (inc = conf_include; inc; inc = inc->next) { /* * ignore files which were part of a _previous_ * successful rehash. */ if (!(inc->flag.type & INCLUDE_NOTLOADED)) continue; if (!counter) my_inc = inc; if (!strcmp(filename, inc->file)) { counter ++; continue; } #ifdef _WIN32 if (!strcasecmp(filename, inc->file)) { counter ++; continue; } #endif #ifdef USE_LIBCURL if (inc->url && !strcmp(original_path, inc->url)) { counter ++; continue; } #endif } if (counter < 1 || !my_inc) { /* * The following is simply for debugging/[sanity * checking]. To make sure that functions call * add_include() or add_remote_include() before * calling us. */ config_error("I don't have a record for %s being included." " Perhaps someone forgot to call add_include()?", filename); abort(); } if (counter > 1 || my_inc->flag.type & INCLUDE_USED) { config_error("%s:%d:include: Config file %s has been loaded before %d time." " You may include each file only once.", my_inc->included_from, my_inc->included_from_line, filename, counter - 1); return -1; } /* end include recursion checking code */ if ((cfptr = config_load(filename, NULL))) { for (cfptr3 = &conf, cfptr2 = conf; cfptr2; cfptr2 = cfptr2->cf_next) cfptr3 = &cfptr2->cf_next; *cfptr3 = cfptr; if (config_verbose > 1) config_status("Loading module blacklist in %s", filename); preprocessor_resolve_conditionals_ce(&cfptr->cf_entries, PREPROCESSOR_PHASE_INITIAL); for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) if (!strcmp(ce->ce_varname, "blacklist-module")) _test_blacklist_module(cfptr, ce); /* Load modules */ if (config_verbose > 1) config_status("Loading modules in %s", filename); if (need_34_upgrade) upgrade_conf_to_34(); /* Load includes */ if (config_verbose > 1) config_status("Searching through %s for include files..", filename); for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) if (!strcmp(ce->ce_varname, "include")) { if (ce->ce_cond) { 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->ce_fileptr->cf_filename, ce->ce_varlinenum); return -1; } ret = _conf_include(cfptr, ce); if (need_34_upgrade) upgrade_conf_to_34(); if (ret < 0) return ret; } my_inc->flag.type |= INCLUDE_USED; return 1; } else { config_error("Could not load config file %s", filename); #ifdef _WIN32 if (!strcmp(filename, "conf/unrealircd.conf")) { if (file_exists("unrealircd.conf")) { config_error("Note that 'unrealircd.conf' now belongs in the 'conf' subdirectory! (So move it to there)"); } else { config_error("New to UnrealIRCd? Be sure to read https://www.unrealircd.org/docs/Installing_%%28Windows%%29"); } } #endif return -1; } } /** Remove all TKL's that were added by the config file(s). * This is done after config passed testing and right before * adding the (new) entries. */ void remove_config_tkls(void) { TKL *tk, *tk_next; int index, index2; /* IP hashed TKL list */ for (index = 0; index < TKLIPHASHLEN1; index++) { for (index2 = 0; index2 < TKLIPHASHLEN2; index2++) { for (tk = tklines_ip_hash[index][index2]; tk; tk = tk_next) { tk_next = tk->next; if (tk->flags & TKL_FLAG_CONFIG) tkl_del_line(tk); } } } /* Generic TKL list */ for (index = 0; index < TKLISTLEN; index++) { for (tk = tklines[index]; tk; tk = tk_next) { tk_next = tk->next; if (tk->flags & TKL_FLAG_CONFIG) tkl_del_line(tk); } } } void config_rehash() { ConfigItem_oper *oper_ptr; ConfigItem_class *class_ptr; ConfigItem_ulines *uline_ptr; ConfigItem_allow *allow_ptr; ConfigItem_except *except_ptr; ConfigItem_ban *ban_ptr; ConfigItem_link *link_ptr; ConfigItem_listen *listen_ptr; ConfigItem_tld *tld_ptr; ConfigItem_vhost *vhost_ptr; ConfigItem_deny_link *deny_link_ptr; ConfigItem_deny_channel *deny_channel_ptr; ConfigItem_allow_channel *allow_channel_ptr; ConfigItem_admin *admin_ptr; ConfigItem_deny_version *deny_version_ptr; ConfigItem_log *log_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); unreal_delete_masks(oper_ptr->mask); 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) { Debug((DEBUG_ERROR, "s_conf: deleting block %s (refcount 0)", link_ptr->servername)); delete_linkblock(link_ptr); } else { Debug((DEBUG_ERROR, "s_conf: marking block %s (refcount %d) as temporary", link_ptr->servername, link_ptr->refcount)); 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; safe_free(allow_ptr->ip); safe_free(allow_ptr->hostname); Auth_FreeAuthConfig(allow_ptr->auth); DelListItem(allow_ptr, conf_allow); safe_free(allow_ptr); } for (except_ptr = conf_except; except_ptr; except_ptr = (ConfigItem_except *) next) { next = (ListStruct *)except_ptr->next; safe_free(except_ptr->mask); DelListItem(except_ptr, conf_except); safe_free(except_ptr); } /* Free ban realname { }, ban server { } and ban version { } */ for (ban_ptr = conf_ban; ban_ptr; ban_ptr = (ConfigItem_ban *) next) { next = (ListStruct *)ban_ptr->next; if (ban_ptr->flag.type2 == CONF_BAN_TYPE_CONF || ban_ptr->flag.type2 == CONF_BAN_TYPE_TEMPORARY) { safe_free(ban_ptr->mask); safe_free(ban_ptr->reason); DelListItem(ban_ptr, conf_ban); safe_free(ban_ptr); } } for (listen_ptr = conf_listen; listen_ptr; listen_ptr = listen_ptr->next) { 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); 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); unreal_delete_masks(vhost_ptr->mask); for (s = vhost_ptr->swhois; s; s = s_next) { s_next = s->next; safe_free(s->line); safe_free(s->setby); safe_free(s); } DelListItem(vhost_ptr, conf_vhost); safe_free(vhost_ptr); } remove_config_tkls(); for (deny_link_ptr = conf_deny_link; deny_link_ptr; deny_link_ptr = (ConfigItem_deny_link *) next) { next = (ListStruct *)deny_link_ptr->next; safe_free(deny_link_ptr->prettyrule); safe_free(deny_link_ptr->mask); crule_free(&deny_link_ptr->rule); DelListItem(deny_link_ptr, conf_deny_link); safe_free(deny_link_ptr); } 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); unreal_delete_masks(deny_channel_ptr->mask); 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); unreal_delete_masks(allow_channel_ptr->mask); 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 (log_ptr = conf_log; log_ptr; log_ptr = (ConfigItem_log *)next) { next = (ListStruct *)log_ptr->next; if (log_ptr->logfd != -1) fd_close(log_ptr->logfd); safe_free(log_ptr->file); DelListItem(log_ptr, conf_log); safe_free(log_ptr); } for (alias_ptr = conf_alias; alias_ptr; alias_ptr = (ConfigItem_alias *)next) { RealCommand *cmptr = find_command(alias_ptr->alias, 0); ConfigItem_alias_format *fmt; next = (ListStruct *)alias_ptr->next; safe_free(alias_ptr->nick); if (cmptr) CommandDelX(NULL, cmptr); safe_free(alias_ptr->alias); if (alias_ptr->format && (alias_ptr->type == ALIAS_COMMAND)) { for (fmt = (ConfigItem_alias_format *) alias_ptr->format; fmt; fmt = (ConfigItem_alias_format *) next2) { next2 = (ListStruct *)fmt->next; safe_free(fmt->format); safe_free(fmt->nick); safe_free(fmt->parameters); unreal_delete_match(fmt->expr); DelListItem(fmt, alias_ptr->format); safe_free(fmt); } } DelListItem(alias_ptr, conf_alias); safe_free(alias_ptr); } for (help_ptr = conf_help; help_ptr; help_ptr = (ConfigItem_help *)next) { MOTDLine *text; next = (ListStruct *)help_ptr->next; safe_free(help_ptr->command); while (help_ptr->text) { text = help_ptr->text->next; safe_free(help_ptr->text->line); safe_free(help_ptr->text); help_ptr->text = text; } DelListItem(help_ptr, conf_help); safe_free(help_ptr); } for (os_ptr = iConf.allow_user_stats_ext; os_ptr; os_ptr = (OperStat *)next) { next = (ListStruct *)os_ptr->next; safe_free(os_ptr->flag); safe_free(os_ptr); } iConf.allow_user_stats_ext = NULL; for (spamex_ptr = iConf.spamexcept; spamex_ptr; spamex_ptr = (SpamExcept *)next) { next = (ListStruct *)spamex_ptr->next; safe_free(spamex_ptr); } iConf.spamexcept = NULL; for (of_ptr = conf_offchans; of_ptr; of_ptr = (ConfigItem_offchans *)next) { next = (ListStruct *)of_ptr->next; safe_free(of_ptr->topic); safe_free(of_ptr); } conf_offchans = NULL; /* Free sni { } blocks */ for (sni = conf_sni; sni; sni = (ConfigItem_sni *)next) { next = (ListStruct *)sni->next; SSL_CTX_free(sni->ssl_ctx); free_tls_options(sni->tls_options); safe_free(sni->name); safe_free(sni); } conf_sni = NULL; free_conf_channelmodes(&iConf.modes_on_join); /* reset conf_files -- should this be in its own function? no, because it's only used here */ safe_free(conf_files->motd_file); safe_free(conf_files->smotd_file); safe_free(conf_files->opermotd_file); safe_free(conf_files->svsmotd_file); safe_free(conf_files->botmotd_file); safe_free(conf_files->rules_file); safe_free(conf_files->pid_file); safe_free(conf_files->tune_file); /* Don't free conf_files->pid_file here; the old value is used to determine if the pidfile location has changed and write_pidfile() needs to be called again. */ safe_free(conf_files); conf_files = NULL; } int config_post_test() { #define Error(x) { config_error((x)); errors++; } int errors = 0; Hook *h; if (!requiredstuff.conf_me) Error("me {} block is missing"); if (!requiredstuff.conf_admin) Error("admin {} block is missing"); if (!requiredstuff.conf_listen) Error("listen {} block is missing"); if (!settings.has_kline_address) Error("set::kline-address is missing"); if (!settings.has_default_server) Error("set::default-server is missing"); if (!settings.has_network_name) Error("set::network-name is missing"); if (!settings.has_help_channel) Error("set::help-channel is missing"); if (nicklengths.min > nicklengths.max) Error("set::nick-length is smaller than set::min-nick-length"); for (h = Hooks[HOOKTYPE_CONFIGPOSTTEST]; h; h = h->next) { int value, errs = 0; if (h->owner && !(h->owner->flags & MODFLAG_TESTING) && !(h->owner->options & MOD_OPT_PERM)) continue; value = (*(h->func.intfunc))(&errs); if (value == -1) { errors += errs; break; } if (value == -2) errors += errs; } return errors; } int config_run() { ConfigEntry *ce; ConfigFile *cfptr; ConfigCommand *cc; int errors = 0; Hook *h; ConfigItem_allow *allow; /* Stage 1: set block first */ for (cfptr = conf; cfptr; cfptr = cfptr->cf_next) { if (config_verbose > 1) config_status("Running %s", cfptr->cf_filename); for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) { if (!strcmp(ce->ce_varname, "set")) { if (_conf_set(cfptr, ce) < 0) errors++; } } } /* Stage 2: now class blocks */ for (cfptr = conf; cfptr; cfptr = cfptr->cf_next) { if (config_verbose > 1) config_status("Running %s", cfptr->cf_filename); for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) { if (!strcmp(ce->ce_varname, "class")) { if (_conf_class(cfptr, ce) < 0) errors++; } } } /* Stage 3: now all the rest */ for (cfptr = conf; cfptr; cfptr = cfptr->cf_next) { if (config_verbose > 1) config_status("Running %s", cfptr->cf_filename); for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) { if (!strcmp(ce->ce_varname, "set") || !strcmp(ce->ce_varname, "class")) continue; // already processed if ((cc = config_binary_search(ce->ce_varname))) { if ((cc->conffunc) && (cc->conffunc(cfptr, ce) < 0)) errors++; } else { int value; for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next) { value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN); if (value == 1) break; } } } } /* * transfer default values from set::ipv6_clones_mask into * each individual allow block. If other similar things like * this stack up here, perhaps this shoul be moved to another * function. */ for(allow = conf_allow; allow; allow = allow->next) if(!allow->ipv6_clone_mask) allow->ipv6_clone_mask = tempiConf.default_ipv6_clone_mask; /* ^^^ TODO: due to the two-stage model now we can do it in conf_allow again * and remove it here. */ close_unbound_listeners(); listen_cleanup(); close_unbound_listeners(); loop.do_bancheck = 1; free_iConf(&iConf); memcpy(&iConf, &tempiConf, sizeof(iConf)); memset(&tempiConf, 0, sizeof(tempiConf)); 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); } NameValue *config_binary_flags_search(NameValue *table, char *cmd, int size) { int start = 0; int stop = size-1; int mid; while (start <= stop) { mid = (start+stop)/2; if (smycmp(cmd,table[mid].name) < 0) { stop = mid-1; } else if (strcmp(cmd,table[mid].name) == 0) { return &(table[mid]); } else start = mid+1; } return NULL; } int config_test() { ConfigEntry *ce; ConfigFile *cfptr; ConfigCommand *cc; int errors = 0; Hook *h; need_34_upgrade = 0; for (cfptr = conf; cfptr; cfptr = cfptr->cf_next) { if (config_verbose > 1) config_status("Testing %s", cfptr->cf_filename); /* First test the set { } block */ for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) { if (!strcmp(ce->ce_varname, "set")) errors += _test_set(cfptr, ce); } /* Now test all the rest */ for (ce = cfptr->cf_entries; ce; ce = ce->ce_next) { /* These are already processed, so skip them here.. */ if (!strcmp(ce->ce_varname, "set")) continue; if ((cc = config_binary_search(ce->ce_varname))) { if (cc->testfunc) errors += (cc->testfunc(cfptr, ce)); } else { int used = 0; for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next) { int value, errs = 0; if (h->owner && !(h->owner->flags & MODFLAG_TESTING) && !(h->owner->options & MOD_OPT_PERM)) continue; value = (*(h->func.intfunc))(cfptr,ce,CONFIG_MAIN,&errs); if (value == 2) used = 1; if (value == 1) { used = 1; break; } if (value == -1) { used = 1; errors += errs; break; } if (value == -2) { used = 1; errors += errs; } } if (!used) { config_error("%s:%i: unknown directive %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_varname); errors++; if (strchr(ce->ce_varname, ':')) { config_error("You cannot use :: in a directive, you have to write them out. " "For example 'set::anti-flood::nick-flood 3:60' needs to be written as: " "set { anti-flood { nick-flood 3:60; } }"); config_error("See also https://www.unrealircd.org/docs/Set_block#Syntax_used_in_this_documentation"); } } } } } errors += config_post_test(); if (errors > 0) { config_error("%i errors encountered", errors); } if (need_34_upgrade) { upgrade_conf_to_34(); } return (errors > 0 ? -1 : 1); } /* * Service functions */ ConfigItem_alias *find_alias(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(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(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(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(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(char *ipmask, int port, int ipv6) { ConfigItem_listen *e; if (!ipmask) return NULL; for (e = conf_listen; e; e = e->next) if ((e->ipv6 == ipv6) && (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(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(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_except *find_except(Client *client, short type) { ConfigItem_except *excepts; for(excepts = conf_except; excepts; excepts = excepts->next) { if (excepts->flag.type == type) { if (match_user(excepts->mask, client, MATCH_CHECK_REAL)) return excepts; } } return NULL; } ConfigItem_tld *find_tld(Client *client) { ConfigItem_tld *tld; for (tld = conf_tld; tld; tld = tld->next) { if (match_user(tld->mask, client, MATCH_CHECK_REAL)) { if ((tld->options & TLD_TLS) && !IsSecureConnect(client)) continue; if ((tld->options & TLD_REMOTE) && MyUser(client)) continue; return tld; } } return NULL; } ConfigItem_link *find_link(char *servername, Client *client) { ConfigItem_link *link; for (link = conf_link; link; link = link->next) { if (match_simple(link->servername, servername) && unreal_mask_match(client, link->incoming.mask)) { 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, 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, 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(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, char *name) { ConfigItem_deny_channel *dchannel; ConfigItem_allow_channel *achannel; for (dchannel = conf_deny_channel; dchannel; dchannel = dchannel->next) { if (match_simple(dchannel->channel, name)) { if (dchannel->class && strcmp(client->local->class->name, dchannel->class)) continue; if (dchannel->mask && !unreal_mask_match(client, dchannel->mask)) continue; break; /* MATCH deny channel { } */ } } if (dchannel) { /* Check exceptions... ('allow channel') */ for (achannel = conf_allow_channel; achannel; achannel = achannel->next) { if (match_simple(achannel->channel, name)) { if (achannel->class && strcmp(client->local->class->name, achannel->class)) continue; if (achannel->mask && !unreal_mask_match(client, achannel->mask)) 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)); } char *pretty_time_val(long timeval) { static char buf[512]; if (timeval == 0) return "0"; buf[0] = 0; if (timeval/86400) snprintf(buf, sizeof(buf), "%ld day%s ", timeval/86400, timeval/86400 != 1 ? "s" : ""); if ((timeval/3600) % 24) snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ld hour%s ", (timeval/3600)%24, (timeval/3600)%24 != 1 ? "s" : ""); if ((timeval/60)%60) snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ld minute%s ", (timeval/60)%60, (timeval/60)%60 != 1 ? "s" : ""); if ((timeval%60)) snprintf(buf+strlen(buf), sizeof(buf)-strlen(buf), "%ld second%s", timeval%60, timeval%60 != 1 ? "s" : ""); /* Strip space at the end (if any) */ if (*buf && (buf[strlen(buf)-1] == ' ')) buf[strlen(buf)-1] = '\0'; return buf; } /* This converts a relative path to an absolute path, but only if necessary. */ void convert_to_absolute_path(char **path, char *reldir) { char *s; if (!*path || !**path) return; /* NULL or empty */ if (strstr(*path, "://")) return; /* URL: don't touch */ 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->ce_vardata) { config_status("%s:%i: include: no filename given", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return -1; } if (!strcmp(ce->ce_vardata, "help.conf")) need_34_upgrade = 1; convert_to_absolute_path(&ce->ce_vardata, CONFDIR); #ifdef USE_LIBCURL if (url_is_valid(ce->ce_vardata)) return remote_include(ce); #else if (strstr(ce->ce_vardata, "://")) { config_error("%s:%d: URL specified: %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); config_error("UnrealIRCd was not compiled with remote includes support " "so you cannot use URLs. You are suggested to re-run ./Config " "and answer YES to the question about remote includes."); return -1; } #endif #if !defined(_WIN32) && !defined(_AMIGA) && !defined(OSXTIGER) && DEFAULT_PERMISSIONS != 0 (void)chmod(ce->ce_vardata, DEFAULT_PERMISSIONS); #endif #ifdef GLOBH #if defined(__OpenBSD__) && defined(GLOB_LIMIT) glob(ce->ce_vardata, GLOB_NOSORT|GLOB_NOCHECK|GLOB_LIMIT, NULL, &files); #else glob(ce->ce_vardata, GLOB_NOSORT|GLOB_NOCHECK, NULL, &files); #endif if (!files.gl_pathc) { globfree(&files); config_status("%s:%i: include %s: invalid file given", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); return -1; } for (i = 0; i < files.gl_pathc; i++) { add_include(files.gl_pathv[i], ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(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->ce_vardata, '/') || strchr(ce->ce_vardata, '\\')) { strlcpy(cPath,ce->ce_vardata,MAX_PATH); cSlash=cPath+strlen(cPath); while(*cSlash != '\\' && *cSlash != '/' && cSlash > cPath) cSlash--; *(cSlash+1)=0; } if ( (hFind = FindFirstFile(ce->ce_vardata, &FindData)) == INVALID_HANDLE_VALUE ) { config_status("%s:%i: include %s: invalid file given", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); return -1; } if (cPath) { path = safe_alloc(strlen(cPath) + strlen(FindData.cFileName)+1); strcpy(path, cPath); strcat(path, FindData.cFileName); add_include(path, ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(path, path); safe_free(path); } else { add_include(FindData.cFileName, ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(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); add_include(path, ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(path, path); safe_free(path); if (ret < 0) break; } else { add_include(FindData.cFileName, ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(FindData.cFileName, FindData.cFileName); } } FindClose(hFind); if (ret < 0) return ret; #else add_include(ce->ce_vardata, ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(ce->ce_vardata, ce->ce_vardata); 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->ce_entries; cep; cep = cep->ce_next) { ca = safe_alloc(sizeof(ConfigItem_admin)); if (!conf_admin) conf_admin_tail = ca; safe_strdup(ca->line, cep->ce_varname); 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->ce_fileptr->cf_filename, ce->ce_varlinenum, "admin"); return 0; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (strlen(cep->ce_varname) > 500) { config_error("%s:%i: oversized data in admin block", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); 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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "name")) { safe_strdup(conf_me->name, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "info")) { safe_strdup(conf_me->info, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "sid")) { safe_strdup(conf_me->sid, cep->ce_vardata); } } 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->ce_fileptr->cf_filename, ce->ce_varlinenum, "me"); return 0; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "me")) continue; /* me::name */ if (!strcmp(cep->ce_varname, "name")) { if (has_name) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "me::name"); continue; } has_name = 1; if (!strchr(cep->ce_vardata, '.')) { config_error("%s:%i: illegal me::name, must be fully qualified hostname", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } if (!valid_host(cep->ce_vardata)) { config_error("%s:%i: illegal me::name contains invalid character(s) [only a-z, 0-9, _, -, . are allowed]", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } if (strlen(cep->ce_vardata) > HOSTLEN) { config_error("%s:%i: illegal me::name, must be less or equal to %i characters", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, HOSTLEN); errors++; } } /* me::info */ else if (!strcmp(cep->ce_varname, "info")) { char *p; char valid = 0; if (has_info) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "me::info"); continue; } has_info = 1; if (strlen(cep->ce_vardata) > (REALLEN-1)) { config_error("%s:%i: too long me::info, must be max. %i characters", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, REALLEN-1); errors++; } /* Valid me::info? Any data except spaces is ok */ for (p=cep->ce_vardata; *p; p++) { if (*p != ' ') { valid = 1; break; } } if (!valid) { config_error("%s:%i: empty me::info, should be a server description.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "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->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } else if (!strcmp(cep->ce_varname, "sid")) { if (has_sid) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "me::sid"); continue; } has_sid = 1; if (!valid_sid(cep->ce_vardata)) { 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->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } if (!isdigit(*cep->ce_vardata)) { config_error("%s:%i: me::sid must be 3 characters long and begin with a number", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } /* Unknown entry */ else { config_error_unknown(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "me", cep->ce_varname); errors++; } } if (!has_name) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "me::name"); errors++; } if (!has_info) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "me::info"); errors++; } if (!has_sid) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "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() calls us with a NULL ce, it's got a bug...but we can't detect that. */ if(!ce) return 1; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "motd")) safe_strdup(conf_files->motd_file, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "shortmotd")) safe_strdup(conf_files->smotd_file, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "opermotd")) safe_strdup(conf_files->opermotd_file, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "svsmotd")) safe_strdup(conf_files->svsmotd_file, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "botmotd")) safe_strdup(conf_files->botmotd_file, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "rules")) safe_strdup(conf_files->rules_file, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "tunefile")) safe_strdup(conf_files->tune_file, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "pidfile")) safe_strdup(conf_files->pid_file, cep->ce_vardata); } 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->ce_entries; cep; cep = cep->ce_next) { /* files::motd */ if (!strcmp(cep->ce_varname, "motd")) { if (has_motd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::motd"); continue; } convert_to_absolute_path(&cep->ce_vardata, CONFDIR); config_test_openfile(cep, O_RDONLY, 0, "files::motd", 0, 1); has_motd = 1; } /* files::smotd */ else if (!strcmp(cep->ce_varname, "shortmotd")) { if (has_smotd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::shortmotd"); continue; } convert_to_absolute_path(&cep->ce_vardata, CONFDIR); config_test_openfile(cep, O_RDONLY, 0, "files::shortmotd", 0, 1); has_smotd = 1; } /* files::rules */ else if (!strcmp(cep->ce_varname, "rules")) { if (has_rules) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::rules"); continue; } convert_to_absolute_path(&cep->ce_vardata, CONFDIR); config_test_openfile(cep, O_RDONLY, 0, "files::rules", 0, 1); has_rules = 1; } /* files::botmotd */ else if (!strcmp(cep->ce_varname, "botmotd")) { if (has_botmotd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::botmotd"); continue; } convert_to_absolute_path(&cep->ce_vardata, CONFDIR); config_test_openfile(cep, O_RDONLY, 0, "files::botmotd", 0, 1); has_botmotd = 1; } /* files::opermotd */ else if (!strcmp(cep->ce_varname, "opermotd")) { if (has_opermotd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::opermotd"); continue; } convert_to_absolute_path(&cep->ce_vardata, 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->ce_varname, "svsmotd")) { if (has_svsmotd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::svsmotd"); continue; } convert_to_absolute_path(&cep->ce_vardata, 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->ce_varname, "pidfile")) { if (has_pidfile) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::pidfile"); continue; } convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR); errors += config_test_openfile(cep, O_WRONLY | O_CREAT, 0600, "files::pidfile", 1, 0); has_pidfile = 1; } /* files::tunefile */ else if (!strcmp(cep->ce_varname, "tunefile")) { if (has_tunefile) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "files::tunefile"); continue; } convert_to_absolute_path(&cep->ce_vardata, PERMDATADIR); errors += config_test_openfile(cep, O_RDWR | O_CREAT, 0600, "files::tunefile", 1, 0); has_tunefile = 1; } /* */ else { config_error("%s:%d: Unknown directive: \"%s\" in files {}", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname); 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->ce_varname,"allow")) entry->type = OPERCLASSENTRY_ALLOW; else entry->type = OPERCLASSENTRY_DENY; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { OperClassACLEntryVar *var = safe_alloc(sizeof(OperClassACLEntryVar)); safe_strdup(var->name, cep->ce_varname); if (cep->ce_vardata) { safe_strdup(var->value, cep->ce_vardata); } AddListItem(var,entry->variables); } return entry; } OperClassACL* _conf_parseACL(char *name, ConfigEntry *ce) { ConfigEntry *cep; OperClassACL *acl = NULL; acl = safe_alloc(sizeof(OperClassACL)); safe_strdup(acl->name, name); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "deny") || !strcmp(cep->ce_varname, "allow")) { OperClassACLEntry *entry = _conf_parseACLEntry(cep); AddListItem(entry,acl->entries); } else { OperClassACL *subAcl = _conf_parseACL(cep->ce_varname,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->ce_vardata); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "parent")) { safe_strdup(operClass->classStruct->ISA, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "permissions")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { OperClassACL *acl = _conf_parseACL(cepp->ce_varname,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->ce_fileptr->cf_filename, ce->ce_varlinenum); 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->ce_vardata) { config_error_noname(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "operclass"); errors++; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "parent")) { if (has_parent) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "operclass::parent"); continue; } has_parent = 1; continue; } else if (!strcmp(cep->ce_varname, "permissions")) { if (has_permissions) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::permissions"); continue; } has_permissions = 1; continue; } else if (!strcmp(cep->ce_varname, "privileges")) { new_permissions_system(conf, cep); errors++; return errors; } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "operclass", cep->ce_varname); errors++; continue; } } if (!has_permissions) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "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->ce_vardata); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "operclass")) safe_strdup(oper->operclass, cep->ce_vardata); if (!strcmp(cep->ce_varname, "password")) oper->auth = AuthBlockToAuthConfig(cep); else if (!strcmp(cep->ce_varname, "class")) { oper->class = find_class(cep->ce_vardata); if (!oper->class || (oper->class->flag.temporary == 1)) { config_status("%s:%i: illegal oper::class, unknown class '%s' using default of class 'default'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); oper->class = default_class; } } else if (!strcmp(cep->ce_varname, "swhois")) { SWhois *s; if (cep->ce_entries) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { s = safe_alloc(sizeof(SWhois)); safe_strdup(s->line, cepp->ce_varname); safe_strdup(s->setby, "oper"); AddListItem(s, oper->swhois); } } else if (cep->ce_vardata) { s = safe_alloc(sizeof(SWhois)); safe_strdup(s->line, cep->ce_vardata); safe_strdup(s->setby, "oper"); AddListItem(s, oper->swhois); } } else if (!strcmp(cep->ce_varname, "snomask")) { safe_strdup(oper->snomask, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "modes")) { oper->modes = set_usermode(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "require-modes")) { oper->require_modes = set_usermode(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "maxlogins")) { oper->maxlogins = atoi(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "mask")) { unreal_add_masks(&oper->mask, cep); } else if (!strcmp(cep->ce_varname, "vhost")) { safe_strdup(oper->vhost, cep->ce_vardata); } } 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_maxlogins = 0; char has_operclass = 0, has_vhost = 0; ConfigEntry *cep; int errors = 0; if (!ce->ce_vardata) { config_error_noname(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "oper"); errors++; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { /* Regular variables */ if (!cep->ce_entries) { if (config_is_blankorempty(cep, "oper")) { errors++; continue; } /* oper::password */ if (!strcmp(cep->ce_varname, "password")) { if (has_password) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::password"); continue; } has_password = 1; if (Auth_CheckError(cep) < 0) errors++; if (ce->ce_vardata && cep->ce_vardata && !strcmp(ce->ce_vardata, "bobsmith") && !strcmp(cep->ce_vardata, "test")) { config_error("%s:%i: please change the the name and password of the " "default 'bobsmith' oper block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } continue; } /* oper::operclass */ else if (!strcmp(cep->ce_varname, "operclass")) { if (has_operclass) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::operclass"); continue; } has_operclass = 1; continue; } /* oper::class */ else if (!strcmp(cep->ce_varname, "class")) { if (has_class) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::class"); continue; } has_class = 1; } /* oper::swhois */ else if (!strcmp(cep->ce_varname, "swhois")) { } /* oper::vhost */ else if (!strcmp(cep->ce_varname, "vhost")) { if (has_vhost) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::vhost"); continue; } has_vhost = 1; } /* oper::snomask */ else if (!strcmp(cep->ce_varname, "snomask")) { if (has_snomask) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::snomask"); continue; } has_snomask = 1; } /* oper::modes */ else if (!strcmp(cep->ce_varname, "modes")) { char *p; for (p = cep->ce_vardata; *p; p++) if (strchr("orzS", *p)) { config_error("%s:%i: oper::modes may not include mode '%c'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p); errors++; } if (has_modes) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::modes"); continue; } has_modes = 1; } /* oper::require-modes */ else if (!strcmp(cep->ce_varname, "require-modes")) { char *p; for (p = cep->ce_vardata; *p; p++) if (strchr("o", *p)) { config_warn("%s:%i: oper::require-modes probably shouldn't include mode '%c'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p); } if (has_require_modes) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::require-modes"); continue; } has_require_modes = 1; } /* oper::maxlogins */ else if (!strcmp(cep->ce_varname, "maxlogins")) { int l; if (has_maxlogins) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::maxlogins"); continue; } has_maxlogins = 1; l = atoi(cep->ce_vardata); if ((l < 0) || (l > 5000)) { config_error("%s:%i: oper::maxlogins: value out of range (%d) should be 0-5000", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, l); errors++; continue; } } /* oper::flags */ else if (!strcmp(cep->ce_varname, "flags")) { config_error("%s:%i: oper::flags no longer exists. UnrealIRCd 4 uses a new style oper block.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; need_34_upgrade = 1; } else if (!strcmp(cep->ce_varname, "mask")) { if (cep->ce_vardata || cep->ce_entries) has_mask = 1; } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper", cep->ce_varname); errors++; continue; } } /* Sections */ else { /* oper::flags {} */ if (!strcmp(cep->ce_varname, "flags")) { config_error("%s:%i: oper::flags no longer exists. UnrealIRCd 4 uses a new style oper block.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; need_34_upgrade = 1; continue; } /* oper::from {} */ else if (!strcmp(cep->ce_varname, "from")) { config_error("%s:%i: oper::from::userhost is now called oper::mask", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; need_34_upgrade = 1; continue; } else if (!strcmp(cep->ce_varname, "swhois")) { /* ok */ } else if (!strcmp(cep->ce_varname, "mask")) { if (cep->ce_vardata || cep->ce_entries) has_mask = 1; } else if (!strcmp(cep->ce_varname, "password")) { if (has_password) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper::password"); continue; } has_password = 1; if (Auth_CheckError(cep) < 0) errors++; } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "oper", cep->ce_varname); errors++; continue; } } } if (!has_password) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "oper::password"); errors++; } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "oper::mask"); errors++; } if (!has_class) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "oper::class"); errors++; } if (!has_operclass) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "oper::operclass"); need_34_upgrade = 1; errors++; } return errors; } /* * The class {} block parser */ int _conf_class(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep, *cep2; ConfigItem_class *class; unsigned char isnew = 0; if (!(class = find_class(ce->ce_vardata))) { class = safe_alloc(sizeof(ConfigItem_class)); safe_strdup(class->name, ce->ce_vardata); isnew = 1; } else { isnew = 0; class->flag.temporary = 0; class->options = 0; /* RESET OPTIONS */ } safe_strdup(class->name, ce->ce_vardata); class->connfreq = 15; /* default */ for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "pingfreq")) class->pingfreq = config_checkval(cep->ce_vardata,CFG_TIME); else if (!strcmp(cep->ce_varname, "connfreq")) class->connfreq = config_checkval(cep->ce_vardata,CFG_TIME); else if (!strcmp(cep->ce_varname, "maxclients")) class->maxclients = atol(cep->ce_vardata); else if (!strcmp(cep->ce_varname, "sendq")) class->sendq = config_checkval(cep->ce_vardata,CFG_SIZE); else if (!strcmp(cep->ce_varname, "recvq")) class->recvq = config_checkval(cep->ce_vardata,CFG_SIZE); else if (!strcmp(cep->ce_varname, "options")) { for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next) if (!strcmp(cep2->ce_varname, "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->ce_vardata) { config_error_noname(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "class"); return 1; } if (!strcasecmp(ce->ce_vardata, "default")) { config_error("%s:%d: Class cannot be named 'default', this class name is reserved for internal use.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "options")) { for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next) { #ifdef FAKELAG_CONFIGURABLE if (!strcmp(cep2->ce_varname, "nofakelag")) ; else #endif { config_error("%s:%d: Unknown option '%s' in class::options", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep2->ce_varname); errors++; } } } else if (config_is_blankorempty(cep, "class")) { errors++; continue; } /* class::pingfreq */ else if (!strcmp(cep->ce_varname, "pingfreq")) { int v = config_checkval(cep->ce_vardata,CFG_TIME); if (has_pingfreq) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "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->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } } /* class::maxclients */ else if (!strcmp(cep->ce_varname, "maxclients")) { long l; if (has_maxclients) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "class::maxclients"); continue; } has_maxclients = 1; l = atol(cep->ce_vardata); if ((l < 1) || (l > 1000000)) { config_error("%s:%i: class::maxclients with illegal value", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } /* class::connfreq */ else if (!strcmp(cep->ce_varname, "connfreq")) { long l; if (has_connfreq) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "class::connfreq"); continue; } has_connfreq = 1; l = config_checkval(cep->ce_vardata,CFG_TIME); if ((l < 5) || (l > 604800)) { config_error("%s:%i: class::connfreq with illegal value (must be >5 and <7d)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } /* class::sendq */ else if (!strcmp(cep->ce_varname, "sendq")) { long l; if (has_sendq) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "class::sendq"); continue; } has_sendq = 1; l = config_checkval(cep->ce_vardata,CFG_SIZE); if ((l <= 0) || (l > 2000000000)) { config_error("%s:%i: class::sendq with illegal value", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } /* class::recvq */ else if (!strcmp(cep->ce_varname, "recvq")) { long l; if (has_recvq) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "class::recvq"); continue; } has_recvq = 1; l = config_checkval(cep->ce_vardata,CFG_SIZE); if ((l < 512) || (l > 32768)) { config_error("%s:%i: class::recvq with illegal value (must be >512 and <32k)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } /* Unknown */ else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "class", cep->ce_varname); errors++; continue; } } if (!has_pingfreq) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "class::pingfreq"); errors++; } if (!has_maxclients) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "class::maxclients"); errors++; } if (!has_sendq) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "restart")) { if (conf_drpass->restartauth) Auth_FreeAuthConfig(conf_drpass->restartauth); conf_drpass->restartauth = AuthBlockToAuthConfig(cep); } else if (!strcmp(cep->ce_varname, "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->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "drpass")) { errors++; continue; } /* drpass::restart */ if (!strcmp(cep->ce_varname, "restart")) { if (has_restart) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "drpass::restart"); continue; } has_restart = 1; if (Auth_CheckError(cep) < 0) errors++; continue; } /* drpass::die */ else if (!strcmp(cep->ce_varname, "die")) { if (has_die) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "drpass::die"); continue; } has_die = 1; if (Auth_CheckError(cep) < 0) errors++; continue; } /* Unknown */ else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "drpass", cep->ce_varname); errors++; continue; } } return errors; } /* * The ulines {} block parser */ int _conf_ulines(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep; ConfigItem_ulines *ca; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { ca = safe_alloc(sizeof(ConfigItem_ulines)); safe_strdup(ca->servername, cep->ce_varname); 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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "mask")) safe_strdup(ca->mask, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "motd")) { safe_strdup(ca->motd_file, cep->ce_vardata); read_motd(cep->ce_vardata, &ca->motd); } else if (!strcmp(cep->ce_varname, "shortmotd")) { safe_strdup(ca->smotd_file, cep->ce_vardata); read_motd(cep->ce_vardata, &ca->smotd); } else if (!strcmp(cep->ce_varname, "opermotd")) { safe_strdup(ca->opermotd_file, cep->ce_vardata); read_motd(cep->ce_vardata, &ca->opermotd); } else if (!strcmp(cep->ce_varname, "botmotd")) { safe_strdup(ca->botmotd_file, cep->ce_vardata); read_motd(cep->ce_vardata, &ca->botmotd); } else if (!strcmp(cep->ce_varname, "rules")) { safe_strdup(ca->rules_file, cep->ce_vardata); read_motd(cep->ce_vardata, &ca->rules); } else if (!strcmp(cep->ce_varname, "options")) { ConfigEntry *cepp; for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls")) ca->options |= TLD_TLS; else if (!strcmp(cepp->ce_varname, "remote")) ca->options |= TLD_REMOTE; } } else if (!strcmp(cep->ce_varname, "channel")) safe_strdup(ca->channel, cep->ce_vardata); } 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_motd = 0, has_rules = 0, has_shortmotd = 0, has_channel = 0; char has_opermotd = 0, has_botmotd = 0, has_options = 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!cep->ce_vardata && strcmp(cep->ce_varname, "options")) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld", cep->ce_varname); errors++; continue; } /* tld::mask */ if (!strcmp(cep->ce_varname, "mask")) { if (has_mask) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::mask"); continue; } has_mask = 1; } /* tld::motd */ else if (!strcmp(cep->ce_varname, "motd")) { if (has_motd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::motd"); continue; } has_motd = 1; convert_to_absolute_path(&cep->ce_vardata, CONFDIR); if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1)) { config_error("%s:%i: tld::motd: %s: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata, strerror(errno)); errors++; } else close(fd); } /* tld::rules */ else if (!strcmp(cep->ce_varname, "rules")) { if (has_rules) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::rules"); continue; } has_rules = 1; convert_to_absolute_path(&cep->ce_vardata, CONFDIR); if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1)) { config_error("%s:%i: tld::rules: %s: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata, strerror(errno)); errors++; } else close(fd); } /* tld::channel */ else if (!strcmp(cep->ce_varname, "channel")) { if (has_channel) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::channel"); continue; } has_channel = 1; } /* tld::shortmotd */ else if (!strcmp(cep->ce_varname, "shortmotd")) { if (has_shortmotd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::shortmotd"); continue; } has_shortmotd = 1; convert_to_absolute_path(&cep->ce_vardata, CONFDIR); if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1)) { config_error("%s:%i: tld::shortmotd: %s: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata, strerror(errno)); errors++; } else close(fd); } /* tld::opermotd */ else if (!strcmp(cep->ce_varname, "opermotd")) { if (has_opermotd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::opermotd"); continue; } has_opermotd = 1; convert_to_absolute_path(&cep->ce_vardata, CONFDIR); if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1)) { config_error("%s:%i: tld::opermotd: %s: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata, strerror(errno)); errors++; } else close(fd); } /* tld::botmotd */ else if (!strcmp(cep->ce_varname, "botmotd")) { if (has_botmotd) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::botmotd"); continue; } has_botmotd = 1; convert_to_absolute_path(&cep->ce_vardata, CONFDIR); if (((fd = open(cep->ce_vardata, O_RDONLY)) == -1)) { config_error("%s:%i: tld::botmotd: %s: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata, strerror(errno)); errors++; } else close(fd); } /* tld::options */ else if (!strcmp(cep->ce_varname, "options")) { ConfigEntry *cep2; if (has_options) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld::options"); continue; } has_options = 1; for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next) { if (strcmp(cep2->ce_varname, "ssl") && strcmp(cep2->ce_varname, "tls") && strcmp(cep2->ce_varname, "remote")) { config_error_unknownopt(cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, "tld", cep2->ce_varname); errors++; } } } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "tld", cep->ce_varname); errors++; continue; } } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "tld::mask"); errors++; } if (!has_motd) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "tld::motd"); errors++; } if (!has_rules) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "tld::rules"); errors++; } return errors; } int _conf_listen(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep; ConfigEntry *cepp; ConfigEntry *tlsconfig = NULL; ConfigItem_listen *listen = NULL; char *ip = NULL; int start=0, end=0, port, isnew; int tmpflags =0; Hook *h; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "ip")) { ip = cep->ce_vardata; } else if (!strcmp(cep->ce_varname, "port")) { port_range(cep->ce_vardata, &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->ce_varname, "options")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { NameValue *ofp; if ((ofp = config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags)))) { tmpflags |= ofp->flag; } 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->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "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; } } } for (port = start; port <= end; port++) { /* First deal with IPv4 */ if (!strchr(ip, ':')) { if (!(listen = find_listen(ip, port, 0))) { listen = safe_alloc(sizeof(ConfigItem_listen)); safe_strdup(listen->ip, ip); listen->port = port; listen->fd = -1; listen->ipv6 = 0; isnew = 1; } else isnew = 0; if (listen->options & LISTENER_BOUND) tmpflags |= LISTENER_BOUND; listen->options = tmpflags; if (isnew) AddListItem(listen, conf_listen); listen->flag.temporary = 0; 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; } 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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "ip")) ; else if (!strcmp(cep->ce_varname, "port")) ; else if (!strcmp(cep->ce_varname, "options")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { NameValue *ofp; if (!config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags))) { 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->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "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; } } } } /* Then deal with IPv6 (if available/enabled) */ if (!DISABLE_IPV6) { if (strchr(ip, ':') || (*ip == '*')) { if (!(listen = find_listen(ip, port, 1))) { listen = safe_alloc(sizeof(ConfigItem_listen)); safe_strdup(listen->ip, ip); listen->port = port; listen->fd = -1; listen->ipv6 = 1; isnew = 1; } else isnew = 0; if (listen->options & LISTENER_BOUND) tmpflags |= LISTENER_BOUND; listen->options = tmpflags; if (isnew) AddListItem(listen, conf_listen); listen->flag.temporary = 0; 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; } 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.. */ for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "ip")) ; else if (!strcmp(cep->ce_varname, "port")) ; else if (!strcmp(cep->ce_varname, "options")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { NameValue *ofp; if (!config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags))) { 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->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "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; } } } } } } return 1; } int _test_listen(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep; ConfigEntry *cepp; int errors = 0; char has_ip = 0, has_port = 0, has_options = 0, port_6667 = 0; char *ip = NULL; Hook *h; if (ce->ce_vardata) { config_error("%s:%i: listen block has a new syntax, see https://www.unrealircd.org/docs/Listen_block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); need_34_upgrade = 1; return 1; } for (cep = ce->ce_entries; cep; cep = cep->ce_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->ce_varname, "options")) { if (has_options) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "listen::options"); continue; } has_options = 1; for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { NameValue *ofp; if (!(ofp = config_binary_flags_search(_ListenerFlags, cepp->ce_varname, ARRAY_SIZEOF(_ListenerFlags)))) { /* 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->ce_fileptr->cf_filename, cepp->ce_varlinenum, "listen::options", cepp->ce_varname); errors++; continue; } } if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls")) have_tls_listeners = 1; /* for ssl config test */ } } else if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options")) { test_tlsblock(conf, cep, &errors); } else if (!cep->ce_vardata) { if (!used_by_module) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "listen", cep->ce_varname); errors++; } continue; /* always */ } else if (!strcmp(cep->ce_varname, "ip")) { has_ip = 1; if (strcmp(cep->ce_vardata, "*") && !is_valid_ip(cep->ce_vardata)) { config_error("%s:%i: listen: illegal listen::ip (%s). Must be either '*' or contain a valid IP.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); return 1; } ip = cep->ce_vardata; } else if (!strcmp(cep->ce_varname, "host")) { config_error("%s:%i: listen: unknown option listen::host, did you mean listen::ip?", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } else if (!strcmp(cep->ce_varname, "port")) { int start = 0, end = 0; has_port = 1; port_range(cep->ce_vardata, &start, &end); if (start == end) { if ((start < 1) || (start > 65535)) { config_error("%s:%i: listen: illegal port (must be 1..65535)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); return 1; } } else { if (end < start) { config_error("%s:%i: listen: illegal port range end value is less than starting value", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); 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->ce_fileptr->cf_filename, cep->ce_varlinenum, 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->ce_fileptr->cf_filename, cep->ce_varlinenum); return 1; } } if ((6667 >= start) && (6667 <= end)) port_6667 = 1; } else { if (!used_by_module) { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "listen", cep->ce_varname); errors++; } continue; /* always */ } } if (!has_ip) { config_error("%s:%d: listen block requires an listen::ip", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } if (!has_port) { config_error("%s:%d: listen block requires an listen::port", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); 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->ce_vardata) { if (!strcmp(ce->ce_vardata, "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)); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "ip")) { safe_strdup(allow->ip, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "hostname")) safe_strdup(allow->hostname, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "password")) allow->auth = AuthBlockToAuthConfig(cep); else if (!strcmp(cep->ce_varname, "class")) { allow->class = find_class(cep->ce_vardata); if (!allow->class || (allow->class->flag.temporary == 1)) { config_status("%s:%i: illegal allow::class, unknown class '%s' using default of class 'default'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); allow->class = default_class; } } else if (!strcmp(cep->ce_varname, "maxperip")) allow->maxperip = atoi(cep->ce_vardata); else if (!strcmp(cep->ce_varname, "redirect-server")) safe_strdup(allow->server, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "redirect-port")) allow->port = atoi(cep->ce_vardata); else if (!strcmp(cep->ce_varname, "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(). */ allow->ipv6_clone_mask = atoi(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "options")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "noident")) allow->flags.noident = 1; else if (!strcmp(cepp->ce_varname, "useip")) allow->flags.useip = 1; else if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls")) allow->flags.tls = 1; } } } if (!allow->hostname) safe_strdup(allow->hostname, "*@NOMATCH"); if (!allow->ip) safe_strdup(allow->ip, "*@NOMATCH"); 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_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->ce_vardata) { if (!strcmp(ce->ce_vardata, "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->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } return errors; } } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (strcmp(cep->ce_varname, "options") && config_is_blankorempty(cep, "allow")) { errors++; continue; } if (!strcmp(cep->ce_varname, "ip")) { if (has_ip) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::ip"); continue; } has_ip = 1; } else if (!strcmp(cep->ce_varname, "maxperip")) { int v = atoi(cep->ce_vardata); if (has_maxperip) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::maxperip"); continue; } has_maxperip = 1; if ((v <= 0) || (v > 65535)) { config_error("%s:%i: allow::maxperip with illegal value (must be 1-65535)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "ipv6-clone-mask")) { /* keep this in sync with _test_set() */ int ipv6mask; ipv6mask = atoi(cep->ce_vardata); 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->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } if (ipv6mask > 128) { config_error("%s:%d: set::default-ipv6-clone-mask was set to %d. The maximum value is 128.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, ipv6mask); errors++; } if (ipv6mask <= 32) { config_warn("%s:%d: allow::ipv6-clone-mask was given a very small value.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); } } else if (!strcmp(cep->ce_varname, "hostname")) { if (has_hostname) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::hostname"); continue; } has_hostname = 1; if (!strcmp(cep->ce_vardata, "*@*") || !strcmp(cep->ce_vardata, "*")) hostname_possible_silliness = 1; } else if (!strcmp(cep->ce_varname, "password")) { if (has_password) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::password"); continue; } has_password = 1; /* some auth check stuff? */ if (Auth_CheckError(cep) < 0) errors++; } else if (!strcmp(cep->ce_varname, "class")) { if (has_class) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::class"); continue; } has_class = 1; } else if (!strcmp(cep->ce_varname, "redirect-server")) { if (has_redirectserver) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::redirect-server"); continue; } has_redirectserver = 1; } else if (!strcmp(cep->ce_varname, "redirect-port")) { if (has_redirectport) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::redirect-port"); continue; } has_redirectport = 1; } else if (!strcmp(cep->ce_varname, "options")) { if (has_options) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow::options"); continue; } has_options = 1; for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "noident")) {} else if (!strcmp(cepp->ce_varname, "useip")) {} else if (!strcmp(cepp->ce_varname, "ssl") || !strcmp(cepp->ce_varname, "tls")) {} else if (!strcmp(cepp->ce_varname, "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->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } else { config_error_unknownopt(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "allow", cepp->ce_varname); errors++; } } } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow", cep->ce_varname); errors++; continue; } } if (!has_ip && !has_hostname) { config_error("%s:%d: allow block needs an allow::ip or allow::hostname", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } if (has_ip && has_hostname) { config_warn("%s:%d: allow block has both allow::ip and allow::hostname which is no longer permitted.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); need_34_upgrade = 1; } else if (hostname_possible_silliness) { config_warn("%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 'ip *;' instead.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); } if (!has_class) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "allow::class"); errors++; } if (!has_maxperip) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "allow::maxperip"); errors++; } return errors; } int _conf_allow_channel(ConfigFile *conf, ConfigEntry *ce) { ConfigItem_allow_channel *allow = NULL; ConfigEntry *cep; char *class = NULL; ConfigEntry *mask = NULL; /* First, search for ::class, if any */ for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "class")) class = cep->ce_vardata; else if (!strcmp(cep->ce_varname, "mask")) mask = cep; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "channel")) { /* This way, we permit multiple ::channel items in one allow block */ allow = safe_alloc(sizeof(ConfigItem_allow_channel)); safe_strdup(allow->channel, cep->ce_vardata); if (class) safe_strdup(allow->class, class); if (mask) unreal_add_masks(&allow->mask, mask); AddListItem(allow, conf_allow_channel); } } return 1; } int _test_allow_channel(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep; int errors = 0; char has_channel = 0, has_class = 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "allow channel")) { errors++; continue; } if (!strcmp(cep->ce_varname, "channel")) { has_channel = 1; } else if (!strcmp(cep->ce_varname, "class")) { if (has_class) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow channel::class"); continue; } has_class = 1; } else if (!strcmp(cep->ce_varname, "mask")) { } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "allow channel", cep->ce_varname); errors++; } } if (!has_channel) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "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->ce_vardata) { config_error("%s:%i: except without type", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } if (!strcmp(ce->ce_vardata, "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->ce_fileptr->cf_filename, ce->ce_varlinenum); safe_strdup(ce->ce_vardata, "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->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); 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)); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "vhost")) { char *user, *host; user = strtok(cep->ce_vardata, "@"); 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->ce_varname, "login")) safe_strdup(vhost->login, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "password")) vhost->auth = AuthBlockToAuthConfig(cep); else if (!strcmp(cep->ce_varname, "mask")) { unreal_add_masks(&vhost->mask, cep); } else if (!strcmp(cep->ce_varname, "swhois")) { SWhois *s; if (cep->ce_entries) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { s = safe_alloc(sizeof(SWhois)); safe_strdup(s->line, cepp->ce_varname); safe_strdup(s->setby, "vhost"); AddListItem(s, vhost->swhois); } } else if (cep->ce_vardata) { s = safe_alloc(sizeof(SWhois)); safe_strdup(s->line, cep->ce_vardata); 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; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "vhost")) { char *at, *tmp, *host; if (has_vhost) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "vhost::vhost"); continue; } has_vhost = 1; if (!cep->ce_vardata) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "vhost", "vhost"); errors++; continue; } if ((at = strchr(cep->ce_vardata, '@'))) { for (tmp = cep->ce_vardata; tmp != at; tmp++) { if (*tmp == '~' && tmp == cep->ce_vardata) continue; if (!isallowed(*tmp)) break; } if (tmp != at) { config_error("%s:%i: vhost::vhost contains an invalid ident", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } host = at+1; } else host = cep->ce_vardata; if (!*host) { config_error("%s:%i: vhost::vhost does not have a host set", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } else { if (!valid_host(host)) { config_error("%s:%i: vhost::vhost contains an invalid host", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } } else if (!strcmp(cep->ce_varname, "login")) { if (has_login) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "vhost::login"); } has_login = 1; if (!cep->ce_vardata) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "vhost", "login"); errors++; continue; } } else if (!strcmp(cep->ce_varname, "password")) { if (has_password) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "vhost::password"); } has_password = 1; if (!cep->ce_vardata) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "vhost", "password"); errors++; continue; } if (Auth_CheckError(cep) < 0) errors++; } else if (!strcmp(cep->ce_varname, "from")) { config_error("%s:%i: vhost::from::userhost is now called oper::mask", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; need_34_upgrade = 1; continue; } else if (!strcmp(cep->ce_varname, "mask")) { has_mask = 1; } else if (!strcmp(cep->ce_varname, "swhois")) { /* multiple is ok */ } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "vhost", cep->ce_varname); errors++; } } if (!has_vhost) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "vhost::vhost"); errors++; } if (!has_login) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "vhost::login"); errors++; } if (!has_password) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "vhost::password"); errors++; } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "vhost::mask"); errors++; } // TODO: 3.2.x -> 4.x upgrading hints return errors; } int _test_sni(ConfigFile *conf, ConfigEntry *ce) { int errors = 0; ConfigEntry *cep; if (!ce->ce_vardata) { config_error("%s:%i: sni block needs a name, eg: sni irc.xyz.com {", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "tls-options")) { test_tlsblock(conf, cep, &errors); } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "sni", cep->ce_varname); errors++; continue; } } return errors; } int _conf_sni(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep; ConfigEntry *tlsconfig = NULL; char *name; ConfigItem_sni *sni = NULL; name = ce->ce_vardata; if (!name) return 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "ssl-options") || !strcmp(cep->ce_varname, "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->ce_vardata) ca->command = NULL; else safe_strdup(ca->command, ce->ce_vardata); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { temp = safe_alloc(sizeof(MOTDLine)); safe_strdup(temp->line, cep->ce_varname); 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->ce_entries) { config_error("%s:%i: empty help block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (strlen(cep->ce_varname) > 500) { config_error("%s:%i: oversized help item", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; continue; } } return errors; } int _conf_log(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep, *cepp; ConfigItem_log *ca; NameValue *ofp = NULL; ca = safe_alloc(sizeof(ConfigItem_log)); ca->logfd = -1; safe_strdup(ca->file, ce->ce_vardata); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "maxsize")) { ca->maxsize = config_checkval(cep->ce_vardata,CFG_SIZE); } else if (!strcmp(cep->ce_varname, "flags")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if ((ofp = config_binary_flags_search(_LogFlags, cepp->ce_varname, ARRAY_SIZEOF(_LogFlags)))) ca->flags |= ofp->flag; } } } AddListItem(ca, conf_log); return 1; } int _test_log(ConfigFile *conf, ConfigEntry *ce) { int fd, errors = 0; ConfigEntry *cep, *cepp; char has_flags = 0, has_maxsize = 0; if (!ce->ce_vardata) { config_error("%s:%i: log block without filename", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } if (!ce->ce_entries) { config_error("%s:%i: empty log block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } /* Convert to absolute path (if needed) unless it's "syslog" */ if (strcmp(ce->ce_vardata, "syslog")) convert_to_absolute_path(&ce->ce_vardata, LOGDIR); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "flags")) { if (has_flags) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "log::flags"); continue; } has_flags = 1; if (!cep->ce_entries) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "log", cep->ce_varname); errors++; continue; } for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!config_binary_flags_search(_LogFlags, cepp->ce_varname, ARRAY_SIZEOF(_LogFlags))) { config_error_unknownflag(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "log", cepp->ce_varname); errors++; } } } else if (!strcmp(cep->ce_varname, "maxsize")) { if (has_maxsize) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "log::maxsize"); continue; } has_maxsize = 1; if (!cep->ce_vardata) { config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "log", cep->ce_varname); errors++; } } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "log", cep->ce_varname); errors++; continue; } } if (!has_flags) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "log::flags"); errors++; } if ((fd = fd_fileopen(ce->ce_vardata, O_WRONLY|O_CREAT)) == -1) { config_error("%s:%i: Couldn't open logfile (%s) for writing: %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata, strerror(errno)); errors++; } else fd_close(fd); return errors; } int _conf_link(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep, *cepp, *ceppp; ConfigItem_link *link = NULL; NameValue *ofp; link = safe_alloc(sizeof(ConfigItem_link)); safe_strdup(link->servername, ce->ce_vardata); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "incoming")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "mask")) { unreal_add_masks(&link->incoming.mask, cepp); } } } else if (!strcmp(cep->ce_varname, "outgoing")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "bind-ip")) safe_strdup(link->outgoing.bind_ip, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "hostname")) safe_strdup(link->outgoing.hostname, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "port")) link->outgoing.port = atoi(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "options")) { /* TODO: options still need to be split */ link->outgoing.options = 0; for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next) { if ((ofp = config_binary_flags_search(_LinkFlags, ceppp->ce_varname, ARRAY_SIZEOF(_LinkFlags)))) link->outgoing.options |= ofp->flag; } } else if (!strcmp(cepp->ce_varname, "ssl-options") || !strcmp(cepp->ce_varname, "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->ce_varname, "password")) link->auth = AuthBlockToAuthConfig(cep); else if (!strcmp(cep->ce_varname, "hub")) safe_strdup(link->hub, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "leaf")) safe_strdup(link->leaf, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "leaf-depth") || !strcmp(cep->ce_varname, "leafdepth")) link->leaf_depth = atoi(cep->ce_vardata); else if (!strcmp(cep->ce_varname, "class")) { link->class = find_class(cep->ce_vardata); if (!link->class || (link->class->flag.temporary == 1)) { config_status("%s:%i: illegal link::class, unknown class '%s' using default of class 'default'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); link->class = default_class; } link->class->xrefcount++; } else if (!strcmp(cep->ce_varname, "verify-certificate")) { link->verify_certificate = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "options")) { link->options = 0; for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if ((ofp = config_binary_flags_search(_LinkFlags, cepp->ce_varname, ARRAY_SIZEOF(_LinkFlags)))) link->options |= ofp->flag; } } } /* The default is 'hub *', unless you specify leaf or hub manually. */ if (!link->hub && !link->leaf) safe_strdup(link->hub, "*"); AddListItem(link, conf_link); return 0; } /** Helper function for erroring on duplicate items. * TODO: make even more friendy for dev's? */ int config_detect_duplicate(int *var, ConfigEntry *ce, int *errors) { if (*var) { config_error("%s:%d: Duplicate %s directive", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_varname); (*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_outgoing = 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->ce_vardata) { config_error("%s:%i: link without servername. Expected: link servername { ... }", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } if (!strchr(ce->ce_vardata, '.')) { config_error("%s:%i: link: bogus server name. Expected: link servername { ... }", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "incoming")) { config_detect_duplicate(&has_incoming, cep, &errors); for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "mask")) { if (cepp->ce_vardata || cepp->ce_entries) has_incoming_mask = 1; else if (config_is_blankorempty(cepp, "link::incoming")) { errors++; continue; } } } } else if (!strcmp(cep->ce_varname, "outgoing")) { config_detect_duplicate(&has_outgoing, cep, &errors); for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "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->ce_varname, "hostname")) { if (config_is_blankorempty(cepp, "link::outgoing")) { errors++; continue; } config_detect_duplicate(&has_outgoing_hostname, cepp, &errors); if (strchr(cepp->ce_vardata, '*') || strchr(cepp->ce_vardata, '?')) { config_error("%s:%i: hostname in link::outgoing(!) cannot contain wildcards", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "port")) { if (config_is_blankorempty(cepp, "link::outgoing")) { errors++; continue; } config_detect_duplicate(&has_outgoing_port, cepp, &errors); } else if (!strcmp(cepp->ce_varname, "options")) { config_detect_duplicate(&has_outgoing_options, cepp, &errors); for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next) { if (!strcmp(ceppp->ce_varname, "autoconnect")) ; else if (!strcmp(ceppp->ce_varname, "ssl") || !strcmp(ceppp->ce_varname, "tls")) ; else if (!strcmp(ceppp->ce_varname, "insecure")) ; else { config_error_unknownopt(ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, "link::outgoing", ceppp->ce_varname); errors++; } // TODO: validate more options (?) and use list rather than code here... } } else if (!strcmp(cepp->ce_varname, "ssl-options") || !strcmp(cepp->ce_varname, "tls-options")) { test_tlsblock(conf, cepp, &errors); } else { config_error("%s:%d: Unknown directive '%s'", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp)); errors++; } } } else if (!strcmp(cep->ce_varname, "password")) { config_detect_duplicate(&has_password, cep, &errors); if (Auth_CheckError(cep) < 0) { errors++; } else { AuthConfig *auth = AuthBlockToAuthConfig(cep); /* hm. would be nicer if handled @auth-system I think. ah well.. */ if ((auth->type != AUTHTYPE_PLAINTEXT) && (auth->type != AUTHTYPE_TLS_CLIENTCERT) && (auth->type != AUTHTYPE_TLS_CLIENTCERTFP) && (auth->type != AUTHTYPE_SPKIFP)) { config_error("%s:%i: password in link block should be plaintext OR should be the " "SSL or SPKI fingerprint of the remote link (=better)", /* TODO: mention some faq or wiki item for more information */ cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } Auth_FreeAuthConfig(auth); } } else if (!strcmp(cep->ce_varname, "hub")) { if (config_is_blankorempty(cep, "link")) { errors++; continue; } config_detect_duplicate(&has_hub, cep, &errors); } else if (!strcmp(cep->ce_varname, "leaf")) { if (config_is_blankorempty(cep, "link")) { errors++; continue; } config_detect_duplicate(&has_leaf, cep, &errors); } else if (!strcmp(cep->ce_varname, "leaf-depth") || !strcmp(cep->ce_varname, "leafdepth")) { if (config_is_blankorempty(cep, "link")) { errors++; continue; } config_detect_duplicate(&has_leaf_depth, cep, &errors); } else if (!strcmp(cep->ce_varname, "class")) { if (config_is_blankorempty(cep, "link")) { errors++; continue; } config_detect_duplicate(&has_class, cep, &errors); } else if (!strcmp(cep->ce_varname, "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->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } else if (!strcmp(cep->ce_varname, "verify-certificate")) { if (config_is_blankorempty(cep, "link")) { errors++; continue; } } else if (!strcmp(cep->ce_varname, "options")) { config_detect_duplicate(&has_options, cep, &errors); for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "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->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname); errors++; } } } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "link", cep->ce_varname); errors++; continue; } } if (!has_incoming && !has_outgoing) { config_error("%s:%d: link block needs at least an incoming or outgoing section.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; need_34_upgrade = 1; } if (has_incoming) { /* If we have an incoming sub-block then we need at least 'mask' and 'password' */ if (!has_incoming_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::incoming::mask"); errors++; } } if (has_outgoing) { /* If we have an outgoing sub-block then we need at least a hostname and port */ if (!has_outgoing_hostname) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::outgoing::hostname"); errors++; } if (!has_outgoing_port) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::outgoing::port"); errors++; } } /* The only other generic options that are required are 'class' and 'password' */ if (!has_password) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "link::password"); errors++; } if (!has_class) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "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->ce_vardata, "realname")) ca->flag.type = CONF_BAN_REALNAME; else if (!strcmp(ce->ce_vardata, "server")) ca->flag.type = CONF_BAN_SERVER; else if (!strcmp(ce->ce_vardata, "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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "mask")) { safe_strdup(ca->mask, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "reason")) safe_strdup(ca->reason, cep->ce_vardata); else if (!strcmp(cep->ce_varname, "action")) ca ->action = banact_stringtoval(cep->ce_vardata); } 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->ce_vardata) { config_error("%s:%i: ban without type", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } else if (!strcmp(ce->ce_vardata, "server")) {} else if (!strcmp(ce->ce_vardata, "realname")) {} else if (!strcmp(ce->ce_vardata, "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->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); return 1; } return errors; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "ban")) { errors++; continue; } if (!strcmp(cep->ce_varname, "mask")) { if (has_mask) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "ban::mask"); continue; } has_mask = 1; } else if (!strcmp(cep->ce_varname, "reason")) { if (has_reason) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "ban::reason"); continue; } has_reason = 1; } else if (!strcmp(cep->ce_varname, "action")) { if (has_action) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "ban::action"); } has_action = 1; if (!banact_stringtoval(cep->ce_vardata)) { config_error("%s:%i: ban::action has unknown action type '%s'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); errors++; } } } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "ban::mask"); errors++; } if (!has_reason) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "ban::reason"); errors++; } if (has_action && type != 'v') { config_error("%s:%d: ban::action specified even though type is not 'version'", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); 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->ce_vardata, "authentication") && strcmp(ce->ce_vardata, "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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "mask")) { char buf[512], *p; strlcpy(buf, cep->ce_vardata, sizeof(buf)); p = strchr(buf, '@'); if (p) { *p++ = '\0'; safe_strdup(usermask, buf); safe_strdup(hostmask, p); } else { safe_strdup(hostmask, cep->ce_vardata); } } else if (!strcmp(cep->ce_varname, "reason")) safe_strdup(reason, cep->ce_vardata); } 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->ce_vardata) { config_error("%s:%i: require without type, did you mean 'require authentication'?", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } if (!strcmp(ce->ce_vardata, "authentication")) {} else if (!strcmp(ce->ce_vardata, "sasl")) { config_warn("%s:%i: the 'require sasl' block is now called 'require authentication'", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); } 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->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); return 1; } return errors; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "require")) { errors++; continue; } if (!strcmp(cep->ce_varname, "mask")) { if (has_mask) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "require::mask"); continue; } has_mask = 1; } else if (!strcmp(cep->ce_varname, "reason")) { if (has_reason) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "require::reason"); continue; } has_reason = 1; } } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "require::mask"); errors++; } if (!has_reason) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "require::reason"); errors++; } return errors; } #define CheckNull(x) if ((!(x)->ce_vardata) || (!(*((x)->ce_vardata)))) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; } #define CheckNullAllowEmpty(x) if ((!(x)->ce_vardata)) { config_error("%s:%i: missing parameter", (x)->ce_fileptr->cf_filename, (x)->ce_varlinenum); errors++; continue; } #define CheckDuplicate(cep, name, display) if (settings.has_##name) { config_warn_duplicate((cep)->ce_fileptr->cf_filename, cep->ce_varlinenum, "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->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "renegotiate-timeout")) { } else if (!strcmp(cepp->ce_varname, "renegotiate-bytes")) { } else if (!strcmp(cepp->ce_varname, "ciphers") || !strcmp(cepp->ce_varname, "server-cipher-list")) { CheckNull(cepp); } else if (!strcmp(cepp->ce_varname, "ciphersuites")) { CheckNull(cepp); } else if (!strcmp(cepp->ce_varname, "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->ce_varname, "protocols")) { char copy[512], *p, *name; int v = 0; int option; char modifier; CheckNull(cepp); strlcpy(copy, cepp->ce_vardata, 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->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), name); #else config_warn("%s:%i: %s: unknown protocol '%s'. " "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, 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->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp)); errors++; } } else if (!strcmp(cepp->ce_varname, "certificate") || !strcmp(cepp->ce_varname, "dh") || !strcmp(cepp->ce_varname, "key") || !strcmp(cepp->ce_varname, "trusted-ca-file")) { char *path; CheckNull(cepp); path = convert_to_absolute_path_duplicate(cepp->ce_vardata, CONFDIR); if (!file_exists(path)) { config_error("%s:%i: %s: could not open '%s': %s", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), path, strerror(errno)); safe_free(path); errors++; } safe_free(path); } else if (!strcmp(cepp->ce_varname, "outdated-protocols")) { char copy[512], *p, *name; int v = 0; int option; char modifier; CheckNull(cepp); strlcpy(copy, cepp->ce_vardata, 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->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), name); #else config_warn("%s:%i: %s: unknown protocol '%s'. " "Valid protocols are: TLSv1,TLSv1.1,TLSv1.2", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, config_var(cepp), name); #endif } } } else if (!strcmp(cepp->ce_varname, "outdated-ciphers")) { CheckNull(cepp); } else if (!strcmp(cepp->ce_varname, "options")) { for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next) if (!config_binary_flags_search(_TLSFlags, ceppp->ce_varname, ARRAY_SIZEOF(_TLSFlags))) { config_error("%s:%i: unknown SSL/TLS option '%s'", ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, ceppp->ce_varname); errors ++; } } else if (!strcmp(cepp->ce_varname, "sts-policy")) { int has_port = 0; int has_duration = 0; for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next) { if (!strcmp(ceppp->ce_varname, "port")) { int port; CheckNull(ceppp); port = atoi(ceppp->ce_vardata); if ((port < 1) || (port > 65535)) { config_error("%s:%i: invalid port number specified in sts-policy::port (%d)", ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, port); errors++; } has_port = 1; } else if (!strcmp(ceppp->ce_varname, "duration")) { long duration; CheckNull(ceppp); duration = config_checkval(ceppp->ce_vardata, CFG_TIME); if (duration < 1) { config_error("%s:%i: invalid duration specified in sts-policy::duration (%ld seconds)", ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, duration); errors++; } has_duration = 1; } else if (!strcmp(ceppp->ce_varname, "preload")) { CheckNull(ceppp); } } if (!has_port) { config_error("%s:%i: sts-policy block without port", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } if (!has_duration) { config_error("%s:%i: sts-policy block without duration", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else { config_error("%s:%i: unknown directive %s", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, 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->dh_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->dh_file, tempiConf.tls_options->dh_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->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "ciphers") || !strcmp(cepp->ce_varname, "server-cipher-list")) { safe_strdup(tlsoptions->ciphers, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "ciphersuites")) { safe_strdup(tlsoptions->ciphersuites, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "ecdh-curves")) { safe_strdup(tlsoptions->ecdh_curves, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "protocols")) { char copy[512], *p, *name; int option; char modifier; strlcpy(copy, cepp->ce_vardata, 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->ce_varname, "dh")) { convert_to_absolute_path(&cepp->ce_vardata, CONFDIR); } else if (!strcmp(cepp->ce_varname, "certificate")) { convert_to_absolute_path(&cepp->ce_vardata, CONFDIR); safe_strdup(tlsoptions->certificate_file, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "key")) { convert_to_absolute_path(&cepp->ce_vardata, CONFDIR); safe_strdup(tlsoptions->key_file, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "trusted-ca-file")) { convert_to_absolute_path(&cepp->ce_vardata, CONFDIR); safe_strdup(tlsoptions->trusted_ca_file, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "outdated-protocols")) { safe_strdup(tlsoptions->outdated_protocols, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "outdated-ciphers")) { safe_strdup(tlsoptions->outdated_ciphers, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "renegotiate-bytes")) { tlsoptions->renegotiate_bytes = config_checkval(cepp->ce_vardata, CFG_SIZE); } else if (!strcmp(cepp->ce_varname, "renegotiate-timeout")) { tlsoptions->renegotiate_timeout = config_checkval(cepp->ce_vardata, CFG_TIME); } else if (!strcmp(cepp->ce_varname, "options")) { tlsoptions->options = 0; for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next) { ofl = config_binary_flags_search(_TLSFlags, ceppp->ce_varname, ARRAY_SIZEOF(_TLSFlags)); if (ofl) /* this should always be true */ tlsoptions->options |= ofl->flag; } } else if (!strcmp(cepp->ce_varname, "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->ce_entries; ceppp; ceppp = ceppp->ce_next) { if (!strcmp(ceppp->ce_varname, "port")) tlsoptions->sts_port = atoi(ceppp->ce_vardata); else if (!strcmp(ceppp->ce_varname, "duration")) tlsoptions->sts_duration = config_checkval(ceppp->ce_vardata, CFG_TIME); else if (!strcmp(ceppp->ce_varname, "preload")) tlsoptions->sts_preload = config_checkval(ceppp->ce_vardata, CFG_YESNO); } } } } int _conf_set(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep, *cepp, *ceppp; Hook *h; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "kline-address")) { safe_strdup(tempiConf.kline_address, cep->ce_vardata); } if (!strcmp(cep->ce_varname, "gline-address")) { safe_strdup(tempiConf.gline_address, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "modes-on-connect")) { tempiConf.conn_modes = (long) set_usermode(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "modes-on-oper")) { tempiConf.oper_modes = (long) set_usermode(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "modes-on-join")) { conf_channelmodes(cep->ce_vardata, &tempiConf.modes_on_join, 0); } else if (!strcmp(cep->ce_varname, "snomask-on-oper")) { safe_strdup(tempiConf.oper_snomask, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "level-on-join")) { tempiConf.level_on_join = channellevel_to_int(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "static-quit")) { safe_strdup(tempiConf.static_quit, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "static-part")) { safe_strdup(tempiConf.static_part, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "who-limit")) { tempiConf.who_limit = atol(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "maxbans")) { tempiConf.maxbans = atol(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "maxbanlength")) { tempiConf.maxbanlength = atol(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "silence-limit")) { tempiConf.silence_limit = atol(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "auto-join")) { safe_strdup(tempiConf.auto_join_chans, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "oper-auto-join")) { safe_strdup(tempiConf.oper_auto_join_chans, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "check-target-nick-bans")) { tempiConf.check_target_nick_bans = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "ping-cookie")) { tempiConf.ping_cookie = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "watch-away-notification")) { tempiConf.watch_away_notification = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "uhnames")) { tempiConf.uhnames = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "allow-userhost-change")) { if (!strcasecmp(cep->ce_vardata, "always")) tempiConf.userhost_allowed = UHALLOW_ALWAYS; else if (!strcasecmp(cep->ce_vardata, "never")) tempiConf.userhost_allowed = UHALLOW_NEVER; else if (!strcasecmp(cep->ce_vardata, "not-on-channels")) tempiConf.userhost_allowed = UHALLOW_NOCHANS; else tempiConf.userhost_allowed = UHALLOW_REJOIN; } else if (!strcmp(cep->ce_varname, "channel-command-prefix")) { safe_strdup(tempiConf.channel_command_prefix, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "restrict-usermodes")) { int i; char *p = safe_alloc(strlen(cep->ce_vardata) + 1), *x = p; /* The data should be something like 'Gw' or something, * but just in case users use '+Gw' then ignore the + (and -). */ for (i=0; i < strlen(cep->ce_vardata); i++) if ((cep->ce_vardata[i] != '+') && (cep->ce_vardata[i] != '-')) *x++ = cep->ce_vardata[i]; *x = '\0'; tempiConf.restrict_usermodes = p; } else if (!strcmp(cep->ce_varname, "restrict-channelmodes")) { int i; char *p = safe_alloc(strlen(cep->ce_vardata) + 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->ce_vardata); i++) if ((cep->ce_vardata[i] != '+') && (cep->ce_vardata[i] != '-')) *x++ = cep->ce_vardata[i]; *x = '\0'; tempiConf.restrict_channelmodes = p; } else if (!strcmp(cep->ce_varname, "restrict-extendedbans")) { safe_strdup(tempiConf.restrict_extendedbans, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "anti-spam-quit-message-time")) { tempiConf.anti_spam_quit_message_time = config_checkval(cep->ce_vardata,CFG_TIME); } else if (!strcmp(cep->ce_varname, "allow-user-stats")) { if (!cep->ce_entries) { safe_strdup(tempiConf.allow_user_stats, cep->ce_vardata); } else { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { OperStat *os = safe_alloc(sizeof(OperStat)); safe_strdup(os->flag, cepp->ce_varname); AddListItem(os, tempiConf.allow_user_stats_ext); } } } else if (!strcmp(cep->ce_varname, "maxchannelsperuser")) { tempiConf.maxchannelsperuser = atoi(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "ping-warning")) { tempiConf.ping_warning = atoi(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "maxdccallow")) { tempiConf.maxdccallow = atoi(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "max-targets-per-command")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { int v; if (!strcmp(cepp->ce_vardata, "max")) v = MAXTARGETS_MAX; else v = atoi(cepp->ce_vardata); setmaxtargets(cepp->ce_varname, v); } } else if (!strcmp(cep->ce_varname, "network-name")) { char *tmp; safe_strdup(tempiConf.network.x_ircnetwork, cep->ce_vardata); for (tmp = cep->ce_vardata; *cep->ce_vardata; cep->ce_vardata++) { if (*cep->ce_vardata == ' ') *cep->ce_vardata='-'; } safe_strdup(tempiConf.network.x_ircnet005, tmp); cep->ce_vardata = tmp; } else if (!strcmp(cep->ce_varname, "default-server")) { safe_strdup(tempiConf.network.x_defserv, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "services-server")) { safe_strdup(tempiConf.network.x_services_name, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "sasl-server")) { safe_strdup(tempiConf.network.x_sasl_server, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "stats-server")) { safe_strdup(tempiConf.network.x_stats_server, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "help-channel")) { safe_strdup(tempiConf.network.x_helpchan, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "hiddenhost-prefix")) { safe_strdup(tempiConf.network.x_hidden_host, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "hide-ban-reason")) { tempiConf.hide_ban_reason = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "prefix-quit")) { if (!strcmp(cep->ce_vardata, "0") || !strcmp(cep->ce_vardata, "no")) safe_free(tempiConf.network.x_prefix_quit); else safe_strdup(tempiConf.network.x_prefix_quit, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "link")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "bind-ip")) { safe_strdup(tempiConf.link_bindip, cepp->ce_vardata); } } } else if (!strcmp(cep->ce_varname, "dns")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "bind-ip")) { safe_strdup(tempiConf.dns_bindip, cepp->ce_vardata); } } } else if (!strcmp(cep->ce_varname, "anti-flood")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "unknown-flood-bantime")) tempiConf.unknown_flood_bantime = config_checkval(cepp->ce_vardata,CFG_TIME); else if (!strcmp(cepp->ce_varname, "unknown-flood-amount")) tempiConf.unknown_flood_amount = atol(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "away-count")) tempiConf.away_count = atol(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "away-period")) tempiConf.away_period = config_checkval(cepp->ce_vardata, CFG_TIME); else if (!strcmp(cepp->ce_varname, "away-flood")) { int cnt, period; config_parse_flood(cepp->ce_vardata, &cnt, &period); tempiConf.away_count = cnt; tempiConf.away_period = period; } else if (!strcmp(cepp->ce_varname, "nick-flood")) { int cnt, period; config_parse_flood(cepp->ce_vardata, &cnt, &period); tempiConf.nick_count = cnt; tempiConf.nick_period = period; } else if (!strcmp(cepp->ce_varname, "away-flood")) { int cnt, period; config_parse_flood(cepp->ce_vardata, &cnt, &period); tempiConf.away_count = cnt; tempiConf.away_period = period; } else if (!strcmp(cepp->ce_varname, "invite-flood")) { int cnt, period; config_parse_flood(cepp->ce_vardata, &cnt, &period); tempiConf.invite_count = cnt; tempiConf.invite_period = period; } else if (!strcmp(cepp->ce_varname, "knock-flood")) { int cnt, period; config_parse_flood(cepp->ce_vardata, &cnt, &period); tempiConf.knock_count = cnt; tempiConf.knock_period = period; } else if (!strcmp(cepp->ce_varname, "connect-flood")) { int cnt, period; config_parse_flood(cepp->ce_vardata, &cnt, &period); tempiConf.throttle_count = cnt; tempiConf.throttle_period = period; } if (!strcmp(cepp->ce_varname, "max-concurrent-conversations")) { for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next) { if (!strcmp(ceppp->ce_varname, "users")) { tempiConf.max_concurrent_conversations_users = atoi(ceppp->ce_vardata); } else if (!strcmp(ceppp->ce_varname, "new-user-every")) { tempiConf.max_concurrent_conversations_new_user_every = config_checkval(ceppp->ce_vardata, CFG_TIME); } } } else { for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next) { int value = (*(h->func.intfunc))(conf,cepp,CONFIG_SET_ANTI_FLOOD); if (value == 1) break; } } } } else if (!strcmp(cep->ce_varname, "options")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "hide-ulines")) { tempiConf.hide_ulines = 1; } else if (!strcmp(cepp->ce_varname, "flat-map")) { tempiConf.flat_map = 1; } else if (!strcmp(cepp->ce_varname, "show-opermotd")) { tempiConf.som = 1; } else if (!strcmp(cepp->ce_varname, "identd-check")) { tempiConf.ident_check = 1; } else if (!strcmp(cepp->ce_varname, "fail-oper-warn")) { tempiConf.fail_oper_warn = 1; } else if (!strcmp(cepp->ce_varname, "show-connect-info")) { tempiConf.show_connect_info = 1; } else if (!strcmp(cepp->ce_varname, "no-connect-tls-info")) { tempiConf.no_connect_tls_info = 1; } else if (!strcmp(cepp->ce_varname, "dont-resolve")) { tempiConf.dont_resolve = 1; } else if (!strcmp(cepp->ce_varname, "mkpasswd-for-everyone")) { tempiConf.mkpasswd_for_everyone = 1; } else if (!strcmp(cepp->ce_varname, "allow-insane-bans")) { tempiConf.allow_insane_bans = 1; } else if (!strcmp(cepp->ce_varname, "allow-part-if-shunned")) { tempiConf.allow_part_if_shunned = 1; } else if (!strcmp(cepp->ce_varname, "disable-cap")) { tempiConf.disable_cap = 1; } else if (!strcmp(cepp->ce_varname, "disable-ipv6")) { /* other code handles this */ } } } else if (!strcmp(cep->ce_varname, "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->ce_varname, "ident")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "connect-timeout")) tempiConf.ident_connect_timeout = config_checkval(cepp->ce_vardata,CFG_TIME); if (!strcmp(cepp->ce_varname, "read-timeout")) tempiConf.ident_read_timeout = config_checkval(cepp->ce_vardata,CFG_TIME); } } else if (!strcmp(cep->ce_varname, "spamfilter")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "ban-time")) tempiConf.spamfilter_ban_time = config_checkval(cepp->ce_vardata,CFG_TIME); else if (!strcmp(cepp->ce_varname, "ban-reason")) safe_strdup(tempiConf.spamfilter_ban_reason, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "virus-help-channel")) safe_strdup(tempiConf.spamfilter_virus_help_channel, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "virus-help-channel-deny")) tempiConf.spamfilter_vchan_deny = config_checkval(cepp->ce_vardata,CFG_YESNO); else if (!strcmp(cepp->ce_varname, "except")) { char *name, *p; SpamExcept *e; safe_strdup(tempiConf.spamexcept_line, cepp->ce_vardata); for (name = strtoken(&p, cepp->ce_vardata, ","); 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->ce_varname, "detect-slow-warn")) { tempiConf.spamfilter_detectslow_warn = atol(cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "detect-slow-fatal")) { tempiConf.spamfilter_detectslow_fatal = atol(cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "stop-on-first-match")) { tempiConf.spamfilter_stop_on_first_match = config_checkval(cepp->ce_vardata, CFG_YESNO); } } } else if (!strcmp(cep->ce_varname, "default-bantime")) { tempiConf.default_bantime = config_checkval(cep->ce_vardata,CFG_TIME); } else if (!strcmp(cep->ce_varname, "ban-version-tkl-time")) { tempiConf.ban_version_tkl_time = config_checkval(cep->ce_vardata,CFG_TIME); } else if (!strcmp(cep->ce_varname, "min-nick-length")) { int v = atoi(cep->ce_vardata); tempiConf.min_nick_length = v; } else if (!strcmp(cep->ce_varname, "nick-length")) { int v = atoi(cep->ce_vardata); tempiConf.nick_length = v; } else if (!strcmp(cep->ce_varname, "topic-length")) { int v = atoi(cep->ce_vardata); tempiConf.topic_length = v; } else if (!strcmp(cep->ce_varname, "away-length")) { int v = atoi(cep->ce_vardata); tempiConf.away_length = v; } else if (!strcmp(cep->ce_varname, "kick-length")) { int v = atoi(cep->ce_vardata); tempiConf.kick_length = v; } else if (!strcmp(cep->ce_varname, "quit-length")) { int v = atoi(cep->ce_vardata); tempiConf.quit_length = v; } else if (!strcmp(cep->ce_varname, "ssl") || !strcmp(cep->ce_varname, "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->ce_varname, "plaintext-policy")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "user")) tempiConf.plaintext_policy_user = policy_strtoval(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "oper")) tempiConf.plaintext_policy_oper = policy_strtoval(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "server")) tempiConf.plaintext_policy_server = policy_strtoval(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "user-message")) addmultiline(&tempiConf.plaintext_policy_user_message, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "oper-message")) addmultiline(&tempiConf.plaintext_policy_oper_message, cepp->ce_vardata); } } else if (!strcmp(cep->ce_varname, "outdated-tls-policy")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "user")) tempiConf.outdated_tls_policy_user = policy_strtoval(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "oper")) tempiConf.outdated_tls_policy_oper = policy_strtoval(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "server")) tempiConf.outdated_tls_policy_server = policy_strtoval(cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "user-message")) safe_strdup(tempiConf.outdated_tls_policy_user_message, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "oper-message")) safe_strdup(tempiConf.outdated_tls_policy_oper_message, cepp->ce_vardata); } } else if (!strcmp(cep->ce_varname, "default-ipv6-clone-mask")) { tempiConf.default_ipv6_clone_mask = atoi(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "hide-list")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "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->ce_varname, "max-unknown-connections-per-ip")) { tempiConf.max_unknown_connections_per_ip = atoi(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "handshake-timeout")) { tempiConf.handshake_timeout = config_checkval(cep->ce_vardata, CFG_TIME); } else if (!strcmp(cep->ce_varname, "sasl-timeout")) { tempiConf.sasl_timeout = config_checkval(cep->ce_vardata, CFG_TIME); } else if (!strcmp(cep->ce_varname, "handshake-delay")) { tempiConf.handshake_delay = config_checkval(cep->ce_vardata, CFG_TIME); } else if (!strcmp(cep->ce_varname, "automatic-ban-target")) { tempiConf.automatic_ban_target = ban_target_strtoval(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "manual-ban-target")) { tempiConf.manual_ban_target = ban_target_strtoval(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "reject-message")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "too-many-connections")) safe_strdup(tempiConf.reject_message_too_many_connections, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "server-full")) safe_strdup(tempiConf.reject_message_server_full, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "unauthorized")) safe_strdup(tempiConf.reject_message_unauthorized, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "kline")) safe_strdup(tempiConf.reject_message_kline, cepp->ce_vardata); else if (!strcmp(cepp->ce_varname, "gline")) safe_strdup(tempiConf.reject_message_gline, cepp->ce_vardata); } } else if (!strcmp(cep->ce_varname, "topic-setter")) { if (!strcmp(cep->ce_vardata, "nick")) tempiConf.topic_setter = SETTER_NICK; else if (!strcmp(cep->ce_vardata, "nick-user-host")) tempiConf.topic_setter = SETTER_NICK_USER_HOST; } else if (!strcmp(cep->ce_varname, "ban-setter")) { if (!strcmp(cep->ce_vardata, "nick")) tempiConf.ban_setter = SETTER_NICK; else if (!strcmp(cep->ce_vardata, "nick-user-host")) tempiConf.ban_setter = SETTER_NICK_USER_HOST; } else if (!strcmp(cep->ce_varname, "ban-setter-sync") || !strcmp(cep->ce_varname, "ban-setter-synch")) { tempiConf.ban_setter_sync = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "part-instead-of-quit-on-comment-change")) { tempiConf.part_instead_of_quit_on_comment_change = config_checkval(cep->ce_vardata, CFG_YESNO); } else if (!strcmp(cep->ce_varname, "broadcast-channel-messages")) { if (!strcmp(cep->ce_vardata, "auto")) tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_AUTO; else if (!strcmp(cep->ce_vardata, "always")) tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_ALWAYS; else if (!strcmp(cep->ce_vardata, "never")) tempiConf.broadcast_channel_messages = BROADCAST_CHANNEL_MESSAGES_NEVER; } else if (!strcmp(cep->ce_varname, "allowed-channelchars")) { tempiConf.allowed_channelchars = allowed_channelchars_strtoval(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "hide-idle-time")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "policy")) tempiConf.hide_idle_time = hideidletime_strtoval(cepp->ce_vardata); } } else { int value; for (h = Hooks[HOOKTYPE_CONFIGRUN]; h; h = h->next) { value = (*(h->func.intfunc))(conf,cep,CONFIG_SET); if (value == 1) break; } } } return 0; } int _test_set(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep, *cepp, *ceppp; int tempi; int errors = 0; Hook *h; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "kline-address")) { CheckNull(cep); CheckDuplicate(cep, kline_address, "kline-address"); if (!strchr(cep->ce_vardata, '@') && !strchr(cep->ce_vardata, ':')) { config_error("%s:%i: set::kline-address must be an e-mail or an URL", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } else if (match_simple("*@unrealircd.com", cep->ce_vardata) || match_simple("*@unrealircd.org",cep->ce_vardata) || match_simple("unreal-*@lists.sourceforge.net",cep->ce_vardata)) { config_error("%s:%i: set::kline-address may not be an UnrealIRCd Team address", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } } else if (!strcmp(cep->ce_varname, "gline-address")) { CheckNull(cep); CheckDuplicate(cep, gline_address, "gline-address"); if (!strchr(cep->ce_vardata, '@') && !strchr(cep->ce_vardata, ':')) { config_error("%s:%i: set::gline-address must be an e-mail or an URL", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } else if (match_simple("*@unrealircd.com", cep->ce_vardata) || match_simple("*@unrealircd.org",cep->ce_vardata) || match_simple("unreal-*@lists.sourceforge.net",cep->ce_vardata)) { config_error("%s:%i: set::gline-address may not be an UnrealIRCd Team address", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } } else if (!strcmp(cep->ce_varname, "modes-on-connect")) { char *p; CheckNull(cep); CheckDuplicate(cep, modes_on_connect, "modes-on-connect"); for (p = cep->ce_vardata; *p; p++) if (strchr("orzSHqtW", *p)) { config_error("%s:%i: set::modes-on-connect may not include mode '%c'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p); errors++; } set_usermode(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "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->ce_vardata; *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': case 'k': case 'l': config_error("%s:%i: set::modes-on-join may not contain +%c", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *c); errors++; break; } } conf_channelmodes(cep->ce_vardata, &temp, 1); if (temp.mode & MODE_SECRET && temp.mode & MODE_PRIVATE) { config_error("%s:%i: set::modes-on-join has both +s and +p", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "modes-on-oper")) { char *p; CheckNull(cep); CheckDuplicate(cep, modes_on_oper, "modes-on-oper"); for (p = cep->ce_vardata; *p; p++) if (strchr("orzS", *p)) { config_error("%s:%i: set::modes-on-oper may not include mode '%c'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p); errors++; } set_usermode(cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "snomask-on-oper")) { CheckNull(cep); CheckDuplicate(cep, snomask_on_oper, "snomask-on-oper"); } else if (!strcmp(cep->ce_varname, "level-on-join")) { CheckNull(cep); CheckDuplicate(cep, level_on_join, "level-on-join"); if (!channellevel_to_int(cep->ce_vardata)) { config_error("%s:%i: set::level-on-join: unknown value '%s', should be one of: none, voice, halfop, op, protect, owner", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); errors++; } } else if (!strcmp(cep->ce_varname, "static-quit")) { CheckNull(cep); CheckDuplicate(cep, static_quit, "static-quit"); } else if (!strcmp(cep->ce_varname, "static-part")) { CheckNull(cep); CheckDuplicate(cep, static_part, "static-part"); } else if (!strcmp(cep->ce_varname, "who-limit")) { CheckNull(cep); CheckDuplicate(cep, who_limit, "who-limit"); } else if (!strcmp(cep->ce_varname, "maxbans")) { CheckNull(cep); CheckDuplicate(cep, maxbans, "maxbans"); } else if (!strcmp(cep->ce_varname, "maxbanlength")) { CheckNull(cep); CheckDuplicate(cep, maxbanlength, "maxbanlength"); } else if (!strcmp(cep->ce_varname, "silence-limit")) { CheckNull(cep); CheckDuplicate(cep, silence_limit, "silence-limit"); } else if (!strcmp(cep->ce_varname, "auto-join")) { CheckNull(cep); CheckDuplicate(cep, auto_join, "auto-join"); } else if (!strcmp(cep->ce_varname, "oper-auto-join")) { CheckNull(cep); CheckDuplicate(cep, oper_auto_join, "oper-auto-join"); } else if (!strcmp(cep->ce_varname, "check-target-nick-bans")) { CheckNull(cep); CheckDuplicate(cep, check_target_nick_bans, "check-target-nick-bans"); } else if (!strcmp(cep->ce_varname, "pingpong-warning")) { config_error("%s:%i: set::pingpong-warning no longer exists (the warning is always off)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); need_34_upgrade = 1; errors++; } else if (!strcmp(cep->ce_varname, "ping-cookie")) { CheckNull(cep); CheckDuplicate(cep, ping_cookie, "ping-cookie"); } else if (!strcmp(cep->ce_varname, "watch-away-notification")) { CheckNull(cep); CheckDuplicate(cep, watch_away_notification, "watch-away-notification"); } else if (!strcmp(cep->ce_varname, "uhnames")) { CheckNull(cep); CheckDuplicate(cep, uhnames, "uhnames"); } else if (!strcmp(cep->ce_varname, "channel-command-prefix")) { CheckNullAllowEmpty(cep); CheckDuplicate(cep, channel_command_prefix, "channel-command-prefix"); } else if (!strcmp(cep->ce_varname, "allow-userhost-change")) { CheckNull(cep); CheckDuplicate(cep, allow_userhost_change, "allow-userhost-change"); if (strcasecmp(cep->ce_vardata, "always") && strcasecmp(cep->ce_vardata, "never") && strcasecmp(cep->ce_vardata, "not-on-channels") && strcasecmp(cep->ce_vardata, "force-rejoin")) { config_error("%s:%i: set::allow-userhost-change is invalid", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } } else if (!strcmp(cep->ce_varname, "anti-spam-quit-message-time")) { CheckNull(cep); CheckDuplicate(cep, anti_spam_quit_message_time, "anti-spam-quit-message-time"); } else if (!strcmp(cep->ce_varname, "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->ce_fileptr->cf_filename, cep->ce_varlinenum); config_warn("Simply delete the oper-only-stats line from your configuration file %s around line %d to get rid of this warning", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); continue; } else if (!strcmp(cep->ce_varname, "allow-user-stats")) { CheckDuplicate(cep, allow_user_stats, "allow-user-stats"); if (!cep->ce_entries) { CheckNull(cep); } else { /* TODO: check the entries for existence? for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { } */ } } else if (!strcmp(cep->ce_varname, "maxchannelsperuser")) { CheckNull(cep); CheckDuplicate(cep, maxchannelsperuser, "maxchannelsperuser"); tempi = atoi(cep->ce_vardata); if (tempi < 1) { config_error("%s:%i: set::maxchannelsperuser must be > 0", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } } else if (!strcmp(cep->ce_varname, "ping-warning")) { CheckNull(cep); CheckDuplicate(cep, ping_warning, "ping-warning"); tempi = atoi(cep->ce_vardata); /* it is pointless to allow setting higher than 170 */ if (tempi > 170) { config_error("%s:%i: set::ping-warning must be < 170", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } } else if (!strcmp(cep->ce_varname, "maxdccallow")) { CheckNull(cep); CheckDuplicate(cep, maxdccallow, "maxdccallow"); } else if (!strcmp(cep->ce_varname, "max-targets-per-command")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { CheckNull(cepp); if (!strcasecmp(cepp->ce_varname, "NAMES") || !strcasecmp(cepp->ce_varname, "WHOWAS")) { if (atoi(cepp->ce_vardata) != 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->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname); errors++; } } else if (!strcasecmp(cepp->ce_varname, "USERHOST") || !strcasecmp(cepp->ce_varname, "USERIP") || !strcasecmp(cepp->ce_varname, "ISON") || !strcasecmp(cepp->ce_varname, "WATCH")) { if (strcmp(cepp->ce_vardata, "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->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname); errors++; } } /* Now check the value syntax in general: */ if (strcmp(cepp->ce_vardata, "max")) /* anything other than 'max'.. */ { int v = atoi(cepp->ce_vardata); if ((v < 1) || (v > 20)) { config_error("%s:%i: set::max-targets-per-command::%s: " "value should be 1-20 or 'max'", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname); errors++; } } } } else if (!strcmp(cep->ce_varname, "network-name")) { char *p; CheckNull(cep); CheckDuplicate(cep, network_name, "network-name"); for (p = cep->ce_vardata; *p; p++) if ((*p < ' ') || (*p > '~')) { config_error("%s:%i: set::network-name can only contain ASCII characters 33-126. Invalid character = '%c'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, *p); errors++; break; } } else if (!strcmp(cep->ce_varname, "default-server")) { CheckNull(cep); CheckDuplicate(cep, default_server, "default-server"); } else if (!strcmp(cep->ce_varname, "services-server")) { CheckNull(cep); CheckDuplicate(cep, services_server, "services-server"); } else if (!strcmp(cep->ce_varname, "sasl-server")) { CheckNull(cep); CheckDuplicate(cep, sasl_server, "sasl-server"); } else if (!strcmp(cep->ce_varname, "stats-server")) { CheckNull(cep); CheckDuplicate(cep, stats_server, "stats-server"); } else if (!strcmp(cep->ce_varname, "help-channel")) { CheckNull(cep); CheckDuplicate(cep, help_channel, "help-channel"); } else if (!strcmp(cep->ce_varname, "hiddenhost-prefix")) { CheckNull(cep); CheckDuplicate(cep, hiddenhost_prefix, "hiddenhost-prefix"); if (strchr(cep->ce_vardata, ' ') || (*cep->ce_vardata == ':')) { config_error("%s:%i: set::hiddenhost-prefix must not contain spaces or be prefixed with ':'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; continue; } } else if (!strcmp(cep->ce_varname, "prefix-quit")) { CheckNull(cep); CheckDuplicate(cep, prefix_quit, "prefix-quit"); } else if (!strcmp(cep->ce_varname, "hide-ban-reason")) { CheckNull(cep); CheckDuplicate(cep, hide_ban_reason, "hide-ban-reason"); } else if (!strcmp(cep->ce_varname, "restrict-usermodes")) { CheckNull(cep); CheckDuplicate(cep, restrict_usermodes, "restrict-usermodes"); if (cep->ce_varname) { int warn = 0; char *p; for (p = cep->ce_vardata; *p; p++) if ((*p == '+') || (*p == '-')) warn = 1; if (warn) { config_status("%s:%i: warning: set::restrict-usermodes: should only contain modechars, no + or -.\n", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); } } } else if (!strcmp(cep->ce_varname, "restrict-channelmodes")) { CheckNull(cep); CheckDuplicate(cep, restrict_channelmodes, "restrict-channelmodes"); if (cep->ce_varname) { int warn = 0; char *p; for (p = cep->ce_vardata; *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->ce_fileptr->cf_filename, cep->ce_varlinenum); } } } else if (!strcmp(cep->ce_varname, "restrict-extendedbans")) { CheckDuplicate(cep, restrict_extendedbans, "restrict-extendedbans"); CheckNull(cep); } else if (!strcmp(cep->ce_varname, "link")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { CheckNull(cepp); if (!strcmp(cepp->ce_varname, "bind-ip")) { CheckDuplicate(cepp, link_bind_ip, "link::bind-ip"); if (strcmp(cepp->ce_vardata, "*")) { if (!is_valid_ip(cepp->ce_vardata)) { config_error("%s:%i: set::link::bind-ip (%s) is not a valid IP", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_vardata); errors++; continue; } } } } } else if (!strcmp(cep->ce_varname, "dns")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { CheckNull(cepp); if (!strcmp(cepp->ce_varname, "nameserver") || !strcmp(cepp->ce_varname, "timeout") || !strcmp(cepp->ce_varname, "retries")) { config_error("%s:%i: set::dns::%s no longer exist in UnrealIRCd 4. " "Please remove it from your configuration file.", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname); errors++; } else if (!strcmp(cepp->ce_varname, "bind-ip")) { CheckDuplicate(cepp, dns_bind_ip, "dns::bind-ip"); if (strcmp(cepp->ce_vardata, "*")) { if (!is_valid_ip(cepp->ce_vardata)) { config_error("%s:%i: set::dns::bind-ip (%s) is not a valid IP", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_vardata); errors++; continue; } } } else { config_error_unknownopt(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::dns", cepp->ce_varname); errors++; } } } else if (!strcmp(cep->ce_varname, "throttle")) { config_error("%s:%i: set::throttle has been renamed. you now use " "set::anti-flood::connect-flood :. " "Or just remove the throttle block and you get the default " "of 3 per 60 seconds.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; need_34_upgrade = 1; continue; } else if (!strcmp(cep->ce_varname, "anti-flood")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "max-concurrent-conversations")) { for (ceppp = cepp->ce_entries; ceppp; ceppp = ceppp->ce_next) { CheckNull(ceppp); if (!strcmp(ceppp->ce_varname, "users")) { int v = atoi(ceppp->ce_vardata); if ((v < 1) || (v > MAXCCUSERS)) { config_error("%s:%i: set::anti-flood::max-concurrent-conversations::users: " "value should be between 1 and %d", ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, MAXCCUSERS); errors++; } } else if (!strcmp(ceppp->ce_varname, "new-user-every")) { long v = config_checkval(ceppp->ce_vardata, 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", ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum); errors++; } } else { config_error_unknownopt(ceppp->ce_fileptr->cf_filename, ceppp->ce_varlinenum, "set::anti-flood", ceppp->ce_varname); errors++; } } continue; /* required here, due to checknull directly below */ } CheckNull(cepp); if (!strcmp(cepp->ce_varname, "unknown-flood-bantime")) { CheckDuplicate(cepp, anti_flood_unknown_flood_bantime, "anti-flood::unknown-flood-bantime"); } else if (!strcmp(cepp->ce_varname, "unknown-flood-amount")) { CheckDuplicate(cepp, anti_flood_unknown_flood_amount, "anti-flood::unknown-flood-amount"); } else if (!strcmp(cepp->ce_varname, "away-count")) { int temp = atol(cepp->ce_vardata); CheckDuplicate(cepp, anti_flood_away_count, "anti-flood::away-count"); if (temp < 1 || temp > 255) { config_error("%s:%i: set::anti-flood::away-count must be between 1 and 255", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "away-period")) { int temp = config_checkval(cepp->ce_vardata, CFG_TIME); CheckDuplicate(cepp, anti_flood_away_period, "anti-flood::away-period"); if (temp < 10) { config_error("%s:%i: set::anti-flood::away-period must be greater than 9", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "away-flood")) { int cnt, period; if (settings.has_anti_flood_away_period) { config_warn("%s:%d: set::anti-flood::away-flood overrides set::anti-flood::away-period", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); continue; } if (settings.has_anti_flood_away_count) { config_warn("%s:%d: set::anti-flood::away-flood overrides set::anti-flood::away-count", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); continue; } settings.has_anti_flood_away_period = 1; settings.has_anti_flood_away_count = 1; if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) || (cnt < 1) || (cnt > 255) || (period < 10)) { config_error("%s:%i: set::anti-flood::away-flood error. Syntax is ':' (eg 5:60), " "count should be 1-255, period should be greater than 9", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "nick-flood")) { int cnt, period; CheckDuplicate(cepp, anti_flood_nick_flood, "anti-flood::nick-flood"); if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) || (cnt < 1) || (cnt > 255) || (period < 5)) { config_error("%s:%i: set::anti-flood::nick-flood error. Syntax is ':' (eg 5:60), " "count should be 1-255, period should be greater than 4", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "invite-flood")) { int cnt, period; CheckDuplicate(cepp, anti_flood_invite_flood, "anti-flood::invite-flood"); if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) || (cnt < 1) || (cnt > 255) || (period < 5)) { config_error("%s:%i: set::anti-flood::invite-flood error. Syntax is ':' (eg 5:60), " "count should be 1-255, period should be greater than 4", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "knock-flood")) { int cnt, period; CheckDuplicate(cepp, anti_flood_knock_flood, "anti-flood::knock-flood"); if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) || (cnt < 1) || (cnt > 255) || (period < 5)) { config_error("%s:%i: set::anti-flood::knock-flood error. Syntax is ':' (eg 5:60), " "count should be 1-255, period should be greater than 4", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "connect-flood")) { int cnt, period; CheckDuplicate(cepp, anti_flood_connect_flood, "anti-flood::connect-flood"); if (!config_parse_flood(cepp->ce_vardata, &cnt, &period) || (cnt < 1) || (cnt > 255) || (period < 1) || (period > 3600)) { config_error("%s:%i: set::anti-flood::connect-flood: Syntax is ':' (eg 5:60), " "count should be 1-255, period should be 1-3600", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else { /* hmm.. I don't like this method. but I just quickly copied it from CONFIG_ALLOW for now... */ int used = 0; Hook *h; 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_SET_ANTI_FLOOD,&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_unknownopt(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::anti-flood", cepp->ce_varname); errors++; } continue; } } } else if (!strcmp(cep->ce_varname, "options")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "hide-ulines")) { CheckDuplicate(cepp, options_hide_ulines, "options::hide-ulines"); } else if (!strcmp(cepp->ce_varname, "flat-map")) { CheckDuplicate(cepp, options_flat_map, "options::flat-map"); } else if (!strcmp(cepp->ce_varname, "show-opermotd")) { CheckDuplicate(cepp, options_show_opermotd, "options::show-opermotd"); } else if (!strcmp(cepp->ce_varname, "identd-check")) { CheckDuplicate(cepp, options_identd_check, "options::identd-check"); } else if (!strcmp(cepp->ce_varname, "fail-oper-warn")) { CheckDuplicate(cepp, options_fail_oper_warn, "options::fail-oper-warn"); } else if (!strcmp(cepp->ce_varname, "show-connect-info")) { CheckDuplicate(cepp, options_show_connect_info, "options::show-connect-info"); } else if (!strcmp(cepp->ce_varname, "no-connect-tls-info")) { CheckDuplicate(cepp, options_no_connect_tls_info, "options::no-connect-tls-info"); } else if (!strcmp(cepp->ce_varname, "dont-resolve")) { CheckDuplicate(cepp, options_dont_resolve, "options::dont-resolve"); } else if (!strcmp(cepp->ce_varname, "mkpasswd-for-everyone")) { CheckDuplicate(cepp, options_mkpasswd_for_everyone, "options::mkpasswd-for-everyone"); } else if (!strcmp(cepp->ce_varname, "allow-insane-bans")) { CheckDuplicate(cepp, options_allow_insane_bans, "options::allow-insane-bans"); } else if (!strcmp(cepp->ce_varname, "allow-part-if-shunned")) { CheckDuplicate(cepp, options_allow_part_if_shunned, "options::allow-part-if-shunned"); } else if (!strcmp(cepp->ce_varname, "disable-cap")) { CheckDuplicate(cepp, options_disable_cap, "options::disable-cap"); } else if (!strcmp(cepp->ce_varname, "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->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::options", cepp->ce_varname); errors++; continue; } } } else if (!strcmp(cep->ce_varname, "hosts")) { config_error("%s:%i: set::hosts has been removed in UnrealIRCd 4. You can use oper::vhost now.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; need_34_upgrade = 1; } else if (!strcmp(cep->ce_varname, "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->ce_varname, "ident")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { int is_ok = 0; CheckNull(cepp); if (!strcmp(cepp->ce_varname, "connect-timeout")) { is_ok = 1; CheckDuplicate(cepp, ident_connect_timeout, "ident::connect-timeout"); } else if (!strcmp(cepp->ce_varname, "read-timeout")) { is_ok = 1; CheckDuplicate(cepp, ident_read_timeout, "ident::read-timeout"); } if (is_ok) { int v = config_checkval(cepp->ce_vardata,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->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname, v); errors++; continue; } } else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::ident", cepp->ce_varname); errors++; continue; } } } else if (!strcmp(cep->ce_varname, "timesync") || !strcmp(cep->ce_varname, "timesynch")) { config_warn("%s:%i: Timesync support has been removed from UnrealIRCd. " "Please remove any set::timesync blocks you may have.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); config_warn("Use the time synchronization feature of your OS/distro instead!"); } else if (!strcmp(cep->ce_varname, "spamfilter")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { CheckNull(cepp); if (!strcmp(cepp->ce_varname, "ban-time")) { long x; CheckDuplicate(cepp, spamfilter_ban_time, "spamfilter::ban-time"); x = config_checkval(cepp->ce_vardata,CFG_TIME); if ((x < 0) > (x > 2000000000)) { config_error("%s:%i: set::spamfilter:ban-time: value '%ld' out of range", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, x); errors++; continue; } } else if (!strcmp(cepp->ce_varname, "ban-reason")) { CheckDuplicate(cepp, spamfilter_ban_reason, "spamfilter::ban-reason"); } else if (!strcmp(cepp->ce_varname, "virus-help-channel")) { CheckDuplicate(cepp, spamfilter_virus_help_channel, "spamfilter::virus-help-channel"); if ((cepp->ce_vardata[0] != '#') || (strlen(cepp->ce_vardata) > CHANNELLEN)) { config_error("%s:%i: set::spamfilter:virus-help-channel: " "specified channelname is too long or contains invalid characters (%s)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cepp->ce_vardata); errors++; continue; } } else if (!strcmp(cepp->ce_varname, "virus-help-channel-deny")) { CheckDuplicate(cepp, spamfilter_virus_help_channel_deny, "spamfilter::virus-help-channel-deny"); } else if (!strcmp(cepp->ce_varname, "except")) { CheckDuplicate(cepp, spamfilter_except, "spamfilter::except"); } else #ifdef SPAMFILTER_DETECTSLOW if (!strcmp(cepp->ce_varname, "detect-slow-warn")) { } else if (!strcmp(cepp->ce_varname, "detect-slow-fatal")) { } else #endif if (!strcmp(cepp->ce_varname, "stop-on-first-match")) { } else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::spamfilter", cepp->ce_varname); errors++; continue; } } } /* TODO: FIX THIS */ else if (!strcmp(cep->ce_varname, "default-bantime")) { long x; CheckDuplicate(cep, default_bantime, "default-bantime"); CheckNull(cep); x = config_checkval(cep->ce_vardata,CFG_TIME); if ((x < 0) > (x > 2000000000)) { config_error("%s:%i: set::default-bantime: value '%ld' out of range", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, x); errors++; } } else if (!strcmp(cep->ce_varname, "ban-version-tkl-time")) { long x; CheckDuplicate(cep, ban_version_tkl_time, "ban-version-tkl-time"); CheckNull(cep); x = config_checkval(cep->ce_vardata,CFG_TIME); if ((x < 0) > (x > 2000000000)) { config_error("%s:%i: set::ban-version-tkl-time: value '%ld' out of range", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, x); errors++; } } else if (!strcmp(cep->ce_varname, "min-nick-length")) { int v; CheckDuplicate(cep, min_nick_length, "min-nick-length"); CheckNull(cep); v = atoi(cep->ce_vardata); if ((v <= 0) || (v > NICKLEN)) { config_error("%s:%i: set::min-nick-length: value '%d' out of range (should be 1-%d)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, NICKLEN); errors++; } else nicklengths.min = v; } else if (!strcmp(cep->ce_varname, "nick-length")) { int v; CheckDuplicate(cep, nick_length, "nick-length"); CheckNull(cep); v = atoi(cep->ce_vardata); if ((v <= 0) || (v > NICKLEN)) { config_error("%s:%i: set::nick-length: value '%d' out of range (should be 1-%d)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, NICKLEN); errors++; } else nicklengths.max = v; } else if (!strcmp(cep->ce_varname, "topic-length")) { int v; CheckNull(cep); v = atoi(cep->ce_vardata); if ((v <= 0) || (v > MAXTOPICLEN)) { config_error("%s:%i: set::topic-length: value '%d' out of range (should be 1-%d)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXTOPICLEN); errors++; } } else if (!strcmp(cep->ce_varname, "away-length")) { int v; CheckNull(cep); v = atoi(cep->ce_vardata); if ((v <= 0) || (v > MAXAWAYLEN)) { config_error("%s:%i: set::away-length: value '%d' out of range (should be 1-%d)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXAWAYLEN); errors++; } } else if (!strcmp(cep->ce_varname, "kick-length")) { int v; CheckNull(cep); v = atoi(cep->ce_vardata); if ((v <= 0) || (v > MAXKICKLEN)) { config_error("%s:%i: set::kick-length: value '%d' out of range (should be 1-%d)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXKICKLEN); errors++; } } else if (!strcmp(cep->ce_varname, "quit-length")) { int v; CheckNull(cep); v = atoi(cep->ce_vardata); if ((v <= 0) || (v > MAXQUITLEN)) { config_error("%s:%i: set::quit-length: value '%d' out of range (should be 1-%d)", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, v, MAXQUITLEN); errors++; } } else if (!strcmp(cep->ce_varname, "ssl") || !strcmp(cep->ce_varname, "tls")) { test_tlsblock(conf, cep, &errors); } else if (!strcmp(cep->ce_varname, "plaintext-policy")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "user") || !strcmp(cepp->ce_varname, "oper") || !strcmp(cepp->ce_varname, "server")) { Policy policy; CheckNull(cepp); policy = policy_strtoval(cepp->ce_vardata); if (!policy) { config_error("%s:%i: set::plaintext-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname); errors++; } } else if (!strcmp(cepp->ce_varname, "user-message") || !strcmp(cepp->ce_varname, "oper-message")) { CheckNull(cepp); } else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::plaintext-policy", cepp->ce_varname); errors++; continue; } } } else if (!strcmp(cep->ce_varname, "outdated-tls-policy")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "user") || !strcmp(cepp->ce_varname, "oper") || !strcmp(cepp->ce_varname, "server")) { Policy policy; CheckNull(cepp); policy = policy_strtoval(cepp->ce_vardata); if (!policy) { config_error("%s:%i: set::outdated-tls-policy::%s: needs to be one of: 'allow', 'warn' or 'reject'", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, cepp->ce_varname); errors++; } } else if (!strcmp(cepp->ce_varname, "user-message") || !strcmp(cepp->ce_varname, "oper-message")) { CheckNull(cepp); } else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::outdated-tls-policy", cepp->ce_varname); errors++; continue; } } } else if (!strcmp(cep->ce_varname, "default-ipv6-clone-mask")) { /* keep this in sync with _test_allow() */ int ipv6mask; ipv6mask = atoi(cep->ce_vardata); 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->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } if (ipv6mask > 128) { config_error("%s:%d: set::default-ipv6-clone-mask was set to %d. The maximum value is 128.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, ipv6mask); errors++; } if (ipv6mask <= 32) { config_warn("%s:%d: set::default-ipv6-clone-mask was given a very small value.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); } } else if (!strcmp(cep->ce_varname, "hide-list")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "deny-channel")) { } else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::hide-list", cepp->ce_varname); errors++; continue; } } } else if (!strcmp(cep->ce_varname, "max-unknown-connections-per-ip")) { int v; CheckNull(cep); v = atoi(cep->ce_vardata); if (v < 1) { config_error("%s:%i: set::max-unknown-connections-per-ip: value should be at least 1.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "handshake-timeout")) { int v; CheckNull(cep); v = config_checkval(cep->ce_vardata, CFG_TIME); if (v < 5) { config_error("%s:%i: set::handshake-timeout: value should be at least 5 seconds.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "sasl-timeout")) { int v; CheckNull(cep); v = config_checkval(cep->ce_vardata, CFG_TIME); if (v < 5) { config_error("%s:%i: set::sasl-timeout: value should be at least 5 seconds.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "handshake-delay")) { int v; CheckNull(cep); v = config_checkval(cep->ce_vardata, CFG_TIME); if (v >= 10) { config_error("%s:%i: set::handshake-delay: value should be less than 10 seconds.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "ban-include-username")) { config_error("%s:%i: set::ban-include-username is no longer supported. " "Use set { automatic-ban-target userip; }; instead.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); config_error("See https://www.unrealircd.org/docs/Set_block#set::automatic-ban-target " "for more information and options."); errors++; } else if (!strcmp(cep->ce_varname, "automatic-ban-target")) { CheckNull(cep); if (!ban_target_strtoval(cep->ce_vardata)) { 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->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); errors++; } } else if (!strcmp(cep->ce_varname, "manual-ban-target")) { CheckNull(cep); if (!ban_target_strtoval(cep->ce_vardata)) { 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->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata); errors++; } } else if (!strcmp(cep->ce_varname, "reject-message")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { CheckNull(cepp); if (!strcmp(cepp->ce_varname, "password-mismatch")) ; else if (!strcmp(cepp->ce_varname, "too-many-connections")) ; else if (!strcmp(cepp->ce_varname, "server-full")) ; else if (!strcmp(cepp->ce_varname, "unauthorized")) ; else if (!strcmp(cepp->ce_varname, "kline")) ; else if (!strcmp(cepp->ce_varname, "gline")) ; else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::reject-message", cepp->ce_varname); errors++; continue; } } } else if (!strcmp(cep->ce_varname, "topic-setter")) { CheckNull(cep); if (strcmp(cep->ce_vardata, "nick") && strcmp(cep->ce_vardata, "nick-user-host")) { config_error("%s:%i: set::topic-setter: value should be 'nick' or 'nick-user-host'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "ban-setter")) { CheckNull(cep); if (strcmp(cep->ce_vardata, "nick") && strcmp(cep->ce_vardata, "nick-user-host")) { config_error("%s:%i: set::ban-setter: value should be 'nick' or 'nick-user-host'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "ban-setter-sync") || !strcmp(cep->ce_varname, "ban-setter-synch")) { CheckNull(cep); } else if (!strcmp(cep->ce_varname, "part-instead-of-quit-on-comment-change")) { CheckNull(cep); } else if (!strcmp(cep->ce_varname, "broadcast-channel-messages")) { CheckNull(cep); if (strcmp(cep->ce_vardata, "auto") && strcmp(cep->ce_vardata, "always") && strcmp(cep->ce_vardata, "never")) { config_error("%s:%i: set::broadcast-channel-messages: value should be 'auto', 'always' or 'never'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "allowed-channelchars")) { CheckNull(cep); if (!allowed_channelchars_strtoval(cep->ce_vardata)) { config_error("%s:%i: set::allowed-channelchars: value should be one of: 'ascii', 'utf8' or 'any'", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "hide-idle-time")) { for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { CheckNull(cepp); if (!strcmp(cepp->ce_varname, "policy")) { if (!hideidletime_strtoval(cepp->ce_vardata)) { config_error("%s:%i: set::hide-idle-time::policy: value should be one of: 'never', 'always', 'usermode' or 'oper-usermode'", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "set::hide-idle-time", cepp->ce_varname); errors++; continue; } } } else { int used = 0; for (h = Hooks[HOOKTYPE_CONFIGTEST]; h; h = h->next) { int value, errs = 0; if (h->owner && !(h->owner->flags & MODFLAG_TESTING) && !(h->owner->options & MOD_OPT_PERM)) continue; value = (*(h->func.intfunc))(conf,cep,CONFIG_SET, &errs); if (value == 2) used = 1; if (value == 1) { used = 1; break; } if (value == -1) { used = 1; errors += errs; break; } if (value == -2) { used = 1; errors += errs; } } if (!used) { config_error("%s:%i: unknown directive set::%s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname); errors++; } } } return errors; } int _conf_loadmodule(ConfigFile *conf, ConfigEntry *ce) { char *ret; if (!ce->ce_vardata) { config_status("%s:%i: loadmodule without filename", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return -1; } if (strstr(ce->ce_vardata, "commands.so") || strstr(ce->ce_vardata, "commands.dll")) { config_error("%s:%i: You are trying to load the 'commands' module, this is no longer supported. " "Fix this by editing your configuration file: remove the loadmodule line for commands and add the following line instead: " "include \"modules.default.conf\";", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); need_34_upgrade = 1; return -1; } if (strstr(ce->ce_vardata, "modules/cloak") && !strcmp(conf->cf_filename, "modules.conf")) { config_error("You seem to have an include for 'modules.conf'."); config_error("If you have this because you are upgrading from 3.4-alpha3 to"); config_error("UnrealIRCd 4 then please change the include \"modules.conf\";"); config_error("into an include \"modules.default.conf\"; (probably in your"); config_error("conf/unrealircd.conf). Yeah, we changed the file name."); // TODO ^: silly win32 wrapping prevents this from being displayed otherwise. PLZ FIX! ! /* let it continue to load anyway? */ } if (is_blacklisted_module(ce->ce_vardata)) { /* config_warn("%s:%i: Module '%s' is blacklisted, not loading", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); */ return 1; } if ((ret = Module_Create(ce->ce_vardata))) { config_status("%s:%i: loadmodule %s: failed to load: %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata, ret); return -1; } return 1; } int _test_loadmodule(ConfigFile *conf, ConfigEntry *ce) { return 0; } int _test_blacklist_module(ConfigFile *conf, ConfigEntry *ce) { char *path; ConfigItem_blacklist_module *m; if (!ce->ce_vardata) { config_status("%s:%i: blacklist-module: no module name given to blacklist", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return -1; } path = Module_TransformPath(ce->ce_vardata); /* Is it a good idea to warn about this? * Yes, the user may have made a typo, thinking (s)he blacklisted something * but due to the typo the blacklist-module is not effective. * No, the user may have blacklisted a bunch of modules of which not all may * be installed at the time. * Hmmmmmm. */ if (!file_exists(path)) { config_warn("%s:%i: blacklist-module for '%s' but module does not exist anyway", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); /* fallthrough */ } m = safe_alloc(sizeof(ConfigItem_blacklist_module)); safe_strdup(m->name, ce->ce_vardata); AddListItem(m, conf_blacklist_module); return 0; } int is_blacklisted_module(char *name) { char *path = Module_TransformPath(name); ConfigItem_blacklist_module *m; for (m = conf_blacklist_module; m; m = m->next) if (!strcasecmp(m->name, name) || !strcasecmp(m->name, path)) return 1; return 0; } void start_listeners(void) { ConfigItem_listen *listenptr; int failed = 0, ports_bound = 0; char boundmsg_ipv4[512], boundmsg_ipv6[512]; *boundmsg_ipv4 = *boundmsg_ipv6 = '\0'; for (listenptr = conf_listen; listenptr; listenptr = listenptr->next) { /* Try to bind to any ports that are not yet bound and not marked as temporary */ if (!(listenptr->options & LISTENER_BOUND) && !listenptr->flag.temporary) { if (add_listener(listenptr) == -1) { ircd_log(LOG_ERROR, "Failed to bind to %s:%i", listenptr->ip, listenptr->port); failed = 1; } else { if (loop.ircd_booted) { ircd_log(LOG_ERROR, "UnrealIRCd is now also listening on %s:%d (%s)%s", listenptr->ip, listenptr->port, listenptr->ipv6 ? "IPv6" : "IPv4", listenptr->options & LISTENER_TLS ? " (SSL/TLS)" : ""); } else { if (listenptr->ipv6) snprintf(boundmsg_ipv6+strlen(boundmsg_ipv6), sizeof(boundmsg_ipv6)-strlen(boundmsg_ipv6), "%s:%d%s, ", listenptr->ip, listenptr->port, listenptr->options & LISTENER_TLS ? "(SSL/TLS)" : ""); else snprintf(boundmsg_ipv4+strlen(boundmsg_ipv4), sizeof(boundmsg_ipv4)-strlen(boundmsg_ipv4), "%s:%d%s, ", listenptr->ip, listenptr->port, listenptr->options & LISTENER_TLS ? "(SSL/TLS)" : ""); } } } /* NOTE: do not merge this with code above (nor in an else block), * as add_listener() affects this flag. */ if (listenptr->options & LISTENER_BOUND) ports_bound++; } if (ports_bound == 0) { ircd_log(LOG_ERROR, "IRCd could not listen on any ports. If you see 'Address already in use' errors " "above then most likely the IRCd is already running (or something else is using the " "specified ports). If you are sure the IRCd is not running then verify your " "listen blocks, maybe you have to bind to a specific IP rather than \"*\"."); exit(-1); } if (failed && !loop.ircd_booted) { ircd_log(LOG_ERROR, "Could not listen on all specified addresses/ports. See errors above. " "Please fix your listen { } blocks and/or make sure no other programs " "are listening on the same port."); exit(-1); } if (!loop.ircd_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'; ircd_log(LOG_ERROR, "UnrealIRCd is now listening on the following addresses/ports:"); ircd_log(LOG_ERROR, "IPv4: %s", *boundmsg_ipv4 ? boundmsg_ipv4 : ""); ircd_log(LOG_ERROR, "IPv6: %s", *boundmsg_ipv6 ? boundmsg_ipv6 : ""); } } /* Actually use configuration */ void run_configuration(void) { start_listeners(); } int _conf_offchans(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep, *cepp; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { ConfigItem_offchans *of = safe_alloc(sizeof(ConfigItem_offchans)); strlcpy(of->chname, cep->ce_varname, CHANNELLEN+1); for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "topic")) safe_strdup(of->topic, cepp->ce_vardata); } AddListItem(of, conf_offchans); } return 0; } int _test_offchans(ConfigFile *conf, ConfigEntry *ce) { int errors = 0; ConfigEntry *cep, *cep2; if (!ce->ce_entries) { config_error("%s:%i: empty official-channels block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } config_warn("set::oficial-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->ce_entries; cep; cep = cep->ce_next) { if (strlen(cep->ce_varname) > CHANNELLEN) { config_error("%s:%i: official-channels: '%s' name too long (max %d characters).", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname, CHANNELLEN); errors++; continue; } if (!valid_channelname(cep->ce_varname)) { config_error("%s:%i: official-channels: '%s' is not a valid channel name.", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_varname); errors++; continue; } for (cep2 = cep->ce_entries; cep2; cep2 = cep2->ce_next) { if (!cep2->ce_vardata) { config_error_empty(cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, "official-channels", cep2->ce_varname); errors++; continue; } if (!strcmp(cep2->ce_varname, "topic")) { if (strlen(cep2->ce_vardata) > MAXTOPICLEN) { config_error("%s:%i: official-channels::%s: topic too long (max %d characters).", cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, cep->ce_varname, MAXTOPICLEN); errors++; continue; } } else { config_error_unknown(cep2->ce_fileptr->cf_filename, cep2->ce_varlinenum, "official-channels", cep2->ce_varname); 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->ce_vardata, CMD_ALIAS))) CommandDelX(NULL, cmptr); if (find_command_simple(ce->ce_vardata)) { config_warn("%s:%i: Alias '%s' would conflict with command (or server token) '%s', alias not added.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata, ce->ce_vardata); return 0; } if ((alias = find_alias(ce->ce_vardata))) DelListItem(alias, conf_alias); alias = safe_alloc(sizeof(ConfigItem_alias)); safe_strdup(alias->alias, ce->ce_vardata); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "format")) { format = safe_alloc(sizeof(ConfigItem_alias_format)); safe_strdup(format->format, cep->ce_vardata); format->expr = unreal_create_match(MATCH_PCRE_REGEX, cep->ce_vardata, NULL); if (!format->expr) abort(); /* Impossible due to _test_alias earlier */ for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (!strcmp(cepp->ce_varname, "nick") || !strcmp(cepp->ce_varname, "target") || !strcmp(cepp->ce_varname, "command")) { safe_strdup(format->nick, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "parameters")) { safe_strdup(format->parameters, cepp->ce_vardata); } else if (!strcmp(cepp->ce_varname, "type")) { if (!strcmp(cepp->ce_vardata, "services")) format->type = ALIAS_SERVICES; else if (!strcmp(cepp->ce_vardata, "stats")) format->type = ALIAS_STATS; else if (!strcmp(cepp->ce_vardata, "normal")) format->type = ALIAS_NORMAL; else if (!strcmp(cepp->ce_vardata, "channel")) format->type = ALIAS_CHANNEL; else if (!strcmp(cepp->ce_vardata, "real")) format->type = ALIAS_REAL; } } AddListItem(format, alias->format); } else if (!strcmp(cep->ce_varname, "nick") || !strcmp(cep->ce_varname, "target")) { safe_strdup(alias->nick, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "type")) { if (!strcmp(cep->ce_vardata, "services")) alias->type = ALIAS_SERVICES; else if (!strcmp(cep->ce_vardata, "stats")) alias->type = ALIAS_STATS; else if (!strcmp(cep->ce_vardata, "normal")) alias->type = ALIAS_NORMAL; else if (!strcmp(cep->ce_vardata, "channel")) alias->type = ALIAS_CHANNEL; else if (!strcmp(cep->ce_vardata, "command")) alias->type = ALIAS_COMMAND; } else if (!strcmp(cep->ce_varname, "spamfilter")) alias->spamfilter = config_checkval(cep->ce_vardata, 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->ce_entries) { config_error("%s:%i: empty alias block", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } if (!ce->ce_vardata) { config_error("%s:%i: alias without name", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } else if (!find_command(ce->ce_vardata, CMD_ALIAS) && find_command(ce->ce_vardata, 0)) { config_status("%s:%i: %s is an existing command, can not add alias", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); errors++; } for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "alias")) { errors++; continue; } if (!strcmp(cep->ce_varname, "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->ce_vardata, &err); if (!expr) { config_error("%s:%i: alias::format contains an invalid regex: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, err); config_error("Upgrading from 3.2.x to UnrealIRCd 4? Note that regex changed from POSIX Regex " "to PCRE Regex!"); /* TODO: refer to some url ? */ } else { unreal_delete_match(expr); } for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next) { if (config_is_blankorempty(cepp, "alias::format")) { errors++; continue; } if (!strcmp(cepp->ce_varname, "nick") || !strcmp(cepp->ce_varname, "command") || !strcmp(cepp->ce_varname, "target")) { if (has_target) { config_warn_duplicate(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "alias::format::target"); continue; } has_target = 1; } else if (!strcmp(cepp->ce_varname, "type")) { if (has_type) { config_warn_duplicate(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "alias::format::type"); continue; } has_type = 1; if (!strcmp(cepp->ce_vardata, "services")) ; else if (!strcmp(cepp->ce_vardata, "stats")) ; else if (!strcmp(cepp->ce_vardata, "normal")) ; else if (!strcmp(cepp->ce_vardata, "channel")) ; else if (!strcmp(cepp->ce_vardata, "real")) ; else { config_error("%s:%i: unknown alias type", cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum); errors++; } } else if (!strcmp(cepp->ce_varname, "parameters")) { if (has_parameters) { config_warn_duplicate(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "alias::format::parameters"); continue; } has_parameters = 1; } else { config_error_unknown(cepp->ce_fileptr->cf_filename, cepp->ce_varlinenum, "alias::format", cepp->ce_varname); errors++; } } if (!has_target) { config_error_missing(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "alias::format::target"); errors++; } if (!has_type) { config_error_missing(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "alias::format::type"); errors++; } if (!has_parameters) { config_error_missing(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "alias::format::parameters"); errors++; } } else if (!strcmp(cep->ce_varname, "nick") || !strcmp(cep->ce_varname, "target")) { if (has_target) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "alias::target"); continue; } has_target = 1; } else if (!strcmp(cep->ce_varname, "type")) { if (has_type) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "alias::type"); continue; } has_type = 1; if (!strcmp(cep->ce_vardata, "services")) ; else if (!strcmp(cep->ce_vardata, "stats")) ; else if (!strcmp(cep->ce_vardata, "normal")) ; else if (!strcmp(cep->ce_vardata, "channel")) ; else if (!strcmp(cep->ce_vardata, "command")) type = 'c'; else { config_error("%s:%i: unknown alias type", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else if (!strcmp(cep->ce_varname, "spamfilter")) ; else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "alias", cep->ce_varname); errors++; } } if (!has_type) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "alias::type"); errors++; } if (!has_format && type == 'c') { config_error("%s:%d: alias::type is 'command' but no alias::format was specified", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } else if (has_format && type != 'c') { config_error("%s:%d: alias::format specified when type is not 'command'", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); errors++; } return errors; } int _conf_deny(ConfigFile *conf, ConfigEntry *ce) { Hook *h; if (!strcmp(ce->ce_vardata, "channel")) _conf_deny_channel(conf, ce); else if (!strcmp(ce->ce_vardata, "link")) _conf_deny_link(conf, ce); else if (!strcmp(ce->ce_vardata, "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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "channel")) { safe_strdup(deny->channel, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "redirect")) { safe_strdup(deny->redirect, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "reason")) { safe_strdup(deny->reason, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "warn")) { deny->warn = config_checkval(cep->ce_vardata,CFG_YESNO); } else if (!strcmp(cep->ce_varname, "class")) { safe_strdup(deny->class, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "mask")) { unreal_add_masks(&deny->mask, cep); } } AddListItem(deny, conf_deny_channel); return 0; } int _conf_deny_link(ConfigFile *conf, ConfigEntry *ce) { ConfigItem_deny_link *deny = NULL; ConfigEntry *cep; deny = safe_alloc(sizeof(ConfigItem_deny_link)); for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "mask")) { safe_strdup(deny->mask, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "rule")) { deny->rule = (char *)crule_parse(cep->ce_vardata); safe_strdup(deny->prettyrule, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "type")) { if (!strcmp(cep->ce_vardata, "all")) deny->flag.type = CRULE_ALL; else if (!strcmp(cep->ce_vardata, "auto")) deny->flag.type = CRULE_AUTO; } } AddListItem(deny, conf_deny_link); 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->ce_entries; cep; cep = cep->ce_next) { if (!strcmp(cep->ce_varname, "mask")) { safe_strdup(deny->mask, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "version")) { safe_strdup(deny->version, cep->ce_vardata); } else if (!strcmp(cep->ce_varname, "flags")) { safe_strdup(deny->flags, cep->ce_vardata); } } AddListItem(deny, conf_deny_version); return 0; } int _test_deny(ConfigFile *conf, ConfigEntry *ce) { ConfigEntry *cep; int errors = 0; Hook *h; if (!ce->ce_vardata) { config_error("%s:%i: deny without type", ce->ce_fileptr->cf_filename, ce->ce_varlinenum); return 1; } if (!strcmp(ce->ce_vardata, "channel")) { char has_channel = 0, has_warn = 0, has_reason = 0, has_redirect = 0, has_class = 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "deny channel")) { errors++; continue; } if (!strcmp(cep->ce_varname, "channel")) { if (has_channel) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny channel::channel"); continue; } has_channel = 1; } else if (!strcmp(cep->ce_varname, "redirect")) { if (has_redirect) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny channel::redirect"); continue; } has_redirect = 1; } else if (!strcmp(cep->ce_varname, "reason")) { if (has_reason) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny channel::reason"); continue; } has_reason = 1; } else if (!strcmp(cep->ce_varname, "warn")) { if (has_warn) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny channel::warn"); continue; } has_warn = 1; } else if (!strcmp(cep->ce_varname, "class")) { if (has_class) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny channel::class"); continue; } has_class = 1; } else if (!strcmp(cep->ce_varname, "mask")) { } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny channel", cep->ce_varname); errors++; } } if (!has_channel) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "deny channel::channel"); errors++; } if (!has_reason) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "deny channel::reason"); errors++; } } else if (!strcmp(ce->ce_vardata, "link")) { char has_mask = 0, has_rule = 0, has_type = 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "deny link")) { errors++; continue; } if (!strcmp(cep->ce_varname, "mask")) { if (has_mask) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny link::mask"); continue; } has_mask = 1; } else if (!strcmp(cep->ce_varname, "rule")) { int val = 0; if (has_rule) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny link::rule"); continue; } has_rule = 1; if ((val = crule_test(cep->ce_vardata))) { config_error("%s:%i: deny link::rule contains an invalid expression: %s", cep->ce_fileptr->cf_filename, cep->ce_varlinenum, crule_errstring(val)); errors++; } } else if (!strcmp(cep->ce_varname, "type")) { if (has_type) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny link::type"); continue; } has_type = 1; if (!strcmp(cep->ce_vardata, "auto")) ; else if (!strcmp(cep->ce_vardata, "all")) ; else { config_status("%s:%i: unknown deny link type", cep->ce_fileptr->cf_filename, cep->ce_varlinenum); errors++; } } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny link", cep->ce_varname); errors++; } } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "deny link::mask"); errors++; } if (!has_rule) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "deny link::rule"); errors++; } if (!has_type) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "deny link::type"); errors++; } } else if (!strcmp(ce->ce_vardata, "version")) { char has_mask = 0, has_version = 0, has_flags = 0; for (cep = ce->ce_entries; cep; cep = cep->ce_next) { if (config_is_blankorempty(cep, "deny version")) { errors++; continue; } if (!strcmp(cep->ce_varname, "mask")) { if (has_mask) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny version::mask"); continue; } has_mask = 1; } else if (!strcmp(cep->ce_varname, "version")) { if (has_version) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny version::version"); continue; } has_version = 1; } else if (!strcmp(cep->ce_varname, "flags")) { if (has_flags) { config_warn_duplicate(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny version::flags"); continue; } has_flags = 1; } else { config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum, "deny version", cep->ce_varname); errors++; } } if (!has_mask) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "deny version::mask"); errors++; } if (!has_version) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "deny version::version"); errors++; } if (!has_flags) { config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum, "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->ce_fileptr->cf_filename, ce->ce_varlinenum, ce->ce_vardata); return 1; } return errors; } return errors; } #ifdef USE_LIBCURL static void conf_download_complete(const char *url, const char *file, const char *errorbuf, int cached, void *inc_key) { ConfigItem_include *inc; if (!loop.ircd_rehashing) return; /* use inc_key to find the correct include block. This should be cheaper than using the full URL. */ for (inc = conf_include; inc; inc = inc->next) { if ( inc_key != (void *)inc ) continue; if (!(inc->flag.type & INCLUDE_REMOTE)) continue; if (inc->flag.type & INCLUDE_NOTLOADED) continue; if (strcasecmp(url, inc->url)) continue; inc->flag.type &= ~INCLUDE_DLQUEUED; break; } if (!inc) { ircd_log(LOG_ERROR, "Downloaded remote include which matches no include statement."); return; } if (!file && !cached) update_remote_include(inc, file, 0, errorbuf); /* DOWNLOAD FAILED */ else { char *urlfile = url_getfilename(url); char *file_basename = unreal_getfilename(urlfile); char *tmp = unreal_mktemp(TMPDIR, file_basename ? file_basename : "download.conf"); safe_free(urlfile); if (cached) { unreal_copyfileex(inc->file, tmp, 1); unreal_copyfileex(inc->file, unreal_mkcache(url), 0); update_remote_include(inc, tmp, 0, NULL); } else { /* copy/hardlink file to another file because our caller will remove(file). */ unreal_copyfileex(file, tmp, 1); update_remote_include(inc, tmp, 0, NULL); unreal_copyfileex(file, unreal_mkcache(url), 0); } } for (inc = conf_include; inc; inc = inc->next) { if (inc->flag.type & INCLUDE_DLQUEUED) return; } rehash_internal(loop.rehash_save_client, loop.rehash_save_sig); } #endif int rehash(Client *client, int sig) { #ifdef USE_LIBCURL ConfigItem_include *inc; char found_remote = 0; if (loop.ircd_rehashing) { if (!sig) sendnotice(client, "A rehash is already in progress"); return 0; } /* Log who or what did the rehash: */ if (sig) { ircd_log(LOG_ERROR, "Rehashing configuration file (SIGHUP signal received)"); } else if (client && client->user) { ircd_log(LOG_ERROR, "Rehashing configuration file (requested by %s!%s@%s)", client->name, client->user->username, client->user->realhost); } else if (client) { ircd_log(LOG_ERROR, "Rehashing configuration file (requested by %s)", client->name); } loop.ircd_rehashing = 1; loop.rehash_save_client = client; loop.rehash_save_sig = sig; for (inc = conf_include; inc; inc = inc->next) { time_t modtime; if (!(inc->flag.type & INCLUDE_REMOTE)) continue; if (inc->flag.type & INCLUDE_NOTLOADED) continue; found_remote = 1; modtime = unreal_getfilemodtime(inc->file); inc->flag.type |= INCLUDE_DLQUEUED; /* use (void *)inc as the key for finding which include block conf_download_complete() should use. */ download_file_async(inc->url, modtime, conf_download_complete, (void *)inc); } if (!found_remote) return rehash_internal(client, sig); return 0; #else loop.ircd_rehashing = 1; return rehash_internal(client, sig); #endif } int rehash_internal(Client *client, int sig) { if (sig == 1) sendto_ops("Got signal SIGHUP, reloading %s file", configfile); loop.ircd_rehashing = 1; /* double checking.. */ if (init_conf(configfile, 1) == 0) run_configuration(); if (sig == 1) reread_motdsandrules(); unload_all_unused_snomasks(); unload_all_unused_umodes(); unload_all_unused_extcmodes(); unload_all_unused_caps(); unload_all_unused_history_backends(); // unload_all_unused_moddata(); -- this will crash extcmodes_check_for_changes(); umodes_check_for_changes(); charsys_check_for_changes(); loop.ircd_rehashing = 0; remote_rehash_client = NULL; return 1; } void link_cleanup(ConfigItem_link *link_ptr) { safe_free(link_ptr->servername); unreal_delete_masks(link_ptr->incoming.mask); Auth_FreeAuthConfig(link_ptr->auth); 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) { Debug((DEBUG_ERROR, "delete_linkblock: deleting %s, refcount=%d", link_ptr->servername, link_ptr->refcount)); 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) { Debug((DEBUG_ERROR, "delete_classblock: deleting %s, clients=%d, xrefcount=%d", class_ptr->name, class_ptr->clients, class_ptr->xrefcount)); safe_free(class_ptr->name); DelListItem(class_ptr, conf_class); safe_free(class_ptr); } void listen_cleanup() { int i = 0; ConfigItem_listen *listen_ptr, *next; for (listen_ptr = conf_listen; listen_ptr; listen_ptr = next) { next = listen_ptr->next; if (listen_ptr->flag.temporary && !listen_ptr->clients) { safe_free(listen_ptr->ip); free_tls_options(listen_ptr->tls_options); DelListItem(listen_ptr, conf_listen); safe_free(listen_ptr); i++; } } if (i) close_unbound_listeners(); } #ifdef USE_LIBCURL char *find_remote_include(char *url, char **errorbuf) { ConfigItem_include *inc; for (inc = conf_include; inc; inc = inc->next) { if (!(inc->flag.type & INCLUDE_NOTLOADED)) continue; if (!(inc->flag.type & INCLUDE_REMOTE)) continue; if (!strcasecmp(url, inc->url)) { *errorbuf = inc->errorbuf; return inc->file; } } return NULL; } char *find_loaded_remote_include(char *url) { ConfigItem_include *inc; for (inc = conf_include; inc; inc = inc->next) { if ((inc->flag.type & INCLUDE_NOTLOADED)) continue; if (!(inc->flag.type & INCLUDE_REMOTE)) continue; if (!strcasecmp(url, inc->url)) return inc->file; } return NULL; } /** * Non-asynchronous remote inclusion to give a user better feedback * when first starting his IRCd. * * The asynchronous friend is rehash() which merely queues remote * includes for download using download_file_async(). */ int remote_include(ConfigEntry *ce) { char *errorbuf = NULL; char *url = ce->ce_vardata; char *file = find_remote_include(url, &errorbuf); int ret; if (!loop.ircd_rehashing || (loop.ircd_rehashing && !file && !errorbuf)) { char *error; if (config_verbose > 0) config_status("Downloading %s", displayurl(url)); file = download_file(url, &error); if (!file) { if (has_cached_version(url)) { config_warn("%s:%i: include: error downloading '%s': %s -- using cached version instead.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, displayurl(url), error); safe_strdup(file, unreal_mkcache(url)); /* Let it pass to load_conf()... */ } else { config_error("%s:%i: include: error downloading '%s': %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, displayurl(url), error); return -1; } } else { unreal_copyfileex(file, unreal_mkcache(url), 0); } add_remote_include(file, url, 0, NULL, ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(file, url); safe_free(file); return ret; } else { if (errorbuf) { if (has_cached_version(url)) { config_warn("%s:%i: include: error downloading '%s': %s -- using cached version instead.", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, displayurl(url), errorbuf); /* Let it pass to load_conf()... */ safe_strdup(file, unreal_mkcache(url)); } else { config_error("%s:%i: include: error downloading '%s': %s", ce->ce_fileptr->cf_filename, ce->ce_varlinenum, displayurl(url), errorbuf); return -1; } } if (config_verbose > 0) config_status("Loading %s from download", url); add_remote_include(file, url, 0, NULL, ce->ce_fileptr->cf_filename, ce->ce_varlinenum); ret = load_conf(file, url); return ret; } return 0; } #endif /** * Add an item to the conf_include list for the specified file. * * Checks for whether or not we're performing recursive includes * belong in conf_load() because that function is able to return an * error code. Any checks in here will end up being ignored by callers * and thus will gain us nothing. * * @param file path to the include file. */ void add_include(const char *file, const char *included_from, int included_from_line) { ConfigItem_include *inc; inc = safe_alloc(sizeof(ConfigItem_include)); safe_strdup(inc->file, file); inc->flag.type = INCLUDE_NOTLOADED; safe_strdup(inc->included_from, included_from); inc->included_from_line = included_from_line; AddListItem(inc, conf_include); } #ifdef USE_LIBCURL /** * Adds a remote include entry to the config_include list. * * This is to be called whenever the included_from and * included_from_line parameters are known. This means that during a * rehash when downloads are done asynchronously, you call this with * the inclued_from and included_from_line information. After the * download is complete and you know there it is stored in the FS, * call update_remote_include(). */ void add_remote_include(const char *file, const char *url, int flags, const char *errorbuf, const char *included_from, int included_from_line) { ConfigItem_include *inc; /* we rely on safe_alloc() zeroing the ConfigItem_include */ inc = safe_alloc(sizeof(ConfigItem_include)); if (included_from) { safe_strdup(inc->included_from, included_from); inc->included_from_line = included_from_line; } safe_strdup(inc->url, url); update_remote_include(inc, file, INCLUDE_NOTLOADED|INCLUDE_REMOTE|flags, errorbuf); AddListItem(inc, conf_include); } /** * Update certain information in a remote include's config_include list entry. * * @param file the place on disk where the downloaded remote include * may be found * @param flags additional flags to set on the config_include entry * @param errorbuf non-NULL if there were errors encountered in * downloading. The error will be stored into the config_include * entry. */ void update_remote_include(ConfigItem_include *inc, const char *file, int flags, const char *errorbuf) { /* * file may be NULL when errorbuf is non-NULL and vice-versa. */ if (file) safe_strdup(inc->file, file); inc->flag.type |= flags; if (errorbuf) safe_strdup(inc->errorbuf, errorbuf); } #endif /** * Clean up conf_include after a rehash fails because of a * configuration file error. * * Duplicates some in unload_loaded_include(). */ void unload_notloaded_includes(void) { ConfigItem_include *inc, *next; for (inc = conf_include; inc; inc = next) { next = inc->next; if ((inc->flag.type & INCLUDE_NOTLOADED) || !(inc->flag.type & INCLUDE_USED)) { #ifdef USE_LIBCURL if (inc->flag.type & INCLUDE_REMOTE) { /* Delete the file, but only if it's not a cached version */ if (strncmp(inc->file, CACHEDIR, strlen(CACHEDIR))) { remove(inc->file); } safe_free(inc->url); safe_free(inc->errorbuf); } #endif safe_free(inc->file); safe_free(inc->included_from); DelListItem(inc, conf_include); safe_free(inc); } } } /** * Clean up conf_include after a successful rehash to make way for * load_includes(). */ void unload_loaded_includes(void) { ConfigItem_include *inc, *next; for (inc = conf_include; inc; inc = next) { next = inc->next; if (!(inc->flag.type & INCLUDE_NOTLOADED) || !(inc->flag.type & INCLUDE_USED)) { #ifdef USE_LIBCURL if (inc->flag.type & INCLUDE_REMOTE) { /* Delete the file, but only if it's not a cached version */ if (strncmp(inc->file, CACHEDIR, strlen(CACHEDIR))) { remove(inc->file); } safe_free(inc->url); safe_free(inc->errorbuf); } #endif safe_free(inc->file); safe_free(inc->included_from); DelListItem(inc, conf_include); safe_free(inc); } } } /** * Mark loaded includes as loaded by removing the INCLUDE_NOTLOADED * flag. Meant to be called only after calling * unload_loaded_includes(). */ void load_includes(void) { ConfigItem_include *inc; /* Doing this for all the includes should actually be faster * than only doing it for includes that are not-loaded */ for (inc = conf_include; inc; inc = inc->next) inc->flag.type &= ~INCLUDE_NOTLOADED; } int tls_tests(void) { if (have_tls_listeners == 0) { config_error("Your server is not listening on any SSL/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#Your_server_is_not_listening_on_any_SSL_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; } 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; 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.\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 SSL/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" " }\n" " password \"%s\" { spkifp; }\n" " class servers;\n" "}\n", conf_me->name, ip ? ip : conf_me->name, port, spkifp); printf("################################################################################\n"); exit(0); }