]> jfr.im git - irc/evilnet/x3.git/blame - src/global.c
fix remote lusers warning
[irc/evilnet/x3.git] / src / global.c
CommitLineData
d76ed9a9 1/* global.c - Global notice service
2 * Copyright 2000-2004 srvx Development Team
3 *
83ff05c3 4 * This file is part of x3.
d76ed9a9 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
b1bf690d 65#define GLOBAL_SYNTAX() svccmd_send_help_brief(user, global, cmd)
d76ed9a9 66#define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
67
dee9951d 68struct globalMessage
69{
70 unsigned long id;
71 long flags;
72
73 time_t posted;
74 char posted_s[24];
75 unsigned long duration;
76
77 char *from;
78 char *message;
79
80 struct globalMessage *prev;
81 struct globalMessage *next;
82};
83
d76ed9a9 84struct userNode *global;
85
86static struct module *global_module;
87static struct service *global_service;
88static struct globalMessage *messageList;
89static long messageCount;
90static time_t last_max_alert;
91static struct log_type *G_LOG;
92
93static struct
94{
95 unsigned long db_backup_frequency;
96 unsigned int announcements_default : 1;
97} global_conf;
98
99#define global_notice(target, format...) send_message(target , global , ## format)
100
101void message_expire(void *data);
102
103static struct globalMessage*
104message_add(long flags, time_t posted, unsigned long duration, char *from, const char *msg)
105{
106 struct globalMessage *message;
dee9951d 107 struct tm tm;
d76ed9a9 108
109 message = malloc(sizeof(struct globalMessage));
d76ed9a9 110 if(!message)
111 {
112 return NULL;
113 }
114
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);
121
dee9951d 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);
126 }
127
d76ed9a9 128 if(messageList)
129 {
130 messageList->prev = message;
131 }
d76ed9a9 132 message->prev = NULL;
133 message->next = messageList;
134
135 messageList = message;
136
137 if(duration)
138 {
139 timeq_add(now + duration, message_expire, message);
140 }
141
142 return message;
143}
144
145static void
146message_del(struct globalMessage *message)
147{
148 if(message->duration)
149 {
150 timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
151 }
152
153 if(message->prev) message->prev->next = message->next;
154 else messageList = message->next;
155
156 if(message->next) message->next->prev = message->prev;
157
158 free(message->from);
159 free(message->message);
160 free(message);
161}
162
163void message_expire(void *data)
164{
165 struct globalMessage *message = data;
166
167 message->duration = 0;
168 message_del(message);
169}
170
171static struct globalMessage*
172message_create(struct userNode *user, unsigned int argc, char *argv[])
173{
174 unsigned long duration = 0;
175 char *text = NULL;
dee9951d 176 char *sender;
d76ed9a9 177 long flags = 0;
178 unsigned int i;
179
dee9951d 180 sender = user->handle_info->handle;
181
d76ed9a9 182 for(i = 0; i < argc; i++)
183 {
184 if((i + 1) > argc)
185 {
186 global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
187 return NULL;
188 }
189
190 if(!irccasecmp(argv[i], "text"))
191 {
192 i++;
193 text = unsplit_string(argv + i, argc - i, NULL);
194 break;
195 } else if (!irccasecmp(argv[i], "sourceless")) {
196 i++;
197 flags |= MESSAGE_OPTION_SOURCELESS;
198 } else if (!irccasecmp(argv[i], "target")) {
199 i++;
200
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;
215 } else {
216 global_notice(user, "GMSG_INVALID_TARGET", argv[i]);
217 return NULL;
218 }
219 } else if (irccasecmp(argv[i], "duration") == 0) {
220 duration = ParseInterval(argv[++i]);
dee9951d 221 } else if (irccasecmp(argv[i], "from") == 0) {
222 sender = argv[++i];
d76ed9a9 223 } else {
224 global_notice(user, "MSG_INVALID_CRITERIA", argv[i]);
225 return NULL;
226 }
227 }
228
229 if(!flags)
230 {
231 flags = MESSAGE_RECIPIENT_LUSERS;
232 }
233
234 if(!text) {
235 global_notice(user, "GMSG_MESSAGE_REQUIRED");
236 return NULL;
237 }
238
dee9951d 239 return message_add(flags, now, duration, sender, text);
d76ed9a9 240}
241
242static const char *
243messageType(const struct globalMessage *message)
244{
245 if((message->flags & MESSAGE_RECIPIENT_ALL) == MESSAGE_RECIPIENT_ALL)
246 {
247 return "all";
248 }
249 if((message->flags & MESSAGE_RECIPIENT_STAFF) == MESSAGE_RECIPIENT_STAFF)
250 {
251 return "staff";
252 }
253 else if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
254 {
255 return "announcement";
256 }
257 else if(message->flags & MESSAGE_RECIPIENT_OPERS)
258 {
259 return "opers";
260 }
261 else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
262 {
263 return "helpers";
264 }
265 else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
266 {
267 return "users";
268 }
269 else
270 {
271 return "channels";
272 }
273}
274
275static void
276notice_target(const char *target, struct globalMessage *message)
277{
278 if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
279 {
280 if(message->flags & MESSAGE_OPTION_IMMEDIATE)
281 {
282 send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
283 }
284 else
285 {
dee9951d 286 send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
d76ed9a9 287 }
288 }
289
290 send_target_message(4, target, global, "%s", message->message);
291}
292
293static int
294notice_channel(const char *key, void *data, void *extra)
295{
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);
300 return 0;
301}
302
303static void
304message_send(struct globalMessage *message)
305{
306 struct userNode *user;
307 unsigned long n;
308 dict_iterator_t it;
309
310 if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
311 {
312 dict_foreach(channels, notice_channel, message);
313 }
314
315 if(message->flags & MESSAGE_RECIPIENT_LUSERS)
316 {
317 notice_target("$*", message);
318 return;
319 }
320
321 if(message->flags & MESSAGE_RECIPIENT_ANNOUNCE)
322 {
323 char announce;
324
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);
332 }
333 }
334
335 if(message->flags & MESSAGE_RECIPIENT_OPERS)
336 {
337 for(n = 0; n < curr_opers.used; n++)
338 {
339 user = curr_opers.list[n];
340
341 if(user->uplink != self)
342 {
343 notice_target(user->nick, message);
344 }
345 }
346 }
347
348 if(message->flags & MESSAGE_RECIPIENT_HELPERS)
349 {
350 for(n = 0; n < curr_helpers.used; n++)
351 {
352 user = curr_helpers.list[n];
353 if (IsOper(user))
354 continue;
355 notice_target(user->nick, message);
356 }
357 }
358}
359
360void
361global_message(long targets, char *text)
362{
363 struct globalMessage *message;
364
365 if(!targets || !global)
366 return;
367
368 message = message_add(targets | MESSAGE_OPTION_SOURCELESS, now, 0, "", text);
369 if(!message)
370 return;
371
372 message_send(message);
373 message_del(message);
374}
375
376static GLOBAL_FUNC(cmd_notice)
377{
378 struct globalMessage *message = NULL;
379 const char *recipient = NULL, *text;
dee9951d 380 char *sender;
d76ed9a9 381 long target = 0;
382
383 assert(argc >= 3);
dee9951d 384 sender = user->handle_info->handle;
d76ed9a9 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;
399 } else {
400 global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
401 return 0;
402 }
dee9951d 403 if(!irccasecmp(argv[2], "from")) {
404 if (argc < 5) {
405 reply("MSG_MISSING_PARAMS", argv[0]);
406 GLOBAL_SYNTAX();
407 return 0;
408 }
409 sender = argv[3];
410 text = unsplit_string(argv + 4, argc - 4, NULL);
411 } else {
412 text = unsplit_string(argv + 2, argc - 2, NULL);
413 }
d76ed9a9 414
dee9951d 415 message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
d76ed9a9 416 if(!message)
d76ed9a9 417 return 0;
d76ed9a9 418
419 recipient = messageType(message);
d76ed9a9 420 message_send(message);
421 message_del(message);
422
423 global_notice(user, "GMSG_MESSAGE_SENT", recipient);
424 return 1;
425}
426
427static GLOBAL_FUNC(cmd_message)
428{
429 struct globalMessage *message = NULL;
430 const char *recipient = NULL;
431
432 assert(argc >= 3);
433 message = message_create(user, argc - 1, argv + 1);
434 if(!message)
435 return 0;
436 recipient = messageType(message);
437 global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
438 return 1;
439}
440
441static GLOBAL_FUNC(cmd_list)
442{
443 struct globalMessage *message;
444 struct helpfile_table table;
445 unsigned int length, nn;
446
447 if(!messageList)
448 {
449 global_notice(user, "GMSG_NO_MESSAGES");
450 return 1;
451 }
452
453 for(nn=0, message = messageList; message; nn++, message=message->next) ;
454 table.length = nn+1;
455 table.width = 5;
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";
464
465 for(nn=1, message = messageList; message; nn++, message = message->next)
466 {
467 char buffer[64];
468
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);
475 else
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))
482 {
483 buffer[sizeof(buffer) - 1] = 0;
484 buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
485 }
486 table.contents[nn][4] = strdup(buffer);
487 }
488 table_send(global, user->nick, 0, NULL, table);
489 for (nn=1; nn<table.length; nn++)
490 {
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]);
495 }
496 free(table.contents[0]);
497 free(table.contents);
498
499 return 1;
500}
501
502static GLOBAL_FUNC(cmd_remove)
503{
504 struct globalMessage *message = NULL;
505 unsigned long id;
506
507 assert(argc >= 2);
508 id = strtoul(argv[1], NULL, 0);
509
510 for(message = messageList; message; message = message->next)
511 {
512 if(message->id == id)
513 {
514 message_del(message);
515 global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
516 return 1;
517 }
518 }
519
520 global_notice(user, "GMSG_ID_INVALID", argv[1]);
521 return 0;
522}
523
524static unsigned int
525send_messages(struct userNode *user, long mask, int obstreperize)
526{
527 struct globalMessage *message = messageList;
528 unsigned int count = 0;
529
530 while(message)
531 {
532 if(message->flags & mask)
533 {
534 if (obstreperize && !count)
535 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
536 notice_target(user->nick, message);
537 count++;
538 }
539
540 message = message->next;
541 }
542 if (obstreperize && count)
543 send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
544 return count;
545}
546
547static GLOBAL_FUNC(cmd_messages)
548{
549 long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
550 unsigned int count;
551
552 if(IsOper(user))
553 mask |= MESSAGE_RECIPIENT_OPERS;
554
555 if(IsHelper(user))
556 mask |= MESSAGE_RECIPIENT_HELPERS;
557
558 count = send_messages(user, mask, 0);
559 if(count)
560 global_notice(user, "GMSG_MESSAGE_COUNT", count);
561 else
562 global_notice(user, "GMSG_NO_MESSAGES");
563
564 return 1;
565}
566
567static int
568global_process_user(struct userNode *user)
569{
570 if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
571 return 0;
572 send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
573
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.
576 */
577 if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
578 {
579 char *message;
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;
584 }
585
586 return 0;
587}
588
589static void
590global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
591{
592 if(IsHelper(user))
593 send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
594}
595
596static void
597global_process_oper(struct userNode *user)
598{
599 if(user->uplink->burst)
600 return;
601 send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
602}
603
604static void
605global_conf_read(void)
606{
607 dict_t conf_node;
608 const char *str;
609
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);
612 return;
613 }
614
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;
619
620 str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
621 if(global && str)
622 NickChange(global, str, 0);
623}
624
625static int
626global_saxdb_read(struct dict *db)
627{
628 struct record_data *hir;
629 time_t posted;
630 long flags;
631 unsigned long duration;
632 char *str, *from, *message;
633 dict_iterator_t it;
634
635 for(it=dict_first(db); it; it=iter_next(it))
636 {
637 hir = iter_data(it);
638 if(hir->type != RECDB_OBJECT)
639 {
640 log_module(G_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
641 continue;
642 }
643
644 str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
645 flags = str ? strtoul(str, NULL, 0) : 0;
646
647 str = database_get_data(hir->d.object, KEY_POSTED, RECDB_QSTRING);
648 posted = str ? strtoul(str, NULL, 0) : 0;
649
650 str = database_get_data(hir->d.object, KEY_DURATION, RECDB_QSTRING);
651 duration = str ? strtoul(str, NULL, 0) : 0;
652
653 from = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING);
654 message = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING);
655
656 message_add(flags, posted, duration, from, message);
657 }
658 return 0;
659}
660
661static int
662global_saxdb_write(struct saxdb_context *ctx)
663{
664 struct globalMessage *message;
665 char str[16];
666
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);
676 }
677 return 0;
678}
679
680static void
681global_db_cleanup(void)
682{
683 while(messageList)
684 message_del(messageList);
685}
686
687void
688init_global(const char *nick)
689{
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);
694
695 conf_register_reload(global_conf_read);
696
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);
703
704 if(nick)
705 {
a32da4c7 706 const char *modes = conf_get_data("services/global/modes", RECDB_QSTRING);
707 global = AddService(nick, modes ? modes : NULL, "Global Services", NULL);
d76ed9a9 708 global_service = service_register(global);
709 }
710 saxdb_register("Global", global_saxdb_read, global_saxdb_write);
711 reg_exit_func(global_db_cleanup);
712 message_register_table(msgtab);
713}