changed spamserv,chanserv and watchdog messages to privmsgs
[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            "ban_duration" "2h"; //only if the channel is registered with chanserv
27            "gline_duration" "1h";
28            "punishment_reason" "Your message contained a forbidden word.";
29  *     };
30  *  };
31  *
32  */
33
34 #include "chanserv.h"
35 #include "opserv.h"
36 #include "conf.h"
37 #include "modcmd.h"
38 #include "saxdb.h"
39 #include "timeq.h"
40 #include "gline.h"
41
42 #define KEY_BADWORDS "badwords"
43 #define KEY_BADWORD_MASK "mask"
44 #define KEY_BADWORD_TRIGGERED "count"
45 #define KEY_BADWORD_ACTION "action"
46 #define KEY_BADWOR_ALERT "alert"
47 #define KEY_CHANNELS "channel"
48 #define KEY_BADWORDID "badwordid"
49
50 static const struct message_entry msgtab[] = {
51     { "WDMSG_REGISTER_SUCCESS", "$b%s$b is now registered with %s." },
52     { "WDMSG_UNREG_SUCCESS", "$b%s$b is now unregistered." },
53     { "WDMSG_NOT_REGISTERED", "$b%s$b is not registered with %s." },
54     { "WDMSG_ALREADY_ADDED", "$b%s$b is already added. (ID: %s)" },
55     { "WDMSG_BADWORD_ADDED", "added '$b%s$b' to the badword list with ID %s." },
56     { "WDMSG_BADWORD_NOT_FOUND", "badword with ID %s does not exist." },
57     { "WDMSG_BADWORD_REMOVED", "badword ID $b%s$b has been removed (mask: '%s')" },
58     { "WDMSG_BADWORD_SET_DONE", "Done." },
59     { "WDMSG_BADWORD_SET_INVALID", "Invalid Option for setting %s" },
60     { "OSMSG_BADWORD_SETTING_INVALID", "unknown setting $b%s$b." },
61     { "WDMSG_BADWORD_SET", "Settings for BadWord entry $b%s$b" },
62     { "WDMSG_BADWORD_SET_MASK",   "$bMASK$b:   %s" },
63     { "WDMSG_BADWORD_SET_ACTION", "$bACTION$b: %s" },
64     { "WDMSG_BADWORD_SET_ALERT",  "$bALERT$b:  %d" },
65     { NULL, NULL }
66 };
67
68 struct badword {
69     char *id;
70     char *badword_mask;
71     unsigned int triggered : 29;
72     unsigned int action : 3;
73     unsigned int alert;
74 };
75
76 struct watchdog_channel {
77     struct chanNode *channel;
78     //struct shitList *shitlist;
79 };
80
81 /* badword.action fields */
82 #define BADACTION_KICK   0
83 #define BADACTION_BAN    1
84 #define BADACTION_KILL   2
85 #define BADACTION_GLINE  3
86
87 #define WDMSG_BADWORD_ALERT "%s used badword '%s' in channel: %s"
88
89 static struct {
90     const char *nick;
91     const char *modes;
92     const char *punishment_reason;
93     unsigned long ban_duration;
94     unsigned long gline_duration;
95     struct chanNode *alert_channel;
96     struct chanNode *oper_channel;
97 } watchdog_conf;
98
99 const char *watchdog_module_deps[] = { NULL };
100 struct userNode *watchdog;
101 static struct module *watchdog_module;
102 static struct service *watchdog_service;
103 static dict_t shitlist;
104 static dict_t chanlist;
105 static struct log_type *MS_LOG;
106 static unsigned int last_badword_id = 0;
107
108 static struct watchdog_channel *add_channel(const char *name);
109 static struct badword *add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, unsigned int alert, const char *id);
110 #define watchdog_notice(target, format...) send_message(target , watchdog , ## format)
111 #define watchdog_debug(format...) do { if(watchdog_conf.alert_channel) send_channel_message(watchdog_conf.alert_channel , watchdog , ## format); } while(0)
112 #define watchdog_oper_message(format...) do { if(watchdog_conf.oper_channel) send_channel_message(watchdog_conf.oper_channel , watchdog , ## format); } while(0)
113
114 static MODCMD_FUNC(cmd_addbad)
115 {
116     dict_iterator_t it;
117     char *mask = unsplit_string(argv + 1, argc - 1, NULL);
118     for (it = dict_first(shitlist); it; it = iter_next(it)) {
119         struct badword *badword = iter_data(it);
120         if(match_ircglob(mask,badword->badword_mask)) {
121             reply("WDMSG_ALREADY_ADDED", mask, badword->id);
122             return 1;
123         }
124     }
125
126     struct badword *new_badword = add_badword(mask, 0, BADACTION_KICK, 0, NULL);
127     for (it = dict_first(shitlist); it; it = iter_next(it)) {
128         struct badword *badword = iter_data(it);
129         if(match_ircglob(badword->badword_mask, new_badword->badword_mask) && badword != new_badword) {
130             dict_remove(shitlist, badword->id);
131         }
132     }
133
134     reply("WDMSG_BADWORD_ADDED", new_badword->badword_mask, new_badword->id);
135     return 1;
136 }
137
138 static MODCMD_FUNC(cmd_delbad)
139 {
140     unsigned int n;
141
142     for (n=1; n<argc; n++) {
143         struct badword *badword = dict_find(shitlist, argv[n], NULL);
144         if (!badword) {
145             reply("WDMSG_BADWORD_NOT_FOUND", argv[n]);
146             continue;
147         }
148         reply("WDMSG_BADWORD_REMOVED", argv[n], badword->badword_mask);
149         dict_remove(shitlist, argv[n]);
150     }
151     return 1;
152 }
153
154 static MODCMD_FUNC(cmd_setbad)
155 {
156     struct badword *badword;
157     if ((badword = dict_find(shitlist, argv[1], NULL))) {
158         if (argc > 3) {
159             unsigned int ii;
160             char *setting = argv[2];
161             char *value = argv[3];
162             for( ii = 0; setting[ ii ]; ii++)
163                 setting[ ii ] = toupper( setting[ ii ] );
164             for( ii = 0; value[ ii ]; ii++)
165                 value[ ii ] = toupper( value[ ii ] );
166             if(!strcmp("MASK",setting)) {
167                   free(badword->badword_mask);
168                   badword->badword_mask = strdup(argv[3]);
169                   badword->triggered = 0;
170                   reply("WDMSG_BADWORD_SET_DONE");
171             }
172             else if(!strcmp("ACTION",setting)) {
173                  if (!strcmp("1",value) || !strcmp("KICK",value)) {
174                     badword->action = BADACTION_KICK;
175                     reply("WDMSG_BADWORD_SET_DONE");
176                  } else if (!strcmp("2",value) || !strcmp("BAN",value)) {
177                     badword->action = BADACTION_BAN;
178                     reply("WDMSG_BADWORD_SET_DONE");
179                  } else if (!strcmp("3",value) || !strcmp("KILL",value)) {
180                     badword->action = BADACTION_KILL;
181                     reply("WDMSG_BADWORD_SET_DONE");
182                  } else if (!strcmp("4",value) || !strcmp("GLINE",value)) {
183                     badword->action = BADACTION_GLINE;
184                     reply("WDMSG_BADWORD_SET_DONE");
185                  } else {
186                     reply("WDMSG_BADWORD_SET_INVALID", setting);
187                  }
188             }
189             else if(!strcmp("ALERT",setting)) {
190                 if (!strcmp("0",value)) {
191                         badword->alert = 0;
192                         reply("WDMSG_BADWORD_SET_DONE");
193                 } else if (!strcmp("1",value)) {
194                         badword->alert = 1;
195                         reply("WDMSG_BADWORD_SET_DONE");
196                 } else {
197                         reply("WDMSG_BADWORD_SET_INVALID", setting);
198                 }
199             } else {
200                  reply("WDMSG_BADWORD_SETTING_INVALID", setting);
201             }
202             
203         } else {
204             reply("WDMSG_BADWORD_SET", badword->id);
205             reply("WDMSG_BADWORD_SET_MASK", badword->badword_mask);
206             switch(badword->action) {
207                 case BADACTION_KICK:
208                   reply("WDMSG_BADWORD_SET_ACTION", "KICK");
209                   break;
210                 case BADACTION_BAN:
211                   reply("WDMSG_BADWORD_SET_ACTION", "BAN");
212                   break;
213                 case BADACTION_KILL:
214                   reply("WDMSG_BADWORD_SET_ACTION", "KILL");
215                   break;
216                 case BADACTION_GLINE:
217                   reply("WDMSG_BADWORD_SET_ACTION", "GLINE");
218                   break;
219                 default:
220                   reply("WDMSG_BADWORD_SET_ACTION", "*undef*");
221             }
222             reply("WDMSG_BADWORD_SET_ALERT", badword->alert);
223         }
224     } else {
225         reply("WDMSG_BADWORD_NOT_FOUND", argv[1]);
226         return 0;
227     }
228     return 1;
229 }
230
231 int
232 badwords_sort(const void *pa, const void *pb)
233 {
234         struct badword *a = *(struct badword**)pa;
235         struct badword *b = *(struct badword**)pb;
236
237         return strtoul(a->id, NULL, 0) - strtoul(b->id, NULL, 0);
238 }
239
240 static MODCMD_FUNC(cmd_listbad)
241 {
242     struct helpfile_table tbl;
243     unsigned int count = 0, ii = 0;
244     struct badword **badwords;
245     
246     dict_iterator_t it;
247     for (it = dict_first(shitlist); it; it = iter_next(it)) {
248         count++;
249     }
250     tbl.length = count+1;
251     tbl.width = 5;
252     tbl.flags = 0;
253     tbl.flags = TABLE_NO_FREE;
254     tbl.contents = malloc(tbl.length * sizeof(tbl.contents[0]));
255     tbl.contents[0] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
256     tbl.contents[0][0] = "#";
257     tbl.contents[0][1] = "Badword";
258     tbl.contents[0][2] = "Action";
259     tbl.contents[0][3] = "(Triggered)";
260     tbl.contents[0][4] = "Alert";
261     if(!count)
262     {
263         table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
264         reply("MSG_NONE");
265         free(tbl.contents[0]);
266         free(tbl.contents);
267         return 0;
268     }
269     badwords = alloca(count * sizeof(badwords[0]));
270     for (it = dict_first(shitlist); it; it = iter_next(it)) {
271         struct badword *bw = iter_data(it);
272         badwords[ii++] = bw;
273     }
274     qsort(badwords, count, sizeof(badwords[0]), badwords_sort);
275     for (ii = 1; ii <= count; ii++) {
276         struct badword *bw = badwords[ii-1];
277         tbl.contents[ii] = malloc(tbl.width * sizeof(tbl.contents[0][0]));
278         tbl.contents[ii][0] = strdup(bw->id);
279         tbl.contents[ii][1] = strdup(bw->badword_mask);
280         switch(bw->action) {
281             case BADACTION_KICK:
282               tbl.contents[ii][2] = "KICK";
283               break;
284             case BADACTION_BAN:
285               tbl.contents[ii][2] = "BAN";
286               break;
287             case BADACTION_KILL:
288               tbl.contents[ii][2] = "KILL";
289               break;
290             case BADACTION_GLINE:
291               tbl.contents[ii][2] = "GLINE";
292               break;
293             default:
294               tbl.contents[ii][2] = "*undef*";
295         }
296         tbl.contents[ii][3] = strtab(bw->triggered);
297         tbl.contents[ii][4] = strtab(bw->alert);
298     }
299     table_send(cmd->parent->bot, user->nick, 0, NULL, tbl);
300     for(ii = 1; ii < tbl.length; ++ii)
301     {
302         free(tbl.contents[ii]);
303     }
304     free(tbl.contents[0]);
305     free(tbl.contents);
306     return 1;
307 }
308
309 static MODCMD_FUNC(cmd_register)
310 {
311     dict_iterator_t it;
312     struct modeNode *mn;
313
314     if(channel)
315     {
316
317         if(channel->bad_channel)
318         {
319             reply("CSMSG_ILLEGAL_CHANNEL", channel->name);
320             return 0;
321         }
322
323         if(!IsHelping(user)
324            && (!(mn = GetUserMode(channel, user)) || !(mn->modes & MODE_CHANOP)))
325         {
326             reply("CSMSG_MUST_BE_OPPED", channel->name);
327             return 0;
328         }
329
330     }
331     else
332     {
333
334         if((argc < 2) || !IsChannelName(argv[1]))
335         {
336             reply("MSG_NOT_CHANNEL_NAME");
337             return 0;
338         }
339
340         if(opserv_bad_channel(argv[1]))
341         {
342             reply("CSMSG_ILLEGAL_CHANNEL", argv[1]);
343             return 0;
344         }
345
346         channel = AddChannel(argv[1], now, NULL, NULL);
347     }
348
349     for (it = dict_first(chanlist); it; it = iter_next(it)) {
350         struct watchdog_channel *chan = iter_data(it);
351         if(chan->channel == channel) {
352             reply("CSMSG_ALREADY_REGGED", channel->name);
353             return 0;
354         }
355     }
356
357     add_channel(channel->name);
358     reply("WDMSG_REGISTER_SUCCESS", channel->name, watchdog->nick);
359     watchdog_oper_message("WDMSG_REGISTER_SUCCESS", channel->name, watchdog->nick);
360     return 1;
361 }
362
363 static MODCMD_FUNC(cmd_unregister)
364 {
365     struct watchdog_channel *chan = NULL;
366     dict_iterator_t it;
367     
368     for (it = dict_first(chanlist); it; it = iter_next(it)) {
369         chan = iter_data(it);
370         if(chan->channel == channel)
371             break;
372     }
373     
374     if(chan && chan->channel == channel) {
375         //found, unregister it!
376         char reason[MAXLEN];
377         sprintf(reason, "Unregistered by %s.", user->handle_info->handle);
378         DelChannelUser(watchdog, channel, reason, 0);
379         dict_remove(chanlist, channel->name);
380         reply("CSMSG_UNREG_SUCCESS", channel->name);
381         watchdog_oper_message("CSMSG_UNREG_SUCCESS", channel->name);
382         return 1;
383     } else {
384         reply("WDMSG_NOT_REGISTERED", channel->name, watchdog->nick);
385         return 0;
386     }
387
388 }
389
390 static void
391 watchdog_detected_badword(struct userNode *user, struct chanNode *chan, struct badword *badword) 
392 {
393     char *hostmask;
394     char *reason = watchdog_conf.punishment_reason;
395     char mask[IRC_NTOP_MAX_SIZE+3] = { '*', '@', '\0' };
396     if(!IsOper(user)) {
397         if(badword->alert == 1) {
398                 log_module(MS_LOG, LOG_WARNING, "%s used badword '%s' in channel: %s", user->nick, badword->badword_mask, chan->name);
399                 watchdog_debug(WDMSG_BADWORD_ALERT, user->nick, badword->badword_mask, chan->name);
400         }
401                 switch(badword->action) {
402                         case BADACTION_BAN:
403                                 hostmask = generate_hostmask(user, GENMASK_STRICT_HOST | GENMASK_ANY_IDENT);
404                                 sanitize_ircmask(hostmask);
405                                 if(chan->channel_info) {
406                                         //registered channel
407                                         add_channel_ban(chan->channel_info, hostmask, watchdog->nick, now, now, now + watchdog_conf.ban_duration, reason);
408                                 }
409                                 struct mod_chanmode change;
410                                 mod_chanmode_init(&change);
411                                 change.argc = 1;
412                                 change.args[0].mode = MODE_BAN;
413                                 change.args[0].u.hostmask = hostmask;
414                                 mod_chanmode_announce(watchdog, chan, &change);
415                                 free(hostmask);
416
417                         case BADACTION_KICK:
418                                 if(GetUserMode(chan, user))
419                                         KickChannelUser(user, chan, watchdog, reason);
420                                 break;
421                         case BADACTION_KILL:
422                                 DelUser(user, watchdog, 1, reason);
423                                 break;
424                         case BADACTION_GLINE:
425                                 irc_ntop(mask + 2, sizeof(mask) - 2, &user->ip);
426                                 gline_add(watchdog->nick, mask, watchdog_conf.gline_duration, reason, now, now, 0, 1);
427                                 break;
428                         default:
429                                 //error?
430                                 break;
431                         }
432     }
433 }
434
435 static void
436 watchdog_channel_message(struct userNode *user, struct chanNode *chan, const char *text, UNUSED_ARG(struct userNode *bot), UNUSED_ARG(unsigned int is_notice))
437 {
438     dict_iterator_t it;
439
440     if(!watchdog || !dict_find(chanlist, chan->name, NULL))
441         return;
442
443     for (it = dict_first(shitlist); it; it = iter_next(it)) {
444         struct badword *badword = iter_data(it);
445         if(match_ircglob(text, badword->badword_mask)) {
446             watchdog_detected_badword(user, chan, badword);
447         }
448     }
449 }
450
451 static struct badword*
452 add_badword(const char *badword_mask, unsigned int triggered, unsigned int action, unsigned int alert, const char *id)
453 {
454     struct badword *badword;
455
456     badword = calloc(1, sizeof(*badword));
457     if (!badword)
458         return NULL;
459
460     if(!id) {
461         last_badword_id++;
462         badword->id = strtab(last_badword_id);
463     } else
464         badword->id = strdup(id);
465     badword->badword_mask = strdup(badword_mask);
466     badword->triggered = triggered;
467     badword->action = action;
468     badword->alert = alert;
469     dict_insert(shitlist, badword->id, badword);
470     return badword;
471 }
472
473 static void
474 free_shitlist_entry(void *data)
475 {
476     struct badword *badword = data;
477     free(badword->id);
478     free(badword->badword_mask);
479     free(badword);
480 }
481
482 static struct watchdog_channel*
483 add_channel(const char *name)
484 {
485     struct watchdog_channel *wc;
486     struct mod_chanmode *change;
487
488     if(!watchdog) //module disabled
489         return NULL;
490
491     wc = calloc(1, sizeof(*wc));
492     if (!wc)
493         return NULL;
494
495     wc->channel = AddChannel(name, now, NULL, NULL);
496     change = mod_chanmode_alloc(1);
497     change->argc = 1;
498     change->args[0].mode = MODE_CHANOP;
499     change->args[0].u.member = AddChannelUser(watchdog, wc->channel);
500     mod_chanmode_announce(watchdog, wc->channel, change);
501         mod_chanmode_free(change);
502     dict_insert(chanlist, wc->channel->name, wc);
503     return wc;
504 }
505
506 static void
507 free_chanlist_entry(void *data)
508 {
509     struct watchdog_channel *wc = data;
510     
511     free(wc);
512 }
513
514 static void
515 watchdog_conf_read(void)
516 {
517     dict_t conf_node;
518     const char *str;
519
520     str = "modules/watchdog";
521     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
522         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
523         return;
524     }
525
526     str = database_get_data(conf_node, "nick", RECDB_QSTRING);
527     if(watchdog_conf.nick && strcmp(watchdog_conf.nick, str)) {
528         //nick changed
529     }
530     watchdog_conf.nick = str;
531     
532     str = database_get_data(conf_node, "modes", RECDB_QSTRING);
533     watchdog_conf.modes = (str ? str : NULL);
534     
535     str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING);
536         watchdog_conf.ban_duration = str ? ParseInterval(str) : ParseInterval("2h");
537     
538     str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING);
539         watchdog_conf.gline_duration = str ? ParseInterval(str) : ParseInterval("1h");
540     
541     str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING);
542         watchdog_conf.punishment_reason = (str ? str : "Your message contained a forbidden word.");
543
544         str = database_get_data(conf_node, "alert_chan", RECDB_QSTRING);
545         if(str)
546         {
547                 watchdog_conf.alert_channel = AddChannel(str, now, "+tinms", NULL);
548         }
549         else
550         {
551                 watchdog_conf.alert_channel = NULL;
552         }
553     
554         str = database_get_data(conf_node, "oper_chan", RECDB_QSTRING);
555         if(str)
556         {
557                 watchdog_conf.oper_channel = AddChannel(str, now, "+tinms", NULL);
558         }
559         else
560         {
561                 watchdog_conf.oper_channel = NULL;
562         }
563 }
564
565 static int
566 watchdog_saxdb_read_shitlist(const char *name, void *data, UNUSED_ARG(void *extra))
567 {
568     struct record_data *rd = data;
569     char *badword;
570     char *triggered, *action, *alert;
571
572      if (rd->type == RECDB_OBJECT) {
573         dict_t obj = GET_RECORD_OBJECT(rd);
574         /* new style structure */
575         badword = database_get_data(obj, KEY_BADWORD_MASK, RECDB_QSTRING);
576         triggered = database_get_data(obj, KEY_BADWORD_TRIGGERED, RECDB_QSTRING);
577         action = database_get_data(obj, KEY_BADWORD_ACTION, RECDB_QSTRING);
578         alert = database_get_data(obj, KEY_BADWOR_ALERT, RECDB_QSTRING);
579
580         add_badword(badword, strtoul(triggered, NULL, 0), strtoul(action, NULL, 0), strtoul(alert, NULL, 0), name);
581     }
582     return 0;
583 }
584
585 static int
586 watchdog_saxdb_read_chanlist(const char *name, void *data, UNUSED_ARG(void *extra))
587 {
588     struct record_data *rd = data;
589
590      if (rd->type == RECDB_OBJECT) {
591         //dict_t obj = GET_RECORD_OBJECT(rd);
592         /* nothing in here, yet */
593
594         add_channel(name);
595     }
596     return 0;
597 }
598
599 static int
600 watchdog_saxdb_read(struct dict *db)
601 {
602     struct dict *object;
603     char *str;
604     str = database_get_data(db, KEY_BADWORDID, RECDB_QSTRING);
605     last_badword_id = str ? strtoul(str, NULL, 0) : 0;
606
607     if ((object = database_get_data(db, KEY_BADWORDS, RECDB_OBJECT)))
608         dict_foreach(object, watchdog_saxdb_read_shitlist, NULL);
609
610     if ((object = database_get_data(db, KEY_CHANNELS, RECDB_OBJECT)))
611         dict_foreach(object, watchdog_saxdb_read_chanlist, NULL);
612
613     return 1;
614 }
615
616 static int
617 watchdog_saxdb_write(struct saxdb_context *ctx)
618 {
619     dict_iterator_t it;
620
621     saxdb_write_int(ctx, KEY_BADWORDID, last_badword_id);
622
623     if (dict_size(shitlist)) {
624         saxdb_start_record(ctx, KEY_BADWORDS, 1);
625         for (it = dict_first(shitlist); it; it = iter_next(it)) {
626             struct badword *badword = iter_data(it);
627             if(badword && badword->badword_mask) {
628                 saxdb_start_record(ctx, iter_key(it), 0);
629                 
630                 saxdb_write_string(ctx, KEY_BADWORD_MASK, badword->badword_mask);
631                 saxdb_write_int(ctx, KEY_BADWORD_TRIGGERED, badword->triggered);
632                 saxdb_write_int(ctx, KEY_BADWORD_ACTION, badword->action);
633                 saxdb_write_int(ctx, KEY_BADWOR_ALERT, badword->alert);
634                 
635                 saxdb_end_record(ctx);
636             }
637         }
638         saxdb_end_record(ctx);
639     }
640
641     if (dict_size(chanlist)) {
642         saxdb_start_record(ctx, KEY_CHANNELS, 1);
643         for (it = dict_first(chanlist); it; it = iter_next(it)) {
644             struct watchdog_channel *wc = iter_data(it);
645             if(wc && wc->channel && wc->channel->name) {
646                 saxdb_start_record(ctx, wc->channel->name, 0);
647                 //anything else?
648                 saxdb_end_record(ctx);
649             }
650         }
651         saxdb_end_record(ctx);
652     }
653     
654     return 0;
655 }
656
657 static void
658 watchdog_cleanup(void)
659 {
660     dict_delete(shitlist);
661     dict_delete(chanlist);
662 }
663
664 int
665 watchdog_init(void)
666 {
667     MS_LOG = log_register_type("Watchdog", "file:watchdog.log");
668     
669     /* set up shitlist dict */
670     dict_delete(shitlist);
671     shitlist = dict_new();
672     dict_set_free_data(shitlist, free_shitlist_entry);
673     /* set up chanlist dict */
674     dict_delete(chanlist);
675     chanlist = dict_new();
676     dict_set_free_data(chanlist, free_chanlist_entry);
677
678     const char *nick, *modes;
679     if((nick = conf_get_data("modules/watchdog/nick", RECDB_QSTRING))) {
680         modes = conf_get_data("modules/watchdog/modes", RECDB_QSTRING);
681         watchdog = AddLocalUser(nick, nick, NULL, "Watchdog Service", modes);
682         watchdog_service = service_register(watchdog);
683         watchdog_service->trigger = ',';
684         reg_allchanmsg_func(watchdog, watchdog_channel_message);
685     }
686
687     conf_register_reload(watchdog_conf_read);
688     reg_exit_func(watchdog_cleanup);
689     saxdb_register("Watchdog", watchdog_saxdb_read, watchdog_saxdb_write);
690
691     watchdog_module = module_register("Watchdog", MS_LOG, "mod-watchdog.help", NULL);
692     modcmd_register(watchdog_module, "addbad", cmd_addbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
693     modcmd_register(watchdog_module, "delbad", cmd_delbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
694     modcmd_register(watchdog_module, "setbad", cmd_setbad, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
695     modcmd_register(watchdog_module, "listbad", cmd_listbad, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
696     modcmd_register(watchdog_module, "register", cmd_register, 1, MODCMD_REQUIRE_AUTHED, "flags", "+acceptchan,+helping", NULL);
697     modcmd_register(watchdog_module, "unregister", cmd_unregister, 1, MODCMD_REQUIRE_AUTHED | MODCMD_REQUIRE_CHANNEL, "flags", "+helping", NULL);
698     message_register_table(msgtab);
699
700     return 1;
701 }
702
703 int
704 watchdog_finalize(void) {
705     dict_t conf_node;
706     const char *str;
707
708     str = "modules/watchdog";
709     if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
710         log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
711         return 0;
712     }
713
714     str = database_get_data(conf_node, "nick", RECDB_QSTRING);
715     if (str) watchdog_conf.nick = str;
716     
717     str = database_get_data(conf_node, "modes", RECDB_QSTRING);
718     if (str) watchdog_conf.modes = str;
719     
720     str = database_get_data(conf_node, "ban_duration", RECDB_QSTRING);
721         if (str) watchdog_conf.ban_duration = ParseInterval(str);
722     
723     str = database_get_data(conf_node, "gline_duration", RECDB_QSTRING);
724         if (str) watchdog_conf.gline_duration = ParseInterval(str);
725     
726     str = database_get_data(conf_node, "punishment_reason", RECDB_QSTRING);
727         if (str) watchdog_conf.punishment_reason = str;
728     
729     return 1;
730 }