mirror of
https://github.com/unrealircd/unrealircd.git
synced 2026-06-27 21:16:38 +02:00
Add draft/multiline support with a default max-lines of 15 for known-users
and 7 for unknown-users (with max-bytes 5250 and 1500 respectively). This
allows pasting a short snippet of code, config file, text from a site, etc.
With multiline you have the guarantee that:
1) You will see the entire text with no delay between lines
2) You won't see another persons chat half-way through such a paste
3) For multiline supporting clients it is now clear that all the text
belongs to each other, which can make selecting/copying it easier.
This basically means short snippets/pastes like that can be completely on
IRC again. No need for a pastebin for it. Though, you may still need such
a service if you are pasting more lines.
Regarding the implementation in UnrealIRCd:
* Clients without multiline get individual fallback lines (concat lines
merged, blank lines skipped, as per spec). And we know that clients like
weechat - which does support multiline - also shows all lines and not
only a few plus snippet style "[.."]. That is another reason for only
allowing 15 lines by default and not something much more. Otherwise all
those clients would get a big wall of text, which just sucks.
* Spamfilter (also) runs on the full text of all lines together, so
splitting a phrase across lines does not evade spamfilter.
* Fakelag: a client can send the BATCH start+PRIVMSG (or NOTICE)+BATCH end
at full speed. We impose no fake lag there. Also, the multiline default
max-lines and max-bytes are lower than the example class::recvq of 8000,
so should be perfectly safe. If the entire BATCH is accepted then we
will impose fake-lag afterwards, with a cap of 15 seconds maximum.
If the BATCH is rejected, we impose half the fakelag plus 2sec.
* If the time between BATCH start and BATCH end is more than 15 seconds
then the BATCH is rejected (set::multiline::batch-timeout).
* The BATCH is atomic (either you see it all, or you see none of it):
* When the client sends it to server, it is buffered first.
* Only after the batch close the server indicates if it is accepted
or rejected. This has various reasons, two of them are: 1) The client
is going to send everything in one go anyway and not wait for a
response between each PRIVMSG, and 2) we can't do many checks in the
buffering stage and skip those after, that would cause a TOCTOU
problem (eg. a banned user still being able to speak).
* If any line gets rejected due to spamfilter or other case
(eg +c, +b ~text with block, etc etc), the entire batch is rejected
* Locally we deliver all or nothing (as said)
* S2S we buffer the batch as well, so if a server splits after having
received 10 lines out of 15, then clients will not see anything.
* We send max-lines and max-bytes, this is the hard upper limit.
* A multiline can still be limited more tight if:
* +f with 't' or 'm' restricts to fewer lines,
eg +f [5t]:15, which means max 5 lines per 15 seconds,
means the max accepted multiline is 5 for that channel.
* +F works the same, except that default +F normal does not
have a 't' at the moment and 'm' is very high (50) so
practically not limited by default.
* There will be a future +f flood subtype for some more control
TODO: we will send CAP NEW on unknown-users <-> known-users to
indicate the new max-lines value if you transition security groups
TODO: chat history does not yet include multiline batches.
This commit is contained in:
@@ -791,6 +791,7 @@ extern MODVAR int dontspread;
|
||||
extern MODVAR int labeled_response_inhibit;
|
||||
extern MODVAR int labeled_response_inhibit_end;
|
||||
extern MODVAR int labeled_response_force;
|
||||
extern MODVAR int echo_message_inhibit;
|
||||
|
||||
/* Efuncs */
|
||||
extern MODVAR void (*do_join)(Client *, int, const char **);
|
||||
@@ -880,6 +881,7 @@ extern MODVAR int (*is_services_but_not_ulined)(Client *client);
|
||||
extern MODVAR void (*parse_message_tags)(Client *cptr, char **str, MessageTag **mtag_list);
|
||||
extern MODVAR const char *(*mtags_to_string)(MessageTag *m, Client *acptr);
|
||||
extern MODVAR int (*can_send_to_channel)(Client *cptr, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtyp, ClientContext *clictx);
|
||||
extern MODVAR int (*can_send_to_user)(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx, int flags);
|
||||
extern MODVAR void (*broadcast_md_globalvar)(ModDataInfo *mdi, ModData *md);
|
||||
extern MODVAR void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender, const char *varname, const char *value);
|
||||
extern MODVAR int (*tkl_ip_hash)(const char *ip);
|
||||
@@ -948,6 +950,7 @@ extern MODVAR int (*utf8_get_block_number)(const char *name);
|
||||
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);
|
||||
/* /Efuncs */
|
||||
|
||||
/* TLS functions */
|
||||
@@ -987,6 +990,7 @@ extern int add_silence_default_handler(Client *client, const char *mask, int sen
|
||||
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 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);
|
||||
@@ -1532,6 +1536,7 @@ extern Tag *add_tag(Client *client, const char *name, int value);
|
||||
extern void free_all_tags(Client *client);
|
||||
extern void del_tag(Client *client, const char *name);
|
||||
extern void bump_tag_serial(Client *client);
|
||||
extern int valid_batch_reference_tag(const char *ref);
|
||||
extern int valid_spamfilter_id(const char *s);
|
||||
extern void download_complete_dontcare(OutgoingWebRequest *request, OutgoingWebResponse *response);
|
||||
extern char *urlencode(const char *s, char *wbuf, int wlen);
|
||||
|
||||
@@ -534,6 +534,11 @@ typedef struct {
|
||||
#define MTAG_HANDLER_FLAGS_NONE 0x0
|
||||
/** This message-tag does not have a CAP REQ xx (eg: for "msgid") */
|
||||
#define MTAG_HANDLER_FLAGS_NO_CAP_NEEDED 0x1
|
||||
/** This tag should only appear on the first message of the
|
||||
* multiline fallback (for clients that don't support multiline).
|
||||
* Used by "msgid" and "+draft/reply".
|
||||
*/
|
||||
#define MTAG_HANDLER_FLAGS_FIRST_ONLY 0x2
|
||||
|
||||
/** Message Tag Handler */
|
||||
struct MessageTagHandler {
|
||||
@@ -2800,6 +2805,7 @@ enum EfunctionType {
|
||||
EFUNC_SEND_ISUPPORT,
|
||||
EFUNC_ISUPPORT_CHECK_FOR_CHANGES,
|
||||
EFUNC_GET_CONNECTIONS_FROM_IP,
|
||||
EFUNC_GET_FLOODPROT_CHANNEL_MAX_LINES,
|
||||
};
|
||||
|
||||
/* Module flags */
|
||||
|
||||
@@ -1457,7 +1457,10 @@ typedef enum FloodOption {
|
||||
FLD_CONVERSATIONS = 5, /**< max-concurrent-conversations */
|
||||
FLD_LAG_PENALTY = 6, /**< lag-penalty / lag-penalty-bytes */
|
||||
FLD_VHOST = 7, /**< vhost-flood */
|
||||
FLD_MULTILINE = 8, /**< multiline max-lines / max-bytes */
|
||||
} FloodOption;
|
||||
#define MULTILINE_MAX_CONFIGURABLE_LINES 200 /**< Maximum configurable max-lines for multiline */
|
||||
#define MULTILINE_MAX_CONFIGURABLE_BYTES 131072 /**< Maximum configurable max-bytes for multiline (128KB) */
|
||||
#define MAXFLOODOPTIONS 10
|
||||
|
||||
typedef struct TrafficStats TrafficStats;
|
||||
@@ -2662,6 +2665,9 @@ struct ConfigItem_badword {
|
||||
#define SKIP_CTCP 0x8
|
||||
#define CHECK_INVISIBLE 0x10
|
||||
|
||||
/* Flags for 'flags' in 'can_send_to_user' (and future: can_send_to_channel) */
|
||||
#define CAN_SEND_SKIP_SPAMFILTER 0x1
|
||||
|
||||
typedef struct GeoIPResult GeoIPResult;
|
||||
struct GeoIPResult {
|
||||
char *country_code;
|
||||
|
||||
+12
-6
@@ -111,10 +111,11 @@ void cmd_alias(ClientContext *clictx, Client *client, MessageTag *mtags, int par
|
||||
{
|
||||
const char *msg = parv[1];
|
||||
const char *errmsg = NULL;
|
||||
/* FIXME: when can_send_to_channel() gets 'int flags', pass
|
||||
* alias->spamfilter ? 0 : CAN_SEND_SKIP_SPAMFILTER
|
||||
*/
|
||||
if (can_send_to_channel(client, channel, &msg, &errmsg, 0, clictx))
|
||||
{
|
||||
if (alias->spamfilter && match_spamfilter(client, parv[1], SPAMF_CHANMSG, cmd, channel->name, 0, clictx, NULL))
|
||||
return;
|
||||
new_message(client, NULL, &mtags);
|
||||
sendto_channel(channel, client, client->direction,
|
||||
NULL, 0, SEND_ALL|SKIP_DEAF, mtags,
|
||||
@@ -124,6 +125,8 @@ void cmd_alias(ClientContext *clictx, Client *client, MessageTag *mtags, int par
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (IsDead(client))
|
||||
return;
|
||||
sendnumeric(client, ERR_CANNOTDOCOMMAND,
|
||||
cmd, "You may not use this command at this time");
|
||||
}
|
||||
@@ -249,10 +252,11 @@ void cmd_alias(ClientContext *clictx, Client *client, MessageTag *mtags, int par
|
||||
{
|
||||
const char *msg = output;
|
||||
const char *errmsg = NULL;
|
||||
if (!can_send_to_channel(client, channel, &msg, &errmsg, 0, clictx))
|
||||
/* FIXME: when can_send_to_channel() gets 'int flags', pass
|
||||
* alias->spamfilter ? 0 : CAN_SEND_SKIP_SPAMFILTER
|
||||
*/
|
||||
if (can_send_to_channel(client, channel, &msg, &errmsg, 0, clictx))
|
||||
{
|
||||
if (alias->spamfilter && match_spamfilter(client, output, SPAMF_CHANMSG, cmd, channel->name, 0, clictx, NULL))
|
||||
return;
|
||||
new_message(client, NULL, &mtags);
|
||||
sendto_channel(channel, client, client->direction,
|
||||
NULL, 0, SEND_ALL|SKIP_DEAF, mtags,
|
||||
@@ -262,7 +266,9 @@ void cmd_alias(ClientContext *clictx, Client *client, MessageTag *mtags, int par
|
||||
return;
|
||||
}
|
||||
}
|
||||
sendnumeric(client, ERR_CANNOTDOCOMMAND, cmd,
|
||||
if (IsDead(client))
|
||||
return;
|
||||
sendnumeric(client, ERR_CANNOTDOCOMMAND, cmd,
|
||||
"You may not use this command at this time");
|
||||
}
|
||||
else if (format->type == ALIAS_REAL)
|
||||
|
||||
@@ -118,6 +118,7 @@ int (*is_services_but_not_ulined)(Client *client);
|
||||
void (*parse_message_tags)(Client *client, char **str, MessageTag **mtag_list);
|
||||
const char *(*mtags_to_string)(MessageTag *m, Client *client);
|
||||
int (*can_send_to_channel)(Client *client, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx);
|
||||
int (*can_send_to_user)(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx, int flags);
|
||||
void (*broadcast_md_globalvar)(ModDataInfo *mdi, ModData *md);
|
||||
void (*broadcast_md_globalvar_cmd)(Client *except, Client *sender, const char *varname, const char *value);
|
||||
int (*tkl_ip_hash)(const char *ip);
|
||||
@@ -191,6 +192,7 @@ int (*utf8_get_block_number)(const char *name);
|
||||
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);
|
||||
|
||||
Efunction *EfunctionAddMain(Module *module, EfunctionType eftype, int (*func)(), void (*vfunc)(), void *(*pvfunc)(), char *(*stringfunc)(), const char *(*conststringfunc)())
|
||||
{
|
||||
@@ -457,6 +459,7 @@ void efunctions_init(void)
|
||||
efunc_init_function(EFUNC_TKL_TYPE_STRING, tkl_type_string, NULL, 0);
|
||||
efunc_init_function(EFUNC_TKL_TYPE_CONFIG_STRING, tkl_type_config_string, NULL, 0);
|
||||
efunc_init_function(EFUNC_CAN_SEND_TO_CHANNEL, can_send_to_channel, NULL, 0);
|
||||
efunc_init_function(EFUNC_CAN_SEND_TO_USER, can_send_to_user, NULL, 0);
|
||||
efunc_init_function(EFUNC_BROADCAST_MD_GLOBALVAR, broadcast_md_globalvar, NULL, 0);
|
||||
efunc_init_function(EFUNC_BROADCAST_MD_GLOBALVAR_CMD, broadcast_md_globalvar_cmd, NULL, 0);
|
||||
efunc_init_function(EFUNC_TKL_IP_HASH, tkl_ip_hash, NULL, 0);
|
||||
@@ -533,4 +536,5 @@ void efunctions_init(void)
|
||||
efunc_init_function(EFUNC_SEND_ISUPPORT, send_isupport, NULL, 0);
|
||||
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);
|
||||
}
|
||||
|
||||
+94
-47
@@ -430,6 +430,7 @@ int flood_option_is_old(const char *name)
|
||||
"knock-flood",
|
||||
"connect-flood",
|
||||
"target-flood",
|
||||
"multiline",
|
||||
NULL
|
||||
};
|
||||
|
||||
@@ -1829,6 +1830,7 @@ void config_setdefaultsettings(Configuration *i)
|
||||
config_parse_flood_generic("4:120", i, "known-users", FLD_KNOCK); /* KNOCK protection: max 4 per 120s */
|
||||
config_parse_flood_generic("10:15", i, "known-users", FLD_CONVERSATIONS); /* 10 users, new user every 15s */
|
||||
config_parse_flood_generic("180:750", i, "known-users", FLD_LAG_PENALTY); /* 180 bytes / 750 msec */
|
||||
config_parse_flood_generic("15:5250", i, "known-users", FLD_MULTILINE); /* max-lines=15, max-bytes=5250 */
|
||||
/* - unknown-users */
|
||||
config_parse_flood_generic("2:60", i, "unknown-users", FLD_NICK); /* NICK flood protection: max 2 per 60s */
|
||||
config_parse_flood_generic("2:90", i, "unknown-users", FLD_JOIN); /* JOIN flood protection: max 2 per 90s */
|
||||
@@ -1838,6 +1840,7 @@ void config_setdefaultsettings(Configuration *i)
|
||||
config_parse_flood_generic("2:120", i, "unknown-users", FLD_KNOCK); /* KNOCK protection: max 2 per 120s */
|
||||
config_parse_flood_generic("4:15", i, "unknown-users", FLD_CONVERSATIONS); /* 4 users, new user every 15s */
|
||||
config_parse_flood_generic("90:1000", i, "unknown-users", FLD_LAG_PENALTY); /* 90 bytes / 1000 msec */
|
||||
config_parse_flood_generic("7:1500", i, "unknown-users", FLD_MULTILINE); /* max-lines=7, max-bytes=1500 */
|
||||
|
||||
/* TLS options */
|
||||
i->tls_options = safe_alloc(sizeof(TLSOptions));
|
||||
@@ -8074,6 +8077,22 @@ int _conf_set(ConfigFile *conf, ConfigEntry *ce)
|
||||
snprintf(buf, sizeof(buf), "%d:%ld", users, every);
|
||||
config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_CONVERSATIONS);
|
||||
}
|
||||
else if (!strcmp(ceppp->name, "multiline"))
|
||||
{
|
||||
/* Same hack: store max-lines in limit, max-bytes in period */
|
||||
char buf[64];
|
||||
int max_lines = 0;
|
||||
int max_bytes = 0;
|
||||
for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
|
||||
{
|
||||
if (!strcmp(cep4->name, "max-lines"))
|
||||
max_lines = atoi(cep4->value);
|
||||
else if (!strcmp(cep4->name, "max-bytes"))
|
||||
max_bytes = config_checkval(cep4->value, CFG_SIZE);
|
||||
}
|
||||
snprintf(buf, sizeof(buf), "%d:%d", max_lines, max_bytes);
|
||||
config_parse_flood_generic(buf, &tempiConf, cepp->name, FLD_MULTILINE);
|
||||
}
|
||||
}
|
||||
if ((lag_penalty != -1) && (lag_penalty_bytes != -1))
|
||||
{
|
||||
@@ -9006,6 +9025,44 @@ int _test_set(ConfigFile *conf, ConfigEntry *ce)
|
||||
}
|
||||
continue; /* required here, due to checknull directly below */
|
||||
}
|
||||
else if (!strcmp(ceppp->name, "multiline"))
|
||||
{
|
||||
for (cep4 = ceppp->items; cep4; cep4 = cep4->next)
|
||||
{
|
||||
CheckNull(cep4);
|
||||
if (!strcmp(cep4->name, "max-lines"))
|
||||
{
|
||||
int v = atoi(cep4->value);
|
||||
if ((v < 2) || (v > MULTILINE_MAX_CONFIGURABLE_LINES))
|
||||
{
|
||||
config_error("%s:%i: set::anti-flood::multiline::max-lines: "
|
||||
"value should be between 2 and %d",
|
||||
cep4->file->filename, cep4->line_number,
|
||||
MULTILINE_MAX_CONFIGURABLE_LINES);
|
||||
errors++;
|
||||
}
|
||||
} else
|
||||
if (!strcmp(cep4->name, "max-bytes"))
|
||||
{
|
||||
int v = config_checkval(cep4->value, CFG_SIZE);
|
||||
if ((v < 256) || (v > MULTILINE_MAX_CONFIGURABLE_BYTES))
|
||||
{
|
||||
config_error("%s:%i: set::anti-flood::multiline::max-bytes: "
|
||||
"value should be between 256 and %d",
|
||||
cep4->file->filename, cep4->line_number,
|
||||
MULTILINE_MAX_CONFIGURABLE_BYTES);
|
||||
errors++;
|
||||
}
|
||||
} else
|
||||
{
|
||||
config_error_unknownopt(cep4->file->filename,
|
||||
cep4->line_number, "set::anti-flood::multiline",
|
||||
cep4->name);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
continue;
|
||||
}
|
||||
else if (!strcmp(ceppp->name, "maxchannelsperuser"))
|
||||
{
|
||||
CheckNull(ceppp);
|
||||
@@ -10336,6 +10393,26 @@ int _test_offchans(ConfigFile *conf, ConfigEntry *ce)
|
||||
return errors;
|
||||
}
|
||||
|
||||
/** Convert alias type string to ALIAS_xxx constant.
|
||||
* @returns ALIAS_SERVICES, ALIAS_STATS, etc., or 0 for unknown.
|
||||
*/
|
||||
static int alias_type_strtoval(const char *s)
|
||||
{
|
||||
if (!strcmp(s, "services"))
|
||||
return ALIAS_SERVICES;
|
||||
if (!strcmp(s, "stats"))
|
||||
return ALIAS_STATS;
|
||||
if (!strcmp(s, "normal"))
|
||||
return ALIAS_NORMAL;
|
||||
if (!strcmp(s, "command"))
|
||||
return ALIAS_COMMAND;
|
||||
if (!strcmp(s, "channel"))
|
||||
return ALIAS_CHANNEL;
|
||||
if (!strcmp(s, "real"))
|
||||
return ALIAS_REAL;
|
||||
return 0;
|
||||
}
|
||||
|
||||
int _conf_alias(ConfigFile *conf, ConfigEntry *ce)
|
||||
{
|
||||
ConfigItem_alias *alias = NULL;
|
||||
@@ -10374,16 +10451,7 @@ int _conf_alias(ConfigFile *conf, ConfigEntry *ce)
|
||||
safe_strdup(format->parameters, cepp->value);
|
||||
}
|
||||
else if (!strcmp(cepp->name, "type")) {
|
||||
if (!strcmp(cepp->value, "services"))
|
||||
format->type = ALIAS_SERVICES;
|
||||
else if (!strcmp(cepp->value, "stats"))
|
||||
format->type = ALIAS_STATS;
|
||||
else if (!strcmp(cepp->value, "normal"))
|
||||
format->type = ALIAS_NORMAL;
|
||||
else if (!strcmp(cepp->value, "channel"))
|
||||
format->type = ALIAS_CHANNEL;
|
||||
else if (!strcmp(cepp->value, "real"))
|
||||
format->type = ALIAS_REAL;
|
||||
format->type = alias_type_strtoval(cepp->value);
|
||||
}
|
||||
}
|
||||
AddListItem(format, alias->format);
|
||||
@@ -10394,16 +10462,7 @@ int _conf_alias(ConfigFile *conf, ConfigEntry *ce)
|
||||
safe_strdup(alias->nick, cep->value);
|
||||
}
|
||||
else if (!strcmp(cep->name, "type")) {
|
||||
if (!strcmp(cep->value, "services"))
|
||||
alias->type = ALIAS_SERVICES;
|
||||
else if (!strcmp(cep->value, "stats"))
|
||||
alias->type = ALIAS_STATS;
|
||||
else if (!strcmp(cep->value, "normal"))
|
||||
alias->type = ALIAS_NORMAL;
|
||||
else if (!strcmp(cep->value, "channel"))
|
||||
alias->type = ALIAS_CHANNEL;
|
||||
else if (!strcmp(cep->value, "command"))
|
||||
alias->type = ALIAS_COMMAND;
|
||||
alias->type = alias_type_strtoval(cep->value);
|
||||
}
|
||||
else if (!strcmp(cep->name, "spamfilter"))
|
||||
alias->spamfilter = config_checkval(cep->value, CFG_YESNO);
|
||||
@@ -10421,8 +10480,8 @@ int _conf_alias(ConfigFile *conf, ConfigEntry *ce)
|
||||
int _test_alias(ConfigFile *conf, ConfigEntry *ce) {
|
||||
int errors = 0;
|
||||
ConfigEntry *cep, *cepp;
|
||||
char has_type = 0, has_target = 0, has_format = 0;
|
||||
char type = 0;
|
||||
char has_type = 0, has_target = 0, has_format = 0, has_spamfilter = 0;
|
||||
int type = 0;
|
||||
|
||||
if (!ce->items)
|
||||
{
|
||||
@@ -10492,17 +10551,7 @@ int _test_alias(ConfigFile *conf, ConfigEntry *ce) {
|
||||
continue;
|
||||
}
|
||||
has_type = 1;
|
||||
if (!strcmp(cepp->value, "services"))
|
||||
;
|
||||
else if (!strcmp(cepp->value, "stats"))
|
||||
;
|
||||
else if (!strcmp(cepp->value, "normal"))
|
||||
;
|
||||
else if (!strcmp(cepp->value, "channel"))
|
||||
;
|
||||
else if (!strcmp(cepp->value, "real"))
|
||||
;
|
||||
else
|
||||
if (!alias_type_strtoval(cepp->value))
|
||||
{
|
||||
config_error("%s:%i: unknown alias type",
|
||||
cepp->file->filename, cepp->line_number);
|
||||
@@ -10565,24 +10614,16 @@ int _test_alias(ConfigFile *conf, ConfigEntry *ce) {
|
||||
continue;
|
||||
}
|
||||
has_type = 1;
|
||||
if (!strcmp(cep->value, "services"))
|
||||
;
|
||||
else if (!strcmp(cep->value, "stats"))
|
||||
;
|
||||
else if (!strcmp(cep->value, "normal"))
|
||||
;
|
||||
else if (!strcmp(cep->value, "channel"))
|
||||
;
|
||||
else if (!strcmp(cep->value, "command"))
|
||||
type = 'c';
|
||||
else {
|
||||
type = alias_type_strtoval(cep->value);
|
||||
if (!type)
|
||||
{
|
||||
config_error("%s:%i: unknown alias type",
|
||||
cep->file->filename, cep->line_number);
|
||||
errors++;
|
||||
}
|
||||
}
|
||||
else if (!strcmp(cep->name, "spamfilter"))
|
||||
;
|
||||
has_spamfilter = 1;
|
||||
else {
|
||||
config_error_unknown(cep->file->filename, cep->line_number,
|
||||
"alias", cep->name);
|
||||
@@ -10595,18 +10636,24 @@ int _test_alias(ConfigFile *conf, ConfigEntry *ce) {
|
||||
"alias::type");
|
||||
errors++;
|
||||
}
|
||||
if (!has_format && type == 'c')
|
||||
if (!has_format && (type == ALIAS_COMMAND))
|
||||
{
|
||||
config_error("%s:%d: alias::type is 'command' but no alias::format was specified",
|
||||
ce->file->filename, ce->line_number);
|
||||
errors++;
|
||||
}
|
||||
else if (has_format && type != 'c')
|
||||
else if (has_format && (type != ALIAS_COMMAND))
|
||||
{
|
||||
config_error("%s:%d: alias::format specified when type is not 'command'",
|
||||
ce->file->filename, ce->line_number);
|
||||
errors++;
|
||||
}
|
||||
if (has_spamfilter && (type == ALIAS_CHANNEL))
|
||||
{
|
||||
config_warn("%s:%d: alias::spamfilter has no effect for channel aliases, "
|
||||
"spamfilter is always checked for channel messages",
|
||||
ce->file->filename, ce->line_number);
|
||||
}
|
||||
return errors;
|
||||
}
|
||||
|
||||
|
||||
+23
-1
@@ -1469,6 +1469,11 @@ int get_connections_from_ip_default_handler(Client *client)
|
||||
return 0;
|
||||
}
|
||||
|
||||
int get_floodprot_channel_max_lines_default_handler(Channel *channel)
|
||||
{
|
||||
return INT_MAX;
|
||||
}
|
||||
|
||||
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)
|
||||
@@ -2832,7 +2837,7 @@ const char *StripControlCodesEx(const char *text, char *output, size_t outputlen
|
||||
/* strip color, bold, underline, and reverse codes from a string */
|
||||
const char *StripControlCodes(const char *text)
|
||||
{
|
||||
static unsigned char new_str[4096];
|
||||
static unsigned char new_str[8192];
|
||||
|
||||
return StripControlCodesEx(text, new_str, sizeof(new_str), 0);
|
||||
}
|
||||
@@ -2883,6 +2888,23 @@ int valid_spamfilter_id(const char *s)
|
||||
return 1;
|
||||
}
|
||||
|
||||
/** Check if a batch reference tag contains only valid characters.
|
||||
* Per the batch spec: only ASCII letters, numbers, and hyphens.
|
||||
*/
|
||||
int valid_batch_reference_tag(const char *ref)
|
||||
{
|
||||
const char *p;
|
||||
|
||||
if (BadPtr(ref) || strlen(ref) > 48)
|
||||
return 0;
|
||||
for (p = ref; *p; p++)
|
||||
{
|
||||
if (!isalnum(*p) && *p != '-')
|
||||
return 0;
|
||||
}
|
||||
return 1;
|
||||
}
|
||||
|
||||
void download_complete_dontcare(OutgoingWebRequest *request, OutgoingWebResponse *response)
|
||||
{
|
||||
#ifdef DEBUGMODE
|
||||
|
||||
@@ -69,7 +69,7 @@ MODULES= \
|
||||
history_backend_null.so tkldb.so channeldb.so whowasdb.so \
|
||||
restrict-commands.so rmtkl.so require-module.so \
|
||||
account-notify.so \
|
||||
message-tags.so batch.so \
|
||||
message-tags.so batch.so multiline.so \
|
||||
account-tag.so labeled-response.so link-security.so \
|
||||
message-ids.so plaintext-policy.so server-time.so sts.so \
|
||||
echo-message.so userip-tag.so userhost-tag.so geoip-tag.so \
|
||||
|
||||
+24
-2
@@ -80,7 +80,24 @@ CMD_FUNC(cmd_batch)
|
||||
Client *target;
|
||||
char buf[512];
|
||||
|
||||
if (MyUser(client) || (parc < 3))
|
||||
if (MyUser(client))
|
||||
{
|
||||
/* No module claimed this client-initiated batch */
|
||||
if (parc >= 2 && parv[1][0] == '+')
|
||||
{
|
||||
if (!valid_batch_reference_tag(parv[1] + 1))
|
||||
sendto_one(client, NULL, ":%s FAIL BATCH INVALID_REFTAG %s :Invalid batch reference tag", me.name, parv[1] + 1);
|
||||
else
|
||||
sendto_one(client, NULL, ":%s FAIL BATCH UNKNOWN_TYPE :Unknown batch type", me.name);
|
||||
}
|
||||
return;
|
||||
}
|
||||
if (parc < 3)
|
||||
return;
|
||||
|
||||
if (parv[2][0] != '+' && parv[2][0] != '-')
|
||||
return;
|
||||
if (!valid_batch_reference_tag(parv[2] + 1))
|
||||
return;
|
||||
|
||||
target = find_client(parv[1], NULL);
|
||||
@@ -109,12 +126,17 @@ CMD_FUNC(cmd_batch)
|
||||
/** This function verifies if the client sending
|
||||
* 'batch' is permitted to do so and uses a permitted
|
||||
* syntax.
|
||||
* We simply allow batch ONLY from servers and with any syntax.
|
||||
* We allow batch from servers (with any syntax) and from
|
||||
* local users (so modules handling client-initiated batches,
|
||||
* like draft/multiline, can see the tag on recv_mtags).
|
||||
*/
|
||||
int batch_mtag_is_ok(Client *client, const char *name, const char *value)
|
||||
{
|
||||
if (IsServer(client))
|
||||
return 1;
|
||||
|
||||
if (MyUser(client) && !BadPtr(value) && valid_batch_reference_tag(value))
|
||||
return 1;
|
||||
|
||||
return 0;
|
||||
}
|
||||
|
||||
@@ -181,11 +181,13 @@ int parse_channel_mode_flood_failed(const char **error_out, ChannelFloodProtecti
|
||||
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);
|
||||
|
||||
MOD_TEST()
|
||||
{
|
||||
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);
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -1301,6 +1303,25 @@ ChannelFloodProtection *get_channel_flood_settings(Channel *channel, int what)
|
||||
return NULL;
|
||||
}
|
||||
|
||||
/** Return the maximum number of lines permitted by +f/+F 'm' and 't' limits.
|
||||
* Returns INT_MAX if no relevant flood protection is set.
|
||||
*/
|
||||
int _get_floodprot_channel_max_lines(Channel *channel)
|
||||
{
|
||||
ChannelFloodProtection *fld;
|
||||
int result = INT_MAX;
|
||||
|
||||
fld = get_channel_flood_settings(channel, CHFLD_MSG);
|
||||
if (fld && fld->limit[CHFLD_MSG])
|
||||
result = MIN(result, fld->limit[CHFLD_MSG]);
|
||||
|
||||
fld = get_channel_flood_settings(channel, CHFLD_TEXT);
|
||||
if (fld && fld->limit[CHFLD_TEXT])
|
||||
result = MIN(result, fld->limit[CHFLD_TEXT]);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
int floodprot_can_send_to_channel(Client *client, Channel *channel, Membership *lp, const char **msg, const char **errmsg, SendType sendtype, ClientContext *clictx)
|
||||
{
|
||||
Membership *mb;
|
||||
|
||||
@@ -66,6 +66,8 @@ MOD_UNLOAD()
|
||||
|
||||
int em_chanmsg(Client *client, Channel *channel, int sendflags, const char *prefix, const char *target, MessageTag *mtags, const char *text, SendType sendtype)
|
||||
{
|
||||
if (echo_message_inhibit)
|
||||
return 0;
|
||||
if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
|
||||
{
|
||||
if (sendtype != SEND_TYPE_TAGMSG)
|
||||
@@ -87,6 +89,8 @@ int em_chanmsg(Client *client, Channel *channel, int sendflags, const char *pref
|
||||
|
||||
int em_usermsg(Client *client, Client *to, MessageTag *mtags, const char *text, SendType sendtype)
|
||||
{
|
||||
if (echo_message_inhibit)
|
||||
return 0;
|
||||
if (MyUser(client) && HasCapabilityFast(client, CAP_ECHO_MESSAGE))
|
||||
{
|
||||
if (sendtype != SEND_TYPE_TAGMSG)
|
||||
|
||||
@@ -46,7 +46,7 @@ MOD_INIT()
|
||||
memset(&mtag, 0, sizeof(mtag));
|
||||
mtag.name = "msgid";
|
||||
mtag.is_ok = msgid_mtag_is_ok;
|
||||
mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
|
||||
mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED | MTAG_HANDLER_FLAGS_FIRST_ONLY;
|
||||
MessageTagHandlerAdd(modinfo->handle, &mtag);
|
||||
|
||||
HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_or_inherit_msgid);
|
||||
|
||||
+34
-20
@@ -28,7 +28,7 @@ CMD_FUNC(cmd_notice);
|
||||
CMD_FUNC(cmd_tagmsg);
|
||||
void cmd_message(ClientContext *clictx, Client *client, MessageTag *recv_mtags, int parc, const char *parv[], SendType sendtype);
|
||||
int _can_send_to_channel(Client *client, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx);
|
||||
int can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx);
|
||||
int _can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx, int flags);
|
||||
|
||||
/* Variables */
|
||||
long CAP_MESSAGE_TAGS = 0; /**< Looked up at MOD_LOAD, may stay 0 if message-tags support is absent */
|
||||
@@ -47,6 +47,7 @@ MOD_TEST()
|
||||
MARK_AS_OFFICIAL_MODULE(modinfo);
|
||||
EfunctionAddConstString(modinfo->handle, EFUNC_STRIPCOLORS, _StripColors);
|
||||
EfunctionAdd(modinfo->handle, EFUNC_CAN_SEND_TO_CHANNEL, _can_send_to_channel);
|
||||
EfunctionAdd(modinfo->handle, EFUNC_CAN_SEND_TO_USER, _can_send_to_user);
|
||||
return MOD_SUCCESS;
|
||||
}
|
||||
|
||||
@@ -83,7 +84,7 @@ MOD_UNLOAD()
|
||||
* text: Pointer to a pointer to a text [in, out]
|
||||
* cmd: Pointer to a pointer which contains the command to use [in, out]
|
||||
*/
|
||||
int can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx)
|
||||
int _can_send_to_user(Client *client, Client *target, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx, int flags)
|
||||
{
|
||||
int ret;
|
||||
Hook *h;
|
||||
@@ -114,12 +115,14 @@ int can_send_to_user(Client *client, Client *target, const char **msgtext, const
|
||||
return 0;
|
||||
}
|
||||
|
||||
// Possible FIXME: make match_spamfilter also use errmsg, or via a wrapper? or use same numeric?
|
||||
if (MyUser(client) && (sendtype != SEND_TYPE_TAGMSG))
|
||||
/* Spamfilter: run on original text, before hooks may transform it
|
||||
* (e.g. +G censor). This ensures spamfilter catches the actual
|
||||
* input even if a user mode would sanitize it for delivery.
|
||||
*/
|
||||
if (!(flags & CAN_SEND_SKIP_SPAMFILTER) && MyUser(client) && (sendtype != SEND_TYPE_TAGMSG))
|
||||
{
|
||||
int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_USERNOTICE : SPAMF_USERMSG);
|
||||
const char *cmd = sendtype_to_cmd(sendtype);
|
||||
|
||||
if (match_spamfilter(client, *msgtext, spamtype, cmd, target->name, 0, clictx, NULL))
|
||||
return 0;
|
||||
}
|
||||
@@ -291,12 +294,6 @@ void cmd_message(ClientContext *clictx, Client *client, MessageTag *recv_mtags,
|
||||
targetstr = pfixchan;
|
||||
}
|
||||
|
||||
if (IsVirus(client) && strcasecmp(channel->name, SPAMFILTER_VIRUSCHAN))
|
||||
{
|
||||
sendnotice(client, "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN);
|
||||
continue;
|
||||
}
|
||||
|
||||
text = parv[2];
|
||||
errmsg = NULL;
|
||||
if (MyUser(client) && !IsULine(client))
|
||||
@@ -323,14 +320,6 @@ void cmd_message(ClientContext *clictx, Client *client, MessageTag *recv_mtags,
|
||||
if ((*parv[2] == '\001') && strncmp(&parv[2][1], "ACTION ", 7))
|
||||
sendflags |= SKIP_CTCP;
|
||||
|
||||
if (MyUser(client) && (sendtype != SEND_TYPE_TAGMSG))
|
||||
{
|
||||
int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG);
|
||||
|
||||
if (match_spamfilter(client, text, spamtype, cmd, channel->name, 0, clictx, NULL))
|
||||
return;
|
||||
}
|
||||
|
||||
new_message(client, recv_mtags, &mtags);
|
||||
|
||||
RunHook(HOOKTYPE_PRE_CHANMSG, client, channel, &mtags, text, sendtype);
|
||||
@@ -409,7 +398,7 @@ void cmd_message(ClientContext *clictx, Client *client, MessageTag *recv_mtags,
|
||||
{
|
||||
const char *errmsg = NULL;
|
||||
text = parv[2];
|
||||
if (!can_send_to_user(client, target, &text, &errmsg, sendtype, clictx))
|
||||
if (!can_send_to_user(client, target, &text, &errmsg, sendtype, clictx, 0))
|
||||
{
|
||||
/* Message is discarded */
|
||||
if (IsDead(client))
|
||||
@@ -627,8 +616,14 @@ int ban_version(Client *client, const char *text)
|
||||
* @returns Returns 1 if the user is allowed to send, otherwise 0.
|
||||
* (note that this behavior was reversed in UnrealIRCd versions <5.x.
|
||||
*/
|
||||
/* FIXME: in a future major release, add 'int flags' parameter
|
||||
* (matching can_send_to_user) so callers like aliases.c can pass
|
||||
* CAN_SEND_SKIP_SPAMFILTER to opt out of the built-in spamfilter
|
||||
* check. Can't add the parameter now without breaking the module API.
|
||||
*/
|
||||
int _can_send_to_channel(Client *client, Channel *channel, const char **msgtext, const char **errmsg, SendType sendtype, ClientContext *clictx)
|
||||
{
|
||||
static char errbuf[256];
|
||||
Membership *lp;
|
||||
int member, i = 0;
|
||||
Hook *h;
|
||||
@@ -638,6 +633,25 @@ int _can_send_to_channel(Client *client, Channel *channel, const char **msgtext,
|
||||
|
||||
*errmsg = NULL;
|
||||
|
||||
if (IsVirus(client) && strcasecmp(channel->name, SPAMFILTER_VIRUSCHAN))
|
||||
{
|
||||
ircsnprintf(errbuf, sizeof(errbuf), "You are only allowed to talk in '%s'", SPAMFILTER_VIRUSCHAN);
|
||||
*errmsg = errbuf;
|
||||
return 0;
|
||||
}
|
||||
|
||||
/* Spamfilter: run on original text, before hooks may transform it
|
||||
* (e.g. +G censor). This ensures spamfilter catches the actual
|
||||
* input even if a channel mode would sanitize it for delivery.
|
||||
*/
|
||||
if (sendtype != SEND_TYPE_TAGMSG)
|
||||
{
|
||||
int spamtype = (sendtype == SEND_TYPE_NOTICE ? SPAMF_CHANNOTICE : SPAMF_CHANMSG);
|
||||
const char *cmd = sendtype_to_cmd(sendtype);
|
||||
if (match_spamfilter(client, *msgtext, spamtype, cmd, channel->name, 0, clictx, NULL))
|
||||
return 0;
|
||||
}
|
||||
|
||||
member = IsMember(client, channel);
|
||||
|
||||
lp = find_membership_link(client->user->channel, channel);
|
||||
|
||||
File diff suppressed because it is too large
Load Diff
@@ -53,7 +53,7 @@ MOD_INIT()
|
||||
memset(&mtag, 0, sizeof(mtag));
|
||||
mtag.name = "+draft/reply";
|
||||
mtag.is_ok = replytag_mtag_is_ok;
|
||||
mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED;
|
||||
mtag.flags = MTAG_HANDLER_FLAGS_NO_CAP_NEEDED | MTAG_HANDLER_FLAGS_FIRST_ONLY;
|
||||
MessageTagHandlerAdd(modinfo->handle, &mtag);
|
||||
|
||||
HookAddVoid(modinfo->handle, HOOKTYPE_NEW_MESSAGE, 0, mtag_add_replytag);
|
||||
|
||||
@@ -753,6 +753,13 @@ static void stats_set_anti_flood(Client *client, FloodSettings *f)
|
||||
f->name,
|
||||
f->limit[i] == INT_MAX ? 0 : (int)f->limit[i]);
|
||||
} else
|
||||
if (i == FLD_MULTILINE)
|
||||
{
|
||||
sendtxtnumeric(client, "anti-flood::%s::multiline::max-lines: %d",
|
||||
f->name, (int)f->limit[i]);
|
||||
sendtxtnumeric(client, "anti-flood::%s::multiline::max-bytes: %d",
|
||||
f->name, (int)f->period[i]);
|
||||
} else
|
||||
{
|
||||
sendtxtnumeric(client, "anti-flood::%s::%s: %d per %s",
|
||||
f->name, floodoption_names[i],
|
||||
|
||||
+1
-1
@@ -5490,7 +5490,7 @@ int _match_spamfilter(Client *client, const char *str_in, int target, const char
|
||||
TKL *winner_tkl = NULL;
|
||||
const char *str;
|
||||
const char *str_deconfused = NULL;
|
||||
char deconfused[512];
|
||||
char deconfused[8192];
|
||||
int ret = -1;
|
||||
char *reason = NULL;
|
||||
#ifdef SPAMFILTER_DETECTSLOW
|
||||
|
||||
+8
-1
@@ -51,6 +51,13 @@ MODVAR int labeled_response_force = 0;
|
||||
*/
|
||||
MODVAR int labeled_response_inhibit_end = 0;
|
||||
|
||||
/* FIXME: in a future major release that allows module API changes,
|
||||
* add sendflags to HOOKTYPE_USERMSG and use a SKIP_ECHO sendflags
|
||||
* flag instead of this global. See HOOKTYPE_CHANMSG which already
|
||||
* has sendflags.
|
||||
*/
|
||||
MODVAR int echo_message_inhibit = 0;
|
||||
|
||||
/** Set to 1 if an UTF8 incompatible nick character set is in use */
|
||||
MODVAR int non_utf8_nick_chars_in_use = 0;
|
||||
|
||||
@@ -941,7 +948,7 @@ MODVAR const char *floodoption_names[] = {
|
||||
"max-concurrent-conversations",
|
||||
"lag-penalty",
|
||||
"vhost-flood",
|
||||
"max-channels-per-user",
|
||||
"multiline",
|
||||
NULL
|
||||
};
|
||||
|
||||
|
||||
Reference in New Issue
Block a user