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