]> jfr.im git - irc/evilnet/x3.git/blob - src/global.c
invite now checks silence/invite lists
[irc/evilnet/x3.git] / src / global.c
1 /* global.c - Global notice service
2 * Copyright 2000-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 #include "conf.h"
22 #include "global.h"
23 #include "modcmd.h"
24 #include "nickserv.h"
25 #include "saxdb.h"
26 #include "timeq.h"
27
28 #define GLOBAL_CONF_NAME "services/global"
29
30 #define GLOBAL_DB "global.db"
31 #define GLOBAL_TEMP_DB "global.db.new"
32
33 /* Global options */
34 #define KEY_DB_BACKUP_FREQ "db_backup_freq"
35 #define KEY_ANNOUNCEMENTS_DEFAULT "announcements_default"
36 #define KEY_NICK "nick"
37
38 /* Message data */
39 #define KEY_FLAGS "flags"
40 #define KEY_POSTED "posted"
41 #define KEY_DURATION "duration"
42 #define KEY_FROM "from"
43 #define KEY_MESSAGE "message"
44
45 /* Clarification: Notices are immediate, they are sent to matching users
46 _once_, then forgotten. Messages are stored in Global's database and
47 continually sent to users as they match the target specification until
48 they are deleted. */
49 static const struct message_entry msgtab[] = {
50 { "GMSG_INVALID_TARGET", "$b%s$b is an invalid message target." },
51 { "GMSG_MESSAGE_REQUIRED", "You $bmust$b provide a message to send." },
52 { "GMSG_MESSAGE_SENT", "Message to $b%s$b sent." },
53 { "GMSG_MESSAGE_ADDED", "Message to $b%s$b with ID %ld added." },
54 { "GMSG_MESSAGE_DELETED", "Message $b%s$b deleted." },
55 { "GMSG_ID_INVALID", "$b%s$b is an invalid message ID." },
56 { "GMSG_MESSAGE_COUNT", "$b%d$b messages found." },
57 { "GMSG_NO_MESSAGES", "There are no messages for you." },
58 { "GMSG_NOTICE_SOURCE", "Notice to [$b%s$b] from %s:" },
59 { "GMSG_MESSAGE_SOURCE", "Notice to [$b%s$b] from %s, posted %s:" },
60 //{ "GMSG_MOTD_HEADER", "$b------------- MESSAGE(S) OF THE DAY --------------$b" },
61 { "GMSG_MOTD_HEADER", "$bNetwork Announcements$b" },
62 { "GMSG_MOTD_BAR", "---------------------------------------" },
63 { "GMSG_MOTD_FOOTER", "--------------- Thank You--------------" },
64 { NULL, NULL }
65 };
66
67 #define GLOBAL_SYNTAX() svccmd_send_help_brief(user, global, cmd)
68 #define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
69
70 struct globalMessage
71 {
72 unsigned long id;
73 long flags;
74
75 time_t posted;
76 char posted_s[24];
77 unsigned long duration;
78
79 char *from;
80 char *message;
81
82 struct globalMessage *prev;
83 struct globalMessage *next;
84 };
85
86 struct userNode *global;
87
88 static struct module *global_module;
89 static struct service *global_service;
90 static struct globalMessage *messageList;
91 extern struct string_list *autojoin_channels;
92 static long messageCount;
93 static time_t last_max_alert;
94 static struct log_type *G_LOG;
95
96 static struct
97 {
98 unsigned long db_backup_frequency;
99 unsigned int announcements_default : 1;
100 } global_conf;
101
102 #define global_notice(target, format...) send_message(target , global , ## format)
103
104 void message_expire(void *data);
105
106 static struct globalMessage*
107 message_add(long flags, time_t posted, unsigned long duration, char *from, const char *msg)
108 {
109 struct globalMessage *message;
110 struct tm tm;
111
112 message = malloc(sizeof(struct globalMessage));
113 if(!message)
114 {
115 return NULL;
116 }
117
118 message->id = messageCount++;
119 message->flags = flags;
120 message->posted = posted;
121 message->duration = duration;
122 message->from = strdup(from);
123 message->message = strdup(msg);
124
125 if ((flags & MESSAGE_OPTION_IMMEDIATE) == 0) {
126 localtime_r(&message->posted, &tm);
127 strftime(message->posted_s, sizeof(message->posted_s),
128 "%I:%M %p, %m/%d/%Y", &tm);
129 }
130
131 if(messageList)
132 {
133 messageList->prev = message;
134 }
135 message->prev = NULL;
136 message->next = messageList;
137
138 messageList = message;
139
140 if(duration)
141 {
142 timeq_add(now + duration, message_expire, message);
143 }
144
145 return message;
146 }
147
148 static void
149 message_del(struct globalMessage *message)
150 {
151 if(message->duration)
152 {
153 timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
154 }
155
156 if(message->prev) message->prev->next = message->next;
157 else messageList = message->next;
158
159 if(message->next) message->next->prev = message->prev;
160
161 free(message->from);
162 free(message->message);
163 free(message);
164 }
165
166 void message_expire(void *data)
167 {
168 struct globalMessage *message = data;
169
170 message->duration = 0;
171 message_del(message);
172 }
173
174 static struct globalMessage*
175 message_create(struct userNode *user, unsigned int argc, char *argv[])
176 {
177 unsigned long duration = 0;
178 char *text = NULL;
179 char *sender;
180 long flags = 0;
181 unsigned int i;
182
183 sender = user->handle_info->handle;
184
185 for(i = 0; i < argc; i++)
186 {
187 if((i + 1) > argc)
188 {
189 global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
190 return NULL;
191 }
192
193 if(!irccasecmp(argv[i], "text"))
194 {
195 i++;
196 text = unsplit_string(argv + i, argc - i, NULL);
197 break;
198 } else if (!irccasecmp(argv[i], "sourceless")) {
199 i++;
200 flags |= MESSAGE_OPTION_SOURCELESS;
201 } else if (!irccasecmp(argv[i], "target")) {
202 i++;
203
204 if(!irccasecmp(argv[i], "all")) {
205 flags |= MESSAGE_RECIPIENT_ALL;
206 } else if(!irccasecmp(argv[i], "users")) {
207 flags |= MESSAGE_RECIPIENT_LUSERS;
208 } else if(!irccasecmp(argv[i], "helpers")) {
209 flags |= MESSAGE_RECIPIENT_HELPERS;
210 } else if(!irccasecmp(argv[i], "opers")) {
211 flags |= MESSAGE_RECIPIENT_OPERS;
212 } else if(!irccasecmp(argv[i], "staff") || !irccasecmp(argv[i], "privileged")) {
213 flags |= MESSAGE_RECIPIENT_STAFF;
214 } else if(!irccasecmp(argv[i], "channels")) {
215 flags |= MESSAGE_RECIPIENT_CHANNELS;
216 } else if(!irccasecmp(argv[i], "announcement") || !irccasecmp(argv[i], "announce")) {
217 flags |= MESSAGE_RECIPIENT_ANNOUNCE;
218 } else {
219 global_notice(user, "GMSG_INVALID_TARGET", argv[i]);
220 return NULL;
221 }
222 } else if (irccasecmp(argv[i], "duration") == 0) {
223 duration = ParseInterval(argv[++i]);
224 } else if (irccasecmp(argv[i], "from") == 0) {
225 sender = argv[++i];
226 } else {
227 global_notice(user, "MSG_INVALID_CRITERIA", argv[i]);
228 return NULL;
229 }
230 }
231
232 if(!flags)
233 {
234 flags = MESSAGE_RECIPIENT_LUSERS;
235 }
236
237 if(!text) {
238 global_notice(user, "GMSG_MESSAGE_REQUIRED");
239 return NULL;
240 }
241
242 return message_add(flags, now, duration, sender, text);
243 }
244
245 static const char *
246 messageType(const struct globalMessage *message)
247 {
248 if((message->flags & MESSAGE_RECIPIENT_ALL) == MESSAGE_RECIPIENT_ALL)
249 {
250 return "all";
251 }
252 if((message->flags & MESSAGE_RECIPIENT_STAFF) == MESSAGE_RECIPIENT_STAFF)
253 {
254 return "staff";
255 }
256 else if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
257 {
258 return "announcement";
259 }
260 else if(message->flags & MESSAGE_RECIPIENT_OPERS)
261 {
262 return "opers";
263 }
264 else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
265 {
266 return "helpers";
267 }
268 else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
269 {
270 return "users";
271 }
272 else
273 {
274 return "channels";
275 }
276 }
277
278 static void
279 notice_target(const char *target, struct globalMessage *message)
280 {
281 if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
282 {
283 if(message->flags & MESSAGE_OPTION_IMMEDIATE)
284 {
285 send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
286 }
287 else
288 {
289 send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
290 }
291 }
292
293 send_target_message(4, target, global, "%s", message->message);
294 }
295
296 static int
297 notice_channel(const char *key, void *data, void *extra)
298 {
299 struct chanNode *channel = data;
300 /* It should be safe to assume channel is not NULL. */
301 if(channel->channel_info)
302 notice_target(key, extra);
303 return 0;
304 }
305
306 static void
307 message_send(struct globalMessage *message)
308 {
309 struct userNode *user;
310 unsigned long n;
311 dict_iterator_t it;
312
313 if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
314 {
315 dict_foreach(channels, notice_channel, message);
316 }
317
318 if(message->flags & MESSAGE_RECIPIENT_LUSERS)
319 {
320 notice_target("$*", message);
321 return;
322 }
323
324 if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
325 {
326 char announce;
327
328 for (it = dict_first(clients); it; it = iter_next(it)) {
329 user = iter_data(it);
330 if (user->uplink == self) continue;
331 announce = user->handle_info ? user->handle_info->announcements : '?';
332 if (announce == 'n') continue;
333 if ((announce == '?') && !global_conf.announcements_default) continue;
334 notice_target(user->nick, message);
335 }
336 }
337
338 if(message->flags & MESSAGE_RECIPIENT_OPERS)
339 {
340 for(n = 0; n < curr_opers.used; n++)
341 {
342 user = curr_opers.list[n];
343
344 if(user->uplink != self)
345 {
346 notice_target(user->nick, message);
347 }
348 }
349 }
350
351 if(message->flags & MESSAGE_RECIPIENT_HELPERS)
352 {
353 for(n = 0; n < curr_helpers.used; n++)
354 {
355 user = curr_helpers.list[n];
356 if (IsOper(user))
357 continue;
358 notice_target(user->nick, message);
359 }
360 }
361 }
362
363 void
364 global_message(long targets, char *text)
365 {
366 struct globalMessage *message;
367
368 if(!targets || !global)
369 return;
370
371 message = message_add(targets | MESSAGE_OPTION_SOURCELESS, now, 0, "", text);
372 if(!message)
373 return;
374
375 message_send(message);
376 message_del(message);
377 }
378
379 static GLOBAL_FUNC(cmd_notice)
380 {
381 struct globalMessage *message = NULL;
382 const char *recipient = NULL, *text;
383 char *sender;
384 long target = 0;
385
386 assert(argc >= 3);
387 sender = user->handle_info->handle;
388 if(!irccasecmp(argv[1], "all")) {
389 target = MESSAGE_RECIPIENT_ALL;
390 } else if(!irccasecmp(argv[1], "users")) {
391 target = MESSAGE_RECIPIENT_LUSERS;
392 } else if(!irccasecmp(argv[1], "helpers")) {
393 target = MESSAGE_RECIPIENT_HELPERS;
394 } else if(!irccasecmp(argv[1], "opers")) {
395 target = MESSAGE_RECIPIENT_OPERS;
396 } else if(!irccasecmp(argv[1], "staff") || !irccasecmp(argv[1], "privileged")) {
397 target |= MESSAGE_RECIPIENT_HELPERS | MESSAGE_RECIPIENT_OPERS;
398 } else if(!irccasecmp(argv[1], "announcement") || !irccasecmp(argv[1], "announce")) {
399 target |= MESSAGE_RECIPIENT_ANNOUNCE;
400 } else if(!irccasecmp(argv[1], "channels")) {
401 target = MESSAGE_RECIPIENT_CHANNELS;
402 } else {
403 global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
404 return 0;
405 }
406 if(!irccasecmp(argv[2], "from")) {
407 if (argc < 5) {
408 reply("MSG_MISSING_PARAMS", argv[0]);
409 GLOBAL_SYNTAX();
410 return 0;
411 }
412 sender = argv[3];
413 text = unsplit_string(argv + 4, argc - 4, NULL);
414 } else {
415 text = unsplit_string(argv + 2, argc - 2, NULL);
416 }
417
418 message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
419 if(!message)
420 return 0;
421
422 recipient = messageType(message);
423 message_send(message);
424 message_del(message);
425
426 global_notice(user, "GMSG_MESSAGE_SENT", recipient);
427 return 1;
428 }
429
430 static GLOBAL_FUNC(cmd_message)
431 {
432 struct globalMessage *message = NULL;
433 const char *recipient = NULL;
434
435 assert(argc >= 3);
436 message = message_create(user, argc - 1, argv + 1);
437 if(!message)
438 return 0;
439 recipient = messageType(message);
440 global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
441 return 1;
442 }
443
444 static GLOBAL_FUNC(cmd_list)
445 {
446 struct globalMessage *message;
447 struct helpfile_table table;
448 unsigned int length, nn;
449
450 if(!messageList)
451 {
452 global_notice(user, "GMSG_NO_MESSAGES");
453 return 1;
454 }
455
456 for(nn=0, message = messageList; message; nn++, message=message->next) ;
457 table.length = nn+1;
458 table.width = 5;
459 table.flags = TABLE_NO_FREE;
460 table.contents = calloc(table.length, sizeof(char**));
461 table.contents[0] = calloc(table.width, sizeof(char*));
462 table.contents[0][0] = "ID";
463 table.contents[0][1] = "Target";
464 table.contents[0][2] = "Expires";
465 table.contents[0][3] = "From";
466 table.contents[0][4] = "Message";
467
468 for(nn=1, message = messageList; message; nn++, message = message->next)
469 {
470 char buffer[64];
471
472 table.contents[nn] = calloc(table.width, sizeof(char*));
473 snprintf(buffer, sizeof(buffer), "%lu", message->id);
474 table.contents[nn][0] = strdup(buffer);
475 table.contents[nn][1] = messageType(message);
476 if(message->duration)
477 intervalString(buffer, message->posted + message->duration - now, user->handle_info);
478 else
479 strcpy(buffer, "Never.");
480 table.contents[nn][2] = strdup(buffer);
481 table.contents[nn][3] = message->from;
482 length = strlen(message->message);
483 safestrncpy(buffer, message->message, sizeof(buffer));
484 if(length > (sizeof(buffer) - 4))
485 {
486 buffer[sizeof(buffer) - 1] = 0;
487 buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
488 }
489 table.contents[nn][4] = strdup(buffer);
490 }
491 table_send(global, user->nick, 0, NULL, table);
492 for (nn=1; nn<table.length; nn++)
493 {
494 free((char*)table.contents[nn][0]);
495 free((char*)table.contents[nn][2]);
496 free((char*)table.contents[nn][4]);
497 free(table.contents[nn]);
498 }
499 free(table.contents[0]);
500 free(table.contents);
501
502 return 1;
503 }
504
505 static GLOBAL_FUNC(cmd_remove)
506 {
507 struct globalMessage *message = NULL;
508 unsigned long id;
509
510 assert(argc >= 2);
511 id = strtoul(argv[1], NULL, 0);
512
513 for(message = messageList; message; message = message->next)
514 {
515 if(message->id == id)
516 {
517 message_del(message);
518 global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
519 return 1;
520 }
521 }
522
523 global_notice(user, "GMSG_ID_INVALID", argv[1]);
524 return 0;
525 }
526
527 static unsigned int
528 send_messages(struct userNode *user, long mask, int obstreperize)
529 {
530 struct globalMessage *message = messageList;
531 unsigned int count = 0;
532
533 while(message)
534 {
535 if(message->flags & mask)
536 {
537 if (obstreperize && !count)
538 {
539 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
540 send_target_message(0, user->nick, global, "GMSG_MOTD_BAR");
541 }
542 notice_target(user->nick, message);
543 count++;
544 }
545
546 message = message->next;
547 }
548 if (obstreperize && count)
549 send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
550 return count;
551 }
552
553 static GLOBAL_FUNC(cmd_messages)
554 {
555 long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
556 unsigned int count;
557
558 if(IsOper(user))
559 mask |= MESSAGE_RECIPIENT_OPERS;
560
561 if(IsHelper(user))
562 mask |= MESSAGE_RECIPIENT_HELPERS;
563
564 count = send_messages(user, mask, 0);
565 if(count)
566 global_notice(user, "GMSG_MESSAGE_COUNT", count);
567 else
568 global_notice(user, "GMSG_NO_MESSAGES");
569
570 return 1;
571 }
572
573 static int
574 global_process_user(struct userNode *user)
575 {
576 if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
577 return 0;
578 send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
579
580 /* only alert on new usercount if the record was broken in the last
581 * 30 seconds, and no alert had been sent in that time.
582 */
583 if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
584 {
585 char *message;
586 message = alloca(36);
587 sprintf(message, "New user count record: %d", max_clients);
588 global_message(MESSAGE_RECIPIENT_OPERS, message);
589 last_max_alert = now;
590 }
591
592 return 0;
593 }
594
595 static void
596 global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
597 {
598 if(IsHelper(user))
599 send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
600 }
601
602 static void
603 global_process_oper(struct userNode *user)
604 {
605 if(user->uplink->burst)
606 return;
607 send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
608 }
609
610 static void
611 global_conf_read(void)
612 {
613 dict_t conf_node;
614 const char *str;
615
616 if (!(conf_node = conf_get_data(GLOBAL_CONF_NAME, RECDB_OBJECT))) {
617 log_module(G_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", GLOBAL_CONF_NAME);
618 return;
619 }
620
621 str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
622 global_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
623 str = database_get_data(conf_node, KEY_ANNOUNCEMENTS_DEFAULT, RECDB_QSTRING);
624 global_conf.announcements_default = str ? enabled_string(str) : 1;
625
626 str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
627 if(global && str)
628 NickChange(global, str, 0);
629 }
630
631 static int
632 global_saxdb_read(struct dict *db)
633 {
634 struct record_data *hir;
635 time_t posted;
636 long flags;
637 unsigned long duration;
638 char *str, *from, *message;
639 dict_iterator_t it;
640
641 for(it=dict_first(db); it; it=iter_next(it))
642 {
643 hir = iter_data(it);
644 if(hir->type != RECDB_OBJECT)
645 {
646 log_module(G_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
647 continue;
648 }
649
650 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
651 flags = str ? strtoul(str, NULL, 0) : 0;
652
653 str = database_get_data(hir->d.object, KEY_POSTED, RECDB_QSTRING);
654 posted = str ? strtoul(str, NULL, 0) : 0;
655
656 str = database_get_data(hir->d.object, KEY_DURATION, RECDB_QSTRING);
657 duration = str ? strtoul(str, NULL, 0) : 0;
658
659 from = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING);
660 message = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING);
661
662 message_add(flags, posted, duration, from, message);
663 }
664 return 0;
665 }
666
667 static int
668 global_saxdb_write(struct saxdb_context *ctx)
669 {
670 struct globalMessage *message;
671 char str[16];
672
673 for(message = messageList; message; message = message->next) {
674 snprintf(str, sizeof(str), "%li", message->id);
675 saxdb_start_record(ctx, str, 0);
676 saxdb_write_int(ctx, KEY_FLAGS, message->flags);
677 saxdb_write_int(ctx, KEY_POSTED, message->posted);
678 saxdb_write_int(ctx, KEY_DURATION, message->duration);
679 saxdb_write_string(ctx, KEY_FROM, message->from);
680 saxdb_write_string(ctx, KEY_MESSAGE, message->message);
681 saxdb_end_record(ctx);
682 }
683 return 0;
684 }
685
686 static void
687 global_db_cleanup(void)
688 {
689 while(messageList)
690 message_del(messageList);
691 }
692
693 void
694 init_global(const char *nick)
695 {
696 struct chanNode *chan;
697 unsigned int i;
698 G_LOG = log_register_type("Global", "file:global.log");
699 reg_new_user_func(global_process_user);
700 reg_auth_func(global_process_auth);
701 reg_oper_func(global_process_oper);
702
703 conf_register_reload(global_conf_read);
704
705 global_module = module_register("Global", G_LOG, "global.help", NULL);
706 modcmd_register(global_module, "LIST", cmd_list, 1, 0, "flags", "+oper", NULL);
707 modcmd_register(global_module, "MESSAGE", cmd_message, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
708 modcmd_register(global_module, "MESSAGES", cmd_messages, 1, 0, NULL);
709 modcmd_register(global_module, "NOTICE", cmd_notice, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
710 modcmd_register(global_module, "REMOVE", cmd_remove, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
711
712 if(nick)
713 {
714 const char *modes = conf_get_data("services/global/modes", RECDB_QSTRING);
715 global = AddService(nick, modes ? modes : NULL, "Global Services", NULL);
716 global_service = service_register(global);
717 }
718
719 if(autojoin_channels && global) {
720 for (i = 0; i < autojoin_channels->used; i++) {
721 chan = AddChannel(autojoin_channels->list[i], now, "+nt", NULL, NULL);
722 AddChannelUser(global, chan)->modes |= MODE_CHANOP;
723 }
724 }
725
726 saxdb_register("Global", global_saxdb_read, global_saxdb_write);
727 reg_exit_func(global_db_cleanup);
728 message_register_table(msgtab);
729 }