mirror of
https://github.com/pissnet/pissircd.git
synced 2025-07-28 14:02:27 +01:00
Add spamfilter hits and hits for exempted users.
* This means we always run spamfilters, even if users are exempts * This way we can gather hits for exempted users on individual spamfilter entries, and possibly detect false positives (which relies on the assumption that those users are innocent) * The hit counters are shown in in RPL_STATSSPAMF and also exposed via the JSON-RCP API. * This commit also adds set::central-spamfilter::except but more on that later since i still want to set a default for that in a future commit. * This also changes take_action() to take flags and adds the option TAKE_ACTION_SIMULATE_USER_ACTION which i intended to use but didn't in the end... not sure if i should keep it :D
This commit is contained in:
parent
0c622c0a73
commit
c18c79e88b
9 changed files with 114 additions and 38 deletions
|
@ -37,6 +37,11 @@ in progress and may not be a stable version.
|
|||
include "some-file-or-url" { restrict-config { name-of-block; name-of-block2; } }
|
||||
```
|
||||
|
||||
### Developers and protocol:
|
||||
* Changes in numeric 229 (RPL_STATSSPAMF): Now includes hits and hits for
|
||||
users that are exempt, two counters right inserted right before the last one.
|
||||
* Several API changes, like `place_host_ban` to `take_action`
|
||||
|
||||
UnrealIRCd 6.1.1.1
|
||||
-------------------
|
||||
This 6.1.1.1 version is an update to 6.1.1: a bug and memory leak was fixed
|
||||
|
|
|
@ -129,6 +129,7 @@ struct Configuration {
|
|||
long central_spamfilter_refresh_time;
|
||||
int central_spamfilter_verbose;
|
||||
int central_spamfilter_enabled;
|
||||
SecurityGroup *central_spamfilter_except;
|
||||
int maxbans;
|
||||
int watch_away_notification;
|
||||
int uhnames;
|
||||
|
|
|
@ -841,7 +841,7 @@ extern MODVAR TKL *(*find_tkline_match_zap)(Client *cptr);
|
|||
extern MODVAR void (*tkl_stats)(Client *cptr, int type, const char *para, int *cnt);
|
||||
extern MODVAR void (*tkl_sync)(Client *client);
|
||||
extern MODVAR void (*cmd_tkl)(Client *client, MessageTag *recv_mtags, int parc, const char *parv[]);
|
||||
extern MODVAR int (*take_action)(Client *client, BanAction *actions, const char *reason, long duration, int skip_set);
|
||||
extern MODVAR int (*take_action)(Client *client, BanAction *actions, const char *reason, long duration, int take_action_flags);
|
||||
extern MODVAR int (*match_spamfilter)(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, TKL **rettk);
|
||||
extern MODVAR int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, const char *cmd);
|
||||
extern MODVAR int (*join_viruschan)(Client *client, TKL *tk, int type);
|
||||
|
|
|
@ -380,7 +380,7 @@
|
|||
#define STR_RPL_STATSNLINE /* 226 */ "n %s %s"
|
||||
#define STR_RPL_STATSVLINE /* 227 */ "v %s %s %s"
|
||||
#define STR_RPL_STATSBANVER /* 228 */ "%s %s"
|
||||
#define STR_RPL_STATSSPAMF /* 229 */ "%c %s %s %s %lld %lld %lld %s %s :%s"
|
||||
#define STR_RPL_STATSSPAMF /* 229 */ "%c %s %s %s %lld %lld %lld %s %s %lld %lld :%s"
|
||||
#define STR_RPL_STATSEXCEPTTKL /* 230 */ "%s %s %lld %lld %s :%s"
|
||||
#define STR_RPL_RULES /* 232 */ ":- %s"
|
||||
#define STR_RPL_STATSLLINE /* 241 */ "%c %s * %s %d %d"
|
||||
|
|
|
@ -1174,6 +1174,7 @@ typedef enum BanActionValue {
|
|||
// do not use 99, it is special in tkl take_action
|
||||
BAN_ACT_SOFT_WARN = 50,
|
||||
BAN_ACT_REPORT = 40,
|
||||
// anything above BAN_ACT_SET will will cause a log message to be emitted
|
||||
BAN_ACT_SET = 30,
|
||||
} BanActionValue;
|
||||
|
||||
|
@ -1198,6 +1199,10 @@ struct BanAction {
|
|||
(x == BAN_ACT_SOFT_DCCBLOCK) || (x == BAN_ACT_SOFT_BLOCK) || \
|
||||
(x == BAN_ACT_SOFT_WARN))
|
||||
|
||||
/** Skip BAN_ACT_SET (eg because you already processed them earlier, like in match_spamfilter) */
|
||||
#define TAKE_ACTION_SKIP_SET 0x1
|
||||
/** Don't ban/kill/block/etc, but do return value as if we did */
|
||||
#define TAKE_ACTION_SIMULATE_USER_ACTION 0x2
|
||||
|
||||
/** Server ban sub-struct of TKL entry (KLINE/GLINE/ZLINE/GZLINE/SHUN) */
|
||||
struct ServerBan {
|
||||
|
@ -1224,6 +1229,8 @@ struct Spamfilter {
|
|||
char *tkl_reason; /**< Reason to use for bans placed by this spamfilter, escaped by unreal_encodespace(). */
|
||||
time_t tkl_duration; /**< Duration of bans placed by this spamfilter */
|
||||
char *id; /**< ID */
|
||||
long long hits; /**< Spamfilter hits (except exempts) */
|
||||
long long hits_except; /**< Spamfilter hits by exempt clients */
|
||||
};
|
||||
|
||||
/** Ban exception sub-struct of TKL entry (ELINE) */
|
||||
|
|
|
@ -74,7 +74,7 @@ TKL *(*find_tkline_match_zap)(Client *client);
|
|||
void (*tkl_stats)(Client *client, int type, const char *para, int *cnt);
|
||||
void (*tkl_sync)(Client *client);
|
||||
void (*cmd_tkl)(Client *client, MessageTag *mtags, int parc, const char *parv[]);
|
||||
int (*take_action)(Client *client, BanAction *action, const char *reason, long duration, int skip_set);
|
||||
int (*take_action)(Client *client, BanAction *action, const char *reason, long duration, int take_action_flags);
|
||||
int (*match_spamfilter)(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, TKL **rettk);
|
||||
int (*match_spamfilter_mtags)(Client *client, MessageTag *mtags, const char *cmd);
|
||||
int (*join_viruschan)(Client *client, TKL *tk, int type);
|
||||
|
|
27
src/conf.c
27
src/conf.c
|
@ -1651,6 +1651,7 @@ void free_iConf(Configuration *i)
|
|||
safe_free(i->sasl_server);
|
||||
safe_free_all_ban_actions(i->handshake_data_flood_ban_action);
|
||||
safe_free(i->central_spamfilter_url);
|
||||
free_security_group(i->central_spamfilter_except);
|
||||
// anti-flood:
|
||||
for (f = i->floodsettings; f; f = f_next)
|
||||
{
|
||||
|
@ -8057,7 +8058,8 @@ int _conf_set(ConfigFile *conf, ConfigEntry *ce)
|
|||
tempiConf.central_spamfilter_verbose = atoi(cepp->value);
|
||||
else if (!strcmp(cepp->name, "enabled"))
|
||||
tempiConf.central_spamfilter_enabled = config_checkval(cepp->value, CFG_YESNO);
|
||||
// TODO: except, with a default of identified users.
|
||||
else if (!strcmp(cepp->name, "except"))
|
||||
conf_match_block(conf, cepp, &tempiConf.central_spamfilter_except);
|
||||
}
|
||||
}
|
||||
else if (!strcmp(cep->name, "default-bantime"))
|
||||
|
@ -9180,6 +9182,14 @@ int _test_set(ConfigFile *conf, ConfigEntry *ce)
|
|||
{
|
||||
for (cepp = cep->items; cepp; cepp = cepp->next)
|
||||
{
|
||||
if (!strcmp(cepp->name, "except"))
|
||||
{
|
||||
test_match_block(conf, cepp, &errors);
|
||||
} else
|
||||
if (!cepp->value)
|
||||
{
|
||||
CheckNull(cepp);
|
||||
} else
|
||||
if (!strcmp(cepp->name, "url"))
|
||||
{
|
||||
} else
|
||||
|
@ -9194,11 +9204,20 @@ int _test_set(ConfigFile *conf, ConfigEntry *ce)
|
|||
errors++;
|
||||
}
|
||||
#endif
|
||||
}
|
||||
else if (!strcmp(cepp->name, "verbose"))
|
||||
} else
|
||||
if (!strcmp(cepp->name, "verbose"))
|
||||
{
|
||||
} else
|
||||
if (!strcmp(cepp->name, "enabled"))
|
||||
{
|
||||
} else
|
||||
{
|
||||
config_error_unknown(cepp->file->filename,
|
||||
cepp->line_number, "set::central-spamfilter",
|
||||
cepp->name);
|
||||
errors++;
|
||||
continue;
|
||||
}
|
||||
// TODO: except
|
||||
}
|
||||
}
|
||||
else if (!strcmp(cep->name, "default-bantime"))
|
||||
|
|
|
@ -581,5 +581,7 @@ void json_expand_tkl(json_t *root, const char *key, TKL *tkl, int detail)
|
|||
json_object_set_new(j, "ban_duration_string", json_string_unreal(pretty_time_val_r(buf, sizeof(buf), tkl->ptr.spamfilter->tkl_duration)));
|
||||
json_object_set_new(j, "spamfilter_targets", json_string_unreal(spamfilter_target_inttostring(tkl->ptr.spamfilter->target)));
|
||||
json_object_set_new(j, "reason", json_string_unreal(unreal_decodespace(tkl->ptr.spamfilter->tkl_reason)));
|
||||
json_object_set_new(j, "hits", json_integer(tkl->ptr.spamfilter->hits));
|
||||
json_object_set_new(j, "hits_except", json_integer(tkl->ptr.spamfilter->hits_except));
|
||||
}
|
||||
}
|
||||
|
|
|
@ -90,7 +90,7 @@ TKL *_find_tkline_match_zap(Client *client);
|
|||
void _tkl_stats(Client *client, int type, const char *para, int *cnt);
|
||||
void _tkl_sync(Client *client);
|
||||
CMD_FUNC(_cmd_tkl);
|
||||
int _take_action(Client *client, BanAction *action, char *reason, long duration, int skip_set);
|
||||
int _take_action(Client *client, BanAction *action, char *reason, long duration, int take_action_flags);
|
||||
int _match_spamfilter(Client *client, const char *str_in, int type, const char *cmd, const char *target, int flags, TKL **rettk);
|
||||
int _match_spamfilter_mtags(Client *client, MessageTag *mtags, char *cmd);
|
||||
int check_mtag_spamfilters_present(void);
|
||||
|
@ -3762,6 +3762,8 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
|
|||
(long long)tkl->ptr.spamfilter->tkl_duration,
|
||||
tkl->ptr.spamfilter->tkl_reason,
|
||||
tkl->set_by,
|
||||
tkl->ptr.spamfilter->hits,
|
||||
tkl->ptr.spamfilter->hits_except,
|
||||
tkl->ptr.spamfilter->match->str);
|
||||
if (para && !strcasecmp(para, "del"))
|
||||
{
|
||||
|
@ -4771,11 +4773,11 @@ void ban_action_run_all_sets(Client *client, BanAction *action)
|
|||
|
||||
/** Take an action on the user, such as banning or killing.
|
||||
* @author Bram Matthys (Syzop), 2003-present
|
||||
* @param client 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.
|
||||
* @param skip_set Skip BAN_ACT_SET (eg because you already processed them earlier, like in match_spamfilter)
|
||||
* @param client 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.
|
||||
* @param take_action_flags One of TAKE_ACTION_*
|
||||
* @note This function assumes that client is a locally connected user.
|
||||
* @retval 0 user is exempt or no action needs to be taken for other reasons (eg only var setting)
|
||||
* @retval BAN_ACT_* the highest BAN_ACT_xxx value, eg BAN_ACT_BLOCK or BAN_ACT_GLINE, etc...
|
||||
|
@ -4783,7 +4785,7 @@ void ban_action_run_all_sets(Client *client, BanAction *action)
|
|||
* @note Be sure to check IsDead(client) if return value is 1 and you are
|
||||
* considering to continue processing.
|
||||
*/
|
||||
int _take_action(Client *client, BanAction *actions, char *reason, long duration, int skip_set)
|
||||
int _take_action(Client *client, BanAction *actions, char *reason, long duration, int take_action_flags)
|
||||
{
|
||||
BanAction *action;
|
||||
int previous_highest = 0;
|
||||
|
@ -4823,6 +4825,8 @@ int _take_action(Client *client, BanAction *actions, char *reason, long duration
|
|||
NULL /*8 reason */
|
||||
};
|
||||
|
||||
if (take_action_flags & TAKE_ACTION_SIMULATE_USER_ACTION)
|
||||
break;
|
||||
ban_target_to_tkl_layer(iConf.automatic_ban_target, action->action, client, &tkllayer[3], &tkllayer[4]);
|
||||
|
||||
/* For soft bans we need to prefix the % in the username */
|
||||
|
@ -4869,11 +4873,15 @@ int _take_action(Client *client, BanAction *actions, char *reason, long duration
|
|||
}
|
||||
case BAN_ACT_SOFT_KILL:
|
||||
case BAN_ACT_KILL:
|
||||
if (take_action_flags & TAKE_ACTION_SIMULATE_USER_ACTION)
|
||||
break;
|
||||
RunHookReturnInt(HOOKTYPE_TAKE_ACTION, !=99, client, action->action, reason, duration);
|
||||
exit_client(client, NULL, reason);
|
||||
break;
|
||||
case BAN_ACT_SOFT_TEMPSHUN:
|
||||
case BAN_ACT_TEMPSHUN:
|
||||
if (take_action_flags & TAKE_ACTION_SIMULATE_USER_ACTION)
|
||||
break;
|
||||
/* We simply mark this connection as shunned and do not add a ban record */
|
||||
unreal_log(ULOG_INFO, "tkl", "TKL_ADD_TEMPSHUN", &me,
|
||||
"Temporary shun added on user $target.details [reason: $shun_reason] [by: $client]",
|
||||
|
@ -4882,10 +4890,12 @@ int _take_action(Client *client, BanAction *actions, char *reason, long duration
|
|||
SetShunned(client);
|
||||
break;
|
||||
case BAN_ACT_REPORT:
|
||||
if (take_action_flags & TAKE_ACTION_SIMULATE_USER_ACTION)
|
||||
break;
|
||||
spamreport(client, client->ip, NULL, action->var);
|
||||
break;
|
||||
case BAN_ACT_SET:
|
||||
if (!skip_set)
|
||||
if (!(take_action_flags & TAKE_ACTION_SKIP_SET))
|
||||
ban_act_set(client, action);
|
||||
break;
|
||||
default:
|
||||
|
@ -5007,6 +5017,15 @@ int _join_viruschan(Client *client, TKL *tkl, int type)
|
|||
return 1;
|
||||
}
|
||||
|
||||
int match_spamfilter_exempt(TKL *tkl, char user_is_exempt_general, char user_is_exempt_central)
|
||||
{
|
||||
if (user_is_exempt_general)
|
||||
return 1;
|
||||
if ((tkl->flags & TKL_FLAG_CENTRAL_SPAMFILTER) && user_is_exempt_central)
|
||||
return 1;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/** match_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_*)
|
||||
|
@ -5030,6 +5049,8 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
|
|||
long ms_past;
|
||||
#endif
|
||||
int tags_serial = client->local ? client->local->tags_serial : 0;
|
||||
char user_is_exempt_general = 0;
|
||||
char user_is_exempt_central = 0;
|
||||
|
||||
if (rettkl)
|
||||
*rettkl = NULL; /* initialize to NULL */
|
||||
|
@ -5052,7 +5073,10 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
|
|||
* Let's check that early: going through elines is likely faster than running the regex(es).
|
||||
*/
|
||||
if (find_tkl_exception(TKL_SPAMF, client))
|
||||
return 0;
|
||||
user_is_exempt_general = 1;
|
||||
|
||||
if (user_allowed_by_security_group(client, iConf.central_spamfilter_except))
|
||||
user_is_exempt_central = 1;
|
||||
|
||||
for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next)
|
||||
{
|
||||
|
@ -5127,17 +5151,25 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
|
|||
if (!winner_tkl && destination && target_is_spamexcept(destination))
|
||||
return 0; /* No problem! */
|
||||
|
||||
// TODO: if (only_actions_of_type(tkl->ptr.spamfilter->action, BAN_ACT_SET)) then don't show the warning unless debugging or something :)
|
||||
|
||||
unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
|
||||
"[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
|
||||
log_data_tkl("tkl", tkl),
|
||||
log_data_string("command", cmd),
|
||||
log_data_string("_space", destination ? " " : ""),
|
||||
log_data_string("destination", destination ? destination : ""),
|
||||
log_data_string("str", str));
|
||||
if (match_spamfilter_exempt(tkl, user_is_exempt_general, user_is_exempt_central))
|
||||
{
|
||||
tkl->ptr.spamfilter->hits_except++;
|
||||
} else
|
||||
{
|
||||
tkl->ptr.spamfilter->hits++;
|
||||
if (highest_spamfilter_action(tkl->ptr.spamfilter->action) > BAN_ACT_SET)
|
||||
{
|
||||
unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
|
||||
"[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
|
||||
log_data_tkl("tkl", tkl),
|
||||
log_data_string("command", cmd),
|
||||
log_data_string("_space", destination ? " " : ""),
|
||||
log_data_string("destination", destination ? destination : ""),
|
||||
log_data_string("str", str));
|
||||
|
||||
RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, str, str_in, target, destination, tkl);
|
||||
RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, str, str_in, target, destination, tkl);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run any SET actions */
|
||||
ban_action_run_all_sets(client, tkl->ptr.spamfilter->action);
|
||||
|
@ -5195,17 +5227,25 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
|
|||
if (!winner_tkl && destination && target_is_spamexcept(destination))
|
||||
return 0; /* No problem! */
|
||||
|
||||
// TODO: if (only_actions_of_type(tkl->ptr.spamfilter->action, BAN_ACT_SET)) then don't show the warning unless debugging or something :)
|
||||
|
||||
unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
|
||||
"[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
|
||||
log_data_tkl("tkl", tkl),
|
||||
log_data_string("command", cmd),
|
||||
log_data_string("_space", destination ? " " : ""),
|
||||
log_data_string("destination", destination ? destination : ""),
|
||||
log_data_string("str", str));
|
||||
if (match_spamfilter_exempt(tkl, user_is_exempt_general, user_is_exempt_central))
|
||||
{
|
||||
tkl->ptr.spamfilter->hits_except++;
|
||||
} else
|
||||
{
|
||||
tkl->ptr.spamfilter->hits++;
|
||||
if (highest_spamfilter_action(tkl->ptr.spamfilter->action) > BAN_ACT_SET)
|
||||
{
|
||||
unreal_log(ULOG_INFO, "tkl", "SPAMFILTER_MATCH", client,
|
||||
"[Spamfilter] $client.details matches filter '$tkl': [cmd: $command$_space$destination: '$str'] [reason: $tkl.reason] [action: $tkl.ban_action]",
|
||||
log_data_tkl("tkl", tkl),
|
||||
log_data_string("command", cmd),
|
||||
log_data_string("_space", destination ? " " : ""),
|
||||
log_data_string("destination", destination ? destination : ""),
|
||||
log_data_string("str", str));
|
||||
|
||||
RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, str, str_in, target, destination, tkl);
|
||||
RunHook(HOOKTYPE_LOCAL_SPAMFILTER, client, str, str_in, target, destination, tkl);
|
||||
}
|
||||
}
|
||||
|
||||
/* Run any SET actions */
|
||||
ban_action_run_all_sets(client, tkl->ptr.spamfilter->action);
|
||||
|
@ -5225,10 +5265,12 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
|
|||
if (!tkl)
|
||||
return 0; /* NOMATCH, we are done */
|
||||
|
||||
/* Spamfilter matched, take action: */
|
||||
if (match_spamfilter_exempt(tkl, user_is_exempt_general, user_is_exempt_central))
|
||||
return 0;
|
||||
|
||||
/* Spamfilter matched */
|
||||
reason = unreal_decodespace(tkl->ptr.spamfilter->tkl_reason);
|
||||
ret = take_action(client, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration, 1);
|
||||
ret = take_action(client, tkl->ptr.spamfilter->action, reason, tkl->ptr.spamfilter->tkl_duration, TAKE_ACTION_SKIP_SET);
|
||||
if (!IsDead(client))
|
||||
{
|
||||
if ((ret == BAN_ACT_BLOCK) || (ret == BAN_ACT_SOFT_BLOCK))
|
||||
|
|
Loading…
Add table
Add a link
Reference in a new issue