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_NICK "nick"
38 #define KEY_FLAGS "flags"
39 #define KEY_POSTED "posted"
40 #define KEY_DURATION "duration"
41 #define KEY_FROM "from"
42 #define KEY_MESSAGE "message"
44 /* Clarification: Notices are immediate, they are sent to matching users
45 _once_, then forgotten. Messages are stored in Global's database and
46 continually sent to users as they match the target specification until
48 static const struct message_entry msgtab[] = {
49 { "GMSG_INVALID_TARGET", "$b%s$b is an invalid message target." },
50 { "GMSG_MESSAGE_REQUIRED", "You $bmust$b provide a message to send." },
51 { "GMSG_MESSAGE_SENT", "Message to $b%s$b sent." },
52 { "GMSG_MESSAGE_ADDED", "Message to $b%s$b with ID %ld added." },
53 { "GMSG_MESSAGE_DELETED", "Message $b%s$b deleted." },
54 { "GMSG_ID_INVALID", "$b%s$b is an invalid message ID." },
55 { "GMSG_MESSAGE_COUNT", "$b%d$b messages found." },
56 { "GMSG_NO_MESSAGES", "There are no messages for you." },
57 { "GMSG_NOTICE_SOURCE", "[$b%s$b] Notice from %s:" },
58 { "GMSG_MESSAGE_SOURCE", "[$b%s$b] Notice from %s, posted %s:" },
59 { "GMSG_MOTD_HEADER", "$b------------- MESSAGE(S) OF THE DAY --------------$b" },
60 { "GMSG_MOTD_FOOTER", "$b---------- END OF MESSAGE(S) OF THE DAY ----------$b" },
64 #define GLOBAL_SYNTAX() svccmd_send_help(user, global, cmd)
65 #define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
74 unsigned long duration;
79 struct globalMessage *prev;
80 struct globalMessage *next;
83 struct userNode *global;
85 static struct module *global_module;
86 static struct service *global_service;
87 static struct globalMessage *messageList;
88 static long messageCount;
89 static time_t last_max_alert;
90 static struct log_type *G_LOG;
94 unsigned long db_backup_frequency;
97 #define global_notice(target, format...) send_message(target , global , ## format)
99 void message_expire(void *data);
101 static struct globalMessage*
102 message_add(long flags, time_t posted, unsigned long duration, char *from, const char *msg)
104 struct globalMessage *message;
107 message = malloc(sizeof(struct globalMessage));
113 message->id = messageCount++;
114 message->flags = flags;
115 message->posted = posted;
116 message->duration = duration;
117 message->from = strdup(from);
118 message->message = strdup(msg);
120 if ((flags & MESSAGE_OPTION_IMMEDIATE) == 0) {
121 localtime_r(&message->posted, &tm);
122 strftime(message->posted_s, sizeof(message->posted_s),
123 "%I:%M %p, %m/%d/%Y", &tm);
128 messageList->prev = message;
130 message->prev = NULL;
131 message->next = messageList;
133 messageList = message;
137 timeq_add(now + duration, message_expire, message);
144 message_del(struct globalMessage *message)
146 if(message->duration)
148 timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
151 if(message->prev) message->prev->next = message->next;
152 else messageList = message->next;
154 if(message->next) message->next->prev = message->prev;
157 free(message->message);
161 void message_expire(void *data)
163 struct globalMessage *message = data;
165 message->duration = 0;
166 message_del(message);
169 static struct globalMessage*
170 message_create(struct userNode *user, unsigned int argc, char *argv[])
172 unsigned long duration = 0;
178 sender = user->handle_info->handle;
180 for(i = 0; i < argc; i++)
184 global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
188 if(!irccasecmp(argv[i], "text"))
191 text = unsplit_string(argv + i, argc - i, NULL);
193 } else if (!irccasecmp(argv[i], "sourceless")) {
195 flags |= MESSAGE_OPTION_SOURCELESS;
196 } else if (!irccasecmp(argv[i], "target")) {
199 if(!irccasecmp(argv[i], "all")) {
200 flags |= MESSAGE_RECIPIENT_ALL;
201 } else if(!irccasecmp(argv[i], "users")) {
202 flags |= MESSAGE_RECIPIENT_LUSERS;
203 } else if(!irccasecmp(argv[i], "helpers")) {
204 flags |= MESSAGE_RECIPIENT_HELPERS;
205 } else if(!irccasecmp(argv[i], "opers")) {
206 flags |= MESSAGE_RECIPIENT_OPERS;
207 } else if(!irccasecmp(argv[i], "staff") || !irccasecmp(argv[i], "privileged")) {
208 flags |= MESSAGE_RECIPIENT_STAFF;
209 } else if(!irccasecmp(argv[i], "channels")) {
210 flags |= MESSAGE_RECIPIENT_CHANNELS;
212 global_notice(user, "GMSG_INVALID_TARGET", argv[i]);
215 } else if (irccasecmp(argv[i], "duration") == 0) {
216 duration = ParseInterval(argv[++i]);
217 } else if (irccasecmp(argv[i], "from") == 0) {
220 global_notice(user, "MSG_INVALID_CRITERIA", argv[i]);
227 flags = MESSAGE_RECIPIENT_LUSERS;
231 global_notice(user, "GMSG_MESSAGE_REQUIRED");
235 return message_add(flags, now, duration, sender, text);
239 messageType(const struct globalMessage *message)
241 if((message->flags & MESSAGE_RECIPIENT_ALL) == MESSAGE_RECIPIENT_ALL)
245 if((message->flags & MESSAGE_RECIPIENT_STAFF) == MESSAGE_RECIPIENT_STAFF)
249 else if(message->flags & MESSAGE_RECIPIENT_OPERS)
253 else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
257 else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
268 notice_target(const char *target, struct globalMessage *message)
270 if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
272 if(message->flags & MESSAGE_OPTION_IMMEDIATE)
274 send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
278 send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
282 send_target_message(4, target, global, "%s", message->message);
286 notice_channel(const char *key, void *data, void *extra)
288 struct chanNode *channel = data;
289 /* It should be safe to assume channel is not NULL. */
290 if(channel->channel_info)
291 notice_target(key, extra);
296 message_send(struct globalMessage *message)
298 struct userNode *user;
301 if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
303 dict_foreach(channels, notice_channel, message);
306 if(message->flags & MESSAGE_RECIPIENT_LUSERS)
308 notice_target("$*", 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;
361 sender = user->handle_info->handle;
362 if(!irccasecmp(argv[1], "all")) {
363 target = MESSAGE_RECIPIENT_ALL;
364 } else if(!irccasecmp(argv[1], "users")) {
365 target = MESSAGE_RECIPIENT_LUSERS;
366 } else if(!irccasecmp(argv[1], "helpers")) {
367 target = MESSAGE_RECIPIENT_HELPERS;
368 } else if(!irccasecmp(argv[1], "opers")) {
369 target = MESSAGE_RECIPIENT_OPERS;
370 } else if(!irccasecmp(argv[1], "staff") || !irccasecmp(argv[1], "privileged")) {
371 target |= MESSAGE_RECIPIENT_HELPERS | MESSAGE_RECIPIENT_OPERS;
372 } else if(!irccasecmp(argv[1], "channels")) {
373 target = MESSAGE_RECIPIENT_CHANNELS;
375 global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
378 if(!irccasecmp(argv[2], "from")) {
380 reply("MSG_MISSING_PARAMS", argv[0]);
385 text = unsplit_string(argv + 4, argc - 4, NULL);
387 text = unsplit_string(argv + 2, argc - 2, NULL);
390 message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
394 recipient = messageType(message);
395 message_send(message);
396 message_del(message);
398 global_notice(user, "GMSG_MESSAGE_SENT", recipient);
402 static GLOBAL_FUNC(cmd_message)
404 struct globalMessage *message = NULL;
405 const char *recipient = NULL;
408 message = message_create(user, argc - 1, argv + 1);
411 recipient = messageType(message);
412 global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
416 static GLOBAL_FUNC(cmd_list)
418 struct globalMessage *message;
419 struct helpfile_table table;
420 unsigned int length, nn;
424 global_notice(user, "GMSG_NO_MESSAGES");
428 for(nn=0, message = messageList; message; nn++, message=message->next) ;
431 table.flags = TABLE_NO_FREE;
432 table.contents = calloc(table.length, sizeof(char**));
433 table.contents[0] = calloc(table.width, sizeof(char*));
434 table.contents[0][0] = "ID";
435 table.contents[0][1] = "Target";
436 table.contents[0][2] = "Expires";
437 table.contents[0][3] = "From";
438 table.contents[0][4] = "Message";
440 for(nn=1, message = messageList; message; nn++, message = message->next)
444 table.contents[nn] = calloc(table.width, sizeof(char*));
445 snprintf(buffer, sizeof(buffer), "%lu", message->id);
446 table.contents[nn][0] = strdup(buffer);
447 table.contents[nn][1] = messageType(message);
448 if(message->duration)
449 intervalString(buffer, message->posted + message->duration - now, user->handle_info);
451 strcpy(buffer, "Never.");
452 table.contents[nn][2] = strdup(buffer);
453 table.contents[nn][3] = message->from;
454 length = strlen(message->message);
455 safestrncpy(buffer, message->message, sizeof(buffer));
456 if(length > (sizeof(buffer) - 4))
458 buffer[sizeof(buffer) - 1] = 0;
459 buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
461 table.contents[nn][4] = strdup(buffer);
463 table_send(global, user->nick, 0, NULL, table);
464 for (nn=1; nn<table.length; nn++)
466 free((char*)table.contents[nn][0]);
467 free((char*)table.contents[nn][2]);
468 free((char*)table.contents[nn][4]);
469 free(table.contents[nn]);
471 free(table.contents[0]);
472 free(table.contents);
477 static GLOBAL_FUNC(cmd_remove)
479 struct globalMessage *message = NULL;
483 id = strtoul(argv[1], NULL, 0);
485 for(message = messageList; message; message = message->next)
487 if(message->id == id)
489 message_del(message);
490 global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
495 global_notice(user, "GMSG_ID_INVALID", argv[1]);
500 send_messages(struct userNode *user, long mask, int obstreperize)
502 struct globalMessage *message = messageList;
503 unsigned int count = 0;
507 if(message->flags & mask)
509 if (obstreperize && !count)
510 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
511 notice_target(user->nick, message);
515 message = message->next;
517 if (obstreperize && count)
518 send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
522 static GLOBAL_FUNC(cmd_messages)
524 long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
528 mask |= MESSAGE_RECIPIENT_OPERS;
531 mask |= MESSAGE_RECIPIENT_HELPERS;
533 count = send_messages(user, mask, 0);
535 global_notice(user, "GMSG_MESSAGE_COUNT", count);
537 global_notice(user, "GMSG_NO_MESSAGES");
543 global_process_user(struct userNode *user)
545 if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
547 send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
549 /* only alert on new usercount if the record was broken in the last
550 * 30 seconds, and no alert had been sent in that time.
552 if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
555 message = alloca(36);
556 sprintf(message, "New user count record: %d", max_clients);
557 global_message(MESSAGE_RECIPIENT_OPERS, message);
558 last_max_alert = now;
565 global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
568 send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
572 global_process_oper(struct userNode *user)
574 if(user->uplink->burst)
576 send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
580 global_conf_read(void)
585 if (!(conf_node = conf_get_data(GLOBAL_CONF_NAME, RECDB_OBJECT))) {
586 log_module(G_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", GLOBAL_CONF_NAME);
590 str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
591 global_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
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)
657 message_del(messageList);
661 init_global(const char *nick)
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);
679 const char *modes = conf_get_data("services/global/modes", RECDB_QSTRING);
680 global = AddLocalUser(nick, nick, NULL, "Global Services", modes);
681 global_service = service_register(global);
683 saxdb_register("Global", global_saxdb_read, global_saxdb_write);
684 reg_exit_func(global_db_cleanup);
685 message_register_table(msgtab);