unrealircd

- supernets unrealircd source & configuration
git clone git://git.acid.vegas/unrealircd.git
Log | Files | Refs | Archive | README | LICENSE

tkl.c (157703B)

      1 /*
      2  * Unreal Internet Relay Chat Daemon, src/modules/tkl.c
      3  * TKL Commands: server bans, spamfilters, etc.
      4  * (C) 1999-2019 Bram Matthys and The UnrealIRCd Team
      5  *
      6  * See file AUTHORS in IRC package for additional names of
      7  * the programmers.
      8  *
      9  * This program is free software; you can redistribute it and/or modify
     10  * it under the terms of the GNU General Public License as published by
     11  * the Free Software Foundation; either version 1, or (at your option)
     12  * any later version.
     13  *
     14  * This program is distributed in the hope that it will be useful,
     15  * but WITHOUT ANY WARRANTY; without even the implied warranty of
     16  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
     17  * GNU General Public License for more details.
     18  *
     19  * You should have received a copy of the GNU General Public License
     20  * along with this program; if not, write to the Free Software
     21  * Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
     22  */
     23 
     24 #include "unrealircd.h"
     25 
     26 ModuleHeader MOD_HEADER
     27 = {
     28 	"tkl",
     29 	"5.0",
     30 	"Server ban commands such as /GLINE, /SPAMFILTER, etc.",
     31 	"UnrealIRCd Team",
     32 	"unrealircd-6",
     33 };
     34 
     35 /* Forward declarations */
     36 int tkl_config_test_spamfilter(ConfigFile *, ConfigEntry *, int, int *);
     37 int tkl_config_run_spamfilter(ConfigFile *, ConfigEntry *, int);
     38 int tkl_config_test_ban(ConfigFile *, ConfigEntry *, int, int *);
     39 int tkl_config_run_ban(ConfigFile *, ConfigEntry *, int);
     40 int tkl_config_test_except(ConfigFile *, ConfigEntry *, int, int *);
     41 int tkl_config_run_except(ConfigFile *, ConfigEntry *, int);
     42 int tkl_config_test_set(ConfigFile *, ConfigEntry *, int, int *);
     43 int tkl_config_run_set(ConfigFile *, ConfigEntry *, int);
     44 int tkl_ip_change(Client *client, const char *oldip);
     45 int tkl_accept(Client *client);
     46 void check_set_spamfilter_utf8_setting_changed(void);
     47 CMD_FUNC(cmd_gline);
     48 CMD_FUNC(cmd_shun);
     49 CMD_FUNC(cmd_tempshun);
     50 CMD_FUNC(cmd_gzline);
     51 CMD_FUNC(cmd_kline);
     52 CMD_FUNC(cmd_zline);
     53 CMD_FUNC(cmd_spamfilter);
     54 CMD_FUNC(cmd_eline);
     55 void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type);
     56 int _tkl_hash(unsigned int c);
     57 char _tkl_typetochar(int type);
     58 int _tkl_chartotype(char c);
     59 char _tkl_configtypetochar(const char *name);
     60 int tkl_banexception_chartotype(char c);
     61 char *_tkl_type_string(TKL *tk);
     62 char *_tkl_type_config_string(TKL *tk);
     63 char *tkl_banexception_configname_to_chars(char *name);
     64 TKL *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, char *set_by,
     65                             time_t expire_at, time_t set_at, int soft, int flags);
     66 TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match,
     67                            char *reason, char *set_by,
     68                            time_t expire_at, time_t set_at, int soft, char *bantypes, int flags);
     69 TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
     70                           time_t expire_at, time_t set_at, int flags);
     71 TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Match *match, char *set_by,
     72                              time_t expire_at, time_t set_at,
     73                              time_t spamf_tkl_duration, char *spamf_tkl_reason,
     74                              int flags);
     75 void _sendnotice_tkl_del(char *removed_by, TKL *tkl);
     76 void _sendnotice_tkl_add(TKL *tkl);
     77 void _free_tkl(TKL *tkl);
     78 void _tkl_del_line(TKL *tkl);
     79 static void _tkl_check_local_remove_shun(TKL *tmp);
     80 char *_tkl_uhost(TKL *tkl, char *buf, size_t buflen, int options);
     81 void tkl_expire_entry(TKL * tmp);
     82 EVENT(tkl_check_expire);
     83 int _find_tkline_match(Client *client, int skip_soft);
     84 int _find_shun(Client *client);
     85 int _find_spamfilter_user(Client *client, int flags);
     86 TKL *_find_qline(Client *client, char *nick, int *ishold);
     87 TKL *_find_tkline_match_zap(Client *client);
     88 void _tkl_stats(Client *client, int type, const char *para, int *cnt);
     89 void _tkl_sync(Client *client);
     90 CMD_FUNC(_cmd_tkl);
     91 int _place_host_ban(Client *client, BanAction action, char *reason, long duration);
     92 int _match_spamfilter(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, TKL **rettk);
     93 int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd);
     94 int check_mtag_spamfilters_present(void);
     95 int _join_viruschan(Client *client, TKL *tk, int type);
     96 void _spamfilter_build_user_string(char *buf, char *nick, Client *client);
     97 int _match_user(const char *rmask, Client *client, int options);
     98 int _unreal_match_iplist(Client *client, NameList *l);
     99 int _match_user_extended_server_ban(const char *banstr, Client *client);
    100 void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *client, const char **tkl_username, const char **tkl_hostname);
    101 int _tkl_ip_hash(char *ip);
    102 int _tkl_ip_hash_type(int type);
    103 TKL *_find_tkl_serverban(int type, char *usermask, char *hostmask, int softban);
    104 TKL *_find_tkl_banexception(int type, char *usermask, char *hostmask, int softban);
    105 TKL *_find_tkl_nameban(int type, char *name, int hold);
    106 TKL *_find_tkl_spamfilter(int type, char *match_string, BanAction action, unsigned short target);
    107 int _find_tkl_exception(int ban_type, Client *client);
    108 int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
    109 int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error);
    110 static void add_default_exempts(void);
    111 int parse_extended_server_ban(const char *mask_in, Client *client, char **error, int skip_checking, char *buf1, size_t buf1len, char *buf2, size_t buf2len);
    112 void _tkl_added(Client *client, TKL *tkl);
    113 
    114 /* Externals (only for us :D) */
    115 extern int MODVAR spamf_ugly_vchanoverride;
    116 
    117 typedef struct TKLTypeTable TKLTypeTable;
    118 struct TKLTypeTable
    119 {
    120 	char *config_name;        /**< The name as used in the configuration file */
    121 	char letter;              /**< The letter ised in the TKL S2S command */
    122 	int type;                 /**< TKL_xxx, optionally OR'ed with TKL_GLOBAL */
    123 	char *log_name;           /**< Used for logging and server notices */
    124 	unsigned tkltype:1;       /**< Is a type available in cmd_tkl() and friends */
    125 	unsigned exceptiontype:1; /**< Is a type available for exceptions */
    126 	unsigned needip:1;        /**< When using this exempt option, only IP addresses are permitted (processed before DNS/ident lookups etc) */
    127 };
    128 
    129 /** This table which defines all TKL types and TKL exception types.
    130  * If you wonder about the messy order: gline/kline/gzline/zline
    131  * are at the top for performance reasons. They make up 99% of the TKLs.
    132  *
    133  * IMPORTANT IF YOU ARE ADDING A NEW TYPE TO THIS TABLE:
    134  * - also update eline_syntax()
    135  * - update help.conf (HELPOP ELINE)
    136  * - more?
    137  */
    138 TKLTypeTable tkl_types[] = {
    139 	/* <config name> <letter> <TKL_xxx type>               <logging name> <tkl option?> <exempt option?> <need ip address?> */
    140 	{ "gline",                'G', TKL_KILL       | TKL_GLOBAL, "G-Line",               1, 1, 0 },
    141 	{ "kline",                'k', TKL_KILL,                    "K-Line",               1, 1, 0 },
    142 	{ "gzline",               'Z', TKL_ZAP        | TKL_GLOBAL, "Global Z-Line",        1, 1, 1 },
    143 	{ "zline",                'z', TKL_ZAP,                     "Z-Line",               1, 1, 1 },
    144 	{ "spamfilter",           'F', TKL_SPAMF      | TKL_GLOBAL, "Spamfilter",           1, 1, 0 },
    145 	{ "qline",                'Q', TKL_NAME       | TKL_GLOBAL, "Q-Line",               1, 1, 0 },
    146 	{ "except",               'E', TKL_EXCEPTION  | TKL_GLOBAL, "Exception",            1, 0, 0 },
    147 	{ "shun",                 's', TKL_SHUN       | TKL_GLOBAL, "Shun",                 1, 1, 0 },
    148 	{ "local-qline",          'q', TKL_NAME,                    "Local Q-Line",         1, 0, 0 },
    149 	{ "local-exception",      'e', TKL_EXCEPTION,               "Local Exception",      1, 0, 0 },
    150 	{ "local-spamfilter",     'f', TKL_SPAMF,                   "Local Spamfilter",     1, 0, 0 },
    151 	{ "blacklist",            'b', TKL_BLACKLIST,               "Blacklist",            0, 1, 1 },
    152 	{ "connect-flood",        'c', TKL_CONNECT_FLOOD,           "Connect flood",        0, 1, 1 },
    153 	{ "maxperip",             'm', TKL_MAXPERIP,                "Max-per-IP",           0, 1, 0 },
    154 	{ "handshake-data-flood", 'd', TKL_HANDSHAKE_DATA_FLOOD,    "Handshake data flood", 0, 1, 1 },
    155 	{ "antirandom",           'r', TKL_ANTIRANDOM,              "Antirandom",           0, 1, 0 },
    156 	{ "antimixedutf8",        '8', TKL_ANTIMIXEDUTF8,           "Antimixedutf8",        0, 1, 0 },
    157 	{ "ban-version",          'v', TKL_BAN_VERSION,             "Ban Version",          0, 1, 0 },
    158 	{ NULL,                   '\0', 0,                          NULL,                   0, 0, 0 },
    159 };
    160 #define ALL_VALID_EXCEPTION_TYPES "kline, gline, zline, gzline, spamfilter, shun, qline, blacklist, connect-flood, handshake-data-flood, antirandom, antimixedutf8, ban-version"
    161 
    162 /* Global variables for this module */
    163 
    164 int max_stats_matches = 1000;
    165 int mtag_spamfilters_present = 0; /**< Are any spamfilters with type SPAMF_MTAG present? */
    166 long previous_spamfilter_utf8 = 0;
    167 static int firstboot = 0;
    168 
    169 MOD_TEST()
    170 {
    171 	MARK_AS_OFFICIAL_MODULE(modinfo);
    172 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_spamfilter);
    173 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_ban);
    174 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_except);
    175 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_set);
    176 	EfunctionAdd(modinfo->handle, EFUNC_TKL_HASH, _tkl_hash);
    177 #if defined(__GNUC__)
    178 #pragma GCC diagnostic push
    179 #pragma GCC diagnostic ignored "-Wcast-function-type"
    180 #endif
    181 	EfunctionAdd(modinfo->handle, EFUNC_TKL_TYPETOCHAR, TO_INTFUNC(_tkl_typetochar));
    182 	EfunctionAdd(modinfo->handle, EFUNC_TKL_CHARTOTYPE, TO_INTFUNC(_tkl_chartotype));
    183 	EfunctionAdd(modinfo->handle, EFUNC_TKL_CONFIGTYPETOCHAR, TO_INTFUNC(_tkl_configtypetochar));
    184 #if defined(__GNUC__)
    185 #pragma GCC diagnostic pop
    186 #endif
    187 	EfunctionAddString(modinfo->handle, EFUNC_TKL_TYPE_STRING, _tkl_type_string);
    188 	EfunctionAddString(modinfo->handle, EFUNC_TKL_TYPE_CONFIG_STRING, _tkl_type_config_string);
    189 	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_SERVERBAN, TO_PVOIDFUNC(_tkl_add_serverban));
    190 	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_BANEXCEPTION, TO_PVOIDFUNC(_tkl_add_banexception));
    191 	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_NAMEBAN, TO_PVOIDFUNC(_tkl_add_nameban));
    192 	EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_SPAMFILTER, TO_PVOIDFUNC(_tkl_add_spamfilter));
    193 	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_DEL_LINE, _tkl_del_line);
    194 	EfunctionAddVoid(modinfo->handle, EFUNC_FREE_TKL, _free_tkl);
    195 	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_CHECK_LOCAL_REMOVE_SHUN, _tkl_check_local_remove_shun);
    196 	EfunctionAdd(modinfo->handle, EFUNC_FIND_TKLINE_MATCH, _find_tkline_match);
    197 	EfunctionAdd(modinfo->handle, EFUNC_FIND_SHUN, _find_shun);
    198 	EfunctionAdd(modinfo->handle, EFUNC_FIND_SPAMFILTER_USER, _find_spamfilter_user);
    199 	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_QLINE, TO_PVOIDFUNC(_find_qline));
    200 	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKLINE_MATCH_ZAP, TO_PVOIDFUNC(_find_tkline_match_zap));
    201 	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SERVERBAN, TO_PVOIDFUNC(_find_tkl_serverban));
    202 	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_BANEXCEPTION, TO_PVOIDFUNC(_find_tkl_banexception));
    203 	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_NAMEBAN, TO_PVOIDFUNC(_find_tkl_nameban));
    204 	EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SPAMFILTER, TO_PVOIDFUNC(_find_tkl_spamfilter));
    205 	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_STATS, _tkl_stats);
    206 	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_SYNCH, _tkl_sync);
    207 	EfunctionAddVoid(modinfo->handle, EFUNC_CMD_TKL, _cmd_tkl);
    208 	EfunctionAdd(modinfo->handle, EFUNC_PLACE_HOST_BAN, _place_host_ban);
    209 	EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER, _match_spamfilter);
    210 	EfunctionAdd(modinfo->handle, EFUNC_MATCH_SPAMFILTER_MTAGS, _match_spamfilter_mtags);
    211 	EfunctionAdd(modinfo->handle, EFUNC_JOIN_VIRUSCHAN, _join_viruschan);
    212 	EfunctionAddVoid(modinfo->handle, EFUNC_SPAMFILTER_BUILD_USER_STRING, _spamfilter_build_user_string);
    213 	EfunctionAdd(modinfo->handle, EFUNC_MATCH_USER, _match_user);
    214 	EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH, _tkl_ip_hash);
    215 	EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH_TYPE, _tkl_ip_hash_type);
    216 	EfunctionAddVoid(modinfo->handle, EFUNC_SENDNOTICE_TKL_ADD, _sendnotice_tkl_add);
    217 	EfunctionAddVoid(modinfo->handle, EFUNC_SENDNOTICE_TKL_DEL, _sendnotice_tkl_del);
    218 	EfunctionAdd(modinfo->handle, EFUNC_FIND_TKL_EXCEPTION, _find_tkl_exception);
    219 	EfunctionAddString(modinfo->handle, EFUNC_TKL_UHOST, _tkl_uhost);
    220 	EfunctionAdd(modinfo->handle, EFUNC_UNREAL_MATCH_IPLIST, _unreal_match_iplist);
    221 	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_PARSE_MASK, TO_INTFUNC(_server_ban_parse_mask));
    222 	EfunctionAdd(modinfo->handle, EFUNC_SERVER_BAN_EXCEPTION_PARSE_MASK, TO_INTFUNC(_server_ban_exception_parse_mask));
    223 	EfunctionAddVoid(modinfo->handle, EFUNC_TKL_ADDED, _tkl_added);
    224 	return MOD_SUCCESS;
    225 }
    226 
    227 MOD_INIT()
    228 {
    229 	MARK_AS_OFFICIAL_MODULE(modinfo);
    230 	if (loop.booted == 0)
    231 		firstboot = 1;
    232 	LoadPersistentLong(modinfo, previous_spamfilter_utf8);
    233 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_spamfilter);
    234 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
    235 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_except);
    236 	HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_set);
    237 	HookAdd(modinfo->handle, HOOKTYPE_IP_CHANGE, 2000000000, tkl_ip_change);
    238 	HookAdd(modinfo->handle, HOOKTYPE_ACCEPT, -1000, tkl_accept);
    239 	CommandAdd(modinfo->handle, "GLINE", cmd_gline, 3, CMD_OPER);
    240 	CommandAdd(modinfo->handle, "SHUN", cmd_shun, 3, CMD_OPER);
    241 	CommandAdd(modinfo->handle, "TEMPSHUN", cmd_tempshun, 2, CMD_OPER);
    242 	CommandAdd(modinfo->handle, "ZLINE", cmd_zline, 3, CMD_OPER);
    243 	CommandAdd(modinfo->handle, "KLINE", cmd_kline, 3, CMD_OPER);
    244 	CommandAdd(modinfo->handle, "GZLINE", cmd_gzline, 3, CMD_OPER);
    245 	CommandAdd(modinfo->handle, "SPAMFILTER", cmd_spamfilter, 7, CMD_OPER);
    246 	CommandAdd(modinfo->handle, "ELINE", cmd_eline, 4, CMD_OPER);
    247 	CommandAdd(modinfo->handle, "TKL", _cmd_tkl, MAXPARA, CMD_OPER|CMD_SERVER);
    248 	add_default_exempts();
    249 	return MOD_SUCCESS;
    250 }
    251 
    252 MOD_LOAD()
    253 {
    254 	check_mtag_spamfilters_present();
    255 	check_set_spamfilter_utf8_setting_changed();
    256 	EventAdd(modinfo->handle, "tklexpire", tkl_check_expire, NULL, 5000, 0);
    257 	return MOD_SUCCESS;
    258 }
    259 
    260 MOD_UNLOAD()
    261 {
    262 	SavePersistentLong(modinfo, previous_spamfilter_utf8);
    263 	return MOD_SUCCESS;
    264 }
    265 
    266 /** Test a spamfilter { } block in the configuration file */
    267 int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    268 {
    269 	ConfigEntry *cep, *cepp;
    270 	int errors = 0;
    271 	char *match = NULL, *reason = NULL;
    272 	char has_target = 0, has_match = 0, has_action = 0, has_reason = 0, has_bantime = 0, has_match_type = 0;
    273 	int match_type = 0;
    274 
    275 	/* We are only interested in spamfilter { } blocks */
    276 	if ((type != CONFIG_MAIN) || strcmp(ce->name, "spamfilter"))
    277 		return 0;
    278 
    279 	for (cep = ce->items; cep; cep = cep->next)
    280 	{
    281 		if (!strcmp(cep->name, "target"))
    282 		{
    283 			if (has_target)
    284 			{
    285 				config_warn_duplicate(cep->file->filename,
    286 					cep->line_number, "spamfilter::target");
    287 				continue;
    288 			}
    289 			has_target = 1;
    290 			if (cep->value)
    291 			{
    292 				if (!spamfilter_getconftargets(cep->value))
    293 				{
    294 					config_error("%s:%i: unknown spamfiler target type '%s'",
    295 						cep->file->filename, cep->line_number, cep->value);
    296 					errors++;
    297 				}
    298 			}
    299 			else if (cep->items)
    300 			{
    301 				for (cepp = cep->items; cepp; cepp = cepp->next)
    302 				{
    303 					if (!spamfilter_getconftargets(cepp->name))
    304 					{
    305 						config_error("%s:%i: unknown spamfiler target type '%s'",
    306 							cepp->file->filename,
    307 							cepp->line_number, cepp->name);
    308 						errors++;
    309 					}
    310 				}
    311 			}
    312 			else
    313 			{
    314 				config_error_empty(cep->file->filename,
    315 					cep->line_number, "spamfilter", cep->name);
    316 				errors++;
    317 			}
    318 			continue;
    319 		}
    320 		if (!cep->value)
    321 		{
    322 			config_error_empty(cep->file->filename, cep->line_number,
    323 				"spamfilter", cep->name);
    324 			errors++;
    325 			continue;
    326 		}
    327 		if (!strcmp(cep->name, "reason"))
    328 		{
    329 			if (has_reason)
    330 			{
    331 				config_warn_duplicate(cep->file->filename,
    332 					cep->line_number, "spamfilter::reason");
    333 				continue;
    334 			}
    335 			has_reason = 1;
    336 			reason = cep->value;
    337 		}
    338 		else if (!strcmp(cep->name, "match"))
    339 		{
    340 			if (has_match)
    341 			{
    342 				config_warn_duplicate(cep->file->filename,
    343 					cep->line_number, "spamfilter::match");
    344 				continue;
    345 			}
    346 			has_match = 1;
    347 			match = cep->value;
    348 		}
    349 		else if (!strcmp(cep->name, "action"))
    350 		{
    351 			if (has_action)
    352 			{
    353 				config_warn_duplicate(cep->file->filename,
    354 					cep->line_number, "spamfilter::action");
    355 				continue;
    356 			}
    357 			has_action = 1;
    358 			if (!banact_stringtoval(cep->value))
    359 			{
    360 				config_error("%s:%i: spamfilter::action has unknown action type '%s'",
    361 					cep->file->filename, cep->line_number, cep->value);
    362 				errors++;
    363 			}
    364 		}
    365 		else if (!strcmp(cep->name, "ban-time"))
    366 		{
    367 			if (has_bantime)
    368 			{
    369 				config_warn_duplicate(cep->file->filename,
    370 					cep->line_number, "spamfilter::ban-time");
    371 				continue;
    372 			}
    373 			has_bantime = 1;
    374 		}
    375 		else if (!strcmp(cep->name, "match-type"))
    376 		{
    377 			if (has_match_type)
    378 			{
    379 				config_warn_duplicate(cep->file->filename,
    380 					cep->line_number, "spamfilter::match-type");
    381 				continue;
    382 			}
    383 			if (!strcasecmp(cep->value, "posix"))
    384 			{
    385 				config_error("%s:%i: this spamfilter uses match-type 'posix' which is no longer supported. "
    386 				             "You must switch over to match-type 'regex' instead. "
    387 				             "See https://www.unrealircd.org/docs/FAQ#spamfilter-posix-deprecated",
    388 				             ce->file->filename, ce->line_number);
    389 				errors++;
    390 				*errs = errors;
    391 				return -1; /* return now, otherwise there will be issues */
    392 			}
    393 			match_type = unreal_match_method_strtoval(cep->value);
    394 			if (match_type == 0)
    395 			{
    396 				config_error("%s:%i: spamfilter::match-type: unknown match type '%s', "
    397 				             "should be one of: 'simple', 'regex' or 'posix'",
    398 				             cep->file->filename, cep->line_number,
    399 				             cep->value);
    400 				errors++;
    401 				continue;
    402 			}
    403 			has_match_type = 1;
    404 		}
    405 		else
    406 		{
    407 			config_error_unknown(cep->file->filename, cep->line_number,
    408 				"spamfilter", cep->name);
    409 			errors++;
    410 			continue;
    411 		}
    412 	}
    413 
    414 	if (match && match_type)
    415 	{
    416 		Match *m;
    417 		char *err;
    418 
    419 		m = unreal_create_match(match_type, match, &err);
    420 		if (!m)
    421 		{
    422 			config_error("%s:%i: spamfilter::match contains an invalid regex: %s",
    423 				ce->file->filename,
    424 				ce->line_number,
    425 				err);
    426 			errors++;
    427 		} else
    428 		{
    429 			unreal_delete_match(m);
    430 		}
    431 	}
    432 
    433 	if (!has_match)
    434 	{
    435 		config_error_missing(ce->file->filename, ce->line_number,
    436 			"spamfilter::match");
    437 		errors++;
    438 	}
    439 	if (!has_target)
    440 	{
    441 		config_error_missing(ce->file->filename, ce->line_number,
    442 			"spamfilter::target");
    443 		errors++;
    444 	}
    445 	if (!has_action)
    446 	{
    447 		config_error_missing(ce->file->filename, ce->line_number,
    448 			"spamfilter::action");
    449 		errors++;
    450 	}
    451 	if (match && reason && (strlen(match) + strlen(reason) > 505))
    452 	{
    453 		config_error("%s:%i: spamfilter block problem: match + reason field are together over 505 bytes, "
    454 		             "please choose a shorter regex or reason",
    455 		             ce->file->filename, ce->line_number);
    456 		errors++;
    457 	}
    458 	if (!has_match_type)
    459 	{
    460 		config_error_missing(ce->file->filename, ce->line_number,
    461 			"spamfilter::match-type");
    462 		errors++;
    463 	}
    464 
    465 	if (match && !strcmp(match, "^LOL! //echo -a \\$\\(\\$decode\\(.+,m\\),[0-9]\\)$"))
    466 	{
    467 		config_warn("*** IMPORTANT ***");
    468 		config_warn("You have old examples in your spamfilter.conf. "
    469 		             "We suggest you to edit this file and replace the examples.");
    470 		config_warn("Please read https://www.unrealircd.org/docs/FAQ#old-spamfilter-conf !!!");
    471 		config_warn("*****************");
    472 	}
    473 	*errs = errors;
    474 	return errors ? -1 : 1;
    475 }
    476 
    477 /** Process a spamfilter { } block in the configuration file */
    478 int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
    479 {
    480 	ConfigEntry *cep;
    481 	ConfigEntry *cepp;
    482 	char *word = NULL;
    483 	time_t bantime = tempiConf.spamfilter_ban_time;
    484 	char *banreason = tempiConf.spamfilter_ban_reason;
    485 	int action = 0, target = 0;
    486 	int match_type = 0;
    487 	Match *m;
    488 
    489 	/* We are only interested in spamfilter { } blocks */
    490 	if ((type != CONFIG_MAIN) || strcmp(ce->name, "spamfilter"))
    491 		return 0;
    492 
    493 	for (cep = ce->items; cep; cep = cep->next)
    494 	{
    495 		if (!strcmp(cep->name, "match"))
    496 		{
    497 			word = cep->value;
    498 		}
    499 		else if (!strcmp(cep->name, "target"))
    500 		{
    501 			if (cep->value)
    502 				target = spamfilter_getconftargets(cep->value);
    503 			else
    504 			{
    505 				for (cepp = cep->items; cepp; cepp = cepp->next)
    506 					target |= spamfilter_getconftargets(cepp->name);
    507 			}
    508 		}
    509 		else if (!strcmp(cep->name, "action"))
    510 		{
    511 			action = banact_stringtoval(cep->value);
    512 		}
    513 		else if (!strcmp(cep->name, "reason"))
    514 		{
    515 			banreason = cep->value;
    516 		}
    517 		else if (!strcmp(cep->name, "ban-time"))
    518 		{
    519 			bantime = config_checkval(cep->value, CFG_TIME);
    520 		}
    521 		else if (!strcmp(cep->name, "match-type"))
    522 		{
    523 			match_type = unreal_match_method_strtoval(cep->value);
    524 		}
    525 	}
    526 
    527 	m = unreal_create_match(match_type, word, NULL);
    528 	tkl_add_spamfilter(TKL_SPAMF,
    529 	                    target,
    530 	                    action,
    531 	                    m,
    532 	                    "-config-",
    533 	                    0,
    534 	                    TStime(),
    535 	                    bantime,
    536 	                    banreason,
    537 	                    TKL_FLAG_CONFIG);
    538 	return 1;
    539 }
    540 
    541 /** Test a ban { } block in the configuration file */
    542 int tkl_config_test_ban(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
    543 {
    544 	ConfigEntry *cep;
    545 	int errors = 0;
    546 	char has_mask = 0, has_reason = 0;
    547 
    548 	/* We are only interested in ban { } blocks */
    549 	if (type != CONFIG_BAN)
    550 		return 0;
    551 
    552 	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
    553 	    strcmp(ce->value, "ip"))
    554 	{
    555 		return 0;
    556 	}
    557 
    558 	for (cep = ce->items; cep; cep = cep->next)
    559 	{
    560 		if (config_is_blankorempty(cep, "ban"))
    561 		{
    562 			errors++;
    563 			continue;
    564 		}
    565 		if (!strcmp(cep->name, "mask"))
    566 		{
    567 			if (has_mask)
    568 			{
    569 				config_warn_duplicate(cep->file->filename,
    570 					cep->line_number, "ban::mask");
    571 				continue;
    572 			}
    573 			has_mask = 1;
    574 		}
    575 		else if (!strcmp(cep->name, "reason"))
    576 		{
    577 			if (has_reason)
    578 			{
    579 				config_warn_duplicate(cep->file->filename,
    580 					cep->line_number, "ban::reason");
    581 				continue;
    582 			}
    583 			has_reason = 1;
    584 		}
    585 		else
    586 		{
    587 			config_error("%s:%i: unknown directive ban %s::%s",
    588 				cep->file->filename, cep->line_number,
    589 				ce->value,
    590 				cep->name);
    591 			errors++;
    592 		}
    593 	}
    594 
    595 	if (!has_mask)
    596 	{
    597 		config_error_missing(ce->file->filename, ce->line_number,
    598 			"ban::mask");
    599 		errors++;
    600 	}
    601 
    602 	if (!has_reason)
    603 	{
    604 		config_error_missing(ce->file->filename, ce->line_number,
    605 			"ban::reason");
    606 		errors++;
    607 	}
    608 
    609 	*errs = errors;
    610 	return errors ? -1 : 1;
    611 }
    612 
    613 /** Process a ban { } block in the configuration file */
    614 int tkl_config_run_ban(ConfigFile *cf, ConfigEntry *ce, int configtype)
    615 {
    616 	ConfigEntry *cep;
    617 	char *usermask = NULL;
    618 	char *hostmask = NULL;
    619 	char *reason = NULL;
    620 	int tkltype;
    621 
    622 	/* We are only interested in ban { } blocks */
    623 	if (configtype != CONFIG_BAN)
    624 		return 0;
    625 
    626 	if (strcmp(ce->value, "nick") && strcmp(ce->value, "user") &&
    627 	    strcmp(ce->value, "ip"))
    628 	{
    629 		return 0;
    630 	}
    631 
    632 	for (cep = ce->items; cep; cep = cep->next)
    633 	{
    634 		if (!strcmp(cep->name, "mask"))
    635 		{
    636 			if (is_extended_server_ban(cep->value))
    637 			{
    638 				char mask1buf[512], mask2buf[512];
    639 				char *err = NULL;
    640 
    641 				if (!parse_extended_server_ban(cep->value, NULL, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
    642 				{
    643 					config_warn("%s:%d: Could not add extended server ban '%s': %s",
    644 						cep->file->filename, cep->line_number, cep->value, err);
    645 					goto tcrb_end;
    646 				}
    647 				safe_strdup(usermask, mask1buf);
    648 				safe_strdup(hostmask, mask2buf);
    649 			} else
    650 			{
    651 				char buf[512];
    652 				char *p;
    653 
    654 				strlcpy(buf, cep->value, sizeof(buf));
    655 				p = strchr(buf, '@');
    656 				if (p)
    657 				{
    658 					*p++ = '\0';
    659 					safe_strdup(usermask, buf);
    660 					safe_strdup(hostmask, p);
    661 				} else {
    662 					safe_strdup(hostmask, cep->value);
    663 				}
    664 			}
    665 		} else
    666 		if (!strcmp(cep->name, "reason"))
    667 		{
    668 			safe_strdup(reason, cep->value);
    669 		}
    670 	}
    671 
    672 	if (!usermask)
    673 		safe_strdup(usermask, "*");
    674 
    675 	if (!reason)
    676 		safe_strdup(reason, "-");
    677 
    678 	if (!strcmp(ce->value, "nick"))
    679 		tkltype = TKL_NAME;
    680 	else if (!strcmp(ce->value, "user"))
    681 		tkltype = TKL_KILL;
    682 	else if (!strcmp(ce->value, "ip"))
    683 		tkltype = TKL_ZAP;
    684 	else
    685 		abort(); /* impossible */
    686 
    687 	if (TKLIsNameBanType(tkltype))
    688 		tkl_add_nameban(tkltype, hostmask, 0, reason, "-config-", 0, TStime(), TKL_FLAG_CONFIG);
    689 	else if (TKLIsServerBanType(tkltype))
    690 		tkl_add_serverban(tkltype, usermask, hostmask, reason, "-config-", 0, TStime(), 0, TKL_FLAG_CONFIG);
    691 
    692 tcrb_end:
    693 	safe_free(usermask);
    694 	safe_free(hostmask);
    695 	safe_free(reason);
    696 	return 1;
    697 }
    698 
    699 int tkl_config_test_except(ConfigFile *cf, ConfigEntry *ce, int configtype, int *errs)
    700 {
    701 	ConfigEntry *cep, *cepp;
    702 	int errors = 0;
    703 	char has_mask = 0, has_match = 0;
    704 
    705 	/* We are only interested in except { } blocks */
    706 	if (configtype != CONFIG_EXCEPT)
    707 		return 0;
    708 
    709 	/* These are the types that we handle */
    710 	if (strcmp(ce->value, "ban") && strcmp(ce->value, "throttle") &&
    711 	    strcmp(ce->value, "tkl") && strcmp(ce->value, "blacklist") &&
    712 	    strcmp(ce->value, "spamfilter"))
    713 	{
    714 		return 0;
    715 	}
    716 
    717 	if (!strcmp(ce->value, "tkl"))
    718 	{
    719 		config_error("%s:%d: except tkl { } has been renamed to except ban { }",
    720 			ce->file->filename, ce->line_number);
    721 		config_status("Please rename your block in the configuration file.");
    722 		*errs = 1;
    723 		return -1;
    724 	}
    725 
    726 	for (cep = ce->items; cep; cep = cep->next)
    727 	{
    728 		if (!strcmp(cep->name, "mask"))
    729 		{
    730 			if (cep->value || cep->items)
    731 			{
    732 				has_mask = 1;
    733 				test_match_block(cf, cep, &errors);
    734 			}
    735 		} else
    736 		if (!strcmp(cep->name, "match"))
    737 		{
    738 			if (cep->value || cep->items)
    739 			{
    740 				has_match = 1;
    741 				test_match_block(cf, cep, &errors);
    742 			}
    743 		} else
    744 		if (!strcmp(cep->name, "type"))
    745 		{
    746 			if (cep->items)
    747 			{
    748 				/* type { x; y; z; }; */
    749 				for (cepp = cep->items; cepp; cepp = cepp->next)
    750 					if (!tkl_banexception_configname_to_chars(cepp->name))
    751 					{
    752 						config_error("%s:%d: except ban::type '%s' unknown. Must be one of: %s",
    753 							cepp->file->filename, cepp->line_number, cepp->name,
    754 							ALL_VALID_EXCEPTION_TYPES);
    755 						errors++;
    756 					}
    757 			} else
    758 			if (cep->value)
    759 			{
    760 				/* type x; */
    761 				if (!tkl_banexception_configname_to_chars(cep->value))
    762 				{
    763 					config_error("%s:%d: except ban::type '%s' unknown. Must be one of: %s",
    764 						cep->file->filename, cep->line_number, cep->value,
    765 						ALL_VALID_EXCEPTION_TYPES);
    766 					errors++;
    767 				}
    768 			}
    769 		} else {
    770 			config_error_unknown(cep->file->filename,
    771 				cep->line_number, "except", cep->name);
    772 			errors++;
    773 			continue;
    774 		}
    775 	}
    776 
    777 	if (!has_mask && !has_match)
    778 	{
    779 		config_error_missing(ce->file->filename, ce->line_number,
    780 			"except ban::match");
    781 		errors++;
    782 	}
    783 	if (has_mask && has_match)
    784 	{
    785 		config_error("%s:%d: You cannot have both ::mask and ::match. "
    786 		             "You should only use except::match.",
    787 		             ce->file->filename, ce->line_number);
    788 		errors++;
    789 	}
    790 
    791 	*errs = errors;
    792 	return errors ? -1 : 1;
    793 }
    794 
    795 int tkl_config_run_except(ConfigFile *cf, ConfigEntry *ce, int configtype)
    796 {
    797 	ConfigEntry *cep, *cepp;
    798 	SecurityGroup *match = NULL;
    799 	char bantypes[64];
    800 
    801 	/* We are only interested in except { } blocks */
    802 	if (configtype != CONFIG_EXCEPT)
    803 		return 0;
    804 
    805 	/* These are the types that we handle */
    806 	if (strcmp(ce->value, "ban") && strcmp(ce->value, "throttle") &&
    807 	    strcmp(ce->value, "blacklist") &&
    808 	    strcmp(ce->value, "spamfilter"))
    809 	{
    810 		return 0;
    811 	}
    812 
    813 	*bantypes = '\0';
    814 
    815 	/* First configure all the types */
    816 	for (cep = ce->items; cep; cep = cep->next)
    817 	{
    818 		if (!strcmp(cep->name, "type"))
    819 		{
    820 			if (cep->items)
    821 			{
    822 				/* type { x; y; z; }; */
    823 				for (cepp = cep->items; cepp; cepp = cepp->next)
    824 				{
    825 					char *str = tkl_banexception_configname_to_chars(cepp->name);
    826 					strlcat(bantypes, str, sizeof(bantypes));
    827 				}
    828 			} else
    829 			if (cep->value)
    830 			{
    831 				/* type x; */
    832 				char *str = tkl_banexception_configname_to_chars(cep->value);
    833 				strlcat(bantypes, str, sizeof(bantypes));
    834 			}
    835 		} else
    836 		if (!strcmp(cep->name, "match") || !strcmp(cep->name, "mask"))
    837 		{
    838 			conf_match_block(cf, cep, &match);
    839 		}
    840 	}
    841 
    842 	if (!*bantypes)
    843 	{
    844 		/* Default setting if no 'type' is specified: */
    845 		if (!strcmp(ce->value, "ban"))
    846 			strlcpy(bantypes, "kGzZs", sizeof(bantypes));
    847 		else if (!strcmp(ce->value, "throttle"))
    848 			strlcpy(bantypes, "c", sizeof(bantypes));
    849 		else if (!strcmp(ce->value, "blacklist"))
    850 			strlcpy(bantypes, "b", sizeof(bantypes));
    851 		else if (!strcmp(ce->value, "spamfilter"))
    852 			strlcpy(bantypes, "f", sizeof(bantypes));
    853 		else
    854 			abort(); /* someone can't code */
    855 	}
    856 
    857 	tkl_add_banexception(TKL_EXCEPTION, "-", "-", match, "Added in configuration file",
    858 	                     "-config-", 0, TStime(), 0, bantypes, TKL_FLAG_CONFIG);
    859 
    860 	return 1;
    861 }
    862 
    863 int tkl_config_test_set(ConfigFile *cf, ConfigEntry *ce, int configtype, int *errs)
    864 {
    865 	int errors = 0;
    866 
    867 	/* We are only interested in set { } blocks */
    868 	if (configtype != CONFIG_SET)
    869 		return 0;
    870 
    871 	if (!strcmp(ce->name, "max-stats-matches"))
    872 	{
    873 		if (!ce->value)
    874 		{
    875 			config_error("%s:%i: set::max-stats-matches: no value specified",
    876 				ce->file->filename, ce->line_number);
    877 			errors++;
    878 		}
    879 		// allow any other value, including 0 and negative.
    880 		*errs = errors;
    881 		return errors ? -1 : 1;
    882 	}
    883 	return 0;
    884 }
    885 
    886 int tkl_config_run_set(ConfigFile *cf, ConfigEntry *ce, int configtype)
    887 {
    888 	/* We are only interested in set { } blocks */
    889 	if (configtype != CONFIG_SET)
    890 		return 0;
    891 
    892 	if (!strcmp(ce->name, "max-stats-matches"))
    893 	{
    894 		max_stats_matches = atoi(ce->value);
    895 		return 1;
    896 	}
    897 
    898 	return 0;
    899 }
    900 
    901 /** Recompile all spamfilters due to set::spamfilter::utf8 setting change */
    902 void recompile_spamfilters(void)
    903 {
    904 	TKL *tkl;
    905 	Match *m;
    906 	char *err;
    907 	int converted = 0;
    908 	int index;
    909 
    910 	index = tkl_hash('F');
    911 	for (tkl = tklines[index]; tkl; tkl = tkl->next)
    912 	{
    913 		if (!TKLIsSpamfilter(tkl) || (tkl->ptr.spamfilter->match->type != MATCH_PCRE_REGEX))
    914 			continue;
    915 		m = unreal_create_match(MATCH_PCRE_REGEX, tkl->ptr.spamfilter->match->str, &err);
    916 		if (!m)
    917 		{
    918 			unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_COMPILE_ERROR", NULL,
    919 			           "Spamfilter no longer compiles upon utf8 change, error: $error. "
    920 			           "Spamfilter '$tkl' ($tkl.reason). "
    921 			           "Spamfilter not transformed to/from utf8.",
    922 			           log_data_tkl("tkl", tkl),
    923 			           log_data_string("error", err ? err : "Unknown"));
    924 			continue;
    925 		}
    926 
    927 		unreal_delete_match(tkl->ptr.spamfilter->match); /* unset old one */
    928 		tkl->ptr.spamfilter->match = m; /* set new one */
    929 		converted++;
    930 	}
    931 	unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_UTF8_CONVERTED", NULL,
    932 	           "Spamfilter: Recompiled $count spamfilters due to set::spamfilter::utf8 change.",
    933 	           log_data_integer("count", converted));
    934 }
    935 
    936 void check_set_spamfilter_utf8_setting_changed(void)
    937 {
    938 	if (firstboot)
    939 	{
    940 		/* First boot, not a rehash */
    941 		previous_spamfilter_utf8 = iConf.spamfilter_utf8;
    942 		return;
    943 	}
    944 
    945 	if (previous_spamfilter_utf8 != iConf.spamfilter_utf8)
    946 		recompile_spamfilters();
    947 
    948 	previous_spamfilter_utf8 = iConf.spamfilter_utf8;
    949 }
    950 
    951 /** Return unique spamfilter id for TKL */
    952 char *spamfilter_id(TKL *tk)
    953 {
    954 	static char buf[128];
    955 
    956 	snprintf(buf, sizeof(buf), "%p", (void *)tk);
    957 	return buf;
    958 }
    959 
    960 int tkl_ip_change(Client *client, const char *oldip)
    961 {
    962 	TKL *tkl;
    963 	if ((tkl = find_tkline_match_zap(client)))
    964 		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
    965 	return 0;
    966 }
    967 
    968 int tkl_accept(Client *client)
    969 {
    970 	TKL *tkl;
    971 	if ((tkl = find_tkline_match_zap(client)))
    972 	{
    973 		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, NO_EXIT_CLIENT);
    974 		return HOOK_DENY;
    975 	}
    976 	return 0;
    977 }
    978 
    979 /** GLINE - Global kline.
    980 ** Syntax: /gline [+|-]u@h mask time :reason
    981 **
    982 ** parv[1] = [+|-]u@h mask
    983 ** parv[2] = for how long
    984 ** parv[3] = reason
    985 */
    986 CMD_FUNC(cmd_gline)
    987 {
    988 	if (IsServer(client))
    989 		return;
    990 
    991 	if (!ValidatePermissionsForPath("server-ban:gline",client,NULL,NULL,NULL))
    992 	{
    993 		sendnumeric(client, ERR_NOPRIVILEGES);
    994 		return;
    995 	}
    996 
    997 	if (parc == 1)
    998 	{
    999 		const char *parv[3];
   1000 		parv[0] = NULL;
   1001 		parv[1] = "gline";
   1002 		parv[2] = NULL;
   1003 		do_cmd(client, recv_mtags, "STATS", 2, parv);
   1004 	} else
   1005 	{
   1006 		cmd_tkl_line(client, parc, parv, "G");
   1007 	}
   1008 }
   1009 
   1010 /** GZLINE - Global zline.
   1011  */
   1012 CMD_FUNC(cmd_gzline)
   1013 {
   1014 	if (IsServer(client))
   1015 		return;
   1016 
   1017 	if (!ValidatePermissionsForPath("server-ban:zline:global",client,NULL,NULL,NULL))
   1018 	{
   1019 		sendnumeric(client, ERR_NOPRIVILEGES);
   1020 		return;
   1021 	}
   1022 
   1023 	if (parc == 1)
   1024 	{
   1025 		const char *parv[3];
   1026 		parv[0] = NULL;
   1027 		parv[1] = "gline"; /* (there's no /STATS gzline, it's included in /STATS gline output) */
   1028 		parv[2] = NULL;
   1029 		do_cmd(client, recv_mtags, "STATS", 2, parv);
   1030 	} else {
   1031 		cmd_tkl_line(client, parc, parv, "Z");
   1032 	}
   1033 }
   1034 
   1035 /** SHUN - Shun a user so it can no longer execute any meaningful commands.
   1036  */
   1037 CMD_FUNC(cmd_shun)
   1038 {
   1039 	if (IsServer(client))
   1040 		return;
   1041 
   1042 	if (!ValidatePermissionsForPath("server-ban:shun",client,NULL,NULL,NULL))
   1043 	{
   1044 		sendnumeric(client, ERR_NOPRIVILEGES);
   1045 		return;
   1046 	}
   1047 
   1048 	if (parc == 1)
   1049 	{
   1050 		const char *parv[3];
   1051 		parv[0] = NULL;
   1052 		parv[1] = "shun";
   1053 		parv[2] = NULL;
   1054 		do_cmd(client, recv_mtags, "STATS", 2, parv);
   1055 	} else {
   1056 		cmd_tkl_line(client, parc, parv, "s");
   1057 	}
   1058 }
   1059 
   1060 /** TEMPSHUN - Temporarily shun a user so it can no longer execute
   1061  *  any meaningful commands - until the user disconnects (session only).
   1062  */
   1063 CMD_FUNC(cmd_tempshun)
   1064 {
   1065 	Client *target;
   1066 	const char *comment = ((parc > 2) && !BadPtr(parv[2])) ? parv[2] : "no reason";
   1067 	const char *name;
   1068 	int remove = 0;
   1069 
   1070 	if (MyUser(client) && (!ValidatePermissionsForPath("server-ban:shun:temporary",client,NULL,NULL,NULL)))
   1071 	{
   1072 		sendnumeric(client, ERR_NOPRIVILEGES);
   1073 		return;
   1074 	}
   1075 	if ((parc < 2) || BadPtr(parv[1]))
   1076 	{
   1077 		sendnumeric(client, ERR_NEEDMOREPARAMS, "TEMPSHUN");
   1078 		return;
   1079 	}
   1080 	if (parv[1][0] == '+')
   1081 		name = parv[1]+1;
   1082 	else if (parv[1][0] == '-')
   1083 	{
   1084 		name = parv[1]+1;
   1085 		remove = 1;
   1086 	} else
   1087 		name = parv[1];
   1088 
   1089 	target = find_user(name, NULL);
   1090 	if (!target)
   1091 	{
   1092 		sendnumeric(client, ERR_NOSUCHNICK, name);
   1093 		return;
   1094 	}
   1095 	if (!MyUser(target))
   1096 	{
   1097 		sendto_one(target, NULL, ":%s TEMPSHUN %c%s :%s",
   1098 		           client->id, remove ? '-' : '+', target->id, comment);
   1099 	} else {
   1100 		char buf[1024];
   1101 		if (!remove)
   1102 		{
   1103 			if (IsShunned(target))
   1104 			{
   1105 				sendnotice(client, "User '%s' already shunned", target->name);
   1106 			} else if (ValidatePermissionsForPath("immune:server-ban:shun",target,NULL,NULL,NULL))
   1107 			{
   1108 				sendnotice(client, "You cannot tempshun '%s' because (s)he is an oper with 'immune:server-ban:shun' privilege", target->name);
   1109 			} else
   1110 			{
   1111 				SetShunned(target);
   1112 				unreal_log(ULOG_INFO, "tkl", "TKL_ADD_TEMPSHUN", client,
   1113 					   "Temporary shun added on user $target.details [reason: $shun_reason] [by: $client]",
   1114 					   log_data_string("shun_reason", comment),
   1115 					   log_data_client("target", target));
   1116 			}
   1117 		} else {
   1118 			if (!IsShunned(target))
   1119 			{
   1120 				sendnotice(client, "User '%s' is not shunned", target->name);
   1121 			} else {
   1122 				ClearShunned(target);
   1123 				unreal_log(ULOG_INFO, "tkl", "TKL_DEL_TEMPSHUN", client,
   1124 					   "Temporary shun removed from user $target.details [by: $client]",
   1125 					   log_data_client("target", target));
   1126 			}
   1127 		}
   1128 	}
   1129 }
   1130 
   1131 /** KLINE - Kill line (ban user from local server)
   1132  */
   1133 CMD_FUNC(cmd_kline)
   1134 {
   1135 	if (IsServer(client))
   1136 		return;
   1137 
   1138 	if (!ValidatePermissionsForPath("server-ban:kline:local:add",client,NULL,NULL,NULL))
   1139 	{
   1140 		sendnumeric(client, ERR_NOPRIVILEGES);
   1141 		return;
   1142 	}
   1143 
   1144 	if (parc == 1)
   1145 	{
   1146 		const char *parv[3];
   1147 		parv[0] = NULL;
   1148 		parv[1] = "kline";
   1149 		parv[2] = NULL;
   1150 		do_cmd(client, recv_mtags, "STATS", 2, parv);
   1151 		return;
   1152 	}
   1153 
   1154 	if (!ValidatePermissionsForPath("server-ban:kline:remove",client,NULL,NULL,NULL) && *parv[1] == '-')
   1155 	{
   1156 		sendnumeric(client, ERR_NOPRIVILEGES);
   1157 		return;
   1158 	}
   1159 
   1160 	cmd_tkl_line(client, parc, parv, "k");
   1161 }
   1162 
   1163 /** Generate stats for '/GLINE -stats' and such */
   1164 void tkl_general_stats(Client *client)
   1165 {
   1166 	int index, index2;
   1167 	TKL *tkl;
   1168 	int total = 0;
   1169 	int subtotal;
   1170 
   1171 	/* First, hashed entries.. */
   1172 	for (index = 0; index < TKLIPHASHLEN1; index++)
   1173 	{
   1174 		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
   1175 		{
   1176 			subtotal = 0;
   1177 			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
   1178 				subtotal++;
   1179 			if (subtotal > 0)
   1180 				sendnotice(client, "Slot %d:%d has %d item(s)", index, index2, subtotal);
   1181 			total += subtotal;
   1182 		}
   1183 	}
   1184 	sendnotice(client, "Hashed TKL items: %d item(s)", total);
   1185 
   1186 	/* Now normal entries.. */
   1187 	subtotal = 0;
   1188 	for (index = 0; index < TKLISTLEN; index++)
   1189 	{
   1190 		for (tkl = tklines[index]; tkl; tkl = tkl->next)
   1191 			subtotal++;
   1192 	}
   1193 	sendnotice(client, "Standard TKL items: %d item(s)", subtotal);
   1194 	total += subtotal;
   1195 	sendnotice(client, "Grand total TKL items: %d item(s)", total);
   1196 }
   1197 
   1198 /** ZLINE - Kill a user as soon as it tries to connect to the server.
   1199  * This happens before any DNS/ident lookups have been done and
   1200  * before any data has been processed (including no TLS handshake, etc.)
   1201  */
   1202 CMD_FUNC(cmd_zline)
   1203 {
   1204 	if (IsServer(client))
   1205 		return;
   1206 
   1207 	if (!ValidatePermissionsForPath("server-ban:zline:local:add",client,NULL,NULL,NULL))
   1208 	{
   1209 		sendnumeric(client, ERR_NOPRIVILEGES);
   1210 		return;
   1211 	}
   1212 
   1213 	if (parc == 1)
   1214 	{
   1215 		const char *parv[3];
   1216 		parv[0] = NULL;
   1217 		parv[1] = "kline"; /* (there's no /STATS zline, it's included in /STATS kline output) */
   1218 		parv[2] = NULL;
   1219 		do_cmd(client, recv_mtags, "STATS", 2, parv);
   1220 		return;
   1221 	}
   1222 
   1223 	if ((parc > 1) && !BadPtr(parv[1]) && !strcasecmp(parv[1], "-stats"))
   1224 	{
   1225 		/* Print some statistics */
   1226 		tkl_general_stats(client);
   1227 		return;
   1228 	}
   1229 
   1230 	cmd_tkl_line(client, parc, parv, "z");
   1231 }
   1232 
   1233 /** Check if a ban is placed with a too broad mask (like '*') */
   1234 int ban_too_broad(char *usermask, char *hostmask)
   1235 {
   1236 	char *p;
   1237 	int cnt = 0;
   1238 
   1239 	/* Scary config setting. Hmmm. */
   1240 	if (ALLOW_INSANE_BANS)
   1241 		return 0;
   1242 
   1243 	/* Allow things like clone@*, dsfsf@*, etc.. */
   1244 	if (!strchr(usermask, '*') && !strchr(usermask, '?'))
   1245 		return 0;
   1246 
   1247 	/* If it's a CIDR, then check /mask first.. */
   1248 	p = strchr(hostmask, '/');
   1249 	if (p)
   1250 	{
   1251 		int cidrlen = atoi(p+1);
   1252 		if (strchr(hostmask, ':'))
   1253 		{
   1254 			if (cidrlen < 48)
   1255 				return 1; /* too broad IPv6 CIDR mask */
   1256 		} else {
   1257 			if (cidrlen < 16)
   1258 				return 1; /* too broad IPv4 CIDR mask */
   1259 		}
   1260 	}
   1261 
   1262 	/* Must at least contain 4 non-wildcard/non-dot characters.
   1263 	 * This will deal with non-CIDR and hosts, but any correct
   1264 	 * CIDR mask will also pass this test (which is fine).
   1265 	 */
   1266 	for (p = hostmask; *p; p++)
   1267 		if (*p != '*' && *p != '.' && *p != '?' && *p != ':')
   1268 			cnt++;
   1269 
   1270 	if (cnt >= 4)
   1271 		return 0;
   1272 
   1273 	return 1;
   1274 }
   1275 
   1276 /** Ugly function, only meant to be called by cmd_tkl_line() */
   1277 static int xline_exists(char *type, char *usermask, char *hostmask)
   1278 {
   1279 	char *umask = usermask;
   1280 	int softban = 0;
   1281 	int tpe = tkl_chartotype(type[0]);
   1282 
   1283 	if (*umask == '%')
   1284 	{
   1285 		umask++;
   1286 		softban = 1;
   1287 	}
   1288 
   1289 	return find_tkl_serverban(tpe, umask, hostmask, softban) ? 1 : 0;
   1290 }
   1291 
   1292 /** Parse an extended server ban such as ~S:aabbccddetc..
   1293  * Used for both syntax checking and to split it into userbuf/hostbuf for TKL protocol.
   1294  * @param mask_in	The input mask (eg: ~S:aabbccddetc)
   1295  * @param client	Client doing the request (used to send errors), can be NULL.
   1296  * @param error		Pointer to set to the error buffer (must be set!)
   1297  * @param skip_checking	Set this to 1 if coming from a remote user/server to skip the .is_ok() check.
   1298  *                      Note that a .conv_param() call can still fail.
   1299  * @param buf1		Buffer to store the extban starter in (eg "~S:") -- can be NULL if you don't need it
   1300  * @param buf1len	Length of buf1
   1301  * @param buf2		Buffer to store the extban remainder in (eg "aabbccddetc") -- can be NULL if you don't need it
   1302  * @param buf2len	Length of buf2
   1303  * @returns 1 if the server ban is acceptable. The ban will then be stored in buf1/buf2 (unless those
   1304  *            were set to NULL by the caller). On failure we return 0 and 'error' is set appropriately.
   1305  */
   1306 int parse_extended_server_ban(const char *mask_in, Client *client, char **error, int skip_checking, char *buf1, size_t buf1len, char *buf2, size_t buf2len)
   1307 {
   1308 	const char *nextbanstr = NULL;
   1309 	Extban *extban;
   1310 	const char *str;
   1311 	char *p;
   1312 	BanContext *b = NULL;
   1313 	char    mask[USERLEN + NICKLEN + HOSTLEN + 32]; // same as extban_conv_param_nuh_or_extban()
   1314 	char newmask[USERLEN + NICKLEN + HOSTLEN + 32];
   1315 	char soft_ban = 0;
   1316 
   1317 	*error = NULL;
   1318 	if (buf1 && buf2)
   1319 		*buf1 = *buf2 = '\0';
   1320 
   1321 	/* Work on a copy */
   1322 	if (*mask_in == '%')
   1323 	{
   1324 		strlcpy(mask, mask_in+1, sizeof(mask));
   1325 		soft_ban = 1;
   1326 	} else {
   1327 		strlcpy(mask, mask_in, sizeof(mask));
   1328 	}
   1329 
   1330 	extban = findmod_by_bantype(mask, &nextbanstr);
   1331 	if (!extban || !(extban->options & EXTBOPT_TKL))
   1332 	{
   1333 		*error = "Invalid or unsupported extended server ban requested. Valid types are for example ~a, ~r, ~S.";
   1334 		goto fail_parse_extended_server_ban;
   1335 	}
   1336 
   1337 	b = safe_alloc(sizeof(BanContext));
   1338 	b->client = client;
   1339 	b->banstr = nextbanstr;
   1340 	b->is_ok_check = EXBCHK_PARAM;
   1341 	b->what = MODE_ADD;
   1342 	b->ban_type = EXBTYPE_TKL;
   1343 
   1344 	/* Run .is_ok() for the extban. This check is skipped if coming from a remote user/server */
   1345 	if (skip_checking == 0)
   1346 	{
   1347 		if (extban->is_ok && !extban->is_ok(b))
   1348 		{
   1349 			*error = "Invalid extended server ban";
   1350 			goto fail_parse_extended_server_ban;
   1351 		}
   1352 	}
   1353 
   1354 	b->banstr = nextbanstr;
   1355 	str = extban->conv_param(b, extban);
   1356 	if (!str)
   1357 	{
   1358 		*error = "Invalid extended server ban";
   1359 		goto fail_parse_extended_server_ban;
   1360 	}
   1361 	str = prefix_with_extban(str, b, extban, newmask, sizeof(newmask));
   1362 	if (str == NULL)
   1363 	{
   1364 		*error = "Unexpected error (1)";
   1365 		goto fail_parse_extended_server_ban;
   1366 	}
   1367 
   1368 	p = strchr(newmask, ':');
   1369 	if (!p)
   1370 	{
   1371 		*error = "Unexpected error (2)";
   1372 		goto fail_parse_extended_server_ban;
   1373 	}
   1374 
   1375 	if (p[1] == ':')
   1376 	{
   1377 		*error = "For technical reasons you cannot use a double : at the beginning of an extended server ban (eg ~a::xyz)";
   1378 		goto fail_parse_extended_server_ban;
   1379 	}
   1380 
   1381 	if (!p[1])
   1382 	{
   1383 		*error = "Empty / too short extended server ban";
   1384 		goto fail_parse_extended_server_ban;
   1385 	}
   1386 
   1387 	/* Now convert the result into two buffers for TKL protocol usage */
   1388 	if (buf1 && buf2)
   1389 	{
   1390 		char save;
   1391 		p++;
   1392 		save = *p;
   1393 		*p = '\0';
   1394 		/* First buffer is eg ~S: or %~S: */
   1395 		snprintf(buf1, buf1len, "%s%s",
   1396 		         soft_ban ? "%" : "",
   1397 		         newmask);
   1398 		*p = save;
   1399 		strlcpy(buf2, p, buf2len); /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
   1400 	}
   1401 	safe_free(b);
   1402 	return 1;
   1403 
   1404 fail_parse_extended_server_ban:
   1405 	safe_free(b);
   1406 	return 0;
   1407 }
   1408 
   1409 /** Parse a server ban request such as 'blah@blah.com' or '~account:EvilUser'
   1410  * @param client	Client requesting the operation (can be NULL)
   1411  * @param add		Set to 1 for add ban, 0 for remove ban
   1412  * @param type		TKL type (character), see 2nd column of tkl_types[], eg 'G' for gline.
   1413  * @param str		The input string
   1414  * @param usermask_out	Will be set to the TKL usermask
   1415  * @param hostmask_out	Will be set to the TKL hostmask
   1416  * @param soft		Will be set to 1 if it's a softban, otherwise 0
   1417  * @param error		On failure, this will contain the error string
   1418  * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
   1419  * @retval 0	Failed: error is set appropriately
   1420  */
   1421 int _server_ban_parse_mask(Client *client, int add, char type, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
   1422 {
   1423 	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE];
   1424 	char *hostmask = NULL, *usermask = NULL;
   1425 	char *mask, *p;
   1426 
   1427 	/* Set defaults */
   1428 	*usermask_out = *hostmask_out = NULL;
   1429 	*soft = 0;
   1430 
   1431 	strlcpy(maskbuf, str, sizeof(maskbuf));
   1432 	mask = maskbuf;
   1433 
   1434 	if ((*mask != '~') && strchr(mask, '!'))
   1435 	{
   1436 		*error = "Cannot have '!' in masks.";
   1437 		return 0;
   1438 	}
   1439 
   1440 	if (*mask == ':')
   1441 	{
   1442 		*error = "Mask cannot start with a ':'.";
   1443 		return 0;
   1444 	}
   1445 
   1446 	if (strchr(mask, ' '))
   1447 	{
   1448 		*error = "Mask may not contain spaces";
   1449 		return 0;
   1450 	}
   1451 
   1452 	/* Check if it's a softban */
   1453 	if (*mask == '%')
   1454 	{
   1455 		*soft = 1;
   1456 		if (!strchr("kGs", type))
   1457 		{
   1458 			*error = "The %% prefix (soft ban) is only available for KLINE, GLINE and SHUN. "
   1459 			         "For technical reasons this will not work for (G)ZLINE.";
   1460 			return 0;
   1461 		}
   1462 	}
   1463 
   1464 	/* Check if it's an extended server ban */
   1465 	if (is_extended_server_ban(mask))
   1466 	{
   1467 		char *err;
   1468 
   1469 		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
   1470 		{
   1471 			/* If adding, reject it */
   1472 			if (add)
   1473 			{
   1474 				*error = err;
   1475 				return 0;
   1476 			} else
   1477 			{
   1478 				/* Always allow any removal attempt... */
   1479 				char *p;
   1480 				char save;
   1481 				p = strchr(mask, ':');
   1482 				p++;
   1483 				save = *p;
   1484 				*p = '\0';
   1485 				strlcpy(mask1buf, mask, sizeof(mask1buf));
   1486 				*p = save;
   1487 				strlcpy(mask2buf, p, sizeof(mask2buf));
   1488 				/* fallthrough */
   1489 			}
   1490 		}
   1491 		if (add && ((type == 'z') || (type == 'Z')))
   1492 		{
   1493 			*error = "(G)Zlines must be placed at *@\037IPMASK\037. "
   1494 			         "Extended server bans don't work here because (g)zlines are processed "
   1495 			         "BEFORE dns and ident lookups are done and before reading any client data. "
   1496 			         "If you want to use extended server bans then use a KLINE/GLINE instead.";
   1497 			return 0;
   1498 		}
   1499 		usermask = mask1buf; /* eg ~S: */
   1500 		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
   1501 	} else
   1502 	{
   1503 		/* Check if it's a hostmask and legal .. */
   1504 		p = strchr(mask, '@');
   1505 		if (p) {
   1506 			if ((p == mask) || !p[1])
   1507 			{
   1508 				*error = "No user@host specified";
   1509 				return 0;
   1510 			}
   1511 			usermask = strtok(mask, "@");
   1512 			hostmask = strtok(NULL, "");
   1513 			if (BadPtr(hostmask))
   1514 			{
   1515 				if (BadPtr(usermask))
   1516 				{
   1517 					*error = "Invalid mask";
   1518 					return 0;
   1519 				}
   1520 				hostmask = usermask;
   1521 				usermask = "*";
   1522 			}
   1523 			if (*hostmask == ':')
   1524 			{
   1525 				*error = "For technical reasons you cannot start the host with a ':', sorry";
   1526 				return 0;
   1527 			}
   1528 			if (add && ((type == 'z') || (type == 'Z')))
   1529 			{
   1530 				/* It's a (G)ZLINE, make sure the user isn't specyfing a HOST.
   1531 				 * Just a warning in 3.2.3, but an error in 3.2.4.
   1532 				 */
   1533 				if (strcmp(usermask, "*"))
   1534 				{
   1535 					*error = "(G)Zlines must be placed at \037*\037@ipmask, not \037user\037@ipmask. This is "
   1536 					         "because (g)zlines are processed BEFORE dns and ident lookups are done. "
   1537 					         "If you want to use usermasks, use a KLINE/GLINE instead.";
   1538 					return 0;
   1539 				}
   1540 				for (p=hostmask; *p; p++)
   1541 				{
   1542 					if (isalpha(*p) && !isxdigit(*p))
   1543 					{
   1544 						*error = "ERROR: (g)zlines must be placed at *@\037IPMASK\037, not *@\037HOSTMASK\037 "
   1545 						         "(so for example *@192.168.* is ok, but *@*.aol.com is not). "
   1546 						         "This is because (g)zlines are processed BEFORE dns and ident lookups are done. "
   1547 						         "If you want to use hostmasks instead of ipmasks, use a KLINE/GLINE instead.";
   1548 						return 0;
   1549 					}
   1550 				}
   1551 			}
   1552 		}
   1553 		else
   1554 		{
   1555 			/* It's seemingly a nick .. let's see if we can find the user */
   1556 			Client *acptr;
   1557 			if ((acptr = find_user(mask, NULL)))
   1558 			{
   1559 				BanAction action = BAN_ACT_KLINE; // just a dummy default
   1560 				if ((type == 'z') || (type == 'Z'))
   1561 					action = BAN_ACT_ZLINE; // to indicate zline (no hostname, no dns, etc)
   1562 				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, (const char **)&usermask, (const char **)&hostmask);
   1563 			}
   1564 			else
   1565 			{
   1566 				*error = "Nickname not found";
   1567 				return 0;
   1568 			}
   1569 		}
   1570 	}
   1571 
   1572 	/* Success! */
   1573 	*usermask_out = usermask;
   1574 	*hostmask_out = hostmask;
   1575 	return 1;
   1576 }
   1577 
   1578 /** Intermediate layer between user functions such as KLINE/GLINE
   1579  * and the TKL layer (cmd_tkl).
   1580  * This allows us doing some syntax checking and other helpful
   1581  * things that are the same for many types of *LINES.
   1582  */
   1583 void cmd_tkl_line(Client *client, int parc, const char *parv[], char *type)
   1584 {
   1585 	time_t secs;
   1586 	int add = 1, soft;
   1587 	time_t i;
   1588 	Client *acptr = NULL;
   1589 	const char *mask;
   1590 	const char *error;
   1591 	char mo[64], mo2[64];
   1592 	char *p, *usermask, *hostmask;
   1593 	const char *tkllayer[10] = {
   1594 		me.name,		/*0  server.name */
   1595 		NULL,			/*1  +|- */
   1596 		NULL,			/*2  G   */
   1597 		NULL,			/*3  user */
   1598 		NULL,			/*4  host */
   1599 		NULL,			/*5  set_by */
   1600 		"0",			/*6  expire_at */
   1601 		NULL,			/*7  set_at */
   1602 		"no reason",	/*8  reason */
   1603 		NULL
   1604 	};
   1605 	struct tm *t;
   1606 
   1607 	if ((parc == 1) || BadPtr(parv[1]))
   1608 		return; /* shouldn't happen */
   1609 
   1610 	mask = parv[1];
   1611 
   1612 	if (*mask == '-')
   1613 	{
   1614 		add = 0;
   1615 		mask++;
   1616 	}
   1617 	else if (*mask == '+')
   1618 	{
   1619 		add = 1;
   1620 		mask++;
   1621 	}
   1622 
   1623 	if (!server_ban_parse_mask(client, add, *type, mask, &usermask, &hostmask, &soft, &error))
   1624 	{
   1625 		sendnotice(client, "[ERROR] %s", error);
   1626 		return;
   1627 	}
   1628 
   1629 	if (add && ban_too_broad(usermask, hostmask))
   1630 	{
   1631 		sendnotice(client, "*** [error] Too broad mask");
   1632 		return;
   1633 	}
   1634 
   1635 	secs = 0;
   1636 
   1637 	if (add && (parc > 3))
   1638 	{
   1639 		secs = config_checkval(parv[2], CFG_TIME);
   1640 		if (secs < 0)
   1641 		{
   1642 			sendnotice(client, "*** [error] The time you specified is out of range!");
   1643 			return;
   1644 		}
   1645 	}
   1646 	tkllayer[1] = add ? "+" : "-";
   1647 	tkllayer[2] = type;
   1648 	tkllayer[3] = usermask;
   1649 	tkllayer[4] = hostmask;
   1650 	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
   1651 	if (add)
   1652 	{
   1653 		if (secs == 0)
   1654 		{
   1655 			if (DEFAULT_BANTIME && (parc <= 3))
   1656 				ircsnprintf(mo, sizeof(mo), "%lld", (long long)(DEFAULT_BANTIME + TStime()));
   1657 			else
   1658 				ircsnprintf(mo, sizeof(mo), "%lld", (long long)secs); /* "0" */
   1659 		}
   1660 		else
   1661 			ircsnprintf(mo, sizeof(mo), "%lld", (long long)(secs + TStime()));
   1662 		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
   1663 		tkllayer[6] = mo;
   1664 		tkllayer[7] = mo2;
   1665 		if (parc > 3) {
   1666 			tkllayer[8] = parv[3];
   1667 		} else if (parc > 2) {
   1668 			tkllayer[8] = parv[2];
   1669 		}
   1670 		/* Blerghhh... */
   1671 		i = atol(mo);
   1672 		t = gmtime(&i);
   1673 		if (!t)
   1674 		{
   1675 			sendnotice(client, "*** [error] The time you specified is out of range");
   1676 			return;
   1677 		}
   1678 
   1679 		/* Some stupid checking */
   1680 		if (xline_exists(type, usermask, hostmask))
   1681 		{
   1682 			sendnotice(client, "ERROR: Ban for %s@%s already exists.", usermask, hostmask);
   1683 			return;
   1684 		}
   1685 
   1686 		/* call the tkl layer .. */
   1687 		cmd_tkl(&me, NULL, 9, tkllayer);
   1688 	}
   1689 	else
   1690 	{
   1691 		/* call the tkl layer .. */
   1692 		cmd_tkl(&me, NULL, 6, tkllayer);
   1693 
   1694 	}
   1695 }
   1696 
   1697 void eline_syntax(Client *client)
   1698 {
   1699 	sendnotice(client, " Syntax: /ELINE <user@host> <bantypes> <expiry-time> <reason>");
   1700 	sendnotice(client, "     Or: /ELINE <extserverban> <bantypes> <expiry-time> <reason>");
   1701 	sendnotice(client, "Valid bantypes are:");
   1702 	sendnotice(client, "k: K-Line     G: G-Line");
   1703 	sendnotice(client, "z: Z-Line     Z: Global Z-Line");
   1704 	sendnotice(client, "Q: Q-Line");
   1705 	sendnotice(client, "s: Shun");
   1706 	sendnotice(client, "F: Spamfilter");
   1707 	sendnotice(client, "b: Blacklist checking");
   1708 	sendnotice(client, "c: Connect flood (bypass set::anti-flood::connect-flood))");
   1709 	sendnotice(client, "d: Handshake data flood (no ZLINE on too much data before registration)");
   1710 	sendnotice(client, "m: Bypass allow::maxperip restriction");
   1711 	sendnotice(client, "r: Bypass antirandom module");
   1712 	sendnotice(client, "8: Bypass antimixedutf8 module");
   1713 	sendnotice(client, "v: Bypass ban version { } blocks");
   1714 	sendnotice(client, "Examples:");
   1715 	sendnotice(client, "/ELINE *@unrealircd.org kGF 0 This user is exempt");
   1716 	sendnotice(client, "/ELINE ~S:1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef kGF 0 Trusted user with this certificate fingerprint");
   1717 	sendnotice(client, "-");
   1718 	sendnotice(client, "To get a list of all current ELINEs, type: /STATS except");
   1719 }
   1720 
   1721 /** Check if any of the specified types require the
   1722  * exception to be placed on *@ip rather than
   1723  * user@host or *@host. For eg zlines.
   1724  */
   1725 TKLTypeTable *eline_type_requires_ip(const char *bantypes)
   1726 {
   1727 	int i;
   1728 
   1729 	for (i=0; tkl_types[i].config_name; i++)
   1730 		if (tkl_types[i].needip && strchr(bantypes, tkl_types[i].letter))
   1731 			return &tkl_types[i];
   1732 	return NULL;
   1733 }
   1734 
   1735 /** Checks a string to see if it contains invalid ban exception types */
   1736 int contains_invalid_server_ban_exception_type(const char *str, char *c)
   1737 {
   1738 	const char *p;
   1739 	for (p = str; *p; p++)
   1740 	{
   1741 		if (!tkl_banexception_chartotype(*p))
   1742 		{
   1743 			*c = *p;
   1744 			return 1;
   1745 		}
   1746 	}
   1747 	return 0;
   1748 }
   1749 
   1750 /** Parse a server ban exception (ELINE) request such as 'blah@blah.com' or '~account:EvilUser'
   1751  * @param client	Client requesting the operation (can be NULL)
   1752  * @param add		Set to 1 for add ban, 0 for remove ban
   1753  * @param bantypes	Ban types to exempt from
   1754  * @param str		The input string
   1755  * @param usermask_out	Will be set to the TKL usermask
   1756  * @param hostmask_out	Will be set to the TKL hostmask
   1757  * @param soft		Will be set to 1 if it's a softban, otherwise 0
   1758  * @param error		On failure, this will contain the error string
   1759  * @retval 1	Success: usermask_out, hostmask_out and soft are set appropriately.
   1760  * @retval 0	Failed: error is set appropriately
   1761  */
   1762 int _server_ban_exception_parse_mask(Client *client, int add, const char *bantypes, const char *str, char **usermask_out, char **hostmask_out, int *soft, const char **error)
   1763 {
   1764 	static char maskbuf[BUFSIZE], mask1buf[BUFSIZE], mask2buf[BUFSIZE], errbuf[BUFSIZE];
   1765 	char *hostmask = NULL, *usermask = NULL;
   1766 	char *mask, *p;
   1767 	TKLTypeTable *t;
   1768 
   1769 	/* Set defaults */
   1770 	*usermask_out = *hostmask_out = NULL;
   1771 	*soft = 0;
   1772 
   1773 	strlcpy(maskbuf, str, sizeof(maskbuf));
   1774 	mask = maskbuf;
   1775 
   1776 	if ((*mask != '~') && strchr(mask, '!'))
   1777 	{
   1778 		*error = "Cannot have '!' in masks.";
   1779 		return 0;
   1780 	}
   1781 
   1782 	if (*mask == ':')
   1783 	{
   1784 		*error = "Mask cannot start with a ':'.";
   1785 		return 0;
   1786 	}
   1787 
   1788 	if (strchr(mask, ' '))
   1789 	{
   1790 		*error = "Mask may not contain spaces";
   1791 		return 0;
   1792 	}
   1793 
   1794 	if (*mask == '%')
   1795 	{
   1796 		*soft = 1;
   1797 		/* do we need more sanity checks here? */
   1798 	}
   1799 
   1800 	/* Check if it's an extended server ban */
   1801 	if (is_extended_server_ban(mask))
   1802 	{
   1803 		char *err;
   1804 
   1805 		if (!parse_extended_server_ban(mask, client, &err, 0, mask1buf, sizeof(mask1buf), mask2buf, sizeof(mask2buf)))
   1806 		{
   1807 			/* If adding, reject it */
   1808 			if (add)
   1809 			{
   1810 				*error = err;
   1811 				return 0;
   1812 			} else
   1813 			{
   1814 				/* Always allow any removal attempt... */
   1815 				char *p;
   1816 				char save;
   1817 				p = strchr(mask, ':');
   1818 				p++;
   1819 				save = *p;
   1820 				*p = '\0';
   1821 				strlcpy(mask1buf, mask, sizeof(mask1buf));
   1822 				*p = save;
   1823 				strlcpy(mask2buf, p, sizeof(mask2buf));
   1824 				/* fallthrough */
   1825 			}
   1826 		}
   1827 		if (add && (t = eline_type_requires_ip(bantypes)))
   1828 		{
   1829 			snprintf(errbuf, sizeof(errbuf),
   1830 			         "ERROR: Ban exception with type '%c' does not work on extended server bans. "
   1831 			         "This is because checking for %s takes places BEFORE "
   1832 			         "extended bans can be checked.", t->letter, t->log_name);
   1833 			*error = errbuf;
   1834 			return 0;
   1835 		}
   1836 		usermask = mask1buf; /* eg ~S: */
   1837 		hostmask = mask2buf; /* eg 1234567890abcdef1234567890abcdef1234567890abcdef1234567890abcdef */
   1838 	} else
   1839 	{
   1840 		/* Check if it's a hostmask and legal .. */
   1841 		p = strchr(mask, '@');
   1842 		if (p) {
   1843 			if ((p == mask) || !p[1])
   1844 			{
   1845 				*error = "No user@host specified";
   1846 				return 0;
   1847 			}
   1848 			usermask = strtok(mask, "@");
   1849 			hostmask = strtok(NULL, "");
   1850 			if (BadPtr(hostmask))
   1851 			{
   1852 				if (BadPtr(usermask))
   1853 				{
   1854 					*error = "Invalid mask";
   1855 					return 0;
   1856 				}
   1857 				hostmask = usermask;
   1858 				usermask = "*";
   1859 			}
   1860 			if (*hostmask == ':')
   1861 			{
   1862 				*error = "For technical reasons you cannot start the host with a ':', sorry";
   1863 				return 0;
   1864 			}
   1865 			if (add && ((t = eline_type_requires_ip(bantypes))))
   1866 			{
   1867 				/* Trying to exempt a user from a (G)ZLINE,
   1868 				 * make sure the user isn't specifying a host then.
   1869 				 */
   1870 				if (strcmp(usermask, "*"))
   1871 				{
   1872 					snprintf(errbuf, sizeof(errbuf),
   1873 					         "Ban exception with type '%c' need to be placed at \037*\037@ipmask, not \037user\037@ipmask. "
   1874 					         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
   1875 					         t->letter, t->log_name);
   1876 					*error = errbuf;
   1877 					return 0;
   1878 				}
   1879 				for (p=hostmask; *p; p++)
   1880 				{
   1881 					if (isalpha(*p) && !isxdigit(*p))
   1882 					{
   1883 						snprintf(errbuf, sizeof(errbuf),
   1884 						         "Ban exception with type '%c' needs to be placed at *@\037ipmask\037, not *@\037hostmask\037. "
   1885 						         "(so for example *@192.168.* is OK, but *@*.aol.com is not). "
   1886 						         "This is because checking %s takes places (possibly) BEFORE any dns and ident lookups.",
   1887 						         t->letter, t->log_name);
   1888 						*error = errbuf;
   1889 						return 0;
   1890 					}
   1891 				}
   1892 			}
   1893 		}
   1894 		else
   1895 		{
   1896 			/* It's seemingly a nick .. let's see if we can find the user */
   1897 			Client *acptr;
   1898 			if ((acptr = find_user(mask, NULL)))
   1899 			{
   1900 				BanAction action = BAN_ACT_KLINE; // just a dummy default
   1901 				if (add && eline_type_requires_ip(bantypes))
   1902 					action = BAN_ACT_ZLINE; // to indicate zline (no hostname, no dns, etc)
   1903 				ban_target_to_tkl_layer(iConf.manual_ban_target, action, acptr, (const char **)&usermask, (const char **)&hostmask);
   1904 			}
   1905 			else
   1906 			{
   1907 				*error = "Nickname not found";
   1908 				return 0;
   1909 			}
   1910 		}
   1911 	}
   1912 
   1913 	/* Success! */
   1914 	*usermask_out = usermask;
   1915 	*hostmask_out = hostmask;
   1916 	return 1;
   1917 }
   1918 
   1919 CMD_FUNC(cmd_eline)
   1920 {
   1921 	time_t secs = 0;
   1922 	int add = 1;
   1923 	int soft = 0;
   1924 	const char *error = NULL;
   1925 	Client *acptr = NULL;
   1926 	char *mask = NULL;
   1927 	char mo[64], mo2[64];
   1928 	char maskbuf[BUFSIZE];
   1929 	char mask1buf[BUFSIZE];
   1930 	char mask2buf[BUFSIZE];
   1931 	const char *p, *bantypes=NULL, *reason=NULL;
   1932 	char *usermask, *hostmask;
   1933 	const char *tkllayer[11] = {
   1934 		me.name,		/*0  server.name */
   1935 		NULL,			/*1  +|- */
   1936 		NULL,			/*2  E   */
   1937 		NULL,			/*3  user */
   1938 		NULL,			/*4  host */
   1939 		NULL,			/*5  set_by */
   1940 		"0",			/*6  expire_at */
   1941 		"-",			/*7  set_at */
   1942 		"-",			/*8  ban types */
   1943 		"-",			/*9  reason */
   1944 		NULL
   1945 	};
   1946 	TKLTypeTable *t;
   1947 
   1948 	if (IsServer(client))
   1949 		return;
   1950 
   1951 	if (!ValidatePermissionsForPath("server-ban:eline",client,NULL,NULL,NULL))
   1952 	{
   1953 		sendnumeric(client, ERR_NOPRIVILEGES);
   1954 		return;
   1955 	}
   1956 
   1957 	/* For del we need at least:
   1958 	 * ELINE -user@host
   1959 	 * The 'add' case is checked later.
   1960 	 */
   1961 	if ((parc < 2) || BadPtr(parv[1]))
   1962 	{
   1963 		eline_syntax(client);
   1964 		return;
   1965 	}
   1966 
   1967 	strlcpy(maskbuf, parv[1], sizeof(maskbuf));
   1968 	mask = maskbuf;
   1969 	if (*mask == '-')
   1970 	{
   1971 		add = 0;
   1972 		mask++;
   1973 	}
   1974 	else if (*mask == '+')
   1975 	{
   1976 		add = 1;
   1977 		mask++;
   1978 	}
   1979 
   1980 	/* For add we need more:
   1981 	 * ELINE user@host bantypes expiry :reason
   1982 	 */
   1983 	if (add)
   1984 	{
   1985 		if ((parc < 5) || BadPtr(parv[4]))
   1986 		{
   1987 			eline_syntax(client);
   1988 			return;
   1989 		}
   1990 		bantypes = parv[2];
   1991 		reason = parv[4];
   1992 	}
   1993 
   1994 	if (!server_ban_exception_parse_mask(client, add, bantypes, mask, &usermask, &hostmask, &soft, &error))
   1995 	{
   1996 		sendnotice(client, "[ERROR] %s", error);
   1997 		return;
   1998 	}
   1999 
   2000 	if (add)
   2001 	{
   2002 		secs = config_checkval(parv[3], CFG_TIME);
   2003 		if ((secs <= 0) && (*parv[3] != '0'))
   2004 		{
   2005 			sendnotice(client, "*** [error] The expiry time you specified is out of range!");
   2006 			eline_syntax(client);
   2007 			return;
   2008 		}
   2009 	}
   2010 
   2011 	tkllayer[1] = add ? "+" : "-";
   2012 	tkllayer[2] = "E";
   2013 	tkllayer[3] = usermask;
   2014 	tkllayer[4] = hostmask;
   2015 	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
   2016 
   2017 	if (add)
   2018 	{
   2019 		char c;
   2020 		/* Add ELINE */
   2021 		if (secs == 0)
   2022 			ircsnprintf(mo, sizeof(mo), "%lld", (long long)secs); /* "0" */
   2023 		else
   2024 			ircsnprintf(mo, sizeof(mo), "%lld", (long long)(secs + TStime()));
   2025 		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
   2026 		tkllayer[6] = mo;
   2027 		tkllayer[7] = mo2;
   2028 		tkllayer[8] = bantypes;
   2029 		if (contains_invalid_server_ban_exception_type(bantypes, &c))
   2030 		{
   2031 			sendnotice(client, "ERROR: bantype '%c' is unrecognized (in '%s'). "
   2032 			                   "Note that the bantypes are case sensitive. "
   2033 			                   "Type /ELINE to see a list of all possible bantypes.",
   2034 			                   c, bantypes);
   2035 			return;
   2036 		}
   2037 		tkllayer[9] = reason;
   2038 		/* call the tkl layer .. */
   2039 		cmd_tkl(&me, NULL, 10, tkllayer);
   2040 	}
   2041 	else
   2042 	{
   2043 		/* Remove ELINE */
   2044 		/* call the tkl layer .. */
   2045 		cmd_tkl(&me, NULL, 10, tkllayer);
   2046 
   2047 	}
   2048 }
   2049 
   2050 
   2051 /** Helper function for cmd_spamfilter, explaining usage. */
   2052 void spamfilter_usage(Client *client)
   2053 {
   2054 	sendnotice(client, "Use: /spamfilter [add|del|remove|+|-] [-simple|-regex] [type] [action] [tkltime] [tklreason] [regex]");
   2055 	sendnotice(client, "See '/helpop ?spamfilter' for more information.");
   2056 	sendnotice(client, "For an easy way to remove an existing spamfilter, use '/spamfilter del' without additional parameters");
   2057 }
   2058 
   2059 /** Helper function for cmd_spamfilter, explaining usage has changed. */
   2060 void spamfilter_new_usage(Client *client, const char *parv[])
   2061 {
   2062 	sendnotice(client, "Unknown match-type '%s'. Must be one of: -regex (new fast PCRE regexes) or "
   2063 	                 "-simple (simple text with ? and * wildcards)",
   2064 	                 parv[2]);
   2065 
   2066 	if (*parv[2] != '-')
   2067 		sendnotice(client, "Using the old 3.2.x /SPAMFILTER syntax? Note the new -regex/-simple field!!");
   2068 
   2069 	spamfilter_usage(client);
   2070 }
   2071 
   2072 /** Delete a spamfilter by ID (the ID can be obtained via '/SPAMFILTER del' */
   2073 void spamfilter_del_by_id(Client *client, const char *id)
   2074 {
   2075 	int index;
   2076 	TKL *tk;
   2077 	int found = 0;
   2078 	char mo[32], mo2[32];
   2079 	const char *tkllayer[13] = {
   2080 		me.name,	/*  0 server.name */
   2081 		NULL,		/*  1 +|- */
   2082 		"F",		/*  2 F   */
   2083 		NULL,		/*  3 usermask (targets) */
   2084 		NULL,		/*  4 hostmask (action) */
   2085 		NULL,		/*  5 set_by */
   2086 		"0",		/*  6 expire_at */
   2087 		"0",		/*  7 set_at */
   2088 		"",			/*  8 tkl time */
   2089 		"",			/*  9 tkl reason */
   2090 		"",			/* 10 match method */
   2091 		"",			/* 11 regex */
   2092 		NULL
   2093 	};
   2094 
   2095 	for (index = 0; index < TKLISTLEN; index++)
   2096 	{
   2097 		for (tk = tklines[index]; tk; tk = tk->next)
   2098 		{
   2099 			if (((tk->type & (TKL_GLOBAL|TKL_SPAMF)) == (TKL_GLOBAL|TKL_SPAMF)) && !strcmp(spamfilter_id(tk), id))
   2100 			{
   2101 				found = 1;
   2102 				break;
   2103 			}
   2104 		}
   2105 		if (found)
   2106 			break; /* break outer loop */
   2107 	}
   2108 
   2109 	if (!tk)
   2110 	{
   2111 		sendnotice(client, "Sorry, no spamfilter found with that ID. Did you run '/spamfilter del' to get the appropriate id?");
   2112 		return;
   2113 	}
   2114 
   2115 	/* Spamfilter found. Now fill the tkllayer */
   2116 	tkllayer[1] = "-";
   2117 	tkllayer[3] = spamfilter_target_inttostring(tk->ptr.spamfilter->target); /* target(s) */
   2118 	mo[0] = banact_valtochar(tk->ptr.spamfilter->action);
   2119 	mo[1] = '\0';
   2120 	tkllayer[4] = mo; /* action */
   2121 	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
   2122 	tkllayer[8] = "-";
   2123 	tkllayer[9] = "-";
   2124 	tkllayer[10] = unreal_match_method_valtostr(tk->ptr.spamfilter->match->type); /* matching type */
   2125 	tkllayer[11] = tk->ptr.spamfilter->match->str; /* regex */
   2126 	ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
   2127 	tkllayer[7] = mo2; /* deletion time */
   2128 
   2129 	cmd_tkl(&me, NULL, 12, tkllayer);
   2130 }
   2131 
   2132 /** Spamfilter to fight spam, advertising, worms and other bad things on IRC.
   2133  * See https://www.unrealircd.org/docs/Spamfilter for general documentation.
   2134  *
   2135  * /SPAMFILTER [add|del|remove|+|-] [match-type] [type] [action] [tkltime] [reason] [regex]
   2136  *                   1                    2         3       4        5        6        7
   2137  */
   2138 CMD_FUNC(cmd_spamfilter)
   2139 {
   2140 	int add = 1;
   2141 	char mo[32], mo2[32];
   2142 	const char *tkllayer[13] = {
   2143 		me.name,	/*  0 server.name */
   2144 		NULL,		/*  1 +|- */
   2145 		"F",		/*  2 F   */
   2146 		NULL,		/*  3 usermask (targets) */
   2147 		NULL,		/*  4 hostmask (action) */
   2148 		NULL,		/*  5 set_by */
   2149 		"0",		/*  6 expire_at */
   2150 		"0",		/*  7 set_at */
   2151 		"",			/*  8 tkl time */
   2152 		"",			/*  9 tkl reason */
   2153 		"",			/* 10 match method */
   2154 		"",			/* 11 regex */
   2155 		NULL
   2156 	};
   2157 	int targets = 0, action = 0;
   2158 	char targetbuf[64], actionbuf[2];
   2159 	char reason[512];
   2160 	int n;
   2161 	Match *m;
   2162 	int match_type = 0;
   2163 	char *err = NULL;
   2164 
   2165 	if (IsServer(client))
   2166 		return;
   2167 
   2168 	if (!ValidatePermissionsForPath("server-ban:spamfilter",client,NULL,NULL,NULL))
   2169 	{
   2170 		sendnumeric(client, ERR_NOPRIVILEGES);
   2171 		return;
   2172 	}
   2173 
   2174 	if (parc == 1)
   2175 	{
   2176 		const char *parv[3];
   2177 		parv[0] = NULL;
   2178 		parv[1] = "spamfilter";
   2179 		parv[2] = NULL;
   2180 		do_cmd(client, recv_mtags, "STATS", 2, parv);
   2181 		return;
   2182 	}
   2183 
   2184 	if ((parc <= 3) && !strcmp(parv[1], "del"))
   2185 	{
   2186 		if (!parv[2])
   2187 		{
   2188 			/* Show STATS with appropriate SPAMFILTER del command */
   2189 			const char *parv[5];
   2190 			parv[0] = NULL;
   2191 			parv[1] = "spamfilter";
   2192 			parv[2] = me.name;
   2193 			parv[3] = "del";
   2194 			parv[4] = NULL;
   2195 			do_cmd(client, recv_mtags, "STATS", 4, parv);
   2196 			return;
   2197 		}
   2198 		spamfilter_del_by_id(client, parv[2]);
   2199 		return;
   2200 	}
   2201 
   2202 	if ((parc == 7) && (*parv[2] != '-'))
   2203 	{
   2204 		spamfilter_new_usage(client,parv);
   2205 		return;
   2206 	}
   2207 
   2208 	if ((parc < 8) || BadPtr(parv[7]))
   2209 	{
   2210 		spamfilter_usage(client);
   2211 		return;
   2212 	}
   2213 
   2214 	/* parv[1]: [add|del|+|-]
   2215 	 * parv[2]: match-type
   2216 	 * parv[3]: type
   2217 	 * parv[4]: action
   2218 	 * parv[5]: tkl time
   2219 	 * parv[6]: tkl reason (or block reason..)
   2220 	 * parv[7]: regex
   2221 	 */
   2222 	if (!strcasecmp(parv[1], "add") || !strcmp(parv[1], "+"))
   2223 		add = 1;
   2224 	else if (!strcasecmp(parv[1], "del") || !strcmp(parv[1], "-") || !strcasecmp(parv[1], "remove"))
   2225 		add = 0;
   2226 	else
   2227 	{
   2228 		sendnotice(client, "1st parameter invalid");
   2229 		spamfilter_usage(client);
   2230 		return;
   2231 	}
   2232 
   2233 	if (add && !strcasecmp(parv[2]+1, "posix"))
   2234 	{
   2235 		sendnotice(client, "ERROR: Spamfilter type 'posix' is DEPRECATED. You must use type 'regex' instead.");
   2236 		sendnotice(client, "See https://www.unrealircd.org/docs/FAQ#spamfilter-posix-deprecated");
   2237 		return;
   2238 	}
   2239 
   2240 	match_type = unreal_match_method_strtoval(parv[2]+1);
   2241 	if (!match_type)
   2242 	{
   2243 		spamfilter_new_usage(client, parv);
   2244 		return;
   2245 	}
   2246 
   2247 	targets = spamfilter_gettargets(parv[3], client);
   2248 	if (!targets)
   2249 	{
   2250 		spamfilter_usage(client);
   2251 		return;
   2252 	}
   2253 
   2254 	strlcpy(targetbuf, spamfilter_target_inttostring(targets), sizeof(targetbuf));
   2255 
   2256 	action = banact_stringtoval(parv[4]);
   2257 	if (!action)
   2258 	{
   2259 		sendnotice(client, "Invalid 'action' field (%s)", parv[4]);
   2260 		spamfilter_usage(client);
   2261 		return;
   2262 	}
   2263 	actionbuf[0] = banact_valtochar(action);
   2264 	actionbuf[1] = '\0';
   2265 
   2266 	if (add)
   2267 	{
   2268 		/* now check the regex / match field... */
   2269 		m = unreal_create_match(match_type, parv[7], &err);
   2270 		if (!m)
   2271 		{
   2272 			sendnotice(client, "Error in regex '%s': %s", parv[7], err);
   2273 			return;
   2274 		}
   2275 		unreal_delete_match(m);
   2276 	}
   2277 
   2278 	tkllayer[1] = add ? "+" : "-";
   2279 	tkllayer[3] = targetbuf;
   2280 	tkllayer[4] = actionbuf;
   2281 	tkllayer[5] = make_nick_user_host(client->name, client->user->username, GetHost(client));
   2282 
   2283 	if (parv[5][0] == '-')
   2284 	{
   2285 		ircsnprintf(mo, sizeof(mo), "%lld", (long long)SPAMFILTER_BAN_TIME);
   2286 		tkllayer[8] = mo;
   2287 	}
   2288 	else
   2289 		tkllayer[8] = parv[5];
   2290 
   2291 	if (parv[6][0] == '-')
   2292 		strlcpy(reason, unreal_encodespace(SPAMFILTER_BAN_REASON), sizeof(reason));
   2293 	else
   2294 		strlcpy(reason, parv[6], sizeof(reason));
   2295 
   2296 	tkllayer[9] = reason;
   2297 	tkllayer[10] = parv[2]+1; /* +1 to skip the '-' */
   2298 	tkllayer[11] = parv[7];
   2299 
   2300 	/* SPAMFILTER LENGTH CHECK.
   2301 	 * We try to limit it here so '/stats f' output shows ok, output of that is:
   2302 	 * :servername 229 destname F <target> <action> <num> <num> <num> <reason> <set_by> :<regex>
   2303 	 * : ^NICKLEN       ^ NICKLEN                                       ^check   ^check   ^check
   2304 	 * And for the other fields (and spacing/etc) we count on max 40 characters.
   2305 	 * We also do >500 instead of >510, since that looks cleaner ;).. so actually we count
   2306 	 * on 50 characters for the rest... -- Syzop
   2307 	 */
   2308 	n = strlen(reason) + strlen(parv[7]) + strlen(tkllayer[6]) + (NICKLEN * 2) + 40;
   2309 	if ((n > 500) && add)
   2310 	{
   2311 		sendnotice(client, "Sorry, spamfilter too long. You'll either have to trim down the "
   2312 		                 "reason or the regex (exceeded by %d bytes)", n - 500);
   2313 		return;
   2314 	}
   2315 
   2316 	if (add)
   2317 	{
   2318 		ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
   2319 		tkllayer[7] = mo2;
   2320 	}
   2321 
   2322 	cmd_tkl(&me, NULL, 12, tkllayer);
   2323 }
   2324 
   2325 /** tkl hash method.
   2326  * @param c   The tkl type character, see tkl_typetochar().
   2327  * @note      The input value 'c' is assumed to be in range a-z or A-Z!
   2328  *            Also, don't blindly change the hashmethod here, some things
   2329  *            depend on 'z' and 'Z' ending up in the same bucket.
   2330  */
   2331 int _tkl_hash(unsigned int c)
   2332 {
   2333 #ifdef DEBUGMODE
   2334 	if ((c >= 'a') && (c <= 'z'))
   2335 		return c-'a';
   2336 	else if ((c >= 'A') && (c <= 'Z'))
   2337 		return c-'A';
   2338 	else {
   2339 		unreal_log(ULOG_ERROR, "bug", "TKL_HASH_INVALID", NULL,
   2340 		           "tkl_hash() called with out of range parameter (c = '$tkl_char') !!!",
   2341 		           log_data_char("tkl_char", c));
   2342 		return 0;
   2343 	}
   2344 #else
   2345 	return (isupper(c) ? c-'A' : c-'a');
   2346 #endif
   2347 }
   2348 
   2349 /** tkl type to tkl character.
   2350  * NOTE: type is assumed to be valid.
   2351  */
   2352 char _tkl_typetochar(int type)
   2353 {
   2354 	int i;
   2355 	for (i=0; tkl_types[i].config_name; i++)
   2356 		if ((tkl_types[i].type == type) && tkl_types[i].tkltype)
   2357 			return tkl_types[i].letter;
   2358 	unreal_log(ULOG_ERROR, "bug", "TKL_TYPETOCHAR_INVALID", NULL,
   2359 	           "tkl_typetochar(): unknown type $tkl_type!!!",
   2360 	           log_data_integer("tkl_type", type));
   2361 	return 0;
   2362 }
   2363 
   2364 /** tkl character to tkl type
   2365  * Returns 0 if invalid type.
   2366  */
   2367 int _tkl_chartotype(char c)
   2368 {
   2369 	int i;
   2370 	for (i=0; tkl_types[i].config_name; i++)
   2371 		if ((tkl_types[i].letter == c) && tkl_types[i].tkltype)
   2372 			return tkl_types[i].type;
   2373 	return 0;
   2374 }
   2375 
   2376 char _tkl_configtypetochar(const char *name)
   2377 {
   2378 	int i;
   2379 	for (i=0; tkl_types[i].config_name; i++)
   2380 		if (!strcmp(tkl_types[i].config_name, name))
   2381 			return tkl_types[i].letter;
   2382 	return 0;
   2383 }
   2384 
   2385 int tkl_banexception_chartotype(char c)
   2386 {
   2387 	int i;
   2388 	for (i=0; tkl_types[i].config_name; i++)
   2389 		if ((tkl_types[i].letter == c) && tkl_types[i].exceptiontype)
   2390 			return tkl_types[i].type;
   2391 	return 0;
   2392 }
   2393 
   2394 char *tkl_banexception_configname_to_chars(char *name)
   2395 {
   2396 	static char buf[128];
   2397 	int i;
   2398 
   2399 	if (!strcasecmp(name, "all"))
   2400 	{
   2401 		/* 'all' means everything except qline: */
   2402 		char *p = buf;
   2403 		for (i=0; tkl_types[i].config_name; i++)
   2404 		{
   2405 			if (tkl_types[i].exceptiontype && !(tkl_types[i].type & TKL_NAME))
   2406 				*p++ = tkl_types[i].letter;
   2407 		}
   2408 		*p = '\0';
   2409 		return buf;
   2410 	}
   2411 
   2412 	for (i=0; tkl_types[i].config_name; i++)
   2413 	{
   2414 		if (!strcasecmp(name, tkl_types[i].config_name) && tkl_types[i].exceptiontype)
   2415 		{
   2416 			buf[0] = tkl_types[i].letter;
   2417 			buf[1] = '\0';
   2418 			return buf;
   2419 		}
   2420 	}
   2421 	return NULL;
   2422 }
   2423 
   2424 /** Show TKL type as a string (used when adding/removing) */
   2425 char *_tkl_type_string(TKL *tkl)
   2426 {
   2427 	static char txt[256];
   2428 	int i;
   2429 
   2430 	*txt = '\0';
   2431 
   2432 	if (TKLIsServerBan(tkl) && (tkl->ptr.serverban->subtype == TKL_SUBTYPE_SOFT))
   2433 		strlcpy(txt, "Soft ", sizeof(txt));
   2434 
   2435 	for (i=0; tkl_types[i].config_name; i++)
   2436 	{
   2437 		if ((tkl_types[i].type == tkl->type) && tkl_types[i].tkltype)
   2438 		{
   2439 			strlcat(txt, tkl_types[i].log_name, sizeof(txt));
   2440 			return txt;
   2441 		}
   2442 	}
   2443 
   2444 	strlcpy(txt, "Unknown *-Line", sizeof(txt));
   2445 	return txt;
   2446 }
   2447 
   2448 /** Short config string, lowercase alnum with possibly hyphens (eg: 'kline') */
   2449 char *_tkl_type_config_string(TKL *tkl)
   2450 {
   2451 	int i;
   2452 
   2453 	for (i=0; tkl_types[i].config_name; i++)
   2454 		if ((tkl_types[i].type == tkl->type) && tkl_types[i].tkltype)
   2455 			return tkl_types[i].config_name;
   2456 
   2457 	return "???";
   2458 }
   2459 
   2460 int tkl_banexception_matches_type(TKL *except, int bantype)
   2461 {
   2462 	char *p;
   2463 	int extype;
   2464 
   2465 	if (!TKLIsBanException(except))
   2466 		abort();
   2467 
   2468 	for (p = except->ptr.banexception->bantypes; *p; p++)
   2469 	{
   2470 		extype = tkl_banexception_chartotype(*p);
   2471 		if ((extype & TKL_SPAMF) || (extype & TKL_SHUN) || (extype & TKL_NAME))
   2472 		{
   2473 			/* For spamfilter, shun and qline we don't care
   2474 			 * whether they are global or not. That would only
   2475 			 * be confusing to the admin.
   2476 			 */
   2477 			extype &= ~TKL_GLOBAL;
   2478 			if (bantype & extype)
   2479 				return 1;
   2480 		} else {
   2481 			/* Rest requires an exact match */
   2482 			if (bantype == extype)
   2483 				return 1;
   2484 		}
   2485 	}
   2486 
   2487 	return 0;
   2488 }
   2489 
   2490 /** Used for finding out which element of the tkl_ip hash table is used (primary element) */
   2491 int _tkl_ip_hash(char *ip)
   2492 {
   2493 	char ipbuf[64], *p;
   2494 
   2495 	for (p = ip; *p; p++)
   2496 	{
   2497 		if ((*p == '?') || (*p == '*') || (*p == '/'))
   2498 			return -1; /* not an entry suitable for the ip hash table */
   2499 	}
   2500 	if (inet_pton(AF_INET, ip, &ipbuf) == 1)
   2501 	{
   2502 		/* IPv4 */
   2503 		unsigned int v = (ipbuf[0] << 24) +
   2504 		                 (ipbuf[1] << 16) +
   2505 		                 (ipbuf[2] << 8)  +
   2506 		                 ipbuf[3];
   2507 		return v % TKLIPHASHLEN2;
   2508 	} else
   2509 	if (inet_pton(AF_INET6, ip, &ipbuf) == 1)
   2510 	{
   2511 		/* IPv6 (only upper 64 bits) */
   2512 		unsigned int v1 = (ipbuf[0] << 24) +
   2513 		                 (ipbuf[1] << 16) +
   2514 		                 (ipbuf[2] << 8)  +
   2515 		                 ipbuf[3];
   2516 		unsigned int v2 = (ipbuf[4] << 24) +
   2517 		                 (ipbuf[5] << 16) +
   2518 		                 (ipbuf[6] << 8)  +
   2519 		                 ipbuf[7];
   2520 		return (v1 ^ v2) % TKLIPHASHLEN2;
   2521 	} else
   2522 	{
   2523 		return -1;
   2524 	}
   2525 }
   2526 
   2527 // TODO: consider efunc
   2528 int tkl_ip_hash_tkl(TKL *tkl)
   2529 {
   2530 	if (TKLIsServerBan(tkl))
   2531 		return tkl_ip_hash(tkl->ptr.serverban->hostmask);
   2532 	if (TKLIsBanException(tkl))
   2533 		return tkl_ip_hash(tkl->ptr.banexception->hostmask);
   2534 	return -1;
   2535 }
   2536 
   2537 /** Used for finding out which tkl_ip hash table needs to be used (secondary element).
   2538  * NOTE: Returns -1 for types that are never on the TKL ip hash table, such as spamfilter.
   2539  *       This can be used by the caller as a quick way to find out if the type is supported.
   2540  */
   2541 int _tkl_ip_hash_type(int type)
   2542 {
   2543 	if ((type == 'Z') || (type == 'z'))
   2544 		return 0;
   2545 	else if (type == 'G')
   2546 		return 1;
   2547 	else if (type == 'k')
   2548 		return 2;
   2549 	else if ((type == 'e') || (type == 'E'))
   2550 		return 3;
   2551 	else
   2552 		return -1;
   2553 }
   2554 
   2555 /* Find the appropriate list 'head' that we need to iterate.
   2556  * This is simply a helper that is used at 3 places and I hate duplicate code.
   2557  * NOTE: this function may return NULL.
   2558  */
   2559 TKL *tkl_find_head(char type, char *hostmask, TKL *def)
   2560 {
   2561 	int index, index2;
   2562 
   2563 	/* First, check ip hash table TKL's... */
   2564 	index = tkl_ip_hash_type(type);
   2565 	if (index >= 0)
   2566 	{
   2567 		index2 = tkl_ip_hash(hostmask);
   2568 		if (index2 >= 0)
   2569 		{
   2570 			/* iterate tklines_ip_hash[index][index2] */
   2571 			return tklines_ip_hash[index][index2];
   2572 		}
   2573 	}
   2574 	/* Fallback to the default */
   2575 	return def;
   2576 }
   2577 
   2578 /** Add a spamfilter entry to the list.
   2579  * @param type                TKL_SPAMF or TKL_SPAMF|TKL_GLOBAL.
   2580  * @param target              The spamfilter target (SPAMF_*)
   2581  * @param action              The spamfilter action (BAN_ACT_*)
   2582  * @param match               The match (this struct may contain a regex for example)
   2583  * @param set_by              Who (or what) set the ban
   2584  * @param expire_at           When will the ban expire (0 for permanent)
   2585  * @param set_at              When was the ban set
   2586  * @param spamf_tkl_duration  When will the ban placed by spamfilter expire
   2587  * @param spamf_tkl_reason    What is the reason for bans placed by spamfilter
   2588  * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
   2589  * @returns                   The TKL entry, or NULL in case of a problem,
   2590  *                            such as a regex failing to compile, memory problem, ..
   2591  */
   2592 TKL *_tkl_add_spamfilter(int type, unsigned short target, BanAction action, Match *match, char *set_by,
   2593                              time_t expire_at, time_t set_at,
   2594                              time_t tkl_duration, char *tkl_reason,
   2595                              int flags)
   2596 {
   2597 	TKL *tkl;
   2598 	int index;
   2599 
   2600 	if (!(type & TKL_SPAMF))
   2601 		abort();
   2602 
   2603 	tkl = safe_alloc(sizeof(TKL));
   2604 	/* First the common fields */
   2605 	tkl->type = type;
   2606 	tkl->flags = flags;
   2607 	tkl->set_at = set_at;
   2608 	safe_strdup(tkl->set_by, set_by);
   2609 	tkl->expire_at = expire_at;
   2610 	/* Then the spamfilter fields */
   2611 	tkl->ptr.spamfilter = safe_alloc(sizeof(Spamfilter));
   2612 	tkl->ptr.spamfilter->target = target;
   2613 	tkl->ptr.spamfilter->action = action;
   2614 	tkl->ptr.spamfilter->match = match;
   2615 	safe_strdup(tkl->ptr.spamfilter->tkl_reason, tkl_reason);
   2616 	tkl->ptr.spamfilter->tkl_duration = tkl_duration;
   2617 
   2618 	if (tkl->ptr.spamfilter->target & SPAMF_USER)
   2619 		loop.do_bancheck_spamf_user = 1;
   2620 	if (tkl->ptr.spamfilter->target & SPAMF_AWAY)
   2621 		loop.do_bancheck_spamf_away = 1;
   2622 
   2623 	/* Spamfilters go via the normal TKL list... */
   2624 	index = tkl_hash(tkl_typetochar(type));
   2625 	AddListItem(tkl, tklines[index]);
   2626 
   2627 	if (target & SPAMF_MTAG)
   2628 		mtag_spamfilters_present = 1;
   2629 
   2630 	return tkl;
   2631 }
   2632 
   2633 /** Add a server ban TKL entry.
   2634  * @param type                The TKL type, one of TKL_*,
   2635  *                            optionally OR'ed with TKL_GLOBAL.
   2636  * @param usermask            The user mask
   2637  * @param hostmask            The host mask
   2638  * @param reason              The reason for the ban
   2639  * @param set_by              Who (or what) set the ban
   2640  * @param expire_at           When will the ban expire (0 for permanent)
   2641  * @param set_at              When was the ban set
   2642  * @param soft                Whether it's a soft-ban
   2643  * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
   2644  * @returns                   The TKL entry, or NULL in case of a problem,
   2645  *                            such as a regex failing to compile, memory problem, ..
   2646  * @note
   2647  * Be sure not to call this function for spamfilters,
   2648  * qlines or exempts, which have their own function!
   2649  */
   2650 TKL *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, char *set_by,
   2651                            time_t expire_at, time_t set_at, int soft, int flags)
   2652 {
   2653 	TKL *tkl;
   2654 	int index, index2;
   2655 
   2656 	if (!TKLIsServerBanType(type))
   2657 		abort();
   2658 
   2659 	tkl = safe_alloc(sizeof(TKL));
   2660 	/* First the common fields */
   2661 	tkl->type = type;
   2662 	tkl->flags = flags;
   2663 	tkl->set_at = set_at;
   2664 	safe_strdup(tkl->set_by, set_by);
   2665 	tkl->expire_at = expire_at;
   2666 	/* Now the server ban fields */
   2667 	tkl->ptr.serverban = safe_alloc(sizeof(ServerBan));
   2668 	safe_strdup(tkl->ptr.serverban->usermask, usermask);
   2669 	safe_strdup(tkl->ptr.serverban->hostmask, hostmask);
   2670 	if (soft)
   2671 		tkl->ptr.serverban->subtype = TKL_SUBTYPE_SOFT;
   2672 	safe_strdup(tkl->ptr.serverban->reason, reason);
   2673 
   2674 	/* For ip hash table TKL's... */
   2675 	index = tkl_ip_hash_type(tkl_typetochar(type));
   2676 	if (index >= 0)
   2677 	{
   2678 		index2 = tkl_ip_hash_tkl(tkl);
   2679 		if (index2 >= 0)
   2680 		{
   2681 			AddListItem(tkl, tklines_ip_hash[index][index2]);
   2682 			return tkl;
   2683 		}
   2684 	}
   2685 
   2686 	/* If we get here it's just for our normal list.. */
   2687 	index = tkl_hash(tkl_typetochar(type));
   2688 	AddListItem(tkl, tklines[index]);
   2689 
   2690 	return tkl;
   2691 }
   2692 
   2693 /** Add a ban exception TKL entry.
   2694  * @param type                TKL_EXCEPTION or TKLEXCEPT|TKL_GLOBAL.
   2695  * @param usermask            The user mask
   2696  * @param hostmask            The host mask
   2697  * @param match               A securitygroup used for matching (can be NULL,
   2698  *                            if not NULL then this field is used as-is and not copied
   2699  *                            so caller should not free!)
   2700  * @param reason              The reason for the ban
   2701  * @param set_by              Who (or what) set the ban
   2702  * @param expire_at           When will the ban expire (0 for permanent)
   2703  * @param set_at              When was the ban set
   2704  * @param soft                Whether it's a soft-ban
   2705  * @param bantypes            The ban types to exempt from
   2706  * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
   2707  * @returns                   The TKL entry, or NULL in case of a problem,
   2708  *                            such as a regex failing to compile, memory problem, ..
   2709  * @note
   2710  * Be sure not to call this function for spamfilters,
   2711  * qlines or exempts, which have their own function!
   2712  */
   2713 TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGroup *match,
   2714                            char *reason, char *set_by,
   2715                            time_t expire_at, time_t set_at, int soft, char *bantypes, int flags)
   2716 {
   2717 	TKL *tkl;
   2718 	int index, index2;
   2719 
   2720 	if (!TKLIsBanExceptionType(type))
   2721 		abort();
   2722 	tkl = safe_alloc(sizeof(TKL));
   2723 	/* First the common fields */
   2724 	tkl->type = type;
   2725 	tkl->flags = flags;
   2726 	tkl->set_at = set_at;
   2727 	safe_strdup(tkl->set_by, set_by);
   2728 	tkl->expire_at = expire_at;
   2729 	/* Now the ban except fields */
   2730 	tkl->ptr.banexception = safe_alloc(sizeof(BanException));
   2731 	safe_strdup(tkl->ptr.banexception->usermask, usermask);
   2732 	safe_strdup(tkl->ptr.banexception->hostmask, hostmask);
   2733 	tkl->ptr.banexception->match = match;
   2734 	if (soft)
   2735 		tkl->ptr.banexception->subtype = TKL_SUBTYPE_SOFT;
   2736 	safe_strdup(tkl->ptr.banexception->bantypes, bantypes);
   2737 	safe_strdup(tkl->ptr.banexception->reason, reason);
   2738 
   2739 	/* For ip hash table TKL's... */
   2740 	index = tkl_ip_hash_type(tkl_typetochar(type));
   2741 	if (index >= 0)
   2742 	{
   2743 		index2 = tkl_ip_hash_tkl(tkl);
   2744 		if (index2 >= 0)
   2745 		{
   2746 			AddListItem(tkl, tklines_ip_hash[index][index2]);
   2747 			return tkl;
   2748 		}
   2749 	}
   2750 
   2751 	/* If we get here it's just for our normal list.. */
   2752 	index = tkl_hash(tkl_typetochar(type));
   2753 	AddListItem(tkl, tklines[index]);
   2754 
   2755 	return tkl;
   2756 }
   2757 
   2758 /** Add a name ban TKL entry (Q-Line), used for banning nicks and channels.
   2759  * @param type                The TKL type, one of TKL_*,
   2760  *                            optionally OR'ed with TKL_GLOBAL.
   2761  * @param name                The nick or channel to be banned (wildcards accepted)
   2762  * @param hold                Flag to indicate services hold
   2763  * @param reason              The reason for the ban
   2764  * @param set_by              Who (or what) set the ban
   2765  * @param expire_at           When will the ban expire (0 for permanent)
   2766  * @param set_at              When was the ban set
   2767  * @param flags               Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
   2768  * @returns                   The TKL entry, or NULL in case of a problem,
   2769  *                            such as a regex failing to compile, memory problem, ..
   2770  * @note
   2771  * Be sure not to call this function for spamfilters,
   2772  * qlines or exempts, which have their own function!
   2773  */
   2774 TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
   2775                           time_t expire_at, time_t set_at, int flags)
   2776 {
   2777 	TKL *tkl;
   2778 	int index;
   2779 
   2780 	if (!TKLIsNameBanType(type))
   2781 		abort();
   2782 
   2783 	tkl = safe_alloc(sizeof(TKL));
   2784 	/* First the common fields */
   2785 	tkl->type = type;
   2786 	tkl->flags = flags;
   2787 	tkl->set_at = set_at;
   2788 	safe_strdup(tkl->set_by, set_by);
   2789 	tkl->expire_at = expire_at;
   2790 	/* Now the name ban fields */
   2791 	tkl->ptr.nameban = safe_alloc(sizeof(ServerBan));
   2792 	safe_strdup(tkl->ptr.nameban->name, name);
   2793 	tkl->ptr.nameban->hold = hold;
   2794 	safe_strdup(tkl->ptr.nameban->reason, reason);
   2795 
   2796 	/* Name bans go via the normal TKL list.. */
   2797 	index = tkl_hash(tkl_typetochar(type));
   2798 	AddListItem(tkl, tklines[index]);
   2799 
   2800 	return tkl;
   2801 }
   2802 
   2803 
   2804 /** Free a TKL entry but do not remove from the list.
   2805  * (this assumes that it was not added yet or is already removed)
   2806  * Most people will use tkl_del_line() instead.
   2807  */
   2808 void _free_tkl(TKL *tkl)
   2809 {
   2810 	/* Free the entry */
   2811 	/* First, the common fields */
   2812 	safe_free(tkl->set_by);
   2813 	/* Now the type specific fields */
   2814 	if (TKLIsServerBan(tkl) && tkl->ptr.serverban)
   2815 	{
   2816 		safe_free(tkl->ptr.serverban->usermask);
   2817 		safe_free(tkl->ptr.serverban->hostmask);
   2818 		safe_free(tkl->ptr.serverban->reason);
   2819 		safe_free(tkl->ptr.serverban);
   2820 	} else
   2821 	if (TKLIsNameBan(tkl) && tkl->ptr.nameban)
   2822 	{
   2823 		safe_free(tkl->ptr.nameban->name);
   2824 		safe_free(tkl->ptr.nameban->reason);
   2825 		safe_free(tkl->ptr.nameban);
   2826 	} else
   2827 	if (TKLIsSpamfilter(tkl) && tkl->ptr.spamfilter)
   2828 	{
   2829 		/* Spamfilter */
   2830 		safe_free(tkl->ptr.spamfilter->tkl_reason);
   2831 		if (tkl->ptr.spamfilter->match)
   2832 			unreal_delete_match(tkl->ptr.spamfilter->match);
   2833 		safe_free(tkl->ptr.spamfilter);
   2834 	} else
   2835 	if (TKLIsBanException(tkl) && tkl->ptr.banexception)
   2836 	{
   2837 		safe_free(tkl->ptr.banexception->usermask);
   2838 		safe_free(tkl->ptr.banexception->hostmask);
   2839 		if (tkl->ptr.banexception->match)
   2840 			free_security_group(tkl->ptr.banexception->match);
   2841 		safe_free(tkl->ptr.banexception->bantypes);
   2842 		safe_free(tkl->ptr.banexception->reason);
   2843 		safe_free(tkl->ptr.banexception);
   2844 	}
   2845 	safe_free(tkl);
   2846 }
   2847 
   2848 /** Delete a TKL entry from the list and free it.
   2849  * @param tkl The TKL entry.
   2850  */
   2851 void _tkl_del_line(TKL *tkl)
   2852 {
   2853 	int index, index2;
   2854 	int found = 0;
   2855 
   2856 	/* Try to find it in the ip TKL hash table first
   2857 	 * (this only applies to server bans)
   2858 	 */
   2859 	index = tkl_ip_hash_type(tkl_typetochar(tkl->type));
   2860 	if (index >= 0)
   2861 	{
   2862 		index2 = tkl_ip_hash_tkl(tkl);
   2863 		if (index2 >= 0)
   2864 		{
   2865 #if 1
   2866 			/* Temporary validation until an rmtkl(?) bug is fixed */
   2867 			TKL *d;
   2868 			int really_found = 0;
   2869 			for (d = tklines_ip_hash[index][index2]; d; d = d->next)
   2870 				if (d == tkl)
   2871 				{
   2872 					really_found = 1;
   2873 					break;
   2874 				}
   2875 			if (!really_found)
   2876 			{
   2877 				unreal_log(ULOG_FATAL, "tkl", "BUG_TKL_DEL_LINE_HASH", NULL,
   2878 				           "[BUG] [Crash] tkl_del_line() for $tkl (type: $tkl.type_string): "
   2879 				           "NOT found in tklines_ip_hash. This should never happen!",
   2880 				           log_data_tkl("tkl", tkl));
   2881 				abort();
   2882 			}
   2883 #endif
   2884 			DelListItem(tkl, tklines_ip_hash[index][index2]);
   2885 			found = 1;
   2886 		}
   2887 	}
   2888 
   2889 	if (!found)
   2890 	{
   2891 		/* If we get here it's just for our normal list.. */
   2892 		index = tkl_hash(tkl_typetochar(tkl->type));
   2893 		DelListItem(tkl, tklines[index]);
   2894 	}
   2895 
   2896 	/* Finally, free the entry */
   2897 	free_tkl(tkl);
   2898 	check_mtag_spamfilters_present();
   2899 }
   2900 
   2901 /** Add some default ban exceptions - for localhost */
   2902 static void add_default_exempts(void)
   2903 {
   2904 	/* The exempted ban types are only ones that will affect other connections as well,
   2905 	 * such as gline, and not policy decissions such as maxperip exempt or bypass qlines.
   2906 	 * Currently the list is: gline, kline, gzline, zline, shun, blacklist,
   2907 	 *                        connect-flood, handshake-data-flood.
   2908 	 */
   2909 	tkl_add_banexception(TKL_EXCEPTION, "*", "127.0.0.1", NULL, "localhost is always exempt",
   2910 	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
   2911 	tkl_add_banexception(TKL_EXCEPTION, "*", "::1", NULL, "localhost is always exempt",
   2912 	                     "-default-", 0, TStime(), 0, "GkZzsbcd", TKL_FLAG_CONFIG);
   2913 }
   2914 
   2915 /*
   2916  * tkl_check_local_remove_shun:
   2917  * removes shun from currently connected users affected by tmp.
   2918  */
   2919 // TODO / FIXME: audit this function, it looks crazy
   2920 void _tkl_check_local_remove_shun(TKL *tmp)
   2921 {
   2922 	long i;
   2923 	char *chost, *cname, *cip;
   2924 	int is_ip;
   2925 	Client *client;
   2926 
   2927 	TKL *tk;
   2928 	int keep_shun;
   2929 
   2930 	for (i = 0; i <= 5; i++)
   2931 	{
   2932 		list_for_each_entry(client, &lclient_list, lclient_node)
   2933 			if (MyUser(client) && IsShunned(client))
   2934 			{
   2935 				chost = client->local->sockhost;
   2936 				cname = client->user->username;
   2937 
   2938 				cip = GetIP(client);
   2939 
   2940 				if ((*tmp->ptr.serverban->hostmask >= '0') && (*tmp->ptr.serverban->hostmask <= '9'))
   2941 					is_ip = 1;
   2942 				else
   2943 					is_ip = 0;
   2944 
   2945 				if (is_ip == 0 ?
   2946 				    (match_simple(tmp->ptr.serverban->hostmask, chost) && match_simple(tmp->ptr.serverban->usermask, cname)) :
   2947 				    (match_simple(tmp->ptr.serverban->hostmask, chost) || match_simple(tmp->ptr.serverban->hostmask, cip))
   2948 				    && match_simple(tmp->ptr.serverban->usermask, cname))
   2949 				{
   2950 					/*
   2951 					  before blindly marking this user as un-shunned, we need to check
   2952 					  if the user is under any other existing shuns. (#0003906)
   2953 					  Unfortunately, this requires crazy amounts of indentation ;-).
   2954 
   2955 					  This enumeration code is based off of _tkl_stats()
   2956 					 */
   2957 					keep_shun = 0;
   2958 					for(tk = tklines[tkl_hash('s')]; tk && !keep_shun; tk = tk->next)
   2959 						if (tk != tmp && match_simple(tk->ptr.serverban->usermask, cname))
   2960 						{
   2961 							if ((*tk->ptr.serverban->hostmask >= '0') && (*tk->ptr.serverban->hostmask <= '9')
   2962 							    /* the hostmask is an IP */
   2963 							    && (match_simple(tk->ptr.serverban->hostmask, chost) || match_simple(tk->ptr.serverban->hostmask, cip)))
   2964 								keep_shun = 1;
   2965 							else
   2966 								/* the hostmask is not an IP */
   2967 								if (match_simple(tk->ptr.serverban->hostmask, chost) && match_simple(tk->ptr.serverban->usermask, cname))
   2968 									keep_shun = 1;
   2969 						}
   2970 
   2971 					if (!keep_shun)
   2972 					{
   2973 						ClearShunned(client);
   2974 					}
   2975 				}
   2976 			}
   2977 	}
   2978 }
   2979 
   2980 
   2981 /** This returns something like user@host, or %user@host, or ~a:Trusted
   2982  * that can be used in oper notices like expiring kline, added kline, etc.
   2983  */
   2984 #define NO_SOFT_PREFIX	1
   2985 char *_tkl_uhost(TKL *tkl, char *buf, size_t buflen, int options)
   2986 {
   2987 	if (TKLIsServerBan(tkl))
   2988 	{
   2989 		if (is_extended_server_ban(tkl->ptr.serverban->usermask))
   2990 		{
   2991 			ircsnprintf(buf, buflen, "%s%s%s",
   2992 				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
   2993 				tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
   2994 		} else {
   2995 			ircsnprintf(buf, buflen, "%s%s@%s",
   2996 				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
   2997 				tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
   2998 		}
   2999 	} else
   3000 	if (TKLIsBanException(tkl))
   3001 	{
   3002 		if (is_extended_server_ban(tkl->ptr.banexception->usermask))
   3003 		{
   3004 			ircsnprintf(buf, buflen, "%s%s%s",
   3005 				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
   3006 				tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask);
   3007 		} else {
   3008 			ircsnprintf(buf, buflen, "%s%s@%s",
   3009 				(!(options & NO_SOFT_PREFIX) && (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT)) ? "%" : "",
   3010 				tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask);
   3011 		}
   3012 	} else
   3013 		abort();
   3014 
   3015 	return buf;
   3016 }
   3017 
   3018 /** Deal with expiration of a specific TKL entry.
   3019  * This is a helper function for tkl_check_expire().
   3020  */
   3021 void tkl_expire_entry(TKL *tkl)
   3022 {
   3023 	if (TKLIsServerBan(tkl))
   3024 	{
   3025 		unreal_log(ULOG_INFO, "tkl", "TKL_EXPIRE", NULL,
   3026 		           "Expiring $tkl.type_string '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
   3027 		           log_data_tkl("tkl", tkl));
   3028 	}
   3029 	else if (TKLIsNameBan(tkl))
   3030 	{
   3031 		if (!tkl->ptr.nameban->hold)
   3032 		{
   3033 			unreal_log(ULOG_INFO, "tkl", "TKL_EXPIRE", NULL,
   3034 			           "Expiring $tkl.type_string '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
   3035 				   log_data_tkl("tkl", tkl));
   3036 		}
   3037 	}
   3038 	else if (TKLIsBanException(tkl))
   3039 	{
   3040 		unreal_log(ULOG_INFO, "tkl", "TKL_EXPIRE", NULL,
   3041 			   "Expiring $tkl.type_string '$tkl' [type: $tkl.exception_types] [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
   3042 			   log_data_tkl("tkl", tkl));
   3043 	}
   3044 
   3045 	// FIXME: so.. this isn't logged? or what?
   3046 	if (tkl->type & TKL_SHUN)
   3047 		tkl_check_local_remove_shun(tkl);
   3048 
   3049 	RunHook(HOOKTYPE_TKL_DEL, NULL, tkl);
   3050 	tkl_del_line(tkl);
   3051 }
   3052 
   3053 /** Regularly check TKL entries for expiration */
   3054 EVENT(tkl_check_expire)
   3055 {
   3056 	TKL *tkl, *next;
   3057 	time_t nowtime;
   3058 	int index, index2;
   3059 
   3060 	nowtime = TStime();
   3061 
   3062 	/* First, hashed entries.. */
   3063 	for (index = 0; index < TKLIPHASHLEN1; index++)
   3064 	{
   3065 		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
   3066 		{
   3067 			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = next)
   3068 			{
   3069 				next = tkl->next;
   3070 				if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
   3071 				{
   3072 					tkl_expire_entry(tkl);
   3073 				}
   3074 			}
   3075 		}
   3076 	}
   3077 
   3078 	/* Now normal entries.. */
   3079 	for (index = 0; index < TKLISTLEN; index++)
   3080 	{
   3081 		for (tkl = tklines[index]; tkl; tkl = next)
   3082 		{
   3083 			next = tkl->next;
   3084 			if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
   3085 			{
   3086 				tkl_expire_entry(tkl);
   3087 			}
   3088 		}
   3089 	}
   3090 }
   3091 
   3092 /* This is just a helper function for find_tkl_exception() */
   3093 static int find_tkl_exception_matcher(Client *client, int ban_type, TKL *except_tkl)
   3094 {
   3095 	char uhost[NICKLEN+HOSTLEN+1];
   3096 
   3097 	if (!TKLIsBanException(except_tkl))
   3098 		return 0;
   3099 
   3100 	if (!tkl_banexception_matches_type(except_tkl, ban_type))
   3101 		return 0;
   3102 
   3103 	/* For config file except ban { } we use security groups instead of simple user/host */
   3104 	if (except_tkl->ptr.banexception->match)
   3105 		return user_allowed_by_security_group(client, except_tkl->ptr.banexception->match);
   3106 
   3107 	tkl_uhost(except_tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
   3108 
   3109 	if (match_user(uhost, client, MATCH_CHECK_REAL))
   3110 	{
   3111 		if (!(except_tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT))
   3112 			return 1; /* hard ban exempt */
   3113 		if ((except_tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) && IsLoggedIn(client))
   3114 			return 1; /* soft ban exempt - only matches if user is logged in */
   3115 	}
   3116 
   3117 	return 0; /* not found */
   3118 }
   3119 
   3120 /** Search for TKL Exceptions for this user.
   3121  * @param ban_type   The ban type to check, normally ban_tkl->type.
   3122  * @param client     The user
   3123  * @returns 1 if ban exempt, 0 if not.
   3124  * @note
   3125  * If you have a TKL ban that matched, say, 'ban_tkl'.
   3126  * Then you call this function like this:
   3127  * if (find_tkl_exception(ban_tkl->type, client))
   3128  *     return 0; // User is exempt
   3129  * [.. continue and ban the user..]
   3130  */
   3131 int _find_tkl_exception(int ban_type, Client *client)
   3132 {
   3133 	TKL *tkl;
   3134 	int index, index2;
   3135 	Hook *hook;
   3136 
   3137 	if (IsServer(client) || IsMe(client))
   3138 		return 1;
   3139 
   3140 	/* First, the TKL ip hash table entries.. */
   3141 	index = tkl_ip_hash_type('e');
   3142 	index2 = tkl_ip_hash(GetIP(client));
   3143 	if (index2 >= 0)
   3144 	{
   3145 		for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
   3146 		{
   3147 			if (find_tkl_exception_matcher(client, ban_type, tkl))
   3148 				return 1; /* exempt */
   3149 		}
   3150 	}
   3151 
   3152 	/* If not banned (yet), then check regular entries.. */
   3153 	for (tkl = tklines[tkl_hash('e')]; tkl; tkl = tkl->next)
   3154 	{
   3155 			if (find_tkl_exception_matcher(client, ban_type, tkl))
   3156 				return 1; /* exempt */
   3157 	}
   3158 
   3159 	for (hook = Hooks[HOOKTYPE_TKL_EXCEPT]; hook; hook = hook->next)
   3160 	{
   3161 		if (hook->func.intfunc(client, ban_type) > 0)
   3162 			return 1; /* exempt by hook */
   3163 	}
   3164 	return 0; /* Not exempt */
   3165 }
   3166 
   3167 /** Helper function for find_tkline_match() */
   3168 int find_tkline_match_matcher(Client *client, int skip_soft, TKL *tkl)
   3169 {
   3170 	char uhost[NICKLEN+HOSTLEN+1];
   3171 
   3172 	if (!TKLIsServerBan(tkl) || (tkl->type & TKL_SHUN))
   3173 		return 0;
   3174 
   3175 	if (skip_soft && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT))
   3176 		return 0;
   3177 
   3178 	tkl_uhost(tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
   3179 
   3180 	if (match_user(uhost, client, MATCH_CHECK_REAL))
   3181 	{
   3182 		/* If hard-ban, or soft-ban&unauthenticated.. */
   3183 		if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
   3184 		    ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(client)))
   3185 		{
   3186 			/* Found match. Now check for exception... */
   3187 			if (find_tkl_exception(tkl->type, client))
   3188 				return 0; /* exempted */
   3189 			return 1; /* banned */
   3190 		}
   3191 	}
   3192 
   3193 	return 0; /* no match */
   3194 }
   3195 
   3196 /** Check if user matches a *LINE. If so, kill the user.
   3197  * @retval 1 if client is banned, 0 if not
   3198  * @note Do not continue processing if the client is killed (0 return value).
   3199  * @note Return value changed with regards to UnrealIRCd 4!
   3200  */
   3201 int _find_tkline_match(Client *client, int skip_soft)
   3202 {
   3203 	TKL *tkl;
   3204 	int banned = 0;
   3205 	int index, index2;
   3206 
   3207 	if (IsServer(client) || IsMe(client))
   3208 		return 0;
   3209 
   3210 	/* First, the TKL ip hash table entries.. */
   3211 	index2 = tkl_ip_hash(GetIP(client));
   3212 	if (index2 >= 0)
   3213 	{
   3214 		for (index = 0; index < TKLIPHASHLEN1; index++)
   3215 		{
   3216 			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
   3217 			{
   3218 				banned = find_tkline_match_matcher(client, skip_soft, tkl);
   3219 				if (banned)
   3220 					break;
   3221 			}
   3222 			if (banned)
   3223 				break;
   3224 		}
   3225 	}
   3226 
   3227 	/* If not banned (yet), then check regular entries.. */
   3228 	if (!banned)
   3229 	{
   3230 		for (index = 0; index < TKLISTLEN; index++)
   3231 		{
   3232 			for (tkl = tklines[index]; tkl; tkl = tkl->next)
   3233 			{
   3234 				banned = find_tkline_match_matcher(client, skip_soft, tkl);
   3235 				if (banned)
   3236 					break;
   3237 			}
   3238 			if (banned)
   3239 				break;
   3240 		}
   3241 	}
   3242 
   3243 	if (!banned)
   3244 		return 0;
   3245 
   3246 	/* User is banned... */
   3247 
   3248 	RunHookReturnInt(HOOKTYPE_FIND_TKLINE_MATCH, !=99, client, tkl);
   3249 
   3250 	if (tkl->type & TKL_KILL)
   3251 	{
   3252 		ircstats.is_ref++;
   3253 		if (tkl->type & TKL_GLOBAL)
   3254 			banned_client(client, "G-Lined", tkl->ptr.serverban->reason, 1, 0);
   3255 		else
   3256 			banned_client(client, "K-Lined", tkl->ptr.serverban->reason, 0, 0);
   3257 		return 1; /* killed */
   3258 	} else
   3259 	if (tkl->type & TKL_ZAP)
   3260 	{
   3261 		ircstats.is_ref++;
   3262 		banned_client(client, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
   3263 		return 1; /* killed */
   3264 	}
   3265 
   3266 	return 0;
   3267 }
   3268 
   3269 /** Check if user is shunned.
   3270  * @param client   Client to check.
   3271  * @returns 1 if shunned, 0 if not.
   3272  */
   3273 int _find_shun(Client *client)
   3274 {
   3275 	TKL *tkl;
   3276 
   3277 	if (IsServer(client) || IsMe(client))
   3278 		return 0;
   3279 
   3280 	if (IsShunned(client))
   3281 		return 1;
   3282 
   3283 	if (ValidatePermissionsForPath("immune:server-ban:shun",client,NULL,NULL,NULL))
   3284 		return 0;
   3285 
   3286 	for (tkl = tklines[tkl_hash('s')]; tkl; tkl = tkl->next)
   3287 	{
   3288 		char uhost[NICKLEN+HOSTLEN+1];
   3289 
   3290 		if (!(tkl->type & TKL_SHUN))
   3291 			continue;
   3292 
   3293 		tkl_uhost(tkl, uhost, sizeof(uhost), NO_SOFT_PREFIX);
   3294 
   3295 		if (match_user(uhost, client, MATCH_CHECK_REAL))
   3296 		{
   3297 			/* If hard-ban, or soft-ban&unauthenticated.. */
   3298 			if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
   3299 			    ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(client)))
   3300 			{
   3301 				/* Found match. Now check for exception... */
   3302 				if (find_tkl_exception(TKL_SHUN, client))
   3303 					return 0;
   3304 				SetShunned(client);
   3305 				return 1;
   3306 			}
   3307 		}
   3308 	}
   3309 
   3310 	return 0;
   3311 }
   3312 
   3313 /** Helper function for spamfilter_build_user_string().
   3314  * This ensures IPv6 hosts are in brackets.
   3315  */
   3316 char *SpamfilterMagicHost(char *i)
   3317 {
   3318 	static char buf[256];
   3319 
   3320 	if (!strchr(i, ':'))
   3321 		return i;
   3322 
   3323 	/* otherwise, it's IPv6.. prepend it with [ and append a ] */
   3324 	ircsnprintf(buf, sizeof(buf), "[%s]", i);
   3325 	return buf;
   3326 }
   3327 
   3328 /** Build the nick:user@host:realname string
   3329  * @param buf     The buffer used for storage, the size of
   3330  *                which should be at least NICKLEN+USERLEN+HOSTLEN+1.
   3331  * @param nick    The nickname (because client can be nick-changing).
   3332  * @param client  The affected client.
   3333  */
   3334 void _spamfilter_build_user_string(char *buf, char *nick, Client *client)
   3335 {
   3336 	snprintf(buf, NICKLEN+USERLEN+HOSTLEN+1, "%s!%s@%s:%s",
   3337 		nick, client->user->username, SpamfilterMagicHost(client->user->realhost), client->info);
   3338 }
   3339 
   3340 
   3341 /** Checks if the user matches a spamfilter of type 'u' (user,
   3342  * nick!user@host:realname ban).
   3343  * Written by: Syzop
   3344  * Assumes: only call for clients, possible assume on local clients [?]
   3345  * Return values: see match_spamfilter()
   3346  */
   3347 int _find_spamfilter_user(Client *client, int flags)
   3348 {
   3349 	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
   3350 
   3351 	if (ValidatePermissionsForPath("immune:server-ban:spamfilter",client,NULL,NULL,NULL))
   3352 		return 0;
   3353 
   3354 	spamfilter_build_user_string(spamfilter_user, client->name, client);
   3355 	return match_spamfilter(client, spamfilter_user, SPAMF_USER, NULL, NULL, flags, NULL);
   3356 }
   3357 
   3358 /** Check a spamfilter against all local users and print a message.
   3359  * This is only used for the 'warn' action (BAN_ACT_WARN).
   3360  */
   3361 int spamfilter_check_users(TKL *tkl)
   3362 {
   3363 	char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
   3364 	char buf[1024];
   3365 	int matches = 0;
   3366 	Client *client;
   3367 
   3368 	list_for_each_entry_reverse(client, &lclient_list, lclient_node)
   3369 	{
   3370 		if (MyUser(client))
   3371 		{
   3372 			spamfilter_build_user_string(spamfilter_user, client->name, client);
   3373 			if (!unreal_match(tkl->ptr.spamfilter->match, spamfilter_user))
   3374 				continue; /* No match */
   3375 
   3376 			/* matched! */
   3377 			unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
   3378 			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
   3379 				   log_data_tkl("tkl", tkl),
   3380 				   log_data_string("command", "USER"),
   3381 				   log_data_string("str", spamfilter_user));
   3382 
   3383 			RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, spamfilter_user, spamfilter_user, SPAMF_USER, NULL, tkl);
   3384 			matches++;
   3385 		}
   3386 	}
   3387 
   3388 	return matches;
   3389 }
   3390 
   3391 /** Check if the nick or channel name is banned (Q-Line).
   3392  * @param client   The possibly affected user.
   3393  * @param name     The nick or channel to check.
   3394  * @param is_hold  This will be SET (so OUT) if it's a services hold.
   3395  *
   3396  * @note Special handling:
   3397  * #*ble* will match with #bbleh
   3398  * *ble* will NOT match with #bbleh, will with bbleh
   3399  */
   3400 TKL *_find_qline(Client *client, char *name, int *ishold)
   3401 {
   3402 	TKL *tkl;
   3403 	int	points = 0;
   3404 	*ishold = 0;
   3405 
   3406 	if (IsServer(client) || IsMe(client))
   3407 		return NULL;
   3408 
   3409 	for (tkl = tklines[tkl_hash('q')]; tkl; tkl = tkl->next)
   3410 	{
   3411 		points = 0;
   3412 
   3413 		if (!TKLIsNameBan(tkl))
   3414 			continue;
   3415 
   3416 		if (((*tkl->ptr.nameban->name == '#' && *name == '#') || (*tkl->ptr.nameban->name != '#' && *name != '#'))
   3417 		    && match_simple(tkl->ptr.nameban->name, name))
   3418 		{
   3419 			points = 1;
   3420 			break;
   3421 		}
   3422 	}
   3423 
   3424 	if (points != 1)
   3425 		return NULL;
   3426 
   3427 	/* It's a services hold (except bans don't override this) */
   3428 	if (tkl->ptr.nameban->hold)
   3429 	{
   3430 		*ishold = 1;
   3431 		return tkl;
   3432 	}
   3433 
   3434 	if (find_tkl_exception(TKL_NAME, client))
   3435 		return NULL; /* exempt */
   3436 
   3437 	return tkl;
   3438 }
   3439 
   3440 /** Helper function for find_tkline_match_zap() */
   3441 TKL *find_tkline_match_zap_matcher(Client *client, TKL *tkl)
   3442 {
   3443 	if (!(tkl->type & TKL_ZAP))
   3444 		return NULL;
   3445 
   3446 	if (match_user(tkl->ptr.serverban->hostmask, client, MATCH_CHECK_IP))
   3447 	{
   3448 		if (find_tkl_exception(TKL_ZAP, client))
   3449 			return NULL; /* exempt */
   3450 		return tkl; /* banned */
   3451 	}
   3452 
   3453 	return NULL; /* no match */
   3454 }
   3455 
   3456 /** Find matching (G)ZLINE, if any.
   3457  * Note: function prototype changed as per UnrealIRCd 4.2.0.
   3458  * @retval The (G)Z-Line that matched, or NULL if no such ban was found.
   3459  */
   3460 TKL *_find_tkline_match_zap(Client *client)
   3461 {
   3462 	TKL *tkl, *ret;
   3463 	int index, index2;
   3464 
   3465 	if (IsServer(client) || IsMe(client))
   3466 		return NULL;
   3467 
   3468 	/* First, the TKL ip hash table entries.. */
   3469 	index = tkl_ip_hash_type('z');
   3470 	index2 = tkl_ip_hash(GetIP(client));
   3471 	if (index2 >= 0)
   3472 	{
   3473 		for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
   3474 		{
   3475 			ret = find_tkline_match_zap_matcher(client, tkl);
   3476 			if (ret)
   3477 				return ret;
   3478 		}
   3479 	}
   3480 
   3481 	/* If not banned (yet), then check regular entries.. */
   3482 	for (tkl = tklines[tkl_hash('z')]; tkl; tkl = tkl->next)
   3483 	{
   3484 		ret = find_tkline_match_zap_matcher(client, tkl);
   3485 		if (ret)
   3486 			return ret;
   3487 	}
   3488 
   3489 	return NULL;
   3490 }
   3491 
   3492 #define BY_MASK 0x1
   3493 #define BY_REASON 0x2
   3494 #define NOT_BY_MASK 0x4
   3495 #define NOT_BY_REASON 0x8
   3496 #define BY_SETBY 0x10
   3497 #define NOT_BY_SETBY 0x20
   3498 
   3499 typedef struct {
   3500 	int flags;
   3501 	const char *mask;
   3502 	const char *reason;
   3503 	const char *set_by;
   3504 } TKLFlag;
   3505 
   3506 /** Parse STATS tkl parameters.
   3507  * TODO: I don't think this is documented anywhere? Or underdocumented at least.
   3508  */
   3509 static void parse_stats_params(const char *para, TKLFlag *flag)
   3510 {
   3511 	static char paratmp[512]; /* <- copy of para, because it gets fragged by strtok() */
   3512 	char *flags, *tmp;
   3513 	char what = '+';
   3514 
   3515 	memset(flag, 0, sizeof(TKLFlag));
   3516 	strlcpy(paratmp, para, sizeof(paratmp));
   3517 	flags = strtok(paratmp, " ");
   3518 	if (!flags)
   3519 		return;
   3520 
   3521 	for (; *flags; flags++)
   3522 	{
   3523 		switch (*flags)
   3524 		{
   3525 			case '+':
   3526 				what = '+';
   3527 				break;
   3528 			case '-':
   3529 				what = '-';
   3530 				break;
   3531 			case 'm':
   3532 				if (flag->mask || !(tmp = strtok(NULL, " ")))
   3533 					continue;
   3534 				if (what == '+')
   3535 					flag->flags |= BY_MASK;
   3536 				else
   3537 					flag->flags |= NOT_BY_MASK;
   3538 				flag->mask = tmp;
   3539 				break;
   3540 			case 'r':
   3541 				if (flag->reason || !(tmp = strtok(NULL, " ")))
   3542 					continue;
   3543 				if (what == '+')
   3544 					flag->flags |= BY_REASON;
   3545 				else
   3546 					flag->flags |= NOT_BY_REASON;
   3547 				flag->reason = tmp;
   3548 				break;
   3549 			case 's':
   3550 				if (flag->set_by || !(tmp = strtok(NULL, " ")))
   3551 					continue;
   3552 				if (what == '+')
   3553 					flag->flags |= BY_SETBY;
   3554 				else
   3555 					flag->flags |= NOT_BY_SETBY;
   3556 				flag->set_by = tmp;
   3557 				break;
   3558 		}
   3559 	}
   3560 }
   3561 
   3562 /** Does this TKL entry match the search terms?
   3563  * This is a helper function for tkl_stats().
   3564  */
   3565 int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklflags, TKL *tkl)
   3566 {
   3567 	/***** First, handle the selection ******/
   3568 
   3569 	if (!BadPtr(para))
   3570 	{
   3571 		if (tklflags->flags & BY_SETBY)
   3572 			if (!match_simple(tklflags->set_by, tkl->set_by))
   3573 				return 0;
   3574 		if (tklflags->flags & NOT_BY_SETBY)
   3575 			if (match_simple(tklflags->set_by, tkl->set_by))
   3576 				return 0;
   3577 		if (TKLIsServerBan(tkl))
   3578 		{
   3579 			if (tklflags->flags & BY_MASK)
   3580 			{
   3581 				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
   3582 					return 0;
   3583 			}
   3584 			if (tklflags->flags & NOT_BY_MASK)
   3585 			{
   3586 				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
   3587 					return 0;
   3588 			}
   3589 			if (tklflags->flags & BY_REASON)
   3590 				if (!match_simple(tklflags->reason, tkl->ptr.serverban->reason))
   3591 					return 0;
   3592 			if (tklflags->flags & NOT_BY_REASON)
   3593 				if (match_simple(tklflags->reason, tkl->ptr.serverban->reason))
   3594 					return 0;
   3595 		} else
   3596 		if (TKLIsNameBan(tkl))
   3597 		{
   3598 			if (tklflags->flags & BY_MASK)
   3599 			{
   3600 				if (!match_simple(tklflags->mask, tkl->ptr.nameban->name))
   3601 					return 0;
   3602 			}
   3603 			if (tklflags->flags & NOT_BY_MASK)
   3604 			{
   3605 				if (match_simple(tklflags->mask, tkl->ptr.nameban->name))
   3606 					return 0;
   3607 			}
   3608 			if (tklflags->flags & BY_REASON)
   3609 				if (!match_simple(tklflags->reason, tkl->ptr.nameban->reason))
   3610 					return 0;
   3611 			if (tklflags->flags & NOT_BY_REASON)
   3612 				if (match_simple(tklflags->reason, tkl->ptr.nameban->reason))
   3613 					return 0;
   3614 		} else
   3615 		if (TKLIsBanException(tkl))
   3616 		{
   3617 			if (tklflags->flags & BY_MASK)
   3618 			{
   3619 				if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
   3620 					return 0;
   3621 			}
   3622 			if (tklflags->flags & NOT_BY_MASK)
   3623 			{
   3624 				if (match_simple(tklflags->mask, make_user_host(tkl->ptr.banexception->usermask, tkl->ptr.banexception->hostmask)))
   3625 					return 0;
   3626 			}
   3627 			if (tklflags->flags & BY_REASON)
   3628 				if (!match_simple(tklflags->reason, tkl->ptr.banexception->reason))
   3629 					return 0;
   3630 			if (tklflags->flags & NOT_BY_REASON)
   3631 				if (match_simple(tklflags->reason, tkl->ptr.banexception->reason))
   3632 					return 0;
   3633 		}
   3634 	}
   3635 
   3636 	/***** If we are still here then we have a match and will will send the STATS entry */
   3637 	if (TKLIsServerBan(tkl))
   3638 	{
   3639 		char uhostbuf[BUFSIZE];
   3640 		char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
   3641 		if (tkl->type == (TKL_KILL | TKL_GLOBAL))
   3642 		{
   3643 			sendnumeric(client, RPL_STATSGLINE, 'G', uhost,
   3644 				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3645 				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
   3646 		} else
   3647 		if (tkl->type == (TKL_ZAP | TKL_GLOBAL))
   3648 		{
   3649 			sendnumeric(client, RPL_STATSGLINE, 'Z', uhost,
   3650 				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3651 				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
   3652 		} else
   3653 		if (tkl->type == (TKL_SHUN | TKL_GLOBAL))
   3654 		{
   3655 			sendnumeric(client, RPL_STATSGLINE, 's', uhost,
   3656 				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3657 				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
   3658 		} else
   3659 		if (tkl->type == (TKL_KILL))
   3660 		{
   3661 			sendnumeric(client, RPL_STATSGLINE, 'K', uhost,
   3662 				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3663 				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
   3664 		} else
   3665 		if (tkl->type == (TKL_ZAP))
   3666 		{
   3667 			sendnumeric(client, RPL_STATSGLINE, 'z', uhost,
   3668 				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3669 				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
   3670 		}
   3671 	} else
   3672 	if (TKLIsSpamfilter(tkl))
   3673 	{
   3674 		sendnumeric(client, RPL_STATSSPAMF,
   3675 			(tkl->type & TKL_GLOBAL) ? 'F' : 'f',
   3676 			unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
   3677 			spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
   3678 			banact_valtostring(tkl->ptr.spamfilter->action),
   3679 			(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3680 			(long long)(TStime() - tkl->set_at),
   3681 			(long long)tkl->ptr.spamfilter->tkl_duration,
   3682 			tkl->ptr.spamfilter->tkl_reason,
   3683 			tkl->set_by,
   3684 			tkl->ptr.spamfilter->match->str);
   3685 		if (para && !strcasecmp(para, "del"))
   3686 		{
   3687 			char *hash = spamfilter_id(tkl);
   3688 			if (tkl->type & TKL_GLOBAL)
   3689 			{
   3690 				sendtxtnumeric(client, "To delete this spamfilter, use /SPAMFILTER del %s", hash);
   3691 				sendtxtnumeric(client, "-");
   3692 			} else {
   3693 				sendtxtnumeric(client, "This spamfilter is stored in the configuration file and cannot be removed with /SPAMFILTER del");
   3694 				sendtxtnumeric(client, "-");
   3695 			}
   3696 		}
   3697 	} else
   3698 	if (TKLIsNameBan(tkl))
   3699 	{
   3700 		sendnumeric(client, RPL_STATSQLINE,
   3701 		            (tkl->type & TKL_GLOBAL) ? 'Q' : 'q',
   3702 		            tkl->ptr.nameban->name,
   3703 		            (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3704 		            (long long)(TStime() - tkl->set_at),
   3705 		            tkl->set_by,
   3706 		            tkl->ptr.nameban->reason);
   3707 	} else
   3708 	if (TKLIsBanException(tkl))
   3709 	{
   3710 		if (tkl->ptr.banexception->match)
   3711 		{
   3712 			/* Config-added: uses security groups */
   3713 			NameValuePrioList *m;
   3714 			for (m = tkl->ptr.banexception->match->printable_list; m; m = m->next)
   3715 			{
   3716 				sendnumeric(client, RPL_STATSEXCEPTTKL, namevalue_nospaces(m),
   3717 					   tkl->ptr.banexception->bantypes,
   3718 					   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3719 					   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason);
   3720 			}
   3721 		} else {
   3722 			/* IRC-added: uses simple user/host mask */
   3723 			char uhostbuf[BUFSIZE];
   3724 			char *uhost = tkl_uhost(tkl, uhostbuf, sizeof(uhostbuf), 0);
   3725 			sendnumeric(client, RPL_STATSEXCEPTTKL, uhost,
   3726 				   tkl->ptr.banexception->bantypes,
   3727 				   (tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
   3728 				   (long long)(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.banexception->reason);
   3729 		}
   3730 	} else
   3731 	{
   3732 		/* That's weird, unknown TKL type */
   3733 		return 0;
   3734 	}
   3735 	return 1;
   3736 }
   3737 
   3738 /* TKL Stats. This is used by /STATS gline and all the others */
   3739 void _tkl_stats(Client *client, int type, const char *para, int *cnt)
   3740 {
   3741 	TKL *tk;
   3742 	TKLFlag tklflags;
   3743 	int index, index2;
   3744 
   3745 	if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
   3746 		return;
   3747 
   3748 	if (!BadPtr(para))
   3749 		parse_stats_params(para, &tklflags);
   3750 
   3751 	/* First the IP hashed entries (if applicable).. */
   3752 	index = tkl_ip_hash_type(tkl_typetochar(type));
   3753 	if (index >= 0)
   3754 	{
   3755 		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
   3756 		{
   3757 			for (tk = tklines_ip_hash[index][index2]; tk; tk = tk->next)
   3758 			{
   3759 				if (type && tk->type != type)
   3760 					continue;
   3761 				if (tkl_stats_matcher(client, type, para, &tklflags, tk))
   3762 				{
   3763 					*cnt += 1;
   3764 					if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
   3765 					{
   3766 						sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
   3767 						sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
   3768 						return;
   3769 					}
   3770 				}
   3771 			}
   3772 		}
   3773 	}
   3774 
   3775 	/* Then the normal entries... */
   3776 	for (index = 0; index < TKLISTLEN; index++)
   3777 	{
   3778 		for (tk = tklines[index]; tk; tk = tk->next)
   3779 		{
   3780 			if (type && tk->type != type)
   3781 				continue;
   3782 			if (tkl_stats_matcher(client, type, para, &tklflags, tk))
   3783 			{
   3784 				*cnt += 1;
   3785 				if ((max_stats_matches > 0) && (*cnt >= max_stats_matches))
   3786 				{
   3787 					sendnumeric(client, ERR_TOOMANYMATCHES, "STATS", "too many matches (set::max-stats-matches)");
   3788 					sendnotice(client, "Consider searching on something more specific, eg '/STATS gline +m *.nl'. See '/STATS' (without parameters) for help.");
   3789 					return;
   3790 				}
   3791 			}
   3792 		}
   3793 	}
   3794 
   3795 	if ((type == (TKL_SPAMF|TKL_GLOBAL)) && (!para || strcasecmp(para, "del")))
   3796 	{
   3797 		/* If requesting spamfilter stats and not spamfilter del, then suggest it. */
   3798 		sendnotice(client, "Tip: if you are looking for an easy way to remove a spamfilter, run '/SPAMFILTER del'.");
   3799 	}
   3800 }
   3801 
   3802 /** Synchronize a TKL entry with the other server.
   3803  * @param sender  The sender (eg: &me).
   3804  * @param to      The remote server.
   3805  * @param tkl     The TKL entry.
   3806  */
   3807 void tkl_sync_send_entry(int add, Client *sender, Client *to, TKL *tkl)
   3808 {
   3809 	char typ;
   3810 
   3811 	if (!(tkl->type & TKL_GLOBAL))
   3812 		return; /* nothing to sync */
   3813 
   3814 	typ = tkl_typetochar(tkl->type);
   3815 
   3816 	if (TKLIsServerBan(tkl))
   3817 	{
   3818 		sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld :%s", sender->name,
   3819 			   add ? '+' : '-',
   3820 			   typ,
   3821 			   (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
   3822 			   *tkl->ptr.serverban->usermask ? tkl->ptr.serverban->usermask : "*",
   3823 			   tkl->ptr.serverban->hostmask, tkl->set_by,
   3824 			   (long long)tkl->expire_at, (long long)tkl->set_at,
   3825 			   tkl->ptr.serverban->reason);
   3826 	} else
   3827 	if (TKLIsNameBan(tkl))
   3828 	{
   3829 		sendto_one(to, NULL, ":%s TKL %c %c %c %s %s %lld %lld :%s", sender->name,
   3830 			   add ? '+' : '-',
   3831 			   typ,
   3832 			   tkl->ptr.nameban->hold ? 'H' : '*',
   3833 			   tkl->ptr.nameban->name,
   3834 			   tkl->set_by,
   3835 			   (long long)tkl->expire_at, (long long)tkl->set_at,
   3836 			   tkl->ptr.nameban->reason);
   3837 	} else
   3838 	if (TKLIsSpamfilter(tkl))
   3839 	{
   3840 		sendto_one(to, NULL, ":%s TKL %c %c %s %c %s %lld %lld %lld %s %s :%s", sender->name,
   3841 			   add ? '+' : '-',
   3842 			   typ,
   3843 			   spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
   3844 			   banact_valtochar(tkl->ptr.spamfilter->action),
   3845 			   tkl->set_by,
   3846 			   (long long)tkl->expire_at, (long long)tkl->set_at,
   3847 			   (long long)tkl->ptr.spamfilter->tkl_duration, tkl->ptr.spamfilter->tkl_reason,
   3848 			   unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
   3849 			   tkl->ptr.spamfilter->match->str);
   3850 	} else
   3851 	if (TKLIsBanException(tkl))
   3852 	{
   3853 		sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld %s :%s", sender->name,
   3854 			   add ? '+' : '-',
   3855 			   typ,
   3856 			   (tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
   3857 			   *tkl->ptr.banexception->usermask ? tkl->ptr.banexception->usermask : "*",
   3858 			   tkl->ptr.banexception->hostmask, tkl->set_by,
   3859 			   (long long)tkl->expire_at, (long long)tkl->set_at,
   3860 			   tkl->ptr.banexception->bantypes,
   3861 			   tkl->ptr.banexception->reason);
   3862 	} else
   3863 	{
   3864 		unreal_log(ULOG_FATAL, "tkl", "BUG_TKL_SYNC_SEND_ENTRY", NULL,
   3865 			   "[BUG] tkl_sync_send_entry() called, but unknown type: $tkl.type_string ($tkl_type_int)",
   3866 			   log_data_tkl("tkl", tkl),
   3867 			   log_data_integer("tkl_type_int", typ));
   3868 		abort();
   3869 	}
   3870 }
   3871 
   3872 /** Broadcast a TKL entry.
   3873  * @param sender  The sender, eg &me
   3874  * @param skip    The client to skip, eg 'client' or NULL.
   3875  * @param tkl     The TKL entry to synchronize with the other servers.
   3876  */
   3877 void tkl_broadcast_entry(int add, Client *sender, Client *skip, TKL *tkl)
   3878 {
   3879 	Client *acptr;
   3880 
   3881 	/* Silly fix for RPC calls that lead to broadcasts from this sender */
   3882 	if (!IsUser(sender) && !IsServer(sender))
   3883 		sender = &me;
   3884 
   3885 	list_for_each_entry(acptr, &server_list, special_node)
   3886 	{
   3887 		if (skip && acptr == skip->direction)
   3888 			continue;
   3889 
   3890 		tkl_sync_send_entry(add, sender, acptr, tkl);
   3891 	}
   3892 }
   3893 
   3894 /** Synchronize all TKL entries with this server.
   3895  * @param client The server to synchronize with.
   3896  */
   3897 void _tkl_sync(Client *client)
   3898 {
   3899 	TKL *tkl;
   3900 	int index, index2;
   3901 
   3902 	/* First, hashed entries.. */
   3903 	for (index = 0; index < TKLIPHASHLEN1; index++)
   3904 	{
   3905 		for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
   3906 		{
   3907 			for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
   3908 			{
   3909 				tkl_sync_send_entry(1, &me, client, tkl);
   3910 			}
   3911 		}
   3912 	}
   3913 
   3914 	/* Then, regular entries.. */
   3915 	for (index = 0; index < TKLISTLEN; index++)
   3916 	{
   3917 		for (tkl = tklines[index]; tkl; tkl = tkl->next)
   3918 		{
   3919 			tkl_sync_send_entry(1, &me, client, tkl);
   3920 		}
   3921 	}
   3922 }
   3923 
   3924 /** Find a server ban TKL - only used to prevent duplicates and for deletion */
   3925 TKL *_find_tkl_serverban(int type, char *usermask, char *hostmask, int softban)
   3926 {
   3927 	char tpe = tkl_typetochar(type);
   3928 	TKL *head, *tkl;
   3929 
   3930 	if (!TKLIsServerBanType(type))
   3931 		abort();
   3932 
   3933 	head = tkl_find_head(tpe, hostmask, tklines[tkl_hash(tpe)]);
   3934 	for (tkl = head; tkl; tkl = tkl->next)
   3935 	{
   3936 		if (tkl->type == type)
   3937 		{
   3938 			if (!strcasecmp(tkl->ptr.serverban->hostmask, hostmask) &&
   3939 			    !strcasecmp(tkl->ptr.serverban->usermask, usermask))
   3940 			{
   3941 				/* And an extra check for soft/hard ban mismatches.. */
   3942 				if ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) == softban)
   3943 					return tkl;
   3944 			}
   3945 		}
   3946 	}
   3947 	return NULL; /* Not found */
   3948 }
   3949 
   3950 /** Find a ban exception TKL - only used to prevent duplicates and for deletion */
   3951 TKL *_find_tkl_banexception(int type, char *usermask, char *hostmask, int softban)
   3952 {
   3953 	char tpe = tkl_typetochar(type);
   3954 	TKL *head, *tkl;
   3955 
   3956 	if (!TKLIsBanExceptionType(type))
   3957 		abort();
   3958 
   3959 	head = tkl_find_head(tpe, hostmask, tklines[tkl_hash(tpe)]);
   3960 	for (tkl = head; tkl; tkl = tkl->next)
   3961 	{
   3962 		if (tkl->type == type)
   3963 		{
   3964 			if (!strcasecmp(tkl->ptr.banexception->hostmask, hostmask) &&
   3965 			    !strcasecmp(tkl->ptr.banexception->usermask, usermask))
   3966 			{
   3967 				/* And an extra check for soft/hard ban mismatches.. */
   3968 				if ((tkl->ptr.banexception->subtype & TKL_SUBTYPE_SOFT) == softban)
   3969 					return tkl;
   3970 			}
   3971 		}
   3972 	}
   3973 	return NULL; /* Not found */
   3974 }
   3975 
   3976 /** Find a name ban TKL (qline) - only used to prevent duplicates and for deletion */
   3977 TKL *_find_tkl_nameban(int type, char *name, int hold)
   3978 {
   3979 	char tpe = tkl_typetochar(type);
   3980 	TKL *tkl;
   3981 
   3982 	if (!TKLIsNameBanType(type))
   3983 		abort();
   3984 
   3985 	for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
   3986 	{
   3987 		if ((tkl->type == type) && !strcasecmp(tkl->ptr.nameban->name, name))
   3988 			return tkl;
   3989 	}
   3990 	return NULL; /* Not found */
   3991 }
   3992 
   3993 /** Find a spamfilter TKL - only used to prevent duplicates and for deletion */
   3994 TKL *_find_tkl_spamfilter(int type, char *match_string, BanAction action, unsigned short target)
   3995 {
   3996 	char tpe = tkl_typetochar(type);
   3997 	TKL *tkl;
   3998 
   3999 	if (!TKLIsSpamfilterType(type))
   4000 		abort();
   4001 
   4002 	for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
   4003 	{
   4004 		if ((type == tkl->type) &&
   4005 		    !strcmp(match_string, tkl->ptr.spamfilter->match->str) &&
   4006 		    (action == tkl->ptr.spamfilter->action) &&
   4007 		    (target == tkl->ptr.spamfilter->target))
   4008 		{
   4009 			return tkl;
   4010 		}
   4011 	}
   4012 	return NULL; /* Not found */
   4013 }
   4014 
   4015 /** Send a notice to opers about the TKL that is being added */
   4016 void _sendnotice_tkl_add(TKL *tkl)
   4017 {
   4018 	/* Don't show notices for temporary nick holds (issued by services) */
   4019 	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
   4020 		return;
   4021 
   4022 	if (TKLIsServerBan(tkl))
   4023 	{
   4024 		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
   4025 			   "$tkl.type_string added: '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
   4026 			   log_data_tkl("tkl", tkl));
   4027 	} else
   4028 	if (TKLIsNameBan(tkl))
   4029 	{
   4030 		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
   4031 			   "$tkl.type_string added: '$tkl' [reason: $tkl.reason] [by: $tkl.set_by] [duration: $tkl.duration_string]",
   4032 			   log_data_tkl("tkl", tkl));
   4033 	} else
   4034 	if (TKLIsSpamfilter(tkl))
   4035 	{
   4036 		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
   4037 			   "Spamfilter added: '$tkl' [type: $tkl.match_type] [targets: $tkl.spamfilter_targets] "
   4038 			   "[action: $tkl.ban_action] [reason: $tkl.reason] [by: $tkl.set_by]",
   4039 			   log_data_tkl("tkl", tkl));
   4040 	} else
   4041 	if (TKLIsBanException(tkl))
   4042 	{
   4043 		unreal_log(ULOG_INFO, "tkl", "TKL_ADD", NULL,
   4044 			   "$tkl.type_string added: '$tkl' [types: $tkl.exception_types] [by: $tkl.set_by] [duration: $tkl.duration_string]",
   4045 			   log_data_tkl("tkl", tkl));
   4046 	} else
   4047 	{
   4048 		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
   4049 		           "[BUG] TKL added of unknown type, unhandled in sendnotice_tkl_add()!!!!");
   4050 	}
   4051 }
   4052 
   4053 /** Send a notice to opers about the TKL that is being deleted */
   4054 void _sendnotice_tkl_del(char *removed_by, TKL *tkl)
   4055 {
   4056 	/* Don't show notices for temporary nick holds (issued by services) */
   4057 	if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
   4058 		return;
   4059 
   4060 	if (TKLIsServerBan(tkl))
   4061 	{
   4062 		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
   4063 			   "$tkl.type_string removed: '$tkl' [reason: $tkl.reason] [by: $removed_by] [set at: $tkl.set_at_string]",
   4064 			   log_data_tkl("tkl", tkl),
   4065 			   log_data_string("removed_by", removed_by));
   4066 	} else
   4067 	if (TKLIsNameBan(tkl))
   4068 	{
   4069 		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
   4070 			   "$tkl.type_string removed: '$tkl' [reason: $tkl.reason] [by: $removed_by] [set at: $tkl.set_at_string]",
   4071 			   log_data_tkl("tkl", tkl),
   4072 			   log_data_string("removed_by", removed_by));
   4073 	} else
   4074 	if (TKLIsSpamfilter(tkl))
   4075 	{
   4076 		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
   4077 			   "Spamfilter removed: '$tkl' [type: $tkl.match_type] [targets: $tkl.spamfilter_targets] "
   4078 			   "[action: $tkl.ban_action] [reason: $tkl.reason] [by: $removed_by] [set at: $tkl.set_at_string]",
   4079 			   log_data_tkl("tkl", tkl),
   4080 			   log_data_string("removed_by", removed_by));
   4081 	} else
   4082 	if (TKLIsBanException(tkl))
   4083 	{
   4084 		unreal_log(ULOG_INFO, "tkl", "TKL_DEL", NULL,
   4085 			   "$tkl.type_string removed: '$tkl' [types: $tkl.exception_types] [by: $removed_by] [set at: $tkl.set_at_string]",
   4086 			   log_data_tkl("tkl", tkl),
   4087 			   log_data_string("removed_by", removed_by));
   4088 	} else
   4089 	{
   4090 		unreal_log(ULOG_ERROR, "tkl", "BUG_UNKNOWN_TKL", NULL,
   4091 		           "[BUG] TKL removed of unknown type, unhandled in sendnotice_tkl_del()!!!!");
   4092 	}
   4093 }
   4094 
   4095 /** Called when a TKL is added by a remote user, local user, RPC user, ..
   4096  * (but not when a TKL is added via the config)
   4097  */
   4098 void _tkl_added(Client *client, TKL *tkl)
   4099 {
   4100 	RunHook(HOOKTYPE_TKL_ADD, client, tkl);
   4101 
   4102 	sendnotice_tkl_add(tkl);
   4103 
   4104 	/* spamfilter 'warn' action is special */
   4105 	if ((tkl->type & TKL_SPAMF) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN) && (tkl->ptr.spamfilter->target & SPAMF_USER))
   4106 		spamfilter_check_users(tkl);
   4107 
   4108 	/* Ban checking executes during run loop for efficiency */
   4109 	loop.do_bancheck = 1;
   4110 
   4111 	if (tkl->type & TKL_GLOBAL)
   4112 		tkl_broadcast_entry(1, client, client, tkl);
   4113 }
   4114 
   4115 /** Add a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
   4116 CMD_FUNC(cmd_tkl_add)
   4117 {
   4118 	TKL *tkl;
   4119 	int type;
   4120 	time_t expire_at, set_at;
   4121 	const char *set_by;
   4122 	char tkl_entry_exists = 0;
   4123 
   4124 	/* we rely on servers to be failsafe.. */
   4125 	if (!IsServer(client) && !IsMe(client))
   4126 		return;
   4127 
   4128 	if (parc < 9)
   4129 		return;
   4130 
   4131 	type = tkl_chartotype(parv[2][0]);
   4132 	if (!type)
   4133 		return;
   4134 
   4135 	/* All TKL types have the following fields in common when adding:
   4136 	 * parv[5]: set_by
   4137 	 * parv[6]: expire_at
   4138 	 * parv[7]: set_at
   4139 	 * ... so we validate them here at the beginning.
   4140 	 */
   4141 
   4142 	set_by = parv[5];
   4143 	expire_at = atol(parv[6]);
   4144 	set_at = atol(parv[7]);
   4145 
   4146 	/* Validate set and expiry time */
   4147 	if ((set_at < 0) || !short_date(set_at, NULL))
   4148 	{
   4149 		unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4150 			"Invalid TKL entry from $client: "
   4151 			"The set-at time is out of range ($set_at). Clock on other server incorrect or bogus entry.",
   4152 			log_data_integer("set_at", set_at));
   4153 		return;
   4154 	}
   4155 	if ((expire_at < 0) || !short_date(expire_at, NULL))
   4156 	{
   4157 		unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4158 			"Invalid TKL entry from $client: "
   4159 			"The expire-at time is out of range ($expire_at). Clock on other server incorrect or bogus entry.",
   4160 			log_data_integer("expire_at", expire_at));
   4161 		return;
   4162 	}
   4163 
   4164 	/* Now comes type-specific validation
   4165 	 * and we check if the TKL entry already exists and needs updating too.
   4166 	 */
   4167 
   4168 	if (TKLIsServerBanType(type))
   4169 	{
   4170 		/* Validate server ban TKL fields */
   4171 		int softban = 0;
   4172 		const char *usermask = parv[3];
   4173 		const char *hostmask = parv[4];
   4174 		const char *reason = parv[8];
   4175 
   4176 		/* Some simple validation on usermask and hostmask:
   4177 		 * may not contain an @. Yeah, some services or self-written
   4178 		 * linked servers are known to have sent this in the past.
   4179 		 */
   4180 		if (strchr(usermask, '@') || strchr(hostmask, '@'))
   4181 		{
   4182 			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4183 				"Invalid TKL entry from $client: "
   4184 				"Invalid user@host $usermask@$hostmask",
   4185 				log_data_string("usermask", usermask),
   4186 				log_data_string("hostmask", hostmask));
   4187 			return;
   4188 		}
   4189 
   4190 		/* In case of a soft ban, strip the percent sign early,
   4191 		 * so parv[3] (username) is really the username without any prefix.
   4192 		 * Set the 'softban' flag if this is the case.
   4193 		 */
   4194 		if (*usermask == '%')
   4195 		{
   4196 			usermask++;
   4197 			softban = 1;
   4198 		}
   4199 
   4200 		tkl = find_tkl_serverban(type, usermask, hostmask, softban);
   4201 		if (tkl)
   4202 		{
   4203 			tkl_entry_exists = 1;
   4204 		} else {
   4205 			tkl = tkl_add_serverban(type, usermask, hostmask, reason,
   4206 			                        set_by, expire_at, set_at, softban, 0);
   4207 		}
   4208 	} else
   4209 	if (TKLIsBanExceptionType(type))
   4210 	{
   4211 		/* Validate ban exception TKL fields */
   4212 		int softban = 0;
   4213 		const char *usermask = parv[3];
   4214 		const char *hostmask = parv[4];
   4215 		const char *bantypes = parv[8];
   4216 		const char *reason;
   4217 
   4218 		if (parc < 10)
   4219 			return;
   4220 
   4221 		reason = parv[9];
   4222 
   4223 		/* Some simple validation on usermask and hostmask:
   4224 		 * may not contain an @. Yeah, some services or self-written
   4225 		 * linked servers are known to have sent this in the past.
   4226 		 */
   4227 		if (strchr(usermask, '@') || strchr(hostmask, '@'))
   4228 		{
   4229 			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4230 				"Invalid TKL entry from $client: "
   4231 				"Invalid TKL except user@host $usermask@$hostmask",
   4232 				log_data_string("usermask", usermask),
   4233 				log_data_string("hostmask", hostmask));
   4234 			return;
   4235 		}
   4236 
   4237 		/* In case of a soft ban, strip the percent sign early,
   4238 		 * so parv[3] (username) is really the username without any prefix.
   4239 		 * Set the 'softban' flag if this is the case.
   4240 		 */
   4241 		if (*usermask == '%')
   4242 		{
   4243 			usermask++;
   4244 			softban = 1;
   4245 		}
   4246 
   4247 		/* At this moment we do not validate 'bantypes' since a missing
   4248 		 * or wrong type does not cause harm anyway.
   4249 		 */
   4250 		tkl = find_tkl_banexception(type, usermask, hostmask, softban);
   4251 		if (tkl)
   4252 		{
   4253 			tkl_entry_exists = 1;
   4254 		} else {
   4255 			tkl = tkl_add_banexception(type, usermask, hostmask, NULL, reason,
   4256 			                           set_by, expire_at, set_at, softban, bantypes, 0);
   4257 		}
   4258 	} else
   4259 	if (TKLIsNameBanType(type))
   4260 	{
   4261 		/* Validate name ban TKL fields */
   4262 		int hold = 0;
   4263 		const char *name = parv[4];
   4264 		const char *reason = parv[8];
   4265 
   4266 		if (*parv[3] == 'H')
   4267 			hold = 1;
   4268 
   4269 		tkl = find_tkl_nameban(type, name, hold);
   4270 		if (tkl)
   4271 		{
   4272 			tkl_entry_exists = 1;
   4273 		} else {
   4274 			tkl = tkl_add_nameban(type, name, hold, reason, set_by, expire_at,
   4275 			                      set_at, 0);
   4276 		}
   4277 	} else
   4278 	if (TKLIsSpamfilterType(type))
   4279 	{
   4280 		/* Validate spamfilter-specific TKL fields */
   4281 		MatchType match_method;
   4282 		const char *match_string;
   4283 		Match *m; /* compiled match_string */
   4284 		time_t tkl_duration;
   4285 		const char *tkl_reason;
   4286 		BanAction action;
   4287 		unsigned short target;
   4288 		/* helper variables */
   4289 		char *err;
   4290 
   4291 		if (parc < 12)
   4292 		{
   4293 			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4294 				"Invalid TKL entry from $client: "
   4295 				"Spamfilter with too few parameters. Running very old UnrealIRCd protocol (3.2.X?)");
   4296 			return;
   4297 		}
   4298 
   4299 		match_string = parv[11];
   4300 
   4301 		match_method = unreal_match_method_strtoval(parv[10]);
   4302 		if (match_method == 0)
   4303 		{
   4304 			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4305 				"Invalid TKL entry from $client: "
   4306 				"Spamfilter '$spamfilter_string' has unknown match-type '$spamfilter_type'",
   4307 				log_data_string("spamfilter_string", match_string),
   4308 				log_data_string("spamfilter_type", parv[10]));
   4309 			return;
   4310 		}
   4311 
   4312 		if (!(target = spamfilter_gettargets(parv[3], NULL)))
   4313 		{
   4314 			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4315 				"Invalid TKL entry from $client: "
   4316 				"Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'",
   4317 				log_data_string("spamfilter_string", match_string),
   4318 				log_data_string("spamfilter_targets", parv[3]));
   4319 			return;
   4320 		}
   4321 
   4322 		if (!(action = banact_chartoval(*parv[4])))
   4323 		{
   4324 			unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4325 				"Invalid TKL entry from $client: "
   4326 				"Spamfilter '$spamfilter_string' has unknown action '$spamfilter_action'",
   4327 				log_data_string("spamfilter_string", match_string),
   4328 				log_data_string("spamfilter_action", parv[4]));
   4329 			return;
   4330 		}
   4331 
   4332 		tkl_duration = config_checkval(parv[8], CFG_TIME);
   4333 		tkl_reason = parv[9];
   4334 
   4335 		tkl = find_tkl_spamfilter(type, match_string, action, target);
   4336 
   4337 		if (tkl)
   4338 		{
   4339 			tkl_entry_exists = 1;
   4340 		} else {
   4341 			m = unreal_create_match(match_method, match_string, &err);
   4342 			if (!m)
   4343 			{
   4344 				unreal_log(ULOG_WARNING, "tkl", "TKL_ADD_INVALID", client,
   4345 					"Invalid TKL entry from $client: "
   4346 					"Spamfilter '$spamfilter_string': regex does not compile: $spamfilter_regex_error",
   4347 					log_data_string("spamfilter_string", match_string),
   4348 					log_data_string("spamfilter_regex_error", err));
   4349 				return;
   4350 			}
   4351 			tkl = tkl_add_spamfilter(type, target, action, m, set_by, expire_at, set_at,
   4352 			                         tkl_duration, tkl_reason, 0);
   4353 		}
   4354 	} else
   4355 	{
   4356 		/* Unhandled, should never happen */
   4357 		abort();
   4358 	}
   4359 
   4360 	if (!tkl)
   4361 		return;
   4362 
   4363 	if (tkl_entry_exists)
   4364 	{
   4365 		/* Let's see if we need to update the existing entry.
   4366 		 * Note that we only update common fields,
   4367 		 * which is acceptable to me. -- Syzop
   4368 		 */
   4369 		if ((set_at < tkl->set_at) || (expire_at != tkl->expire_at) || strcmp(tkl->set_by, parv[5]))
   4370 		{
   4371 			/* here's how it goes:
   4372 			 * set_at: oldest wins
   4373 			 * expire_at: longest wins
   4374 			 * set_by: highest strcmp wins
   4375 			 *
   4376 			 * We broadcast the result of this back to all servers except
   4377 			 * sptr->direction, because that side will do the same thing and
   4378 			 * send it back to his servers (except us)... no need for a
   4379 			 * double networkwide flood ;p. -- Syzop
   4380 			 */
   4381 			tkl->set_at = MIN(tkl->set_at, set_at);
   4382 
   4383 			if (!tkl->expire_at || !expire_at)
   4384 				tkl->expire_at = 0;
   4385 			else
   4386 				tkl->expire_at = MAX(tkl->expire_at, expire_at);
   4387 
   4388 			if (strcmp(tkl->set_by, parv[5]) < 0)
   4389 				safe_strdup(tkl->set_by, parv[5]);
   4390 
   4391 			if (type & TKL_GLOBAL)
   4392 				tkl_broadcast_entry(1, client, client, tkl);
   4393 		}
   4394 		return;
   4395 	}
   4396 
   4397 	tkl_added(client, tkl);
   4398 }
   4399 
   4400 /** Delete a TKL using the TKL layer. See cmd_tkl for parv[] and protocol documentation. */
   4401 CMD_FUNC(cmd_tkl_del)
   4402 {
   4403 	TKL *tkl;
   4404 	int type;
   4405 	const char *removed_by;
   4406 
   4407 	if (!IsServer(client) && !IsMe(client))
   4408 		return;
   4409 
   4410 	if (parc < 6)
   4411 		return;
   4412 
   4413 	type = tkl_chartotype(parv[2][0]);
   4414 	if (type == 0)
   4415 		return;
   4416 
   4417 	removed_by = parv[5];
   4418 
   4419 	if (TKLIsServerBanType(type))
   4420 	{
   4421 		const char *usermask = parv[3];
   4422 		const char *hostmask = parv[4];
   4423 		int softban = 0;
   4424 
   4425 		if (*usermask == '%')
   4426 		{
   4427 			usermask++;
   4428 			softban = 1;
   4429 		}
   4430 
   4431 		tkl = find_tkl_serverban(type, usermask, hostmask, softban);
   4432 	}
   4433 	else if (TKLIsBanExceptionType(type))
   4434 	{
   4435 		const char *usermask = parv[3];
   4436 		const char *hostmask = parv[4];
   4437 		int softban = 0;
   4438 		/* other parameters are ignored */
   4439 
   4440 		if (*usermask == '%')
   4441 		{
   4442 			usermask++;
   4443 			softban = 1;
   4444 		}
   4445 
   4446 		tkl = find_tkl_banexception(type, usermask, hostmask, softban);
   4447 	}
   4448 	else if (TKLIsNameBanType(type))
   4449 	{
   4450 		int hold = 0;
   4451 		const char *name = parv[4];
   4452 
   4453 		if (*parv[3] == 'H')
   4454 			hold = 1;
   4455 		tkl = find_tkl_nameban(type, name, hold);
   4456 	}
   4457 	else if (TKLIsSpamfilterType(type))
   4458 	{
   4459 		const char *match_string;
   4460 		unsigned short target;
   4461 		BanAction action;
   4462 
   4463 		if (parc < 9)
   4464 		{
   4465 			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
   4466 				"Invalid TKL deletion request from $client: "
   4467 				"Spamfilter with too few parameters. Running very old UnrealIRCd protocol (3.2.X?)");
   4468 			return; /* bogus */
   4469 		}
   4470 		if (parc >= 12)
   4471 			match_string = parv[11];
   4472 		else if (parc >= 11)
   4473 			match_string = parv[10];
   4474 		else
   4475 			match_string = parv[8];
   4476 
   4477 		if (!(target = spamfilter_gettargets(parv[3], NULL)))
   4478 		{
   4479 			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
   4480 				"Invalid TKL deletion request from $client: "
   4481 				"Spamfilter '$spamfilter_string' has unknown targets '$spamfilter_targets'",
   4482 				log_data_string("spamfilter_string", match_string),
   4483 				log_data_string("spamfilter_targets", parv[3]));
   4484 			return;
   4485 		}
   4486 
   4487 		if (!(action = banact_chartoval(*parv[4])))
   4488 		{
   4489 			unreal_log(ULOG_WARNING, "tkl", "TKL_DEL_INVALID", client,
   4490 				"Invalid TKL deletion request from $client: "
   4491 				"Spamfilter '$spamfilter_string' has unknown action '$spamfilter_action'",
   4492 				log_data_string("spamfilter_string", match_string),
   4493 				log_data_string("spamfilter_action", parv[4]));
   4494 			return;
   4495 		}
   4496 		tkl = find_tkl_spamfilter(type, match_string, action, target);
   4497 	} else
   4498 	{
   4499 		/* This can never happen, unless someone added a TKL type
   4500 		 * to UnrealIRCd but forgot to add the removal code :D.
   4501 		 */
   4502 		abort();
   4503 	}
   4504 
   4505 	if (!tkl)
   4506 		return; /* Item not found, nothing to remove. */
   4507 
   4508 	if (tkl->flags & TKL_FLAG_CONFIG)
   4509 		return; /* Item is in the configuration file (persistent) */
   4510 
   4511 	/* broadcast remove msg to opers... */
   4512 	sendnotice_tkl_del(removed_by, tkl);
   4513 
   4514 	if (type & TKL_SHUN)
   4515 		tkl_check_local_remove_shun(tkl);
   4516 
   4517 	RunHook(HOOKTYPE_TKL_DEL, client, tkl);
   4518 
   4519 	if (type & TKL_GLOBAL)
   4520 	{
   4521 		/* This is a bit of a hack for #5629. Will consider real fix post-release. */
   4522 		safe_strdup(tkl->set_by, removed_by);
   4523 		tkl_broadcast_entry(0, client, client, tkl);
   4524 	}
   4525 
   4526 	if (TKLIsBanException(tkl))
   4527 	{
   4528 		/* Since an exception has been removed we have to re-check if
   4529 		 * any connected user is now matched by a ban.
   4530 		 * Set flag here, actual checking takes place in main loop.
   4531 		 */
   4532 		loop.do_bancheck = 1;
   4533 	}
   4534 
   4535 	tkl_del_line(tkl);
   4536 }
   4537 
   4538 /** TKL command: server to server handling of *LINEs and SPAMFILTERs.
   4539  * HISTORY:
   4540  * This was originall called Timed KLines, but today it's
   4541  * used by various *line types eg: zline, gline, gzline, shun,
   4542  * but also by spamfilter etc...
   4543  * DOCUMENTATION
   4544  * See (also) https://www.unrealircd.org/docs/Server_protocol:TKL_command
   4545  * USAGE:
   4546  * This routine is used both internally by the ircd (to
   4547  * for example add local klines, zlines, etc) and over the
   4548  * network (glines, gzlines, spamfilter, etc).
   4549  *
   4550  *           serverban  serverban  spamfilter      spamfilter         sqline:    ban exception:
   4551  *           add:       remove:    remove in U4:   with TKLEXT2:
   4552  * parv[ 1]: +          -          -               +/-                +/-        +/-
   4553  * parv[ 2]: type       type       type            type               type       type
   4554  * parv[ 3]: user       user       target          target             hold       user
   4555  * parv[ 4]: host       host       action          action             host       host
   4556  * parv[ 5]: set_by     removedby  (un)set_by      set_by/unset_by    set_by     set_by
   4557  * parv[ 6]: expire_at             expire_at (0)   expire_at (0)      expire_at  expire_at
   4558  * parv[ 7]: set_at                set_at          set_at             set_at     set_at
   4559  * parv[ 8]: reason                regex           tkl duration       reason     except_type
   4560  * parv[ 9]:                                       tkl reason [A]                reason
   4561  * parv[10]:                                       match-type [B]
   4562  * parv[11]:                                       match-string [C]
   4563  *
   4564  * [A] tkl reason field must be escaped by caller [eg: use unreal_encodespace()
   4565  *     if cmd_tkl is called internally].
   4566  * [B] match-type must be one of: regex, simple.
   4567  * [C] Could be a regex or a regular string with wildcards, depending on [B]
   4568  */
   4569 CMD_FUNC(_cmd_tkl)
   4570 {
   4571 	if (!IsServer(client) && !IsOper(client) && !IsMe(client))
   4572 		return;
   4573 
   4574 	if (parc < 2)
   4575 		return;
   4576 
   4577 	switch (*parv[1])
   4578 	{
   4579 		case '+':
   4580 			CALL_CMD_FUNC(cmd_tkl_add);
   4581 			break;
   4582 		case '-':
   4583 			CALL_CMD_FUNC(cmd_tkl_del);
   4584 			break;
   4585 		default:
   4586 			break;
   4587 	}
   4588 }
   4589 
   4590 /** Configure the username/hostname TKL layer based on the BAN_TARGET_* configuration */
   4591 void ban_target_to_tkl_layer(BanTarget ban_target, BanAction action, Client *client, const char **tkl_username, const char **tkl_hostname)
   4592 {
   4593 	static char username[USERLEN+1];
   4594 	static char hostname[HOSTLEN+8];
   4595 
   4596 	if ((action == BAN_ACT_ZLINE) || (action == BAN_ACT_GZLINE))
   4597 		ban_target = BAN_TARGET_IP; /* The only possible choice with ZLINE/GZLINE, other info is unavailable */
   4598 
   4599 	if (ban_target == BAN_TARGET_ACCOUNT)
   4600 	{
   4601 		if (IsLoggedIn(client) && (*client->user->account != ':'))
   4602 		{
   4603 			/* Place a ban on ~a:Accountname */
   4604 			strlcpy(username, "~a:", sizeof(username));
   4605 			strlcpy(hostname, client->user->account, sizeof(hostname));
   4606 			*tkl_username = username;
   4607 			*tkl_hostname = hostname;
   4608 			return;
   4609 		}
   4610 		ban_target = BAN_TARGET_IP; /* fallback */
   4611 	} else
   4612 	if (ban_target == BAN_TARGET_CERTFP)
   4613 	{
   4614 		const char *fp = moddata_client_get(client, "certfp");
   4615 		if (fp)
   4616 		{
   4617 			/* Place a ban on ~S:sha256sumofclientcertificate */
   4618 			strlcpy(username, "~S:", sizeof(username));
   4619 			strlcpy(hostname, fp, sizeof(hostname));
   4620 			*tkl_username = username;
   4621 			*tkl_hostname = hostname;
   4622 			return;
   4623 		}
   4624 		ban_target = BAN_TARGET_IP; /* fallback */
   4625 	}
   4626 
   4627 	/* Below we deal with the more common choices... */
   4628 
   4629 	/* First, set the username */
   4630 	if (((ban_target == BAN_TARGET_USERIP) || (ban_target == BAN_TARGET_USERHOST)) && strcmp(client->ident, "unknown"))
   4631 		strlcpy(username, client->ident, sizeof(username));
   4632 	else
   4633 		strlcpy(username, "*", sizeof(username));
   4634 
   4635 	/* Now set the host-portion of the TKL */
   4636 	if (((ban_target == BAN_TARGET_HOST) || (ban_target == BAN_TARGET_USERHOST)) && client->user && *client->user->realhost)
   4637 		strlcpy(hostname, client->user->realhost, sizeof(hostname));
   4638 	else
   4639 		strlcpy(hostname, GetIP(client), sizeof(hostname));
   4640 
   4641 	*tkl_username = username;
   4642 	*tkl_hostname = hostname;
   4643 }
   4644 
   4645 /** Take an action on the user, such as banning or killing.
   4646  * @author Bram Matthys (Syzop), 2003-present
   4647  * @param client     The client which is affected.
   4648  * @param action   The type of ban (one of BAN_ACT_*).
   4649  * @param reason   The ban reason.
   4650  * @param duration The ban duration in seconds.
   4651  * @note This function assumes that client is a locally connected user.
   4652  * @retval 1 if action is taken, 0 if user is exempted.
   4653  * @note Be sure to check IsDead(client) if return value is 1 and you are
   4654  *       considering to continue processing.
   4655  */
   4656 int _place_host_ban(Client *client, BanAction action, char *reason, long duration)
   4657 {
   4658 	/* If this is a soft action and the user is logged in, then the ban does not apply.
   4659 	 * NOTE: Actually in such a case it would be better if place_host_ban() would not
   4660 	 * be called at all. Or at least, the caller should not take any action
   4661 	 * (eg: the message should be delivered, the user may connect, etc..)
   4662 	 * The following is more like secondary protection in case the caller forgets...
   4663 	 */
   4664 	if (IsSoftBanAction(action) && IsLoggedIn(client))
   4665 		return 0;
   4666 
   4667 	switch(action)
   4668 	{
   4669 		case BAN_ACT_TEMPSHUN:
   4670 			/* We simply mark this connection as shunned and do not add a ban record */
   4671 			unreal_log(ULOG_INFO, "tkl", "TKL_ADD_TEMPSHUN", &me,
   4672 				   "Temporary shun added on user $target.details [reason: $shun_reason] [by: $client]",
   4673 				   log_data_string("shun_reason", reason),
   4674 				   log_data_client("target", client));
   4675 			SetShunned(client);
   4676 			return 1;
   4677 		case BAN_ACT_GZLINE:
   4678 		case BAN_ACT_GLINE:
   4679 		case BAN_ACT_SOFT_GLINE:
   4680 		case BAN_ACT_ZLINE:
   4681 		case BAN_ACT_KLINE:
   4682 		case BAN_ACT_SOFT_KLINE:
   4683 		case BAN_ACT_SHUN:
   4684 		case BAN_ACT_SOFT_SHUN:
   4685 		{
   4686 			char ip[128], user[USERLEN+3], mo[100], mo2[100];
   4687 			const char *tkllayer[9] = {
   4688 				me.name,	/*0  server.name */
   4689 				"+",		/*1  +|- */
   4690 				"?",		/*2  type */
   4691 				"*",		/*3  user */
   4692 				NULL,		/*4  host */
   4693 				NULL,
   4694 				NULL,		/*6  expire_at */
   4695 				NULL,		/*7  set_at */
   4696 				NULL		/*8  reason */
   4697 			};
   4698 
   4699 			ban_target_to_tkl_layer(iConf.automatic_ban_target, action, client, &tkllayer[3], &tkllayer[4]);
   4700 
   4701 			/* For soft bans we need to prefix the % in the username */
   4702 			if (IsSoftBanAction(action))
   4703 			{
   4704 				char tmp[USERLEN+3];
   4705 				snprintf(tmp, sizeof(tmp), "%%%s", tkllayer[3]);
   4706 				strlcpy(user, tmp, sizeof(user));
   4707 				tkllayer[3] = user;
   4708 			}
   4709 
   4710 			if ((action == BAN_ACT_KLINE) || (action == BAN_ACT_SOFT_KLINE))
   4711 				tkllayer[2] = "k";
   4712 			else if (action == BAN_ACT_ZLINE)
   4713 				tkllayer[2] = "z";
   4714 			else if (action == BAN_ACT_GZLINE)
   4715 				tkllayer[2] = "Z";
   4716 			else if ((action == BAN_ACT_GLINE) || (action == BAN_ACT_SOFT_GLINE))
   4717 				tkllayer[2] = "G";
   4718 			else if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
   4719 				tkllayer[2] = "s";
   4720 			tkllayer[5] = me.name;
   4721 			if (!duration)
   4722 				strlcpy(mo, "0", sizeof(mo)); /* perm */
   4723 			else
   4724 				ircsnprintf(mo, sizeof(mo), "%lld", (long long)(duration + TStime()));
   4725 			ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
   4726 			tkllayer[6] = mo;
   4727 			tkllayer[7] = mo2;
   4728 			tkllayer[8] = reason;
   4729 			cmd_tkl(&me, NULL, 9, tkllayer);
   4730 			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
   4731 			if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
   4732 			{
   4733 				find_shun(client);
   4734 				return 1;
   4735 			} /* else.. */
   4736 			return find_tkline_match(client, 0);
   4737 		}
   4738 		case BAN_ACT_SOFT_KILL:
   4739 		case BAN_ACT_KILL:
   4740 		default:
   4741 			RunHookReturnInt(HOOKTYPE_PLACE_HOST_BAN, !=99, client, action, reason, duration);
   4742 			exit_client(client, NULL, reason);
   4743 			return 1;
   4744 	}
   4745 	return 0; /* no action taken (weird) */
   4746 }
   4747 
   4748 /** This function compares two spamfilters ('one' and 'two') and will return
   4749  * a 'winner' based on which one has the strongest action.
   4750  * If both have equal action then some additional logic is applied simply
   4751  * to ensure we (almost) always return the same winner regardless of the
   4752  * order of the spamfilters (which may differ between servers).
   4753  */
   4754 TKL *choose_winning_spamfilter(TKL *one, TKL *two)
   4755 {
   4756 	int n;
   4757 
   4758 	if (!TKLIsSpamfilter(one) || !TKLIsSpamfilter(two))
   4759 		abort();
   4760 
   4761 	/* First, see if the action field differs... */
   4762 	if (one->ptr.spamfilter->action != two->ptr.spamfilter->action)
   4763 	{
   4764 		/* We can simply compare the action. Highest (strongest) wins. */
   4765 		if (one->ptr.spamfilter->action > two->ptr.spamfilter->action)
   4766 			return one;
   4767 		else
   4768 			return two;
   4769 	}
   4770 
   4771 	/* Ok, try comparing the regex then.. */
   4772 	n = strcmp(one->ptr.spamfilter->match->str, two->ptr.spamfilter->match->str);
   4773 	if (n < 0)
   4774 		return one;
   4775 	if (n > 0)
   4776 		return two;
   4777 
   4778 	/* Hmm.. regex is identical. Try the 'reason' field. */
   4779 	n = strcmp(one->ptr.spamfilter->tkl_reason, two->ptr.spamfilter->tkl_reason);
   4780 	if (n < 0)
   4781 		return one;
   4782 	if (n > 0)
   4783 		return two;
   4784 
   4785 	/* Hmm.. 'reason' is identical as well.
   4786 	 * Make a final decision, could still be identical but would be unlikely.
   4787 	 */
   4788 	return (one->ptr.spamfilter->target > two->ptr.spamfilter->target) ? one : two;
   4789 }
   4790 
   4791 /** Checks if 'target' is on the spamfilter exception list.
   4792  * RETURNS 1 if found in list, 0 if not.
   4793  */
   4794 static int target_is_spamexcept(const char *target)
   4795 {
   4796 	SpamExcept *e;
   4797 
   4798 	for (e = iConf.spamexcept; e; e = e->next)
   4799 	{
   4800 		if (match_simple(e->name, target))
   4801 			return 1;
   4802 	}
   4803 	return 0;
   4804 }
   4805 
   4806 /** Make user join the virus channel.
   4807  * @param client  The user that was doing something bad.
   4808  * @param tk    The TKL entry that matched this user.
   4809  * @param type  The spamfilter type (SPAMF_*)
   4810  *              TODO: Looks redundant?
   4811  */
   4812 int _join_viruschan(Client *client, TKL *tkl, int type)
   4813 {
   4814 	const char *xparv[3];
   4815 	char chbuf[CHANNELLEN + 16], buf[2048];
   4816 	Channel *channel;
   4817 	int ret;
   4818 
   4819 	snprintf(buf, sizeof(buf), "0,%s", SPAMFILTER_VIRUSCHAN);
   4820 	xparv[0] = NULL;
   4821 	xparv[1] = buf;
   4822 	xparv[2] = NULL;
   4823 
   4824 	/* RECURSIVE CAUTION in case we ever add blacklisted chans */
   4825 	spamf_ugly_vchanoverride = 1;
   4826 	do_cmd(client, NULL, "JOIN", 2, xparv);
   4827 	spamf_ugly_vchanoverride = 0;
   4828 
   4829 	if (IsDead(client))
   4830 		return 0; /* killed due to JOIN */
   4831 
   4832 	sendnotice(client, "You are now restricted to talking in %s: %s",
   4833 		SPAMFILTER_VIRUSCHAN, unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
   4834 
   4835 	channel = find_channel(SPAMFILTER_VIRUSCHAN);
   4836 	if (channel)
   4837 	{
   4838 		MessageTag *mtags = NULL;
   4839 		ircsnprintf(chbuf, sizeof(chbuf), "@%s", channel->name);
   4840 		ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s matched filter '%s' [%s] [%s]",
   4841 			client->name, tkl->ptr.spamfilter->match->str, cmdname_by_spamftarget(type),
   4842 			unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
   4843 		new_message(&me, NULL, &mtags);
   4844 		sendto_channel(channel, &me, NULL, "o",
   4845 		               0, SEND_ALL|SKIP_DEAF, mtags,
   4846 		               ":%s NOTICE %s :%s", me.name, chbuf, buf);
   4847 		free_message_tags(mtags);
   4848 	}
   4849 	SetVirus(client);
   4850 	return 1;
   4851 }
   4852 
   4853 /** match_spamfilter: executes the spamfilter on the input string.
   4854  * @param str		The text (eg msg text, notice text, part text, quit text, etc
   4855  * @param target	The spamfilter target (SPAMF_*)
   4856  * @param cmd		The command (eg: "PRIVMSG")
   4857  * @param destination	The destination as a text string (eg: "somenick", can be NULL.. eg for away)
   4858  * @param flags		Any flags (SPAMFLAG_*)
   4859  * @param rettkl	Pointer to an aTKLline struct, _used for special circumstances only_
   4860  * RETURN VALUE:
   4861  * 1 if spamfilter matched and it should be blocked (or client exited), 0 if not matched.
   4862  * In case of 1, be sure to check IsDead(client)..
   4863  */
   4864 int _match_spamfilter(Client *client, const char *str_in, int target, const char *cmd, const char *destination, int flags, TKL **rettkl)
   4865 {
   4866 	TKL *tkl;
   4867 	TKL *winner_tkl = NULL;
   4868 	const char *str;
   4869 	int ret = -1;
   4870 	char *reason = NULL;
   4871 #ifdef SPAMFILTER_DETECTSLOW
   4872 	struct rusage rnow, rprev;
   4873 	long ms_past;
   4874 #endif
   4875 
   4876 	if (rettkl)
   4877 		*rettkl = NULL; /* initialize to NULL */
   4878 
   4879 	if (!cmd)
   4880 		cmd = cmdname_by_spamftarget(target);
   4881 
   4882 	if (target == SPAMF_USER)
   4883 		str = str_in;
   4884 	else
   4885 		str = StripControlCodes(str_in);
   4886 
   4887 	/* (note: using client->user check here instead of IsUser()
   4888 	 * due to SPAMF_USER where user isn't marked as client/person yet.
   4889 	 */
   4890 	if (!client->user || ValidatePermissionsForPath("immune:server-ban:spamfilter",client,NULL,NULL,NULL) || IsULine(client))
   4891 		return 0;
   4892 
   4893 	/* Client exempt from spamfilter checking?
   4894 	 * Let's check that early: going through elines is likely faster than running the regex(es).
   4895 	 */
   4896 	if (find_tkl_exception(TKL_SPAMF, client))
   4897 		return 0;
   4898 
   4899 	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
   4900 	{
   4901 		if (!(tkl->ptr.spamfilter->target & target))
   4902 			continue;
   4903 
   4904 		if ((flags & SPAMFLAG_NOWARN) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN))
   4905 			continue;
   4906 
   4907 		/* If the action is 'soft' (for non-logged in users only) then
   4908 		 * don't bother running the spamfilter if the user is logged in.
   4909 		 */
   4910 		if (IsSoftBanAction(tkl->ptr.spamfilter->action) && IsLoggedIn(client))
   4911 			continue;
   4912 
   4913 #ifdef SPAMFILTER_DETECTSLOW
   4914 		memset(&rnow, 0, sizeof(rnow));
   4915 		memset(&rprev, 0, sizeof(rnow));
   4916 
   4917 		getrusage(RUSAGE_SELF, &rprev);
   4918 #endif
   4919 
   4920 		ret = unreal_match(tkl->ptr.spamfilter->match, str);
   4921 
   4922 #ifdef SPAMFILTER_DETECTSLOW
   4923 		getrusage(RUSAGE_SELF, &rnow);
   4924 
   4925 		ms_past = ((rnow.ru_utime.tv_sec - rprev.ru_utime.tv_sec) * 1000) +
   4926 		          ((rnow.ru_utime.tv_usec - rprev.ru_utime.tv_usec) / 1000);
   4927 
   4928 		if ((SPAMFILTER_DETECTSLOW_FATAL > 0) && (ms_past > SPAMFILTER_DETECTSLOW_FATAL))
   4929 		{
   4930 			unreal_log(ULOG_ERROR, "tkl", "SPAMFILTER_SLOW_FATAL", NULL,
   4931 			           "[Spamfilter] WARNING: Too slow spamfilter detected (took $msec_time msec to execute) "
   4932 			           "-- spamfilter will be \002REMOVED!\002: $tkl",
   4933 			           log_data_tkl("tkl", tkl),
   4934 			           log_data_integer("msec_time", ms_past));
   4935 			tkl_del_line(tkl);
   4936 			return 0; /* Act as if it didn't match, even if it did.. it's gone now anyway.. */
   4937 		} else
   4938 		if ((SPAMFILTER_DETECTSLOW_WARN > 0) && (ms_past > SPAMFILTER_DETECTSLOW_WARN))
   4939 		{
   4940 			unreal_log(ULOG_WARNING, "tkl", "SPAMFILTER_SLOW_WARN", NULL,
   4941 			           "[Spamfilter] WARNING: Slow spamfilter detected (took $msec_time msec to execute): $tkl",
   4942 			           log_data_tkl("tkl", tkl),
   4943 			           log_data_integer("msec_time", ms_past));
   4944 		}
   4945 #endif
   4946 
   4947 		if (ret)
   4948 		{
   4949 			/* We have a match! But.. perhaps it's on the exceptions list? */
   4950 			if (!winner_tkl && destination && target_is_spamexcept(destination))
   4951 				return 0; /* No problem! */
   4952 
   4953 			unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
   4954 			           "[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
   4955 				   log_data_tkl("tkl", tkl),
   4956 				   log_data_string("command", cmd),
   4957 				   log_data_string("_space", destination ? " " : ""),
   4958 				   log_data_string("destination", destination ? destination : ""),
   4959 				   log_data_string("str", str));
   4960 
   4961 			RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, str, str_in, target, destination, tkl);
   4962 
   4963 			/* If we should stop after the first match, we end here... */
   4964 			if (SPAMFILTER_STOP_ON_FIRST_MATCH)
   4965 			{
   4966 				winner_tkl = tkl;
   4967 				break;
   4968 			}
   4969 
   4970 			/* Otherwise.. we set 'winner_tkl' to the spamfilter with the strongest action. */
   4971 			if (!winner_tkl)
   4972 				winner_tkl = tkl;
   4973 			else
   4974 				winner_tkl = choose_winning_spamfilter(tkl, winner_tkl);
   4975 
   4976 			/* and continue.. */
   4977 		}
   4978 	}
   4979 
   4980 	tkl = winner_tkl;
   4981 
   4982 	if (!tkl)
   4983 		return 0; /* NOMATCH, we are done */
   4984 
   4985 	/* Spamfilter matched, take action: */
   4986 
   4987 	reason = unreal_decodespace(tkl->ptr.spamfilter->tkl_reason);
   4988 	if ((tkl->ptr.spamfilter->action == BAN_ACT_BLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_BLOCK))
   4989 	{
   4990 		switch(target)
   4991 		{
   4992 			case SPAMF_USERMSG:
   4993 			case SPAMF_USERNOTICE:
   4994 			{
   4995 				char errmsg[512];
   4996 				ircsnprintf(errmsg, sizeof(errmsg), "Message blocked: %s", reason);
   4997 				sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg);
   4998 				break;
   4999 			}
   5000 			case SPAMF_CHANNOTICE:
   5001 				break; /* no replies to notices */
   5002 			case SPAMF_CHANMSG:
   5003 			{
   5004 				sendto_one(client, NULL, ":%s 404 %s %s :Message blocked: %s",
   5005 					me.name, client->name, destination, reason);
   5006 				break;
   5007 			}
   5008 			case SPAMF_MTAG:
   5009 			{
   5010 				sendnumericfmt(client, ERR_CANNOTDOCOMMAND, "%s :Command blocked: %s",
   5011 					cmd, reason);
   5012 				break;
   5013 			}
   5014 			case SPAMF_DCC:
   5015 			{
   5016 				char errmsg[512];
   5017 				ircsnprintf(errmsg, sizeof(errmsg), "DCC blocked: %s", reason);
   5018 				sendnumeric(client, ERR_CANTSENDTOUSER, destination, errmsg);
   5019 				break;
   5020 			}
   5021 			case SPAMF_AWAY:
   5022 				/* hack to deal with 'after-away-was-set-filters' */
   5023 				if (client->user->away && !strcmp(str_in, client->user->away))
   5024 				{
   5025 					/* free away & broadcast the unset */
   5026 					safe_free(client->user->away);
   5027 					client->user->away = NULL;
   5028 					sendto_server(client, 0, 0, NULL, ":%s AWAY", client->id);
   5029 				}
   5030 				break;
   5031 			case SPAMF_TOPIC:
   5032 				//...
   5033 				sendnotice(client, "Setting of topic on %s to that text is blocked: %s",
   5034 					destination, reason);
   5035 				break;
   5036 			default:
   5037 				break;
   5038 		}
   5039 		return 1;
   5040 	} else
   5041 	if ((tkl->ptr.spamfilter->action == BAN_ACT_WARN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_WARN))
   5042 	{
   5043 		if ((target != SPAMF_USER) && (target != SPAMF_QUIT))
   5044 			sendnumeric(client, RPL_SPAMCMDFWD, cmd, reason);
   5045 		return 0;
   5046 	} else
   5047 	if ((tkl->ptr.spamfilter->action == BAN_ACT_DCCBLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_DCCBLOCK))
   5048 	{
   5049 		if (target == SPAMF_DCC)
   5050 		{
   5051 			sendnotice(client, "DCC to %s blocked: %s", destination, reason);
   5052 			sendnotice(client, "*** You have been blocked from sending files, reconnect to regain permission to send files");
   5053 			SetDCCBlock(client);
   5054 		}
   5055 		return 1;
   5056 	} else
   5057 	if ((tkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN))
   5058 	{
   5059 		if (IsVirus(client)) /* Already tagged */
   5060 			return 0;
   5061 
   5062 		/* There's a race condition for SPAMF_USER, so 'rettk' is used for SPAMF_USER
   5063 		 * when a user is currently connecting and filters are checked:
   5064 		 */
   5065 		if (!IsUser(client))
   5066 		{
   5067 			if (rettkl)
   5068 				*rettkl = tkl;
   5069 			return 1;
   5070 		}
   5071 
   5072 		join_viruschan(client, tkl, target);
   5073 		return 1;
   5074 	} else
   5075 	{
   5076 		return place_host_ban(client, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration);
   5077 	}
   5078 
   5079 	return 0; /* NOTREACHED */
   5080 }
   5081 
   5082 /** Check message-tag spamfilters.
   5083  * @param client	The client
   5084  * @param mtags		Message tags sent by client
   5085  * @param cmd		Command to be executed (can be NULL)
   5086  * @retval Return 1 to stop processing the command (ignore it) or 0 to allow/continue as normal
   5087  */
   5088 int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd)
   5089 {
   5090 	MessageTag *m;
   5091 	char buf[4096];
   5092 	char *str;
   5093 
   5094 	/* This is a shortcut: if there are no spamfilters present
   5095 	 * on message tags then we can return immediately.
   5096 	 * Saves a lot of CPU and it is quite likely too!
   5097 	 */
   5098 	if (mtag_spamfilters_present == 0)
   5099 		return 0;
   5100 
   5101 	for (m = mtags; m; m = m->next)
   5102 	{
   5103 		if (m->value)
   5104 		{
   5105 			snprintf(buf, sizeof(buf), "%s=%s", m->name, m->value);
   5106 			str = buf;
   5107 		} else {
   5108 			str = m->name;
   5109 		}
   5110 		if (match_spamfilter(client, str, SPAMF_MTAG, cmd, NULL, 0, NULL))
   5111 			return 1;
   5112 	}
   5113 	return 0;
   5114 }
   5115 
   5116 /** Updates 'mtag_spamfilters_present' based on if any spamfilters
   5117  * are present with the SPAMF_MTAG target.
   5118  */
   5119 int check_mtag_spamfilters_present(void)
   5120 {
   5121 	TKL *tkl;
   5122 
   5123 	for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
   5124 	{
   5125 		if (tkl->ptr.spamfilter->target & SPAMF_MTAG)
   5126 		{
   5127 			mtag_spamfilters_present = 1;
   5128 			return 1;
   5129 		}
   5130 	}
   5131 
   5132 	mtag_spamfilters_present = 0;
   5133 	return 0;
   5134 }
   5135 
   5136 /** CIDR function to compare the first 'mask' bits.
   5137  * @author Taken from atheme
   5138  * @returns 1 if equal, 0 if not.
   5139  */
   5140 static int comp_with_mask(void *addr, void *dest, u_int mask)
   5141 {
   5142 	if (memcmp(addr, dest, mask / 8) == 0)
   5143 	{
   5144 		int n = mask / 8;
   5145 		int m = (0xffff << (8 - (mask % 8)));
   5146 		if (mask % 8 == 0 || (((u_char *) addr)[n] & m) == (((u_char *) dest)[n] & m))
   5147 		{
   5148 			return (1);
   5149 		}
   5150 	}
   5151 	return (0);
   5152 }
   5153 
   5154 #define IPSZ 16
   5155 
   5156 /** Match a user against a mask.
   5157  * This will deal with 'nick!user@host', 'user@host' and just 'host'.
   5158  * We try to match the 'host' portion against the client IP, real host, etc...
   5159  * CIDR support is available so 'host' may be like '1.2.0.0/16'.
   5160  * @returns 1 on match, 0 on no match.
   5161  */
   5162 int _match_user(const char *rmask, Client *client, int options)
   5163 {
   5164 	char mask[NICKLEN+USERLEN+HOSTLEN+8];
   5165 	char clientip[IPSZ], maskip[IPSZ];
   5166 	char *p = NULL;
   5167 	char *nmask = NULL, *umask = NULL, *hmask = NULL;
   5168 	int cidr = -1; /* CIDR length, -1 for no CIDR */
   5169 
   5170 	strlcpy(mask, rmask, sizeof(mask));
   5171 
   5172 	if ((options & MATCH_CHECK_EXTENDED) &&
   5173 	    is_extended_server_ban(mask) &&
   5174 	    client->user)
   5175 	{
   5176 		/* Check user properties / extbans style */
   5177 		return _match_user_extended_server_ban(rmask, client);
   5178 	}
   5179 
   5180 	if (!(options & MATCH_MASK_IS_UHOST))
   5181 	{
   5182 		p = strchr(mask, '!');
   5183 		if (p)
   5184 		{
   5185 			*p++ = '\0';
   5186 			if (!*mask)
   5187 				return 0; /* NOMATCH: '!...' */
   5188 			nmask = mask;
   5189 			umask = p;
   5190 
   5191 			/* Could just as well check nick right now */
   5192 			if (!match_simple(nmask, client->name))
   5193 				return 0; /* NOMATCH: nick mask did not match */
   5194 		}
   5195 	}
   5196 
   5197 	if (!(options & (MATCH_MASK_IS_HOST)))
   5198 	{
   5199 		p = strchr(p ? p : mask, '@');
   5200 		if (p)
   5201 		{
   5202 			char *client_username = (client->user && *client->user->username) ? client->user->username : client->ident;
   5203 
   5204 			*p++ = '\0';
   5205 			if (!*p || !*mask)
   5206 				return 0; /* NOMATCH: '...@' or '@...' */
   5207 			hmask = p;
   5208 			if (!umask)
   5209 				umask = mask;
   5210 
   5211 			/* Check user portion right away */
   5212 			if (!match_simple(umask, client_username))
   5213 				return 0; /* NOMATCH: user mask did not match */
   5214 		} else {
   5215 			if (nmask)
   5216 				return 0; /* NOMATCH: 'abc!def' (or even just 'abc!') */
   5217 			hmask = mask;
   5218 		}
   5219 	} else {
   5220 		hmask = mask;
   5221 	}
   5222 
   5223 	/* If we get here then we have done checking nick / ident (if it was needed)
   5224 	 * and now need to match the 'host' portion.
   5225 	 */
   5226 
   5227 	/**** Check visible host ****/
   5228 	if (options & MATCH_CHECK_VISIBLE_HOST)
   5229 	{
   5230 		char *hostname = client->user ? GetHost(client) : (MyUser(client) ? client->local->sockhost : NULL);
   5231 		if (hostname && match_simple(hmask, hostname))
   5232 			return 1; /* MATCH: visible host */
   5233 	}
   5234 
   5235 	/**** Check cloaked host ****/
   5236 	if (options & MATCH_CHECK_CLOAKED_HOST)
   5237 	{
   5238 		if (client->user && match_simple(hmask, client->user->cloakedhost))
   5239 			return 1; /* MATCH: cloaked host */
   5240 	}
   5241 
   5242 	/**** check on IP ****/
   5243 	if (options & MATCH_CHECK_IP)
   5244 	{
   5245 		p = strchr(hmask, '/');
   5246 		if (p)
   5247 		{
   5248 			*p++ = '\0';
   5249 			cidr = atoi(p);
   5250 			if (cidr <= 0)
   5251 				return 0; /* NOMATCH: invalid CIDR */
   5252 		}
   5253 
   5254 		if (strchr(hmask, '?') || strchr(hmask, '*'))
   5255 		{
   5256 			/* Wildcards */
   5257 			if (client->ip && match_simple(hmask, client->ip))
   5258 				return 1; /* MATCH (IP with wildcards) */
   5259 		} else
   5260 		if (strchr(hmask, ':'))
   5261 		{
   5262 			/* IPv6 hostmask */
   5263 
   5264 			/* We can actually return here on match/nomatch as we don't need to check the
   5265 			 * virtual host and things like that since ':' can never be in a hostname.
   5266 			 */
   5267 			if (!client->ip || !strchr(client->ip, ':'))
   5268 				return 0; /* NOMATCH: hmask is IPv6 address and client is not IPv6 */
   5269 			if (!inet_pton(AF_INET6, client->ip, clientip))
   5270 				return 0; /* NOMATCH: unusual failure */
   5271 			if (!inet_pton(AF_INET6, hmask, maskip))
   5272 				return 0; /* NOMATCH: invalid IPv6 IP in hostmask */
   5273 
   5274 			if (cidr < 0)
   5275 				return comp_with_mask(clientip, maskip, 128); /* MATCH/NOMATCH by exact IP */
   5276 
   5277 			if (cidr > 128)
   5278 				return 0; /* NOMATCH: invalid CIDR */
   5279 
   5280 			return comp_with_mask(clientip, maskip, cidr);
   5281 		} else
   5282 		{
   5283 			/* Host is not IPv6 and does not contain wildcards.
   5284 			 * So could be a literal IPv4 address or IPv4 CIDR.
   5285 			 * NOTE: could also be neither (like a real hostname), so don't return 0 on nomatch,
   5286 			 * in that case we should just continue...
   5287 			 * The exception is CIDR. If we have CIDR mask then don't bother checking for
   5288 			 * virtual hosts and things like that since '/' can never be in a hostname.
   5289 			 */
   5290 			if (client->ip && inet_pton(AF_INET, client->ip, clientip) && inet_pton(AF_INET, hmask, maskip))
   5291 			{
   5292 				if (cidr < 0)
   5293 				{
   5294 					if (comp_with_mask(clientip, maskip, 32))
   5295 						return 1; /* MATCH: exact IP */
   5296 				}
   5297 				else if (cidr > 32)
   5298 					return 0; /* NOMATCH: invalid CIDR */
   5299 				else
   5300 					return comp_with_mask(clientip, maskip, cidr); /* MATCH/NOMATCH by CIDR */
   5301 			}
   5302 		}
   5303 	}
   5304 
   5305 	/**** Check real host ****/
   5306 	if (options & MATCH_CHECK_REAL_HOST)
   5307 	{
   5308 		char *hostname = client->user ? client->user->realhost : (MyUser(client) ? client->local->sockhost : NULL);
   5309 		if (hostname && match_simple(hmask, hostname))
   5310 			return 1; /* MATCH: hostname match */
   5311 	}
   5312 
   5313 	return 0; /* NOMATCH: nothing of the above matched */
   5314 }
   5315 
   5316 /** Returns 1 if the user is allowed by any of the security groups in the named list.
   5317  * This is only used by security-group::security-group and
   5318  * security-group::exclude-security-group.
   5319  * @param client	Client to check
   5320  * @param l		The NameList
   5321  * @returns 1 if any of the security groups match, 0 if none of them matched.
   5322  */
   5323 int _unreal_match_iplist(Client *client, NameList *l)
   5324 {
   5325 	char client_ipv6 = 0;
   5326 	char clientip[IPSZ], maskip[IPSZ];
   5327 
   5328 	if (!client->ip)
   5329 		return 0; /* unusual, maybe services? */
   5330 
   5331 	if (strchr(client->ip, ':'))
   5332 	{
   5333 		client_ipv6 = 1;
   5334 		if (!inet_pton(AF_INET6, client->ip, clientip))
   5335 			return 0; /* unusual failure */
   5336 	} else {
   5337 		if (!inet_pton(AF_INET, client->ip, clientip))
   5338 			return 0; /* unusual failure */
   5339 	}
   5340 
   5341 	for (; l; l = l->next)
   5342 	{
   5343 		char mask[512], *p;
   5344 		int cidr = -1; /* CIDR length, -1 for no CIDR */
   5345 
   5346 		strlcpy(mask, l->name, sizeof(mask));
   5347 		p = strchr(mask, '/');
   5348 		if (p)
   5349 		{
   5350 			*p++ = '\0';
   5351 			cidr = atoi(p);
   5352 			if (cidr <= 0)
   5353 				return 0; /* NOMATCH: invalid CIDR */
   5354 		}
   5355 
   5356 		/* Three possible types: wildcard, ipv6, ipv4 */
   5357 
   5358 		if (strchr(mask, '*') || strchr(mask, '?'))
   5359 		{
   5360 			/* Wildcards */
   5361 			if (match_simple(mask, client->ip))
   5362 				return 1; /* MATCH by wildcard IP */
   5363 		}
   5364 		else if (strchr(mask, ':'))
   5365 		{
   5366 			/* IPv6 */
   5367 			if (!client_ipv6)
   5368 				continue; /* NOMATCH: client is IPv4 */
   5369 			if (!inet_pton(AF_INET6, mask, maskip))
   5370 				continue; /* NOMATCH: invalid IPv6 IP in mask */
   5371 			if (cidr < 0)
   5372 			{
   5373 				/* Try to match by exact IP */
   5374 				if (comp_with_mask(clientip, maskip, 128))
   5375 					return 1; /* MATCH by exact IP */
   5376 			} else
   5377 			if (cidr > 128)
   5378 			{
   5379 				continue; /* NOMATCH: invalid CIDR */
   5380 			} else
   5381 			if (comp_with_mask(clientip, maskip, cidr))
   5382 			{
   5383 				return 1; /* MATCH by CIDR */
   5384 			}
   5385 		} else
   5386 		{
   5387 			/* IPv4 */
   5388 			if (client_ipv6)
   5389 				continue; /* NOMATCH: client is IPv6 */
   5390 			if (!inet_pton(AF_INET, mask, maskip))
   5391 				continue; /* NOMATCH: invalid IPv6 IP in mask */
   5392 			if (cidr < 0)
   5393 			{
   5394 				/* Try to match by exact IP */
   5395 				if (comp_with_mask(clientip, maskip, 32))
   5396 					return 1; /* MATCH: by exact IP */
   5397 			} else
   5398 			if (cidr > 32)
   5399 			{
   5400 				continue; /* NOMATCH: invalid CIDR */
   5401 			} else
   5402 			if (comp_with_mask(clientip, maskip, cidr))
   5403 			{
   5404 				return 1; /* MATCH by CIDR */
   5405 			}
   5406 		}
   5407 	}
   5408 	return 0;
   5409 }
   5410 
   5411 
   5412 int _match_user_extended_server_ban(const char *banstr, Client *client)
   5413 {
   5414 	const char *nextbanstr;
   5415 	Extban *extban;
   5416 	BanContext *b;
   5417 	int ret;
   5418 
   5419 	if (!is_extended_server_ban(banstr))
   5420 		return 0; /* we should never have been called */
   5421 
   5422 	extban = findmod_by_bantype(banstr, &nextbanstr);
   5423 	if (!extban ||
   5424 	    !(extban->options & EXTBOPT_TKL) ||
   5425 	    !(extban->is_banned_events & BANCHK_TKL))
   5426 	{
   5427 		return 0; /* extban not found or of incorrect type (eg ~T) */
   5428 	}
   5429 
   5430 	b = safe_alloc(sizeof(BanContext));
   5431 	b->client = client;
   5432 	b->banstr = nextbanstr;
   5433 	b->ban_check_types = BANCHK_TKL;
   5434 	ret = extban->is_banned(b);
   5435 	safe_free(b);
   5436 	return ret;
   5437 }