fix possible crash on user deletion
[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 #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__)
101 #endif
102
103 void message_expire(void *data);
104
105 static struct globalMessage*
106 message_add(long flags, unsigned long posted, unsigned long duration, char *from, const char *msg)
107 {
108     struct globalMessage *message;
109     time_t feh;
110
111     message = malloc(sizeof(struct globalMessage));
112     if(!message)
113     {
114         return NULL;
115     }
116
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);
123
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));
128     }
129
130     if(messageList)
131     {
132         messageList->prev = message;
133     }
134     message->prev = NULL;
135     message->next = messageList;
136
137     messageList = message;
138
139     if(duration)
140     {
141         timeq_add(now + duration, message_expire, message);
142     }
143
144     return message;
145 }
146
147 static void
148 message_del(struct globalMessage *message)
149 {
150     if(message->duration)
151     {
152         timeq_del(0, NULL, message, TIMEQ_IGNORE_FUNC | TIMEQ_IGNORE_WHEN);
153     }
154
155     if(message->prev) message->prev->next = message->next;
156     else messageList = message->next;
157
158     if(message->next) message->next->prev = message->prev;
159
160     free(message->from);
161     free(message->message);
162     free(message);
163 }
164
165 void message_expire(void *data)
166 {
167     struct globalMessage *message = data;
168
169     message->duration = 0;
170     message_del(message);
171 }
172
173 static struct globalMessage*
174 message_create(struct userNode *user, unsigned int argc, char *argv[])
175 {
176     unsigned long duration = 0;
177     char *text = NULL;
178     char *sender;
179     long flags = 0;
180     unsigned int i;
181
182     sender = user->handle_info->handle;
183
184     for(i = 0; i < argc; i++)
185     {
186         if((i + 1) > argc)
187         {
188             global_notice(user, "MSG_MISSING_PARAMS", argv[argc]);
189             return NULL;
190         }
191
192         if(!irccasecmp(argv[i], "text"))
193         {
194             i++;
195             text = unsplit_string(argv + i, argc - i, NULL);
196             break;
197         } else if (!irccasecmp(argv[i], "sourceless")) {
198             i++;
199             flags |= MESSAGE_OPTION_SOURCELESS;
200         } else if (!irccasecmp(argv[i], "target")) {
201             i++;
202
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;
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_OPERS)
254     {
255         return "opers";
256     }
257     else if(message->flags & MESSAGE_RECIPIENT_HELPERS)
258     {
259         return "helpers";
260     }
261     else if(message->flags & MESSAGE_RECIPIENT_LUSERS)
262     {
263         return "users";
264     }
265     else
266     {
267         return "channels";
268     }
269 }
270
271 static void
272 notice_target(const char *target, struct globalMessage *message)
273 {
274     if(!(message->flags & MESSAGE_OPTION_SOURCELESS))
275     {
276         if(message->flags & MESSAGE_OPTION_IMMEDIATE)
277         {
278             send_target_message(0, target, global, "GMSG_NOTICE_SOURCE", messageType(message), message->from);
279         }
280         else
281         {
282             send_target_message(0, target, global, "GMSG_MESSAGE_SOURCE", messageType(message), message->from, message->posted_s);
283         }
284     }
285
286     send_target_message(4, target, global, "%s", message->message);
287 }
288
289 static int
290 notice_channel(const char *key, void *data, void *extra)
291 {
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);
296     return 0;
297 }
298
299 static void
300 message_send(struct globalMessage *message)
301 {
302     struct userNode *user;
303     unsigned long n;
304
305     if(message->flags & MESSAGE_RECIPIENT_CHANNELS)
306     {
307         dict_foreach(channels, notice_channel, message);
308     }
309
310     if(message->flags & MESSAGE_RECIPIENT_LUSERS)
311     {
312         notice_target("$*", message);
313         return;
314     }
315
316     if(message->flags & MESSAGE_RECIPIENT_OPERS)
317     {
318         for(n = 0; n < curr_opers.used; n++)
319         {
320             user = curr_opers.list[n];
321
322             if(user->uplink != self)
323             {
324                 notice_target(user->nick, message);
325             }
326         }
327     }
328
329     if(message->flags & MESSAGE_RECIPIENT_HELPERS)
330     {
331         for(n = 0; n < curr_helpers.used; n++)
332         {
333             user = curr_helpers.list[n];
334             if (IsOper(user))
335                 continue;
336             notice_target(user->nick, message);
337         }
338     }
339 }
340
341 void
342 global_message(long targets, char *text)
343 {
344     struct globalMessage *message;
345
346     if(!targets || !global)
347         return;
348
349     message = message_add(targets | MESSAGE_OPTION_SOURCELESS, now, 0, "", text);
350     if(!message)
351         return;
352
353     message_send(message);
354     message_del(message);
355 }
356
357 static GLOBAL_FUNC(cmd_notice)
358 {
359     struct globalMessage *message = NULL;
360     const char *recipient = NULL, *text;
361     char *sender;
362     long target = 0;
363
364     assert(argc >= 3);
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;
378     } else {
379         global_notice(user, "GMSG_INVALID_TARGET", argv[1]);
380         return 0;
381     }
382     if(!irccasecmp(argv[2], "from")) {
383         if (argc < 5) {
384             reply("MSG_MISSING_PARAMS", argv[0]);
385             GLOBAL_SYNTAX();
386             return 0;
387         }
388         sender = argv[3];
389         text = unsplit_string(argv + 4, argc - 4, NULL);
390     } else {
391         text = unsplit_string(argv + 2, argc - 2, NULL);
392     }
393
394     message = message_add(target | MESSAGE_OPTION_IMMEDIATE, now, 0, sender, text);
395     if(!message)
396         return 0;
397
398     recipient = messageType(message);
399     message_send(message);
400     message_del(message);
401
402     global_notice(user, "GMSG_MESSAGE_SENT", recipient);
403     return 1;
404 }
405
406 static GLOBAL_FUNC(cmd_message)
407 {
408     struct globalMessage *message = NULL;
409     const char *recipient = NULL;
410
411     assert(argc >= 3);
412     message = message_create(user, argc - 1, argv + 1);
413     if(!message)
414         return 0;
415     recipient = messageType(message);
416     global_notice(user, "GMSG_MESSAGE_ADDED", recipient, message->id);
417     return 1;
418 }
419
420 static GLOBAL_FUNC(cmd_list)
421 {
422     struct globalMessage *message;
423     struct helpfile_table table;
424     unsigned int length, nn;
425
426     if(!messageList)
427     {
428         global_notice(user, "GMSG_NO_MESSAGES");
429         return 1;
430     }
431
432     for(nn=0, message = messageList; message; nn++, message=message->next) ;
433     table.length = nn+1;
434     table.width = 5;
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";
443
444     for(nn=1, message = messageList; message; nn++, message = message->next)
445     {
446         char buffer[64];
447
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);
454         else
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))
461         {
462             buffer[sizeof(buffer) - 1] = 0;
463             buffer[sizeof(buffer) - 2] = buffer[sizeof(buffer) - 3] = buffer[sizeof(buffer) - 4] = '.';
464         }
465         table.contents[nn][4] = strdup(buffer);
466     }
467     table_send(global, user->nick, 0, NULL, table);
468     for (nn=1; nn<table.length; nn++)
469     {
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]);
474     }
475     free(table.contents[0]);
476     free(table.contents);
477
478     return 1;
479 }
480
481 static GLOBAL_FUNC(cmd_remove)
482 {
483     struct globalMessage *message = NULL;
484     unsigned long id;
485
486     assert(argc >= 2);
487     id = strtoul(argv[1], NULL, 0);
488
489     for(message = messageList; message; message = message->next)
490     {
491         if(message->id == id)
492         {
493             message_del(message);
494             global_notice(user, "GMSG_MESSAGE_DELETED", argv[1]);
495             return 1;
496         }
497     }
498
499     global_notice(user, "GMSG_ID_INVALID", argv[1]);
500     return 0;
501 }
502
503 static unsigned int
504 send_messages(struct userNode *user, long mask, int obstreperize)
505 {
506     struct globalMessage *message = messageList;
507     unsigned int count = 0;
508
509     while(message)
510     {
511         if(message->flags & mask)
512         {
513             if (obstreperize && !count)
514                 send_target_message(0, user->nick, global, "GMSG_MOTD_HEADER");
515             notice_target(user->nick, message);
516             count++;
517         }
518
519         message = message->next;
520     }
521     if (obstreperize && count)
522         send_target_message(0, user->nick, global, "GMSG_MOTD_FOOTER");
523     return count;
524 }
525
526 static GLOBAL_FUNC(cmd_messages)
527 {
528     long mask = MESSAGE_RECIPIENT_LUSERS | MESSAGE_RECIPIENT_CHANNELS;
529     unsigned int count;
530
531     if(IsOper(user))
532         mask |= MESSAGE_RECIPIENT_OPERS;
533
534     if(IsHelper(user))
535         mask |= MESSAGE_RECIPIENT_HELPERS;
536
537     count = send_messages(user, mask, 0);
538     if(count)
539         global_notice(user, "GMSG_MESSAGE_COUNT", count);
540     else
541         global_notice(user, "GMSG_NO_MESSAGES");
542
543     return 1;
544 }
545
546 static void
547 global_process_user(struct userNode *user)
548 {
549     if(IsLocal(user) || self->uplink->burst || user->uplink->burst)
550         return;
551     send_messages(user, MESSAGE_RECIPIENT_LUSERS, 1);
552
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.
555      */
556     if((now - max_clients_time) <= 30 && (now - last_max_alert) > 30)
557     {
558         char *message;
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;
563     }
564 }
565
566 static void
567 global_process_auth(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
568 {
569     if(IsHelper(user))
570         send_messages(user, MESSAGE_RECIPIENT_HELPERS, 0);
571 }
572
573 static void
574 global_process_oper(struct userNode *user)
575 {
576     if(user->uplink->burst)
577         return;
578     send_messages(user, MESSAGE_RECIPIENT_OPERS, 0);
579 }
580
581 static void
582 global_conf_read(void)
583 {
584     dict_t conf_node;
585     const char *str;
586
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);
589         return;
590     }
591
592     str = database_get_data(conf_node, KEY_DB_BACKUP_FREQ, RECDB_QSTRING);
593     global_conf.db_backup_frequency = str ? ParseInterval(str) : 7200;
594
595     str = database_get_data(conf_node, KEY_NICK, RECDB_QSTRING);
596     if(global && str)
597         NickChange(global, str, 0);
598 }
599
600 static int
601 global_saxdb_read(struct dict *db)
602 {
603     struct record_data *hir;
604     unsigned long posted;
605     long flags;
606     unsigned long duration;
607     char *str, *from, *message;
608     dict_iterator_t it;
609
610     for(it=dict_first(db); it; it=iter_next(it))
611     {
612         hir = iter_data(it);
613         if(hir->type != RECDB_OBJECT)
614         {
615             log_module(G_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
616             continue;
617         }
618
619         str = database_get_data(hir->d.object, KEY_FLAGS, RECDB_QSTRING);
620         flags = str ? strtoul(str, NULL, 0) : 0;
621
622         str = database_get_data(hir->d.object, KEY_POSTED, RECDB_QSTRING);
623         posted = str ? strtoul(str, NULL, 0) : 0;
624
625         str = database_get_data(hir->d.object, KEY_DURATION, RECDB_QSTRING);
626         duration = str ? strtoul(str, NULL, 0) : 0;
627
628         from = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING);
629         message = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING);
630
631         message_add(flags, posted, duration, from, message);
632     }
633     return 0;
634 }
635
636 static int
637 global_saxdb_write(struct saxdb_context *ctx)
638 {
639     struct globalMessage *message;
640     char str[16];
641
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);
651     }
652     return 0;
653 }
654
655 static void
656 global_db_cleanup(void)
657 {
658     while(messageList)
659         message_del(messageList);
660 }
661
662 void
663 init_global(const char *nick)
664 {
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);
669
670     conf_register_reload(global_conf_read);
671
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);
678
679     if(nick)
680     {
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);
684     }
685     saxdb_register("Global", global_saxdb_read, global_saxdb_write);
686     reg_exit_func(global_db_cleanup);
687     message_register_table(msgtab);
688 }