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