1 /* global.c - Global notice service
2 * Copyright 2000-2004 srvx Development Team
4 * This file is part of x3.
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.
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.
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.
28 #define GLOBAL_CONF_NAME "services/global"
30 #define GLOBAL_DB "global.db"
31 #define GLOBAL_TEMP_DB "global.db.new"
34 #define KEY_DB_BACKUP_FREQ "db_backup_freq"
35 #define KEY_ANNOUNCEMENTS_DEFAULT "announcements_default"
36 #define KEY_NICK "nick"
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"
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
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", "Notice to [$b%s$b] from %s:" },
59 { "GMSG_MESSAGE_SOURCE", "Notice to [$b%s$b] from %s, posted %s:" },
60 //{ "GMSG_MOTD_HEADER", "$b------------- MESSAGE(S) OF THE DAY --------------$b" },
61 { "GMSG_MOTD_HEADER", "$bNetwork Announcements$b" },
62 { "GMSG_MOTD_BAR", "---------------------------------------" },
63 { "GMSG_MOTD_FOOTER", "--------------- Thank You--------------" },
67 #define GLOBAL_SYNTAX() svccmd_send_help_brief(user, global, cmd)
68 #define GLOBAL_FUNC(NAME) MODCMD_FUNC(NAME)
77 unsigned long duration
;
82 struct globalMessage
*prev
;
83 struct globalMessage
*next
;
86 struct userNode
*global
;
88 static struct module *global_module
;
89 static struct service
*global_service
;
90 static struct globalMessage
*messageList
;
91 static long messageCount
;
92 static time_t last_max_alert
;
93 static struct log_type
*G_LOG
;
97 unsigned long db_backup_frequency
;
98 unsigned int announcements_default
: 1;
101 #define global_notice(target, format...) send_message(target , global , ## format)
103 void message_expire(void *data
);
105 static struct globalMessage
*
106 message_add(long flags
, time_t posted
, unsigned long duration
, char *from
, const char *msg
)
108 struct globalMessage
*message
;
111 message
= malloc(sizeof(struct globalMessage
));
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
);
124 if ((flags
& MESSAGE_OPTION_IMMEDIATE
) == 0) {
125 localtime_r(&message
->posted
, &tm
);
126 strftime(message
->posted_s
, sizeof(message
->posted_s
),
127 "%I:%M %p, %m/%d/%Y", &tm
);
132 messageList
->prev
= message
;
134 message
->prev
= NULL
;
135 message
->next
= messageList
;
137 messageList
= message
;
141 timeq_add(now
+ duration
, message_expire
, message
);
148 message_del(struct globalMessage
*message
)
150 if(message
->duration
)
152 timeq_del(0, NULL
, message
, TIMEQ_IGNORE_FUNC
| TIMEQ_IGNORE_WHEN
);
155 if(message
->prev
) message
->prev
->next
= message
->next
;
156 else messageList
= message
->next
;
158 if(message
->next
) message
->next
->prev
= message
->prev
;
161 free(message
->message
);
165 void message_expire(void *data
)
167 struct globalMessage
*message
= data
;
169 message
->duration
= 0;
170 message_del(message
);
173 static struct globalMessage
*
174 message_create(struct userNode
*user
, unsigned int argc
, char *argv
[])
176 unsigned long duration
= 0;
182 sender
= user
->handle_info
->handle
;
184 for(i
= 0; i
< argc
; i
++)
188 global_notice(user
, "MSG_MISSING_PARAMS", argv
[argc
]);
192 if(!irccasecmp(argv
[i
], "text"))
195 text
= unsplit_string(argv
+ i
, argc
- i
, NULL
);
197 } else if (!irccasecmp(argv
[i
], "sourceless")) {
199 flags
|= MESSAGE_OPTION_SOURCELESS
;
200 } else if (!irccasecmp(argv
[i
], "target")) {
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 if(!irccasecmp(argv
[i
], "announcement") || !irccasecmp(argv
[i
], "announce")) {
216 flags
|= MESSAGE_RECIPIENT_ANNOUNCE
;
218 global_notice(user
, "GMSG_INVALID_TARGET", argv
[i
]);
221 } else if (irccasecmp(argv
[i
], "duration") == 0) {
222 duration
= ParseInterval(argv
[++i
]);
223 } else if (irccasecmp(argv
[i
], "from") == 0) {
226 global_notice(user
, "MSG_INVALID_CRITERIA", argv
[i
]);
233 flags
= MESSAGE_RECIPIENT_LUSERS
;
237 global_notice(user
, "GMSG_MESSAGE_REQUIRED");
241 return message_add(flags
, now
, duration
, sender
, text
);
245 messageType(const struct globalMessage
*message
)
247 if((message
->flags
& MESSAGE_RECIPIENT_ALL
) == MESSAGE_RECIPIENT_ALL
)
251 if((message
->flags
& MESSAGE_RECIPIENT_STAFF
) == MESSAGE_RECIPIENT_STAFF
)
255 else if(message
->flags
& MESSAGE_RECIPIENT_ANNOUNCE
)
257 return "announcement";
259 else if(message
->flags
& MESSAGE_RECIPIENT_OPERS
)
263 else if(message
->flags
& MESSAGE_RECIPIENT_HELPERS
)
267 else if(message
->flags
& MESSAGE_RECIPIENT_LUSERS
)
278 notice_target(const char *target
, struct globalMessage
*message
)
280 if(!(message
->flags
& MESSAGE_OPTION_SOURCELESS
))
282 if(message
->flags
& MESSAGE_OPTION_IMMEDIATE
)
284 send_target_message(0, target
, global
, "GMSG_NOTICE_SOURCE", messageType(message
), message
->from
);
288 send_target_message(0, target
, global
, "GMSG_MESSAGE_SOURCE", messageType(message
), message
->from
, message
->posted_s
);
292 send_target_message(4, target
, global
, "%s", message
->message
);
296 notice_channel(const char *key
, void *data
, void *extra
)
298 struct chanNode
*channel
= data
;
299 /* It should be safe to assume channel is not NULL. */
300 if(channel
->channel_info
)
301 notice_target(key
, extra
);
306 message_send(struct globalMessage
*message
)
308 struct userNode
*user
;
312 if(message
->flags
& MESSAGE_RECIPIENT_CHANNELS
)
314 dict_foreach(channels
, notice_channel
, message
);
317 if(message
->flags
& MESSAGE_RECIPIENT_LUSERS
)
319 notice_target("$*", message
);
323 if(message
->flags
& MESSAGE_RECIPIENT_ANNOUNCE
)
327 for (it
= dict_first(clients
); it
; it
= iter_next(it
)) {
328 user
= iter_data(it
);
329 if (user
->uplink
== self
) continue;
330 announce
= user
->handle_info
? user
->handle_info
->announcements
: '?';
331 if (announce
== 'n') continue;
332 if ((announce
== '?') && !global_conf
.announcements_default
) continue;
333 notice_target(user
->nick
, message
);
337 if(message
->flags
& MESSAGE_RECIPIENT_OPERS
)
339 for(n
= 0; n
< curr_opers
.used
; n
++)
341 user
= curr_opers
.list
[n
];
343 if(user
->uplink
!= self
)
345 notice_target(user
->nick
, message
);
350 if(message
->flags
& MESSAGE_RECIPIENT_HELPERS
)
352 for(n
= 0; n
< curr_helpers
.used
; n
++)
354 user
= curr_helpers
.list
[n
];
357 notice_target(user
->nick
, message
);
363 global_message(long targets
, char *text
)
365 struct globalMessage
*message
;
367 if(!targets
|| !global
)
370 message
= message_add(targets
| MESSAGE_OPTION_SOURCELESS
, now
, 0, "", text
);
374 message_send(message
);
375 message_del(message
);
378 static GLOBAL_FUNC(cmd_notice
)
380 struct globalMessage
*message
= NULL
;
381 const char *recipient
= NULL
, *text
;
386 sender
= user
->handle_info
->handle
;
387 if(!irccasecmp(argv
[1], "all")) {
388 target
= MESSAGE_RECIPIENT_ALL
;
389 } else if(!irccasecmp(argv
[1], "users")) {
390 target
= MESSAGE_RECIPIENT_LUSERS
;
391 } else if(!irccasecmp(argv
[1], "helpers")) {
392 target
= MESSAGE_RECIPIENT_HELPERS
;
393 } else if(!irccasecmp(argv
[1], "opers")) {
394 target
= MESSAGE_RECIPIENT_OPERS
;
395 } else if(!irccasecmp(argv
[1], "staff") || !irccasecmp(argv
[1], "privileged")) {
396 target
|= MESSAGE_RECIPIENT_HELPERS
| MESSAGE_RECIPIENT_OPERS
;
397 } else if(!irccasecmp(argv
[1], "announcement") || !irccasecmp(argv
[1], "announce")) {
398 target
|= MESSAGE_RECIPIENT_ANNOUNCE
;
399 } else if(!irccasecmp(argv
[1], "channels")) {
400 target
= MESSAGE_RECIPIENT_CHANNELS
;
402 global_notice(user
, "GMSG_INVALID_TARGET", argv
[1]);
405 if(!irccasecmp(argv
[2], "from")) {
407 reply("MSG_MISSING_PARAMS", argv
[0]);
412 text
= unsplit_string(argv
+ 4, argc
- 4, NULL
);
414 text
= unsplit_string(argv
+ 2, argc
- 2, NULL
);
417 message
= message_add(target
| MESSAGE_OPTION_IMMEDIATE
, now
, 0, sender
, text
);
421 recipient
= messageType(message
);
422 message_send(message
);
423 message_del(message
);
425 global_notice(user
, "GMSG_MESSAGE_SENT", recipient
);
429 static GLOBAL_FUNC(cmd_message
)
431 struct globalMessage
*message
= NULL
;
432 const char *recipient
= NULL
;
435 message
= message_create(user
, argc
- 1, argv
+ 1);
438 recipient
= messageType(message
);
439 global_notice(user
, "GMSG_MESSAGE_ADDED", recipient
, message
->id
);
443 static GLOBAL_FUNC(cmd_list
)
445 struct globalMessage
*message
;
446 struct helpfile_table table
;
447 unsigned int length
, nn
;
451 global_notice(user
, "GMSG_NO_MESSAGES");
455 for(nn
=0, message
= messageList
; message
; nn
++, message
=message
->next
) ;
458 table
.flags
= TABLE_NO_FREE
;
459 table
.contents
= calloc(table
.length
, sizeof(char**));
460 table
.contents
[0] = calloc(table
.width
, sizeof(char*));
461 table
.contents
[0][0] = "ID";
462 table
.contents
[0][1] = "Target";
463 table
.contents
[0][2] = "Expires";
464 table
.contents
[0][3] = "From";
465 table
.contents
[0][4] = "Message";
467 for(nn
=1, message
= messageList
; message
; nn
++, message
= message
->next
)
471 table
.contents
[nn
] = calloc(table
.width
, sizeof(char*));
472 snprintf(buffer
, sizeof(buffer
), "%lu", message
->id
);
473 table
.contents
[nn
][0] = strdup(buffer
);
474 table
.contents
[nn
][1] = messageType(message
);
475 if(message
->duration
)
476 intervalString(buffer
, message
->posted
+ message
->duration
- now
, user
->handle_info
);
478 strcpy(buffer
, "Never.");
479 table
.contents
[nn
][2] = strdup(buffer
);
480 table
.contents
[nn
][3] = message
->from
;
481 length
= strlen(message
->message
);
482 safestrncpy(buffer
, message
->message
, sizeof(buffer
));
483 if(length
> (sizeof(buffer
) - 4))
485 buffer
[sizeof(buffer
) - 1] = 0;
486 buffer
[sizeof(buffer
) - 2] = buffer
[sizeof(buffer
) - 3] = buffer
[sizeof(buffer
) - 4] = '.';
488 table
.contents
[nn
][4] = strdup(buffer
);
490 table_send(global
, user
->nick
, 0, NULL
, table
);
491 for (nn
=1; nn
<table
.length
; nn
++)
493 free((char*)table
.contents
[nn
][0]);
494 free((char*)table
.contents
[nn
][2]);
495 free((char*)table
.contents
[nn
][4]);
496 free(table
.contents
[nn
]);
498 free(table
.contents
[0]);
499 free(table
.contents
);
504 static GLOBAL_FUNC(cmd_remove
)
506 struct globalMessage
*message
= NULL
;
510 id
= strtoul(argv
[1], NULL
, 0);
512 for(message
= messageList
; message
; message
= message
->next
)
514 if(message
->id
== id
)
516 message_del(message
);
517 global_notice(user
, "GMSG_MESSAGE_DELETED", argv
[1]);
522 global_notice(user
, "GMSG_ID_INVALID", argv
[1]);
527 send_messages(struct userNode
*user
, long mask
, int obstreperize
)
529 struct globalMessage
*message
= messageList
;
530 unsigned int count
= 0;
534 if(message
->flags
& mask
)
536 if (obstreperize
&& !count
)
538 send_target_message(0, user
->nick
, global
, "GMSG_MOTD_HEADER");
539 send_target_message(0, user
->nick
, global
, "GMSG_MOTD_BAR");
541 notice_target(user
->nick
, message
);
545 message
= message
->next
;
547 if (obstreperize
&& count
)
548 send_target_message(0, user
->nick
, global
, "GMSG_MOTD_FOOTER");
552 static GLOBAL_FUNC(cmd_messages
)
554 long mask
= MESSAGE_RECIPIENT_LUSERS
| MESSAGE_RECIPIENT_CHANNELS
;
558 mask
|= MESSAGE_RECIPIENT_OPERS
;
561 mask
|= MESSAGE_RECIPIENT_HELPERS
;
563 count
= send_messages(user
, mask
, 0);
565 global_notice(user
, "GMSG_MESSAGE_COUNT", count
);
567 global_notice(user
, "GMSG_NO_MESSAGES");
573 global_process_user(struct userNode
*user
)
575 if(IsLocal(user
) || self
->uplink
->burst
|| user
->uplink
->burst
)
577 send_messages(user
, MESSAGE_RECIPIENT_LUSERS
, 1);
579 /* only alert on new usercount if the record was broken in the last
580 * 30 seconds, and no alert had been sent in that time.
582 if((now
- max_clients_time
) <= 30 && (now
- last_max_alert
) > 30)
585 message
= alloca(36);
586 sprintf(message
, "New user count record: %d", max_clients
);
587 global_message(MESSAGE_RECIPIENT_OPERS
, message
);
588 last_max_alert
= now
;
595 global_process_auth(struct userNode
*user
, UNUSED_ARG(struct handle_info
*old_handle
))
598 send_messages(user
, MESSAGE_RECIPIENT_HELPERS
, 0);
602 global_process_oper(struct userNode
*user
)
604 if(user
->uplink
->burst
)
606 send_messages(user
, MESSAGE_RECIPIENT_OPERS
, 0);
610 global_conf_read(void)
615 if (!(conf_node
= conf_get_data(GLOBAL_CONF_NAME
, RECDB_OBJECT
))) {
616 log_module(G_LOG
, LOG_ERROR
, "config node `%s' is missing or has wrong type.", GLOBAL_CONF_NAME
);
620 str
= database_get_data(conf_node
, KEY_DB_BACKUP_FREQ
, RECDB_QSTRING
);
621 global_conf
.db_backup_frequency
= str
? ParseInterval(str
) : 7200;
622 str
= database_get_data(conf_node
, KEY_ANNOUNCEMENTS_DEFAULT
, RECDB_QSTRING
);
623 global_conf
.announcements_default
= str
? enabled_string(str
) : 1;
625 str
= database_get_data(conf_node
, KEY_NICK
, RECDB_QSTRING
);
627 NickChange(global
, str
, 0);
631 global_saxdb_read(struct dict
*db
)
633 struct record_data
*hir
;
636 unsigned long duration
;
637 char *str
, *from
, *message
;
640 for(it
=dict_first(db
); it
; it
=iter_next(it
))
643 if(hir
->type
!= RECDB_OBJECT
)
645 log_module(G_LOG
, LOG_WARNING
, "Unexpected rectype %d for %s.", hir
->type
, iter_key(it
));
649 str
= database_get_data(hir
->d
.object
, KEY_FLAGS
, RECDB_QSTRING
);
650 flags
= str
? strtoul(str
, NULL
, 0) : 0;
652 str
= database_get_data(hir
->d
.object
, KEY_POSTED
, RECDB_QSTRING
);
653 posted
= str
? strtoul(str
, NULL
, 0) : 0;
655 str
= database_get_data(hir
->d
.object
, KEY_DURATION
, RECDB_QSTRING
);
656 duration
= str
? strtoul(str
, NULL
, 0) : 0;
658 from
= database_get_data(hir
->d
.object
, KEY_FROM
, RECDB_QSTRING
);
659 message
= database_get_data(hir
->d
.object
, KEY_MESSAGE
, RECDB_QSTRING
);
661 message_add(flags
, posted
, duration
, from
, message
);
667 global_saxdb_write(struct saxdb_context
*ctx
)
669 struct globalMessage
*message
;
672 for(message
= messageList
; message
; message
= message
->next
) {
673 snprintf(str
, sizeof(str
), "%li", message
->id
);
674 saxdb_start_record(ctx
, str
, 0);
675 saxdb_write_int(ctx
, KEY_FLAGS
, message
->flags
);
676 saxdb_write_int(ctx
, KEY_POSTED
, message
->posted
);
677 saxdb_write_int(ctx
, KEY_DURATION
, message
->duration
);
678 saxdb_write_string(ctx
, KEY_FROM
, message
->from
);
679 saxdb_write_string(ctx
, KEY_MESSAGE
, message
->message
);
680 saxdb_end_record(ctx
);
686 global_db_cleanup(void)
689 message_del(messageList
);
693 init_global(const char *nick
)
695 G_LOG
= log_register_type("Global", "file:global.log");
696 reg_new_user_func(global_process_user
);
697 reg_auth_func(global_process_auth
);
698 reg_oper_func(global_process_oper
);
700 conf_register_reload(global_conf_read
);
702 global_module
= module_register("Global", G_LOG
, "global.help", NULL
);
703 modcmd_register(global_module
, "LIST", cmd_list
, 1, 0, "flags", "+oper", NULL
);
704 modcmd_register(global_module
, "MESSAGE", cmd_message
, 3, MODCMD_REQUIRE_AUTHED
, "flags", "+oper", NULL
);
705 modcmd_register(global_module
, "MESSAGES", cmd_messages
, 1, 0, NULL
);
706 modcmd_register(global_module
, "NOTICE", cmd_notice
, 3, MODCMD_REQUIRE_AUTHED
, "flags", "+oper", NULL
);
707 modcmd_register(global_module
, "REMOVE", cmd_remove
, 2, MODCMD_REQUIRE_AUTHED
, "flags", "+oper", NULL
);
711 const char *modes
= conf_get_data("services/global/modes", RECDB_QSTRING
);
712 global
= AddService(nick
, modes
? modes
: NULL
, "Global Services", NULL
);
713 global_service
= service_register(global
);
715 saxdb_register("Global", global_saxdb_read
, global_saxdb_write
);
716 reg_exit_func(global_db_cleanup
);
717 message_register_table(msgtab
);