]> jfr.im git - irc/evilnet/x3.git/blob - src/mod-track.c
fixing bug with track all/none
[irc/evilnet/x3.git] / src / mod-track.c
1 /* mod-track.c - User surveillance module
2 * Copyright 2002-2004 srvx Development Team
3 *
4 * This file is part of x3.
5 *
6 * x3 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 * "track" {
24 * // What data to show.
25 * "snomask" "nick,join,part,kick,new,del,auth,chanmode,umode";
26 * // Where to send track messages?
27 * "channel" "#wherever";
28 * // Which bot?
29 * "bot" "OpServ";
30 * // Show new users and joins from net joins? (off by default)
31 * "show_bursts" "0";
32 * };
33 * };
34 */
35
36 #include "conf.h"
37 #include "chanserv.h"
38 #include "helpfile.h"
39 #include "nickserv.h"
40 #include "modcmd.h"
41 #include "proto.h"
42 #include "dict.h"
43 #include "hash.h"
44
45 #ifdef HAVE_NETINET_IN_H
46 #include <netinet/in.h>
47 #endif
48 #ifdef HAVE_ARPA_INET_H
49 #include <arpa/inet.h>
50 #endif
51
52 /* track snomask definitions */
53 #define TRACK_NICK 0x0001 /* report nickchanges */
54 #define TRACK_JOIN 0x0002 /* report join/part */
55 #define TRACK_PART 0x0004 /* report parts */
56 #define TRACK_KICK 0x0008 /* report kicks */
57 #define TRACK_NEW 0x0010 /* report new users */
58 #define TRACK_DEL 0x0020 /* report quits */
59 #define TRACK_AUTH 0x0040 /* report auths */
60 #define TRACK_CHANMODE 0x0080 /* report channel modes */
61 #define TRACK_UMODE 0x0100 /* report user modes */
62
63 /* check track status */
64 #define check_track_nick(x) ((x).snomask & TRACK_NICK)
65 #define check_track_join(x) ((x).snomask & TRACK_JOIN)
66 #define check_track_part(x) ((x).snomask & TRACK_PART)
67 #define check_track_kick(x) ((x).snomask & TRACK_KICK)
68 #define check_track_new(x) ((x).snomask & TRACK_NEW)
69 #define check_track_del(x) ((x).snomask & TRACK_DEL)
70 #define check_track_auth(x) ((x).snomask & TRACK_AUTH)
71 #define check_track_chanmode(x) ((x).snomask & TRACK_CHANMODE)
72 #define check_track_umode(x) ((x).snomask & TRACK_UMODE)
73
74 /* set track status */
75 #define set_track_nick(x) ((x).snomask |= TRACK_NICK)
76 #define set_track_join(x) ((x).snomask |= TRACK_JOIN|TRACK_PART)
77 #define set_track_part(x) ((x).snomask |= TRACK_PART)
78 #define set_track_kick(x) ((x).snomask |= TRACK_KICK)
79 #define set_track_new(x) ((x).snomask |= TRACK_NEW|TRACK_DEL)
80 #define set_track_del(x) ((x).snomask |= TRACK_DEL)
81 #define set_track_auth(x) ((x).snomask |= TRACK_AUTH)
82 #define set_track_chanmode(x) ((x).snomask |= TRACK_CHANMODE)
83 #define set_track_umode(x) ((x).snomask |= TRACK_UMODE)
84 #define set_track_all(x) ((x).snomask |= TRACK_NICK|TRACK_JOIN|TRACK_PART|TRACK_KICK|TRACK_NEW|TRACK_DEL|TRACK_AUTH|TRACK_CHANMODE|TRACK_UMODE)
85
86 /* clear track status */
87 #define clear_track_nick(x) ((x).snomask &= ~TRACK_NICK)
88 #define clear_track_join(x) ((x).snomask &= ~(TRACK_JOIN|TRACK_PART))
89 #define clear_track_part(x) ((x).snomask &= ~TRACK_PART)
90 #define clear_track_kick(x) ((x).snomask &= ~TRACK_KICK)
91 #define clear_track_new(x) ((x).snomask &= ~(TRACK_NEW|TRACK_DEL))
92 #define clear_track_del(x) ((x).snomask &= ~TRACK_DEL)
93 #define clear_track_auth(x) ((x).snomask &= ~TRACK_AUTH)
94 #define clear_track_chanmode(x) ((x).snomask &= ~TRACK_CHANMODE)
95 #define clear_track_umode(x) ((x).snomask &= ~TRACK_UMODE)
96 #define clear_track_all(x) ((x).snomask &= ~(TRACK_NICK|TRACK_JOIN|TRACK_PART|TRACK_KICK|TRACK_NEW|TRACK_DEL|TRACK_AUTH|TRACK_CHANMODE|TRACK_UMODE))
97
98 extern struct modcmd *opserv_define_func(const char *name, modcmd_func_t *func, int min_level, int reqchan, int min_argc);
99
100 extern time_t now;
101 static struct {
102 struct chanNode *channel;
103 struct userNode *bot;
104 unsigned int snomask;
105 unsigned int show_bursts : 1;
106 unsigned int enabled : 1;
107 } track_cfg;
108 static char timestamp[16];
109 static dict_t track_db = NULL;
110
111 const char *track_module_deps[] = { NULL };
112
113 static int finalized;
114 int track_finalize(void);
115
116 #define TRACK(FORMAT, ARGS...) send_channel_message(track_cfg.channel, track_cfg.bot, "%s "FORMAT, timestamp , ## ARGS)
117 #define UPDATE_TIMESTAMP() strftime(timestamp, sizeof(timestamp), "[%H:%M:%S]", localtime(&now))
118
119 void add_track_user(struct userNode *user) { dict_insert(track_db, (const char *)user->nick, user); }
120 static void del_track_user(struct userNode *user) { dict_remove2(track_db, (const char *)user->nick, 1); }
121 static int
122 check_track_user(struct userNode *user)
123 {
124 int found;
125 dict_find(track_db, (const char *)user->nick, &found);
126 return found;
127 }
128
129 static void
130 parse_track_conf(char *line)
131 {
132 char *t = NULL, *s = line;
133
134 while(s)
135 {
136 if ((t = strchr(s, ',')))
137 *t++ = 0;
138
139 switch (tolower(s[0]))
140 {
141 case 'a':
142 if(!strcasecmp(s, "auth"))
143 set_track_auth(track_cfg);
144 break;
145 case 'c':
146 if(!strcasecmp(s, "chanmode"))
147 set_track_chanmode(track_cfg);
148 break;
149 case 'd':
150 if(!strcasecmp(s, "del"))
151 set_track_del(track_cfg);
152 break;
153 case 'j':
154 if(!strcasecmp(s, "join"))
155 set_track_join(track_cfg);
156 break;
157 case 'k':
158 if(!strcasecmp(s, "kick"))
159 set_track_kick(track_cfg);
160 break;
161 case 'n':
162 if(!strcasecmp(s, "new"))
163 set_track_new(track_cfg);
164 if(!strcasecmp(s, "nick"))
165 set_track_nick(track_cfg);
166 break;
167 case 'p':
168 if(!strcasecmp(s, "part"))
169 set_track_nick(track_cfg);
170 break;
171 case 'u':
172 if(!strcasecmp(s, "umode"))
173 set_track_umode(track_cfg);
174 break;
175 }
176 s = t;
177 }
178 }
179
180 static void
181 track_nick_change(struct userNode *user, const char *old_nick) {
182 if (!track_cfg.enabled) return;
183 if (check_track_nick(track_cfg) && check_track_user(user))
184 {
185 UPDATE_TIMESTAMP();
186 TRACK("$bNICK$b change %s -> %s", old_nick, user->nick);
187 }
188 }
189
190 static int
191 track_join(struct modeNode *mNode) {
192 struct userNode *user = mNode->user;
193 struct chanNode *chan = mNode->channel;
194 if (!track_cfg.enabled) return 0;
195 if (user->uplink->burst && !track_cfg.show_bursts) return 0;
196 if (check_track_join(track_cfg) && check_track_user(user))
197 {
198 UPDATE_TIMESTAMP();
199 if (chan->members.used == 1) {
200 TRACK("$bCREATE$b %s by %s", chan->name, user->nick);
201 } else {
202 TRACK("$bJOIN$b %s by %s", chan->name, user->nick);
203 }
204 }
205 return 0;
206 }
207
208 static void
209 track_part(struct modeNode *mn, const char *reason) {
210 if (!track_cfg.enabled) return;
211 if (mn->user->dead) return;
212 if (check_track_part(track_cfg) && check_track_user(mn->user))
213 {
214 UPDATE_TIMESTAMP();
215 TRACK("$bPART$b %s by %s (%s)", mn->channel->name, mn->user->nick, reason ? reason : "");
216 }
217 }
218
219 static void
220 track_kick(struct userNode *kicker, struct userNode *victim, struct chanNode *chan) {
221 if (!track_cfg.enabled) return;
222 if (check_track_kick(track_cfg) && ((check_track_user(kicker) || check_track_user(victim))))
223 {
224 UPDATE_TIMESTAMP();
225 TRACK("$bKICK$b %s from %s by %s", victim->nick, chan->name, (kicker ? kicker->nick : "some server"));
226 }
227 }
228
229 static int
230 track_new_user(struct userNode *user) {
231 if (!track_cfg.enabled) return 0;
232 if (user->uplink->burst && !track_cfg.show_bursts) return 0;
233 if (check_track_new(track_cfg) && check_track_user(user))
234 {
235 UPDATE_TIMESTAMP();
236 TRACK("$bNICK$b %s %s@%s [%s] on %s", user->nick, user->ident, user->hostname, irc_ntoa(&user->ip), user->uplink->name);
237 }
238 return 0;
239 }
240
241 static void
242 track_del_user(struct userNode *user, struct userNode *killer, const char *why) {
243 if (!track_cfg.enabled) return;
244 if (check_track_del(track_cfg) && (check_track_user(user) || (killer && check_track_user(killer))))
245 {
246 UPDATE_TIMESTAMP();
247 if (killer) {
248 TRACK("$bKILL$b %s (%s@%s, on %s) by %s (%s)", user->nick, user->ident, user->hostname, user->uplink->name, killer->nick, why);
249 } else {
250 TRACK("$bQUIT$b %s (%s@%s, on %s) (%s)", user->nick, user->ident, user->hostname, user->uplink->name, why);
251 }
252 del_track_user(user);
253 }
254 }
255
256 static void
257 track_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle)) {
258 if (!track_cfg.enabled) return;
259 if (user->uplink->burst && !track_cfg.show_bursts) return;
260 if (user->handle_info && (check_track_auth(track_cfg) && check_track_user(user))) {
261 UPDATE_TIMESTAMP();
262 TRACK("$bAUTH$b %s!%s@%s [%s] on %s as %s", user->nick, user->ident, user->hostname,
263 irc_ntoa(&user->ip), user->uplink->name, user->handle_info->handle);
264 }
265 }
266
267 static void
268 track_user_mode(struct userNode *user, const char *mode_change) {
269 if (!track_cfg.enabled) return;
270 if (user->uplink->burst && !track_cfg.show_bursts) return;
271 if (!mode_change[1]) return; /* warning there has to be atleast one char in the buffer */
272 if(check_track_umode(track_cfg) && check_track_user(user))
273 {
274 UPDATE_TIMESTAMP();
275 TRACK("$bUMODE$b %s %s", user->nick, mode_change);
276 }
277 }
278
279 static void
280 track_oper(struct userNode *user) {
281 if (!track_cfg.enabled) return;
282 if (user->uplink->burst && !track_cfg.show_bursts) return;
283 UPDATE_TIMESTAMP();
284 TRACK("$bOPER$b %s!%s@%s [%s] on %s", user->nick, user->ident, user->hostname, irc_ntoa(&user->ip), user->uplink->name);
285 }
286
287 static void
288 track_channel_mode(struct userNode *who, struct chanNode *channel, char **modes, unsigned int argc)
289 {
290 if (!track_cfg.enabled) return;
291 if(who)
292 {
293 if (who->uplink->burst && !track_cfg.show_bursts) return;
294 if (!check_track_chanmode(track_cfg) || !check_track_user(who)) return;
295 } else
296 return;
297
298 static char targets[MAXLEN], string[MAXLEN];
299 struct userNode *un = NULL;
300 char *tmp = NULL, *tg = NULL, *md = NULL;
301 int add = 0;
302
303 string[0] = 0;
304 targets[0] = 0;
305
306 if (argc > 0)
307 unsplit_string(modes, argc, string);
308 else
309 strcpy(string, *modes);
310
311 if((tg = strchr(string, ' ')))
312 {
313 *tg++ = 0;
314 for(md = string; *md; md++)
315 {
316 if (*md == '+')
317 {
318 add = 1;
319 md++;
320 }
321 if (*md == '-')
322 {
323 add = 0;
324 md++;
325 }
326 switch(*md)
327 {
328 case 'k':
329 {
330 strcat(targets, " ");
331 if ((tmp = strchr(tg, ' ')))
332 *tmp++ = 0;
333 strcat(targets, tg);
334 if(tmp)
335 tg = tmp;
336 break;
337 }
338 case 'l':
339 {
340 if(add)
341 {
342 strcat(targets, " ");
343 if ((tmp = strchr(tg, ' ')))
344 *tmp++ = 0;
345 strcat(targets, tg);
346 if(tmp)
347 tg = tmp;
348 break;
349 }
350 }
351 case 'b':
352 {
353 strcat(targets, " ");
354 if ((tmp = strchr(tg, ' ')))
355 *tmp++ = 0;
356 strcat(targets, tg);
357 if(tmp)
358 tg = tmp;
359 break;
360 }
361 case 'e':
362 {
363 strcat(targets, " ");
364 if ((tmp = strchr(tg, ' ')))
365 *tmp++ = 0;
366 strcat(targets, tg);
367 if(tmp)
368 tg = tmp;
369 break;
370 }
371 case 'o':
372 {
373 strcat(targets, " ");
374 if ((tmp = strchr(tg, ' ')))
375 *tmp++ = 0;
376 if((un = GetUserN(tg)))
377 strcat(targets, un->nick);
378 else
379 strcat(targets, tg);
380 if(tmp)
381 tg = tmp;
382 break;
383 }
384 case 'v':
385 {
386 strcat(targets, " ");
387 if ((tmp = strchr(tg, ' ')))
388 *tmp++ = 0;
389 if((un = GetUserN(tg)))
390 strcat(targets, un->nick);
391 else
392 strcat(targets, tg);
393 if(tmp)
394 tg = tmp;
395 break;
396 }
397 }
398 }
399 }
400 UPDATE_TIMESTAMP();
401 if (who)
402 TRACK("$bMODE$b %s %s%s by %s", channel->name, string, targets, who->nick);
403 else
404 TRACK("$bMODE$b %s %s%s", channel->name, string, targets);
405 }
406
407 static void
408 check_track_state(struct userNode *user)
409 {
410 send_message_type(4, user, track_cfg.bot, "TRACK is tracking: %s%s%s%s%s%s%s%s%s",
411 check_track_nick(track_cfg) ? " nick":"", check_track_join(track_cfg) ? " join":"",
412 check_track_part(track_cfg) ? " part":"", check_track_kick(track_cfg) ? " kick":"",
413 check_track_new(track_cfg) ? " new":"", check_track_del(track_cfg) ? " del":"",
414 check_track_auth(track_cfg) ? " auth":"", check_track_chanmode(track_cfg) ? " chanmode":"",
415 check_track_umode(track_cfg) ? " umode":"");
416 }
417
418 MODCMD_FUNC(cmd_track)
419 {
420 unsigned int i, add;
421 const char *data;
422 char changed = false;
423
424 if(argc == 1)
425 {
426 svccmd_send_help_brief(user, track_cfg.bot, cmd);
427 check_track_state(user);
428 return 0;
429 }
430
431 for(i = 1; i < argc; i++)
432 {
433 data = argv[i];
434 add = 2;
435 changed = true;
436
437 if(data[0] == '+')
438 add = 1;
439 if(data[0] == '-')
440 add = 0;
441
442 if(add == 2)
443 {
444 if ((!strcasecmp(data, "all")))
445 {
446 set_track_all(track_cfg);
447 check_track_state(user);
448 TRACK("$bALERT$b TRACK fully enabled by %s", user->nick);
449 }
450 else if (!strcasecmp(data, "none"))
451 {
452 clear_track_all(track_cfg);
453 check_track_state(user);
454 TRACK("$bALERT$b TRACK disabled by %s", user->nick);
455 }
456 else
457 {
458 send_message_type(4, user, track_cfg.bot, "Unrecognised parameter: %s", data);
459 svccmd_send_help_brief(user, track_cfg.bot, cmd);
460 }
461 return 0;
462 }
463
464 *data++;
465
466 switch(tolower(data[0]))
467 {
468 case 'a':
469 if(!strcasecmp(data, "auth"))
470 {
471 if (add)
472 set_track_auth(track_cfg);
473 else
474 clear_track_auth(track_cfg);
475 }
476 break;
477 case 'c':
478 if(!strcasecmp(data, "chanmode"))
479 {
480 if (add)
481 set_track_chanmode(track_cfg);
482 else
483 clear_track_chanmode(track_cfg);
484 }
485 break;
486 case 'd':
487 if(!strcasecmp(data, "del"))
488 {
489 if (add)
490 set_track_del(track_cfg);
491 else
492 clear_track_del(track_cfg);
493 }
494 break;
495 case 'j':
496 if(!strcasecmp(data, "join"))
497 {
498 if(add)
499 set_track_join(track_cfg);
500 else
501 clear_track_join(track_cfg);
502 }
503 break;
504 case 'k':
505 if(!strcasecmp(data, "kick"))
506 {
507 if(add)
508 set_track_kick(track_cfg);
509 else
510 clear_track_kick(track_cfg);
511 }
512 break;
513 case 'n':
514 if(!strcasecmp(data, "new"))
515 {
516 if(add)
517 set_track_new(track_cfg);
518 else
519 clear_track_new(track_cfg);
520 }
521 if(!strcasecmp(data, "nick"))
522 {
523 if(add)
524 set_track_nick(track_cfg);
525 else
526 clear_track_nick(track_cfg);
527 }
528 break;
529 case 'p':
530 if(!strcasecmp(data, "part"))
531 {
532 if(add)
533 set_track_part(track_cfg);
534 else
535 clear_track_part(track_cfg);
536 }
537 break;
538 case 'u':
539 if(!strcasecmp(data, "umode"))
540 {
541 if(add)
542 set_track_umode(track_cfg);
543 else
544 clear_track_umode(track_cfg);
545 }
546 break;
547 default:
548 TRACK("Error, Unknown value %s", data);
549 break;
550 }
551 }
552 check_track_state(user);
553
554 if(changed)
555 {
556 char buf[256];
557 unsigned int pos = 0;
558 memset(buf, 0, sizeof(buf));
559 for(i = 1; i < argc; i++)
560 {
561 unsigned int len;
562 data = argv[i];
563 len = strlen(data);
564 if(pos + len > sizeof(buf))
565 break;
566 strcpy(&buf[pos], data);
567 pos += len;
568 }
569
570 UPDATE_TIMESTAMP();
571 TRACK("$bALERT$b TRACK command called with parameters '%s' by %s",
572 buf, user->nick);
573 }
574 return 0;
575 }
576
577 MODCMD_FUNC(cmd_deltrack)
578 {
579 struct userNode *un = NULL;
580
581 if((argc > 1) && (un = dict_find(clients, argv[1], NULL)))
582 {
583 if(check_track_user(un))
584 {
585 del_track_user(un);
586 UPDATE_TIMESTAMP();
587 TRACK("$bALERT$b No longer monitoring %s!%s@%s on %s requested by %s",
588 un->nick, un->ident, un->hostname, un->uplink->name, user->nick);
589 }
590 else
591 send_message_type(4, user, track_cfg.bot, "This nick isn't monitored.");
592 }
593 else
594 {
595 send_message_type(4, user, track_cfg.bot, "No nick or invalid nick specified.");
596 svccmd_send_help_brief(user, track_cfg.bot, cmd);
597 }
598 return 0;
599 }
600
601 MODCMD_FUNC(cmd_addtrack)
602 {
603 struct userNode *un = NULL;
604
605 if((argc > 1) && (un = dict_find(clients, argv[1], NULL)))
606 {
607 add_track_user(un);
608 UPDATE_TIMESTAMP();
609 TRACK("$bALERT$b Manually enabled monitoring of %s!%s@%s on %s requested by %s",
610 un->nick, un->ident, un->hostname, un->uplink->name, user->nick);
611 send_message_type(4, user, track_cfg.bot, "Now tracking %s!%s@%s on %s", un->nick,un->ident,un->hostname, un->uplink->name);
612 }
613 else
614 {
615 send_message_type(4, user, track_cfg.bot, "No nick or invalid nick specified.");
616 svccmd_send_help_brief(user, track_cfg.bot, cmd);
617 }
618 return 0;
619 }
620
621 MODCMD_FUNC(cmd_listtrack)
622 {
623 dict_iterator_t it, next;
624 if (track_db == NULL) return 0;
625 struct userNode *un = NULL;
626 send_message_type(4, user, track_cfg.bot, "Currently tracking:");
627 for (it=dict_first(track_db); it; it=next) {
628 next = iter_next(it);
629 un = it->data;
630 send_message_type(4, user, track_cfg.bot, "%s!%s@%s [%s] on %s",
631 un->nick, un->ident, un->hostname, irc_ntoa(&un->ip), un->uplink->name);
632 }
633 send_message_type(4, user, track_cfg.bot, "End of track list.");
634 return 0;
635 }
636
637 static void
638 track_conf_read(void) {
639 dict_t node;
640 char *str;
641
642 node = conf_get_data("modules/track", RECDB_OBJECT);
643 if (!node)
644 return;
645 str = database_get_data(node, "snomask", RECDB_QSTRING);
646 if (!str)
647 track_cfg.snomask = TRACK_NICK|TRACK_KICK|TRACK_JOIN|TRACK_PART|TRACK_CHANMODE|TRACK_NEW|TRACK_DEL|TRACK_AUTH;
648 else
649 parse_track_conf(str);
650 str = database_get_data(node, "channel", RECDB_QSTRING);
651 if (!str)
652 return;
653 // XXX - dont do addchannel if the channel is being shared with
654 // another module:
655 track_cfg.channel = AddChannel(str, now, "+sntOm", NULL, NULL);
656 if (!track_cfg.channel)
657 return;
658 str = database_get_data(node, "show_bursts", RECDB_QSTRING);
659 track_cfg.show_bursts = str ? enabled_string(str) : 0;
660 track_cfg.enabled = 1;
661 if (finalized)
662 track_finalize();
663 }
664
665 void
666 track_cleanup(void) {
667 track_cfg.enabled = 0;
668 unreg_del_user_func(track_del_user);
669 }
670
671 int
672 track_init(void) {
673 track_db = dict_new();
674
675 reg_exit_func(track_cleanup);
676 conf_register_reload(track_conf_read);
677 reg_nick_change_func(track_nick_change);
678 reg_join_func(track_join);
679 reg_part_func(track_part);
680 reg_kick_func(track_kick);
681 reg_new_user_func(track_new_user);
682 reg_del_user_func(track_del_user);
683 reg_auth_func(track_auth);
684 reg_channel_mode_func(track_channel_mode);
685 reg_user_mode_func(track_user_mode);
686 reg_oper_func(track_oper);
687 opserv_define_func("TRACK", cmd_track, 800, 0, 0);
688 opserv_define_func("DELTRACK", cmd_deltrack, 800, 0, 0);
689 opserv_define_func("ADDTRACK", cmd_addtrack, 800, 0, 0);
690 opserv_define_func("LISTTRACK", cmd_listtrack, 800, 0, 0);
691 return 1;
692 }
693
694 int
695 track_finalize(void) {
696 struct mod_chanmode change;
697 dict_t node;
698 char *str;
699
700 finalized = 1;
701 node = conf_get_data("modules/track", RECDB_OBJECT);
702 if (!node)
703 return 0;
704 str = database_get_data(node, "snomask", RECDB_QSTRING);
705 if (!str)
706 track_cfg.snomask = TRACK_NICK|TRACK_KICK|TRACK_JOIN|TRACK_PART|TRACK_CHANMODE|TRACK_NEW|TRACK_DEL|TRACK_AUTH;
707 else
708 parse_track_conf(str);
709 str = database_get_data(node, "bot", RECDB_QSTRING);
710 if (!str)
711 return 0;
712 track_cfg.bot = GetUserH(str);
713 if (!track_cfg.bot)
714 return 0;
715 mod_chanmode_init(&change);
716 change.argc = 1;
717 change.args[0].mode = MODE_CHANOP;
718 change.args[0].u.member = AddChannelUser(track_cfg.bot, track_cfg.channel);
719 mod_chanmode_announce(track_cfg.bot, track_cfg.channel, &change);
720 return 1;
721 }
722
723