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