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