1 /* global.c - Global notice service
2 * Copyright 2000-2004 srvx Development Team
4 * This program is free software; you can redistribute it and/or modify
5 * it under the terms of the GNU General Public License as published by
6 * the Free Software Foundation; either version 2 of the License, or
7 * (at your option) any later version. Important limitations are
8 * listed in the COPYING file that accompanies this software.
10 * This program is distributed in the hope that it will be useful,
11 * but WITHOUT ANY WARRANTY; without even the implied warranty of
12 * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
13 * GNU General Public License for more details.
15 * You should have received a copy of the GNU General Public License
16 * along with this program; if not, email srvx-maintainers@srvx.net.
26 #define GLOBAL_CONF_NAME "services/global"
28 #define GLOBAL_DB "global.db"
29 #define GLOBAL_TEMP_DB "global.db.new"
32 #define KEY_DB_BACKUP_FREQ "db_backup_freq"
33 #define KEY_ANNOUNCEMENTS_DEFAULT "announcements_default"
34 #define KEY_NICK "nick"
37 #define KEY_FLAGS "flags"
38 #define KEY_POSTED "posted"
39 #define KEY_DURATION "duration"
40 #define KEY_FROM "from"
41 #define KEY_MESSAGE "message"
43 /* Clarification: Notices are immediate, they are sent to matching users
44 _once_, then forgotten. Messages are stored in Global's database and
45 continually sent to users as they match the target specification until
47 static const struct message_entry msgtab[] = {
48 { "GMSG_INVALID_TARGET", "$b%s$b is an invalid message target." },
49 { "GMSG_MESSAGE_REQUIRED", "You $bmust$b provide a message to send." },
50 { "GMSG_MESSAGE_SENT", "Message to $b%s$b sent." },
51 { "GMSG_MESSAGE_ADDED", "Message to $b%s$b with ID %ld added." },
52 { "GMSG_MESSAGE_DELETED", "Message $b%s$b deleted." },
53 { "GMSG_ID_INVALID", "$b%s$b is an invalid message ID." },
54 { "GMSG_MESSAGE_COUNT", "$b%d$b messages found." },
55 { "GMSG_NO_MESSAGES", "There are no messages for you." },
56 { "GMSG_NOTICE_SOURCE", "[$b%s$b] Notice from %s:" },
57 { "GMSG_MESSAGE_SOURCE", "[$b%s$b] Notice from %s, posted %s:" },
58 { "GMSG_MOTD_HEADER", "$b------------- MESSAGE(S) OF THE DAY --------------$b" },
59 { "GMSG_MOTD_FOOTER", "$b---------- END OF MESSAGE(S) OF THE DAY ----------$b" },
63 #define GLOBAL_SYNTAX() svccmd_send_help(user, global, cmd)
64 #define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
66 struct userNode *global;
68 static struct module *global_module;
69 static struct service *global_service;
70 static struct globalMessage *messageList;
71 static long messageCount;
72 static time_t last_max_alert;
73 static struct log_type *G_LOG;
77 unsigned long db_backup_frequency;
78 unsigned int announcements_default : 1;
81 #define global_notice(target, format...) send_message(target , global , ## format)
83 void message_expire(void *data);
85 static struct globalMessage*
86 message_add(long flags, time_t posted, unsigned long duration, char *from, const char *msg)
88 struct globalMessage *message;
90 message = malloc(sizeof(struct globalMessage));
97 message->id = messageCount++;
98 message->flags = flags;
99 message->posted = posted;
100 message->duration = duration;
101 message->from = strdup(from);
102 message->message = strdup(msg);
106 messageList->prev = message;
109 message->prev = NULL;
110 message->next = messageList;
112 messageList = message;
116 timeq_add(now + duration, message_expire, message);
123 message_del(struct globalMessage *message)
125 if(message->duration)
127 timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
130 if(message->prev) message->prev->next = message->next;
131 else messageList = message->next;
133 if(message->next) message->next->prev = message->prev;
136 free(message->message);
140 void message_expire(void *data)
142 struct globalMessage *message = data;
144 message->duration = 0;
145 message_del(message);
148 static struct globalMessage*
149 message_create(struct userNode *user, unsigned int argc, char *argv[])
151 unsigned long duration = 0;
156 for(i = 0; i < argc; i++)
160 global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
164 if(!irccasecmp(argv[i], "text"))
167 text = unsplit_string(argv + i, argc - i, NULL);
169 } else if (!irccasecmp(argv[i], "sourceless")) {
171 flags |= MESSAGE_OPTION_SOURCELESS;
172 } else if (!irccasecmp(argv[i], "target")) {
175 if(!irccasecmp(argv[i], "all")) {
176 flags |= MESSAGE_RECIPIENT_ALL;
177 } else if(!irccasecmp(argv[i], "users")) {
178 flags |= MESSAGE_RECIPIENT_LUSERS;
179 } else if(!irccasecmp(argv[i], "helpers")) {
180 flags |= MESSAGE_RECIPIENT_HELPERS;
181 } else if(!irccasecmp(argv[i], "opers")) {
182 flags |= MESSAGE_RECIPIENT_OPERS;
183 } else if(!irccasecmp(argv[i], "staff") || !irccasecmp(argv[i], "privileged")) {
184 flags |= MESSAGE_RECIPIENT_STAFF;
185 } else if(!irccasecmp(argv[i], "channels")) {
186 flags |= MESSAGE_RECIPIENT_CHANNELS;
187 } else if(!irccasecmp(argv[i], "announcement") || !irccasecmp(argv[i], "announce")) {
188 flags |= MESSAGE_RECIPIENT_ANNOUNCE;
190 global_notice(user, "GMSG_INVALID_TARGET", argv[i]);
193 } else if (irccasecmp(argv[i], "duration") == 0) {
194 duration = ParseInterval(argv[++i]);
196 global_notice(user, "MSG_INVALID_CRITERIA", argv[i]);
203 flags = MESSAGE_RECIPIENT_LUSERS;
207 global_notice(user, "GMSG_MESSAGE_REQUIRED");
211 return message_add(flags, now, duration, user->handle_info->handle, text);
215 messageType(const struct globalMessage *message)
217 if((message->flags & MESSAGE_RECIPIENT_ALL) == MESSAGE_RECIPIENT_ALL)
221 if((message->flags & MESSAGE_RECIPIENT_STAFF) == MESSAGE_RECIPIENT_STAFF)
225 else if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
227 return "announcement";
229 else if(message->flags & MESSAGE_RECIPIENT_OPERS)
233 else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
237 else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
248 notice_target(const char *target, struct globalMessage *message)
250 if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
252 if(message->flags & MESSAGE_OPTION_IMMEDIATE)
254 send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
261 localtime_r(&message->posted, &tm);
262 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", &tm);
263 send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, posted);
267 send_target_message(4, target, global, "%s", message->message);
271 notice_channel(const char *key, void *data, void *extra)
273 struct chanNode *channel = data;
274 /* It should be safe to assume channel is not NULL. */
275 if(channel->channel_info)
276 notice_target(key, extra);
281 message_send(struct globalMessage *message)
283 struct userNode *user;
287 if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
289 dict_foreach(channels, notice_channel, message);
292 if(message->flags & MESSAGE_RECIPIENT_LUSERS)
294 notice_target("$*", message);
298 if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
302 for (it = dict_first(clients); it; it = iter_next(it)) {
303 user = iter_data(it);
304 if (user->uplink == self) continue;
305 announce = user->handle_info ? user->handle_info->announcements : '?';
306 if (announce == 'n') continue;
307 if ((announce == '?') && !global_conf.announcements_default) continue;
308 notice_target(user->nick, message);
312 if(message->flags & MESSAGE_RECIPIENT_OPERS)
314 for(n = 0; n < curr_opers.used; n++)
316 user = curr_opers.list[n];
318 if(user->uplink != self)
320 notice_target(user->nick, message);
325 if(message->flags & MESSAGE_RECIPIENT_HELPERS)
327 for(n = 0; n < curr_helpers.used; n++)
329 user = curr_helpers.list[n];
332 notice_target(user->nick, message);
338 global_message(long targets, char *text)
340 struct globalMessage *message;
342 if(!targets || !global)
345 message = message_add(targets | MESSAGE_OPTION_SOURCELESS, now, 0, "", text);
349 message_send(message);
350 message_del(message);
353 static GLOBAL_FUNC(cmd_notice)
355 struct globalMessage *message = NULL;
356 const char *recipient = NULL, *text;
360 if(!irccasecmp(argv[1], "all")) {
361 target = MESSAGE_RECIPIENT_ALL;
362 } else if(!irccasecmp(argv[1], "users")) {
363 target = MESSAGE_RECIPIENT_LUSERS;
364 } else if(!irccasecmp(argv[1], "helpers")) {
365 target = MESSAGE_RECIPIENT_HELPERS;
366 } else if(!irccasecmp(argv[1], "opers")) {
367 target = MESSAGE_RECIPIENT_OPERS;
368 } else if(!irccasecmp(argv[1], "staff") || !irccasecmp(argv[1], "privileged")) {
369 target |= MESSAGE_RECIPIENT_HELPERS | MESSAGE_RECIPIENT_OPERS;
370 } else if(!irccasecmp(argv[1], "announcement") || !irccasecmp(argv[1], "announce")) {
371 target |= MESSAGE_RECIPIENT_ANNOUNCE;
372 } else if(!irccasecmp(argv[1], "channels")) {
373 target = MESSAGE_RECIPIENT_CHANNELS;
375 global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
379 text = unsplit_string(argv + 2, argc - 2, NULL);
380 message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, user->handle_info->handle, text);
387 recipient = messageType(message);
389 message_send(message);
390 message_del(message);
392 global_notice(user, "GMSG_MESSAGE_SENT", recipient);
396 static GLOBAL_FUNC(cmd_message)
398 struct globalMessage *message = NULL;
399 const char *recipient = NULL;
402 message = message_create(user, argc - 1, argv + 1);
405 recipient = messageType(message);
406 global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
410 static GLOBAL_FUNC(cmd_list)
412 struct globalMessage *message;
413 struct helpfile_table table;
414 unsigned int length, nn;
418 global_notice(user, "GMSG_NO_MESSAGES");
422 for(nn=0, message = messageList; message; nn++, message=message->next) ;
425 table.flags = TABLE_NO_FREE;
426 table.contents = calloc(table.length, sizeof(char**));
427 table.contents[0] = calloc(table.width, sizeof(char*));
428 table.contents[0][0] = "ID";
429 table.contents[0][1] = "Target";
430 table.contents[0][2] = "Expires";
431 table.contents[0][3] = "From";
432 table.contents[0][4] = "Message";
434 for(nn=1, message = messageList; message; nn++, message = message->next)
438 table.contents[nn] = calloc(table.width, sizeof(char*));
439 snprintf(buffer, sizeof(buffer), "%lu", message->id);
440 table.contents[nn][0] = strdup(buffer);
441 table.contents[nn][1] = messageType(message);
442 if(message->duration)
444 intervalString(buffer, message->posted + message->duration - now);
448 strcpy(buffer, "Never.");
450 table.contents[nn][2] = strdup(buffer);
451 table.contents[nn][3] = message->from;
452 length = strlen(message->message);
453 safestrncpy(buffer, message->message, sizeof(buffer));
454 if(length > (sizeof(buffer) - 4))
456 buffer[sizeof(buffer) - 1] = 0;
457 buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
459 table.contents[nn][4] = strdup(buffer);
461 table_send(global, user->nick, 0, NULL, table);
462 for (nn=1; nn<table.length; nn++)
464 free((char*)table.contents[nn][0]);
465 free((char*)table.contents[nn][2]);
466 free((char*)table.contents[nn][4]);
467 free(table.contents[nn]);
469 free(table.contents[0]);
470 free(table.contents);
475 static GLOBAL_FUNC(cmd_remove)
477 struct globalMessage *message = NULL;
481 id = strtoul(argv[1], NULL, 0);
483 for(message = messageList; message; message = message->next)
485 if(message->id == id)
487 message_del(message);
488 global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
493 global_notice(user, "GMSG_ID_INVALID", argv[1]);
498 send_messages(struct userNode *user, long mask, int obstreperize)
500 struct globalMessage *message = messageList;
501 unsigned int count = 0;
505 if(message->flags & mask)
507 if (obstreperize && !count)
508 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
509 notice_target(user->nick, message);
513 message = message->next;
515 if (obstreperize && count)
516 send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
520 static GLOBAL_FUNC(cmd_messages)
522 long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
526 mask |= MESSAGE_RECIPIENT_OPERS;
529 mask |= MESSAGE_RECIPIENT_HELPERS;
531 count = send_messages(user, mask, 0);
533 global_notice(user, "GMSG_MESSAGE_COUNT", count);
535 global_notice(user, "GMSG_NO_MESSAGES");
541 global_process_user(struct userNode *user)
543 if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
545 send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
547 /* only alert on new usercount if the record was broken in the last
548 * 30 seconds, and no alert had been sent in that time.
550 if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
553 message = alloca(36);
554 sprintf(message, "New user count record: %d", max_clients);
555 global_message(MESSAGE_RECIPIENT_OPERS, message);
556 last_max_alert = now;
563 global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
566 send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
570 global_process_oper(struct userNode *user)
572 if(user->uplink->burst)
574 send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
578 global_conf_read(void)
583 if (!(conf_node = conf_get_data(GLOBAL_CONF_NAME, RECDB_OBJECT))) {
584 log_module(G_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", GLOBAL_CONF_NAME);
588 str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
589 global_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
590 str = database_get_data(conf_node, KEY_ANNOUNCEMENTS_DEFAULT, RECDB_QSTRING);
591 global_conf.announcements_default = str ? enabled_string(str) : 1;
593 str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
595 NickChange(global, str, 0);
599 global_saxdb_read(struct dict *db)
601 struct record_data *hir;
604 unsigned long duration;
605 char *str, *from, *message;
608 for(it=dict_first(db); it; it=iter_next(it))
611 if(hir->type != RECDB_OBJECT)
613 log_module(G_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
617 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
618 flags = str ? strtoul(str, NULL, 0) : 0;
620 str = database_get_data(hir->d.object, KEY_POSTED, RECDB_QSTRING);
621 posted = str ? strtoul(str, NULL, 0) : 0;
623 str = database_get_data(hir->d.object, KEY_DURATION, RECDB_QSTRING);
624 duration = str ? strtoul(str, NULL, 0) : 0;
626 from = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING);
627 message = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING);
629 message_add(flags, posted, duration, from, message);
635 global_saxdb_write(struct saxdb_context *ctx)
637 struct globalMessage *message;
640 for(message = messageList; message; message = message->next) {
641 snprintf(str, sizeof(str), "%li", message->id);
642 saxdb_start_record(ctx, str, 0);
643 saxdb_write_int(ctx, KEY_FLAGS, message->flags);
644 saxdb_write_int(ctx, KEY_POSTED, message->posted);
645 saxdb_write_int(ctx, KEY_DURATION, message->duration);
646 saxdb_write_string(ctx, KEY_FROM, message->from);
647 saxdb_write_string(ctx, KEY_MESSAGE, message->message);
648 saxdb_end_record(ctx);
654 global_db_cleanup(void)
656 while (messageList) message_del(messageList);
660 init_global(const char *nick)
662 global = AddService(nick, "Global Services");
663 G_LOG = log_register_type("Global", "file:global.log");
664 reg_new_user_func(global_process_user);
665 reg_auth_func(global_process_auth);
666 reg_oper_func(global_process_oper);
668 conf_register_reload(global_conf_read);
670 global_module = module_register("Global", G_LOG, "global.help", NULL);
671 modcmd_register(global_module, "LIST", cmd_list, 1, 0, "flags", "+oper", NULL);
672 modcmd_register(global_module, "MESSAGE", cmd_message, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
673 modcmd_register(global_module, "MESSAGES", cmd_messages, 1, 0, NULL);
674 modcmd_register(global_module, "NOTICE", cmd_notice, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
675 modcmd_register(global_module, "REMOVE", cmd_remove, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
677 global_service = service_register(global, 0);
678 saxdb_register("Global", global_saxdb_read, global_saxdb_write);
679 reg_exit_func(global_db_cleanup);
680 message_register_table(msgtab);