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 unsigned long last_max_alert;
90 static struct log_type *G_LOG;
94 unsigned long db_backup_frequency;
97 #if defined(GCC_VARMACROS)
98 # define global_notice(target, ARGS...) send_message(target, global, ARGS)
99 #elif defined(C99_VARMACROS)
100 # define global_notice(target, ...) send_message(target, global, __VA_ARGS__)
103 void message_expire(void *data);
105 static struct globalMessage*
106 message_add(long flags, unsigned long posted, unsigned long duration, char *from, const char *msg)
108 struct globalMessage *message;
111 message = malloc(sizeof(struct globalMessage));
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);
124 if ((flags & MESSAGE_OPTION_IMMEDIATE) == 0) {
125 feh = message->posted;
126 strftime(message->posted_s, sizeof(message->posted_s),
127 "%I:%M %p, %m/%d/%Y", localtime(&feh));
132 messageList->prev = message;
134 message->prev = NULL;
135 message->next = messageList;
137 messageList = message;
141 timeq_add(now + duration, message_expire, message);
148 message_del(struct globalMessage *message)
150 if(message->duration)
152 timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
155 if(message->prev) message->prev->next = message->next;
156 else messageList = message->next;
158 if(message->next) message->next->prev = message->prev;
161 free(message->message);
165 void message_expire(void *data)
167 struct globalMessage *message = data;
169 message->duration = 0;
170 message_del(message);
173 static struct globalMessage*
174 message_create(struct userNode *user, unsigned int argc, char *argv[])
176 unsigned long duration = 0;
182 sender = user->handle_info->handle;
184 for(i = 0; i < argc; i++)
188 global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
192 if(!irccasecmp(argv[i], "text"))
195 text = unsplit_string(argv + i, argc - i, NULL);
197 } else if (!irccasecmp(argv[i], "sourceless")) {
199 flags |= MESSAGE_OPTION_SOURCELESS;
200 } else if (!irccasecmp(argv[i], "target")) {
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;
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_OPERS)
257 else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
261 else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
272 notice_target(const char *target, struct globalMessage *message)
274 if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
276 if(message->flags & MESSAGE_OPTION_IMMEDIATE)
278 send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
282 send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
286 send_target_message(4, target, global, "%s", message->message);
290 notice_channel(const char *key, void *data, void *extra)
292 struct chanNode *channel = data;
293 /* It should be safe to assume channel is not NULL. */
294 if(channel->channel_info)
295 notice_target(key, extra);
300 message_send(struct globalMessage *message)
302 struct userNode *user;
305 if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
307 dict_foreach(channels, notice_channel, message);
310 if(message->flags & MESSAGE_RECIPIENT_LUSERS)
312 notice_target("$*", message);
316 if(message->flags & MESSAGE_RECIPIENT_OPERS)
318 for(n = 0; n < curr_opers.used; n++)
320 user = curr_opers.list[n];
322 if(user->uplink != self)
324 notice_target(user->nick, message);
329 if(message->flags & MESSAGE_RECIPIENT_HELPERS)
331 for(n = 0; n < curr_helpers.used; n++)
333 user = curr_helpers.list[n];
336 notice_target(user->nick, message);
342 global_message(long targets, char *text)
344 struct globalMessage *message;
346 if(!targets || !global)
349 message = message_add(targets | MESSAGE_OPTION_SOURCELESS, now, 0, "", text);
353 message_send(message);
354 message_del(message);
357 static GLOBAL_FUNC(cmd_notice)
359 struct globalMessage *message = NULL;
360 const char *recipient = NULL, *text;
365 sender = user->handle_info->handle;
366 if(!irccasecmp(argv[1], "all")) {
367 target = MESSAGE_RECIPIENT_ALL;
368 } else if(!irccasecmp(argv[1], "users")) {
369 target = MESSAGE_RECIPIENT_LUSERS;
370 } else if(!irccasecmp(argv[1], "helpers")) {
371 target = MESSAGE_RECIPIENT_HELPERS;
372 } else if(!irccasecmp(argv[1], "opers")) {
373 target = MESSAGE_RECIPIENT_OPERS;
374 } else if(!irccasecmp(argv[1], "staff") || !irccasecmp(argv[1], "privileged")) {
375 target |= MESSAGE_RECIPIENT_HELPERS | MESSAGE_RECIPIENT_OPERS;
376 } else if(!irccasecmp(argv[1], "channels")) {
377 target = MESSAGE_RECIPIENT_CHANNELS;
379 global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
382 if(!irccasecmp(argv[2], "from")) {
384 reply("MSG_MISSING_PARAMS", argv[0]);
389 text = unsplit_string(argv + 4, argc - 4, NULL);
391 text = unsplit_string(argv + 2, argc - 2, NULL);
394 message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
398 recipient = messageType(message);
399 message_send(message);
400 message_del(message);
402 global_notice(user, "GMSG_MESSAGE_SENT", recipient);
406 static GLOBAL_FUNC(cmd_message)
408 struct globalMessage *message = NULL;
409 const char *recipient = NULL;
412 message = message_create(user, argc - 1, argv + 1);
415 recipient = messageType(message);
416 global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
420 static GLOBAL_FUNC(cmd_list)
422 struct globalMessage *message;
423 struct helpfile_table table;
424 unsigned int length, nn;
428 global_notice(user, "GMSG_NO_MESSAGES");
432 for(nn=0, message = messageList; message; nn++, message=message->next) ;
435 table.flags = TABLE_NO_FREE;
436 table.contents = calloc(table.length, sizeof(char**));
437 table.contents[0] = calloc(table.width, sizeof(char*));
438 table.contents[0][0] = "ID";
439 table.contents[0][1] = "Target";
440 table.contents[0][2] = "Expires";
441 table.contents[0][3] = "From";
442 table.contents[0][4] = "Message";
444 for(nn=1, message = messageList; message; nn++, message = message->next)
448 table.contents[nn] = calloc(table.width, sizeof(char*));
449 snprintf(buffer, sizeof(buffer), "%lu", message->id);
450 table.contents[nn][0] = strdup(buffer);
451 table.contents[nn][1] = messageType(message);
452 if(message->duration)
453 intervalString(buffer, message->posted + message->duration - now, user->handle_info);
455 strcpy(buffer, "Never.");
456 table.contents[nn][2] = strdup(buffer);
457 table.contents[nn][3] = message->from;
458 length = strlen(message->message);
459 safestrncpy(buffer, message->message, sizeof(buffer));
460 if(length > (sizeof(buffer) - 4))
462 buffer[sizeof(buffer) - 1] = 0;
463 buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
465 table.contents[nn][4] = strdup(buffer);
467 table_send(global, user->nick, 0, NULL, table);
468 for (nn=1; nn<table.length; nn++)
470 free((char*)table.contents[nn][0]);
471 free((char*)table.contents[nn][2]);
472 free((char*)table.contents[nn][4]);
473 free(table.contents[nn]);
475 free(table.contents[0]);
476 free(table.contents);
481 static GLOBAL_FUNC(cmd_remove)
483 struct globalMessage *message = NULL;
487 id = strtoul(argv[1], NULL, 0);
489 for(message = messageList; message; message = message->next)
491 if(message->id == id)
493 message_del(message);
494 global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
499 global_notice(user, "GMSG_ID_INVALID", argv[1]);
504 send_messages(struct userNode *user, long mask, int obstreperize)
506 struct globalMessage *message = messageList;
507 unsigned int count = 0;
511 if(message->flags & mask)
513 if (obstreperize && !count)
514 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
515 notice_target(user->nick, message);
519 message = message->next;
521 if (obstreperize && count)
522 send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
526 static GLOBAL_FUNC(cmd_messages)
528 long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
532 mask |= MESSAGE_RECIPIENT_OPERS;
535 mask |= MESSAGE_RECIPIENT_HELPERS;
537 count = send_messages(user, mask, 0);
539 global_notice(user, "GMSG_MESSAGE_COUNT", count);
541 global_notice(user, "GMSG_NO_MESSAGES");
547 global_process_user(struct userNode *user)
549 if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
551 send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
553 /* only alert on new usercount if the record was broken in the last
554 * 30 seconds, and no alert had been sent in that time.
556 if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
559 message = alloca(36);
560 sprintf(message, "New user count record: %d", max_clients);
561 global_message(MESSAGE_RECIPIENT_OPERS, message);
562 last_max_alert = now;
567 global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
570 send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
574 global_process_oper(struct userNode *user)
576 if(user->uplink->burst)
578 send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
582 global_conf_read(void)
587 if (!(conf_node = conf_get_data(GLOBAL_CONF_NAME, RECDB_OBJECT))) {
588 log_module(G_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", GLOBAL_CONF_NAME);
592 str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
593 global_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
595 str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
597 NickChange(global, str, 0);
601 global_saxdb_read(struct dict *db)
603 struct record_data *hir;
604 unsigned long posted;
606 unsigned long duration;
607 char *str, *from, *message;
610 for(it=dict_first(db); it; it=iter_next(it))
613 if(hir->type != RECDB_OBJECT)
615 log_module(G_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
619 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
620 flags = str ? strtoul(str, NULL, 0) : 0;
622 str = database_get_data(hir->d.object, KEY_POSTED, RECDB_QSTRING);
623 posted = str ? strtoul(str, NULL, 0) : 0;
625 str = database_get_data(hir->d.object, KEY_DURATION, RECDB_QSTRING);
626 duration = str ? strtoul(str, NULL, 0) : 0;
628 from = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING);
629 message = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING);
631 message_add(flags, posted, duration, from, message);
637 global_saxdb_write(struct saxdb_context *ctx)
639 struct globalMessage *message;
642 for(message = messageList; message; message = message->next) {
643 snprintf(str, sizeof(str), "%li", message->id);
644 saxdb_start_record(ctx, str, 0);
645 saxdb_write_int(ctx, KEY_FLAGS, message->flags);
646 saxdb_write_int(ctx, KEY_POSTED, message->posted);
647 saxdb_write_int(ctx, KEY_DURATION, message->duration);
648 saxdb_write_string(ctx, KEY_FROM, message->from);
649 saxdb_write_string(ctx, KEY_MESSAGE, message->message);
650 saxdb_end_record(ctx);
656 global_db_cleanup(void)
659 message_del(messageList);
663 init_global(const char *nick)
665 G_LOG = log_register_type("Global", "file:global.log");
666 reg_new_user_func(global_process_user);
667 reg_auth_func(global_process_auth);
668 reg_oper_func(global_process_oper);
670 conf_register_reload(global_conf_read);
672 global_module = module_register("Global", G_LOG, "global.help", NULL);
673 modcmd_register(global_module, "LIST", cmd_list, 1, 0, "flags", "+oper", NULL);
674 modcmd_register(global_module, "MESSAGE", cmd_message, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
675 modcmd_register(global_module, "MESSAGES", cmd_messages, 1, 0, NULL);
676 modcmd_register(global_module, "NOTICE", cmd_notice, 3, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
677 modcmd_register(global_module, "REMOVE", cmd_remove, 2, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
681 const char *modes = conf_get_data("services/global/modes", RECDB_QSTRING);
682 global = AddLocalUser(nick, nick, NULL, "Global Services", modes);
683 global_service = service_register(global);
685 saxdb_register("Global", global_saxdb_read, global_saxdb_write);
686 reg_exit_func(global_db_cleanup);
687 message_register_table(msgtab);