2 * ircd-ratbox: A slightly useful ircd.
3 * filter.c: Drop messages we don't like
5 * Copyright (C) 1990 Jarkko Oikarinen and University of Oulu, Co Center
6 * Copyright (C) 1996-2002 Hybrid Development Team
7 * Copyright (C) 2002-2005 ircd-ratbox development team
9 * This program is free software; you can redistribute it and/or modify
10 * it under the terms of the GNU General Public License as published by
11 * the Free Software Foundation; either version 2 of the License, or
12 * (at your option) any later version.
14 * This program is distributed in the hope that it will be useful,
15 * but WITHOUT ANY WARRANTY; without even the implied warranty of
16 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
17 * GNU General Public License for more details.
19 * You should have received a copy of the GNU General Public License
20 * along with this program; if not, write to the Free Software
21 * Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
35 #include "s_newconf.h"
42 #include "inline/stringops.h"
45 #include <hs_common.h>
46 #include <hs_runtime.h>
52 #define FILTER_EXIT_MSG "Connection closed"
54 static const char filter_desc
[] = "Filter messages using a precompiled Hyperscan database";
56 static void filter_msg_user(void *data
);
57 static void filter_msg_channel(void *data
);
58 static void filter_client_quit(void *data
);
59 static void on_client_exit(void *data
);
61 static void mo_setfilter(struct MsgBuf
*, struct Client
*, struct Client
*, int, const char **);
62 static void me_setfilter(struct MsgBuf
*, struct Client
*, struct Client
*, int, const char **);
64 static char *filter_data
= NULL
;
65 static size_t filter_data_len
= 0;
66 static hs_database_t
*filter_db
;
67 static hs_scratch_t
*filter_scratch
;
69 static int filter_enable
= 1;
71 static const char *cmdname
[MESSAGE_TYPE_COUNT
] = {
72 [MESSAGE_TYPE_PRIVMSG
] = "PRIVMSG",
73 [MESSAGE_TYPE_NOTICE
] = "NOTICE",
74 [MESSAGE_TYPE_PART
] = "PART",
83 #define ACT_DROP (1 << 0)
84 #define ACT_KILL (1 << 1)
85 #define ACT_ALARM (1 << 2)
87 static enum filter_state state
= FILTER_EMPTY
;
88 static char check_str
[21] = "";
90 static unsigned filter_chmode
, filter_umode
;
92 mapi_hfn_list_av1 filter_hfnlist
[] = {
93 { "privmsg_user", (hookfn
) filter_msg_user
},
94 { "privmsg_channel", (hookfn
) filter_msg_channel
},
95 { "client_quit", (hookfn
) filter_client_quit
},
96 { "client_exit", (hookfn
) on_client_exit
},
101 struct Message setfilter_msgtab
= {
102 "SETFILTER", 0, 0, 0, 0,
103 {mg_unreg
, mg_not_oper
, mg_ignore
, mg_ignore
, {me_setfilter
, 2}, {mo_setfilter
, 2}}
109 filter_umode
= user_modes
['u'] = find_umode_slot();
110 construct_umodebuf();
111 filter_chmode
= cflag_add('u', chm_simple
);
120 construct_umodebuf();
125 hs_free_scratch(filter_scratch
);
127 hs_free_database(filter_db
);
129 rb_free(filter_data
);
133 mapi_clist_av1 filter_clist
[] = { &setfilter_msgtab
, NULL
};
135 DECLARE_MODULE_AV2(filter
, modinit
, moddeinit
, filter_clist
, NULL
, filter_hfnlist
, NULL
, "0.4", filter_desc
);
138 setfilter(const char *check
, const char *data
, const char **error
)
140 if (error
) *error
= "unknown";
142 if (!strcasecmp(data
, "disable")) {
144 sendto_realops_snomask(SNO_GENERAL
, L_ALL
| L_NETWIDE
,
145 "Filtering disabled.");
148 if (!strcasecmp(data
, "enable")) {
150 sendto_realops_snomask(SNO_GENERAL
, L_ALL
| L_NETWIDE
,
151 "Filtering enabled.");
155 if (strlen(check
) > sizeof check_str
- 1) {
156 if (error
) *error
= "check string too long";
160 if (!strcasecmp(data
, "new")) {
161 if (state
== FILTER_FILLING
) {
162 rb_free(filter_data
);
166 state
= FILTER_FILLING
;
167 strcpy(check_str
, check
);
171 if (!strcasecmp(data
, "drop")) {
173 if (error
) *error
= "no database to drop";
176 hs_free_database(filter_db
);
181 if (!strcasecmp(data
, "abort")) {
182 if (state
!= FILTER_FILLING
) {
183 if (error
) *error
= "not filling";
186 state
= filter_db
? FILTER_LOADED
: FILTER_EMPTY
;
187 rb_free(filter_data
);
193 if (strcmp(check
, check_str
) != 0) {
194 if (error
) *error
= "check strings don't match";
198 if (!strcasecmp(data
, "apply")) {
199 if (state
!= FILTER_FILLING
) {
200 if (error
) *error
= "not loading anything";
204 hs_error_t r
= hs_deserialize_database(filter_data
, filter_data_len
, &db
);
205 if (r
!= HS_SUCCESS
) {
206 if (error
) *error
= "couldn't deserialize db";
209 r
= hs_alloc_scratch(db
, &filter_scratch
);
210 if (r
!= HS_SUCCESS
) {
211 if (error
) *error
= "couldn't allocate scratch";
212 hs_free_database(db
);
216 hs_free_database(filter_db
);
218 state
= FILTER_LOADED
;
220 sendto_realops_snomask(SNO_GENERAL
, L_ALL
| L_NETWIDE
,
221 "New filters loaded.");
222 rb_free(filter_data
);
229 if (error
) *error
= "unknown command or data doesn't start with +";
235 if (state
== FILTER_FILLING
) {
237 unsigned char *d
= rb_base64_decode((unsigned char *)data
, strlen(data
), &dl
);
239 if (error
) *error
= "invalid data";
242 if (filter_data_len
+ dl
> 10000000ul) {
243 if (error
) *error
= "data over size limit";
247 filter_data
= rb_realloc(filter_data
, filter_data_len
+ dl
);
248 memcpy(filter_data
+ filter_data_len
, d
, dl
);
250 filter_data_len
+= dl
;
252 if (error
) *error
= "send \"new\" first";
258 /* /SETFILTER [server-mask] <check> { NEW | APPLY | <data> }
259 * <check> must be the same for the entirety of a new...data...apply run,
260 * and exists just to ensure runs don't mix
261 * NEW prepares a buffer to receive a hyperscan database
262 * <data> is base64 encoded chunks of hyperscan database, which are decoded
263 * and appended to the buffer
264 * APPLY deserialises the buffer and sets the resulting hyperscan database
265 * as the one to use for filtering */
267 mo_setfilter(struct MsgBuf
*msgbuf
, struct Client
*client_p
, struct Client
*source_p
, int parc
, const char **parv
)
272 if (!IsOperAdmin(source_p
)) {
273 sendto_one(source_p
, form_str(ERR_NOPRIVS
), me
.name
, source_p
->name
, "admin");
279 if(match(parv
[1], me
.name
)) {
282 sendto_match_servs(source_p
, parv
[1],
284 "ENCAP %s SETFILTER %s :%s", parv
[1], check
, data
);
285 } else if (parc
== 3) {
290 sendto_one_notice(source_p
, ":SETFILTER needs 2 or 3 params, have %d", parc
- 1);
295 int r
= setfilter(check
, data
, &error
);
297 sendto_one_notice(source_p
, ":SETFILTER failed: %s", error
);
299 sendto_one_notice(source_p
, ":SETFILTER ok");
305 me_setfilter(struct MsgBuf
*msgbuf
, struct Client
*client_p
, struct Client
*source_p
, int parc
, const char **parv
)
307 if(!IsPerson(source_p
))
311 int r
= setfilter(parv
[1], parv
[2], &error
);
313 sendto_one_notice(source_p
, ":SETFILTER failed: %s", error
);
319 /* will be called for every match
320 * hyperscan provides us one piece of information about the expression
321 * matched, an integer ID. we're co-opting the lowest 3 bits of this
322 * as a flag set. conveniently, this means all we really need to do
323 * here is or the IDs together. */
324 int match_callback(unsigned id
,
325 unsigned long long from
,
326 unsigned long long to
,
330 unsigned *context
= context_
;
335 static char check_buffer
[2000];
336 static char clean_buffer
[BUFSIZE
];
338 unsigned match_message(const char *prefix
,
339 struct Client
*source
,
351 snprintf(check_buffer
, sizeof check_buffer
, "%s:%s!%s@%s#%c %s%s%s :%s",
368 source
->user
&& source
->user
->suser
[0] != '\0' ? '1' : '0',
371 target
? target
: "",
373 hs_error_t r
= hs_scan(filter_db
, check_buffer
, strlen(check_buffer
), 0, filter_scratch
, match_callback
, &state
);
374 if (r
!= HS_SUCCESS
&& r
!= HS_SCAN_TERMINATED
)
380 filter_msg_user(void *data_
)
382 hook_data_privmsg_user
*data
= data_
;
383 struct Client
*s
= data
->source_p
;
384 /* we only need to filter once */
388 /* opers are immune to checking, for obvious reasons
389 * anything sent to an oper is also immune, because that should make it
390 * less impossible to deal with reports. */
391 if (IsOper(s
) || IsOper(data
->target_p
)) {
394 if (data
->target_p
->umodes
& filter_umode
) {
397 char *text
= strcpy(clean_buffer
, data
->text
);
399 strip_unprintable(text
);
400 unsigned r
= match_message("0", s
, cmdname
[data
->msgtype
], "0", data
->text
) |
401 match_message("1", s
, cmdname
[data
->msgtype
], "0", text
);
403 if (data
->msgtype
== MESSAGE_TYPE_PRIVMSG
) {
404 sendto_one_numeric(s
, ERR_CANNOTSENDTOCHAN
,
405 form_str(ERR_CANNOTSENDTOCHAN
),
406 data
->target_p
->name
);
411 sendto_realops_snomask(SNO_GENERAL
, L_ALL
| L_NETWIDE
,
412 "FILTER: %s!%s@%s [%s]",
413 s
->name
, s
->username
, s
->host
, s
->sockhost
);
417 exit_client(NULL
, s
, s
, FILTER_EXIT_MSG
);
422 filter_msg_channel(void *data_
)
424 hook_data_privmsg_channel
*data
= data_
;
425 struct Client
*s
= data
->source_p
;
426 /* we only need to filter once */
430 /* just normal oper immunity for channels. i'd like to have a mode that
431 * disables the filter per-channel, but that's for the future */
435 if (data
->chptr
->mode
.mode
& filter_chmode
) {
438 char *text
= strcpy(clean_buffer
, data
->text
);
440 strip_unprintable(text
);
441 unsigned r
= match_message("0", s
, cmdname
[data
->msgtype
], data
->chptr
->chname
, data
->text
) |
442 match_message("1", s
, cmdname
[data
->msgtype
], data
->chptr
->chname
, text
);
444 if (data
->msgtype
== MESSAGE_TYPE_PRIVMSG
) {
445 sendto_one_numeric(s
, ERR_CANNOTSENDTOCHAN
,
446 form_str(ERR_CANNOTSENDTOCHAN
),
447 data
->chptr
->chname
);
452 sendto_realops_snomask(SNO_GENERAL
, L_ALL
| L_NETWIDE
,
453 "FILTER: %s!%s@%s [%s]",
454 s
->name
, s
->username
, s
->host
, s
->sockhost
);
458 exit_client(NULL
, s
, s
, FILTER_EXIT_MSG
);
463 filter_client_quit(void *data_
)
465 hook_data_client_quit
*data
= data_
;
466 struct Client
*s
= data
->client
;
470 char *text
= strcpy(clean_buffer
, data
->orig_reason
);
472 strip_unprintable(text
);
473 unsigned r
= match_message("0", s
, "QUIT", NULL
, data
->orig_reason
) |
474 match_message("1", s
, "QUIT", NULL
, text
);
479 sendto_realops_snomask(SNO_GENERAL
, L_ALL
| L_NETWIDE
,
480 "FILTER: %s!%s@%s [%s]",
481 s
->name
, s
->username
, s
->host
, s
->sockhost
);
483 /* No point in doing anything with ACT_KILL */
487 on_client_exit(void *data_
)
489 /* If we see a netsplit, abort the current FILTER_FILLING attempt */
490 hook_data_client_exit
*data
= data_
;
492 if (!IsServer(data
->target
)) return;
494 if (state
== FILTER_FILLING
) {
495 state
= filter_db
? FILTER_LOADED
: FILTER_EMPTY
;