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