]> jfr.im git - irc/freenode/syn.git/blame - masks.c
add licence info
[irc/freenode/syn.git] / masks.c
CommitLineData
0a30e865
SB
1/*
2 * syn: a utility bot to manage IRC network access
3 * Copyright (C) 2009-2016 Stephen Bennett
4 *
5 * This program is free software: you can redistribute it and/or modify
6 * it under the terms of the GNU Affero General Public License as
7 * published by the Free Software Foundation, either version 3 of the
8 * License, or (at your option) any later version.
9 *
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU Affero General Public License for more details.
14 *
15 * You should have received a copy of the GNU Affero General Public License
16 * along with this program. If not, see <https://www.gnu.org/licenses/>.
17 */
18
19
ca6bbb10
SB
20#include "atheme.h"
21
22#include "syn.h"
23
52b278d6 24static void masks_newuser(hook_user_nick_t *data);
ca6bbb10
SB
25
26static void syn_cmd_addmask(sourceinfo_t *si, int parc, char **parv);
27static void syn_cmd_delmask(sourceinfo_t *si, int parc, char **parv);
28static void syn_cmd_setmask(sourceinfo_t *si, int parc, char **parv);
b45a5ca4 29static void syn_cmd_listmask(sourceinfo_t *si, int parc, char **parv);
ca6bbb10 30
9ecbc9a7
JK
31command_t syn_addmask = { "ADDMASK", N_("Adds a lethal, suspicious or exempt mask"), "syn:general", 1, syn_cmd_addmask, { .path = "syn/addmask" } };
32command_t syn_delmask = { "DELMASK", N_("Removes a lethal, suspicious or exempt mask"), "syn:general", 1, syn_cmd_delmask, { .path = "syn/delmask" } };
33command_t syn_setmask = { "SETMASK", N_("Modifies settings for a lethal, suspicious or exempt mask"), "syn:general", 1, syn_cmd_setmask, { .path = "syn/setmask" } };
34command_t syn_listmask = { "LISTMASK", N_("Displays configured mask lists"), "syn:general", 1, syn_cmd_listmask, { .path = "syn/listmask" } };
ca6bbb10
SB
35
36static unsigned int lethal_mask_duration = 3600*24;
37static char *lethal_mask_message = NULL;
38
b918d5d9
SB
39static mowgli_eventloop_timer_t *expire_masks_timer;
40
ca6bbb10
SB
41typedef enum
42{
43 mask_exempt,
44 mask_suspicious,
45 mask_lethal,
46 mask_unknown
47} mask_type;
48
49typedef struct
50{
51 char *regex;
52 atheme_regex_t *re;
53 int reflags;
4db6f543 54
ca6bbb10 55 mask_type type;
4db6f543 56
ca6bbb10 57 time_t expires;
4db6f543 58 time_t added;
52267ba7 59 time_t last_match;
4db6f543 60 char setter[NICKLEN*2+2];
ca6bbb10
SB
61} mask_t;
62
b918d5d9 63mowgli_list_t masks;
ca6bbb10
SB
64
65struct {
66 const char *s;
67 mask_type t;
68} mask_string_map[] = {
69 { "exempt", mask_exempt },
70 { "suspicious", mask_suspicious },
71 { "lethal", mask_lethal },
72 { NULL, 0 }
73};
74
75const char *string_from_mask_type(mask_type t)
76{
77 for (int i=0; mask_string_map[i].s != NULL; ++i)
78 if (mask_string_map[i].t == t)
79 return mask_string_map[i].s;
80 return NULL;
81}
82
83mask_type mask_type_from_string(const char *s)
84{
85 for (int i=0; mask_string_map[i].s != NULL; ++i)
86 if (0 == strcasecmp(mask_string_map[i].s, s))
87 return mask_string_map[i].t;
88 return mask_unknown;
89}
90
b45a5ca4
SB
91static void check_expiry(void *v)
92{
b918d5d9
SB
93 mowgli_node_t *n, *tn;
94 MOWGLI_LIST_FOREACH_SAFE(n, tn, masks.head)
b45a5ca4
SB
95 {
96 mask_t *m = n->data;
97
98 if (m->expires == 0)
99 continue;
100 if (m->expires > CURRTIME)
101 continue;
102
103 syn_report("Expiring %s mask \2%s\2", string_from_mask_type(m->type), m->regex);
104 regex_destroy(m->re);
105 free(m->regex);
106 free(m);
b918d5d9 107 mowgli_node_delete(n, &masks);
b45a5ca4
SB
108 }
109}
ca6bbb10 110
b19af353
SB
111static void save_maskdb()
112{
b918d5d9 113 mowgli_node_t *n;
b19af353
SB
114 FILE *f = fopen(DATADIR "/masks.db", "w");
115 if (!f)
116 {
117 slog(LG_ERROR, "Couldn't open masks.db for writing: %s", strerror(errno));
118 return;
119 }
120
b918d5d9 121 MOWGLI_LIST_FOREACH(n, masks.head)
b19af353
SB
122 {
123 mask_t *m = n->data;
124
52267ba7 125 fprintf(f, "/%s/%s %d %s %lu %lu %lu\n",
b19af353 126 m->regex, m->reflags & AREGEX_ICASE ? "i" : "",
52267ba7 127 m->type, m->setter, m->added, m->expires, m->last_match);
b19af353
SB
128 }
129 fclose(f);
130}
131
132static void load_maskdb()
133{
134 FILE *f = fopen(DATADIR "/masks.db", "r");
135 if (!f)
136 {
137 slog(LG_DEBUG, "Couldn't open masks db for reading: %s", strerror(errno));
138 return;
139 }
140
141 char line[BUFSIZE*2];
142 while(fgets(line, sizeof(line), f))
143 {
144 char *args = line;
145 int flags = 0;
146 char *regex = regex_extract(args, &args, &flags);
147
148 atheme_regex_t *re= regex_create(regex, flags);
149
150 if (!re || !regex)
151 {
152 slog(LG_DEBUG, "Invalid entry %s in masks db", line);
153 continue;
154 }
155
156 char setter[BUFSIZE*2];
157 int type;
52267ba7 158 time_t added, expires, last_match = 0;
b19af353 159
52267ba7 160 sscanf(args, "%d %s %lu %lu %lu", &type, setter, &added, &expires, &last_match);
b19af353
SB
161
162 mask_t *mask = malloc(sizeof(mask_t));
163 mask->regex = sstrdup(regex);
164 mask->reflags = flags;
165 mask->re = re;
166 strncpy(mask->setter, setter, sizeof(mask->setter));
167 mask->added = added;
168 mask->expires = expires;
52267ba7 169 mask->last_match = last_match;
b19af353
SB
170 mask->type = type;
171
b918d5d9 172 mowgli_node_add(mask, mowgli_node_create(), &masks);
b19af353
SB
173 }
174
175 fclose(f);
176}
ca6bbb10 177
492a4dc0 178static void mod_init(module_t *m)
ca6bbb10
SB
179{
180 use_syn_main_symbols(m);
b45a5ca4 181 use_syn_util_symbols(m);
ca6bbb10
SB
182 use_syn_kline_symbols(m);
183
b918d5d9
SB
184 add_uint_conf_item("lethalmask_duration", &syn->conf_table, 0, &lethal_mask_duration, 0, (unsigned int)-1, 3600*24);
185 add_dupstr_conf_item("lethalmask_message", &syn->conf_table, 0, &lethal_mask_message, "Banned");
ca6bbb10 186
b918d5d9
SB
187 service_named_bind_command("syn", &syn_addmask);
188 service_named_bind_command("syn", &syn_delmask);
189 service_named_bind_command("syn", &syn_setmask);
190 service_named_bind_command("syn", &syn_listmask);
ca6bbb10 191
52b278d6
SB
192 hook_add_event("user_nickchange");
193 hook_add_user_nickchange(masks_newuser);
ca6bbb10 194 hook_add_event("user_add");
a3064be2 195 hook_add_user_add(masks_newuser);
b45a5ca4 196
b918d5d9 197 expire_masks_timer = mowgli_timer_add(base_eventloop, "masks_check_expiry", check_expiry, NULL, 60);
b19af353
SB
198
199 load_maskdb();
ca6bbb10
SB
200}
201
492a4dc0 202static void mod_deinit(module_unload_intent_t intent)
ca6bbb10 203{
b19af353
SB
204 save_maskdb();
205
b918d5d9
SB
206 service_named_unbind_command("syn", &syn_addmask);
207 service_named_unbind_command("syn", &syn_delmask);
208 service_named_unbind_command("syn", &syn_setmask);
209 service_named_unbind_command("syn", &syn_listmask);
b45a5ca4 210
b918d5d9
SB
211 del_conf_item("lethalmask_duration", &syn->conf_table);
212 del_conf_item("lethalmask_message", &syn->conf_table);
ad487621 213
a3064be2 214 hook_del_user_add(masks_newuser);
52b278d6 215 hook_del_user_nickchange(masks_newuser);
b45a5ca4 216
b918d5d9 217 mowgli_timer_destroy(base_eventloop, expire_masks_timer);
ca6bbb10
SB
218}
219
52b278d6 220void masks_newuser(hook_user_nick_t *data)
ca6bbb10 221{
a3064be2 222 user_t *u = data->u;
ca6bbb10 223
3cafe86e
SB
224 /* If the user has already been killed, don't try to do anything */
225 if (!u)
226 return;
227
ca6bbb10
SB
228 char nuh[NICKLEN+USERLEN+HOSTLEN+GECOSLEN];
229 snprintf(nuh, sizeof(nuh), "%s!%s@%s %s", u->nick, u->user, u->host, u->gecos);
230
231 int blocked = 0, exempt = 0;
232 char *suspicious_regex = NULL, *blocked_regex = NULL;
233
b918d5d9 234 mowgli_node_t *n;
52267ba7 235 mask_t *m;
b918d5d9 236 MOWGLI_LIST_FOREACH(n, masks.head)
ca6bbb10 237 {
52267ba7 238 m = n->data;
ca6bbb10
SB
239
240 if (! regex_match(m->re, nuh))
241 continue;
242
0846976c
JK
243 m->last_match = CURRTIME;
244
ca6bbb10
SB
245 switch (m->type)
246 {
247 case mask_exempt:
248 exempt = 1;
249 break;
250 case mask_suspicious:
251 suspicious_regex = m->regex;
252 break;
253 case mask_lethal:
254 blocked = 1;
255 blocked_regex = m->regex;
256 break;
257 case mask_unknown:
258 break;
259 }
ca6bbb10
SB
260 }
261
262 if (exempt == 1)
263 return;
264
265 if (blocked == 1)
266 {
267 syn_report("Killing client %s(%s@%s) due to lethal mask %s",
268 u->nick, u->user, u->host, blocked_regex);
269 syn_kill_or_kline(u, lethal_mask_duration, lethal_mask_message);
3cafe86e 270 data->u = NULL;
ca6bbb10
SB
271 return;
272 }
273
274 if (suspicious_regex)
275 {
276 syn_report("Client %s(%s@%s) matches suspicious mask %s",
277 u->nick, u->user, u->host, suspicious_regex);
278 return;
279 }
280}
281
282void syn_cmd_addmask(sourceinfo_t *si, int parc, char **parv)
283{
284 char *pattern;
285 char *stype;
286 mask_type type;
287 mask_t *newmask;
288 int flags;
b45a5ca4 289 time_t duration = 0;
ca6bbb10
SB
290
291 char *args = parv[0];
292
293 if (args == NULL)
294 {
767e4663
SB
295 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ADDMASK");
296 command_fail(si, fault_needmoreparams, "Syntax: ADDMASK /<regex>/[i] <type>");
ca6bbb10
SB
297 return;
298 }
299
300 pattern = regex_extract(args, &args, &flags);
301 if (pattern == NULL)
302 {
767e4663
SB
303 command_fail(si, fault_badparams, STR_INVALID_PARAMS, "ADDMASK");
304 command_fail(si, fault_badparams, "Syntax: ADDMASK /<regex>/[i] <type>");
ca6bbb10
SB
305 return;
306 }
307
b45a5ca4 308 stype = strtok(args, " ");
ca6bbb10 309
c0a3a11e 310 if (!stype || *stype == '\0')
ca6bbb10 311 {
767e4663
SB
312 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "ADDMASK");
313 command_fail(si, fault_needmoreparams, "Syntax: ADDMASK /<regex>/[i] <type>");
ca6bbb10
SB
314 return;
315 }
316
317 type = mask_type_from_string(stype);
318 if (type == mask_unknown)
319 {
767e4663 320 command_fail(si, fault_badparams, "Invalid mask type \2%s\2.", stype);
ca6bbb10
SB
321 return;
322 }
323
b45a5ca4
SB
324 char *sduration = strtok(NULL, " ");
325 if (sduration && *sduration == '~')
326 {
327 duration = syn_parse_duration(++sduration);
328 }
329
b918d5d9
SB
330 mowgli_node_t *n;
331 MOWGLI_LIST_FOREACH(n, masks.head)
ca6bbb10
SB
332 {
333 mask_t *m = n->data;
334
335 if (0 == strcmp(m->regex, pattern))
336 {
767e4663 337 command_fail(si, fault_nochange, "\2%s\2 was already added (%s); not re-adding", pattern, string_from_mask_type(m->type));
ca6bbb10
SB
338 return;
339 }
340 }
341
342 atheme_regex_t *regex = regex_create(pattern, flags);
343 if (regex == NULL)
344 {
767e4663 345 command_fail(si, fault_badparams, "The provided regex \2%s\2 is invalid.", pattern);
ca6bbb10
SB
346 return;
347 }
348
349 newmask = malloc(sizeof(mask_t));
350 newmask->regex = sstrdup(pattern);
351 newmask->reflags = flags;
352 newmask->re = regex;
353 newmask->type = type;
b45a5ca4
SB
354 if (duration > 0)
355 newmask->expires = CURRTIME + 60*duration;
356 else
357 newmask->expires = 0;
ca6bbb10 358
4db6f543 359 newmask->added = CURRTIME;
52267ba7 360 newmask->last_match = 0;
4db6f543
SB
361 strncpy(newmask->setter, get_oper_name(si), sizeof(newmask->setter));
362
b918d5d9 363 mowgli_node_add(newmask, mowgli_node_create(), &masks);
b45a5ca4 364
834c9c67
JK
365 syn_report("\002ADDMASK\002 /%s/%s (%s) by %s, expires %s",
366 pattern, (flags & AREGEX_ICASE) ? "i" : "", stype, get_oper_name(si), syn_format_expiry(newmask->expires));
767e4663 367 command_success_nodata(si, "Added \2%s\2 to %s mask list, expiring %s.",
b45a5ca4 368 pattern, stype, syn_format_expiry(newmask->expires));
2b3db230
SB
369
370 save_maskdb();
ca6bbb10
SB
371}
372
373void syn_cmd_delmask(sourceinfo_t *si, int parc, char **parv)
374{
b45a5ca4
SB
375 char *args = parv[0];
376
377 if (!args)
378 {
767e4663
SB
379 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "DELMASK");
380 command_fail(si, fault_needmoreparams, "Syntax: DELMASK /<regex>/");
b45a5ca4
SB
381 return;
382 }
383
384 int flags = 0;
385 char *pattern = regex_extract(args, &args, &flags);
386
387 if (!pattern)
388 {
767e4663
SB
389 command_fail(si, fault_badparams, STR_INVALID_PARAMS, "DELMASK");
390 command_fail(si, fault_needmoreparams, "Syntax: DELMASK /<regex>/");
b45a5ca4
SB
391 return;
392 }
393
b918d5d9
SB
394 mowgli_node_t *n, *tn;
395 MOWGLI_LIST_FOREACH_SAFE(n, tn, masks.head)
b45a5ca4
SB
396 {
397 mask_t *m = n->data;
398 if (0 == strcmp(pattern, m->regex))
399 {
834c9c67 400 syn_report("\002DELMASK\002 /%s/%s (%s) by %s", pattern, (m->reflags & AREGEX_ICASE) ? "i" : "", string_from_mask_type(m->type), get_oper_name(si));
767e4663 401 command_success_nodata(si, "Removing \2%s\2 from %s mask list", pattern, string_from_mask_type(m->type));
b45a5ca4
SB
402 regex_destroy(m->re);
403 free(m->regex);
404 free(m);
b918d5d9 405 mowgli_node_delete(n, &masks);
2b3db230
SB
406
407 save_maskdb();
408
b45a5ca4
SB
409 return;
410 }
411 }
412
767e4663 413 command_fail(si, fault_nochange, "\2%s\2 was not found in any mask list", pattern);
ca6bbb10
SB
414}
415
416void syn_cmd_setmask(sourceinfo_t *si, int parc, char **parv)
417{
b45a5ca4
SB
418 char *args = parv[0];
419
420 if (!args)
421 {
767e4663 422 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "SETMASK");
867a1c56 423 command_fail(si, fault_needmoreparams, "Syntax: SETMASK /<regex>/ <type|~expiry>");
b45a5ca4
SB
424 return;
425 }
426
427 int flags = 0;
428 char *pattern = regex_extract(args, &args, &flags);
429
430 if (!pattern)
431 {
767e4663 432 command_fail(si, fault_badparams, STR_INVALID_PARAMS, "SETMASK");
867a1c56 433 command_fail(si, fault_needmoreparams, "Syntax: SETMASK /<regex>/ <type|~expiry>");
b45a5ca4
SB
434 return;
435 }
436
b918d5d9 437 mowgli_node_t *n, *tn;
b45a5ca4 438 mask_t *m;
b918d5d9 439 MOWGLI_LIST_FOREACH_SAFE(n, tn, masks.head)
b45a5ca4
SB
440 {
441 m = n->data;
442 if (0 == strcmp(pattern, m->regex))
443 break;
444 m = NULL;
445 }
446
447 if (!m)
448 {
767e4663 449 command_fail(si, fault_nochange, "\2%s\2 was not found in any mask list", pattern);
b45a5ca4
SB
450 return;
451 }
452
453 char *nextarg = strtok(args, " ");
867a1c56
SB
454
455 if (!nextarg)
456 {
457 command_fail(si, fault_needmoreparams, STR_INSUFFICIENT_PARAMS, "SETMASK");
458 command_fail(si, fault_needmoreparams, "Syntax: SETMASK /<regex>/ <type|~expiry>");
459 return;
460 }
461
b45a5ca4
SB
462 mask_type t = mask_type_from_string(nextarg);
463 if (t != mask_unknown)
464 {
465 m->type = t;
834c9c67 466 syn_report("\002SETMASK\002 /%s/%s type->%s by %s", pattern, (m->reflags & AREGEX_ICASE) ? "i" : "", nextarg, get_oper_name(si));
767e4663 467 command_success_nodata(si, "Changed type of mask \2%s\2 to %s", pattern, nextarg);
2b3db230
SB
468
469 save_maskdb();
470
b45a5ca4
SB
471 return;
472 }
473
474 if (*nextarg == '~')
475 {
476 time_t duration = syn_parse_duration(++nextarg);
477 if (duration > 0)
478 {
479 m->expires = CURRTIME + duration * 60;
834c9c67 480 syn_report("\002SETMASK\002 /%s/%s duration->%d by %s", pattern, (m->reflags & AREGEX_ICASE) ? "i" : "", duration, get_oper_name(si));
767e4663 481 command_success_nodata(si, "Changed expiry of mask \2%s\2 to %ld minutes", pattern, duration);
b45a5ca4
SB
482 }
483 else
484 {
485 m->expires = 0;
834c9c67 486 syn_report("\002SETMASK\002 /%s/%s expiry->off by %s", pattern, (m->reflags & AREGEX_ICASE) ? "i" : "", get_oper_name(si));
767e4663 487 command_success_nodata(si, "Expiry disabled for mask \2%s\2.", pattern);
b45a5ca4 488 }
2b3db230
SB
489
490 save_maskdb();
491
b45a5ca4
SB
492 return;
493 }
494
767e4663 495 command_fail(si, fault_badparams, STR_INVALID_PARAMS, "SETMASK");
867a1c56 496 command_fail(si, fault_badparams, "Syntax: SETMASK /<regex>/ <type|~expiry>");
ca6bbb10
SB
497}
498
b45a5ca4
SB
499void syn_cmd_listmask(sourceinfo_t *si, int parc, char **parv)
500{
501 mask_type t = mask_unknown;
502
503 if (parc > 0)
504 {
505 t = mask_type_from_string(parv[0]);
506 }
507
508 int count = 0;
509
b918d5d9
SB
510 mowgli_node_t *n;
511 MOWGLI_LIST_FOREACH(n, masks.head)
b45a5ca4
SB
512 {
513 mask_t *m = n->data;
514
515 if (t != mask_unknown && t != m->type)
516 continue;
517
52267ba7
JK
518 char added[BUFSIZE], expires[BUFSIZE], last_match[BUFSIZE];
519 strncpy(added, syn_format_expiry(m->added), BUFSIZE);
520 strncpy(expires, syn_format_expiry(m->expires), BUFSIZE);
521 strncpy(last_match, syn_format_expiry(m->last_match), BUFSIZE);
522 command_success_nodata(si, "\2/%s/%s\2 (%s), set by %s on %s, expires %s, last matched %s",
834c9c67 523 m->regex, (m->reflags & AREGEX_ICASE) ? "i" : "", string_from_mask_type(m->type), m->setter,
52267ba7 524 added, expires, last_match);
b45a5ca4
SB
525
526 ++count;
527 }
528
767e4663 529 command_success_nodata(si, "%d masks found", count);
b45a5ca4 530}
492a4dc0
JK
531
532DECLARE_MODULE_V1
533(
534 "syn/masks", false, mod_init, mod_deinit,
535 "$Revision$",
536 "Stephen Bennett <stephen -at- freenode.net>"
537);