Merge branch 'unrealircd:unreal60_dev' into piss60

This commit is contained in:
angryce 2023-07-10 19:56:40 +02:00 committed by GitHub
commit fa73b6217a
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
18 changed files with 759 additions and 631 deletions

View file

@ -5,10 +5,22 @@ in progress and may not be a stable version.
### Enhancements:
* [spamfilter { } block](https://www.unrealircd.org/docs/Spamfilter_block) improvements:
* The `action` item now supports multiple actions
* A new action is setting a TAG on a user, or increasing the value of a TAG
* A new option `rule` with minimal 'if'-like preconditions and functions
* A new option `report` to call a spamreport block, see next.
* Spamfilters now always run, even for users that are exempt via a
[except ban block](https://www.unrealircd.org/docs/Except_ban_block)
with `type spamfilter`. However, for exempt users no action is taken
or logged. This allows us to count hits and hits for except users.
The idea is that the hits for except users can be a useful measurement
to detect false positives. These hitcounts are exposed in `SPAMFILTER`
and `STATS spamfilter`.
* Optional items allowing more complex rules:
* spamfilter::rule: with minimal 'if'-like preconditions and functions.
If this returns false then the spamfilter will not run at all (no hit).
* spamfilter::except: this is meant as an alternative to 'rule' and
works like a regular [except item](https://www.unrealircd.org/docs/Mask_item).
If this matches, then the spamfilter will not run at all (no hit).
* The `action` item now supports multiple actions:
* A new action `set` to set a TAG on a user, or increasing the value of one
* A new action `report` to call a spamreport block, see next.
* A new [spamreport { } block](https://www.unrealircd.org/docs/Spamreport_block):
* This can do a HTTP(S) call to services like DroneBL to report spam hits,
so they can blacklist the IP address and other users on IRC can benefit.
@ -37,6 +49,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

View file

@ -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;

View file

@ -739,6 +739,7 @@ extern void free_all_ban_actions(BanAction *actions);
#define safe_free_all_ban_actions(x) do { free_all_ban_actions(x); x = NULL; } while(0)
#define safe_free_single_ban_action(x) do { free_single_ban_action(x); x = NULL; } while(0)
BanAction *duplicate_ban_actions(BanAction *actions);
extern int highest_spamfilter_action(BanAction *action);
extern BanActionValue banact_stringtoval(const char *s);
extern const char *banact_valtostring(BanActionValue val);
extern BanActionValue banact_chartoval(char c);
@ -819,7 +820,8 @@ extern MODVAR TKL *(*tkl_add_banexception)(int type, const char *usermask, const
extern MODVAR TKL *(*tkl_add_nameban)(int type, const char *name, int hold, const char *reason, const char *setby,
time_t expire_at, time_t set_at, int flags);
extern MODVAR TKL *(*tkl_add_spamfilter)(int type, const char *id, unsigned short target, BanAction *action,
Match *match, const char *rule, const char *setby,
Match *match, const char *rule, SecurityGroup *except,
const char *setby,
time_t expire_at, time_t set_at,
time_t spamf_tkl_duration, const char *spamf_tkl_reason,
int flags);
@ -840,7 +842,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);

View file

@ -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"

View file

@ -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,9 @@ 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 */
SecurityGroup *except; /**< Don't run this spamfitler at all for these users (not counting towards hits_except btw) */
};
/** Ban exception sub-struct of TKL entry (ELINE) */

View file

@ -56,7 +56,7 @@ TKL *(*tkl_add_serverban)(int type, const char *usermask, const char *hostmask,
TKL *(*tkl_add_nameban)(int type, const char *name, int hold, const char *reason, const char *setby,
time_t expire_at, time_t set_at, int flags);
TKL *(*tkl_add_spamfilter)(int type, const char *id, unsigned short target, BanAction *action,
Match *match, const char *rule,
Match *match, const char *rule, SecurityGroup *except,
const char *setby,
time_t expire_at, time_t set_at,
time_t spamf_tkl_duration, const char *spamf_tkl_reason,
@ -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);

View file

@ -1240,7 +1240,7 @@ ConfigFile *config_parse_with_offset(const char *filename, char *confdata, unsig
/* fall through */
case '\t':
case ' ':
//case '=': // why would we break on = ??
case '=':
case '\r':
break;
case '@':
@ -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"))
@ -11424,16 +11443,16 @@ int test_dynamic_set_block_item(ConfigFile *conf, const char *security_group, Co
}
else if (!strcmp(cep->name, "restrict-usermodes"))
{
char *p;
CheckNull(cep);
if (cep->name) {
int warn = 0;
char *p;
for (p = cep->value; *p; p++)
if ((*p == '+') || (*p == '-'))
warn = 1;
if (warn) {
for (p = cep->value; *p; p++)
{
if ((*p == '+') || (*p == '-'))
{
config_status("%s:%i: warning: set::restrict-usermodes: should only contain modechars, no + or -.\n",
cep->file->filename, cep->line_number);
break;
}
}
} else

File diff suppressed because it is too large Load diff

View file

@ -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));
}
}

View file

@ -1094,6 +1094,18 @@ const char *ban_actions_to_string(BanAction *actions)
return buf;
}
/* Find the highest value in a BanAction linked list (the strongest action, eg gline>block) */
int highest_spamfilter_action(BanAction *action)
{
int highest = 0;
for (; action; action = action->next)
if (action->action > highest)
highest = action->action;
return highest;
}
void free_single_ban_action(BanAction *action)
{
safe_free(action->var);

View file

@ -692,19 +692,25 @@ CMD_OVERRIDE_FUNC(override_msg)
logbuf[0] = '\0';
lookalikespam_score(text, logbuf, sizeof(logbuf));
unreal_log(ULOG_INFO, "antimixedutf8", "ANTIMIXEDUTF8_HIT", client,
"[antimixedutf8] Client $client.details hit score $score -- taking action. Mixed scripts detected: $scripts",
"[antimixedutf8] Client $client.details hit score $score. Mixed scripts detected: $scripts",
log_data_integer("score", score),
log_data_string("scripts", logbuf));
/* Take the action */
retval = take_action(client, cfg.ban_action, cfg.ban_reason, cfg.ban_time, 0);
if (retval == 1)
return;
if (retval == BAN_ACT_BLOCK)
if ((retval == BAN_ACT_WARN) || (retval == BAN_ACT_SOFT_WARN))
{
/* no action */
} else
if ((retval == BAN_ACT_BLOCK) || (retval == BAN_ACT_SOFT_BLOCK))
{
sendnotice(client, "%s", cfg.ban_reason);
return;
} else if (retval > 0)
{
return;
}
/* fallthrough for retval <=0 */
}
CALL_NEXT_COMMAND_OVERRIDE();

View file

@ -611,7 +611,7 @@ int antirandom_config_test(ConfigFile *cf, ConfigEntry *ce, int type, int *errs)
{
req.threshold = 1;
} else
if (!strcmp(cep->name, "ban-action"))
if (!strcmp(cep->name, "ban-action") || !strcmp(cep->name, "action"))
{
req.ban_action = 1;
errors += test_ban_action_config(cep);
@ -672,7 +672,7 @@ int antirandom_config_run(ConfigFile *cf, ConfigEntry *ce, int type)
{
cfg.threshold = atoi(cep->value);
} else
if (!strcmp(cep->name, "ban-action"))
if (!strcmp(cep->name, "ban-action") || !strcmp(cep->name, "action"))
{
cfg.ban_action = parse_ban_action_config(cep);
} else
@ -866,20 +866,26 @@ int antirandom_preconnect(Client *client)
score = get_spam_score(client);
if (score > cfg.threshold)
{
if (has_actions_of_type(cfg.ban_action, BAN_ACT_WARN))
int n = take_action(client, cfg.ban_action, cfg.ban_reason, cfg.ban_time, 0);
if ((n == BAN_ACT_WARN) || (n == BAN_ACT_SOFT_WARN))
{
unreal_log(ULOG_INFO, "antirandom", "ANTIRANDOM_DENIED_USER", client,
"[antirandom] would have denied access to user with score $score: $client.details:$client.user.realname",
log_data_integer("score", score));
} else
if (cfg.show_failedconnects) // FIXME: this is not entirely correct, with like soft actions or var setting, etc!
if (n <= 0)
{
unreal_log(ULOG_INFO, "antirandom", "ANTIRANDOM_DENIED_USER", client,
"[antirandom] denied access to user with score $score: $client.details:$client.user.realname",
log_data_integer("score", score));
}
if (take_action(client, cfg.ban_action, cfg.ban_reason, cfg.ban_time, 0))
/* No action / exempt */
} else
{
if (cfg.show_failedconnects)
{
unreal_log(ULOG_INFO, "antirandom", "ANTIRANDOM_DENIED_USER", client,
"[antirandom] denied access to user with score $score: $client.details:$client.user.realname",
log_data_integer("score", score));
}
return HOOK_DENY;
}
}
return HOOK_CONTINUE;
}

View file

@ -876,7 +876,7 @@ int blacklist_preconnect(Client *client)
return HOOK_CONTINUE; /* yup, so the softban does not apply. */
if (blacklist_action(client, blu->save_opernotice, blu->save_action, blu->save_reason, blu->save_tkltime,
blu->save_blacklist, blu->save_blacklist_dns_name, blu->save_blacklist_dns_reply))
blu->save_blacklist, blu->save_blacklist_dns_name, blu->save_blacklist_dns_reply) > 0)
{
return HOOK_DENY;
}

