d1aea5d46c27e6180c1b61a2d95c9dba4b9f60e9
[srvx.git] / src / global.c
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. */
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" },
62     { NULL, NULL }
63 };
64
65 #define GLOBAL_SYNTAX()   svccmd_send_help(user, global, cmd)
66 #define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
67
68 struct 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
84 struct userNode *global;
85
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;
92
93 static 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
101 void message_expire(void *data);
102
103 static struct globalMessage*
104 message_add(long flags, time_t posted, unsigned long duration, char *from, const char *msg)
105 {
106     struct globalMessage *message;
107     struct tm tm;
108
109     message = malloc(sizeof(struct globalMessage));
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
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
128     if(messageList)
129     {
130         messageList->prev = message;
131     }
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
145 static void
146 message_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
163 void message_expire(void *data)
164 {
165     struct globalMessage *message = data;
166
167     message->duration = 0;
168     message_del(message);
169 }
170
171 static struct globalMessage*
172 message_create(struct userNode *user, unsigned int argc, char *argv[])
173 {
174     unsigned long duration = 0;
175     char *text = NULL;
176     char *sender;
177     long flags = 0;
178     unsigned int i;
179
180     sender = user->handle_info->handle;
181     
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]);
221         } else if (irccasecmp(argv[i], "from") == 0) {
222             sender = argv[++i];
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
239     return message_add(flags, now, duration, sender, text);
240 }
241
242 static const char *
243 messageType(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
275 static void
276 notice_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         {
286             send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
287         }
288     }
289
290     send_target_message(4, target, global, "%s", message->message);
291 }
292
293 static int
294 notice_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
303 static void
304 message_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
360 void
361 global_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
376 static GLOBAL_FUNC(cmd_notice)
377 {
378     struct globalMessage *message = NULL;
379     const char *recipient = NULL, *text;
380     char *sender;
381     long target = 0;
382
383     assert(argc >= 3);
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;
399     } else {
400         global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
401         return 0;
402     }
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     }
414
415     message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
416     if(!message)
417         return 0;
418
419     recipient = messageType(message);
420     message_send(message);
421     message_del(message);
422
423     global_notice(user, "GMSG_MESSAGE_SENT", recipient);
424     return 1;
425 }
426
427 static 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
441 static 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
502 static 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
524 static unsigned int
525 send_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
547 static 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
567 static int
568 global_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
589 static void
590 global_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
596 static void
597 global_process_oper(struct userNode *user)
598 {
599     if(user->uplink->burst)
600         return;
601     send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
602 }
603
604 static void
605 global_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
625 static int
626 global_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
661 static int
662 global_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
680 static void
681 global_db_cleanup(void)
682 {
683     while(messageList)
684         message_del(messageList);
685 }
686
687 void
688 init_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     {
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);
709     }
710     saxdb_register("Global", global_saxdb_read, global_saxdb_write);
711     reg_exit_func(global_db_cleanup);
712     message_register_table(msgtab);
713 }