#include "stdinc.h"
#include "channel.h"
#include "client.h"
+#include "chmode.h"
#include "match.h"
#include "ircd.h"
#include "numeric.h"
#include "send.h"
-#include "s_serv.h"
#include "s_newconf.h"
+#include "s_serv.h"
+#include "s_user.h"
#include "msg.h"
#include "parse.h"
#include "modules.h"
#define FILTER_USER 0
#define FILTER_HOST 0
+#define FILTER_EXIT_MSG "Connection closed"
+
+static const char filter_desc[] = "Filter messages using a precompiled Hyperscan database";
+
static void filter_msg_user(void *data);
static void filter_msg_channel(void *data);
+static void filter_client_quit(void *data);
static void on_client_exit(void *data);
static void mo_setfilter(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
static const char *cmdname[MESSAGE_TYPE_COUNT] = {
[MESSAGE_TYPE_PRIVMSG] = "PRIVMSG",
[MESSAGE_TYPE_NOTICE] = "NOTICE",
+ [MESSAGE_TYPE_PART] = "PART",
};
enum filter_state {
static enum filter_state state = FILTER_EMPTY;
static char check_str[21] = "";
+static unsigned filter_chmode, filter_umode;
+
mapi_hfn_list_av1 filter_hfnlist[] = {
- { "privmsg_user", (hookfn) filter_msg_user },
- { "privmsg_channel", (hookfn) filter_msg_channel },
- { "client_exit", (hookfn) on_client_exit },
+ { "privmsg_user", filter_msg_user },
+ { "privmsg_channel", filter_msg_channel },
+ { "client_quit", filter_client_quit },
+ { "client_exit", on_client_exit },
{ NULL, NULL }
};
{mg_unreg, mg_not_oper, mg_ignore, mg_ignore, {me_setfilter, 2}, {mo_setfilter, 2}}
};
+static int
+modinit(void)
+{
+ filter_umode = user_modes['u'] = find_umode_slot();
+ construct_umodebuf();
+ filter_chmode = cflag_add('u', chm_simple);
+ return 0;
+}
+
static void
moddeinit(void)
{
+ if (filter_umode) {
+ user_modes['u'] = 0;
+ construct_umodebuf();
+ }
+ if (filter_chmode)
+ cflag_orphan('u');
if (filter_scratch)
hs_free_scratch(filter_scratch);
if (filter_db)
mapi_clist_av1 filter_clist[] = { &setfilter_msgtab, NULL };
-DECLARE_MODULE_AV1(filter, NULL, moddeinit, filter_clist, NULL, filter_hfnlist, "0.3");
+DECLARE_MODULE_AV2(filter, modinit, moddeinit, filter_clist, NULL, filter_hfnlist, NULL, "0.4", filter_desc);
static int
setfilter(const char *check, const char *data, const char **error)
if (!strcasecmp(data, "disable")) {
filter_enable = 0;
- sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
+ sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
"Filtering disabled.");
return 0;
}
if (!strcasecmp(data, "enable")) {
filter_enable = 1;
- sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
+ sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
"Filtering enabled.");
return 0;
}
return 0;
}
+ if (!strcasecmp(data, "drop")) {
+ if (!filter_db) {
+ if (error) *error = "no database to drop";
+ return -1;
+ }
+ hs_free_database(filter_db);
+ filter_db = 0;
+ return 0;
+ }
+
+ if (!strcasecmp(data, "abort")) {
+ if (state != FILTER_FILLING) {
+ if (error) *error = "not filling";
+ return -1;
+ }
+ state = filter_db ? FILTER_LOADED : FILTER_EMPTY;
+ rb_free(filter_data);
+ filter_data = 0;
+ filter_data_len = 0;
+ return 0;
+ }
+
if (strcmp(check, check_str) != 0) {
if (error) *error = "check strings don't match";
return -1;
r = hs_alloc_scratch(db, &filter_scratch);
if (r != HS_SUCCESS) {
if (error) *error = "couldn't allocate scratch";
+ hs_free_database(db);
return -1;
}
if (filter_db) {
}
state = FILTER_LOADED;
filter_db = db;
- sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
+ sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
"New filters loaded.");
rb_free(filter_data);
filter_data = 0;
return 0;
}
+ if (*data != '+') {
+ if (error) *error = "unknown command or data doesn't start with +";
+ return -1;
+ }
+
+ data += 1;
+
if (state == FILTER_FILLING) {
int dl;
- unsigned char *d = rb_base64_decode(data, strlen(data), &dl);
+ unsigned char *d = rb_base64_decode((unsigned char *)data, strlen(data), &dl);
if (!d) {
if (error) *error = "invalid data";
return -1;
return 0;
}
+static char check_buffer[2000];
+static char clean_buffer[BUFSIZE];
-unsigned match_message(const char *msg)
+unsigned match_message(const char *prefix,
+ struct Client *source,
+ const char *command,
+ const char *target,
+ const char *msg)
{
unsigned state = 0;
if (!filter_enable)
return 0;
if (!filter_db)
return 0;
- hs_error_t r = hs_scan(filter_db, msg, strlen(msg), 0, filter_scratch, match_callback, &state);
+ 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
+ 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);
if (r != HS_SUCCESS && r != HS_SCAN_TERMINATED)
return 0;
return state;
}
-static char check_buffer[2000];
-static char clean_buffer[BUFSIZE];
-
void
filter_msg_user(void *data_)
{
if (IsOper(s) || IsOper(data->target_p)) {
return;
}
+ if (data->target_p->umodes & filter_umode) {
+ return;
+ }
char *text = strcpy(clean_buffer, data->text);
strip_colour(text);
strip_unprintable(text);
- snprintf(check_buffer, sizeof check_buffer, ":%s!%s@%s#%c %s 0 :%s",
-#if FILTER_NICK
- s->name,
-#else
- "*",
-#endif
-#if FILTER_USER
- s->username,
-#else
- "*",
-#endif
-#if FILTER_HOST
- s->host,
-#else
- "*",
-#endif
- s->user && s->user->suser[0] != '\0' ? '1' : '0',
- cmdname[data->msgtype],
- text);
- unsigned r = match_message(check_buffer);
+ unsigned r = match_message("0", s, cmdname[data->msgtype], "0", data->text) |
+ match_message("1", s, cmdname[data->msgtype], "0", text);
if (r & ACT_DROP) {
- sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
- form_str(ERR_CANNOTSENDTOCHAN),
- data->target_p->name);
+ if (data->msgtype == MESSAGE_TYPE_PRIVMSG) {
+ sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
+ form_str(ERR_CANNOTSENDTOCHAN),
+ data->target_p->name);
+ }
data->approved = 1;
}
if (r & ACT_ALARM) {
- sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
+ sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
"FILTER: %s!%s@%s [%s]",
s->name, s->username, s->host, s->sockhost);
}
if (r & ACT_KILL) {
data->approved = 1;
- exit_client(NULL, s, s, "Excess flood");
+ exit_client(NULL, s, s, FILTER_EXIT_MSG);
}
}
if (IsOper(s)) {
return;
}
+ if (data->chptr->mode.mode & filter_chmode) {
+ return;
+ }
char *text = strcpy(clean_buffer, data->text);
strip_colour(text);
strip_unprintable(text);
- snprintf(check_buffer, sizeof check_buffer, ":%s!%s@%s#%c %s %s :%s",
-#if FILTER_NICK
- s->name,
-#else
- "*",
-#endif
-#if FILTER_USER
- s->username,
-#else
- "*",
-#endif
-#if FILTER_HOST
- s->host,
-#else
- "*",
-#endif
- s->user && s->user->suser[0] != '\0' ? '1' : '0',
- cmdname[data->msgtype],
- data->chptr->chname,
- text);
- unsigned r = match_message(check_buffer);
+ 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);
if (r & ACT_DROP) {
- sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
- form_str(ERR_CANNOTSENDTOCHAN),
- data->chptr->chname);
+ if (data->msgtype == MESSAGE_TYPE_PRIVMSG) {
+ sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
+ form_str(ERR_CANNOTSENDTOCHAN),
+ data->chptr->chname);
+ }
data->approved = 1;
}
if (r & ACT_ALARM) {
- sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
+ sendto_realops_snomask(SNO_GENERAL, L_NETWIDE,
"FILTER: %s!%s@%s [%s]",
s->name, s->username, s->host, s->sockhost);
}
if (r & ACT_KILL) {
data->approved = 1;
- exit_client(NULL, s, s, "Excess flood");
+ exit_client(NULL, s, s, FILTER_EXIT_MSG);
}
}
+void
+filter_client_quit(void *data_)
+{
+ hook_data_client_quit *data = data_;
+ struct Client *s = data->client;
+ if (IsOper(s)) {
+ return;
+ }
+ 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);
+ if (r & ACT_DROP) {
+ data->reason = NULL;
+ }
+ if (r & ACT_ALARM) {
+ sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
+ "FILTER: %s!%s@%s [%s]",
+ s->name, s->username, s->host, s->sockhost);
+ }
+ /* No point in doing anything with ACT_KILL */
+}
+
void
on_client_exit(void *data_)
{
state = filter_db ? FILTER_LOADED : FILTER_EMPTY;
}
}
-