]> jfr.im git - solanum.git/blobdiff - extensions/filter.c
add help for `chm_regmsg`
[solanum.git] / extensions / filter.c
index f7649862f54718f8511fea7858443525301237ef..bebcbdfcab8c8757e746a972ddf75de236ca2024 100644 (file)
 #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 **);
@@ -64,6 +71,7 @@ static int filter_enable = 1;
 static const char *cmdname[MESSAGE_TYPE_COUNT] = {
        [MESSAGE_TYPE_PRIVMSG] = "PRIVMSG",
        [MESSAGE_TYPE_NOTICE] = "NOTICE",
+       [MESSAGE_TYPE_PART] = "PART",
 };
 
 enum filter_state {
@@ -79,10 +87,13 @@ 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 }
 };
 
@@ -92,9 +103,24 @@ struct Message setfilter_msgtab = {
        {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)
@@ -106,7 +132,7 @@ moddeinit(void)
 
 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)
@@ -115,13 +141,13 @@ 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;
        }
@@ -142,6 +168,28 @@ setfilter(const char *check, const char *data, const char **error)
                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;
@@ -161,6 +209,7 @@ setfilter(const char *check, const char *data, const char **error)
                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) {
@@ -168,7 +217,7 @@ setfilter(const char *check, const char *data, const char **error)
                }
                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;
@@ -176,9 +225,16 @@ setfilter(const char *check, const char *data, const char **error)
                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;
@@ -276,23 +332,50 @@ int match_callback(unsigned id,
        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_)
 {
@@ -308,43 +391,30 @@ 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);
        }
 }
 
@@ -362,47 +432,57 @@ filter_msg_channel(void *data_)
        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_)
 {
@@ -415,4 +495,3 @@ on_client_exit(void *data_)
                state = filter_db ? FILTER_LOADED : FILTER_EMPTY;
        }
 }
-