diff --git a/doc/conf/help/help.conf b/doc/conf/help/help.conf index 745d75abe..e0553063c 100644 --- a/doc/conf/help/help.conf +++ b/doc/conf/help/help.conf @@ -403,11 +403,13 @@ help Chmodef { " k Knock +K"; " m Messages +m M"; " n Nickchange +N"; + " p Paste drop m, M"; " t Text kick b, d"; " r Repeat kick d, b"; " -"; " The difference between type m and t is that m is tallied for the entire"; " channel whereas t is tallied per user."; + " Type p counts multiline paste events (3+ lines) for the entire channel."; " If you choose to specify an action for a mode, you may also specify a"; " time (in minutes) after which the specific action will be reversed."; " See also https://www.unrealircd.org/docs/Channel_anti-flood_settings#Channel_mode_f"; diff --git a/include/h.h b/include/h.h index 5f1a2d0da..f6df191ca 100644 --- a/include/h.h +++ b/include/h.h @@ -951,6 +951,7 @@ extern MODVAR void (*send_isupport)(Client *client); extern MODVAR void (*isupport_check_for_changes)(void); extern MODVAR int (*get_connections_from_ip)(Client *client); extern MODVAR int (*get_floodprot_channel_max_lines)(Channel *channel); +extern MODVAR int (*floodprot_check_multiline_batch)(Channel *channel, Client *client, int line_count); /* /Efuncs */ /* TLS functions */ @@ -991,6 +992,7 @@ extern int del_silence_default_handler(Client *client, const char *mask); extern int is_silenced_default_handler(Client *client, Client *acptr); extern int get_connections_from_ip_default_handler(Client *client); extern int get_floodprot_channel_max_lines_default_handler(Channel *channel); +extern int floodprot_check_multiline_batch_default_handler(Channel *channel, Client *client, int line_count); extern void do_unreal_log_remote_deliver_default_handler(LogLevel loglevel, const char *subsystem, const char *event_id, MultiLine *msg, const char *json_serialized); extern int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost, const char *autojoin_channels); extern void webserver_send_response_default_handler(Client *client, int status, char *msg); diff --git a/include/modules.h b/include/modules.h index bf5c0d67b..12066935a 100644 --- a/include/modules.h +++ b/include/modules.h @@ -2806,6 +2806,7 @@ enum EfunctionType { EFUNC_ISUPPORT_CHECK_FOR_CHANGES, EFUNC_GET_CONNECTIONS_FROM_IP, EFUNC_GET_FLOODPROT_CHANNEL_MAX_LINES, + EFUNC_FLOODPROT_CHECK_MULTILINE_BATCH, }; /* Module flags */ diff --git a/src/api-efunctions.c b/src/api-efunctions.c index 17ac0d42f..e85d00bd2 100644 --- a/src/api-efunctions.c +++ b/src/api-efunctions.c @@ -193,6 +193,7 @@ void (*send_isupport)(Client *client); void (*isupport_check_for_changes)(void); int (*get_connections_from_ip)(Client *client); int (*get_floodprot_channel_max_lines)(Channel *channel); +int (*floodprot_check_multiline_batch)(Channel *channel, Client *client, int line_count); Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)()) { @@ -537,4 +538,5 @@ void efunctions_init(void) efunc_init_function(EFUNC_ISUPPORT_CHECK_FOR_CHANGES, isupport_check_for_changes, NULL, 0); efunc_init_function(EFUNC_GET_CONNECTIONS_FROM_IP, get_connections_from_ip, get_connections_from_ip_default_handler, 0); efunc_init_function(EFUNC_GET_FLOODPROT_CHANNEL_MAX_LINES, get_floodprot_channel_max_lines, get_floodprot_channel_max_lines_default_handler, 0); + efunc_init_function(EFUNC_FLOODPROT_CHECK_MULTILINE_BATCH, floodprot_check_multiline_batch, floodprot_check_multiline_batch_default_handler, 0); } diff --git a/src/misc.c b/src/misc.c index 1f180dbb3..c67ab0bc7 100644 --- a/src/misc.c +++ b/src/misc.c @@ -1474,6 +1474,11 @@ int get_floodprot_channel_max_lines_default_handler(Channel *channel) return INT_MAX; } +int floodprot_check_multiline_batch_default_handler(Channel *channel, Client *client, int line_count) +{ + return 0; +} + int make_oper_default_handler(Client *client, const char *operblock_name, const char *operclass, ConfigItem_class *clientclass, long modes, const char *snomask, const char *vhost, const char *autojoin_channels) diff --git a/src/modules/chanmodes/floodprot.c b/src/modules/chanmodes/floodprot.c index 5824cc4ea..d776f18be 100644 --- a/src/modules/chanmodes/floodprot.c +++ b/src/modules/chanmodes/floodprot.c @@ -36,8 +36,9 @@ typedef enum Flood { CHFLD_NICK = 4, CHFLD_TEXT = 5, CHFLD_REPEAT = 6, + CHFLD_PASTE = 7, } Flood; -#define NUMFLD 7 /* 7 flood types */ +#define NUMFLD 8 /* 8 flood types */ /** Configuration settings */ struct { @@ -69,6 +70,7 @@ FloodType floodtypes[] = { { 'k', CHFLD_KNOCK, "knockflood", 'K', "", NULL, 0, }, { 'm', CHFLD_MSG, "msg/noticeflood", 'm', "M", "~quiet:~security-group:unknown-users", 0, }, { 'n', CHFLD_NICK, "nickflood", 'N', "", "~nickchange:~security-group:unknown-users", 0, }, + { 'p', CHFLD_PASTE, "pasteflood", '\0', "mM", "~quiet:~security-group:unknown-users", 0, }, { 't', CHFLD_TEXT, "msg/noticeflood", '\0', "bd", NULL, 1, }, { 'r', CHFLD_REPEAT, "repeating", '\0', "bd", NULL, 1, }, }; @@ -182,12 +184,15 @@ int floodprot_server_quit(Client *client, MessageTag *mtags); void inherit_settings(ChannelFloodProtection *from, ChannelFloodProtection *to); void reapply_profiles(void); int _get_floodprot_channel_max_lines(Channel *channel); +int _floodprot_check_multiline_batch(Channel *channel, Client *client, int line_count); MOD_TEST() { + MARK_AS_OFFICIAL_MODULE(modinfo); HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, floodprot_config_test_set_block); HookAdd(modinfo->handle, HOOKTYPE_CONFIGTEST, 0, floodprot_config_test_antiflood_block); EfunctionAdd(modinfo->handle, EFUNC_GET_FLOODPROT_CHANNEL_MAX_LINES, _get_floodprot_channel_max_lines); + EfunctionAdd(modinfo->handle, EFUNC_FLOODPROT_CHECK_MULTILINE_BATCH, _floodprot_check_multiline_batch); return MOD_SUCCESS; } @@ -334,27 +339,27 @@ static void init_default_channel_flood_profiles(void) ChannelFloodProfile *f; f = safe_alloc(sizeof(ChannelFloodProfile)); - cmodef_put_param(&f->settings, "[10j#R10,30m#M10,7c#C15,5n#N15,10k#K15]:15"); + cmodef_put_param(&f->settings, "[10j#R10,30m#M10,7c#C15,5n#N15,10k#K15,1p]:15"); safe_strdup(f->settings.profile, "very-strict"); AddListItem(f, channel_flood_profiles); f = safe_alloc(sizeof(ChannelFloodProfile)); - cmodef_put_param(&f->settings, "[15j#R10,40m#M10,7c#C15,8n#N15,10k#K15]:15"); + cmodef_put_param(&f->settings, "[15j#R10,40m#M10,7c#C15,8n#N15,10k#K15,1p]:15"); safe_strdup(f->settings.profile, "strict"); AddListItem(f, channel_flood_profiles); f = safe_alloc(sizeof(ChannelFloodProfile)); - cmodef_put_param(&f->settings, "[30j#R10,40m#M10,7c#C15,8n#N15,10k#K15]:15"); + cmodef_put_param(&f->settings, "[30j#R10,40m#M10,7c#C15,8n#N15,10k#K15,2p]:15"); safe_strdup(f->settings.profile, "normal"); AddListItem(f, channel_flood_profiles); f = safe_alloc(sizeof(ChannelFloodProfile)); - cmodef_put_param(&f->settings, "[45j#R10,60m#M10,7c#C15,10n#N15,10k#K15]:15"); + cmodef_put_param(&f->settings, "[45j#R10,60m#M10,7c#C15,10n#N15,10k#K15,2p]:15"); safe_strdup(f->settings.profile, "relaxed"); AddListItem(f, channel_flood_profiles); f = safe_alloc(sizeof(ChannelFloodProfile)); - cmodef_put_param(&f->settings, "[60j#R10,90m#M10,7c#C15,10n#N15,10k#K15]:15"); + cmodef_put_param(&f->settings, "[60j#R10,90m#M10,7c#C15,10n#N15,10k#K15,3p]:15"); safe_strdup(f->settings.profile, "very-relaxed"); AddListItem(f, channel_flood_profiles); @@ -1292,12 +1297,12 @@ ChannelFloodProtection *get_channel_flood_settings(Channel *channel, int what) if (channel->mode.mode & EXTMODE_FLOODLIMIT) { fld = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'f'); - if (fld->action[what]) + if (fld->limit[what]) return fld; } fld = (ChannelFloodProtection *)GETPARASTRUCT(channel, 'F'); - if (fld && fld->action[what]) + if (fld && fld->limit[what]) return fld; return NULL; @@ -1322,6 +1327,31 @@ int _get_floodprot_channel_max_lines(Channel *channel) return result; } +/** Check if a multiline batch is allowed by the channel's +f 'p' (paste) limit. + * Called from multiline.c before delivering a batch to the channel. + * @param channel The target channel + * @param client The user sending the batch + * @param line_count Number of lines in the batch + * @returns 0 if allowed, 1 if denied (paste limit exceeded) + */ +int _floodprot_check_multiline_batch(Channel *channel, Client *client, int line_count) +{ + /* Exempt short batches (2 lines or less) — that's just normal conversation */ + if (line_count < 3) + return 0; + + if (!IsFloodLimit(channel)) + return 0; + + if (check_channel_access(client, channel, "hoaq") || IsULine(client)) + return 0; + + if (do_floodprot(channel, client, CHFLD_PASTE)) + return 1; /* paste flood detected, reject batch */ + + return 0; +} + int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype, ClientContext *clictx) { Membership *mb; @@ -1735,28 +1765,25 @@ int do_floodprot(Channel *channel, Client *client, int what) unknown_user = user_allowed_by_security_group_name(client, "known-users") ? 0 : 1; - if (fld->limit[what]) + if (TStime() - fld->timer[what] >= fld->per) { - if (TStime() - fld->timer[what] >= fld->per) - { - /* reset */ - fld->timer[what] = TStime(); - fld->counter[what] = 1; - fld->counter_unknown_users[what] = unknown_user; - } else - { - fld->counter[what]++; + /* reset */ + fld->timer[what] = TStime(); + fld->counter[what] = 1; + fld->counter_unknown_users[what] = unknown_user; + } else + { + fld->counter[what]++; - if (unknown_user) - fld->counter_unknown_users[what]++; + if (unknown_user) + fld->counter_unknown_users[what]++; - if ((fld->counter[what] > fld->limit[what]) && - (TStime() - fld->timer[what] < fld->per)) - { - if (MyUser(client)) - do_floodprot_action(channel, what); - return 1; /* flood detected! */ - } + if ((fld->counter[what] > fld->limit[what]) && + (TStime() - fld->timer[what] < fld->per)) + { + if (MyUser(client)) + do_floodprot_action(channel, what); + return 1; /* flood detected! */ } } return 0; diff --git a/src/modules/multiline.c b/src/modules/multiline.c index abc41baba..0b3259f2f 100644 --- a/src/modules/multiline.c +++ b/src/modules/multiline.c @@ -1204,6 +1204,13 @@ static void multiline_deliver_channel(Client *client, MultilineBatch *batch, Cha safe_free(concat_text); } + /* Check channel +f 'p' (paste flood) limit */ + if (MyUser(client) && floodprot_check_multiline_batch(channel, client, batch->line_count)) + { + sendto_one(client, NULL, ":%s FAIL BATCH MULTILINE_PASTE_LIMIT :Too many paste events in channel (+f)", me.name); + return; + } + /* Generate outgoing message tags */ new_message(client, batch->client_mtags, &mtags);