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