1
0
mirror of https://github.com/unrealircd/unrealircd.git synced 2026-07-02 02:06:38 +02:00
Files
unrealircd/src/modules/tkl.c
T
Bram Matthys 8a6c84876e Rewrite/cleanup huge portion of TKL handling (16 files updated, but
src/modules/tkl.c is the main one).
Also move DB writing/reading functions to src/misc.c so they can be
removed out of channeldb and tkldb.

Important note to current tkldb users:
Unfortunately due to the major cleanup I had to remove upgrading
for previously saved tkl db files. That seemed not worth the effort
for maybe <15 current users or so. It also makes the tkldb code
a lot more cleaner. Otherwise it would be a huge mess.

Currently a FIXME item: spamfilter support in RMTKL.
2019-08-31 15:29:04 +02:00

3872 lines
106 KiB
C

/*
* Unreal Internet Relay Chat Daemon, src/modules/m_tkl.c
* TKL Commands: server bans, spamfilters, etc.
* (C) 1999-2019 Bram Matthys and The UnrealIRCd Team
*
* See file AUTHORS in IRC package for additional names of
* the programmers.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 1, or (at your option)
* any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 675 Mass Ave, Cambridge, MA 02139, USA.
*/
#include "unrealircd.h"
/* Forward declarations */
int tkl_config_test_spamfilter(ConfigFile *, ConfigEntry *, int, int *);
int tkl_config_run_spamfilter(ConfigFile *, ConfigEntry *, int);
int tkl_config_test_ban(ConfigFile *, ConfigEntry *, int, int *);
int tkl_config_run_ban(ConfigFile *, ConfigEntry *, int);
CMD_FUNC(m_gline);
CMD_FUNC(m_shun);
CMD_FUNC(m_tempshun);
CMD_FUNC(m_gzline);
CMD_FUNC(m_kline);
CMD_FUNC(m_zline);
CMD_FUNC(m_spamfilter);
int m_tkl_line(aClient *cptr, aClient *sptr, int parc, char *parv[], char* type);
int _tkl_hash(unsigned int c);
char _tkl_typetochar(int type);
int _tkl_chartotype(char c);
char *_tkl_type_string(aTKline *tk);
aTKline *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, char *set_by,
time_t expire_at, time_t set_at, int soft, int flags);
aTKline *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
time_t expire_at, time_t set_at, int flags);
aTKline *_tkl_add_spamfilter(int type, unsigned short target, unsigned short action, aMatch *match, char *set_by,
time_t expire_at, time_t set_at,
time_t spamf_tkl_duration, char *spamf_tkl_reason,
int flags);
void _sendnotice_tkl_del(char *removed_by, aTKline *tkl);
void _sendnotice_tkl_add(aTKline *tkl);
void _free_tkl(aTKline *tkl);
void _tkl_del_line(aTKline *tkl);
static void _tkl_check_local_remove_shun(aTKline *tmp);
void tkl_expire_entry(aTKline * tmp);
EVENT(tkl_check_expire);
int _find_tkline_match(aClient *cptr, int skip_soft);
int _find_shun(aClient *cptr);
int _find_spamfilter_user(aClient *sptr, int flags);
aTKline *_find_qline(aClient *cptr, char *nick, int *ishold);
aTKline *_find_tkline_match_zap(aClient *cptr);
void _tkl_stats(aClient *cptr, int type, char *para);
void _tkl_synch(aClient *sptr);
CMD_FUNC(_m_tkl);
int _place_host_ban(aClient *sptr, int action, char *reason, long duration);
int _run_spamfilter(aClient *sptr, char *str_in, int type, char *target, int flags, aTKline **rettk);
int _join_viruschan(aClient *sptr, aTKline *tk, int type);
void _spamfilter_build_user_string(char *buf, char *nick, aClient *acptr);
int _match_user(char *rmask, aClient *acptr, int options);
int _tkl_ip_hash(char *ip);
int _tkl_ip_hash_type(int type);
aTKline *_find_tkl_serverban(int type, char *usermask, char *hostmask, int softban);
aTKline *_find_tkl_nameban(int type, char *name, int hold);
aTKline *_find_tkl_spamfilter(int type, char *match_string, unsigned short action, unsigned short target);
/* Externals (only for us :D) */
extern int MODVAR spamf_ugly_vchanoverride;
ModuleHeader MOD_HEADER(tkl)
= {
"tkl",
"5.0",
"Server ban commands such as /GLINE, /SPAMFILTER, etc.",
"UnrealIRCd Team",
"unrealircd-5",
};
MOD_TEST(tkl)
{
MARK_AS_OFFICIAL_MODULE(modinfo);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_spamfilter);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, tkl_config_test_ban);
EfunctionAdd(modinfo->handle, EFUNC_TKL_HASH, _tkl_hash);
EfunctionAdd(modinfo->handle, EFUNC_TKL_TYPETOCHAR, TO_INTFUNC(_tkl_typetochar));
EfunctionAdd(modinfo->handle, EFUNC_TKL_CHARTOTYPE, TO_INTFUNC(_tkl_chartotype));
EfunctionAddPChar(modinfo->handle, EFUNC_TKL_TYPE_STRING, _tkl_type_string);
EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_SERVERBAN, TO_PVOIDFUNC(_tkl_add_serverban));
EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_NAMEBAN, TO_PVOIDFUNC(_tkl_add_nameban));
EfunctionAddPVoid(modinfo->handle, EFUNC_TKL_ADD_SPAMFILTER, TO_PVOIDFUNC(_tkl_add_spamfilter));
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_DEL_LINE, _tkl_del_line);
EfunctionAddVoid(modinfo->handle, EFUNC_FREE_TKL, _free_tkl);
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_CHECK_LOCAL_REMOVE_SHUN, _tkl_check_local_remove_shun);
EfunctionAdd(modinfo->handle, EFUNC_FIND_TKLINE_MATCH, _find_tkline_match);
EfunctionAdd(modinfo->handle, EFUNC_FIND_SHUN, _find_shun);
EfunctionAdd(modinfo->handle, EFUNC_FIND_SPAMFILTER_USER, _find_spamfilter_user);
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_QLINE, TO_PVOIDFUNC(_find_qline));
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKLINE_MATCH_ZAP, TO_PVOIDFUNC(_find_tkline_match_zap));
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SERVERBAN, TO_PVOIDFUNC(_find_tkl_serverban));
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_NAMEBAN, TO_PVOIDFUNC(_find_tkl_nameban));
EfunctionAddPVoid(modinfo->handle, EFUNC_FIND_TKL_SPAMFILTER, TO_PVOIDFUNC(_find_tkl_spamfilter));
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_STATS, _tkl_stats);
EfunctionAddVoid(modinfo->handle, EFUNC_TKL_SYNCH, _tkl_synch);
EfunctionAdd(modinfo->handle, EFUNC_M_TKL, _m_tkl);
EfunctionAdd(modinfo->handle, EFUNC_PLACE_HOST_BAN, _place_host_ban);
EfunctionAdd(modinfo->handle, EFUNC_DOSPAMFILTER, _run_spamfilter);
EfunctionAdd(modinfo->handle, EFUNC_DOSPAMFILTER_VIRUSCHAN, _join_viruschan);
EfunctionAddVoid(modinfo->handle, EFUNC_SPAMFILTER_BUILD_USER_STRING, _spamfilter_build_user_string);
EfunctionAdd(modinfo->handle, EFUNC_MATCH_USER, _match_user);
EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH, _tkl_ip_hash);
EfunctionAdd(modinfo->handle, EFUNC_TKL_IP_HASH_TYPE, _tkl_ip_hash_type);
EfunctionAddVoid(modinfo->handle, EFUNC_SENDNOTICE_TKL_ADD, _sendnotice_tkl_add);
EfunctionAddVoid(modinfo->handle, EFUNC_SENDNOTICE_TKL_DEL, _sendnotice_tkl_del);
return MOD_SUCCESS;
}
MOD_INIT(tkl)
{
MARK_AS_OFFICIAL_MODULE(modinfo);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_spamfilter);
HookAdd(modinfo->handle, HOOKTYPE_CONFIGRUN, 0, tkl_config_run_ban);
CommandAdd(modinfo->handle, "GLINE", m_gline, 3, M_OPER);
CommandAdd(modinfo->handle, "SHUN", m_shun, 3, M_OPER);
CommandAdd(modinfo->handle, "TEMPSHUN", m_tempshun, 2, M_OPER);
CommandAdd(modinfo->handle, "ZLINE", m_zline, 3, M_OPER);
CommandAdd(modinfo->handle, "KLINE", m_kline, 3, M_OPER);
CommandAdd(modinfo->handle, "GZLINE", m_gzline, 3, M_OPER);
CommandAdd(modinfo->handle, "SPAMFILTER", m_spamfilter, 7, M_OPER);
CommandAdd(modinfo->handle, "TKL", _m_tkl, MAXPARA, M_OPER|M_SERVER);
MARK_AS_OFFICIAL_MODULE(modinfo);
return MOD_SUCCESS;
}
MOD_LOAD(tkl)
{
EventAdd(modinfo->handle, "tklexpire", 5, 0, tkl_check_expire, NULL);
return MOD_SUCCESS;
}
MOD_UNLOAD(tkl)
{
return MOD_SUCCESS;
}
/** Test a spamfilter { } block in the configuration file */
int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
ConfigEntry *cep, *cepp;
int errors = 0;
char *match = NULL, *reason = NULL;
char has_target = 0, has_match = 0, has_action = 0, has_reason = 0, has_bantime = 0, has_match_type = 0;
int match_type = 0;
/* We are only interested in spamfilter { } blocks */
if ((type != CONFIG_MAIN) || strcmp(ce->ce_varname, "spamfilter"))
return 0;
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "target"))
{
if (has_target)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "spamfilter::target");
continue;
}
has_target = 1;
if (cep->ce_vardata)
{
if (!spamfilter_getconftargets(cep->ce_vardata))
{
config_error("%s:%i: unknown spamfiler target type '%s'",
cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
errors++;
}
}
else if (cep->ce_entries)
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
{
if (!spamfilter_getconftargets(cepp->ce_varname))
{
config_error("%s:%i: unknown spamfiler target type '%s'",
cepp->ce_fileptr->cf_filename,
cepp->ce_varlinenum, cepp->ce_varname);
errors++;
}
}
}
else
{
config_error_empty(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "spamfilter", cep->ce_varname);
errors++;
}
continue;
}
if (!cep->ce_vardata)
{
config_error_empty(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
"spamfilter", cep->ce_varname);
errors++;
continue;
}
if (!strcmp(cep->ce_varname, "reason"))
{
if (has_reason)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "spamfilter::reason");
continue;
}
has_reason = 1;
reason = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "match"))
{
if (has_match)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "spamfilter::match");
continue;
}
has_match = 1;
match = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "action"))
{
if (has_action)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "spamfilter::action");
continue;
}
has_action = 1;
if (!banact_stringtoval(cep->ce_vardata))
{
config_error("%s:%i: spamfilter::action has unknown action type '%s'",
cep->ce_fileptr->cf_filename, cep->ce_varlinenum, cep->ce_vardata);
errors++;
}
}
else if (!strcmp(cep->ce_varname, "ban-time"))
{
if (has_bantime)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "spamfilter::ban-time");
continue;
}
has_bantime = 1;
}
else if (!strcmp(cep->ce_varname, "match-type"))
{
if (has_match_type)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "spamfilter::match-type");
continue;
}
if (!strcasecmp(cep->ce_vardata, "posix"))
{
config_error("%s:%i: this spamfilter uses match-type 'posix' which is no longer supported. "
"You must switch over to match-type 'regex' instead. "
"See https://www.unrealircd.org/docs/FAQ#spamfilter-posix-deprecated",
ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
errors++;
*errs = errors;
return -1; /* return now, otherwise there will be issues */
}
match_type = unreal_match_method_strtoval(cep->ce_vardata);
if (match_type == 0)
{
config_error("%s:%i: spamfilter::match-type: unknown match type '%s', "
"should be one of: 'simple', 'regex' or 'posix'",
cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
cep->ce_vardata);
errors++;
continue;
}
has_match_type = 1;
}
else
{
config_error_unknown(cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
"spamfilter", cep->ce_varname);
errors++;
continue;
}
}
if (match && match_type)
{
aMatch *m;
char *err;
m = unreal_create_match(match_type, match, &err);
if (!m)
{
config_error("%s:%i: spamfilter::match contains an invalid regex: %s",
ce->ce_fileptr->cf_filename,
ce->ce_varlinenum,
err);
errors++;
} else
{
unreal_delete_match(m);
}
}
if (!has_match)
{
config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
"spamfilter::match");
errors++;
}
if (!has_target)
{
config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
"spamfilter::target");
errors++;
}
if (!has_action)
{
config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
"spamfilter::action");
errors++;
}
if (match && reason && (strlen(match) + strlen(reason) > 505))
{
config_error("%s:%i: spamfilter block problem: match + reason field are together over 505 bytes, "
"please choose a shorter regex or reason",
ce->ce_fileptr->cf_filename, ce->ce_varlinenum);
errors++;
}
if (!has_match_type)
{
config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
"spamfilter::match-type");
errors++;
}
if (!has_match_type && !has_match && has_action && has_target)
{
need_34_upgrade = 1;
}
if (match && !strcmp(match, "^LOL! //echo -a \\$\\(\\$decode\\(.+,m\\),[0-9]\\)$"))
{
config_warn("*** IMPORTANT ***");
config_warn("You have old examples in your spamfilter.conf. "
"We suggest you to edit this file and replace the examples.");
config_warn("Please read https://www.unrealircd.org/docs/FAQ#old-spamfilter-conf !!!");
config_warn("*****************");
}
*errs = errors;
return errors ? -1 : 1;
}
/** Process a spamfilter { } block in the configuration file */
int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
{
ConfigEntry *cep;
ConfigEntry *cepp;
aTKline *tk;
char *word = NULL, *reason = NULL;
time_t bantime = (SPAMFILTER_BAN_TIME ? SPAMFILTER_BAN_TIME : 86400);
char *banreason = "<internally added by ircd>";
int action = 0, target = 0;
int match_type = 0;
aMatch *m;
/* We are only interested in spamfilter { } blocks */
if ((type != CONFIG_MAIN) || strcmp(ce->ce_varname, "spamfilter"))
return 0;
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "match"))
{
word = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "target"))
{
if (cep->ce_vardata)
target = spamfilter_getconftargets(cep->ce_vardata);
else
{
for (cepp = cep->ce_entries; cepp; cepp = cepp->ce_next)
target |= spamfilter_getconftargets(cepp->ce_varname);
}
}
else if (!strcmp(cep->ce_varname, "action"))
{
action = banact_stringtoval(cep->ce_vardata);
}
else if (!strcmp(cep->ce_varname, "reason"))
{
reason = cep->ce_vardata;
}
else if (!strcmp(cep->ce_varname, "ban-time"))
{
bantime = config_checkval(cep->ce_vardata, CFG_TIME);
}
else if (!strcmp(cep->ce_varname, "match-type"))
{
match_type = unreal_match_method_strtoval(cep->ce_vardata);
}
}
m = unreal_create_match(match_type, word, NULL);
tkl_add_spamfilter(TKL_SPAMF,
target,
action,
m,
me.name,
0,
TStime(),
bantime,
banreason,
TKL_FLAG_CONFIG);
return 1;
}
/** Test a ban { } block in the configuration file */
int tkl_config_test_ban(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
ConfigEntry *cep;
int errors = 0;
char has_mask = 0, has_reason = 0;
/* We are only interested in ban { } blocks */
if (type != CONFIG_BAN)
return 0;
if (strcmp(ce->ce_vardata, "nick") && strcmp(ce->ce_vardata, "user") &&
strcmp(ce->ce_vardata, "ip"))
{
return 0;
}
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (config_is_blankorempty(cep, "ban"))
{
errors++;
continue;
}
if (!strcmp(cep->ce_varname, "mask"))
{
if (has_mask)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "ban::mask");
continue;
}
has_mask = 1;
}
else if (!strcmp(cep->ce_varname, "reason"))
{
if (has_reason)
{
config_warn_duplicate(cep->ce_fileptr->cf_filename,
cep->ce_varlinenum, "ban::reason");
continue;
}
has_reason = 1;
}
else
{
config_error("%s:%i: unknown directive ban %s::%s",
cep->ce_fileptr->cf_filename, cep->ce_varlinenum,
ce->ce_vardata,
cep->ce_varname);
errors++;
}
}
if (!has_mask)
{
config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
"ban::mask");
errors++;
}
if (!has_reason)
{
config_error_missing(ce->ce_fileptr->cf_filename, ce->ce_varlinenum,
"ban::reason");
errors++;
}
*errs = errors;
return errors ? -1 : 1;
}
/** Process a ban { } block in the configuration file */
int tkl_config_run_ban(ConfigFile *cf, ConfigEntry *ce, int configtype)
{
ConfigEntry *cep;
char *usermask = NULL;
char *hostmask = NULL;
char *reason = NULL;
int tkltype;
/* We are only interested in ban { } blocks */
if (configtype != CONFIG_BAN)
return 0;
if (strcmp(ce->ce_vardata, "nick") && strcmp(ce->ce_vardata, "user") &&
strcmp(ce->ce_vardata, "ip"))
{
return 0;
}
for (cep = ce->ce_entries; cep; cep = cep->ce_next)
{
if (!strcmp(cep->ce_varname, "mask"))
{
char buf[512], *p;
strlcpy(buf, cep->ce_vardata, sizeof(buf));
p = strchr(buf, '@');
if (p)
{
*p++ = '\0';
usermask = strdup(buf);
hostmask = strdup(p);
} else {
hostmask = strdup(cep->ce_vardata);
}
} else
if (!strcmp(cep->ce_varname, "reason"))
{
reason = strdup(cep->ce_vardata);
}
}
if (!usermask)
usermask = strdup("*");
if (!reason)
reason = strdup("-");
if (!strcmp(ce->ce_vardata, "nick"))
tkltype = TKL_NAME;
else if (!strcmp(ce->ce_vardata, "user"))
tkltype = TKL_KILL;
else if (!strcmp(ce->ce_vardata, "ip"))
tkltype = TKL_ZAP;
else
abort(); /* impossible */
if (TKLIsNameBanType(tkltype))
tkl_add_nameban(tkltype, hostmask, 0, reason, "-config-", 0, TStime(), TKL_FLAG_CONFIG);
else if (TKLIsServerBanType(tkltype))
tkl_add_serverban(tkltype, usermask, hostmask, reason, "-config-", 0, TStime(), 0, TKL_FLAG_CONFIG);
safefree(usermask);
safefree(hostmask);
safefree(reason);
return 1;
}
/** Return unique spamfilter id for aTKline */
char *spamfilter_id(aTKline *tk)
{
static char buf[128];
snprintf(buf, sizeof(buf), "%p", (void *)tk);
return buf;
}
/** GLINE - Global kline.
** Syntax: /gline [+|-]u@h mask time :reason
**
** parv[1] = [+|-]u@h mask
** parv[2] = for how long
** parv[3] = reason
*/
CMD_FUNC(m_gline)
{
if (IsServer(sptr))
return 0;
if (!ValidatePermissionsForPath("server-ban:gline",sptr,NULL,NULL,NULL))
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
if (parc == 1)
{
char *parv[3];
parv[0] = NULL;
parv[1] = "gline";
parv[2] = NULL;
return do_cmd(sptr, sptr, recv_mtags, "STATS", 2, parv);
}
return m_tkl_line(cptr, sptr, parc, parv, "G");
}
/** GZLINE - Global zline.
*/
CMD_FUNC(m_gzline)
{
if (IsServer(sptr))
return 0;
if (!ValidatePermissionsForPath("server-ban:zline:global",sptr,NULL,NULL,NULL))
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
if (parc == 1)
{
char *parv[3];
parv[0] = NULL;
parv[1] = "gline"; /* (there's no /STATS gzline, it's included in /STATS gline output) */
parv[2] = NULL;
return do_cmd(sptr, sptr, recv_mtags, "STATS", 2, parv);
}
return m_tkl_line(cptr, sptr, parc, parv, "Z");
}
/** SHUN - Shun a user so it can no longer execute any meaningful commands.
*/
CMD_FUNC(m_shun)
{
if (IsServer(sptr))
return 0;
if (!ValidatePermissionsForPath("server-ban:shun",sptr,NULL,NULL,NULL))
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
if (parc == 1)
{
char *parv[3];
parv[0] = NULL;
parv[1] = "shun";
parv[2] = NULL;
return do_cmd(sptr, sptr, recv_mtags, "STATS", 2, parv);
}
return m_tkl_line(cptr, sptr, parc, parv, "s");
}
/** TEMPSHUN - Temporarily shun a user so it can no longer execute
* any meaningful commands - until the user disconnects (session only).
*/
CMD_FUNC(m_tempshun)
{
aClient *acptr;
char *comment = ((parc > 2) && !BadPtr(parv[2])) ? parv[2] : "no reason";
char *name;
int remove = 0;
if (MyClient(sptr) && (!ValidatePermissionsForPath("server-ban:shun:temporary",sptr,NULL,NULL,NULL)))
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
if ((parc < 2) || BadPtr(parv[1]))
{
sendnumeric(sptr, ERR_NEEDMOREPARAMS, "TEMPSHUN");
return 0;
}
if (parv[1][0] == '+')
name = parv[1]+1;
else if (parv[1][0] == '-')
{
name = parv[1]+1;
remove = 1;
} else
name = parv[1];
acptr = find_person(name, NULL);
if (!acptr)
{
sendnumeric(sptr, ERR_NOSUCHNICK, name);
return 0;
}
if (!MyClient(acptr))
{
sendto_one(acptr->from, NULL, ":%s TEMPSHUN %s :%s",
sptr->name, parv[1], comment);
} else {
char buf[1024];
if (!remove)
{
if (IsShunned(acptr))
{
sendnotice(sptr, "User '%s' already shunned", acptr->name);
} else if (ValidatePermissionsForPath("immune:server-ban:shun",acptr,NULL,NULL,NULL))
{
sendnotice(sptr, "You cannot tempshun '%s' because (s)he is an oper with 'immune:server-ban:shun' privilege", acptr->name);
} else
{
SetShunned(acptr);
ircsnprintf(buf, sizeof(buf), "Temporary shun added on user %s (%s@%s) by %s [%s]",
acptr->name, acptr->user->username, acptr->user->realhost,
sptr->name, comment);
sendto_snomask_global(SNO_TKL, "%s", buf);
}
} else {
if (!IsShunned(acptr))
{
sendnotice(sptr, "User '%s' is not shunned", acptr->name);
} else {
ClearShunned(acptr);
ircsnprintf(buf, sizeof(buf), "Removed temporary shun on user %s (%s@%s) by %s",
acptr->name, acptr->user->username, acptr->user->realhost,
sptr->name);
sendto_snomask_global(SNO_TKL, "%s", buf);
}
}
}
return 0;
}
/** KLINE - Kill line (ban user from local server)
*/
CMD_FUNC(m_kline)
{
if (IsServer(sptr))
return 0;
if (!ValidatePermissionsForPath("server-ban:kline:local:add",sptr,NULL,NULL,NULL))
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
if (parc == 1)
{
char *parv[3];
parv[0] = NULL;
parv[1] = "kline";
parv[2] = NULL;
return do_cmd(sptr, sptr, recv_mtags, "STATS", 2, parv);
}
if (!ValidatePermissionsForPath("server-ban:kline:remove",sptr,NULL,NULL,NULL) && *parv[1] == '-')
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
return m_tkl_line(cptr, sptr, parc, parv, "k");
}
/** Generate stats for '/GLINE -stats' and such */
void tkl_general_stats(aClient *sptr)
{
int index, index2;
aTKline *tkl;
int total = 0;
int subtotal;
/* First, hashed entries.. */
for (index = 0; index < TKLIPHASHLEN1; index++)
{
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
{
subtotal = 0;
for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
subtotal++;
if (subtotal > 0)
sendnotice(sptr, "Slot %d:%d has %d item(s)", index, index2, subtotal);
total += subtotal;
}
}
sendnotice(sptr, "Hashed TKL items: %d item(s)", total);
/* Now normal entries.. */
subtotal = 0;
for (index = 0; index < TKLISTLEN; index++)
{
for (tkl = tklines[index]; tkl; tkl = tkl->next)
subtotal++;
}
sendnotice(sptr, "Standard TKL items: %d item(s)", subtotal);
total += subtotal;
sendnotice(sptr, "Grand total TKL items: %d item(s)", total);
}
/** ZLINE - Kill a user as soon as it tries to connect to the server.
* This happens before any DNS/ident lookups have been done and
* before any data has been processed (including no SSL/TLS handshake, etc.)
*/
CMD_FUNC(m_zline)
{
if (IsServer(sptr))
return 0;
if (!ValidatePermissionsForPath("server-ban:zline:local:add",sptr,NULL,NULL,NULL))
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
if (parc == 1)
{
char *parv[3];
parv[0] = NULL;
parv[1] = "kline"; /* (there's no /STATS zline, it's included in /STATS kline output) */
parv[2] = NULL;
return do_cmd(sptr, sptr, recv_mtags, "STATS", 2, parv);
}
if ((parc > 1) && !BadPtr(parv[1]) && !strcasecmp(parv[1], "-stats"))
{
/* Print some statistics */
tkl_general_stats(sptr);
return 0;
}
return m_tkl_line(cptr, sptr, parc, parv, "z");
}
/** Check if a ban is placed with a too broad mask (like '*') */
int ban_too_broad(char *usermask, char *hostmask)
{
char *p;
int cnt = 0;
/* Scary config setting. Hmmm. */
if (ALLOW_INSANE_BANS)
return 0;
/* Allow things like clone@*, dsfsf@*, etc.. */
if (!strchr(usermask, '*') && !strchr(usermask, '?'))
return 0;
/* If it's a CIDR, then check /mask first.. */
p = strchr(hostmask, '/');
if (p)
{
int cidrlen = atoi(p+1);
if (strchr(hostmask, ':'))
{
if (cidrlen < 48)
return 1; /* too broad IPv6 CIDR mask */
} else {
if (cidrlen < 16)
return 1; /* too broad IPv4 CIDR mask */
}
}
/* Must at least contain 4 non-wildcard/non-dot characters.
* This will deal with non-CIDR and hosts, but any correct
* CIDR mask will also pass this test (which is fine).
*/
for (p = hostmask; *p; p++)
if (*p != '*' && *p != '.' && *p != '?')
cnt++;
if (cnt >= 4)
return 0;
return 1;
}
/** Intermediate layer between user functions such as KLINE/GLINE
* and the TKL layer (m_tkl).
* This allows us doing some syntax checking and other helpful
* things that are the same for many types of *LINES.
*/
int m_tkl_line(aClient *cptr, aClient *sptr, int parc, char *parv[], char* type)
{
time_t secs;
int whattodo = 0; /* 0 = add 1 = del */
time_t i;
aClient *acptr = NULL;
char *mask = NULL;
char mo[1024], mo2[1024];
char *p, *usermask, *hostmask;
char *tkllayer[10] = {
me.name, /*0 server.name */
NULL, /*1 +|- */
NULL, /*2 G */
NULL, /*3 user */
NULL, /*4 host */
NULL, /*5 set_by */
"0", /*6 expire_at */
NULL, /*7 set_at */
"no reason", /*8 reason */
NULL
};
struct tm *t;
if (parc == 1)
return 0; /* shouldn't happen */
mask = parv[1];
if (*mask == '-')
{
whattodo = 1;
mask++;
}
else if (*mask == '+')
{
whattodo = 0;
mask++;
}
if (strchr(mask, '!'))
{
sendnotice(sptr, "[error] Cannot have '!' in masks.");
return 0;
}
if (*mask == ':')
{
sendnotice(sptr, "[error] Mask cannot start with a ':'.");
return 0;
}
if (strchr(mask, ' '))
return 0;
/* Check if it's a softban */
if (*mask == '%')
{
if (!strchr("kGs", *type))
{
sendnotice(sptr, "The %% prefix (soft ban) is only available for KLINE, GLINE and SHUN."
"For technical reasons this will not work for (G)ZLINE.");
return 0;
}
}
/* Check if it's a hostmask and legal .. */
p = strchr(mask, '@');
if (p) {
if ((p == mask) || !p[1])
{
sendnotice(sptr, "Error: no user@host specified");
return 0;
}
usermask = strtok(mask, "@");
hostmask = strtok(NULL, "");
if (BadPtr(hostmask)) {
if (BadPtr(usermask)) {
return 0;
}
hostmask = usermask;
usermask = "*";
}
if (*hostmask == ':')
{
sendnotice(sptr, "[error] For (weird) technical reasons you cannot start the host with a ':', sorry");
return 0;
}
if (((*type == 'z') || (*type == 'Z')) && !whattodo)
{
/* It's a (G)ZLINE, make sure the user isn't specyfing a HOST.
* Just a warning in 3.2.3, but an error in 3.2.4.
*/
if (strcmp(usermask, "*"))
{
sendnotice(sptr, "ERROR: (g)zlines must be placed at \037*\037@ipmask, not \037user\037@ipmask. This is "
"because (g)zlines are processed BEFORE dns and ident lookups are done. "
"If you want to use usermasks, use a KLINE/GLINE instead.");
return -1;
}
for (p=hostmask; *p; p++)
if (isalpha(*p) && !isxdigit(*p))
{
sendnotice(sptr, "ERROR: (g)zlines must be placed at *@\037IPMASK\037, not *@\037HOSTMASK\037 "
"(so for example *@192.168.* is ok, but *@*.aol.com is not). "
"This is because (g)zlines are processed BEFORE dns and ident lookups are done. "
"If you want to use hostmasks instead of ipmasks, use a KLINE/GLINE instead.");
return -1;
}
}
/* set 'p' right for later... */
p = hostmask-1;
}
else
{
/* It's seemingly a nick .. let's see if we can find the user */
if ((acptr = find_person(mask, NULL)))
{
usermask = "*";
if ((*type == 'z') || (*type == 'Z'))
{
/* Fill in IP */
hostmask = GetIP(acptr);
if (!hostmask)
{
sendnotice(sptr, "Could not get IP for user '%s'", acptr->name);
return 0;
}
} else {
/* Fill in host */
hostmask = acptr->user->realhost;
}
p = hostmask - 1;
}
else
{
sendnumeric(sptr, ERR_NOSUCHNICK, mask);
return 0;
}
}
if (!whattodo && ban_too_broad(usermask, hostmask))
{
sendnotice(sptr, "*** [error] Too broad mask");
return 0;
}
secs = 0;
if (whattodo == 0 && (parc > 3))
{
secs = atime(parv[2]);
if (secs < 0)
{
sendnotice(sptr, "*** [error] The time you specified is out of range!");
return 0;
}
}
tkllayer[1] = whattodo == 0 ? "+" : "-";
tkllayer[2] = type;
tkllayer[3] = usermask;
tkllayer[4] = hostmask;
tkllayer[5] = make_nick_user_host(sptr->name, sptr->user->username, GetHost(sptr));
if (whattodo == 0)
{
if (secs == 0)
{
if (DEFAULT_BANTIME && (parc <= 3))
ircsnprintf(mo, sizeof(mo), "%lld", (long long)(DEFAULT_BANTIME + TStime()));
else
ircsnprintf(mo, sizeof(mo), "%lld", (long long)secs); /* "0" */
}
else
ircsnprintf(mo, sizeof(mo), "%lld", (long long)(secs + TStime()));
ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
tkllayer[6] = mo;
tkllayer[7] = mo2;
if (parc > 3) {
tkllayer[8] = parv[3];
} else if (parc > 2) {
tkllayer[8] = parv[2];
}
/* Blerghhh... */
i = atol(mo);
t = gmtime(&i);
if (!t)
{
sendnotice(sptr, "*** [error] The time you specified is out of range");
return 0;
}
/* call the tkl layer .. */
m_tkl(&me, &me, NULL, 9, tkllayer);
}
else
{
/* call the tkl layer .. */
m_tkl(&me, &me, NULL, 6, tkllayer);
}
return 0;
}
/** Helper function for m_spamfilter, explaining usage. */
int spamfilter_usage(aClient *sptr)
{
sendnotice(sptr, "Use: /spamfilter [add|del|remove|+|-] [-simple|-regex|-posix] [type] [action] [tkltime] [tklreason] [regex]");
sendnotice(sptr, "See '/helpop ?spamfilter' for more information.");
sendnotice(sptr, "For an easy way to remove an existing spamfilter, use '/spamfilter del' without additional parameters");
return 0;
}
/** Helper function for m_spamfilter, explaining usage has changed. */
int spamfilter_new_usage(aClient *cptr, aClient *sptr, char *parv[])
{
sendnotice(sptr, "Unknown match-type '%s'. Must be one of: -regex (new fast PCRE regexes), "
"-posix (old unreal 3.2.x posix regexes) or "
"-simple (simple text with ? and * wildcards)",
parv[2]);
if (*parv[2] != '-')
sendnotice(sptr, "Using the old 3.2.x /SPAMFILTER syntax? Note the new -regex/-posix/-simple field!!");
return spamfilter_usage(cptr);
}
/** Delete a spamfilter by ID (the ID can be obtained via '/SPAMFILTER del' */
int spamfilter_del_by_id(aClient *sptr, char *id)
{
int index;
aTKline *tk;
int found = 0;
char mo[32], mo2[32];
char *tkllayer[13] = {
me.name, /* 0 server.name */
NULL, /* 1 +|- */
"F", /* 2 F */
NULL, /* 3 usermask (targets) */
NULL, /* 4 hostmask (action) */
NULL, /* 5 set_by */
"0", /* 6 expire_at */
"0", /* 7 set_at */
"", /* 8 tkl time */
"", /* 9 tkl reason */
"", /* 10 match method */
"", /* 11 regex */
NULL
};
for (index = 0; index < TKLISTLEN; index++)
{
for (tk = tklines[index]; tk; tk = tk->next)
{
if (((tk->type & (TKL_GLOBAL|TKL_SPAMF)) == (TKL_GLOBAL|TKL_SPAMF)) && !strcmp(spamfilter_id(tk), id))
{
found = 1;
break;
}
}
if (found)
break; /* break outer loop */
}
if (!tk)
{
sendnotice(sptr, "Sorry, no spamfilter found with that ID. Did you run '/spamfilter del' to get the appropriate id?");
return 0;
}
/* Spamfilter found. Now fill the tkllayer */
tkllayer[1] = "-";
tkllayer[3] = spamfilter_target_inttostring(tk->ptr.spamfilter->target); /* target(s) */
mo[0] = banact_valtochar(tk->ptr.spamfilter->action);
mo[1] = '\0';
tkllayer[4] = mo; /* action */
tkllayer[5] = make_nick_user_host(sptr->name, sptr->user->username, GetHost(sptr));
tkllayer[8] = "-";
tkllayer[9] = "-";
tkllayer[10] = unreal_match_method_valtostr(tk->ptr.spamfilter->match->type); /* matching type */
tkllayer[11] = tk->ptr.spamfilter->match->str; /* regex */
ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
tkllayer[7] = mo2; /* deletion time */
m_tkl(&me, &me, NULL, 12, tkllayer);
return 0;
}
/** Spamfilter to fight spam, advertising, worms and other bad things on IRC.
* See https://www.unrealircd.org/docs/Spamfilter for general documentation.
*
* /SPAMFILTER [add|del|remove|+|-] [match-type] [type] [action] [tkltime] [reason] [regex]
* 1 2 3 4 5 6 7
*/
CMD_FUNC(m_spamfilter)
{
int whattodo = 0; /* 0 = add 1 = del */
char mo[32], mo2[32];
char *tkllayer[13] = {
me.name, /* 0 server.name */
NULL, /* 1 +|- */
"F", /* 2 F */
NULL, /* 3 usermask (targets) */
NULL, /* 4 hostmask (action) */
NULL, /* 5 set_by */
"0", /* 6 expire_at */
"0", /* 7 set_at */
"", /* 8 tkl time */
"", /* 9 tkl reason */
"", /* 10 match method */
"", /* 11 regex */
NULL
};
int targets = 0, action = 0;
char targetbuf[64], actionbuf[2];
char reason[512];
int n;
aMatch *m;
int match_type = 0;
char *err = NULL;
if (IsServer(sptr))
return 0;
if (!ValidatePermissionsForPath("server-ban:spamfilter",sptr,NULL,NULL,NULL))
{
sendnumeric(sptr, ERR_NOPRIVILEGES);
return 0;
}
if (parc == 1)
{
char *parv[3];
parv[0] = NULL;
parv[1] = "spamfilter";
parv[2] = NULL;
return do_cmd(sptr, sptr, recv_mtags, "STATS", 2, parv);
}
if ((parc <= 3) && !strcmp(parv[1], "del"))
{
if (!parv[2])
{
/* Show STATS with appropriate SPAMFILTER del command */
char *parv[5];
parv[0] = NULL;
parv[1] = "spamfilter";
parv[2] = me.name;
parv[3] = "del";
parv[4] = NULL;
return do_cmd(sptr, sptr, recv_mtags, "STATS", 4, parv);
}
return spamfilter_del_by_id(sptr, parv[2]);
}
if ((parc == 7) && (*parv[2] != '-'))
return spamfilter_new_usage(cptr,sptr,parv);
if ((parc < 8) || BadPtr(parv[7]))
return spamfilter_usage(sptr);
/* parv[1]: [add|del|+|-]
* parv[2]: match-type
* parv[3]: type
* parv[4]: action
* parv[5]: tkl time
* parv[6]: tkl reason (or block reason..)
* parv[7]: regex
*/
if (!strcasecmp(parv[1], "add") || !strcmp(parv[1], "+"))
whattodo = 0;
else if (!strcasecmp(parv[1], "del") || !strcmp(parv[1], "-") || !strcasecmp(parv[1], "remove"))
whattodo = 1;
else
{
sendnotice(sptr, "1st parameter invalid");
return spamfilter_usage(sptr);
}
if ((whattodo == 0) && !strcasecmp(parv[2]+1, "posix"))
{
sendnotice(sptr, "ERROR: Spamfilter type 'posix' is DEPRECATED. You must use type 'regex' instead.");
sendnotice(sptr, "See https://www.unrealircd.org/docs/FAQ#spamfilter-posix-deprecated");
return 0;
}
match_type = unreal_match_method_strtoval(parv[2]+1);
if (!match_type)
{
return spamfilter_new_usage(cptr,sptr,parv);
}
targets = spamfilter_gettargets(parv[3], sptr);
if (!targets)
return spamfilter_usage(sptr);
strlcpy(targetbuf, spamfilter_target_inttostring(targets), sizeof(targetbuf));
action = banact_stringtoval(parv[4]);
if (!action)
{
sendnotice(sptr, "Invalid 'action' field (%s)", parv[4]);
return spamfilter_usage(sptr);
}
actionbuf[0] = banact_valtochar(action);
actionbuf[1] = '\0';
if (whattodo == 0)
{
/* now check the regex / match field... */
m = unreal_create_match(match_type, parv[7], &err);
if (!m)
{
sendnotice(sptr, "Error in regex '%s': %s", parv[7], err);
return 0;
}
unreal_delete_match(m);
}
tkllayer[1] = whattodo ? "-" : "+";
tkllayer[3] = targetbuf;
tkllayer[4] = actionbuf;
tkllayer[5] = make_nick_user_host(sptr->name, sptr->user->username, GetHost(sptr));
if (parv[5][0] == '-')
{
ircsnprintf(mo, sizeof(mo), "%lld", (long long)SPAMFILTER_BAN_TIME);
tkllayer[8] = mo;
}
else
tkllayer[8] = parv[5];
if (parv[6][0] == '-')
strlcpy(reason, unreal_encodespace(SPAMFILTER_BAN_REASON), sizeof(reason));
else
strlcpy(reason, parv[6], sizeof(reason));
tkllayer[9] = reason;
tkllayer[10] = parv[2]+1; /* +1 to skip the '-' */
tkllayer[11] = parv[7];
/* SPAMFILTER LENGTH CHECK.
* We try to limit it here so '/stats f' output shows ok, output of that is:
* :servername 229 destname F <target> <action> <num> <num> <num> <reason> <set_by> :<regex>
* : ^NICKLEN ^ NICKLEN ^check ^check ^check
* And for the other fields (and spacing/etc) we count on max 40 characters.
* We also do >500 instead of >510, since that looks cleaner ;).. so actually we count
* on 50 characters for the rest... -- Syzop
*/
n = strlen(reason) + strlen(parv[7]) + strlen(tkllayer[6]) + (NICKLEN * 2) + 40;
if ((n > 500) && (whattodo == 0))
{
sendnotice(sptr, "Sorry, spamfilter too long. You'll either have to trim down the "
"reason or the regex (exceeded by %d bytes)", n - 500);
return 0;
}
if (whattodo == 0)
{
ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
tkllayer[7] = mo2;
}
m_tkl(&me, &me, NULL, 12, tkllayer);
return 0;
}
/** tkl hash method.
* @param c The tkl type character, see tkl_typetochar().
* @notes The input value 'c' is assumed to be in range a-z or A-Z!
* Also, don't blindly change the hashmethod here, some things
* depend on 'z' and 'Z' ending up in the same bucket.
*/
int _tkl_hash(unsigned int c)
{
#ifdef DEBUGMODE
if ((c >= 'a') && (c <= 'z'))
return c-'a';
else if ((c >= 'A') && (c <= 'Z'))
return c-'A';
else {
sendto_realops("[BUG] tkl_hash() called with out of range parameter (c = '%c') !!!", c);
ircd_log(LOG_ERROR, "[BUG] tkl_hash() called with out of range parameter (c = '%c') !!!", c);
return 0;
}
#else
return (isupper(c) ? c-'A' : c-'a');
#endif
}
/** tkl type to tkl character.
* NOTE: type is assumed to be valid.
*/
char _tkl_typetochar(int type)
{
if (type & TKL_GLOBAL)
{
if (type & TKL_KILL)
return 'G';
if (type & TKL_ZAP)
return 'Z';
if (type & TKL_SHUN)
return 's';
if (type & TKL_SPAMF)
return 'F';
if (type & TKL_NAME)
return 'Q';
if (type & TKL_EXCEPT)
return 'E';
} else {
if (type & TKL_KILL)
return 'k';
if (type & TKL_ZAP)
return 'z';
if (type & TKL_SPAMF)
return 'f';
if (type & TKL_NAME)
return 'q';
if (type & TKL_EXCEPT)
return 'e';
}
sendto_realops("[BUG]: tkl_typetochar(): unknown type 0x%x !!!", type);
ircd_log(LOG_ERROR, "[BUG] tkl_typetochar(): unknown type 0x%x !!!", type);
return 0;
}
/** tkl character to tkl type
* Returns 0 if invalid type.
*/
int _tkl_chartotype(char c)
{
switch(c)
{
case 'G':
return TKL_KILL|TKL_GLOBAL;
case 'Z':
return TKL_ZAP|TKL_GLOBAL;
case 's':
return TKL_SHUN|TKL_GLOBAL;
case 'F':
return TKL_SPAMF|TKL_GLOBAL;
case 'Q':
return TKL_NAME|TKL_GLOBAL;
case 'k':
return TKL_KILL;
case 'z':
return TKL_ZAP;
case 'f':
return TKL_SPAMF;
case 'q':
return TKL_NAME;
case 'E':
return TKL_EXCEPT|TKL_GLOBAL;
case 'e':
return TKL_EXCEPT;
default:
return 0;
}
/* NOTREACHED */
}
/** Used for finding out which element of the tkl_ip hash table is used (primary element) */
int _tkl_ip_hash(char *ip)
{
char ipbuf[64], *p;
for (p = ip; *p; p++)
{
if ((*p == '?') || (*p == '*') || (*p == '/'))
return -1; /* not an entry suitable for the ip hash table */
}
if (inet_pton(AF_INET, ip, &ipbuf) == 1)
{
/* IPv4 */
unsigned int v = (ipbuf[0] << 24) +
(ipbuf[1] << 16) +
(ipbuf[2] << 8) +
ipbuf[3];
return v % TKLIPHASHLEN2;
} else
if (inet_pton(AF_INET6, ip, &ipbuf) == 1)
{
/* IPv6 (only upper 64 bits) */
unsigned int v1 = (ipbuf[0] << 24) +
(ipbuf[1] << 16) +
(ipbuf[2] << 8) +
ipbuf[3];
unsigned int v2 = (ipbuf[4] << 24) +
(ipbuf[5] << 16) +
(ipbuf[6] << 8) +
ipbuf[7];
return (v1 ^ v2) % TKLIPHASHLEN2;
} else
{
return -1;
}
}
/** Used for finding out which tkl_ip hash table needs to be used (secondary element).
* NOTE: Returns -1 for types that are never on the TKL ip hash table, such as spamfilter.
* This can be used by the caller as a quick way to find out if the type is supported.
*/
int _tkl_ip_hash_type(int type)
{
if ((type == 'Z') || (type == 'z'))
return 0;
else if (type == 'G')
return 1;
else if (type == 'k')
return 2;
else if ((type == 'e') || (type == 'E'))
return 3;
else
return -1;
}
/* Find the appropriate list 'head' that we need to iterate.
* This is simply a helper that is used at 3 places and I hate duplicate code.
* NOTE: this function may return NULL.
*/
aTKline *tkl_find_head(char type, char *hostmask, aTKline *def)
{
int index, index2;
/* First, check ip hash table TKL's... */
index = tkl_ip_hash_type(type);
if (index >= 0)
{
index2 = tkl_ip_hash(hostmask);
if (index2 >= 0)
{
/* iterate tklines_ip_hash[index][index2] */
return tklines_ip_hash[index][index2];
}
}
/* Fallback to the default */
return def;
}
/** Add a spamfilter entry to the list.
* @param type TKL_SPAMF or TKL_SPAMF|TKL_GLOBAL.
* @param target The spamfilter target (SPAMF_*)
* @param action The spamfilter action (BAN_ACT_*)
* @param match The match (this struct may contain a regex for example)
* @param set_by Who (or what) set the ban
* @param expire_at When will the ban expire (0 for permanent)
* @param set_at When was the ban set
* @param spamf_tkl_duration When will the ban placed by spamfilter expire
* @param spamf_tkl_reason What is the reason for bans placed by spamfilter
* @param flags Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
* @returns The TKL entry, or NULL in case of a problem,
* such as a regex failing to compile, memory problem, ..
*/
aTKline *_tkl_add_spamfilter(int type, unsigned short target, unsigned short action, aMatch *match, char *set_by,
time_t expire_at, time_t set_at,
time_t tkl_duration, char *tkl_reason,
int flags)
{
aTKline *tkl;
int index;
if (!(type & TKL_SPAMF))
abort();
tkl = MyMallocEx(sizeof(aTKline));
/* First the common fields */
tkl->type = type;
tkl->flags = flags;
tkl->set_at = set_at;
tkl->set_by = strdup(set_by);
tkl->expire_at = expire_at;
/* Then the spamfilter fields */
tkl->ptr.spamfilter = MyMallocEx(sizeof(Spamfilter));
tkl->ptr.spamfilter->target = target;
tkl->ptr.spamfilter->action = action;
tkl->ptr.spamfilter->match = match;
tkl->ptr.spamfilter->tkl_reason = strdup(tkl_reason);
tkl->ptr.spamfilter->tkl_duration = tkl_duration;
if (tkl->ptr.spamfilter->target & SPAMF_USER)
loop.do_bancheck_spamf_user = 1;
if (tkl->ptr.spamfilter->target & SPAMF_AWAY)
loop.do_bancheck_spamf_away = 1;
/* Spamfilters go via the normal TKL list... */
index = tkl_hash(tkl_typetochar(type));
AddListItem(tkl, tklines[index]);
return tkl;
}
/** Add a server ban TKL entry.
* @param type The TKL type, one of TKL_*,
* optionally OR'ed with TKL_GLOBAL.
* @param usermask The user mask
* @param hostmask The host mask
* @param reason The reason for the ban
* @param set_by Who (or what) set the ban
* @param expire_at When will the ban expire (0 for permanent)
* @param set_at When was the ban set
* @param soft Whether it's a soft-ban
* @param flags Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
* @returns The TKL entry, or NULL in case of a problem,
* such as a regex failing to compile, memory problem, ..
* @notes
* Be sure not to call this function for spamfilters,
* qlines or exempts, which have their own function!
*/
aTKline *_tkl_add_serverban(int type, char *usermask, char *hostmask, char *reason, char *set_by,
time_t expire_at, time_t set_at, int soft, int flags)
{
aTKline *tkl;
int index, index2;
if (!TKLIsServerBanType(type))
abort();
tkl = MyMallocEx(sizeof(aTKline));
/* First the common fields */
tkl->type = type;
tkl->flags = flags;
tkl->set_at = set_at;
tkl->set_by = strdup(set_by);
tkl->expire_at = expire_at;
/* Now the server ban fields */
tkl->ptr.serverban = MyMallocEx(sizeof(ServerBan));
tkl->ptr.serverban->usermask = strdup(usermask);
tkl->ptr.serverban->hostmask = strdup(hostmask);
if (soft)
tkl->ptr.serverban->subtype = TKL_SUBTYPE_SOFT;
tkl->ptr.serverban->reason = strdup(reason);
/* For ip hash table TKL's... */
index = tkl_ip_hash_type(tkl_typetochar(type));
if (index >= 0)
{
index2 = tkl_ip_hash(tkl->ptr.serverban->hostmask);
if (index2 >= 0)
{
AddListItem(tkl, tklines_ip_hash[index][index2]);
return tkl;
}
}
/* If we get here it's just for our normal list.. */
index = tkl_hash(tkl_typetochar(type));
AddListItem(tkl, tklines[index]);
return tkl;
}
/** Add a name ban TKL entry (Q-Line), used for banning nicks and channels.
* @param type The TKL type, one of TKL_*,
* optionally OR'ed with TKL_GLOBAL.
* @param name The nick or channel to be banned (wildcards accepted)
* @param hold Flag to indicate services hold
* @param reason The reason for the ban
* @param set_by Who (or what) set the ban
* @param expire_at When will the ban expire (0 for permanent)
* @param set_at When was the ban set
* @param flags Any TKL_FLAG_* (TKL_FLAG_CONFIG, etc..)
* @returns The TKL entry, or NULL in case of a problem,
* such as a regex failing to compile, memory problem, ..
* @notes
* Be sure not to call this function for spamfilters,
* qlines or exempts, which have their own function!
*/
aTKline *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
time_t expire_at, time_t set_at, int flags)
{
aTKline *tkl;
int index, index2;
if (!TKLIsNameBanType(type))
abort();
tkl = MyMallocEx(sizeof(aTKline));
/* First the common fields */
tkl->type = type;
tkl->flags = flags;
tkl->set_at = set_at;
tkl->set_by = strdup(set_by);
tkl->expire_at = expire_at;
/* Now the name ban fields */
tkl->ptr.nameban = MyMallocEx(sizeof(ServerBan));
tkl->ptr.nameban->name = strdup(name);
tkl->ptr.nameban->hold = hold;
tkl->ptr.serverban->reason = strdup(reason);
/* Name bans go via the normal TKL list.. */
index = tkl_hash(tkl_typetochar(type));
AddListItem(tkl, tklines[index]);
return tkl;
}
/** Free a TKL entry but do not remove from the list.
* (this assumes that it was not added yet or is already removed)
* Most people will use tkl_del_line() instead.
*/
void _free_tkl(aTKline *tkl)
{
/* Free the entry */
/* First, the common fields */
safefree(tkl->set_by);
/* Now the type specific fields */
if (TKLIsServerBan(tkl) && tkl->ptr.serverban)
{
safefree(tkl->ptr.serverban->usermask);
safefree(tkl->ptr.serverban->hostmask);
safefree(tkl->ptr.serverban->reason);
MyFree(tkl->ptr.serverban);
} else
if (TKLIsNameBan(tkl) && tkl->ptr.nameban)
{
safefree(tkl->ptr.nameban->name);
safefree(tkl->ptr.nameban->reason);
MyFree(tkl->ptr.nameban);
} else
if (TKLIsSpamfilter(tkl) && tkl->ptr.spamfilter)
{
/* Spamfilter */
safefree(tkl->ptr.spamfilter->tkl_reason);
if (tkl->ptr.spamfilter->match)
unreal_delete_match(tkl->ptr.spamfilter->match);
MyFree(tkl->ptr.spamfilter);
}
MyFree(tkl);
}
/** Delete a TKL entry from the list and free it.
* @param tkl The TKL entry.
*/
void _tkl_del_line(aTKline *tkl)
{
int index, index2;
int found = 0;
/* Try to find it in the ip TKL hash table first
* (this only applies to server bans)
*/
index = tkl_ip_hash_type(tkl_typetochar(tkl->type));
if (index >= 0)
{
index2 = tkl_ip_hash(tkl->ptr.serverban->hostmask);
if (index2 >= 0)
{
DelListItem(tkl, tklines_ip_hash[index][index2]);
found = 1;
}
}
if (!found)
{
/* If we get here it's just for our normal list.. */
index = tkl_hash(tkl_typetochar(tkl->type));
DelListItem(tkl, tklines[index]);
}
/* Finally, free the entry */
free_tkl(tkl);
}
/*
* tkl_check_local_remove_shun:
* removes shun from currently connected users affected by tmp.
*/
// TODO / FIXME: audit this function, it looks crazy
void _tkl_check_local_remove_shun(aTKline *tmp)
{
long i;
char *chost, *cname, *cip;
int is_ip;
aClient *acptr;
aTKline *tk;
int keep_shun;
for (i = 0; i <= 5; i++)
{
list_for_each_entry(acptr, &lclient_list, lclient_node)
if (MyClient(acptr) && IsShunned(acptr))
{
chost = acptr->local->sockhost;
cname = acptr->user->username;
cip = GetIP(acptr);
if ((*tmp->ptr.serverban->hostmask >= '0') && (*tmp->ptr.serverban->hostmask <= '9'))
is_ip = 1;
else
is_ip = 0;
if (is_ip == 0 ?
(match_simple(tmp->ptr.serverban->hostmask, chost) && match_simple(tmp->ptr.serverban->usermask, cname)) :
(match_simple(tmp->ptr.serverban->hostmask, chost) || match_simple(tmp->ptr.serverban->hostmask, cip))
&& match_simple(tmp->ptr.serverban->usermask, cname))
{
/*
before blindly marking this user as un-shunned, we need to check
if the user is under any other existing shuns. (#0003906)
Unfortunately, this requires crazy amounts of indentation ;-).
This enumeration code is based off of _tkl_stats()
*/
keep_shun = 0;
for(tk = tklines[tkl_hash('s')]; tk && !keep_shun; tk = tk->next)
if(tk != tmp && match_simple(tk->ptr.serverban->usermask, cname))
{
if ((*tk->ptr.serverban->hostmask >= '0') && (*tk->ptr.serverban->hostmask <= '9')
/* the hostmask is an IP */
&& (match_simple(tk->ptr.serverban->hostmask, chost) || match_simple(tk->ptr.serverban->hostmask, cip)))
keep_shun = 1;
else
/* the hostmask is not an IP */
if (match_simple(tk->ptr.serverban->hostmask, chost) && match_simple(tk->ptr.serverban->usermask, cname))
keep_shun = 1;
}
if(!keep_shun)
{
ClearShunned(acptr);
}
}
}
}
}
/** Deal with expiration of a specific TKL entry.
* This is a helper function for tkl_check_expire().
*/
void tkl_expire_entry(aTKline *tkl)
{
char *whattype = tkl_type_string(tkl);
if (!tkl)
return;
if (tkl->type & TKL_SPAMF)
{
/* Impossible */
} else
if (TKLIsServerBan(tkl))
{
sendto_snomask(SNO_TKL,
"*** Expiring %s (%s@%s) made by %s (Reason: %s) set %lld seconds ago",
whattype, tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask, tkl->set_by, tkl->ptr.serverban->reason,
(long long)(TStime() - tkl->set_at));
ircd_log
(LOG_TKL, "Expiring %s (%s@%s) made by %s (Reason: %s) set %lld seconds ago",
whattype, tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask, tkl->set_by, tkl->ptr.serverban->reason,
(long long)(TStime() - tkl->set_at));
}
else if (TKLIsNameBan(tkl))
{
if (!tkl->ptr.nameban->hold)
{
sendto_snomask(SNO_TKL,
"*** Expiring %s (%s) made by %s (Reason: %s) set %lld seconds ago",
whattype, tkl->ptr.nameban->name, tkl->set_by, tkl->ptr.nameban->reason,
(long long)(TStime() - tkl->set_at));
ircd_log
(LOG_TKL, "Expiring %s (%s) made by %s (Reason: %s) set %lld seconds ago",
whattype, tkl->ptr.nameban->name, tkl->set_by, tkl->ptr.nameban->reason,
(long long)(TStime() - tkl->set_at));
}
}
if (tkl->type & TKL_SHUN)
tkl_check_local_remove_shun(tkl);
RunHook3(HOOKTYPE_TKL_DEL, NULL, NULL, tkl);
tkl_del_line(tkl);
}
/** Regularly check TKL entries for expiration */
EVENT(tkl_check_expire)
{
aTKline *tkl, *next;
time_t nowtime;
int index, index2;
nowtime = TStime();
/* First, hashed entries.. */
for (index = 0; index < TKLIPHASHLEN1; index++)
{
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
{
for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = next)
{
next = tkl->next;
if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
{
tkl_expire_entry(tkl);
}
}
}
}
/* Now normal entries.. */
for (index = 0; index < TKLISTLEN; index++)
{
for (tkl = tklines[index]; tkl; tkl = next)
{
next = tkl->next;
if (tkl->expire_at <= nowtime && !(tkl->expire_at == 0))
{
tkl_expire_entry(tkl);
}
}
}
}
/** Helper function for find_tkline_match() */
int find_tkline_match_matcher(aClient *cptr, int skip_soft, aTKline *tkl)
{
char uhost[NICKLEN+HOSTLEN+1];
ConfigItem_except *excepts;
int match_type = 0;
Hook *hook;
if (!TKLIsServerBan(tkl) || (tkl->type & TKL_SHUN))
return 0;
if (skip_soft && (tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT))
return 0;
snprintf(uhost, sizeof(uhost), "%s@%s", tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
if (match_user(uhost, cptr, MATCH_CHECK_REAL))
{
/* If hard-ban, or soft-ban&unauthenticated.. */
if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(cptr)))
{
/* Found match. Now check for exception... */
if (((tkl->type & TKL_KILL) || (tkl->type & TKL_ZAP)) && !(tkl->type & TKL_GLOBAL))
match_type = CONF_EXCEPT_BAN;
else
match_type = CONF_EXCEPT_TKL;
for (excepts = conf_except; excepts; excepts = excepts->next)
{
if (excepts->flag.type != match_type || (match_type == CONF_EXCEPT_TKL &&
excepts->type != tkl->type))
continue;
if (match_user(excepts->mask, cptr, MATCH_CHECK_REAL))
return 0; /* exempt by except block */
}
for (hook = Hooks[HOOKTYPE_TKL_EXCEPT]; hook; hook = hook->next)
{
if (hook->func.intfunc(cptr, tkl) > 0)
return 0; /* exempt by hook */
}
return 1; /* banned */
}
}
return 0; /* no match */
}
/** Check if user matches a *LINE. If so, kill the user.
* @retval <0 if client is banned (user is killed, don't touch 'cptr' anymore),
* otherwise the client is not banned (either no match or on an exception list).
*/
int _find_tkline_match(aClient *cptr, int skip_soft)
{
aTKline *tkl;
int banned = 0;
int index, index2;
if (IsServer(cptr) || IsMe(cptr))
return -1;
/* First, the TKL ip hash table entries.. */
index2 = tkl_ip_hash(GetIP(cptr));
if (index2 >= 0)
{
for (index = 0; index < TKLIPHASHLEN1; index++)
{
for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
{
banned = find_tkline_match_matcher(cptr, skip_soft, tkl);
if (banned)
break;
}
if (banned)
break;
}
}
/* If not banned (yet), then check regular entries.. */
if (!banned)
{
for (index = 0; index < TKLISTLEN; index++)
{
for (tkl = tklines[index]; tkl; tkl = tkl->next)
{
banned = find_tkline_match_matcher(cptr, skip_soft, tkl);
if (banned)
break;
}
if (banned)
break;
}
}
if (!banned)
return 1;
/* User is banned... */
RunHookReturnInt2(HOOKTYPE_FIND_TKLINE_MATCH, cptr, tkl, !=99);
if (tkl->type & TKL_KILL)
{
ircstp->is_ref++;
if (tkl->type & TKL_GLOBAL)
return banned_client(cptr, "G-Lined", tkl->ptr.serverban->reason, 1, 0);
else
return banned_client(cptr, "K-Lined", tkl->ptr.serverban->reason, 0, 0);
}
if (tkl->type & TKL_ZAP)
{
ircstp->is_ref++;
return banned_client(cptr, "Z-Lined", tkl->ptr.serverban->reason, (tkl->type & TKL_GLOBAL)?1:0, 0);
}
return 3;
}
/** Check if user is shunned. Returns 2 in such a case (FIXME: why 2 ?) */
int _find_shun(aClient *cptr)
{
aTKline *tkl;
ConfigItem_except *excepts;
int match_type = 0;
Hook *hook;
int banned = 0;
if (IsServer(cptr) || IsMe(cptr))
return -1;
if (IsShunned(cptr))
return 1;
if (ValidatePermissionsForPath("immune:server-ban:shun",cptr,NULL,NULL,NULL))
return 1;
for (tkl = tklines[tkl_hash('s')]; tkl; tkl = tkl->next)
{
char uhost[NICKLEN+HOSTLEN+1];
if (!(tkl->type & TKL_SHUN))
continue;
snprintf(uhost, sizeof(uhost), "%s@%s", tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask);
if (match_user(uhost, cptr, MATCH_CHECK_REAL))
{
/* If hard-ban, or soft-ban&unauthenticated.. */
if (!(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ||
((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) && !IsLoggedIn(cptr)))
{
/* Found match. Now check for exception... */
banned = 1;
match_type = CONF_EXCEPT_TKL;
for (excepts = conf_except; excepts; excepts = excepts->next)
{
if (excepts->flag.type != match_type || (match_type == CONF_EXCEPT_TKL &&
excepts->type != tkl->type))
continue;
if (match_user(excepts->mask, cptr, MATCH_CHECK_REAL))
{
banned = 0; /* exempt by except block */
break;
}
}
for (hook = Hooks[HOOKTYPE_TKL_EXCEPT]; hook; hook = hook->next)
{
if (hook->func.intfunc(cptr, tkl) > 0)
{
banned = 0; /* exempt by hook */
break;
}
}
if (banned)
break;
}
}
}
if (!banned)
return 1;
SetShunned(cptr);
return 2;
}
/** Helper function for spamfilter_build_user_string().
* This ensures IPv6 hosts are in brackets.
*/
char *SpamfilterMagicHost(char *i)
{
static char buf[256];
if (!strchr(i, ':'))
return i;
/* otherwise, it's IPv6.. prepend it with [ and append a ] */
ircsnprintf(buf, sizeof(buf), "[%s]", i);
return buf;
}
/** Build the nick:user@host:realname string
* @param buf The buffer used for storage, the size of
* which should be at least NICKLEN+USERLEN+HOSTLEN+1.
* @param nick The nickname (because acptr can be nick-changing).
* @param acptr The affected client.
*/
void _spamfilter_build_user_string(char *buf, char *nick, aClient *acptr)
{
snprintf(buf, NICKLEN+USERLEN+HOSTLEN+1, "%s!%s@%s:%s",
nick, acptr->user->username, SpamfilterMagicHost(acptr->user->realhost), acptr->info);
}
/** Checks if the user matches a spamfilter of type 'u' (user,
* nick!user@host:realname ban).
* Written by: Syzop
* Assumes: only call for clients, possible assume on local clients [?]
* Return values: see run_spamfilter()
*/
int _find_spamfilter_user(aClient *sptr, int flags)
{
char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
if (ValidatePermissionsForPath("immune:server-ban:spamfilter",sptr,NULL,NULL,NULL))
return 0;
spamfilter_build_user_string(spamfilter_user, sptr->name, sptr);
return run_spamfilter(sptr, spamfilter_user, SPAMF_USER, NULL, flags, NULL);
}
/** Check a spamfilter against all local users and print a message.
* This is only used for the 'warn' action (BAN_ACT_WARN).
*/
int spamfilter_check_users(aTKline *tkl)
{
char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
char buf[1024];
int matches = 0;
aClient *acptr;
list_for_each_entry_reverse(acptr, &lclient_list, lclient_node)
{
if (MyClient(acptr))
{
spamfilter_build_user_string(spamfilter_user, acptr->name, acptr);
if (!unreal_match(tkl->ptr.spamfilter->match, spamfilter_user))
continue; /* No match */
/* matched! */
ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s!%s@%s matches filter '%s': [%s: '%s'] [%s]",
acptr->name, acptr->user->username, acptr->user->realhost,
tkl->ptr.spamfilter->match->str,
"user", spamfilter_user,
unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
sendto_snomask_global(SNO_SPAMF, "%s", buf);
ircd_log(LOG_SPAMFILTER, "%s", buf);
RunHook6(HOOKTYPE_LOCAL_SPAMFILTER, acptr, spamfilter_user, spamfilter_user, SPAMF_USER, NULL, tkl);
matches++;
}
}
return matches;
}
/** Similarly to previous, but match against all global users.
* FUNCTION IS UNUSED !!
*/
int spamfilter_check_all_users(aClient *from, aTKline *tkl)
{
char spamfilter_user[NICKLEN + USERLEN + HOSTLEN + REALLEN + 64]; /* n!u@h:r */
int matches = 0;
aClient *acptr;
list_for_each_entry(acptr, &client_list, client_node)
{
if (IsPerson(acptr))
{
spamfilter_build_user_string(spamfilter_user, acptr->name, acptr);
if (!unreal_match(tkl->ptr.spamfilter->match, spamfilter_user))
continue; /* No match */
/* matched! */
sendnotice(from, "[Spamfilter] %s!%s@%s matches filter '%s': [%s: '%s'] [%s]",
acptr->name, acptr->user->username, acptr->user->realhost,
tkl->ptr.spamfilter->match->str,
"user", spamfilter_user,
unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
matches++;
}
}
return matches;
}
/** Check if the nick or channel name is banned (Q-Line).
* @param cptr The possibly affected user.
* @param name The nick or channel to check.
* @param is_hold This will be SET (so OUT) if it's a services hold.
*
* @notes Special handling:
* #*ble* will match with #bbleh
* *ble* will NOT match with #bbleh, will with bbleh
*/
aTKline *_find_qline(aClient *cptr, char *name, int *ishold)
{
aTKline *tkl;
int points = 0;
ConfigItem_except *excepts;
*ishold = 0;
if (IsServer(cptr) || IsMe(cptr))
return NULL;
for (tkl = tklines[tkl_hash('q')]; tkl; tkl = tkl->next)
{
points = 0;
if (!TKLIsNameBan(tkl))
continue;
if (((*tkl->ptr.nameban->name == '#' && *name == '#') || (*tkl->ptr.nameban->name != '#' && *name != '#'))
&& match_simple(tkl->ptr.nameban->name, name))
{
points = 1;
break;
}
}
if (points != 1)
return NULL;
/* It's a services hold */
if (tkl->ptr.nameban->hold)
{
*ishold = 1;
return tkl;
}
for (excepts = conf_except; excepts; excepts = excepts->next)
{
if (excepts->flag.type != CONF_EXCEPT_TKL || excepts->type != TKL_NAME)
continue;
if (match_user(excepts->mask, cptr, MATCH_CHECK_REAL))
return NULL; /* exempt */
}
return tkl;
}
/** Helper function for find_tkline_match_zap() */
aTKline *find_tkline_match_zap_matcher(aClient *cptr, aTKline *tkl)
{
ConfigItem_except *excepts;
Hook *hook;
if (!(tkl->type & TKL_ZAP))
return NULL;
if (match_user(tkl->ptr.serverban->hostmask, cptr, MATCH_CHECK_IP))
{
for (excepts = conf_except; excepts; excepts = excepts->next)
{
/* This used to be:
* if (excepts->flag.type != CONF_EXCEPT_TKL || excepts->type != tkl->type)
* It now checks for 'except ban', hope this is what most people want,
* it is at least the same as in find_tkline_match, which is how it currently
* is when a user is connected. -- Syzop/20081221
*/
if (excepts->flag.type != CONF_EXCEPT_BAN)
continue;
if (match_user(excepts->mask, cptr, MATCH_CHECK_IP))
return NULL; /* exempt */
}
for (hook = Hooks[HOOKTYPE_TKL_EXCEPT]; hook; hook = hook->next)
if (hook->func.intfunc(cptr, tkl) > 0)
return NULL; /* exempt */
return tkl;
}
return NULL;
}
/** Find matching (G)ZLINE, if any.
* Note: function prototype changed as per UnrealIRCd 4.2.0.
* @retval The (G)Z-Line that matched, or NULL if no such ban was found.
*/
aTKline *_find_tkline_match_zap(aClient *cptr)
{
aTKline *tkl, *ret;
int index, index2;
if (IsServer(cptr) || IsMe(cptr))
return NULL;
/* First, the TKL ip hash table entries.. */
index = tkl_ip_hash_type('z');
index2 = tkl_ip_hash(GetIP(cptr));
if (index2 >= 0)
{
for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
{
ret = find_tkline_match_zap_matcher(cptr, tkl);
if (ret)
return ret;
}
}
/* If not banned (yet), then check regular entries.. */
for (tkl = tklines[tkl_hash('z')]; tkl; tkl = tkl->next)
{
ret = find_tkline_match_zap_matcher(cptr, tkl);
if (ret)
return ret;
}
return NULL;
}
#define BY_MASK 0x1
#define BY_REASON 0x2
#define NOT_BY_MASK 0x4
#define NOT_BY_REASON 0x8
#define BY_SETBY 0x10
#define NOT_BY_SETBY 0x20
typedef struct {
int flags;
char *mask;
char *reason;
char *set_by;
} TKLFlag;
/** Parse STATS tkl parameters.
* TODO: I don't think this is documented anywhere? Or underdocumented at least.
*/
static void parse_stats_params(char *para, TKLFlag *flag)
{
static char paratmp[512]; /* <- copy of para, because it gets fragged by strtok() */
char *flags, *tmp;
char what = '+';
bzero(flag, sizeof(TKLFlag));
strlcpy(paratmp, para, sizeof(paratmp));
flags = strtok(paratmp, " ");
if (!flags)
return;
for (; *flags; flags++)
{
switch (*flags)
{
case '+':
what = '+';
break;
case '-':
what = '-';
break;
case 'm':
if (flag->mask || !(tmp = strtok(NULL, " ")))
continue;
if (what == '+')
flag->flags |= BY_MASK;
else
flag->flags |= NOT_BY_MASK;
flag->mask = tmp;
break;
case 'r':
if (flag->reason || !(tmp = strtok(NULL, " ")))
continue;
if (what == '+')
flag->flags |= BY_REASON;
else
flag->flags |= NOT_BY_REASON;
flag->reason = tmp;
break;
case 's':
if (flag->set_by || !(tmp = strtok(NULL, " ")))
continue;
if (what == '+')
flag->flags |= BY_SETBY;
else
flag->flags |= NOT_BY_SETBY;
flag->set_by = tmp;
break;
}
}
}
/** Does this TKL entry match the search terms?
* This is a helper function for tkl_stats().
*/
void tkl_stats_matcher(aClient *cptr, int type, char *para, TKLFlag *tklflags, aTKline *tkl)
{
/* This handles the selection */
if (!BadPtr(para))
{
if (tklflags->flags & BY_SETBY)
if (!match_simple(tklflags->set_by, tkl->set_by))
return;
if (tklflags->flags & NOT_BY_SETBY)
if (match_simple(tklflags->set_by, tkl->set_by))
return;
if (TKLIsServerBan(tkl))
{
if (tklflags->flags & BY_MASK)
{
if (tkl->type & TKL_NAME)
{
if (!match_simple(tklflags->mask, tkl->ptr.nameban->name))
return;
}
else if (!match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
return;
}
if (tklflags->flags & NOT_BY_MASK)
{
if (tkl->type & TKL_NAME)
{
if (match_simple(tklflags->mask, tkl->ptr.nameban->name))
return;
}
else if (match_simple(tklflags->mask, make_user_host(tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask)))
return;
}
if (tklflags->flags & BY_REASON)
if (!match_simple(tklflags->reason, tkl->ptr.serverban->reason))
return;
if (tklflags->flags & NOT_BY_REASON)
if (match_simple(tklflags->reason, tkl->ptr.serverban->reason))
return;
}
}
/* If we are still here, then we will send the STATS entry */
if (tkl->type == (TKL_KILL | TKL_GLOBAL))
{
sendnumeric(cptr, RPL_STATSGLINE, 'G',
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
(tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
}
if (tkl->type == (TKL_ZAP | TKL_GLOBAL))
{
sendnumeric(cptr, RPL_STATSGLINE, 'Z',
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
(tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
}
if (tkl->type == (TKL_SHUN | TKL_GLOBAL))
{
sendnumeric(cptr, RPL_STATSGLINE, 's',
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
(tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
}
if (tkl->type == (TKL_KILL))
{
sendnumeric(cptr, RPL_STATSGLINE, 'K',
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
(tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
}
if (tkl->type == (TKL_ZAP))
{
sendnumeric(cptr, RPL_STATSGLINE, 'z',
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
(tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
(TStime() - tkl->set_at), tkl->set_by, tkl->ptr.serverban->reason);
}
if (tkl->type & TKL_SPAMF)
{
sendnumeric(cptr, RPL_STATSSPAMF,
(tkl->type & TKL_GLOBAL) ? 'F' : 'f',
unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
banact_valtostring(tkl->ptr.spamfilter->action),
(tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
TStime() - tkl->set_at,
tkl->ptr.spamfilter->tkl_duration, tkl->ptr.spamfilter->tkl_reason,
tkl->set_by,
tkl->ptr.spamfilter->match->str);
if (para && !strcasecmp(para, "del"))
{
char *hash = spamfilter_id(tkl);
if (tkl->type & TKL_GLOBAL)
{
sendtxtnumeric(cptr, "To delete this spamfilter, use /SPAMFILTER del %s", hash);
sendtxtnumeric(cptr, "-");
} else {
sendtxtnumeric(cptr, "This spamfilter is stored in the configuration file and cannot be removed with /SPAMFILTER del");
sendtxtnumeric(cptr, "-");
}
}
}
if (tkl->type & TKL_NAME)
{
sendnumeric(cptr, RPL_STATSQLINE, (tkl->type & TKL_GLOBAL) ? 'Q' : 'q',
tkl->ptr.nameban->name, (tkl->expire_at != 0) ? (tkl->expire_at - TStime()) : 0,
TStime() - tkl->set_at, tkl->set_by, tkl->ptr.nameban->reason);
}
}
/* TKL Stats. This is used by /STATS gline and all the others */
void _tkl_stats(aClient *cptr, int type, char *para)
{
aTKline *tk;
TKLFlag tklflags;
int index, index2;
if (!BadPtr(para))
parse_stats_params(para, &tklflags);
/* First the IP hashed entries (if applicable).. */
index = tkl_ip_hash_type(tkl_typetochar(type));
if (index >= 0)
{
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
{
for (tk = tklines_ip_hash[index][index2]; tk; tk = tk->next)
{
if (type && tk->type != type)
continue;
tkl_stats_matcher(cptr, type, para, &tklflags, tk);
}
}
}
/* Then the normal entries... */
for (index = 0; index < TKLISTLEN; index++)
{
for (tk = tklines[index]; tk; tk = tk->next)
{
if (type && tk->type != type)
continue;
tkl_stats_matcher(cptr, type, para, &tklflags, tk);
}
}
if ((type == (TKL_SPAMF|TKL_GLOBAL)) && (!para || strcasecmp(para, "del")))
{
/* If requesting spamfilter stats and not spamfilter del, then suggest it. */
sendnotice(cptr, "Tip: if you are looking for an easy way to remove a spamfilter, run '/SPAMFILTER del'.");
}
}
/** Synchronize a TKL entry with the other server.
* @param sender The sender (eg: &me).
* @param to The remote server.
* @param tkl The TKL entry.
*/
void tkl_synch_send_entry(int add, aClient *sender, aClient *to, aTKline *tkl)
{
char typ;
if (!(tkl->type & TKL_GLOBAL))
return; /* nothing to sync */
typ = tkl_typetochar(tkl->type);
if (TKLIsSpamfilter(tkl))
{
sendto_one(to, NULL, ":%s TKL %c %c %s %s %s %lld %lld %lld %s %s :%s", sender->name,
add ? '+' : '-',
typ,
spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
banact_valtostring(tkl->ptr.spamfilter->action),
tkl->set_by,
(long long)tkl->expire_at, (long long)tkl->set_at,
(long long)tkl->ptr.spamfilter->tkl_duration, tkl->ptr.spamfilter->tkl_reason,
unreal_match_method_valtostr(tkl->ptr.spamfilter->match->type),
tkl->ptr.spamfilter->match->str);
} else
if (TKLIsServerBan(tkl))
{
sendto_one(to, NULL, ":%s TKL %c %c %s%s %s %s %lld %lld :%s", sender->name,
add ? '+' : '-',
typ,
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
*tkl->ptr.serverban->usermask ? tkl->ptr.serverban->usermask : "*",
tkl->ptr.serverban->hostmask, tkl->set_by,
(long long)tkl->expire_at, (long long)tkl->set_at,
tkl->ptr.serverban->reason);
} else
if (TKLIsNameBan(tkl))
{
sendto_one(to, NULL, ":%s TKL %c %c %c %s %s %lld %lld :%s", sender->name,
add ? '+' : '-',
typ,
tkl->ptr.nameban->hold ? 'H' : '*',
tkl->ptr.nameban->name,
tkl->set_by,
(long long)tkl->expire_at, (long long)tkl->set_at,
tkl->ptr.nameban->reason);
}
}
/** Broadcast a TKL entry.
* @param sender The sender, eg &me
* @param skip The client to skip, eg cptr or NULL.
* @param tkl The TKL entry to synchronize with the other servers.
*/
void tkl_broadcast_entry(int add, aClient *sender, aClient *skip, aTKline *tkl)
{
aClient *cptr;
list_for_each_entry(cptr, &server_list, special_node)
{
if (skip && cptr == skip->from)
continue;
tkl_synch_send_entry(add, sender, cptr, tkl);
}
}
/** Synchronize all TKL entries with this server.
* @param sptr The server to synchronize with.
*/
void _tkl_synch(aClient *sptr)
{
aTKline *tkl;
int index, index2;
/* First, hashed entries.. */
for (index = 0; index < TKLIPHASHLEN1; index++)
{
for (index2 = 0; index2 < TKLIPHASHLEN2; index2++)
{
for (tkl = tklines_ip_hash[index][index2]; tkl; tkl = tkl->next)
{
tkl_synch_send_entry(1, &me, sptr, tkl);
}
}
}
/* Then, regular entries.. */
for (index = 0; index < TKLISTLEN; index++)
{
for (tkl = tklines[index]; tkl; tkl = tkl->next)
{
tkl_synch_send_entry(1, &me, sptr, tkl);
}
}
}
/** Show TKL type as a string (used when adding/removing) */
char *_tkl_type_string(aTKline *tkl)
{
static char txt[256];
*txt = '\0';
if (TKLIsServerBan(tkl) && (tkl->ptr.serverban->subtype == TKL_SUBTYPE_SOFT))
strlcpy(txt, "Soft ", sizeof(txt));
switch (tkl->type)
{
case TKL_KILL:
strlcat(txt, "K-Line", sizeof(txt));
break;
case TKL_ZAP:
strlcat(txt, "Z-Line", sizeof(txt));
break;
case TKL_KILL | TKL_GLOBAL:
strlcat(txt, "G-Line", sizeof(txt));
break;
case TKL_ZAP | TKL_GLOBAL:
strlcat(txt, "Global Z-Line", sizeof(txt));
break;
case TKL_SHUN | TKL_GLOBAL:
strlcat(txt, "Shun", sizeof(txt));
break;
case TKL_NAME | TKL_GLOBAL:
strlcat(txt, "Global Q-Line", sizeof(txt));
break;
case TKL_NAME:
strlcat(txt, "Q-Line", sizeof(txt));
break;
case TKL_SPAMF:
strlcat(txt, "Local Spamfilter", sizeof(txt));
break;
case TKL_SPAMF | TKL_GLOBAL:
strlcat(txt, "Spamfilter", sizeof(txt));
break;
case TKL_EXCEPT:
strlcat(txt, "Local Exception", sizeof(txt));
break;
case TKL_EXCEPT | TKL_GLOBAL:
strlcat(txt, "Exception", sizeof(txt));
break;
default:
strlcat(txt, "Unknown *-Line", sizeof(txt));
}
return txt;
}
/** Find a server ban TKL - only used to prevent duplicates and for deletion */
aTKline *_find_tkl_serverban(int type, char *usermask, char *hostmask, int softban)
{
char tpe = tkl_typetochar(type);
aTKline *head, *tkl;
if (!TKLIsServerBanType(type))
abort();
head = tkl_find_head(tpe, hostmask, tklines[tkl_hash(tpe)]);
for (tkl = head; tkl; tkl = tkl->next)
{
if (tkl->type == type)
{
if (!stricmp(tkl->ptr.serverban->hostmask, hostmask) &&
!stricmp(tkl->ptr.serverban->usermask, usermask))
{
/* And an extra check for soft/hard ban mismatches.. */
if ((tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) == softban)
return tkl;
}
}
}
return NULL; /* Not found */
}
/** Find a name ban TKL (qline) - only used to prevent duplicates and for deletion */
aTKline *_find_tkl_nameban(int type, char *name, int hold)
{
char tpe = tkl_typetochar(type);
aTKline *tkl;
if (!TKLIsNameBanType(type))
abort();
for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
{
if ((tkl->type == type) && !stricmp(tkl->ptr.nameban->name, name))
return tkl;
}
return NULL; /* Not found */
}
/** Find a spamfilter TKL - only used to prevent duplicates and for deletion */
aTKline *_find_tkl_spamfilter(int type, char *match_string, unsigned short action, unsigned short target)
{
char tpe = tkl_typetochar(type);
aTKline *tkl;
if (!TKLIsSpamfilterType(type))
abort();
for (tkl = tklines[tkl_hash(tpe)]; tkl; tkl = tkl->next)
{
if ((type == tkl->type) &&
!strcmp(match_string, tkl->ptr.spamfilter->match->str) &&
(action == tkl->ptr.spamfilter->action) &&
(target == tkl->ptr.spamfilter->target))
{
return tkl;
}
}
return NULL; /* Not found */
}
/** Send a notice to opers about the TKL that is being added */
void _sendnotice_tkl_add(aTKline *tkl)
{
char buf[512];
char set_at[128];
char expire_at[128];
char *tkl_type_str; /**< Eg: "K-Line" */
/* Don't show notices for temporary nick holds (issued by services) */
if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
return;
tkl_type_str = tkl_type_string(tkl);
*buf = *set_at = *expire_at = '\0';
short_date(tkl->set_at, set_at);
if (tkl->expire_at > 0)
short_date(tkl->expire_at, expire_at);
if (TKLIsServerBan(tkl))
{
if (tkl->expire_at != 0)
{
ircsnprintf(buf, sizeof(buf), "%s added for %s%s@%s on %s GMT (from %s to expire at %s GMT: %s)",
tkl_type_str,
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
set_at, tkl->set_by, expire_at, tkl->ptr.serverban->reason);
} else {
ircsnprintf(buf, sizeof(buf), "Permanent %s added for %s%s@%s on %s GMT (from %s: %s)",
tkl_type_str,
(tkl->ptr.serverban->subtype & TKL_SUBTYPE_SOFT) ? "%" : "",
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
set_at, tkl->set_by, tkl->ptr.serverban->reason);
}
} else
if (TKLIsNameBan(tkl))
{
if (tkl->expire_at > 0)
{
ircsnprintf(buf, sizeof(buf), "%s added for %s on %s GMT (from %s to expire at %s GMT: %s)",
tkl_type_str, tkl->ptr.nameban->name, set_at, tkl->set_by, expire_at, tkl->ptr.nameban->reason);
} else {
ircsnprintf(buf, sizeof(buf), "Permanent %s added for %s on %s GMT (from %s: %s)",
tkl_type_str, tkl->ptr.nameban->name, set_at, tkl->set_by, tkl->ptr.nameban->reason);
}
} else
if (TKLIsSpamfilter(tkl))
{
/* Spamfilter */
ircsnprintf(buf, sizeof(buf),
"Spamfilter added: '%s' [target: %s] [action: %s] [reason: %s] on %s GMT (from %s)",
tkl->ptr.spamfilter->match->str,
spamfilter_target_inttostring(tkl->ptr.spamfilter->target),
banact_valtostring(tkl->ptr.spamfilter->action),
unreal_decodespace(tkl->ptr.spamfilter->tkl_reason),
set_at,
tkl->set_by);
} else
{
ircsnprintf(buf, sizeof(buf), "[BUG] %s added but type unhandled in sendnotice_tkl_add()!!!", tkl_type_str);
}
sendto_snomask(SNO_TKL, "*** %s", buf);
ircd_log(LOG_TKL, "%s", buf);
}
/** Send a notice to opers about the TKL that is being deleted */
void _sendnotice_tkl_del(char *removed_by, aTKline *tkl)
{
char buf[512];
char set_at[128];
char *tkl_type_str;
/* Don't show notices for temporary nick holds (issued by services) */
if (TKLIsNameBan(tkl) && tkl->ptr.nameban->hold)
return;
tkl_type_str = tkl_type_string(tkl); /* eg: "K-Line" */
*buf = *set_at = '\0';
short_date(tkl->set_at, set_at);
if (TKLIsServerBan(tkl))
{
ircsnprintf(buf, sizeof(buf),
"%s removed %s %s@%s (set at %s - reason: %s)",
removed_by, tkl_type_str,
tkl->ptr.serverban->usermask, tkl->ptr.serverban->hostmask,
set_at, tkl->ptr.serverban->reason);
} else
if (TKLIsNameBan(tkl))
{
ircsnprintf(buf, sizeof(buf),
"%s removed %s %s (set at %s - reason: %s)",
removed_by, tkl_type_str, tkl->ptr.nameban->name, set_at, tkl->ptr.nameban->reason);
} else
if (TKLIsSpamfilter(tkl))
{
ircsnprintf(buf, sizeof(buf),
"%s removed Spamfilter '%s' (set at %s)",
removed_by, tkl->ptr.spamfilter->match->str, set_at);
} else
{
ircsnprintf(buf, sizeof(buf), "[BUG] %s added but type unhandled in sendnotice_tkl_del()!!!!!", tkl_type_str);
}
sendto_snomask(SNO_TKL, "*** %s", buf);
ircd_log(LOG_TKL, "%s", buf);
}
/** Add a TKL using the TKL layer. See m_tkl for parv[] and protocol documentation. */
CMD_FUNC(m_tkl_add)
{
aTKline *tkl;
int type;
time_t expire_at, set_at;
char *set_by;
char tkl_entry_exists = 0;
/* we rely on servers to be failsafe.. */
if (!IsServer(sptr) && !IsMe(sptr))
return 0;
if (parc < 9)
return 0;
type = tkl_chartotype(parv[2][0]);
if (!type)
return 0;
/* All TKL types have the following fields in common when adding:
* parv[5]: set_by
* parv[6]: expire_at
* parv[7]: set_at
* ... so we validate them here at the beginning.
*/
set_by = parv[5];
expire_at = atol(parv[6]);
set_at = atol(parv[7]);
/* Validate set and expiry time */
if ((set_at < 0) || !short_date(set_at, NULL))
{
sendto_realops("Invalid TKL entry from %s, set-at time is out of range (%lld) -- not added. Clock on other server incorrect or bogus entry.",
sptr->name, (long long)set_at);
return 0;
}
if ((expire_at < 0) || !short_date(expire_at, NULL))
{
sendto_realops("Invalid TKL entry from %s, expiry time is out of range (%lld) -- not added. Clock on other server incorrect or bogus entry.",
sptr->name, (long long)expire_at);
return 0;
}
/* Now comes type-specific validation
* and we check if the TKL entry already exists and needs updating too.
*/
if (TKLIsServerBanType(type))
{
/* Validate server ban TKL fields */
int softban = 0;
char *usermask = parv[3];
char *hostmask = parv[4];
char *reason = parv[8];
/* Some simple validation on usermask and hostmask:
* may not contain an @. Yeah, some services or self-written
* linked servers are known to have sent this in the past.
*/
if (strchr(usermask, '@') || strchr(hostmask, '@'))
{
sendto_realops("Ignoring TKL entry %s@%s from %s. "
"Invalid usermask '%s' or hostmask '%s'.",
usermask, hostmask, sptr->name, usermask, hostmask);
return 0;
}
/* In case of a soft ban, strip the percent sign early,
* so parv[3] (username) is really the username without any prefix.
* Set the 'softban' flag if this is the case.
*/
if (*usermask == '%')
{
usermask++;
softban = 1;
}
tkl = find_tkl_serverban(type, usermask, hostmask, softban);
if (tkl)
{
tkl_entry_exists = 1;
} else {
tkl = tkl_add_serverban(type, usermask, hostmask, reason,
set_by, expire_at, set_at, softban, 0);
}
} else
if (TKLIsNameBanType(type))
{
/* Validate name ban TKL fields */
int hold = 0;
char *name = parv[4];
char *reason = parv[8];
if (*parv[3] == 'H')
hold = 1;
tkl = find_tkl_nameban(type, name, hold);
if (tkl)
{
tkl_entry_exists = 1;
} else {
tkl = tkl_add_nameban(type, name, hold, reason, set_by, expire_at,
set_at, 0);
}
} else
if (TKLIsSpamfilterType(type))
{
/* Validate spamfilter-specific TKL fields */
MatchType match_method;
char *match_string;
aMatch *m; /* compiled match_string */
time_t tkl_duration;
char *tkl_reason;
unsigned short action;
unsigned short target;
/* helper variables */
char *err;
if (parc < 12)
{
sendto_realops("Ignoring spamfilter from %s. Running very old UnrealIRCd protocol (3.2.X?)", sptr->name);
return 0;
}
match_string = parv[11];
if (!strcasecmp(parv[10], "posix"))
{
sendto_realops("Ignoring spamfilter from %s. Spamfilter is of type 'posix' (TRE) which "
"is not supported in UnrealIRCd 5. Suggestion: upgrade the other server.",
sptr->name);
return 0;
}
match_method = unreal_match_method_strtoval(parv[10]);
if (match_method == 0)
{
sendto_realops("Ignoring spamfilter '%s' from %s with unknown match type '%s'",
match_string, sptr->name, parv[10]);
return 0;
}
if (!(target = spamfilter_gettargets(parv[3], NULL)))
{
sendto_realops("Ignoring spamfilter '%s' from %s with unknown target type '%s'",
match_string, sptr->name, parv[3]);
return 0;
}
if (!(action = banact_chartoval(*parv[4])))
{
sendto_realops("Ignoring spamfilter '%s' from %s with unknown action type '%s'",
match_string, sptr->name, parv[4]);
return 0;
}
tkl_duration = config_checkval(parv[8], CFG_TIME);
tkl_reason = parv[9];
tkl = find_tkl_spamfilter(type, match_string, action, target);
if (tkl)
{
tkl_entry_exists = 1;
} else {
m = unreal_create_match(match_method, match_string, &err);
if (!m)
{
sendto_realops("[TKL ERROR] ERROR: Trying to add a spamfilter which does not compile. "
" ERROR='%s', Spamfilter='%s', from='%s'",
err, match_string, sptr->name);
return 0;
}
tkl = tkl_add_spamfilter(type, target, action, m, set_by, expire_at, set_at,
tkl_duration, tkl_reason, 0);
}
} else
{
/* Unhandled, should never happen */
abort();
}
if (!tkl)
return 0;
if (tkl_entry_exists)
{
/* Let's see if we need to update the existing entry.
* Note that we only update common fields,
* which is acceptable to me. -- Syzop
*/
if ((set_at < tkl->set_at) || (expire_at != tkl->expire_at) || strcmp(tkl->set_by, parv[5]))
{
/* here's how it goes:
* set_at: oldest wins
* expire_at: longest wins
* set_by: highest strcmp wins
*
* We broadcast the result of this back to all servers except
* cptr's direction, because cptr will do the same thing and
* send it back to his servers (except us)... no need for a
* double networkwide flood ;p. -- Syzop
*/
tkl->set_at = MIN(tkl->set_at, set_at);
if (!tkl->expire_at || !expire_at)
tkl->expire_at = 0;
else
tkl->expire_at = MAX(tkl->expire_at, expire_at);
if (strcmp(tkl->set_by, parv[5]) < 0)
safestrdup(tkl->set_by, parv[5]);
if (type & TKL_GLOBAL)
tkl_broadcast_entry(1, sptr, cptr, tkl);
}
return 0;
}
/* Below this line we will only use 'tkl'. No parc/parv reading anymore. */
RunHook3(HOOKTYPE_TKL_ADD, cptr, sptr, tkl);
sendnotice_tkl_add(tkl);
/* spamfilter 'warn' action is special */
if ((tkl->type & TKL_SPAMF) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN) && (tkl->ptr.spamfilter->target & SPAMF_USER))
spamfilter_check_users(tkl);
/* Ban checking executes during run loop for efficiency */
loop.do_bancheck = 1;
if (type & TKL_GLOBAL)
tkl_broadcast_entry(1, sptr, cptr, tkl);
return 0;
}
/** Delete a TKL using the TKL layer. See m_tkl for parv[] and protocol documentation. */
CMD_FUNC(m_tkl_del)
{
aTKline *tkl;
int type;
char *removed_by;
if (!IsServer(sptr) && !IsMe(sptr))
return 0;
if (parc < 6)
return 0;
type = tkl_chartotype(parv[2][0]);
if (type == 0)
return 0;
removed_by = parv[5];
if (TKLIsServerBanType(type))
{
char *usermask = parv[3];
char *hostmask = parv[4];
int softban = 0;
if (*usermask == '%')
{
usermask++;
softban = 1;
}
tkl = find_tkl_serverban(type, usermask, hostmask, softban);
}
else if (TKLIsNameBanType(type))
{
int hold = 0;
char *name = parv[4];
if (*parv[3] == 'H')
hold = 1;
tkl = find_tkl_nameban(type, name, hold);
}
else if (TKLIsSpamfilterType(type))
{
char *match_string;
unsigned short target;
unsigned short action;
if (parc < 9)
{
sendto_realops("[BUG] m_tkl called with bogus spamfilter removal request [f/F], from=%s, parc=%d",
sptr->name, parc);
return 0; /* bogus */
}
if (parc >= 12)
match_string = parv[11];
else if (parc >= 11)
match_string = parv[10];
else
match_string = parv[8];
if (!(target = spamfilter_gettargets(parv[3], NULL)))
{
sendto_realops("Ignoring spamfilter deletion request for '%s' from %s with unknown target type '%s'",
match_string, sptr->name, parv[3]);
return 0;
}
if (!(action = banact_chartoval(*parv[4])))
{
sendto_realops("Ignoring spamfilter deletion request for '%s' from %s with unknown action type '%s'",
match_string, sptr->name, parv[4]);
return 0;
}
tkl = find_tkl_spamfilter(type, match_string, action, target);
} else
{
/* This can never happen, unless someone added a TKL type
* to UnrealIRCd but forgot to add the removal code :D.
*/
abort();
}
if (!tkl)
return 0; /* Item not found, nothing to remove. */
if (tkl->flags & TKL_FLAG_CONFIG)
return 0; /* Item is in the configuration file (persistent) */
/* broadcast remove msg to opers... */
sendnotice_tkl_del(removed_by, tkl);
if (type & TKL_SHUN)
tkl_check_local_remove_shun(tkl);
RunHook3(HOOKTYPE_TKL_DEL, cptr, sptr, tkl);
if (type & TKL_GLOBAL)
tkl_broadcast_entry(0, sptr, cptr, tkl);
tkl_del_line(tkl);
return 0;
}
/** TKL command: server to server handling of *LINEs and SPAMFILTERs.
* HISTORY:
* This was originall called Timed KLines, but today it's
* used by various *line types eg: zline, gline, gzline, shun,
* but also by spamfilter etc...
* DOCUMENTATION
* See (also) https://www.unrealircd.org/docs/Server_protocol:TKL_command
* USAGE:
* This routine is used both internally by the ircd (to
* for example add local klines, zlines, etc) and over the
* network (glines, gzlines, spamfilter, etc).
* add: remove: spamfilter spamfilter sqline:
* remove in U4: with TKLEXT2:
* parv[ 1]: + - - +/- +/-
* parv[ 2]: type type type type type
* parv[ 3]: user user target target hold
* parv[ 4]: host host action action host
* parv[ 5]: set_by removedby (un)set_by set_by set_by
* parv[ 6]: expire_at expire_at (0) expire_at (0) expire_at
* parv[ 7]: set_at set_at set_at set_at
* parv[ 8]: reason regex tkl duration reason
* parv[ 9]: tkl reason [A]
* parv[10]: match-type [B]
* parv[11]: match-string [C]
*
* [A] tkl reason field must be escaped by caller [eg: use unreal_encodespace()
* if m_tkl is called internally].
* [B] match-type must be one of: regex, simple, posix.
* [C] Could be a regex or a regular string with wildcards, depending on [B]
*/
CMD_FUNC(_m_tkl)
{
if (!IsServer(sptr) && !IsOper(sptr) && !IsMe(sptr))
return 0;
if (parc < 2)
return 0;
switch (*parv[1])
{
case '+':
return m_tkl_add(cptr, sptr, recv_mtags, parc, parv);
case '-':
return m_tkl_del(cptr, sptr, recv_mtags, parc, parv);
default:
break;
}
return 0;
}
/** Take an action on the user, such as banning or killing.
* @author Bram Matthys (Syzop), 2003-present
* @param sptr The client which is affected.
* @param action The type of ban (one of BAN_ACT_*).
* @param reason The ban reason.
* @param duration The ban duration in seconds.
* @note This function assumes that sptr is a locally connected user.
* @retval -1 in case of block/tempshun.
* @retval -5 in case of kill/zline/gline/etc (-5 = FLUSH_BUFFER).
* one should no longer read from 'sptr' as the client
* has been freed.
* @retval 0 no action is taken, the user is exempted.
*/
int _place_host_ban(aClient *sptr, int action, char *reason, long duration)
{
/* If this is a soft action and the user is logged in, then the ban does not apply.
* NOTE: Actually in such a case it would be better if place_host_ban() would not
* be called at all. Or at least, the caller should not take any action
* (eg: the message should be delivered, the user may connect, etc..)
* The following is more like secondary protection in case the caller forgets...
*/
if (IsSoftBanAction(action) && IsLoggedIn(sptr))
return 0;
RunHookReturnInt4(HOOKTYPE_PLACE_HOST_BAN, sptr, action, reason, duration, !=99);
switch(action)
{
case BAN_ACT_TEMPSHUN:
/* We simply mark this connection as shunned and do not add a ban record */
sendto_snomask(SNO_TKL, "Temporary shun added at user %s (%s@%s) [%s]",
sptr->name,
sptr->user ? sptr->user->username : "unknown",
sptr->user ? sptr->user->realhost : GetIP(sptr),
reason);
SetShunned(sptr);
break;
case BAN_ACT_GZLINE:
case BAN_ACT_GLINE:
case BAN_ACT_SOFT_GLINE:
case BAN_ACT_ZLINE:
case BAN_ACT_KLINE:
case BAN_ACT_SOFT_KLINE:
case BAN_ACT_SHUN:
case BAN_ACT_SOFT_SHUN:
{
char ip[128], user[USERLEN+3], mo[100], mo2[100];
char *tkllayer[9] = {
me.name, /*0 server.name */
"+", /*1 +|- */
"?", /*2 type */
"*", /*3 user */
NULL, /*4 host */
NULL,
NULL, /*6 expire_at */
NULL, /*7 set_at */
NULL /*8 reason */
};
strlcpy(ip, GetIP(sptr), sizeof(ip));
if (iConf.ban_include_username && (action != BAN_ACT_ZLINE) && (action != BAN_ACT_GZLINE))
{
if (sptr->user)
strlcpy(user, sptr->user->username, sizeof(user));
else
strlcpy(user, "*", sizeof(user));
tkllayer[3] = user;
}
/* For soft bans we need to prefix the % in the username */
if (IsSoftBanAction(action))
{
char tmp[USERLEN+3];
snprintf(tmp, sizeof(tmp), "%%%s", tkllayer[3]);
strlcpy(user, tmp, sizeof(user));
tkllayer[3] = user;
}
if ((action == BAN_ACT_KLINE) || (action == BAN_ACT_SOFT_KLINE))
tkllayer[2] = "k";
else if (action == BAN_ACT_ZLINE)
tkllayer[2] = "z";
else if (action == BAN_ACT_GZLINE)
tkllayer[2] = "Z";
else if ((action == BAN_ACT_GLINE) || (action == BAN_ACT_SOFT_GLINE))
tkllayer[2] = "G";
else if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
tkllayer[2] = "s";
tkllayer[4] = ip;
tkllayer[5] = me.name;
if (!duration)
strlcpy(mo, "0", sizeof(mo)); /* perm */
else
ircsnprintf(mo, sizeof(mo), "%lld", (long long)(duration + TStime()));
ircsnprintf(mo2, sizeof(mo2), "%lld", (long long)TStime());
tkllayer[6] = mo;
tkllayer[7] = mo2;
tkllayer[8] = reason;
m_tkl(&me, &me, NULL, 9, tkllayer);
if ((action == BAN_ACT_SHUN) || (action == BAN_ACT_SOFT_SHUN))
{
find_shun(sptr);
return -1;
} else
return find_tkline_match(sptr, 0);
}
case BAN_ACT_SOFT_KILL:
case BAN_ACT_KILL:
default:
return exit_client(sptr, sptr, sptr, NULL, reason);
}
return -1;
}
/** This function compares two spamfilters ('one' and 'two') and will return
* a 'winner' based on which one has the strongest action.
* If both have equal action then some additional logic is applied simply
* to ensure we (almost) always return the same winner regardless of the
* order of the spamfilters (which may differ between servers).
*/
aTKline *choose_winning_spamfilter(aTKline *one, aTKline *two)
{
int n;
if (!TKLIsSpamfilter(one) || !TKLIsSpamfilter(two))
abort();
/* First, see if the action field differs... */
if (one->ptr.spamfilter->action != two->ptr.spamfilter->action)
{
/* We can simply compare the action. Highest (strongest) wins. */
if (one->ptr.spamfilter->action > two->ptr.spamfilter->action)
return one;
else
return two;
}
/* Ok, try comparing the regex then.. */
n = strcmp(one->ptr.spamfilter->match->str, two->ptr.spamfilter->match->str);
if (n < 0)
return one;
if (n > 0)
return two;
/* Hmm.. regex is identical. Try the 'reason' field. */
n = strcmp(one->ptr.spamfilter->tkl_reason, two->ptr.spamfilter->tkl_reason);
if (n < 0)
return one;
if (n > 0)
return two;
/* Hmm.. 'reason' is identical as well.
* Make a final decision, could still be identical but would be unlikely.
*/
return (one->ptr.spamfilter->target > two->ptr.spamfilter->target) ? one : two;
}
/** Checks if 'target' is on the spamfilter exception list.
* RETURNS 1 if found in list, 0 if not.
*/
static int target_is_spamexcept(char *target)
{
SpamExcept *e;
for (e = iConf.spamexcept; e; e = e->next)
{
if (match_simple(e->name, target))
return 1;
}
return 0;
}
/** Make user join the virus channel.
* @param sptr The user that was doing something bad.
* @param tk The TKL entry that matched this user.
* @param type The spamfilter type (SPAMF_*)
* TODO: Looks redundant?
*/
int _join_viruschan(aClient *sptr, aTKline *tkl, int type)
{
char *xparv[3], chbuf[CHANNELLEN + 16], buf[2048];
aChannel *chptr;
int ret;
snprintf(buf, sizeof(buf), "0,%s", SPAMFILTER_VIRUSCHAN);
xparv[0] = sptr->name;
xparv[1] = buf;
xparv[2] = NULL;
/* RECURSIVE CAUTION in case we ever add blacklisted chans */
spamf_ugly_vchanoverride = 1;
ret = do_cmd(sptr, sptr, NULL, "JOIN", 2, xparv);
spamf_ugly_vchanoverride = 0;
if (ret == FLUSH_BUFFER)
return FLUSH_BUFFER; /* don't ask me how we could have died... */
sendnotice(sptr, "You are now restricted to talking in %s: %s",
SPAMFILTER_VIRUSCHAN, unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
chptr = find_channel(SPAMFILTER_VIRUSCHAN, NULL);
if (chptr)
{
MessageTag *mtags = NULL;
ircsnprintf(chbuf, sizeof(chbuf), "@%s", chptr->chname);
ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s matched filter '%s' [%s] [%s]",
sptr->name, tkl->ptr.spamfilter->match->str, cmdname_by_spamftarget(type),
unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
new_message(&me, NULL, &mtags);
sendto_channel(chptr, &me, NULL, PREFIX_OP|PREFIX_ADMIN|PREFIX_OWNER,
0, SEND_ALL|SKIP_DEAF, mtags,
":%s NOTICE %s :%s", me.name, chbuf, buf);
free_message_tags(mtags);
}
SetVirus(sptr);
return 0;
}
/** run_spamfilter: executes the spamfilter on the input string.
* @param str The text (eg msg text, notice text, part text, quit text, etc
* @param target The spamfilter target (SPAMF_*)
* @param destination The destination as a text string (eg: "somenick", can be NULL.. eg for away)
* @param flags Any flags (SPAMFLAG_*)
* @param rettkl Pointer to an aTKLline struct, _used for special circumstances only_
* RETURN VALUE:
* 0 if not matched, non-0 if it should be blocked.
* Return value can be FLUSH_BUFFER (-2) which means 'sptr' is
* _NOT_ valid anymore so you should return immediately
* (like from m_message, m_part, m_quit, etc).
*/
int _run_spamfilter(aClient *sptr, char *str_in, int target, char *destination, int flags, aTKline **rettkl)
{
aTKline *tkl;
aTKline *winner_tkl = NULL;
char *str;
int ret = -1;
char *reason = NULL;
#ifdef SPAMFILTER_DETECTSLOW
struct rusage rnow, rprev;
long ms_past;
#endif
if (rettkl)
*rettkl = NULL; /* initialize to NULL */
if (target == SPAMF_USER)
str = str_in;
else
str = (char *)StripControlCodes(str_in);
/* (note: using sptr->user check here instead of IsPerson()
* due to SPAMF_USER where user isn't marked as client/person yet.
*/
if (!sptr->user || ValidatePermissionsForPath("immune:server-ban:spamfilter",sptr,NULL,NULL,NULL) || IsULine(sptr))
return 0;
for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
{
if (!(tkl->ptr.spamfilter->target & target))
continue;
if ((flags & SPAMFLAG_NOWARN) && (tkl->ptr.spamfilter->action == BAN_ACT_WARN))
continue;
/* If the action is 'soft' (for non-logged in users only) then
* don't bother running the spamfilter if the user is logged in.
*/
if (IsSoftBanAction(tkl->ptr.spamfilter->action) && IsLoggedIn(sptr))
continue;
#ifdef SPAMFILTER_DETECTSLOW
memset(&rnow, 0, sizeof(rnow));
memset(&rprev, 0, sizeof(rnow));
getrusage(RUSAGE_SELF, &rprev);
#endif
ret = unreal_match(tkl->ptr.spamfilter->match, str);
#ifdef SPAMFILTER_DETECTSLOW
getrusage(RUSAGE_SELF, &rnow);
ms_past = ((rnow.ru_utime.tv_sec - rprev.ru_utime.tv_sec) * 1000) +
((rnow.ru_utime.tv_usec - rprev.ru_utime.tv_usec) / 1000);
if ((SPAMFILTER_DETECTSLOW_FATAL > 0) && (ms_past > SPAMFILTER_DETECTSLOW_FATAL))
{
sendto_realops("[Spamfilter] WARNING: Too slow spamfilter detected (took %ld msec to execute) "
"-- spamfilter will be \002REMOVED!\002: %s", ms_past, tkl->ptr.spamfilter->match->str);
tkl_del_line(tkl);
return 0; /* Act as if it didn't match, even if it did.. it's gone now anyway.. */
} else
if ((SPAMFILTER_DETECTSLOW_WARN > 0) && (ms_past > SPAMFILTER_DETECTSLOW_WARN))
{
sendto_realops("[Spamfilter] WARNING: SLOW Spamfilter detected (took %ld msec to execute): %s",
ms_past, tkl->ptr.spamfilter->match->str);
}
#endif
if (ret)
{
/* We have a match! */
char buf[1024];
char destinationbuf[48];
if (destination) {
destinationbuf[0] = ' ';
strlcpy(destinationbuf+1, destination, sizeof(destinationbuf)-1); /* cut it off */
} else
destinationbuf[0] = '\0';
/* Hold on.. perhaps it's on the exceptions list... */
if (!winner_tkl && destination && target_is_spamexcept(destination))
return 0; /* No problem! */
ircsnprintf(buf, sizeof(buf), "[Spamfilter] %s!%s@%s matches filter '%s': [%s%s: '%s'] [%s]",
sptr->name, sptr->user->username, sptr->user->realhost,
tkl->ptr.spamfilter->match->str,
cmdname_by_spamftarget(target), destinationbuf, str,
unreal_decodespace(tkl->ptr.spamfilter->tkl_reason));
sendto_snomask_global(SNO_SPAMF, "%s", buf);
ircd_log(LOG_SPAMFILTER, "%s", buf);
RunHook6(HOOKTYPE_LOCAL_SPAMFILTER, sptr, str, str_in, target, destination, tkl);
/* If we should stop after the first match, we end here... */
if (SPAMFILTER_STOP_ON_FIRST_MATCH)
{
winner_tkl = tkl;
break;
}
/* Otherwise.. we set 'winner_tkl' to the spamfilter with the strongest action. */
if (!winner_tkl)
winner_tkl = tkl;
else
winner_tkl = choose_winning_spamfilter(tkl, winner_tkl);
/* and continue.. */
}
}
tkl = winner_tkl;
if (!tkl)
return 0; /* NOMATCH, we are done */
/* Spamfilter matched, take action: */
reason = unreal_decodespace(tkl->ptr.spamfilter->tkl_reason);
if ((tkl->ptr.spamfilter->action == BAN_ACT_BLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_BLOCK))
{
switch(target)
{
case SPAMF_USERMSG:
case SPAMF_USERNOTICE:
sendnotice(sptr, "Message to %s blocked: %s", destination, reason);
break;
case SPAMF_CHANMSG:
case SPAMF_CHANNOTICE:
sendto_one(sptr, NULL, ":%s 404 %s %s :Message blocked: %s",
me.name, sptr->name, destination, reason);
break;
case SPAMF_DCC:
sendnotice(sptr, "DCC to %s blocked: %s", destination, reason);
break;
case SPAMF_AWAY:
/* hack to deal with 'after-away-was-set-filters' */
if (sptr->user->away && !strcmp(str_in, sptr->user->away))
{
/* free away & broadcast the unset */
MyFree(sptr->user->away);
sptr->user->away = NULL;
sendto_server(sptr, 0, 0, NULL, ":%s AWAY", sptr->name);
}
break;
case SPAMF_TOPIC:
//...
sendnotice(sptr, "Setting of topic on %s to that text is blocked: %s",
destination, reason);
break;
default:
break;
}
return -1;
} else
if ((tkl->ptr.spamfilter->action == BAN_ACT_WARN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_WARN))
{
if ((target != SPAMF_USER) && (target != SPAMF_QUIT))
sendnumeric(sptr, RPL_SPAMCMDFWD, cmdname_by_spamftarget(target), reason);
return 0;
} else
if ((tkl->ptr.spamfilter->action == BAN_ACT_DCCBLOCK) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_DCCBLOCK))
{
if (target == SPAMF_DCC)
{
sendnotice(sptr, "DCC to %s blocked: %s", destination, reason);
sendnotice(sptr, "*** You have been blocked from sending files, reconnect to regain permission to send files");
sptr->flags |= FLAGS_DCCBLOCK;
}
return -1;
} else
if ((tkl->ptr.spamfilter->action == BAN_ACT_VIRUSCHAN) || (tkl->ptr.spamfilter->action == BAN_ACT_SOFT_VIRUSCHAN))
{
if (IsVirus(sptr)) /* Already tagged */
return 0;
/* There's a race condition for SPAMF_USER, so 'rettk' is used for SPAMF_USER
* when a user is currently connecting and filters are checked:
*/
if (!IsClient(sptr))
{
if (rettkl)
*rettkl = tkl;
return -5;
}
join_viruschan(sptr, tkl, target);
return -5;
} else
return place_host_ban(sptr, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration);
return 0; /* NOTREACHED */
}
/** CIDR function to compare the first 'mask' bits.
* @returns 1 if equal, 0 if not.
* @notes Taken from atheme
*/
static int comp_with_mask(void *addr, void *dest, u_int mask)
{
if (memcmp(addr, dest, mask / 8) == 0)
{
int n = mask / 8;
int m = (0xffff << (8 - (mask % 8)));
if (mask % 8 == 0 || (((u_char *) addr)[n] & m) == (((u_char *) dest)[n] & m))
{
return (1);
}
}
return (0);
}
#define IPSZ 16
/** Match a user against a mask.
* This will deal with 'nick!user@host', 'user@host' and just 'host'.
* We try to match the 'host' portion against the client IP, real host, etc...
* CIDR support is available so 'host' may be like '1.2.0.0/16'.
* @returns 1 on match, 0 on no match.
*/
int _match_user(char *rmask, aClient *acptr, int options)
{
char mask[NICKLEN+USERLEN+HOSTLEN+8];
char clientip[IPSZ], maskip[IPSZ];
char *p = NULL;
char *nmask = NULL, *umask = NULL, *hmask = NULL;
int cidr = -1; /* CIDR length, -1 for no CIDR */
strlcpy(mask, rmask, sizeof(mask));
if (!(options & MATCH_MASK_IS_UHOST))
{
p = strchr(mask, '!');
if (p)
{
*p++ = '\0';
if (!*mask)
return 0; /* NOMATCH: '!...' */
nmask = mask;
umask = p;
/* Could just as well check nick right now */
if (!match_simple(nmask, acptr->name))
return 0; /* NOMATCH: nick mask did not match */
}
}
if (!(options & (MATCH_MASK_IS_HOST)))
{
p = strchr(p ? p : mask, '@');
if (p)
{
char *client_username = (acptr->user && *acptr->user->username) ? acptr->user->username : acptr->username;
*p++ = '\0';
if (!*p || !*mask)
return 0; /* NOMATCH: '...@' or '@...' */
hmask = p;
if (!umask)
umask = mask;
/* Check user portion right away */
if (!match_simple(umask, client_username))
return 0; /* NOMATCH: user mask did not match */
} else {
if (nmask)
return 0; /* NOMATCH: 'abc!def' (or even just 'abc!') */
hmask = mask;
}
} else {
hmask = mask;
}
/* If we get here then we have done checking nick / ident (if it was needed)
* and now need to match the 'host' portion.
*/
/**** Check visible host ****/
if (options & MATCH_CHECK_VISIBLE_HOST)
{
char *hostname = acptr->user ? GetHost(acptr) : (MyClient(acptr) ? acptr->local->sockhost : NULL);
if (hostname && match_simple(hmask, hostname))
return 1; /* MATCH: visible host */
}
/**** Check cloaked host ****/
if (options & MATCH_CHECK_CLOAKED_HOST)
{
if (acptr->user && match_simple(hmask, acptr->user->cloakedhost))
return 1; /* MATCH: cloaked host */
}
/**** check on IP ****/
if (options & MATCH_CHECK_IP)
{
p = strchr(hmask, '/');
if (p)
{
*p++ = '\0';
cidr = atoi(p);
if (cidr <= 0)
return 0; /* NOMATCH: invalid CIDR */
}
if (strchr(hmask, '?') || strchr(hmask, '*'))
{
/* Wildcards */
if (acptr->ip && match_simple(hmask, acptr->ip))
return 1; /* MATCH (IP with wildcards) */
} else
if (strchr(hmask, ':'))
{
/* IPv6 hostmask */
/* We can actually return here on match/nomatch as we don't need to check the
* virtual host and things like that since ':' can never be in a hostname.
*/
if (!acptr->ip || !strchr(acptr->ip, ':'))
return 0; /* NOMATCH: hmask is IPv6 address and client is not IPv6 */
if (!inet_pton6(acptr->ip, clientip))
return 0; /* NOMATCH: unusual failure */
if (!inet_pton6(hmask, maskip))
return 0; /* NOMATCH: invalid IPv6 IP in hostmask */
if (cidr < 0)
return comp_with_mask(clientip, maskip, 128); /* MATCH/NOMATCH by exact IP */
if (cidr > 128)
return 0; /* NOMATCH: invalid CIDR */
return comp_with_mask(clientip, maskip, cidr);
} else
{
/* Host is not IPv6 and does not contain wildcards.
* So could be a literal IPv4 address or IPv4 CIDR.
* NOTE: could also be neither (like a real hostname), so don't return 0 on nomatch,
* in that case we should just continue...
* The exception is CIDR. If we have CIDR mask then don't bother checking for
* virtual hosts and things like that since '/' can never be in a hostname.
*/
if (acptr->ip && inet_pton4(acptr->ip, clientip) && inet_pton4(hmask, maskip))
{
if (cidr < 0)
{
if (comp_with_mask(clientip, maskip, 32))
return 1; /* MATCH: exact IP */
}
else if (cidr > 32)
return 0; /* NOMATCH: invalid CIDR */
else
return comp_with_mask(clientip, maskip, cidr); /* MATCH/NOMATCH by CIDR */
}
}
}
/**** Check real host ****/
if (options & MATCH_CHECK_REAL_HOST)
{
char *hostname = acptr->user ? acptr->user->realhost : (MyClient(acptr) ? acptr->local->sockhost : NULL);
if (hostname && match_simple(hmask, hostname))
return 1; /* MATCH: hostname match */
}
return 0; /* NOMATCH: nothing of the above matched */
}