diff --git a/doc/reference.conf b/doc/reference.conf index 435ca45d..b3e9407b 100644 --- a/doc/reference.conf +++ b/doc/reference.conf @@ -1484,6 +1484,23 @@ general { * requires extensions/drain to be loaded. */ drain_reason = "This server is not accepting connections."; + + /* filter_sees_user_info: Whether the filter is given the nick!user@host of message senders. + * If set to NO, the filter is given a literal "*!*@*" instead. + * Requires extension/filter to be loaded. + */ + filter_sees_user_info = no; + + /* filter_bypass_all: If set to NO, the only filter action that can bypass +u is DROP when paired with BYPASS. + * If set to YES, all filter actions can bypass +u when paired with BYPASS. + * Requires extension/filter to be loaded. + */ + filter_bypass_all = no; + + /* filter_exit_message: The QUIT message shown when a user is disconnected due to the filter KILL action. + * Requires extension/filter to be loaded. + */ + filter_exit_message = "Connection closed"; }; modules { diff --git a/doc/server-version-info.txt b/doc/server-version-info.txt index 194c6b35..c81fe1af 100644 --- a/doc/server-version-info.txt +++ b/doc/server-version-info.txt @@ -17,6 +17,8 @@ +----------------------------+ | 'e' | USE_EXCEPT | |------+---------------------| + | 'F' | FILTER_CAN_SPY | + |------+---------------------| | 'I' | USE_INVEX | |------+---------------------| | 'K' | USE_KNOCK | diff --git a/extensions/filter.c b/extensions/filter.c index bebcbdfc..6afb967e 100644 --- a/extensions/filter.c +++ b/extensions/filter.c @@ -33,6 +33,7 @@ #include "numeric.h" #include "send.h" #include "s_newconf.h" +#include "s_conf.h" #include "s_serv.h" #include "s_user.h" #include "msg.h" @@ -45,11 +46,7 @@ #include #include -#define FILTER_NICK 0 -#define FILTER_USER 0 -#define FILTER_HOST 0 - -#define FILTER_EXIT_MSG "Connection closed" +#define FILTER_DEFAULT_EXIT_MSG "Connection closed" static const char filter_desc[] = "Filter messages using a precompiled Hyperscan database"; @@ -80,9 +77,15 @@ enum filter_state { FILTER_LOADED }; +struct match_context { + unsigned int actions; + bool require_bypass; +}; + #define ACT_DROP (1 << 0) #define ACT_KILL (1 << 1) #define ACT_ALARM (1 << 2) +#define ACT_BYPASS (1 << 3) static enum filter_state state = FILTER_EMPTY; static char check_str[21] = ""; @@ -327,8 +330,17 @@ int match_callback(unsigned id, unsigned flags, void *context_) { - unsigned *context = context_; - *context |= id; + struct match_context *context = context_; + + if (context->require_bypass) + { + unsigned int bypass_mask = ConfigFileEntry.filter_bypass_all ? UINT_MAX : ACT_DROP; + if (id & ACT_BYPASS) + context->actions |= id & bypass_mask; + } + else + context->actions |= id; + return 0; } @@ -337,43 +349,34 @@ static char clean_buffer[BUFSIZE]; unsigned match_message(const char *prefix, struct Client *source, + bool require_bypass, const char *command, const char *target, const char *msg) { - unsigned state = 0; + struct match_context ctx = { 0, require_bypass }; + if (!filter_enable) return 0; if (!filter_db) return 0; if (!command) return 0; + snprintf(check_buffer, sizeof check_buffer, "%s:%s!%s@%s#%c %s%s%s :%s", prefix, -#if FILTER_NICK - source->name, -#else - "*", -#endif -#if FILTER_USER - source->username, -#else - "*", -#endif -#if FILTER_HOST - source->host, -#else - "*", -#endif + ConfigFileEntry.filter_sees_user_info ? source->name : "*", + ConfigFileEntry.filter_sees_user_info ? source->username : "*", + ConfigFileEntry.filter_sees_user_info ? source->host : "*", source->user && source->user->suser[0] != '\0' ? '1' : '0', command, target ? " " : "", target ? target : "", msg); - hs_error_t r = hs_scan(filter_db, check_buffer, strlen(check_buffer), 0, filter_scratch, match_callback, &state); + hs_error_t r = hs_scan(filter_db, check_buffer, strlen(check_buffer), 0, filter_scratch, match_callback, &ctx); if (r != HS_SUCCESS && r != HS_SCAN_TERMINATED) return 0; - return state; + return ctx.actions; } void @@ -381,6 +384,7 @@ filter_msg_user(void *data_) { hook_data_privmsg_user *data = data_; struct Client *s = data->source_p; + /* we only need to filter once */ if (!MyClient(s)) { return; @@ -391,14 +395,14 @@ filter_msg_user(void *data_) if (IsOper(s) || IsOper(data->target_p)) { return; } - if (data->target_p->umodes & filter_umode) { - return; - } + + bool require_bypass = (data->target_p->umodes & filter_umode) == filter_umode; char *text = strcpy(clean_buffer, data->text); strip_colour(text); strip_unprintable(text); - unsigned r = match_message("0", s, cmdname[data->msgtype], "0", data->text) | - match_message("1", s, cmdname[data->msgtype], "0", text); + unsigned r = match_message("0", s, require_bypass, cmdname[data->msgtype], "0", data->text) | + match_message("1", s, require_bypass, cmdname[data->msgtype], "0", text); + if (r & ACT_DROP) { if (data->msgtype == MESSAGE_TYPE_PRIVMSG) { sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN, @@ -413,8 +417,11 @@ filter_msg_user(void *data_) s->name, s->username, s->host, s->sockhost); } if (r & ACT_KILL) { + const char *msg = ConfigFileEntry.filter_exit_message; + if (msg == NULL) + msg = FILTER_DEFAULT_EXIT_MSG; data->approved = 1; - exit_client(NULL, s, s, FILTER_EXIT_MSG); + exit_client(NULL, s, s, msg); } } @@ -423,6 +430,7 @@ filter_msg_channel(void *data_) { hook_data_privmsg_channel *data = data_; struct Client *s = data->source_p; + /* we only need to filter once */ if (!MyClient(s)) { return; @@ -432,14 +440,14 @@ filter_msg_channel(void *data_) if (IsOper(s)) { return; } - if (data->chptr->mode.mode & filter_chmode) { - return; - } + + bool require_bypass = (data->chptr->mode.mode & filter_chmode) == filter_chmode; char *text = strcpy(clean_buffer, data->text); strip_colour(text); strip_unprintable(text); - unsigned r = match_message("0", s, cmdname[data->msgtype], data->chptr->chname, data->text) | - match_message("1", s, cmdname[data->msgtype], data->chptr->chname, text); + unsigned r = match_message("0", s, require_bypass, cmdname[data->msgtype], data->chptr->chname, data->text) | + match_message("1", s, require_bypass, cmdname[data->msgtype], data->chptr->chname, text); + if (r & ACT_DROP) { if (data->msgtype == MESSAGE_TYPE_PRIVMSG) { sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN, @@ -454,8 +462,11 @@ filter_msg_channel(void *data_) s->name, s->username, s->host, s->sockhost); } if (r & ACT_KILL) { + const char *msg = ConfigFileEntry.filter_exit_message; + if (msg == NULL) + msg = FILTER_DEFAULT_EXIT_MSG; data->approved = 1; - exit_client(NULL, s, s, FILTER_EXIT_MSG); + exit_client(NULL, s, s, msg); } } @@ -470,8 +481,8 @@ filter_client_quit(void *data_) char *text = strcpy(clean_buffer, data->orig_reason); strip_colour(text); strip_unprintable(text); - unsigned r = match_message("0", s, "QUIT", NULL, data->orig_reason) | - match_message("1", s, "QUIT", NULL, text); + unsigned r = match_message("0", s, false, "QUIT", NULL, data->orig_reason) | + match_message("1", s, false, "QUIT", NULL, text); if (r & ACT_DROP) { data->reason = NULL; } diff --git a/include/s_conf.h b/include/s_conf.h index 5cb5bdf9..ad609b52 100644 --- a/include/s_conf.h +++ b/include/s_conf.h @@ -243,6 +243,8 @@ struct config_file_entry int away_interval; int tls_ciphers_oper_only; int oper_secure_only; + int filter_sees_user_info; + int filter_bypass_all; char **hidden_caps; @@ -268,6 +270,7 @@ struct config_file_entry char *server_full_client_message; char *illegal_name_long_client_message; char *illegal_name_short_client_message; + char *filter_exit_message; }; struct config_channel_entry diff --git a/ircd/newconf.c b/ircd/newconf.c index 99796eb3..e782cfe8 100644 --- a/ircd/newconf.c +++ b/ircd/newconf.c @@ -2781,6 +2781,9 @@ static struct ConfEntry conf_general_table[] = { "illegal_name_short_client_message", CF_QSTRING, NULL, BUFSIZE, &ConfigFileEntry.illegal_name_short_client_message }, { "tls_ciphers_oper_only", CF_YESNO, NULL, 0, &ConfigFileEntry.tls_ciphers_oper_only }, { "oper_secure_only", CF_YESNO, NULL, 0, &ConfigFileEntry.oper_secure_only }, + { "filter_sees_user_info", CF_YESNO, NULL, 0, &ConfigFileEntry.filter_sees_user_info }, + { "filter_bypass_all", CF_YESNO, NULL, 0, &ConfigFileEntry.filter_bypass_all }, + { "filter_exit_message", CF_QSTRING, NULL, BUFSIZE, &ConfigFileEntry.filter_exit_message }, { "\0", 0, NULL, 0, NULL } }; diff --git a/ircd/s_conf.c b/ircd/s_conf.c index 4997c73f..981fc2de 100644 --- a/ircd/s_conf.c +++ b/ircd/s_conf.c @@ -1561,6 +1561,8 @@ clear_out_old_conf(void) ConfigFileEntry.illegal_name_long_client_message = NULL; rb_free(ConfigFileEntry.illegal_name_short_client_message); ConfigFileEntry.illegal_name_short_client_message = NULL; + rb_free(ConfigFileEntry.filter_exit_message); + ConfigFileEntry.filter_exit_message = NULL; if (ConfigFileEntry.hidden_caps != NULL) { diff --git a/modules/m_info.c b/modules/m_info.c index 155ee6fe..f3b83488 100644 --- a/modules/m_info.c +++ b/modules/m_info.c @@ -244,6 +244,11 @@ static struct InfoStruct info_table[] = { "Short message to users when their username contains illegal characters.", INFO_STRING(&ConfigFileEntry.illegal_name_short_client_message), }, + { + "filter_exit_message", + "Message to quit users with if they hit a filter KILL action.", + INFO_STRING(&ConfigFileEntry.filter_exit_message), + }, { "disable_auth", "Controls whether auth checking is disabled or not", @@ -675,6 +680,16 @@ static struct InfoStruct info_table[] = { "Require TLS to become an oper", INFO_INTBOOL_YN(&ConfigFileEntry.oper_secure_only), }, + { + "filter_sees_user_info", + "Let the spamfilter engine see the hostmasks of senders", + INFO_INTBOOL_YN(&ConfigFileEntry.filter_sees_user_info), + }, + { + "filter_bypass_all", + "Let the spamfilter BYPASS action work on KILL/ALARM in addition to DROP", + INFO_INTBOOL_YN(&ConfigFileEntry.filter_bypass_all), + }, { NULL, NULL, 0, { NULL } }, }; diff --git a/modules/m_version.c b/modules/m_version.c index d9f7c769..77ba3d7a 100644 --- a/modules/m_version.c +++ b/modules/m_version.c @@ -122,6 +122,9 @@ confopts(void) if(ConfigChannel.use_except) *p++ = 'e'; + if (ConfigFileEntry.filter_sees_user_info || ConfigFileEntry.filter_bypass_all) + *p++ = 'F'; + if(ConfigChannel.use_invex) *p++ = 'I';