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