1 /* mod-memoserv.c - MemoServ module for srvx
2 * Copyright 2003-2004 Martijn Smit and srvx Development Team
4 * This file is part of srvx.
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.
21 /* Adds new section to srvx.conf:
25 * "message_expiry" "30d"; // age when messages are deleted; set
26 * // to 0 to disable message expiration
30 * After that, to make the module active on an existing bot:
31 * /msg opserv bind nickserv * *memoserv.*
33 * If you want a dedicated MemoServ bot, make sure the service control
34 * commands are bound to OpServ:
35 * /msg opserv bind opserv service *modcmd.joiner
36 * /msg opserv bind opserv service\ add *modcmd.service\ add
37 * /msg opserv bind opserv service\ rename *modcmd.service\ rename
38 * /msg opserv bind opserv service\ trigger *modcmd.service\ trigger
39 * /msg opserv bind opserv service\ remove *modcmd.service\ remove
41 * /msg opserv service add MemoServ User-to-user Memorandum Service
42 * /msg opserv bind memoserv help *modcmd.help
43 * Restart srvx with the updated conf file (as above, butwith "bot"
44 * "MemoServ"), and bind the commands to it:
45 * /msg opserv bind memoserv * *memoserv.*
46 * /msg opserv bind memoserv set *modcmd.joiner
55 #define KEY_SENT "sent"
56 #define KEY_RECIPIENT "to"
57 #define KEY_FROM "from"
58 #define KEY_MESSAGE "msg"
59 #undef KEY_READ /* thanks microsoft! */
60 #define KEY_READ "read"
61 #define KEY_ACCOUNTS "accounts"
62 #define KEY_MESSAGES "messages"
64 static const struct message_entry msgtab[] = {
65 { "MSMSG_CANNOT_SEND_SELF", "You cannot send to yourself." },
66 { "MSMSG_CANNOT_SEND", "You cannot send to account $b%s$b." },
67 { "MSMSG_MEMO_SENT", "Message sent to $b%s$b." },
68 { "MSMSG_NO_MESSAGES", "You have no messages." },
69 { "MSMSG_MEMOS_FOUND", "Found $b%d$b matches.\nUse /msg $S READ <ID> to read a message." },
70 { "MSMSG_CLEAN_INBOX", "You have $b%d$b or more messages, please clean out your inbox.\nUse /msg $S READ <ID> to read a message." },
71 { "MSMSG_LIST_HEAD", "$bID$b $bFrom$b $bTime Sent$b" },
72 { "MSMSG_LIST_FORMAT", "%-2u %s %s" },
73 { "MSMSG_MEMO_HEAD", "Memo %u From $b%s$b, received on %s:" },
74 { "MSMSG_BAD_MESSAGE_ID", "$b%s$b is not a valid message ID (it should be a number between 0 and %u)." },
75 { "MSMSG_NO_SUCH_MEMO", "You have no memo with that ID." },
76 { "MSMSG_MEMO_DELETED", "Memo $b%d$b deleted." },
77 { "MSMSG_EXPIRY_OFF", "I am currently not expiring messages. (turned off)" },
78 { "MSMSG_EXPIRY", "Messages will be expired when they are %s old (%d seconds)." },
79 { "MSMSG_MESSAGES_EXPIRED", "$b%lu$b message(s) expired." },
80 { "MSMSG_MEMOS_INBOX", "You have $b%d$b new message(s) in your inbox and %d old messages. Use /msg $S LIST to list them." },
81 { "MSMSG_NEW_MESSAGE", "You have a new message from $b%s$b (ID $b%d$b)." },
82 { "MSMSG_DELETED_ALL", "Deleted all of your messages." },
83 { "MSMSG_USE_CONFIRM", "Please use /msg $S DELETE * $bCONFIRM$b to delete $uall$u of your messages." },
84 { "MSMSG_STATUS_TOTAL", "I have $b%u$b memos in my database." },
85 { "MSMSG_STATUS_EXPIRED", "$b%ld$b memos expired during the time I am awake." },
86 { "MSMSG_STATUS_SENT", "$b%ld$b memos have been sent." },
87 { "MSMSG_SET_NOTIFY", "$bNotify: $b %s" },
88 { "MSMSG_SET_AUTHNOTIFY", "$bAuthNotify: $b %s" },
89 { "MSMSG_SET_PRIVATE", "$bPrivate: $b %s" },
94 struct memo_account *recipient;
95 struct memo_account *sender;
98 unsigned int is_read : 1;
101 DECLARE_LIST(memoList, struct memo*);
102 DEFINE_LIST(memoList, struct memo*)
104 /* memo_account.flags fields */
105 #define MEMO_NOTIFY_NEW 1
106 #define MEMO_NOTIFY_LOGIN 2
107 #define MEMO_DENY_NONCHANNEL 4
109 struct memo_account {
110 struct handle_info *handle;
112 struct memoList sent;
113 struct memoList recvd;
117 struct userNode *bot;
118 unsigned long message_expiry;
121 const char *memoserv_module_deps[] = { NULL };
122 static struct module *memoserv_module;
123 static struct log_type *MS_LOG;
124 static unsigned long memoCount;
125 static unsigned long memosSent;
126 static unsigned long memosExpired;
127 static struct dict *memos; /* memo_account->handle->handle -> memo_account */
129 static struct memo_account *
130 memoserv_get_account(struct handle_info *hi)
132 struct memo_account *ma;
135 ma = dict_find(memos, hi->handle, NULL);
138 ma = calloc(1, sizeof(*ma));
142 ma->flags = MEMO_NOTIFY_NEW | MEMO_NOTIFY_LOGIN;
143 dict_insert(memos, ma->handle->handle, ma);
148 delete_memo(struct memo *memo)
150 memoList_remove(&memo->recipient->recvd, memo);
151 memoList_remove(&memo->sender->sent, memo);
158 delete_memo_account(void *data)
160 struct memo_account *ma = data;
162 while (ma->recvd.used)
163 delete_memo(ma->recvd.list[0]);
164 while (ma->sent.used)
165 delete_memo(ma->sent.list[0]);
166 memoList_clean(&ma->recvd);
167 memoList_clean(&ma->sent);
175 for (it = dict_first(memos); it; it = iter_next(it)) {
176 struct memo_account *account = iter_data(it);
178 for (ii = 0; ii < account->sent.used; ++ii) {
179 struct memo *memo = account->sent.list[ii];
180 if ((now - memo->sent) > memoserv_conf.message_expiry) {
190 expire_memos(UNUSED_ARG(void *data))
192 if (memoserv_conf.message_expiry) {
194 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
199 add_memo(unsigned long sent, struct memo_account *recipient, struct memo_account *sender, char *message)
203 memo = calloc(1, sizeof(*memo));
207 memo->recipient = recipient;
208 memoList_append(&recipient->recvd, memo);
209 memo->sender = sender;
210 memoList_append(&sender->sent, memo);
212 memo->message = strdup(message);
219 memoserv_can_send(struct userNode *bot, struct userNode *user, struct memo_account *account)
221 extern struct userData *_GetChannelUser(struct chanData *channel, struct handle_info *handle, int override, int allow_suspended);
222 struct userData *dest;
224 if (!user->handle_info)
226 if (user->handle_info == account->handle) {
227 send_message(user, bot, "MSMSG_CANNOT_SEND_SELF");
230 if (!(account->flags & MEMO_DENY_NONCHANNEL))
232 for (dest = account->handle->channels; dest; dest = dest->u_next) {
233 if (dest->seen && _GetChannelUser(dest->channel, user->handle_info, 1, 0))
236 send_message(user, bot, "MSMSG_CANNOT_SEND", account->handle->handle);
240 static struct memo *find_memo(struct userNode *user, struct svccmd *cmd, struct memo_account *ma, const char *msgid, unsigned int *id)
243 if (!isdigit(msgid[0])) {
245 reply("MSMSG_BAD_MESSAGE_ID", msgid, ma->recvd.used - 1);
247 reply("MSMSG_NO_MESSAGES");
250 memoid = atoi(msgid);
251 if (memoid >= ma->recvd.used) {
252 reply("MSMSG_NO_SUCH_MEMO");
255 return ma->recvd.list[*id = memoid];
258 static MODCMD_FUNC(cmd_send)
261 struct handle_info *hi;
262 struct memo_account *ma, *sender;
264 if (!(hi = modcmd_get_handle_info(user, argv[1])))
266 if (!(sender = memoserv_get_account(user->handle_info))
267 || !(ma = memoserv_get_account(hi))) {
268 reply("MSG_INTERNAL_FAILURE");
271 if (!(memoserv_can_send(cmd->parent->bot, user, ma)))
273 message = unsplit_string(argv + 2, argc - 2, NULL);
274 add_memo(now, ma, sender, message);
275 if (ma->flags & MEMO_NOTIFY_NEW) {
276 struct userNode *other;
277 for (other = ma->handle->users; other; other = other->next_authed)
278 send_message(other, cmd->parent->bot, "MSMSG_NEW_MESSAGE", user->nick, ma->recvd.used - 1);
280 reply("MSMSG_MEMO_SENT", ma->handle->handle);
284 static MODCMD_FUNC(cmd_list)
286 struct memo_account *ma;
292 if (!(ma = memoserv_get_account(user->handle_info)))
294 reply("MSMSG_LIST_HEAD");
295 for (ii = 0; (ii < ma->recvd.used) && (ii < 15); ++ii) {
296 memo = ma->recvd.list[ii];
298 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", localtime(&feh));
299 reply("MSMSG_LIST_FORMAT", ii, memo->sender->handle->handle, posted);
304 reply("MSMSG_CLEAN_INBOX", ii);
306 reply("MSMSG_MEMOS_FOUND", ii);
310 static MODCMD_FUNC(cmd_read)
312 struct memo_account *ma;
318 if (!(ma = memoserv_get_account(user->handle_info)))
320 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
322 memo_sent = memo->sent;
323 strftime(posted, sizeof(posted), "%I:%M %p, %m/%d/%Y", localtime(&memo_sent));
324 reply("MSMSG_MEMO_HEAD", memoid, memo->sender->handle->handle, posted);
325 send_message_type(4, user, cmd->parent->bot, "%s", memo->message);
330 static MODCMD_FUNC(cmd_delete)
332 struct memo_account *ma;
336 if (!(ma = memoserv_get_account(user->handle_info)))
338 if (!irccasecmp(argv[1], "*") || !irccasecmp(argv[1], "all")) {
339 if ((argc < 3) || irccasecmp(argv[2], "confirm")) {
340 reply("MSMSG_USE_CONFIRM");
343 while (ma->recvd.used)
344 delete_memo(ma->recvd.list[0]);
345 reply("MSMSG_DELETED_ALL");
349 if (!(memo = find_memo(user, cmd, ma, argv[1], &memoid)))
352 reply("MSMSG_MEMO_DELETED", memoid);
356 static MODCMD_FUNC(cmd_expire)
358 unsigned long old_expired = memosExpired;
360 reply("MSMSG_MESSAGES_EXPIRED", memosExpired - old_expired);
364 static MODCMD_FUNC(cmd_expiry)
366 char interval[INTERVALLEN];
368 if (!memoserv_conf.message_expiry) {
369 reply("MSMSG_EXPIRY_OFF");
373 intervalString(interval, memoserv_conf.message_expiry, user->handle_info);
374 reply("MSMSG_EXPIRY", interval, memoserv_conf.message_expiry);
378 static MODCMD_FUNC(cmd_set_notify)
380 struct memo_account *ma;
383 if (!(ma = memoserv_get_account(user->handle_info)))
387 if (enabled_string(choice)) {
388 ma->flags |= MEMO_NOTIFY_NEW;
389 } else if (disabled_string(choice)) {
390 ma->flags &= ~MEMO_NOTIFY_NEW;
392 reply("MSG_INVALID_BINARY", choice);
397 choice = (ma->flags & MEMO_NOTIFY_NEW) ? "on" : "off";
398 reply("MSMSG_SET_NOTIFY", choice);
402 static MODCMD_FUNC(cmd_set_authnotify)
404 struct memo_account *ma;
407 if (!(ma = memoserv_get_account(user->handle_info)))
411 if (enabled_string(choice)) {
412 ma->flags |= MEMO_NOTIFY_LOGIN;
413 } else if (disabled_string(choice)) {
414 ma->flags &= ~MEMO_NOTIFY_LOGIN;
416 reply("MSG_INVALID_BINARY", choice);
421 choice = (ma->flags & MEMO_NOTIFY_LOGIN) ? "on" : "off";
422 reply("MSMSG_SET_AUTHNOTIFY", choice);
426 static MODCMD_FUNC(cmd_set_private)
428 struct memo_account *ma;
431 if (!(ma = memoserv_get_account(user->handle_info)))
435 if (enabled_string(choice)) {
436 ma->flags |= MEMO_DENY_NONCHANNEL;
437 } else if (disabled_string(choice)) {
438 ma->flags &= ~MEMO_DENY_NONCHANNEL;
440 reply("MSG_INVALID_BINARY", choice);
445 choice = (ma->flags & MEMO_DENY_NONCHANNEL) ? "on" : "off";
446 reply("MSMSG_SET_PRIVATE", choice);
450 static MODCMD_FUNC(cmd_status)
452 reply("MSMSG_STATUS_TOTAL", memoCount);
453 reply("MSMSG_STATUS_EXPIRED", memosExpired);
454 reply("MSMSG_STATUS_SENT", memosSent);
459 memoserv_conf_read(void)
464 str = "modules/memoserv";
465 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
466 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
470 str = database_get_data(conf_node, "message_expiry", RECDB_QSTRING);
471 memoserv_conf.message_expiry = str ? ParseInterval(str) : 60*24*30;
475 memoserv_saxdb_read_messages(struct dict *db)
478 struct handle_info *sender, *recipient;
479 struct record_data *hir;
484 for (it = dict_first(db); it; it = iter_next(it)) {
486 if (hir->type != RECDB_OBJECT) {
487 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for %s.", hir->type, iter_key(it));
491 if (!(str = database_get_data(hir->d.object, KEY_SENT, RECDB_QSTRING))) {
492 log_module(MS_LOG, LOG_ERROR, "Date sent not present in memo %s; skipping", iter_key(it));
495 sent = strtoul(str, NULL, 0);
497 if (!(str = database_get_data(hir->d.object, KEY_RECIPIENT, RECDB_QSTRING))) {
498 log_module(MS_LOG, LOG_ERROR, "Recipient not present in memo %s; skipping", iter_key(it));
500 } else if (!(recipient = get_handle_info(str))) {
501 log_module(MS_LOG, LOG_ERROR, "Invalid recipient %s in memo %s; skipping", str, iter_key(it));
505 if (!(str = database_get_data(hir->d.object, KEY_FROM, RECDB_QSTRING))) {
506 log_module(MS_LOG, LOG_ERROR, "Sender not present in memo %s; skipping", iter_key(it));
508 } else if (!(sender = get_handle_info(str))) {
509 log_module(MS_LOG, LOG_ERROR, "Invalid sender %s in memo %s; skipping", str, iter_key(it));
513 if (!(str = database_get_data(hir->d.object, KEY_MESSAGE, RECDB_QSTRING))) {
514 log_module(MS_LOG, LOG_ERROR, "Message not present in memo %s; skipping", iter_key(it));
518 memo = add_memo(sent, memoserv_get_account(recipient), memoserv_get_account(sender), str);
519 if ((str = database_get_data(hir->d.object, KEY_READ, RECDB_QSTRING)))
526 memoserv_saxdb_read_accounts(struct dict *db)
528 struct memo_account *ma;
529 struct handle_info *hi;
530 struct record_data *rd;
534 for (it = dict_first(db); it; it = iter_next(it)) {
535 hi = get_handle_info(iter_key(it));
537 log_module(MS_LOG, LOG_WARNING, "No account known for %s.", iter_key(it));
541 ma = memoserv_get_account(hi);
543 log_module(MS_LOG, LOG_WARNING, "Unable to allocate memory for account %s.", iter_key(it));
548 if (rd->type == RECDB_QSTRING) {
551 log_module(MS_LOG, LOG_WARNING, "Unexpected rectype %d for accounts/%s.", rd->type, iter_key(it));
556 ma->flags = strtol(str, NULL, 0);
561 memoserv_saxdb_read(struct dict *db)
565 obj = database_get_data(db, KEY_ACCOUNTS, RECDB_OBJECT);
567 return memoserv_saxdb_read_messages(db);
569 memoserv_saxdb_read_accounts(obj);
570 obj = database_get_data(db, KEY_MESSAGES, RECDB_OBJECT);
571 return memoserv_saxdb_read_messages(obj);
576 memoserv_saxdb_write(struct saxdb_context *ctx)
579 struct memo_account *ma;
582 unsigned int id = 0, ii;
584 saxdb_start_record(ctx, "accounts", 1);
585 for (it = dict_first(memos); it; it = iter_next(it)) {
587 saxdb_write_int(ctx, ma->handle->handle, ma->flags);
589 saxdb_end_record(ctx);
591 saxdb_start_record(ctx, "messages", 1);
592 for (it = dict_first(memos); it; it = iter_next(it)) {
594 for (ii = 0; ii < ma->recvd.used; ++ii) {
595 memo = ma->recvd.list[ii];
596 snprintf(str, sizeof(str), "%x", id++);
597 saxdb_start_record(ctx, str, 0);
598 saxdb_write_int(ctx, KEY_SENT, memo->sent);
599 saxdb_write_string(ctx, KEY_RECIPIENT, memo->recipient->handle->handle);
600 saxdb_write_string(ctx, KEY_FROM, memo->sender->handle->handle);
601 saxdb_write_string(ctx, KEY_MESSAGE, memo->message);
603 saxdb_write_int(ctx, KEY_READ, 1);
604 saxdb_end_record(ctx);
607 saxdb_end_record(ctx);
613 memoserv_cleanup(void)
619 memoserv_check_messages(struct userNode *user, UNUSED_ARG(struct handle_info *old_handle))
621 unsigned int ii, unseen;
622 struct memo_account *ma;
625 if (!(ma = memoserv_get_account(user->handle_info))
626 || !(ma->flags & MEMO_NOTIFY_LOGIN))
628 for (ii = unseen = 0; ii < ma->recvd.used; ++ii) {
629 memo = ma->recvd.list[ii];
633 if (ma->recvd.used && memoserv_conf.bot)
634 send_message(user, memoserv_conf.bot, "MSMSG_MEMOS_INBOX", unseen, ma->recvd.used - unseen);
638 memoserv_rename_account(struct handle_info *hi, const char *old_handle)
640 struct memo_account *ma;
641 if (!(ma = dict_find(memos, old_handle, NULL)))
643 dict_remove2(memos, old_handle, 1);
644 dict_insert(memos, hi->handle, ma);
648 memoserv_unreg_account(UNUSED_ARG(struct userNode *user), struct handle_info *handle)
650 dict_remove(memos, handle->handle);
656 MS_LOG = log_register_type("MemoServ", "file:memoserv.log");
658 dict_set_free_data(memos, delete_memo_account);
659 reg_auth_func(memoserv_check_messages);
660 reg_handle_rename_func(memoserv_rename_account);
661 reg_unreg_func(memoserv_unreg_account);
662 conf_register_reload(memoserv_conf_read);
663 reg_exit_func(memoserv_cleanup);
664 saxdb_register("MemoServ", memoserv_saxdb_read, memoserv_saxdb_write);
666 memoserv_module = module_register("MemoServ", MS_LOG, "mod-memoserv.help", NULL);
667 modcmd_register(memoserv_module, "send", cmd_send, 3, MODCMD_REQUIRE_AUTHED, NULL);
668 modcmd_register(memoserv_module, "list", cmd_list, 1, MODCMD_REQUIRE_AUTHED, NULL);
669 modcmd_register(memoserv_module, "read", cmd_read, 2, MODCMD_REQUIRE_AUTHED, NULL);
670 modcmd_register(memoserv_module, "delete", cmd_delete, 2, MODCMD_REQUIRE_AUTHED, NULL);
671 modcmd_register(memoserv_module, "expire", cmd_expire, 1, MODCMD_REQUIRE_AUTHED, "flags", "+oper", NULL);
672 modcmd_register(memoserv_module, "expiry", cmd_expiry, 1, 0, NULL);
673 modcmd_register(memoserv_module, "status", cmd_status, 1, 0, NULL);
674 modcmd_register(memoserv_module, "set notify", cmd_set_notify, 1, 0, NULL);
675 modcmd_register(memoserv_module, "set authnotify", cmd_set_authnotify, 1, 0, NULL);
676 modcmd_register(memoserv_module, "set private", cmd_set_private, 1, 0, NULL);
677 message_register_table(msgtab);
679 if (memoserv_conf.message_expiry)
680 timeq_add(now + memoserv_conf.message_expiry, expire_memos, NULL);
685 memoserv_finalize(void) {
689 str = "modules/memoserv";
690 if (!(conf_node = conf_get_data(str, RECDB_OBJECT))) {
691 log_module(MS_LOG, LOG_ERROR, "config node `%s' is missing or has wrong type.", str);
695 str = database_get_data(conf_node, "bot", RECDB_QSTRING);
697 memoserv_conf.bot = GetUserH(str);