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