From e03a5dfd5ffe47fe186165b8b94c7cbc935a10bb Mon Sep 17 00:00:00 2001 From: Bram Matthys Date: Sat, 6 Jul 2024 09:03:49 +0200 Subject: [PATCH] Support ::destination and ::exclude-destination in security groups / mask items at selected places (there needs to be explicit code in place to handle this). At the moment it is supported at two places only: * For spamfilters (was already possible via crules via ::rule with a destination('xyz') but now non-crule destination "#xyz"; works as well, eg: spamfilter { ... except { destination "#main"; } } Note that if you want to exempt a destination in all spamfilters, we already have set::spamfilter::except for that! * In restrict commands for like channel-message and such: set { restrict-commands { channel-message { except { connect-time 600; destination "#test"; } } } } Allow passing a crule_context via user_allowed_by_security_group_context() and make user_allowed_by_security_group() call that. Actually document spamfilter::except online in the docs (yeah you won't see it in this commit, just mentioning...) And yeah, by now i wonder if we should really call it crule_context since it is more like a security group matching context, but.. whatever. --- include/h.h | 1 + include/struct.h | 2 ++ src/modules/restrict-commands.c | 42 ++++++++++++++++++++---------- src/modules/tkl.c | 16 ++++++------ src/securitygroup.c | 46 +++++++++++++++++++++++---------- 5 files changed, 71 insertions(+), 36 deletions(-) diff --git a/include/h.h b/include/h.h index 7cfd7917e..4f27a42da 100644 --- a/include/h.h +++ b/include/h.h @@ -1306,6 +1306,7 @@ extern void free_security_group(SecurityGroup *s); extern SecurityGroup *duplicate_security_group(SecurityGroup *s); extern void set_security_group_defaults(void); extern int user_allowed_by_security_group(Client *client, SecurityGroup *s); +extern int user_allowed_by_security_group_context(Client *client, SecurityGroup *s, crule_context *context); extern int user_allowed_by_security_group_name(Client *client, const char *secgroupname); extern const char *get_security_groups(Client *client); extern int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors); diff --git a/include/struct.h b/include/struct.h index 8a86b58f4..10d2b4f0d 100644 --- a/include/struct.h +++ b/include/struct.h @@ -2220,6 +2220,7 @@ struct SecurityGroup { NameList *security_group; char *prettyrule; /* ::rule as a string */ CRuleNode *rule; /**< parsed crule */ + NameList *destination; NameValuePrioList *extended; /* Exclude */ int exclude_identified; @@ -2233,6 +2234,7 @@ struct SecurityGroup { NameList *exclude_security_group; char *exclude_prettyrule; /* ::exclude-rule as a string */ CRuleNode *exclude_rule; /**< parsed crule */ + NameList *exclude_destination; NameValuePrioList *exclude_extended; /* Settings */ DynamicSetBlock settings; diff --git a/src/modules/restrict-commands.c b/src/modules/restrict-commands.c index cb8c26c1b..61b86c953 100644 --- a/src/modules/restrict-commands.c +++ b/src/modules/restrict-commands.c @@ -49,8 +49,8 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type); int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype); int rcmd_can_send_to_user(Client *client, Client *target, const char **text, const char **errmsg, SendType sendtype); int rcmd_can_join(Client *client, Channel *channel, const char *key, char **errmsg); -int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag); -int rcmd_block_join(Client *client, const char **errmsg); +int rcmd_block_message(Client *client, const char *destination, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag); +int rcmd_block_join(Client *client, Channel *channel, const char **errmsg); CMD_OVERRIDE_FUNC(rcmd_override); // Globals @@ -298,7 +298,7 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type) rcmd->except->identified = config_checkval(cep2->value, CFG_YESNO); continue; } - + if (!strcmp(cep2->name, "exempt-webirc")) { rcmd->except->webirc = config_checkval(cep2->value, CFG_YESNO); @@ -323,18 +323,18 @@ int rcmd_configrun(ConfigFile *cf, ConfigEntry *ce, int type) return 1; } -int rcmd_canbypass(Client *client, RestrictedCommand *rcmd) +int rcmd_canbypass(Client *client, RestrictedCommand *rcmd, crule_context *context) { if (!client || !rcmd) return 1; - if (user_allowed_by_security_group(client, rcmd->except)) + if (user_allowed_by_security_group_context(client, rcmd->except, context)) return 1; return 0; } int rcmd_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype) { - if (rcmd_block_message(client, *msg, sendtype, errmsg, "channel", (sendtype == SEND_TYPE_NOTICE ? "channel-notice" : "channel-message"))) + if (rcmd_block_message(client, channel->name, *msg, sendtype, errmsg, "channel", (sendtype == SEND_TYPE_NOTICE ? "channel-notice" : "channel-message"))) return HOOK_DENY; return HOOK_CONTINUE; @@ -346,14 +346,15 @@ int rcmd_can_send_to_user(Client *client, Client *target, const char **text, con if ((client == target) || IsULine(target)) return HOOK_CONTINUE; /* bypass/exempt */ - if (rcmd_block_message(client, *text, sendtype, errmsg, "user", (sendtype == SEND_TYPE_NOTICE ? "private-notice" : "private-message"))) + if (rcmd_block_message(client, target->name, *text, sendtype, errmsg, "user", (sendtype == SEND_TYPE_NOTICE ? "private-notice" : "private-message"))) return HOOK_DENY; return HOOK_CONTINUE; } -int rcmd_block_message(Client *client, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag) +int rcmd_block_message(Client *client, const char *destination, const char *text, SendType sendtype, const char **errmsg, const char *display, const char *conftag) { + crule_context context; RestrictedCommand *rcmd; static char errbuf[256]; @@ -361,8 +362,12 @@ int rcmd_block_message(Client *client, const char *text, SendType sendtype, cons if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client)) return 0; + memset(&context, 0, sizeof(context)); + context.client = client; + context.destination = destination; + rcmd = find_restrictions_byconftag(conftag); - if (rcmd && !rcmd_canbypass(client, rcmd)) + if (rcmd && !rcmd_canbypass(client, rcmd, &context)) { int notice = (sendtype == SEND_TYPE_NOTICE ? 1 : 0); // temporary hack FIXME !!! if (rcmd->except->connect_time) @@ -386,6 +391,7 @@ int rcmd_block_message(Client *client, const char *text, SendType sendtype, cons CMD_OVERRIDE_FUNC(rcmd_override) { RestrictedCommand *rcmd; + crule_context context; if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client)) { @@ -393,8 +399,11 @@ CMD_OVERRIDE_FUNC(rcmd_override) return; } + memset(&context, 0, sizeof(context)); + context.client = client; + rcmd = find_restrictions_bycmd(ovr->command->cmd); - if (rcmd && !rcmd_canbypass(client, rcmd)) + if (rcmd && !rcmd_canbypass(client, rcmd, &context)) { if (rcmd->except->connect_time) { @@ -422,27 +431,32 @@ int rcmd_can_join(Client *client, Channel *channel, const char *key, char **errm if (channel->users || has_channel_mode(channel, 'P')) return 0; - else if (rcmd_block_join(client, &join_err)) + else if (rcmd_block_join(client, channel, &join_err)) { static char formatted_errmsg[512]; snprintf(formatted_errmsg, sizeof(formatted_errmsg), "JOIN :%s", join_err); - + *errmsg = formatted_errmsg; return ERR_CANNOTDOCOMMAND; } return 0; } -int rcmd_block_join(Client *client, const char **errmsg) +int rcmd_block_join(Client *client, Channel *channel, const char **errmsg) { RestrictedCommand *rcmd; + crule_context context; static char errbuf[256]; if (!MyUser(client) || !client->local || IsOper(client) || IsULine(client)) return 0; + memset(&context, 0, sizeof(context)); + context.client = client; + context.destination = channel->name; + rcmd = find_restrictions_byconftag("channel-create"); - if (rcmd && !rcmd_canbypass(client, rcmd)) + if (rcmd && !rcmd_canbypass(client, rcmd, &context)) { if (rcmd->except->connect_time) { diff --git a/src/modules/tkl.c b/src/modules/tkl.c index e0e5bbce3..cf2b5a1c1 100644 --- a/src/modules/tkl.c +++ b/src/modules/tkl.c @@ -5362,6 +5362,7 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char int stop_processing_general_spamfilters = 0; int stop_processing_central_spamfilters = 0; int content_revealed = 0; + crule_context context; if (rettkl) *rettkl = NULL; /* initialize to NULL */ @@ -5380,13 +5381,18 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char if (!client->user || ValidatePermissionsForPath("immune:server-ban:spamfilter",client,NULL,NULL,NULL) || IsULine(client)) return 0; + memset(&context, 0, sizeof(context)); + context.client = client; + context.text = str_in; + context.destination = destination; + /* Client exempt from spamfilter checking? * Let's check that early: going through elines is likely faster than running the regex(es). */ if (find_tkl_exception(TKL_SPAMF, client)) user_is_exempt_general = 1; - if (user_allowed_by_security_group(client, iConf.central_spamfilter_except)) + if (user_allowed_by_security_group_context(client, iConf.central_spamfilter_except, &context)) user_is_exempt_central = 1; for (tkl = tklines[tkl_hash('F')]; tkl; tkl = tkl->next) @@ -5420,22 +5426,16 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char /* Run any pre 'rule' if there is any (false means 'no hit') */ if (tkl->ptr.spamfilter->rule) { - crule_context context; - memset(&context, 0, sizeof(context)); - context.client = client; - context.text = str_in; - context.destination = destination; if (!crule_eval(&context, tkl->ptr.spamfilter->rule)) 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)) + if (tkl->ptr.spamfilter->except && user_allowed_by_security_group_context(client, tkl->ptr.spamfilter->except, &context)) 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 ? #ifdef SPAMFILTER_DETECTSLOW if (tkl->ptr.spamfilter->match->type == MATCH_PCRE_REGEX) { diff --git a/src/securitygroup.c b/src/securitygroup.c index 7d12f4758..7cf50ca8b 100644 --- a/src/securitygroup.c +++ b/src/securitygroup.c @@ -249,6 +249,9 @@ int test_match_item(ConfigFile *conf, ConfigEntry *cep, int *errors) if (!strcmp(cep->name, "security-group") || !strcmp(cep->name, "exclude-security-group")) { } else + if (!strcmp(cep->name, "destination") || !strcmp(cep->name, "exclude-destination")) + { + } else if (!strcmp(cep->name, "rule") || !strcmp(cep->name, "exclude-rule")) { int val = crule_test(cep->value); @@ -435,6 +438,10 @@ int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block) safe_strdup(s->prettyrule, cep->value); s->rule = crule_parse(s->prettyrule); } + else if (!strcmp(cep->name, "destination")) + { + unreal_add_names(&s->destination, cep); + } else if (!strcmp(cep->name, "exclude-webirc")) s->exclude_webirc = config_checkval(cep->value, CFG_YESNO); else if (!strcmp(cep->name, "exclude-websocket")) @@ -463,6 +470,10 @@ int conf_match_item(ConfigFile *conf, ConfigEntry *cep, SecurityGroup **block) safe_strdup(s->exclude_prettyrule, cep->value); s->exclude_rule = crule_parse(s->exclude_prettyrule); } + else if (!strcmp(cep->name, "exclude-destination")) + { + unreal_add_names(&s->exclude_destination, cep); + } else { /* Let's see if an extended server ban exists for this item... this needs to be LAST! */ @@ -621,6 +632,8 @@ void free_security_group(SecurityGroup *s) unreal_delete_masks(s->exclude_mask); free_entire_name_list(s->security_group); free_entire_name_list(s->exclude_security_group); + free_entire_name_list(s->destination); + free_entire_name_list(s->exclude_destination); safe_crule_free(s->rule); safe_crule_free(s->exclude_rule); safe_free(s->prettyrule); @@ -647,6 +660,8 @@ SecurityGroup *duplicate_security_group(SecurityGroup *s) n->exclude_mask = unreal_duplicate_masks(s->exclude_mask); n->security_group = duplicate_name_list(s->security_group); n->exclude_security_group = duplicate_name_list(s->exclude_security_group); + n->destination = duplicate_name_list(s->destination); + n->exclude_destination = duplicate_name_list(s->exclude_destination); if (s->prettyrule) { safe_strdup(n->prettyrule, s->prettyrule); @@ -786,23 +801,12 @@ int user_allowed_by_security_group_list(Client *client, NameList *l) return 0; } -/** Helper for security-group::rule and mask::rule */ -int user_allowed_by_rule(Client *client, CRuleNode *rule) -{ - crule_context context; - - memset(&context, 0, sizeof(context)); - context.client = client; - - return crule_eval(&context, rule); -} - /** Returns 1 if the user is OK as far as the security-group is concerned. * @param client The client to check * @param s The security-group to check against * @retval 1 if user is allowed by security-group, 0 if not. */ -int user_allowed_by_security_group(Client *client, SecurityGroup *s) +int user_allowed_by_security_group_context(Client *client, SecurityGroup *s, crule_context *context) { static int recursion_security_group = 0; @@ -850,12 +854,14 @@ int user_allowed_by_security_group(Client *client, SecurityGroup *s) goto user_not_allowed; if (s->exclude_ip && unreal_match_iplist(client, s->exclude_ip)) goto user_not_allowed; - if (s->exclude_rule && user_allowed_by_rule(client, s->exclude_rule)) + if (s->exclude_rule && crule_eval(context, s->exclude_rule)) goto user_not_allowed; if (s->exclude_extended && user_matches_extended_list(client, s->exclude_extended)) goto user_not_allowed; if (s->exclude_security_group && user_allowed_by_security_group_list(client, s->exclude_security_group)) goto user_not_allowed; + if (s->exclude_destination && context->destination && find_name_list_match(s->exclude_destination, context->destination)) + goto user_not_allowed; /* Then process INCLUSION criteria... */ if (s->identified && IsLoggedIn(client)) @@ -897,12 +903,14 @@ int user_allowed_by_security_group(Client *client, SecurityGroup *s) goto user_allowed; if (s->ip && unreal_match_iplist(client, s->ip)) goto user_allowed; - if (s->rule && user_allowed_by_rule(client, s->rule)) + if (s->rule && crule_eval(context, s->rule)) goto user_allowed; if (s->extended && user_matches_extended_list(client, s->extended)) goto user_allowed; if (s->security_group && user_allowed_by_security_group_list(client, s->security_group)) goto user_allowed; + if (s->destination && context->destination && find_name_list_match(s->destination, context->destination)) + goto user_allowed; user_not_allowed: recursion_security_group--; @@ -913,6 +921,16 @@ user_allowed: return 1; } +int user_allowed_by_security_group(Client *client, SecurityGroup *s) +{ + crule_context context; + + memset(&context, 0, sizeof(context)); + context.client = client; + + return user_allowed_by_security_group_context(client, s, &context); +} + /** Returns 1 if the user is OK as far as the security-group is concerned - "by name" version. * @param client The client to check * @param secgroupname The name of the security-group to check against