1 /* global.c - Global notice service
2 * Copyright 2000-2004 srvx Development Team
4 * This file is part of srvx.
6 * srvx 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.
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.
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.
28 #define GLOBAL_CONF_NAME "services/global"
30 #define GLOBAL_DB "global.db"
31 #define GLOBAL_TEMP_DB "global.db.new"
34 #define KEY_DB_BACKUP_FREQ "db_backup_freq"
35 #define KEY_ANNOUNCEMENTS_DEFAULT "announcements_default"
36 #define KEY_NICK "nick"
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"
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
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", "[$b%s$b] Notice from %s:" },
59 { "GMSG_MESSAGE_SOURCE", "[$b%s$b] Notice from %s, posted %s:" },
60 { "GMSG_MOTD_HEADER", "$b------------- MESSAGE(S) OF THE DAY --------------$b" },
61 { "GMSG_MOTD_FOOTER", "$b---------- END OF MESSAGE(S) OF THE DAY ----------$b" },
65 #define GLOBAL_SYNTAX() svccmd_send_help(user, global, cmd)
66 #define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
75 unsigned long duration;
80 struct globalMessage *prev;
81 struct globalMessage *next;
84 struct userNode *global;
86 static struct module *global_module;
87 static struct service *global_service;
88 static struct globalMessage *messageList;
89 static long messageCount;
90 static time_t last_max_alert;
91 static struct log_type *G_LOG;
95 unsigned long db_backup_frequency;
96 unsigned int announcements_default : 1;
99 #define global_notice(target, format...) send_message(target , global , ## format)
101 void message_expire(void *data);
103 static struct globalMessage*
104 message_add(long flags, time_t posted, unsigned long duration, char *from, const char *msg)
106 struct globalMessage *message;
109 message = malloc(sizeof(struct globalMessage));
115 message->id = messageCount++;
116 message->flags = flags;
117 message->posted = posted;
118 message->duration = duration;
119 message->from = strdup(from);
120 message->message = strdup(msg);
122 if ((flags & MESSAGE_OPTION_IMMEDIATE) == 0) {
123 localtime_r(&message->posted, &tm);
124 strftime(message->posted_s, sizeof(message->posted_s),
125 "%I:%M %p, %m/%d/%Y", &tm);
130 messageList->prev = message;
132 message->prev = NULL;
133 message->next = messageList;
135 messageList = message;
139 timeq_add(now + duration, message_expire, message);
146 message_del(struct globalMessage *message)
148 if(message->duration)
150 timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
153 if(message->prev) message->prev->next = message->next;
154 else messageList = message->next;
156 if(message->next) message->next->prev = message->prev;
159 free(message->message);
163 void message_expire(void *data)
165 struct globalMessage *message = data;
167 message->duration = 0;
168 message_del(message);
171 static struct globalMessage*
172 message_create(struct userNode *user, unsigned int argc, char *argv[])
174 unsigned long duration = 0;
180 sender = user->handle_info->handle;
182 for(i = 0; i < argc; i++)
186 global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
190 if(!irccasecmp(argv[i], "text"))
193 text = unsplit_string(argv + i, argc - i, NULL);
195 } else if (!irccasecmp(argv[i], "sourceless")) {
197 flags |= MESSAGE_OPTION_SOURCELESS;
198 } else if (!irccasecmp(argv[i], "target")) {
201 if(!irccasecmp(argv[i], "all")) {
202 flags |= MESSAGE_RECIPIENT_ALL;
203 } else if(!irccasecmp(argv[i], "users")) {
204 flags |= MESSAGE_RECIPIENT_LUSERS;
205 } else if(!irccasecmp(argv[i], "helpers")) {
206 flags |= MESSAGE_RECIPIENT_HELPERS;
207 } else if(!irccasecmp(argv[i], "opers")) {
208 flags |= MESSAGE_RECIPIENT_OPERS;
209 } else if(!irccasecmp(argv[i], "staff") || !irccasecmp(argv[i], "privileged")) {
210 flags |= MESSAGE_RECIPIENT_STAFF;
211 } else if(!irccasecmp(argv[i], "channels")) {
212 flags |= MESSAGE_RECIPIENT_CHANNELS;
213 } else if(!irccasecmp(argv[i], "announcement") || !irccasecmp(argv[i], "announce")) {
214 flags |= MESSAGE_RECIPIENT_ANNOUNCE;
216 global_notice(user, "GMSG_INVALID_TARGET", argv[i]);
219 } else if (irccasecmp(argv[i], "duration") == 0) {
220 duration = ParseInterval(argv[++i]);
221 } else if (irccasecmp(argv[i], "from") == 0) {
224 global_notice(user, "MSG_INVALID_CRITERIA", argv[i]);
231 flags = MESSAGE_RECIPIENT_LUSERS;
235 global_notice(user, "GMSG_MESSAGE_REQUIRED");
239 return message_add(flags, now, duration, sender, text);
243 messageType(const struct globalMessage *message)
245 if((message->flags & MESSAGE_RECIPIENT_ALL) == MESSAGE_RECIPIENT_ALL)
249 if((message->flags & MESSAGE_RECIPIENT_STAFF) == MESSAGE_RECIPIENT_STAFF)
253 else if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
255 return "announcement";
257 else if(message->flags & MESSAGE_RECIPIENT_OPERS)
261 else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
265 else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
276 notice_target(const char *target, struct globalMessage *message)
278 if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
280 if(message->flags & MESSAGE_OPTION_IMMEDIATE)
282 send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
286 send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
290 send_target_message(4, target, global, "%s", message->message);
294 notice_channel(const char *key, void *data, void *extra)
296 struct chanNode *channel = data;
297 /* It should be safe to assume channel is not NULL. */
298 if(channel->channel_info)
299 notice_target(key, extra);
304 message_send(struct globalMessage *message)
306 struct userNode *user;
310 if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
312 dict_foreach(channels, notice_channel, message);
315 if(message->flags & MESSAGE_RECIPIENT_LUSERS)
317 notice_target("$*", message);
321 if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
325 for (it = dict_first(clients); it; it = iter_next(it)) {
326 user = iter_data(it);
327 if (user->uplink == self) continue;
328 announce = user->handle_info ? user->handle_info->announcements : '?';
329 if (announce == 'n') continue;
330 if ((announce == '?') && !global_conf.announcements_default) continue;
331 notice_target(user->nick, message);
335 if(message->flags & MESSAGE_RECIPIENT_OPERS)
337 for(n = 0; n < curr_opers.used; n++)
339 user = curr_opers.list[n];
341 if(user->uplink != self)
343 notice_target(user->nick, message);
348 if(message->flags & MESSAGE_RECIPIENT_HELPERS)
350 for(n = 0; n < curr_helpers.used; n++)
352 user = curr_helpers.list[n];
355 notice_target(user->nick, message);
361 global_message(long targets, char *text)
363 struct globalMessage *message;
365 if(!targets || !global)
368 message = message_add(targets | MESSAGE_OPTION_SOURCELESS, now, 0, "", text);
372 message_send(message);
373 message_del(message);
376 static GLOBAL_FUNC(cmd_notice)
378 struct globalMessage *message = NULL;
379 const char *recipient = NULL, *text;
384 sender = user->handle_info->handle;
385 if(!irccasecmp(argv[1], "all")) {
386 target = MESSAGE_RECIPIENT_ALL;
387 } else if(!irccasecmp(argv[1], "users")) {
388 target = MESSAGE_RECIPIENT_LUSERS;
389 } else if(!irccasecmp(argv[1], "helpers")) {
390 target = MESSAGE_RECIPIENT_HELPERS;
391 } else if(!irccasecmp(argv[1], "opers")) {
392 target = MESSAGE_RECIPIENT_OPERS;
393 } else if(!irccasecmp(argv[1], "staff") || !irccasecmp(argv[1], "privileged")) {
394 target |= MESSAGE_RECIPIENT_HELPERS | MESSAGE_RECIPIENT_OPERS;
395 } else if(!irccasecmp(argv[1], "announcement") || !irccasecmp(argv[1], "announce")) {
396 target |= MESSAGE_RECIPIENT_ANNOUNCE;
397 } else if(!irccasecmp(argv[1], "channels")) {
398 target = MESSAGE_RECIPIENT_CHANNELS;
400 global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
403 if(!irccasecmp(argv[2], "from")) {
405 reply("MSG_MISSING_PARAMS", argv[0]);
410 text = unsplit_string(argv + 4, argc - 4, NULL);
412 text = unsplit_string(argv + 2, argc - 2, NULL);
415 message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
419 recipient = messageType(message);
420 message_send(message);
421 message_del(message);
423 global_notice(user, "GMSG_MESSAGE_SENT", recipient);
427 static GLOBAL_FUNC(cmd_message)
429 struct globalMessage *message = NULL;
430 const char *recipient = NULL;
433 message = message_create(user, argc - 1, argv + 1);
436 recipient = messageType(message);
437 global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
441 static GLOBAL_FUNC(cmd_list)
443 struct globalMessage *message;
444 struct helpfile_table table;
445 unsigned int length, nn;
449 global_notice(user, "GMSG_NO_MESSAGES");
453 for(nn=0, message = messageList; message; nn++, message=message->next) ;
456 table.flags = TABLE_NO_FREE;
457 table.contents = calloc(table.length, sizeof(char**));
458 table.contents[0] = calloc(table.width, sizeof(char*));
459 table.contents[0][0] = "ID";
460 table.contents[0][1] = "Target";
461 table.contents[0][2] = "Expires";
462 table.contents[0][3] = "From";
463 table.contents[0][4] = "Message";
465 for(nn=1, message = messageList; message; nn++, message = message->next)
469 table.contents[nn] = calloc(table.width, sizeof(char*));
470 snprintf(buffer, sizeof(buffer), "%lu", message->id);
471 table.contents[nn][0] = strdup(buffer);
472 table.contents[nn][1] = messageType(message);
473 if(message->duration)
474 intervalString(buffer, message->posted + message->duration - now, user->handle_info);
476 strcpy(buffer, "Never.");
477 table.contents[nn][2] = strdup(buffer);
478 table.contents[nn][3] = message->from;
479 length = strlen(message->message);
480 safestrncpy(buffer, message->message, sizeof(buffer));
481 if(length > (sizeof(buffer) - 4))
483 buffer[sizeof(buffer) - 1] = 0;
484 buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
486 table.contents[nn][4] = strdup(buffer);
488 table_send(global, user->nick, 0, NULL, table);
489 for (nn=1; nn<table.length; nn++)
491 free((char*)table.contents[nn][0]);
492 free((char*)table.contents[nn][2]);
493 free((char*)table.contents[nn][4]);
494 free(table.contents[nn]);
496 free(table.contents[0]);
497 free(table.contents);
502 static GLOBAL_FUNC(cmd_remove)
504 struct globalMessage *message = NULL;
508 id = strtoul(argv[1], NULL, 0);
510 for(message = messageList; message; message = message->next)
512 if(message->id == id)
514 message_del(message);
515 global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
520 global_notice(user, "GMSG_ID_INVALID", argv[1]);
525 send_messages(struct userNode *user, long mask, int obstreperize)
527 struct globalMessage *message = messageList;
528 unsigned int count = 0;
532 if(message->flags & mask)
534 if (obstreperize && !count)
535 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
536 notice_target(user->nick, message);
540 message = message->next;
542 if (obstreperize && count)
543 send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
547 static GLOBAL_FUNC(cmd_messages)
549 long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
553 mask |= MESSAGE_RECIPIENT_OPERS;
556 mask |= MESSAGE_RECIPIENT_HELPERS;
558 count = send_messages(user, mask, 0);
560 global_notice(user, "GMSG_MESSAGE_COUNT", count);
562 global_notice(user, "GMSG_NO_MESSAGES");
568 global_process_user(struct userNode *user)
570 if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
572 send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
574 /* only alert on new usercount if the record was broken in the last
575 * 30 seconds, and no alert had been sent in that time.
577 if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
580 message = alloca(36);
581 sprintf(message, "New user count record: %d", max_clients);
582 global_message(MESSAGE_RECIPIENT_OPERS, message);
583 last_max_alert = now;
590 global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
593 send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
597 global_process_oper(struct userNode *user)
599 if(user->uplink->burst)
601 send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
605 global_conf_read(void)
610 if (!(conf_node = conf_get_data(GLOBAL_CONF_NAME, RECDB_OBJECT))) {
611 log_module(G_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", GLOBAL_CONF_NAME);
615 str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
616 global_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
617 str = database_get_data(conf_node, KEY_ANNOUNCEMENTS_DEFAULT, RECDB_QSTRING);
618 global_conf.announcements_default = str ? enabled_string(str) : 1;
620 str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
622 NickChange(global, str, 0);
626 global_saxdb_read(struct dict *db)
628 struct record_data *hir;
631 unsigned long duration;
632 char *str, *from, *message;
635 for(it=dict_first(db); it; it=iter_next(it))
638 if(hir->type != RECDB_OBJECT)
640 log_module(G_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
644 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
645 flags = str ? strtoul(str, NULL, 0) : 0;
647 str = database_get_data(hir->d.object, KEY_POSTED, RECDB_QSTRING);
648 posted = str ? strtoul(str, NULL, 0) : 0;
650 str = database_get_data(hir->d.object, KEY_DURATION, RECDB_QSTRING);
651 duration = str ? strtoul(str, NULL, 0) : 0;
653 from = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING);
654 message = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING);
656 message_add(flags, posted, duration, from, message);
662 global_saxdb_write(struct saxdb_context *ctx)
664 struct globalMessage *message;
667 for(message = messageList; message; message = message->next) {
668 snprintf(str, sizeof(str), "%li", message->id);
669 saxdb_start_record(ctx, str, 0);
670 saxdb_write_int(ctx, KEY_FLAGS, message->flags);
671 saxdb_write_int(ctx, KEY_POSTED, message->posted);
672 saxdb_write_int(ctx, KEY_DURATION, message->duration);
673 saxdb_write_string(ctx, KEY_FROM, message->from);
674 saxdb_write_string(ctx, KEY_MESSAGE, message->message);
675 saxdb_end_record(ctx);
681 global_db_cleanup(void)
684 message_del(messageList);
688 init_global(const char *nick)
690 G_LOG = log_register_type("Global", "file:global.log");
691 reg_new_user_func(global_process_user);
692 reg_auth_func(global_process_auth);
693 reg_oper_func(global_process_oper);
695 conf_register_reload(global_conf_read);
697 global_module = module_register("Global", G_LOG, "global.help", NULL);
698 modcmd_register(global_module, "LIST", cmd_list, 1, 0, "flags", "+oper", NULL);
699 modcmd_register(global_module, "MESSAGE", cmd_message, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
700 modcmd_register(global_module, "MESSAGES", cmd_messages, 1, 0, NULL);
701 modcmd_register(global_module, "NOTICE", cmd_notice, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
702 modcmd_register(global_module, "REMOVE", cmd_remove, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
706 const char *modes = conf_get_data("services/global/modes", RECDB_QSTRING);
707 global = AddLocalUser(nick, nick, NULL, "Global Services", modes);
708 global_service = service_register(global);
710 saxdb_register("Global", global_saxdb_read, global_saxdb_write);
711 reg_exit_func(global_db_cleanup);
712 message_register_table(msgtab);