]> jfr.im git - solanum.git/blob - extensions/filter.c
85239bdb3847b7d917024a64151af76cfc192b8e
[solanum.git] / extensions / filter.c
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"
30 #include "chmode.h"
31 #include "match.h"
32 #include "ircd.h"
33 #include "numeric.h"
34 #include "send.h"
35 #include "s_newconf.h"
36 #include "s_serv.h"
37 #include "s_user.h"
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
48 #define FILTER_NICK 0
49 #define FILTER_USER 0
50 #define FILTER_HOST 0
51
52 #define FILTER_EXIT_MSG "Connection closed"
53
54 static const char filter_desc[] = "Filter messages using a precompiled Hyperscan database";
55
56 static void filter_msg_user(void *data);
57 static void filter_msg_channel(void *data);
58 static void on_client_exit(void *data);
59
60 static void mo_setfilter(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
61 static void me_setfilter(struct MsgBuf *, struct Client *, struct Client *, int, const char **);
62
63 static char *filter_data = NULL;
64 static size_t filter_data_len = 0;
65 static hs_database_t *filter_db;
66 static hs_scratch_t *filter_scratch;
67
68 static int filter_enable = 1;
69
70 static const char *cmdname[MESSAGE_TYPE_COUNT] = {
71 [MESSAGE_TYPE_PRIVMSG] = "PRIVMSG",
72 [MESSAGE_TYPE_NOTICE] = "NOTICE",
73 };
74
75 enum 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
85 static enum filter_state state = FILTER_EMPTY;
86 static char check_str[21] = "";
87
88 static unsigned filter_chmode, filter_umode;
89
90 mapi_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
98 struct 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
103 static int
104 modinit(void)
105 {
106 filter_umode = user_modes['u'] = find_umode_slot();
107 construct_umodebuf();
108 filter_chmode = cflag_add('u', chm_simple);
109 return 0;
110 }
111
112 static void
113 moddeinit(void)
114 {
115 if (filter_umode) {
116 user_modes['u'] = 0;
117 construct_umodebuf();
118 }
119 if (filter_chmode)
120 cflag_orphan('u');
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
130 mapi_clist_av1 filter_clist[] = { &setfilter_msgtab, NULL };
131
132 DECLARE_MODULE_AV2(filter, modinit, moddeinit, filter_clist, NULL, filter_hfnlist, NULL, "0.4", filter_desc);
133
134 static int
135 setfilter(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
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
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";
209 return -1;
210 }
211 if (filter_db) {
212 hs_free_database(filter_db);
213 }
214 state = FILTER_LOADED;
215 filter_db = db;
216 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
217 "New filters loaded.");
218 rb_free(filter_data);
219 filter_data = 0;
220 filter_data_len = 0;
221 return 0;
222 }
223
224 if (*data != '+') {
225 if (error) *error = "unknown command or data doesn't start with +";
226 return -1;
227 }
228
229 data += 1;
230
231 if (state == FILTER_FILLING) {
232 int dl;
233 unsigned char *d = rb_base64_decode((unsigned char *)data, strlen(data), &dl);
234 if (!d) {
235 if (error) *error = "invalid data";
236 return -1;
237 }
238 if (filter_data_len + dl > 10000000ul) {
239 if (error) *error = "data over size limit";
240 rb_free(d);
241 return -1;
242 }
243 filter_data = rb_realloc(filter_data, filter_data_len + dl);
244 memcpy(filter_data + filter_data_len, d, dl);
245 rb_free(d);
246 filter_data_len += dl;
247 } else {
248 if (error) *error = "send \"new\" first";
249 return -1;
250 }
251 return 0;
252 }
253
254 /* /SETFILTER [server-mask] <check> { NEW | APPLY | <data> }
255 * <check> must be the same for the entirety of a new...data...apply run,
256 * and exists just to ensure runs don't mix
257 * NEW prepares a buffer to receive a hyperscan database
258 * <data> is base64 encoded chunks of hyperscan database, which are decoded
259 * and appended to the buffer
260 * APPLY deserialises the buffer and sets the resulting hyperscan database
261 * as the one to use for filtering */
262 static void
263 mo_setfilter(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char **parv)
264 {
265 int for_me = 0;
266 const char *check;
267 const char *data;
268 if (!IsOperAdmin(source_p)) {
269 sendto_one(source_p, form_str(ERR_NOPRIVS), me.name, source_p->name, "admin");
270 return;
271 }
272 if (parc == 4) {
273 check = parv[2];
274 data = parv[3];
275 if(match(parv[1], me.name)) {
276 for_me = 1;
277 }
278 sendto_match_servs(source_p, parv[1],
279 CAP_ENCAP, NOCAPS,
280 "ENCAP %s SETFILTER %s :%s", parv[1], check, data);
281 } else if (parc == 3) {
282 check = parv[1];
283 data = parv[2];
284 for_me = 1;
285 } else {
286 sendto_one_notice(source_p, ":SETFILTER needs 2 or 3 params, have %d", parc - 1);
287 return;
288 }
289 if (for_me) {
290 const char *error;
291 int r = setfilter(check, data, &error);
292 if (r) {
293 sendto_one_notice(source_p, ":SETFILTER failed: %s", error);
294 } else {
295 sendto_one_notice(source_p, ":SETFILTER ok");
296 }
297 }
298 }
299
300 static void
301 me_setfilter(struct MsgBuf *msgbuf, struct Client *client_p, struct Client *source_p, int parc, const char **parv)
302 {
303 if(!IsPerson(source_p))
304 return;
305
306 const char *error;
307 int r = setfilter(parv[1], parv[2], &error);
308 if (r) {
309 sendto_one_notice(source_p, ":SETFILTER failed: %s", error);
310 }
311
312 return;
313 }
314
315 /* will be called for every match
316 * hyperscan provides us one piece of information about the expression
317 * matched, an integer ID. we're co-opting the lowest 3 bits of this
318 * as a flag set. conveniently, this means all we really need to do
319 * here is or the IDs together. */
320 int match_callback(unsigned id,
321 unsigned long long from,
322 unsigned long long to,
323 unsigned flags,
324 void *context_)
325 {
326 unsigned *context = context_;
327 *context |= id;
328 return 0;
329 }
330
331 static char check_buffer[2000];
332 static char clean_buffer[BUFSIZE];
333
334 unsigned match_message(const char *prefix,
335 struct Client *source,
336 const char *command,
337 const char *target,
338 const char *msg)
339 {
340 unsigned state = 0;
341 if (!filter_enable)
342 return 0;
343 if (!filter_db)
344 return 0;
345 snprintf(check_buffer, sizeof check_buffer, "%s:%s!%s@%s#%c %s %s :%s",
346 prefix,
347 #if FILTER_NICK
348 source->name,
349 #else
350 "*",
351 #endif
352 #if FILTER_USER
353 source->username,
354 #else
355 "*",
356 #endif
357 #if FILTER_HOST
358 source->host,
359 #else
360 "*",
361 #endif
362 source->user && source->user->suser[0] != '\0' ? '1' : '0',
363 command, target,
364 msg);
365 hs_error_t r = hs_scan(filter_db, check_buffer, strlen(check_buffer), 0, filter_scratch, match_callback, &state);
366 if (r != HS_SUCCESS && r != HS_SCAN_TERMINATED)
367 return 0;
368 return state;
369 }
370
371 void
372 filter_msg_user(void *data_)
373 {
374 hook_data_privmsg_user *data = data_;
375 struct Client *s = data->source_p;
376 /* we only need to filter once */
377 if (!MyClient(s)) {
378 return;
379 }
380 /* opers are immune to checking, for obvious reasons
381 * anything sent to an oper is also immune, because that should make it
382 * less impossible to deal with reports. */
383 if (IsOper(s) || IsOper(data->target_p)) {
384 return;
385 }
386 if (data->target_p->umodes & filter_umode) {
387 return;
388 }
389 char *text = strcpy(clean_buffer, data->text);
390 strip_colour(text);
391 strip_unprintable(text);
392 unsigned r = match_message("0", s, cmdname[data->msgtype], "0", data->text) |
393 match_message("1", s, cmdname[data->msgtype], "0", text);
394 if (r & ACT_DROP) {
395 if (data->msgtype == MESSAGE_TYPE_PRIVMSG) {
396 sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
397 form_str(ERR_CANNOTSENDTOCHAN),
398 data->target_p->name);
399 }
400 data->approved = 1;
401 }
402 if (r & ACT_ALARM) {
403 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
404 "FILTER: %s!%s@%s [%s]",
405 s->name, s->username, s->host, s->sockhost);
406 }
407 if (r & ACT_KILL) {
408 data->approved = 1;
409 exit_client(NULL, s, s, FILTER_EXIT_MSG);
410 }
411 }
412
413 void
414 filter_msg_channel(void *data_)
415 {
416 hook_data_privmsg_channel *data = data_;
417 struct Client *s = data->source_p;
418 /* we only need to filter once */
419 if (!MyClient(s)) {
420 return;
421 }
422 /* just normal oper immunity for channels. i'd like to have a mode that
423 * disables the filter per-channel, but that's for the future */
424 if (IsOper(s)) {
425 return;
426 }
427 if (data->chptr->mode.mode & filter_chmode) {
428 return;
429 }
430 char *text = strcpy(clean_buffer, data->text);
431 strip_colour(text);
432 strip_unprintable(text);
433 unsigned r = match_message("0", s, cmdname[data->msgtype], data->chptr->chname, data->text) |
434 match_message("1", s, cmdname[data->msgtype], data->chptr->chname, text);
435 if (r & ACT_DROP) {
436 if (data->msgtype == MESSAGE_TYPE_PRIVMSG) {
437 sendto_one_numeric(s, ERR_CANNOTSENDTOCHAN,
438 form_str(ERR_CANNOTSENDTOCHAN),
439 data->chptr->chname);
440 }
441 data->approved = 1;
442 }
443 if (r & ACT_ALARM) {
444 sendto_realops_snomask(SNO_GENERAL, L_ALL | L_NETWIDE,
445 "FILTER: %s!%s@%s [%s]",
446 s->name, s->username, s->host, s->sockhost);
447 }
448 if (r & ACT_KILL) {
449 data->approved = 1;
450 exit_client(NULL, s, s, FILTER_EXIT_MSG);
451 }
452 }
453
454 void
455 on_client_exit(void *data_)
456 {
457 /* If we see a netsplit, abort the current FILTER_FILLING attempt */
458 hook_data_client_exit *data = data_;
459
460 if (!IsServer(data->target)) return;
461
462 if (state == FILTER_FILLING) {
463 state = filter_db ? FILTER_LOADED : FILTER_EMPTY;
464 }
465 }
466