View file

@ -247,7 +247,7 @@ RPC_CALL_FUNC(rpc_spamfilter_add)
return;
}
tkl = tkl_add_spamfilter(type, NULL, targets, banact_value_to_struct(action), m, NULL, set_by, 0, TStime(),
tkl = tkl_add_spamfilter(type, NULL, targets, banact_value_to_struct(action), m, NULL, NULL, set_by, 0, TStime(),
ban_duration, reason, 0);
if (!tkl)

View file

@ -868,7 +868,7 @@ int stats_set(Client *client, const char *para)
sendtxtnumeric(client, "link::bind-ip: %s", LINK_BINDIP);
sendtxtnumeric(client, "anti-flood::connect-flood: %d per %s", THROTTLING_COUNT, pretty_time_val(THROTTLING_PERIOD));
sendtxtnumeric(client, "anti-flood::handshake-data-flood::amount: %ld bytes", iConf.handshake_data_flood_amount);
sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-action: %s", banact_valtostring(iConf.handshake_data_flood_ban_action->action));
sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-action: %s", ban_actions_to_string(iConf.handshake_data_flood_ban_action));
sendtxtnumeric(client, "anti-flood::handshake-data-flood::ban-time: %s", pretty_time_val(iConf.handshake_data_flood_ban_time));
/* set::anti-flood */

View file

@ -69,7 +69,7 @@ TKL *_tkl_add_banexception(int type, char *usermask, char *hostmask, SecurityGro
TKL *_tkl_add_nameban(int type, char *name, int hold, char *reason, char *set_by,
time_t expire_at, time_t set_at, int flags);
TKL *_tkl_add_spamfilter(int type, const char *id, unsigned short target, BanAction *action,
Match *match, const char *rule,
Match *match, const char *rule, SecurityGroup *except,
const char *set_by,
time_t expire_at, time_t set_at,
time_t spamf_tkl_duration, const char *spamf_tkl_reason,
@ -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);
@ -435,6 +435,10 @@ int tkl_config_test_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type, int *e
}
has_match_type = 1;
}
else if (!strcmp(cep->name, "except"))
{
test_match_block(cf, cep, &errors);
}
else
{
config_error_unknown(cep->file->filename, cep->line_number,
@ -528,6 +532,7 @@ int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
int match_type = 0;
Match *m = NULL;
int flag = TKL_FLAG_CONFIG;
SecurityGroup *except;
/* We are only interested in spamfilter { } blocks */
if ((type != CONFIG_MAIN) || strcmp(ce->name, "spamfilter"))
@ -577,6 +582,10 @@ int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
{
match_type = unreal_match_method_strtoval(cep->value);
}
else if (!strcmp(cep->name, "except"))
{
conf_match_block(cf, cep, &except);
}
}
if (!match && rule)
@ -590,6 +599,7 @@ int tkl_config_run_spamfilter(ConfigFile *cf, ConfigEntry *ce, int type)
action,
m,
rule,
except,
"-config-",
0,
TStime(),
@ -2642,6 +2652,8 @@ TKL *tkl_find_head(char type, char *hostmask, TKL *def)
* @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 rule Rule, if present, then only run spamfilter if true
* @param except When not to run the spamfilter
* @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
@ -2652,7 +2664,7 @@ TKL *tkl_find_head(char type, char *hostmask, TKL *def)
* such as a regex failing to compile, memory problem, ..
*/
TKL *_tkl_add_spamfilter(int type, const char *id, unsigned short target, BanAction *action,
Match *match, const char *rule,
Match *match, const char *rule, SecurityGroup *except,
const char *set_by,
time_t expire_at, time_t set_at,
time_t tkl_duration, const char *tkl_reason,
@ -2689,6 +2701,7 @@ TKL *_tkl_add_spamfilter(int type, const char *id, unsigned short target, BanAct
tkl->ptr.spamfilter->action = action;
tkl->ptr.spamfilter->match = match;
safe_strdup(tkl->ptr.spamfilter->tkl_reason, tkl_reason);
tkl->ptr.spamfilter->except = except;
tkl->ptr.spamfilter->tkl_duration = tkl_duration;
if (tkl->ptr.spamfilter->target & SPAMF_USER)
@ -3756,12 +3769,14 @@ int tkl_stats_matcher(Client *client, int type, const char *para, TKLFlag *tklfl
(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->action),
ban_actions_to_string(tkl->ptr.spamfilter->action),
(tkl->expire_at != 0) ? (long long)(tkl->expire_at - TStime()) : 0,
(long long)(TStime() - tkl->set_at),
(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"))
{
@ -4183,8 +4198,12 @@ void _tkl_added(Client *client, TKL *tkl)
sendnotice_tkl_add(tkl);
/* spamfilter 'warn' action is special */
if ((tkl->type & TKL_SPAMF) && (tkl->ptr.spamfilter->action->action == BAN_ACT_WARN) && (tkl->ptr.spamfilter->target & SPAMF_USER))
if ((tkl->type & TKL_SPAMF) &&
has_actions_of_type(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;
@ -4429,7 +4448,8 @@ CMD_FUNC(cmd_tkl_add)
log_data_string("spamfilter_regex_error", err));
return;
}
tkl = tkl_add_spamfilter(type, NULL, target, banact_value_to_struct(action), m, NULL, set_by, expire_at, set_at,
tkl = tkl_add_spamfilter(type, NULL, target, banact_value_to_struct(action), m, NULL, NULL,
set_by, expire_at, set_at,
tkl_duration, tkl_reason, 0);
}
} else
@ -4761,19 +4781,17 @@ void ban_act_set(Client *client, BanAction *action)
void ban_action_run_all_sets(Client *client, BanAction *action)
{
for (; action; action = action->next)
{
if (action->action == BAN_ACT_SET)
ban_act_set(client, 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...
@ -4781,7 +4799,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;
@ -4821,6 +4839,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 */
@ -4867,11 +4887,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]",
@ -4880,10 +4904,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:
@ -4899,18 +4925,6 @@ int _take_action(Client *client, BanAction *actions, char *reason, long duration
return highest;
}
/* Find the highest value in a BanAction linked list (the strongest action, eg gline>block) */
int highest_spamfilter_action(BanAction *action)
{
int highest = 0;
for (; action; action = action->next)
if (action->action > highest)
highest = action->action;
return highest;
}
/** 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
@ -5017,6 +5031,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_*)
@ -5040,6 +5063,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 */
@ -5062,7 +5087,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)
{
@ -5078,7 +5106,7 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
if (IsLoggedIn(client) && only_soft_actions(tkl->ptr.spamfilter->action))
continue;
/* Run any pre 'rule' if there is any */
/* Run any pre 'rule' if there is any (false means 'no hit') */
if (tkl->ptr.spamfilter->rule)
{
crule_context context;
@ -5088,6 +5116,10 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
continue;
}
/* Check any 'except' rule if there is any (true means 'no hit') */
if (tkl->ptr.spamfilter->except && user_allowed_by_security_group(client, tkl->ptr.spamfilter->except))
continue;
if (tkl->ptr.spamfilter->match && (tkl->ptr.spamfilter->match->type != MATCH_NONE))
{
// TODO: wait, why are we running slow spamfilter detection for simple (non-regex) too ?
@ -5137,17 +5169,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);
@ -5205,17 +5245,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);
@ -5235,10 +5283,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))

View file

@ -724,6 +724,7 @@ int read_tkldb(void)
tkl->ptr.spamfilter->action,
tkl->ptr.spamfilter->match,
NULL,
NULL,
tkl->set_by, tkl->expire_at, tkl->set_at,
tkl->ptr.spamfilter->tkl_duration,
tkl->ptr.spamfilter->tkl_reason,

View file

@ -82,7 +82,7 @@ SSL_CTX *https_new_ctx(void);
void unreal_https_connect_handshake(int fd, int revents, void *data);
int https_connect(Download *handle);
int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle);
void https_connect_send_header(Download *handle);
int https_connect_send_header(Download *handle);
void https_receive_response(int fd, int revents, void *data);
int https_handle_response_header(Download *handle, char *readbuf, int n);
int https_handle_response_body(Download *handle, char *readbuf, int n);
@ -91,7 +91,7 @@ void https_done_cached(Download *handle);
void https_redirect(Download *handle);
int https_parse_header(char *buffer, int len, char **key, char **value, char **lastloc, int *end_of_request);
char *url_find_end_of_request(char *header, int totalsize, int *remaining_bytes);
void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3)));
int https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...) __attribute__((format(printf,2,3)));
void url_free_handle(Download *handle)
{
@ -136,7 +136,10 @@ void url_cancel_handle_by_callback_data(void *ptr)
}
}
void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)
/** Cancel and free the HTTPS request.
* @returns Always returns -1
*/
int https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)
{
va_list vl;
va_start(vl, pattern);
@ -145,6 +148,7 @@ void https_cancel(Download *handle, FORMAT_STRING(const char *pattern), ...)
if (handle->callback)
handle->callback(handle->url, NULL, NULL, 0, handle->errorbuf, 0, handle->callback_data);
url_free_handle(handle);
return -1;
}
void download_file_async(const char *url, time_t cachetime, vFP callback, void *callback_data, char *original_url, int maxredirects)
@ -311,11 +315,7 @@ void unreal_https_connect_handshake(int fd, int revents, void *data)
SSL_set_tlsext_host_name(handle->ssl, handle->hostname);
if (https_connect(handle) < 0)
{
/* Some fatal error already */
https_cancel(handle, "TLS_connect() failed early");
return;
}
return; /* fatal error, handle is freed */
/* Is now connecting... */
}
@ -369,7 +369,12 @@ void https_connect_retry(int fd, int revents, void *data)
https_connect(handle);
}
// Based on unreal_tls_connect()
/* Actually do the SSL_connect()
* Based on unreal_tls_connect() but different return values.
* @retval 1 connected
* @retval 0 in progress
* @retval -1 error, handle freed!
*/
int https_connect(Download *handle)
{
int ssl_err;
@ -399,21 +404,18 @@ int https_connect(Download *handle)
fd_setselect(handle->fd, FD_SELECT_WRITE, https_connect_retry, handle);
return 0;
default:
return https_fatal_tls_error(ssl_err, ERRNO, handle);
return https_fatal_tls_error(ssl_err, ERRNO, handle); /* -1 */
}
/* NOTREACHED */
return -1;
return 0;
}
/* We are connected now. */
if (!verify_certificate(handle->ssl, handle->hostname, &errstr))
{
https_cancel(handle, "TLS Certificate error for server: %s", errstr);
return -1;
}
https_connect_send_header(handle);
return 1;
return https_cancel(handle, "TLS Certificate error for server: %s", errstr); /* -1 */
return https_connect_send_header(handle);
}
/**
@ -423,6 +425,7 @@ int https_connect(Download *handle)
* @param where The location, one of the SAFE_SSL_* defines.
* @param my_errno A preserved value of errno to pass to ssl_error_str().
* @param client The client the error is associated with.
* @returns Always -1
*/
int https_fatal_tls_error(int ssl_error, int my_errno, Download *handle)
{
@ -508,7 +511,7 @@ int url_parse(const char *url, char **hostname, int *port, char **username, char
return 1;
}
void https_connect_send_header(Download *handle)
int https_connect_send_header(Download *handle)
{
char buf[8192];
char hostandport[512];
@ -606,12 +609,10 @@ void https_connect_send_header(Download *handle)
ssl_err = SSL_write(handle->ssl, buf, strlen(buf));
if (ssl_err < 0)
{
https_fatal_tls_error(ssl_err, ERRNO, handle);
return;
}
return https_fatal_tls_error(ssl_err, ERRNO, handle);
fd_setselect(handle->fd, FD_SELECT_WRITE, NULL, handle);
fd_setselect(handle->fd, FD_SELECT_READ, https_receive_response, handle);
return 1;
}
void https_receive_response(int fd, int revents, void *data)