]> jfr.im git - solanum.git/blame - extensions/filter.c
Merge pull request #302 from edk0/sasl-usercloak
[solanum.git] / extensions / filter.c
CommitLineData
a2d9c494
EK
1/*
2 * ircd-ratbox: A slightly useful ircd.
3 * filter.c: Drop messages we don't like
4 *
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
8 *
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.
13 *
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.
18 *
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
22 * USA
23 *
24 * $Id$
25 */
26
27#include "stdinc.h"
28#include "channel.h"
29#include "client.h"
8692240f 30#include "chmode.h"
a2d9c494
EK
31#include "match.h"
32#include "ircd.h"
33#include "numeric.h"
34#include "send.h"
a2d9c494 35#include "s_newconf.h"
8692240f
EK
36#include "s_serv.h"
37#include "s_user.h"
a2d9c494
EK
38#include "msg.h"
39#include "parse.h"
40#include "modules.h"
41#include "operhash.h"
42#include "inline/stringops.h"
43#include "msgbuf.h"
44
45#include <hs_common.h>
46#include <hs_runtime.h>
47
81e41406
EK
48#define FILTER_NICK 0
49#define FILTER_USER 0
50#define FILTER_HOST 0
51
649bda6d
EK
52#define FILTER_EXIT_MSG "Connection closed"
53
a8f402e9
EK
54static const char filter_desc[] = "Filter messages using a precompiled Hyperscan database";
55
a2d9c494
EK
56static void filter_msg_user(void *data);
57static void filter_msg_channel(void *data);
58static void on_client_exit(void *data);
59
60static void mo_setfilter(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
61static void me_setfilter(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
62
63static char *filter_data = NULL;
64static size_t filter_data_len = 0;
65static hs_database_t *filter_db;
66static hs_scratch_t *filter_scratch;
67
68static int filter_enable = 1;
69
70static const char *cmdname[MESSAGE_TYPE_COUNT] = {
71 [MESSAGE_TYPE_PRIVMSG] = "PRIVMSG",
72 [MESSAGE_TYPE_NOTICE] = "NOTICE",
73};
74
75enum filter_state {
76 FILTER_EMPTY,
77 FILTER_FILLING,
78 FILTER_LOADED
79};
80
81#define ACT_DROP (1 << 0)
82#define ACT_KILL (1 << 1)
83#define ACT_ALARM (1 << 2)
84
85static enum filter_state state = FILTER_EMPTY;
86static char check_str[21] = "";
87
8692240f
EK
88static unsigned filter_chmode, filter_umode;
89
a2d9c494
EK
90mapi_hfn_list_av1 filter_hfnlist[] = {
91 { "privmsg_user", (hookfn) filter_msg_user },
92 { "privmsg_channel", (hookfn) filter_msg_channel },
93 { "client_exit", (hookfn) on_client_exit },
94 { NULL, NULL }
95};
96
97
98struct Message setfilter_msgtab = {
99 "SETFILTER", 0, 0, 0, 0,
100 {mg_unreg, mg_not_oper, mg_ignore, mg_ignore, {me_setfilter, 2}, {mo_setfilter, 2}}
101};
102
eb06afc3 103static int
8692240f
EK
104modinit(void)
105{
106 filter_umode = user_modes['u'] = find_umode_slot();
107 construct_umodebuf();
108 filter_chmode = cflag_add('u', chm_simple);
eb06afc3 109 return 0;
8692240f
EK
110}
111
a2d9c494
EK
112static void
113moddeinit(void)
114{
8692240f
EK
115 if (filter_umode) {
116 user_modes['u'] = 0;
117 construct_umodebuf();
118 }
119 if (filter_chmode)
120 cflag_orphan('u');
a2d9c494
EK
121 if (filter_scratch)
122 hs_free_scratch(filter_scratch);
123 if (filter_db)
124 hs_free_database(filter_db);
125 if (filter_data)
126 rb_free(filter_data);
127}
128
129
130mapi_clist_av1 filter_clist[] = { &setfilter_msgtab, NULL };
131
a8f402e9 132DECLARE_MODULE_AV2(filter, modinit, moddeinit, filter_clist, NULL, filter_hfnlist, NULL, "0.4", filter_desc);
a2d9c494
EK
133
134static int
135setfilter(const char *check, const char *data, const char **error)
136{
137 if (error) *error = "unknown";
138
139 if (!strcasecmp(data, "disable")) {
140 filter_enable = 0;
141 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
142 "Filtering disabled.");
143 return 0;
144 }
145 if (!strcasecmp(data, "enable")) {
146 filter_enable = 1;
147 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
148 "Filtering enabled.");
149 return 0;
150 }
151
152 if (strlen(check) > sizeof check_str - 1) {
153 if (error) *error = "check string too long";
154 return -1;
155 }
156
157 if (!strcasecmp(data, "new")) {
158 if (state == FILTER_FILLING) {
159 rb_free(filter_data);
160 filter_data = 0;
161 filter_data_len = 0;
162 }
163 state = FILTER_FILLING;
164 strcpy(check_str, check);
165 return 0;
166 }
167
344af24c
EK
168 if (!strcasecmp(data, "drop")) {
169 if (!filter_db) {
170 if (error) *error = "no database to drop";
171 return -1;
172 }
173 hs_free_database(filter_db);
174 filter_db = 0;
175 return 0;
176 }
177
178 if (!strcasecmp(data, "abort")) {
179 if (state != FILTER_FILLING) {
180 if (error) *error = "not filling";
181 return -1;
182 }
183 state = filter_db ? FILTER_LOADED : FILTER_EMPTY;
184 rb_free(filter_data);
185 filter_data = 0;
186 filter_data_len = 0;
187 return 0;
188 }
189
a2d9c494
EK
190 if (strcmp(check, check_str) != 0) {
191 if (error) *error = "check strings don't match";
192 return -1;
193 }
194
195 if (!strcasecmp(data, "apply")) {
196 if (state != FILTER_FILLING) {
197 if (error) *error = "not loading anything";
198 return -1;
199 }
200 hs_database_t *db;
201 hs_error_t r = hs_deserialize_database(filter_data, filter_data_len, &db);
202 if (r != HS_SUCCESS) {
203 if (error) *error = "couldn't deserialize db";
204 return -1;
205 }
206 r = hs_alloc_scratch(db, &filter_scratch);
207 if (r != HS_SUCCESS) {
208 if (error) *error = "couldn't allocate scratch";
09784400 209 hs_free_database(db);
a2d9c494
EK
210 return -1;
211 }
212 if (filter_db) {
213 hs_free_database(filter_db);
214 }
215 state = FILTER_LOADED;
216 filter_db = db;
217 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
218 "New filters loaded.");
219 rb_free(filter_data);
220 filter_data = 0;
221 filter_data_len = 0;
222 return 0;
223 }
224
dc141aad
EK
225 if (*data != '+') {
226 if (error) *error = "unknown command or data doesn't start with +";
227 return -1;
228 }
229
230 data += 1;
231
a2d9c494
EK
232 if (state == FILTER_FILLING) {
233 int dl;
0cbb1ba9 234 unsigned char *d = rb_base64_decode((unsigned char *)data, strlen(data), &dl);
a2d9c494
EK
235 if (!d) {
236 if (error) *error = "invalid data";
237 return -1;
238 }
239 if (filter_data_len + dl > 10000000ul) {
240 if (error) *error = "data over size limit";
241 rb_free(d);
242 return -1;
243 }
244 filter_data = rb_realloc(filter_data, filter_data_len + dl);
245 memcpy(filter_data + filter_data_len, d, dl);
246 rb_free(d);
247 filter_data_len += dl;
248 } else {
249 if (error) *error = "send \"new\" first";
250 return -1;
251 }
252 return 0;
253}
254
255/* /SETFILTER [server-mask] <check> { NEW | APPLY | <data> }
256 * <check> must be the same for the entirety of a new...data...apply run,
257 * and exists just to ensure runs don't mix
258 * NEW prepares a buffer to receive a hyperscan database
259 * <data> is base64 encoded chunks of hyperscan database, which are decoded
260 * and appended to the buffer
261 * APPLY deserialises the buffer and sets the resulting hyperscan database
262 * as the one to use for filtering */
263static void
264mo_setfilter(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char **parv)
265{
266 int for_me = 0;
267 const char *check;
268 const char *data;
269 if (!IsOperAdmin(source_p)) {
270 sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "admin");
271 return;
272 }
273 if (parc == 4) {
274 check = parv[2];
275 data = parv[3];
276 if(match(parv[1], me.name)) {
277 for_me = 1;
278 }
279 sendto_match_servs(source_p, parv[1],
280 CAP_ENCAP, NOCAPS,
281 "ENCAP %s SETFILTER %s :%s", parv[1], check, data);
282 } else if (parc == 3) {
283 check = parv[1];
284 data = parv[2];
285 for_me = 1;
286 } else {
287 sendto_one_notice(source_p, ":SETFILTER needs 2 or 3 params, have %d", parc - 1);
288 return;
289 }
290 if (for_me) {
291 const char *error;
292 int r = setfilter(check, data, &error);
293 if (r) {
294 sendto_one_notice(source_p, ":SETFILTER failed: %s", error);
295 } else {
296 sendto_one_notice(source_p, ":SETFILTER ok");
297 }
298 }
299}
300
301static void
302me_setfilter(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char **parv)
303{
304 if(!IsPerson(source_p))
305 return;
306
307 const char *error;
308 int r = setfilter(parv[1], parv[2], &error);
309 if (r) {
310 sendto_one_notice(source_p, ":SETFILTER failed: %s", error);
311 }
312
313 return;
314}
315
316/* will be called for every match
317 * hyperscan provides us one piece of information about the expression
318 * matched, an integer ID. we're co-opting the lowest 3 bits of this
319 * as a flag set. conveniently, this means all we really need to do
320 * here is or the IDs together. */
321int match_callback(unsigned id,
322 unsigned long long from,
323 unsigned long long to,
324 unsigned flags,
325 void *context_)
326{
327 unsigned *context = context_;
328 *context |= id;
329 return 0;
330}
331
744ac308
EK
332static char check_buffer[2000];
333static char clean_buffer[BUFSIZE];
a2d9c494 334
744ac308
EK
335unsigned match_message(const char *prefix,
336 struct Client *source,
337 const char *command,
338 const char *target,
339 const char *msg)
a2d9c494
EK
340{
341 unsigned state = 0;
342 if (!filter_enable)
343 return 0;
344 if (!filter_db)
345 return 0;
744ac308
EK
346 snprintf(check_buffer, sizeof check_buffer, "%s:%s!%s@%s#%c %s %s :%s",
347 prefix,
348#if FILTER_NICK
349 source->name,
350#else
351 "*",
352#endif
353#if FILTER_USER
354 source->username,
355#else
356 "*",
357#endif
358#if FILTER_HOST
359 source->host,
360#else
361 "*",
362#endif
363 source->user && source->user->suser[0] != '\0' ? '1' : '0',
364 command, target,
365 msg);
366 hs_error_t r = hs_scan(filter_db, check_buffer, strlen(check_buffer), 0, filter_scratch, match_callback, &state);
a2d9c494
EK
367 if (r != HS_SUCCESS && r != HS_SCAN_TERMINATED)
368 return 0;
369 return state;
370}
371
a2d9c494
EK
372void
373filter_msg_user(void *data_)
374{
375 hook_data_privmsg_user *data = data_;
376 struct Client *s = data->source_p;
86ee00db
EK
377 /* we only need to filter once */
378 if (!MyClient(s)) {
379 return;
380 }
a2d9c494
EK
381 /* opers are immune to checking, for obvious reasons
382 * anything sent to an oper is also immune, because that should make it
383 * less impossible to deal with reports. */
384 if (IsOper(s) || IsOper(data->target_p)) {
385 return;
386 }
8692240f
EK
387 if (data->target_p->umodes & filter_umode) {
388 return;
389 }
f982238e 390 char *text = strcpy(clean_buffer, data->text);
a2d9c494
EK
391 strip_colour(text);
392 strip_unprintable(text);
744ac308
EK
393 unsigned r = match_message("0", s, cmdname[data->msgtype], "0", data->text) |
394 match_message("1", s, cmdname[data->msgtype], "0", text);
7bb7f899 395 if (r & ACT_DROP) {
d928bc7a
EK
396 if (data->msgtype == MESSAGE_TYPE_PRIVMSG) {
397 sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
398 form_str(ERR_CANNOTSENDTOCHAN),
399 data->target_p->name);
400 }
a2d9c494
EK
401 data->approved = 1;
402 }
86ee00db 403 if (r & ACT_ALARM) {
6a14bf78 404 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
a2d9c494
EK
405 "FILTER: %s!%s@%s [%s]",
406 s->name, s->username, s->host, s->sockhost);
407 }
6a14bf78 408 if (r & ACT_KILL) {
7bb7f899 409 data->approved = 1;
649bda6d 410 exit_client(NULL, s, s, FILTER_EXIT_MSG);
6a14bf78 411 }
a2d9c494
EK
412}
413
414void
415filter_msg_channel(void *data_)
416{
417 hook_data_privmsg_channel *data = data_;
418 struct Client *s = data->source_p;
86ee00db
EK
419 /* we only need to filter once */
420 if (!MyClient(s)) {
421 return;
422 }
a2d9c494
EK
423 /* just normal oper immunity for channels. i'd like to have a mode that
424 * disables the filter per-channel, but that's for the future */
425 if (IsOper(s)) {
426 return;
427 }
8692240f
EK
428 if (data->chptr->mode.mode & filter_chmode) {
429 return;
430 }
f982238e 431 char *text = strcpy(clean_buffer, data->text);
a2d9c494
EK
432 strip_colour(text);
433 strip_unprintable(text);
744ac308
EK
434 unsigned r = match_message("0", s, cmdname[data->msgtype], data->chptr->chname, data->text) |
435 match_message("1", s, cmdname[data->msgtype], data->chptr->chname, text);
7bb7f899 436 if (r & ACT_DROP) {
d928bc7a
EK
437 if (data->msgtype == MESSAGE_TYPE_PRIVMSG) {
438 sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
439 form_str(ERR_CANNOTSENDTOCHAN),
440 data->chptr->chname);
441 }
a2d9c494
EK
442 data->approved = 1;
443 }
86ee00db 444 if (r & ACT_ALARM) {
6a14bf78 445 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
a2d9c494
EK
446 "FILTER: %s!%s@%s [%s]",
447 s->name, s->username, s->host, s->sockhost);
448 }
6a14bf78 449 if (r & ACT_KILL) {
7bb7f899 450 data->approved = 1;
649bda6d 451 exit_client(NULL, s, s, FILTER_EXIT_MSG);
6a14bf78 452 }
a2d9c494
EK
453}
454
455void
456on_client_exit(void *data_)
457{
458 /* If we see a netsplit, abort the current FILTER_FILLING attempt */
459 hook_data_client_exit *data = data_;
460
461 if (!IsServer(data->target)) return;
462
463 if (state == FILTER_FILLING) {
464 state = filter_db ? FILTER_LOADED : FILTER_EMPTY;
465 }
466}
467