2 * syn: a utility bot to manage IRC network access
3 * Copyright (C) 2009-2016 Stephen Bennett
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.
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.
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/>.
24 static void masks_newuser(hook_user_nick_t
*data
);
26 static void syn_cmd_addmask(sourceinfo_t
*si
, int parc
, char **parv
);
27 static void syn_cmd_delmask(sourceinfo_t
*si
, int parc
, char **parv
);
28 static void syn_cmd_setmask(sourceinfo_t
*si
, int parc
, char **parv
);
29 static void syn_cmd_listmask(sourceinfo_t
*si
, int parc
, char **parv
);
31 command_t syn_addmask
= { "ADDMASK", N_("Adds a lethal, suspicious or exempt mask"), "syn:general", 1, syn_cmd_addmask
, { .path
= "syn/addmask" } };
32 command_t syn_delmask
= { "DELMASK", N_("Removes a lethal, suspicious or exempt mask"), "syn:general", 1, syn_cmd_delmask
, { .path
= "syn/delmask" } };
33 command_t syn_setmask
= { "SETMASK", N_("Modifies settings for a lethal, suspicious or exempt mask"), "syn:general", 1, syn_cmd_setmask
, { .path
= "syn/setmask" } };
34 command_t syn_listmask
= { "LISTMASK", N_("Displays configured mask lists"), "syn:general", 1, syn_cmd_listmask
, { .path
= "syn/listmask" } };
36 static unsigned int lethal_mask_duration
= 3600*24;
37 static char *lethal_mask_message
= NULL
;
39 static mowgli_eventloop_timer_t
*expire_masks_timer
;
60 char setter
[NICKLEN
*2+2];
68 } mask_string_map
[] = {
69 { "exempt", mask_exempt
},
70 { "suspicious", mask_suspicious
},
71 { "lethal", mask_lethal
},
75 const char *string_from_mask_type(mask_type t
)
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
;
83 mask_type
mask_type_from_string(const char *s
)
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
;
91 static void check_expiry(void *v
)
93 mowgli_node_t
*n
, *tn
;
94 MOWGLI_LIST_FOREACH_SAFE(n
, tn
, masks
.head
)
100 if (m
->expires
> CURRTIME
)
103 syn_report("Expiring %s mask \2%s\2", string_from_mask_type(m
->type
), m
->regex
);
104 regex_destroy(m
->re
);
107 mowgli_node_delete(n
, &masks
);
111 static void save_maskdb()
114 FILE *f
= fopen(DATADIR
"/masks.db", "w");
117 slog(LG_ERROR
, "Couldn't open masks.db for writing: %s", strerror(errno
));
121 MOWGLI_LIST_FOREACH(n
, masks
.head
)
125 fprintf(f
, "/%s/%s %d %s %lu %lu %lu\n",
126 m
->regex
, m
->reflags
& AREGEX_ICASE
? "i" : "",
127 m
->type
, m
->setter
, m
->added
, m
->expires
, m
->last_match
);
132 static void load_maskdb()
134 FILE *f
= fopen(DATADIR
"/masks.db", "r");
137 slog(LG_DEBUG
, "Couldn't open masks db for reading: %s", strerror(errno
));
141 char line
[BUFSIZE
*2];
142 while(fgets(line
, sizeof(line
), f
))
146 char *regex
= regex_extract(args
, &args
, &flags
);
148 atheme_regex_t
*re
= regex_create(regex
, flags
);
152 slog(LG_DEBUG
, "Invalid entry %s in masks db", line
);
156 char setter
[BUFSIZE
*2];
158 time_t added
, expires
, last_match
= 0;
160 sscanf(args
, "%d %s %lu %lu %lu", &type
, setter
, &added
, &expires
, &last_match
);
162 mask_t
*mask
= malloc(sizeof(mask_t
));
163 mask
->regex
= sstrdup(regex
);
164 mask
->reflags
= flags
;
166 strncpy(mask
->setter
, setter
, sizeof(mask
->setter
));
168 mask
->expires
= expires
;
169 mask
->last_match
= last_match
;
172 mowgli_node_add(mask
, mowgli_node_create(), &masks
);
178 static void mod_init(module_t
*m
)
180 use_syn_main_symbols(m
);
181 use_syn_util_symbols(m
);
182 use_syn_kline_symbols(m
);
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");
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
);
192 hook_add_event("user_nickchange");
193 hook_add_user_nickchange(masks_newuser
);
194 hook_add_event("user_add");
195 hook_add_user_add(masks_newuser
);
197 expire_masks_timer
= mowgli_timer_add(base_eventloop
, "masks_check_expiry", check_expiry
, NULL
, 60);
202 static void mod_deinit(module_unload_intent_t intent
)
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
);
211 del_conf_item("lethalmask_duration", &syn
->conf_table
);
212 del_conf_item("lethalmask_message", &syn
->conf_table
);
214 hook_del_user_add(masks_newuser
);
215 hook_del_user_nickchange(masks_newuser
);
217 mowgli_timer_destroy(base_eventloop
, expire_masks_timer
);
220 void masks_newuser(hook_user_nick_t
*data
)
224 /* If the user has already been killed, don't try to do anything */
228 char nuh
[NICKLEN
+USERLEN
+HOSTLEN
+GECOSLEN
];
229 snprintf(nuh
, sizeof(nuh
), "%s!%s@%s %s", u
->nick
, u
->user
, u
->host
, u
->gecos
);
231 int blocked
= 0, exempt
= 0;
232 char *suspicious_regex
= NULL
, *blocked_regex
= NULL
;
236 MOWGLI_LIST_FOREACH(n
, masks
.head
)
240 if (! regex_match(m
->re
, nuh
))
243 m
->last_match
= CURRTIME
;
250 case mask_suspicious
:
251 suspicious_regex
= m
->regex
;
255 blocked_regex
= m
->regex
;
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
);
274 if (suspicious_regex
)
276 syn_report("Client %s(%s@%s) matches suspicious mask %s",
277 u
->nick
, u
->user
, u
->host
, suspicious_regex
);
282 void syn_cmd_addmask(sourceinfo_t
*si
, int parc
, char **parv
)
291 char *args
= parv
[0];
295 command_fail(si
, fault_needmoreparams
, STR_INSUFFICIENT_PARAMS
, "ADDMASK");
296 command_fail(si
, fault_needmoreparams
, "Syntax: ADDMASK /<regex>/[i] <type>");
300 pattern
= regex_extract(args
, &args
, &flags
);
303 command_fail(si
, fault_badparams
, STR_INVALID_PARAMS
, "ADDMASK");
304 command_fail(si
, fault_badparams
, "Syntax: ADDMASK /<regex>/[i] <type>");
308 stype
= strtok(args
, " ");
310 if (!stype
|| *stype
== '\0')
312 command_fail(si
, fault_needmoreparams
, STR_INSUFFICIENT_PARAMS
, "ADDMASK");
313 command_fail(si
, fault_needmoreparams
, "Syntax: ADDMASK /<regex>/[i] <type>");
317 type
= mask_type_from_string(stype
);
318 if (type
== mask_unknown
)
320 command_fail(si
, fault_badparams
, "Invalid mask type \2%s\2.", stype
);
324 char *sduration
= strtok(NULL
, " ");
325 if (sduration
&& *sduration
== '~')
327 duration
= syn_parse_duration(++sduration
);
331 MOWGLI_LIST_FOREACH(n
, masks
.head
)
335 if (0 == strcmp(m
->regex
, pattern
))
337 command_fail(si
, fault_nochange
, "\2%s\2 was already added (%s); not re-adding", pattern
, string_from_mask_type(m
->type
));
342 atheme_regex_t
*regex
= regex_create(pattern
, flags
);
345 command_fail(si
, fault_badparams
, "The provided regex \2%s\2 is invalid.", pattern
);
349 newmask
= malloc(sizeof(mask_t
));
350 newmask
->regex
= sstrdup(pattern
);
351 newmask
->reflags
= flags
;
353 newmask
->type
= type
;
355 newmask
->expires
= CURRTIME
+ 60*duration
;
357 newmask
->expires
= 0;
359 newmask
->added
= CURRTIME
;
360 newmask
->last_match
= 0;
361 strncpy(newmask
->setter
, get_oper_name(si
), sizeof(newmask
->setter
));
363 mowgli_node_add(newmask
, mowgli_node_create(), &masks
);
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
));
367 command_success_nodata(si
, "Added \2%s\2 to %s mask list, expiring %s.",
368 pattern
, stype
, syn_format_expiry(newmask
->expires
));
373 void syn_cmd_delmask(sourceinfo_t
*si
, int parc
, char **parv
)
375 char *args
= parv
[0];
379 command_fail(si
, fault_needmoreparams
, STR_INSUFFICIENT_PARAMS
, "DELMASK");
380 command_fail(si
, fault_needmoreparams
, "Syntax: DELMASK /<regex>/");
385 char *pattern
= regex_extract(args
, &args
, &flags
);
389 command_fail(si
, fault_badparams
, STR_INVALID_PARAMS
, "DELMASK");
390 command_fail(si
, fault_needmoreparams
, "Syntax: DELMASK /<regex>/");
394 mowgli_node_t
*n
, *tn
;
395 MOWGLI_LIST_FOREACH_SAFE(n
, tn
, masks
.head
)
398 if (0 == strcmp(pattern
, m
->regex
))
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
));
401 command_success_nodata(si
, "Removing \2%s\2 from %s mask list", pattern
, string_from_mask_type(m
->type
));
402 regex_destroy(m
->re
);
405 mowgli_node_delete(n
, &masks
);
413 command_fail(si
, fault_nochange
, "\2%s\2 was not found in any mask list", pattern
);
416 void syn_cmd_setmask(sourceinfo_t
*si
, int parc
, char **parv
)
418 char *args
= parv
[0];
422 command_fail(si
, fault_needmoreparams
, STR_INSUFFICIENT_PARAMS
, "SETMASK");
423 command_fail(si
, fault_needmoreparams
, "Syntax: SETMASK /<regex>/ <type|~expiry>");
428 char *pattern
= regex_extract(args
, &args
, &flags
);
432 command_fail(si
, fault_badparams
, STR_INVALID_PARAMS
, "SETMASK");
433 command_fail(si
, fault_needmoreparams
, "Syntax: SETMASK /<regex>/ <type|~expiry>");
437 mowgli_node_t
*n
, *tn
;
439 MOWGLI_LIST_FOREACH_SAFE(n
, tn
, masks
.head
)
442 if (0 == strcmp(pattern
, m
->regex
))
449 command_fail(si
, fault_nochange
, "\2%s\2 was not found in any mask list", pattern
);
453 char *nextarg
= strtok(args
, " ");
457 command_fail(si
, fault_needmoreparams
, STR_INSUFFICIENT_PARAMS
, "SETMASK");
458 command_fail(si
, fault_needmoreparams
, "Syntax: SETMASK /<regex>/ <type|~expiry>");
462 mask_type t
= mask_type_from_string(nextarg
);
463 if (t
!= mask_unknown
)
466 syn_report("\002SETMASK\002 /%s/%s type->%s by %s", pattern
, (m
->reflags
& AREGEX_ICASE
) ? "i" : "", nextarg
, get_oper_name(si
));
467 command_success_nodata(si
, "Changed type of mask \2%s\2 to %s", pattern
, nextarg
);
476 time_t duration
= syn_parse_duration(++nextarg
);
479 m
->expires
= CURRTIME
+ duration
* 60;
480 syn_report("\002SETMASK\002 /%s/%s duration->%d by %s", pattern
, (m
->reflags
& AREGEX_ICASE
) ? "i" : "", duration
, get_oper_name(si
));
481 command_success_nodata(si
, "Changed expiry of mask \2%s\2 to %ld minutes", pattern
, duration
);
486 syn_report("\002SETMASK\002 /%s/%s expiry->off by %s", pattern
, (m
->reflags
& AREGEX_ICASE
) ? "i" : "", get_oper_name(si
));
487 command_success_nodata(si
, "Expiry disabled for mask \2%s\2.", pattern
);
495 command_fail(si
, fault_badparams
, STR_INVALID_PARAMS
, "SETMASK");
496 command_fail(si
, fault_badparams
, "Syntax: SETMASK /<regex>/ <type|~expiry>");
499 void syn_cmd_listmask(sourceinfo_t
*si
, int parc
, char **parv
)
501 mask_type t
= mask_unknown
;
505 t
= mask_type_from_string(parv
[0]);
511 MOWGLI_LIST_FOREACH(n
, masks
.head
)
515 if (t
!= mask_unknown
&& t
!= m
->type
)
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",
523 m
->regex
, (m
->reflags
& AREGEX_ICASE
) ? "i" : "", string_from_mask_type(m
->type
), m
->setter
,
524 added
, expires
, last_match
);
529 command_success_nodata(si
, "%d masks found", count
);
534 "syn/masks", false, mod_init
, mod_deinit
,
536 "Stephen Bennett <stephen -at- freenode.net>"