changed type of shitlist to dict_t and added cmd_listbad, cmd_addbad & cmd_delbad
[srvx.git] / src / mod-watchdog.c
1 /* mod-watchdog.c - Watchdog module for srvx
2  * Copyright 2003-2004 Martijn Smit and srvx Development Team
3  *
4  * This file is part of srvx.
5  *
6  * srvx is free software; you can redistribute it and/or modify
7  * it under the terms of the GNU General Public License as published by
8  * the Free Software Foundation; either version 2 of the License, or
9  * (at your option) any later version.
10  *
11  * This program is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with srvx; if not, write to the Free Software Foundation,
18  * Inc., 59 Temple Place, Suite 330, Boston, MA  02111-1307  USA.
19  */
20
21 /* Adds new section to srvx.conf:
22  * "modules" {
23  *     "watchdog" {
24  *         "nick" "Watchdog";
25  *         "modes" "+iok";
26  *     };
27  *  };
28  *
29  */
30
31 #include "chanserv.h"
32 #include "opserv.h"
33 #include "conf.h"
34 #include "modcmd.h"
35 #include "saxdb.h"
36 #include "timeq.h"
37
38 #define KEY_BADWORDS "badwords"
39 #define KEY_BADWORD_MASK "mask"
40 #define KEY_BADWORD_TRIGGERED "count"
41 #define KEY_BADWORD_ACTION "action"
42 #define KEY_CHANNELS "channel"
43 #define KEY_BADWORDID "badwordid"
44
45 static const struct message_entry msgtab[] = {
46     { "WDMSG_REGISTER_SUCCESS", "$b%s$b is now registered with %s." },
47     { "WDMSG_NOT_REGISTERED", "$b%s$b is not registered with %s." },
48     { "WDMSG_ALREADY_ADDED", "$b%s$b is already added. (ID: %s)" },
49     { "WDMSG_BADWORD_ADDED", "added '$b%s$b' to the badword list with ID %s." },
50     { "WDMSG_BADWORD_NOT_FOUND", "badword with ID %s does not exist." },
51     { "WDMSG_BADWORD_REMOVED", "badword ID $b%s$b has been removed (mask: '%s')" },
52     { "ID_DEBUG", "%s: %s" },
53     { NULL, NULL }
54 };
55
56 struct badword {
57     char *id;
58     char *badword_mask;
59     unsigned int triggered : 29;
60     unsigned int action : 3;
61 };
62
63 struct watchdog_channel {
64     struct chanNode *channel;
65     //struct shitList *shitlist;
66 };
67
68 /* badword.action fields */
69 #define BADACTION_KICK   1
70 #define BADACTION_KILL   2
71 #define BADACTION_GLINE  3
72
73 static struct {
74     const char *nick;
75     const char *modes;
76 } watchdog_conf;
77
78 const char *watchdog_module_deps[] = { NULL };
79 struct userNode *watchdog;
80 static struct module *watchdog_module;
81 static struct service *watchdog_service;
82 static dict_t shitlist;
83 static dict_t chanlist;
84 static struct log_type *MS_LOG;
85 static unsigned int last_badword_id = 0;
86
87 static struct watchdog_channel *add_channel(const char *name);
88 static struct badword *add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, const char *id);
89 #define watchdog_notice(target, format...) send_message(target , watchdog , ## format)
90
91 static MODCMD_FUNC(cmd_addbad)
92 {
93     dict_iterator_t it;
94     char *mask = unsplit_string(argv + 1, argc - 1, NULL);
95     for (it = dict_first(shitlist); it; it = iter_next(it)) {
96         struct badword *badword = iter_data(it);
97         if(match_ircglob(mask,badword->badword_mask)) {
98             reply("WDMSG_ALREADY_ADDED", mask, badword->id);
99             return 1;
100         }
101     }
102
103     struct badword *new_badword = add_badword(mask, 0, BADACTION_KICK, NULL);
104     for (it = dict_first(shitlist); it; it = iter_next(it)) {
105         struct badword *badword = iter_data(it);
106         if(match_ircglob(badword->badword_mask, new_badword->badword_mask) && badword != new_badword) {
107             dict_remove(shitlist, badword->id);
108         }
109     }
110
111     reply("WDMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id);
112     return 1;
113 }
114
115 static MODCMD_FUNC(cmd_delbad)
116 {
117     unsigned int n;
118
119     for (n=1; n<argc; n++) {
120         struct badword *badword = dict_find(shitlist, argv[n], NULL);
121         if (!badword) {
122             reply("WDMSG_BADWORD_NOT_FOUND", argv[n]);
123             continue;
124         }
125         reply("WDMSG_BADWORD_REMOVED", argv[n], badword->badword_mask);
126         dict_remove(shitlist, argv[n]);
127     }
128     return 1;
129 }
130
131 static MODCMD_FUNC(cmd_editbad)
132 {
133     //to be continued...
134     return 1;
135 }
136
137 int
138 badwords_sort(const void *pa, const void *pb)
139 {
140         struct badword *a = *(struct badword**)pa;
141         struct badword *b = *(struct badword**)pb;
142
143         return strtoul(a->id, NULL, 0) - strtoul(b->id, NULL, 0);
144 }
145
146 static MODCMD_FUNC(cmd_listbad)
147 {
148     struct helpfile_table tbl;
149     unsigned int count = 0, ii = 0;
150     struct badword **badwords;
151     
152     dict_iterator_t it;
153     for (it = dict_first(shitlist); it; it = iter_next(it)) {
154         count++;
155     }
156     tbl.length = count+1;
157     tbl.width = 4;
158     tbl.flags = 0;
159     tbl.flags = TABLE_NO_FREE;
160     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
161     tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
162     tbl.contents[0][0] = "#";
163     tbl.contents[0][1] = "Badword";
164     tbl.contents[0][2] = "Action";
165     tbl.contents[0][3] = "(Triggered)";
166     if(!count)
167     {
168         table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
169         reply("MSG_NONE");
170         free(tbl.contents[0]);
171         free(tbl.contents);
172         return 0;
173     }
174     badwords = alloca(count * sizeof(badwords[0]));
175     for (it = dict_first(shitlist); it; it = iter_next(it)) {
176         struct badword *bw = iter_data(it);
177         badwords[ii++] = bw;
178     }
179     qsort(badwords, count, sizeof(badwords[0]), badwords_sort);
180     for (ii = 1; ii <= count; ii++) {
181         struct badword *bw = badwords[ii-1];
182         tbl.contents[ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
183         tbl.contents[ii][0] = strdup(bw->id);
184         tbl.contents[ii][1] = strdup(bw->badword_mask);
185         switch(bw->action) {
186             case BADACTION_KICK:
187               tbl.contents[ii][2] = "KICK";
188               break;
189             case BADACTION_KILL:
190               tbl.contents[ii][2] = "KILL";
191               break;
192             case BADACTION_GLINE:
193               tbl.contents[ii][2] = "GLINE";
194               break;
195             default:
196               tbl.contents[ii][2] = "*undef*";
197         }
198         tbl.contents[ii][3] = strtab(bw->triggered);
199     }
200     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
201     for(ii = 1; ii < tbl.length; ++ii)
202     {
203         free(tbl.contents[ii]);
204     }
205     free(tbl.contents[0]);
206     free(tbl.contents);
207     return 1;
208 }
209
210 static MODCMD_FUNC(cmd_register)
211 {
212     dict_iterator_t it;
213
214     if((argc < 2) || !IsChannelName(argv[1]))
215     {
216         reply("MSG_NOT_CHANNEL_NAME");
217         return 0;
218     }
219
220     if(opserv_bad_channel(argv[1]))
221     {
222         reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
223         return 0;
224     }
225
226     channel = AddChannel(argv[1], now, NULL, NULL);
227
228     for (it = dict_first(chanlist); it; it = iter_next(it)) {
229         struct watchdog_channel *chan = iter_data(it);
230         if(chan->channel == channel) {
231             reply("CSMSG_ALREADY_REGGED", channel->name);
232             return 0;
233         }
234     }
235
236     add_channel(channel->name);
237     reply("WDMSG_REGISTER_SUCCESS", channel->name, watchdog->nick);
238     return 1;
239 }
240
241 static MODCMD_FUNC(cmd_unregister)
242 {
243     struct watchdog_channel *chan = NULL;
244     dict_iterator_t it;
245     
246     for (it = dict_first(chanlist); it; it = iter_next(it)) {
247         chan = iter_data(it);
248         if(chan->channel == channel)
249             break;
250     }
251     
252     if(chan && chan->channel == channel) {
253         //found, unregister it!
254         DelChannelUser(watchdog, channel, "unregistered.", 0);
255         dict_remove(chanlist, channel->name);
256         reply("CSMSG_UNREG_SUCCESS", channel->name);
257         return 1;
258     } else {
259         reply("WDMSG_NOT_REGISTERED", channel->name, watchdog->nick);
260         return 0;
261     }
262
263 }
264
265 static void
266 watchdog_channel_message(struct userNode *user, struct chanNode *chan, const char *text, struct userNode *bot, unsigned int is_notice)
267 {
268     //to be continued...
269 }
270
271 static struct badword*
272 add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, const char *id)
273 {
274     struct badword *badword;
275
276     badword = calloc(1, sizeof(*badword));
277     if (!badword)
278         return NULL;
279
280     if(!id) {
281         last_badword_id++;
282         badword->id = strtab(last_badword_id);
283     } else
284         badword->id = strdup(id);
285     badword->badword_mask = strdup(badword_mask);
286     badword->triggered = triggered;
287     badword->action = action;
288     dict_insert(shitlist, badword->id, badword);
289     return badword;
290 }
291
292 static void
293 free_shitlist_entry(void *data)
294 {
295     struct badword *badword = data;
296     free(badword->id);
297     free(badword->badword_mask);
298     free(badword);
299 }
300
301 static struct watchdog_channel*
302 add_channel(const char *name)
303 {
304     struct watchdog_channel *wc;
305     struct mod_chanmode *change;
306
307     if(!watchdog) //module disabled
308         return NULL;
309
310     wc = calloc(1, sizeof(*wc));
311     if (!wc)
312         return NULL;
313
314     wc->channel = AddChannel(name, now, NULL, NULL);
315     change = mod_chanmode_alloc(1);
316     change->argc = 1;
317     change->args[0].mode = MODE_CHANOP;
318     change->args[0].u.member = AddChannelUser(watchdog, wc->channel);
319     mod_chanmode_announce(watchdog, wc->channel, change);
320         mod_chanmode_free(change);
321     dict_insert(chanlist, wc->channel->name, wc);
322     return wc;
323 }
324
325 static void
326 free_chanlist_entry(void *data)
327 {
328     struct watchdog_channel *wc = data;
329     
330     free(wc);
331 }
332
333 static void
334 watchdog_conf_read(void)
335 {
336     dict_t conf_node;
337     const char *str;
338
339     str = "modules/watchdog";
340     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
341         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
342         return;
343     }
344
345     str = database_get_data(conf_node, "nick", RECDB_QSTRING);
346     if(watchdog_conf.nick && strcmp(watchdog_conf.nick, str)) {
347         //nick changed
348     }
349     watchdog_conf.nick = str;
350     
351     str = database_get_data(conf_node, "modes", RECDB_QSTRING);
352     watchdog_conf.modes = (str ? str : NULL);
353 }
354
355 static int
356 watchdog_saxdb_read_shitlist(const char *name, void *data, UNUSED_ARG(void *extra))
357 {
358     struct record_data *rd = data;
359     char *badword;
360     char *triggered, *action;
361
362      if (rd->type == RECDB_OBJECT) {
363         dict_t obj = GET_RECORD_OBJECT(rd);
364         /* new style structure */
365         badword = database_get_data(obj, KEY_BADWORD_MASK, RECDB_QSTRING);
366         triggered = database_get_data(obj, KEY_BADWORD_TRIGGERED, RECDB_QSTRING);
367         action = database_get_data(obj, KEY_BADWORD_ACTION, RECDB_QSTRING);
368
369         add_badword(badword, strtoul(triggered, NULL, 0), strtoul(action, NULL, 0), name);
370     }
371     return 0;
372 }
373
374 static int
375 watchdog_saxdb_read_chanlist(const char *name, void *data, UNUSED_ARG(void *extra))
376 {
377     struct record_data *rd = data;
378
379      if (rd->type == RECDB_OBJECT) {
380         dict_t obj = GET_RECORD_OBJECT(rd);
381         /* nothing in here, yet */
382
383         add_channel(name);
384     }
385     return 0;
386 }
387
388 static int
389 watchdog_saxdb_read(struct dict *db)
390 {
391     struct dict *object;
392     char *str;
393     str = database_get_data(db, KEY_BADWORDID, RECDB_QSTRING);
394     last_badword_id = str ? strtoul(str, NULL, 0) : 0;
395
396     if ((object = database_get_data(db, KEY_BADWORDS, RECDB_OBJECT)))
397         dict_foreach(object, watchdog_saxdb_read_shitlist, NULL);
398
399     if ((object = database_get_data(db, KEY_CHANNELS, RECDB_OBJECT)))
400         dict_foreach(object, watchdog_saxdb_read_chanlist, NULL);
401
402     return 1;
403 }
404
405 static int
406 watchdog_saxdb_write(struct saxdb_context *ctx)
407 {
408     char str[10];
409     dict_iterator_t it;
410
411     saxdb_write_int(ctx, KEY_BADWORDID, last_badword_id);
412
413     if (dict_size(shitlist)) {
414         saxdb_start_record(ctx, KEY_BADWORDS, 1);
415         for (it = dict_first(shitlist); it; it = iter_next(it)) {
416             struct badword *badword = iter_data(it);
417             saxdb_start_record(ctx, iter_key(it), 0);
418             
419             saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask);
420             saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered);
421             saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action);
422             
423             saxdb_end_record(ctx);
424         }
425         saxdb_end_record(ctx);
426     }
427
428     if (dict_size(chanlist)) {
429         saxdb_start_record(ctx, KEY_CHANNELS, 1);
430         for (it = dict_first(chanlist); it; it = iter_next(it)) {
431             struct watchdog_channel *wc = iter_data(it);
432             saxdb_start_record(ctx, wc->channel->name, 0);
433             //anything else?
434             saxdb_end_record(ctx);
435         }
436         saxdb_end_record(ctx);
437     }
438     
439     return 0;
440 }
441
442 static void
443 watchdog_cleanup(void)
444 {
445     dict_delete(shitlist);
446     dict_delete(chanlist);
447 }
448
449 int
450 watchdog_init(void)
451 {
452     MS_LOG = log_register_type("Watchdog", "file:watchdog.log");
453     
454     /* set up shitlist dict */
455     dict_delete(shitlist);
456     shitlist = dict_new();
457     dict_set_free_data(shitlist, free_shitlist_entry);
458     /* set up chanlist dict */
459     dict_delete(chanlist);
460     chanlist = dict_new();
461     dict_set_free_data(chanlist, free_chanlist_entry);
462
463     const char *nick, *modes;
464     if((nick = conf_get_data("modules/watchdog/nick", RECDB_QSTRING))) {
465         modes = conf_get_data("modules/watchdog/modes", RECDB_QSTRING);
466         watchdog = AddLocalUser(nick, nick, NULL, "Watchdog Service", modes);
467         watchdog_service = service_register(watchdog);
468         watchdog_service->trigger = ',';
469         reg_allchanmsg_func(watchdog, watchdog_channel_message);
470     }
471
472     conf_register_reload(watchdog_conf_read);
473     reg_exit_func(watchdog_cleanup);
474     saxdb_register("Watchdog", watchdog_saxdb_read, watchdog_saxdb_write);
475
476     watchdog_module = module_register("Watchdog", MS_LOG, "mod-watchdog.help", NULL);
477     modcmd_register(watchdog_module, "addbad", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
478     modcmd_register(watchdog_module, "delbad", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
479     modcmd_register(watchdog_module, "editbad", cmd_editbad, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
480     modcmd_register(watchdog_module, "listbad", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
481     modcmd_register(watchdog_module, "register", cmd_register, 2, MODCMD_REQUIRE_AUTHED, "flags", "+helping", NULL);
482     modcmd_register(watchdog_module, "unregister", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_CHANNEL, "flags", "+helping", NULL);
483     message_register_table(msgtab);
484
485     return 1;
486 }
487
488 int
489 watchdog_finalize(void) {
490     dict_t conf_node;
491     const char *str;
492
493     str = "modules/watchdog";
494     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
495         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
496         return 0;
497     }
498
499     str = database_get_data(conf_node, "nick", RECDB_QSTRING);
500     if (str) watchdog_conf.nick = str;
501     
502     str = database_get_data(conf_node, "modes", RECDB_QSTRING);
503     if (str) watchdog_conf.modes = str;
504     return 1;
505 }