Convert time-related variables to consistently use "unsigned long".
[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_NICK            "nick"
36
37 /* Message data */
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"
43
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
47    they are deleted. */
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" },
61     { NULL, NULL }
62 };
63
64 #define GLOBAL_SYNTAX()   svccmd_send_help(user, global, cmd)
65 #define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
66
67 struct globalMessage
68 {
69     unsigned long   id;
70     long            flags;
71
72     unsigned long   posted;
73     char            posted_s[24];
74     unsigned long   duration;
75
76     char            *from;
77     char            *message;
78
79     struct globalMessage    *prev;
80     struct globalMessage    *next;
81 };
82
83 struct userNode *global;
84
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;
91
92 static struct
93 {
94     unsigned long db_backup_frequency;
95 } global_conf;
96
97 #define global_notice(target, format...) send_message(target , global , ## format)
98
99 void message_expire(void *data);
100
101 static struct globalMessage*
102 message_add(long flags, unsigned long posted, unsigned long duration, char *from, const char *msg)
103 {
104     struct globalMessage *message;
105     time_t feh;
106
107     message = malloc(sizeof(struct globalMessage));
108     if(!message)
109     {
110         return NULL;
111     }
112
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);
119
120     if ((flags & MESSAGE_OPTION_IMMEDIATE) == 0) {
121         feh = message->posted;
122         strftime(message->posted_s, sizeof(message->posted_s),
123                  "%I:%M %p, %m/%d/%Y", localtime(&feh));
124     }
125
126     if(messageList)
127     {
128         messageList->prev = message;
129     }
130     message->prev = NULL;
131     message->next = messageList;
132
133     messageList = message;
134
135     if(duration)
136     {
137         timeq_add(now + duration, message_expire, message);
138     }
139
140     return message;
141 }
142
143 static void
144 message_del(struct globalMessage *message)
145 {
146     if(message->duration)
147     {
148         timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
149     }
150
151     if(message->prev) message->prev->next = message->next;
152     else messageList = message->next;
153
154     if(message->next) message->next->prev = message->prev;
155
156     free(message->from);
157     free(message->message);
158     free(message);
159 }
160
161 void message_expire(void *data)
162 {
163     struct globalMessage *message = data;
164
165     message->duration = 0;
166     message_del(message);
167 }
168
169 static struct globalMessage*
170 message_create(struct userNode *user, unsigned int argc, char *argv[])
171 {
172     unsigned long duration = 0;
173     char *text = NULL;
174     char *sender;
175     long flags = 0;
176     unsigned int i;
177
178     sender = user->handle_info->handle;
179
180     for(i = 0; i < argc; i++)
181     {
182         if((i + 1) > argc)
183         {
184             global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
185             return NULL;
186         }
187
188         if(!irccasecmp(argv[i], "text"))
189         {
190             i++;
191             text = unsplit_string(argv + i, argc - i, NULL);
192             break;
193         } else if (!irccasecmp(argv[i], "sourceless")) {
194             i++;
195             flags |= MESSAGE_OPTION_SOURCELESS;
196         } else if (!irccasecmp(argv[i], "target")) {
197             i++;
198
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;
211             } else {
212                 global_notice(user, "GMSG_INVALID_TARGET", argv[i]);
213                 return NULL;
214             }
215         } else if (irccasecmp(argv[i], "duration") == 0) {
216             duration = ParseInterval(argv[++i]);
217         } else if (irccasecmp(argv[i], "from") == 0) {
218             sender = argv[++i];
219         } else {
220             global_notice(user, "MSG_INVALID_CRITERIA", argv[i]);
221             return NULL;
222         }
223     }
224
225     if(!flags)
226     {
227         flags = MESSAGE_RECIPIENT_LUSERS;
228     }
229
230     if(!text) {
231         global_notice(user, "GMSG_MESSAGE_REQUIRED");
232         return NULL;
233     }
234
235     return message_add(flags, now, duration, sender, text);
236 }
237
238 static const char *
239 messageType(const struct globalMessage *message)
240 {
241     if((message->flags & MESSAGE_RECIPIENT_ALL) == MESSAGE_RECIPIENT_ALL)
242     {
243         return "all";
244     }
245     if((message->flags & MESSAGE_RECIPIENT_STAFF) == MESSAGE_RECIPIENT_STAFF)
246     {
247         return "staff";
248     }
249     else if(message->flags & MESSAGE_RECIPIENT_OPERS)
250     {
251         return "opers";
252     }
253     else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
254     {
255         return "helpers";
256     }
257     else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
258     {
259         return "users";
260     }
261     else
262     {
263         return "channels";
264     }
265 }
266
267 static void
268 notice_target(const char *target, struct globalMessage *message)
269 {
270     if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
271     {
272         if(message->flags & MESSAGE_OPTION_IMMEDIATE)
273         {
274             send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
275         }
276         else
277         {
278             send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
279         }
280     }
281
282     send_target_message(4, target, global, "%s", message->message);
283 }
284
285 static int
286 notice_channel(const char *key, void *data, void *extra)
287 {
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);
292     return 0;
293 }
294
295 static void
296 message_send(struct globalMessage *message)
297 {
298     struct userNode *user;
299     unsigned long n;
300
301     if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
302     {
303         dict_foreach(channels, notice_channel, message);
304     }
305
306     if(message->flags & MESSAGE_RECIPIENT_LUSERS)
307     {
308         notice_target("$*", message);
309         return;
310     }
311
312     if(message->flags & MESSAGE_RECIPIENT_OPERS)
313     {
314         for(n = 0; n < curr_opers.used; n++)
315         {
316             user = curr_opers.list[n];
317
318             if(user->uplink != self)
319             {
320                 notice_target(user->nick, message);
321             }
322         }
323     }
324
325     if(message->flags & MESSAGE_RECIPIENT_HELPERS)
326     {
327         for(n = 0; n < curr_helpers.used; n++)
328         {
329             user = curr_helpers.list[n];
330             if (IsOper(user))
331                 continue;
332             notice_target(user->nick, message);
333         }
334     }
335 }
336
337 void
338 global_message(long targets, char *text)
339 {
340     struct globalMessage *message;
341
342     if(!targets || !global)
343         return;
344
345     message = message_add(targets | MESSAGE_OPTION_SOURCELESS, now, 0, "", text);
346     if(!message)
347         return;
348
349     message_send(message);
350     message_del(message);
351 }
352
353 static GLOBAL_FUNC(cmd_notice)
354 {
355     struct globalMessage *message = NULL;
356     const char *recipient = NULL, *text;
357     char *sender;
358     long target = 0;
359
360     assert(argc >= 3);
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;
374     } else {
375         global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
376         return 0;
377     }
378     if(!irccasecmp(argv[2], "from")) {
379         if (argc < 5) {
380             reply("MSG_MISSING_PARAMS", argv[0]);
381             GLOBAL_SYNTAX();
382             return 0;
383         }
384         sender = argv[3];
385         text = unsplit_string(argv + 4, argc - 4, NULL);
386     } else {
387         text = unsplit_string(argv + 2, argc - 2, NULL);
388     }
389
390     message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
391     if(!message)
392         return 0;
393
394     recipient = messageType(message);
395     message_send(message);
396     message_del(message);
397
398     global_notice(user, "GMSG_MESSAGE_SENT", recipient);
399     return 1;
400 }
401
402 static GLOBAL_FUNC(cmd_message)
403 {
404     struct globalMessage *message = NULL;
405     const char *recipient = NULL;
406
407     assert(argc >= 3);
408     message = message_create(user, argc - 1, argv + 1);
409     if(!message)
410         return 0;
411     recipient = messageType(message);
412     global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
413     return 1;
414 }
415
416 static GLOBAL_FUNC(cmd_list)
417 {
418     struct globalMessage *message;
419     struct helpfile_table table;
420     unsigned int length, nn;
421
422     if(!messageList)
423     {
424         global_notice(user, "GMSG_NO_MESSAGES");
425         return 1;
426     }
427
428     for(nn=0, message = messageList; message; nn++, message=message->next) ;
429     table.length = nn+1;
430     table.width = 5;
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";
439
440     for(nn=1, message = messageList; message; nn++, message = message->next)
441     {
442         char buffer[64];
443
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);
450         else
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))
457         {
458             buffer[sizeof(buffer) - 1] = 0;
459             buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
460         }
461         table.contents[nn][4] = strdup(buffer);
462     }
463     table_send(global, user->nick, 0, NULL, table);
464     for (nn=1; nn<table.length; nn++)
465     {
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]);
470     }
471     free(table.contents[0]);
472     free(table.contents);
473
474     return 1;
475 }
476
477 static GLOBAL_FUNC(cmd_remove)
478 {
479     struct globalMessage *message = NULL;
480     unsigned long id;
481
482     assert(argc >= 2);
483     id = strtoul(argv[1], NULL, 0);
484
485     for(message = messageList; message; message = message->next)
486     {
487         if(message->id == id)
488         {
489             message_del(message);
490             global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
491             return 1;
492         }
493     }
494
495     global_notice(user, "GMSG_ID_INVALID", argv[1]);
496     return 0;
497 }
498
499 static unsigned int
500 send_messages(struct userNode *user, long mask, int obstreperize)
501 {
502     struct globalMessage *message = messageList;
503     unsigned int count = 0;
504
505     while(message)
506     {
507         if(message->flags & mask)
508         {
509             if (obstreperize && !count)
510                 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
511             notice_target(user->nick, message);
512             count++;
513         }
514
515         message = message->next;
516     }
517     if (obstreperize && count)
518         send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
519     return count;
520 }
521
522 static GLOBAL_FUNC(cmd_messages)
523 {
524     long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
525     unsigned int count;
526
527     if(IsOper(user))
528         mask |= MESSAGE_RECIPIENT_OPERS;
529
530     if(IsHelper(user))
531         mask |= MESSAGE_RECIPIENT_HELPERS;
532
533     count = send_messages(user, mask, 0);
534     if(count)
535         global_notice(user, "GMSG_MESSAGE_COUNT", count);
536     else
537         global_notice(user, "GMSG_NO_MESSAGES");
538
539     return 1;
540 }
541
542 static int
543 global_process_user(struct userNode *user)
544 {
545     if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
546         return 0;
547     send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
548
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.
551      */
552     if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
553     {
554         char *message;
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;
559     }
560
561     return 0;
562 }
563
564 static void
565 global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
566 {
567     if(IsHelper(user))
568         send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
569 }
570
571 static void
572 global_process_oper(struct userNode *user)
573 {
574     if(user->uplink->burst)
575         return;
576     send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
577 }
578
579 static void
580 global_conf_read(void)
581 {
582     dict_t conf_node;
583     const char *str;
584
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);
587         return;
588     }
589
590     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
591     global_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
592
593     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
594     if(global && str)
595         NickChange(global, str, 0);
596 }
597
598 static int
599 global_saxdb_read(struct dict *db)
600 {
601     struct record_data *hir;
602     unsigned long posted;
603     long flags;
604     unsigned long duration;
605     char *str, *from, *message;
606     dict_iterator_t it;
607
608     for(it=dict_first(db); it; it=iter_next(it))
609     {
610         hir = iter_data(it);
611         if(hir->type != RECDB_OBJECT)
612         {
613             log_module(G_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
614             continue;
615         }
616
617         str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
618         flags = str ? strtoul(str, NULL, 0) : 0;
619
620         str = database_get_data(hir->d.object, KEY_POSTED, RECDB_QSTRING);
621         posted = str ? strtoul(str, NULL, 0) : 0;
622
623         str = database_get_data(hir->d.object, KEY_DURATION, RECDB_QSTRING);
624         duration = str ? strtoul(str, NULL, 0) : 0;
625
626         from = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING);
627         message = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING);
628
629         message_add(flags, posted, duration, from, message);
630     }
631     return 0;
632 }
633
634 static int
635 global_saxdb_write(struct saxdb_context *ctx)
636 {
637     struct globalMessage *message;
638     char str[16];
639
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);
649     }
650     return 0;
651 }
652
653 static void
654 global_db_cleanup(void)
655 {
656     while(messageList)
657         message_del(messageList);
658 }
659
660 void
661 init_global(const char *nick)
662 {
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);
667
668     conf_register_reload(global_conf_read);
669
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);
676
677     if(nick)
678     {
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);
682     }
683     saxdb_register("Global", global_saxdb_read, global_saxdb_write);
684     reg_exit_func(global_db_cleanup);
685     message_register_table(msgtab);
686 }